プロシージャー主導型RPGによってチームの生産性を向上させる
プロシージャー主導型RPGには大きなメリットがたくさんあり、それらの多くについて、以前から記事で取り上げてきました。この記事では、皆さんのショップでプロシージャー主導型RPGを実装することによって得られるだろうと私が考える最大のメリットを紹介しようと思います。すなわち、適切に書かれて実装されたプロシージャーは再利用できるということです。
あるタスクを巧みに処理する新しいプロシージャーを書けたときには、そうした成果を誇りに思えるはずです。しかし、そのようなプロシージャーも、自分のプログラム内にしまったままになっているのではないでしょうか。チーム全体で利用できるように、それをサービス プログラムにエクスポートするべきです。ところが、ふと考えると、「確かにそうだけど、このショップには、このプロシージャーを必要としている人は他に誰もいないのではないか」と思ってしまいがちです。その通りなのかもしれません。しかし、このメリットを活用する鍵は、まずは1つのタスクに焦点を合わせたプロシージャーを書くことから始めて、サービス プログラムからそれらすべてをエクスポートすることです。それらすべてです。
では、プログラム内のプロシージャーを取り上げて、サービス プログラムにそれをエクスポートする方法について見てみましょう。ここでは、曜日(1、2、3などの曜日番号)を取得するサンプル プログラムを書いています。
**Free
Ctl-Opt Main(Process_Rebate_Checks);
Ctl-Opt ActGrp(*Caller);
Dcl-Proc Process_Rebate_Checks;
Dcl-s nDayOfWeek Uns(3);
// Processing Rebate Check logic...
nDayOfWeek = get_day_of_week(%Date());
// Remaining Rebate Check Logic...
On-Exit;
End-Proc Process_Rebate_Checks;
Dcl-Proc get_day_of_week;
Dcl-Pi get_day_of_week Uns(3);
InputDate Date Const;
End-Pi;
Dcl-s result Uns(3);
Test(e) InputDate;
If %Error;
Return -1;
Endif;
Exec SQL
Set :result = DayOfWeek(:InputDate);
If SQLState <> SQL_Success;
Return -1;
EndIf;
Return result;
On-Exit;
End-Proc get_day_of_week;
上の例では、何曜日なのかを知る必要があったので、get_day_of_weekプロシージャーでそれを実現しています。かなり単純です。渡された日付が有効な日付であることを確認してから、SQL関数のDayOfWeekを使用して、曜日番号を取得して答えを返します。
では、このプロシージャーを dateutilsという新たなサービス プログラムへ移動してみましょう。
まず、プロトタイプを格納する新たなコピーブックを作成します。これが鍵となります。それにより、このプロシージャーを使用したい人であれば誰でも、/copyコンパイラー ディレクティブを使用して、自分のプログラムにプロシージャー インターフェースをすべて取り込むことができるようになるからです。プロシージャー タイプをコピーブックに置くことのもうひとつのメリットは、プロシージャー プロトタイプが変更された場合に、プログラムを再コンパイルしないで済むかもしれないことです。たとえば、誰かがOptions(*Nopass)を指定してパラメーターを追加した場合、再コンパイルは必要でないでしょう。そして、 dateutilscというコピーブックは、次のようになります。
**Free
Dcl-Pi dateutils_get_day_of_week Uns(3);
InputDate Date Const;
End-Pi;
次に、後でモジュールにコンパイルされる新たなソース メンバーを作成します。
**Free
Ctl-Opt NoMain;
/copy dateutilsc
Dcl-Proc dateutils_get_day_of_week;
Dcl-Pi dateutils_get_day_of_week Uns(3);
InputDate Date Const;
End-Pi;
Dcl-s result Uns(3);
Test(e) InputDate;
If %Error;
Return -1;
Endif;
Exec SQL
Set :result = DayOfWeek(:InputDate);
If SQLState <> SQL_Success;
Return -1;
EndIf;
Return result;
On-Exit;
End-Proc dateutils_get_day_of_week;
この場合もやはり、コピーブック dateutilscの/copyは、プロシージャーのプロトタイプを取り込みます。デフォルトでは、RPGコンパイラーは、ライブラリー リストのQRPGLESRCでそのコピーブックを検索します。以前に勤務したショップでは、コピーブックをQCPYSRCに保管していました。どちらでも問題ありませんが、コピーブックをQRPGLESRC以外のソース物理ファイルまたは別のライブラリーに保存することを選んだ場合は、以下のいずれかの形式で/copyを指定する必要があることは覚えておくようにしてください。
/copy libraryname/filename,membername
/copy filename,membername
/copy membername
プロシージャーのそれ以外の部分で唯一変わっているのは(そうするかしないかはまったく自由に選ぶべきことですが)、サービス プログラム内のすべてのプロシージャーの名前を、サービス プログラム名から始まるように命名するようにしているという点です。そのようにしておくことで、サービス プログラム内のプロシージャーを呼び出すコードを判読するときに、そのプロシージャーがどのサービス プログラムにあるかすぐに分かりやすくなります。
次に、必須ではありませんが、バインダー ソースを作成するのが望ましいと思われます。これは、どのプロシージャーまたは「シンボル」をエクスポートするかをコンパイラーに指示するだけです。ここでは、以下のように、QBNDSRCにdateutils.bndソースを作成しました。
STRPGMEXP PGMLVL(*CURRENT) LVLCHK(*NO)
EXPORT SYMBOL(dateutils_get_day_of_week)
ENDPGMEXP
ここでは、バインダー言語について非常に簡単に触れたに過ぎません。バインダー言語の詳細については、こちら(https://www.ibm.com/docs/ja/i/7.5?topic=concepts-binder-language)を参照してください。
それでは、モジュールをコンパイルしましょう。次いで、サービス プログラムを作成します。
さて、このプロシージャーを抜き出した元のプログラムは、次のようになっています。
**Free
Ctl-Opt Main(Process_Rebate_Checks);
Ctl-Opt ActGrp(*Caller);
Ctl-Opt Bnddir(Utils);
/copy dateutilsc
Dcl-Proc Process_Rebate_Checks;
Dcl-s nDayOfWeek Uns(3);
// Processing Rebate Check logic...
nDayOfWeek = dateutils_get_day_of_week(%Date());
// Remaining Rebate Check Logic...
On-Exit;
End-Proc Process_Rebate_Checks;
ここで注目するべき変わった点として、最上部でCtl-Optを追加して、コンパイラーが、呼び出すプロシージャーをUtilsバインディング ディレクトリーでチェックできるようになっています。次に、 dateutilscの/copyコンパイラー ディレクティブを追加し、プロシージャー名をget_day_of_weekからdateutils_get_day_of_weekに変更して、そのプロシージャーを削除しています。これは、すべてが利用するサービス プログラム内で存続しているためです。
次のステップは、必須というわけではありませんが、特にプログラムが複数のサービス プログラム内のプロシージャーを呼び出すことが必要になったときには、有用なコーディング手法と言えるでしょう。一言で言えば、プログラムが参照しているプロシージャーをコンパイラーが探し出すことができるサービス プログラムまたはモジュールのリストを提供するだけです。バインディング ディレクトリーを使用したくない場合は、コンパイル コマンドでどのサービス プログラムを参照するか指定することが必要になります。そうするくらいなら、バインディング ディレクトリーの方が楽だとすぐに分かると思います。バインディング ディレクトリーの作成は、次のように、2つの手順だけで非常に簡単に設定できます。
バインディング ディレクトリーを作成 | CRTBNDDIR BNDDIR(MYLIB/UTILS) TEXT('Binding directory for Utility Service Programs') |
新規作成したバインディング ディレクトリーにサービス プログラムを追加 | ADDBNDDIRE BNDDIR(MYLIB/UTILS) OBJ((MYLIB/DATEUTILS *SRVPGM *DEFER)) |
ここでもやはり、バインディング ディレクトリーについては、非常に簡単に触れたに過ぎません。あまり馴染みがないという場合は、さらに詳しく調べてみることを強くお勧めします。
これで、スリムになった新しいプログラムをコンパイルする準備ができました。ところで、正確に言えば、コンパイルされたオブジェクト自体は小さくなっていませんが、ソース メンバーは小さくなっています。