ServiceNow研究所
開発・導入

ServiceNowのScript Include開発のTips

2023/3/4 公開

Script Includeはサーバーサイドの処理を共通化するための機能ですが、Business RuleやACLで書いていたコードが他の個所でも利用することが分かったとき、そのままScript Includeにコピペしただけでは十分に再利用性を高められなかったり、テストが困難になってしまう場合があります。

この記事では、Script Includeにおいて再利用性とテスト容易性を高めるためのTipsを、アンチパターンと共に紹介します。

Tips: Script Includeの中でコンテキスト依存の値を取得せず、引数として受け取る

Script Includeの関数中では、コンテキスト(呼び出し元や呼び出される状況)に依存する値は取得せず、コンテキスト依存の情報は引数として受け取るようにします。

アンチパターン: current変数を参照する

アンチパターンとして、Scirpt Includeの関数内でcurrent変数を参照してしまっていることがあります。

アンチパターン: current変数を参照する

func: function() {
  var incident_sys_id = current.sys_id; // 現在のインシデントレコードのSysIDを取得
  // incident_sys_id を用いた処理
},

この例では、インシデントテーブルに関するBusiness Rule(ビジネスルール)などから呼び出されることを前提として、current.sys_idを参照していますが、このコードには次の問題があります。

次のように、SysIDを引数で受け取るように変更することでこれらの問題を解消できます。

よい例: コンテキスト依存の情報は引数でもらう

func: function(incident_sys_id) {
  // incident_sys_id を用いた処理
},

その上で、SysIDの取得は呼び出し側で行うようにします。

呼び出し側コード例

var si = new <Script IncludeのAPI名>();
si.func(current.sys_id);

こうしておくと、例えばProblem(問題)テーブルのBusiness Ruleから次のようにして呼び出すこともできます。

呼び出し側コード例

var si = new <Script IncludeのAPI名>();
si.func(current.incident.sys_id);

アンチパターン: new GlideDateTime()で現在時刻を取得

もう一つのアンチパターンとして、時刻を使った処理をする場合にScirpt Includeの関数内でnew GlideDateTime()やgs.nowDateTime()で現在時刻を取得してしまっていることがあります。

アンチパターン: new GlideDateTime()で現在時刻を取得

func: function() {
  var now = new GlideDateTime(); // 現在時刻(UTC)を取得
  // now を用いた処理
},

この処理もcurrentと同様にコンテキストに依存しており、再利用性やテスト容易性を下げています。特にテストについては、現在時刻の取得とその時刻を使った処理を同じ関数に入れてしまうと、特定の時刻におけるその関数の挙動をテストするためには実際にその時刻にテストを行わなければならなくなります。

この場合も時刻を引数として取得することで問題を解消できます。

よい例: コンテキスト依存の情報は引数でもらう

func: function(now_gdt) {
  // now_gdt を用いた処理
},

なお、時刻を用いた処理部分さえ分離できていれば、現在時刻を取得した上でその処理を呼び出すという最小限の機能を持った関数をScript Includeに定義することは問題ありません。この作りにしておけば、時刻を用いた処理部分をきちんとテストし、呼び出し側の関数は特定の時刻で一度テストすればよいでしょう。

許容できる例: メイン処理は分離した上で、コンテキスト依存部分を最小限にする

func: function(now_gdt) {
  // now_gdt を用いた処理
},

callerFunc: function() {
  var now_gdt = new GlideDateTime();
  this.func(now_gdt);
},

Tips: クライアントサイドから呼び出す関数は引数の取得、メイン処理の呼び出し、戻り値の返却のみを行うようにする(ラッパー関数に限定する)

こちらはクライアントサイドスクリプトからGlideAjaxを経由して呼び出されるClient callable Script Includeを作成する場合のTipsです。

前提 Client callable Script Includeは引数と戻り値の受け渡し方に制約がある

アンチパターンの前に、まずClient callable Script Includeを作る際は、引数と戻り値の受け渡し方に次の制約があることを確認しておきます。

詳しくは下記の記事を参照ください。

ServiceNowのGlideAjaxとは何か - クライアントサイドからScript Includeを呼び出すための仕組み

ServiceNowのScript Includeとは何か

アンチパターン: クライアントサイドから呼び出す関数内でそのままメイン処理を行う

アンチパターンとして、Client callable Script Includeの関数の中でgetParameter()で引数を受け取り、同じ関数の中でそのままメインの処理を行ってしまっていることがあります。

アンチパターン: クライアントサイドから呼び出す関数内でそのままメイン処理を行う

func: function(/* この中には引数を定義しない */) {
  var arg1 = this.getParameter('sysparm_arg1'); // 引数"sysparm_arg1"を取得
  var arg2 = this.getParameter('sysparm_arg2'); // 引数"sysparm_arg2"を取得
  //
  // arg1, arg2を使った処理 処理結果がresult変数に入っているとする
  //
  return JSON.stringify(result);
},

このようなコードを書いてしまうと、上述の制約に起因した次の問題があります。

これらの問題を解消するために、メイン処理を行う関数を分離した上で、クライアントサイドから呼び出す関数には、1.引数の取得、2.処理を行う関数の呼び出し、3.結果のJSON文字列化(必要な場合のみ)と返却、の3つの役割だけを持たせるようにします。別の言い方をすると、クライアントサイドから呼び出してよいのはラッパー関数に限定します。

Client callable Script Includeのサンプルコード

wokerFunc: function(arg1, arg2) { // メイン処理を行う関数
  //
  // arg1, arg2を使った処理 処理結果がresult変数に入っているとする
  //
  return result
},

callerFunc: function() {
  // 1.引数の取得
  var arg1 = this.getParameter('sysparm_arg1'); // 引数"sysparm_arg1"を取得
  var arg2 = this.getParameter('sysparm_arg2'); // 引数"sysparm_arg2"を取得

  // 2.処理を行う関数(workerFunc)の呼び出し
  var result = this.workerFunc(arg1, arg2);

  // 3.結果のJSON文字列化(必要な場合のみ)と返却
  return JSON.stringify(result);
}, 

メイン処理をサーバーサイドで完結する関数に分離したことで、サーバーサイドからもクライアントサイドからも利用できる関数になり、任意のパラメータでテストしやすくなりました(System Definition > Scripts – Backgroundメニューからぱっと試したり、ATFによる自動テストが使えるようになった)。

このように分離した場合、テスト方針は次のようにしています。
・処理を行う関数(上述のworkerFunc)を処理内容に応じてテスト
・ラッパー関数(上述のcallerFunc)は呼び出し側の任意のクライアントサイドスクリプトのテストでカバーされると考え実施しない(あまりないと思いますが、その時点で具体的な呼び出し側のクライアントサイドスクリプトを開発する想定がない場合は、テスト用のクライアントサイドスクリプトを作成)

以上です。