ServiceNowのScript Include開発のTips

開発・導入

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

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

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

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

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

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

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

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

  • currentがインシデントレコードではないコンテキストから利用できない
  • この処理のテストを行う際には、テストで使うインシデントレコードがcurrentになるように呼び出すテストをしなければならない(が、面倒)

次のように、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()で現在時刻を取得してしまっていることがあります

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を作る際は、引数と戻り値の受け渡し方に次の制約があることを確認しておきます。

  • 引数の受け渡しは、一般的な関数の引数の形ではなく、クライアント側でaddParam()、Scirpt Include側でgetParameter()関数を呼び出して行う
  • Script Includeから戻り値として受け渡せるのは文字列のみ(オブジェクトを受け渡す場合はJSON文字列で受け渡す)

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

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

アンチパターンとして、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);
},

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

  • 同じ処理をサーバーサイドスクリプトでも使いたい場合、引数や戻り値の受け渡し方法が異なるため、クライアント用とサーバ用で別の関数を用意する必要がある
  • 複数のパラメータでテストを行う場合に、クライアントサイドで呼び出し用のテスト用Client Script等を用意し、都度パラメータの変更、保存、実行が必要になる

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

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)は呼び出し側の任意のクライアントサイドスクリプトのテストでカバーされると考え実施しない(あまりないと思いますが、その時点で具体的な呼び出し側のクライアントサイドスクリプトを開発する想定がない場合は、テスト用のクライアントサイドスクリプトを作成)

以上です。

コメント

タイトルとURLをコピーしました