前回の復習
前回は、保守しやすいシステムを構築する手段として利用可能なサービス・プログラムを、FFRPG で作成する方法を解説しました。金額を引数で受け取り、税込価格を戻り値として戻す簡単な関数の作成を通して、サービス・プログラムの基本はおおよそ理解していただけたと思います。複数のプログラムで共通で使用するロジックがあれば、サービス・プログラムにユーザー関数としてまとめておくことにより、プログラム作成者はその関数を各プログラムから呼び出すだけで、ロジックを知らなくても結果を取得することができるようになります。また、引数や戻り値が変わらないのであれば、サービス・プログラムの更新だけで、それを使用するプログラムは一切変更せずにロジックを修正することができることも解説しました。保守しやすいシステム実現にサービス・プログラムが有効であることを理解していただけたと思います。
今回は、このサービス・プログラムをさらに詳細に解説していきます。修正の際の考慮点、プロトタイプ定義の共通化などを取り上げます。それでははじめましょう!
サービス・プログラムの修正
前回、税込価格を計算する関数 getIncludeTax を税率 8% で作成し、それを呼び出すプログラム icafe023 を作成しました。その後、税率を 10% に変更してサービス・プログラムを再作成しましたが、icafe023 はなんら変更することなく実行することができましたね。これは、icafe023 とこの関数との接点である引数と戻り値を何も変更していないからです。内部ロジックを見えないようにカプセル化し、外部との接点を最小限にしておく、この疎結合の考え方がお互いを独立させ保守の容易性を飛躍的に高めているのです。
ただし、税率 8% のロジックを持つ関数を icafe023 から一度実行し、その後 10% でサービス・プログラムを更新して再度 icafe023 を実行しても、結果は変わらず 8% で計算されていました。変更した税率を有効にするにはサインオンし直して、再度プログラムを実行しなければなりませんでしたね。どうしてサービス・プログラムの更新がすぐに有効にならないのでしょうか?
この理由を明らかにするには、バインディング処理の理解が必要です。
「
第三回 – プログラムの基本構造」で解説した通り、サブ・プロシージャーは静的呼び出しで実行されます。ユーザー関数もサブ・プロシージャーなので、呼び出しは静的呼び出しです。この呼出しは各関数のアドレスを使用して行われるのですが、この一連のアドレス解決をバインディング処理といいます。実際のバインディング処理は以下の通りです。
- プログラムを実行(活動化)
- そのプログラムで使用するサービス・プログラムが活動化(メモリーにロード)
- 内部の各関数のアドレス解決を実施
関数を呼び出すプログラムは、それぞれの関数をこのアドレスを使って呼び出します。CALL コマンドによる動的呼び出しと比較して、アドレスによる呼び出しは高速に実行可能です。動的呼び出しは、毎回オブジェクトの検索(ライブラリーを特定するか、ライブラリー・リストの先頭からオブジェクトを検索)をするため、呼び出しごとの負荷は避けられません。
前回作成した icafe023 プログラムは dftactgrp(*no) を指定しており、その結果 QILE という名前の活動化グループ内で実行されます。サービス・プログラムがメモリーにロードされるタイミングは、先ほど解説した通りそれを呼び出すプログラムが活動化されるときです。今回の例で言えば、icafe023 が実行されたときです。サービス・プログラムの活動化は1つの活動化グループ内で1回だけです。名前付きの活動化グループは、明示的に削除しない限り、プログラムが終了しても存在しており、同じプログラムが実行されると再利用されます。つまり、最初に実行した時にアドレス解決したサービス・プログラム内の関数(メモリー上にロードされているもの)は、その活動化グループが再利用される限りずっと使われることになります。
ではすでにプログラムから使用されるためにメモリーにロードされたサービス・プログラムを更新するとどうなるのでしょうか。ここで、サービス・プログラムを更新するコマンド UPDSRVPGM のヘルプを見てみましょう。
「サービス・プログラムがこのコマンドによって置き換えられている間であっても、結合サービス・プログラムを実行中の他のジョブは実行することができます。現在実行中のサービス・プログラムはライブラリーQRPLOBJに移動され、サービス・プログラムの更新済みバージョンがサービス・プログラムのライブラリーに挿入されます。サービス・プログラムの現在の活動化は、QRPLOBJライブラリー内のサービス・プログラムのバージョンを使用して実行し続けます。」
つまり、*SRVPGM オブジェクトは更新できるが、実行中のプログラムは前のバージョンが使用されるということです。これが前回サービス・プログラムを修正して税率を変更したのに、その修正が変更後のプログラム実行に反映されなかった理由です。サービス・プログラムを修正し、かつそのサービス・プログラムが活動化されていたら、該当の活動化グループを再作成する必要があるのですね。
名前付きの活動化グループを削除する方法は以下の2通りです。
- ジョブを終了する(対話型の場合はサインオフ)
- RCLACTGRP コマンドを使用する
今後サービス・プログラムの修正をしたが、変更が反映されないケースの場合は、サインオンし直すか、以下コマンドを実行してください。
RCLACTGRP ACTGRP(QILE)
プロトタイプ定義の共通化
icafe023 は、icafesrv サービス・プログラム内に定義された getIncludeTax を呼び出すために、プロトタイプ定義が必要でした。これは、コンパイラーにプログラム内で呼び出す関数名、引数の数とそれぞれの属性、戻り値の型を知らせる目的で記述します。コンパイラーは、実際に関数呼び出しのコーディングがプロトタイプ定義と一致しているかをチェックし、一致していなければ *MODULE オブジェクトを作成しません。
プログラム作成には、CRTPGM コマンドの実行が必要ですが、このステップではコマンドで指定(BNDSRVPGM パラメータ)したサービス・プログラム内に呼び出す関数が存在しているかのチェックは行いますが、引数や戻り値についてはチェックされません。これを記号解決(名前解決)といいます。
- モジュールのコンパイル時
- プロトタイプ定義通りにその関数が呼び出されるようコーディングされているかチェック
- プログラム作成時
- 使用している関数名がサービス・プログラム内に存在しているかチェック
それぞれのステップで何が行われているかを理解することは重要です。プロトタイプ定義と、サービス・プログラムに定義されている実際の関数の引数や戻り値は異なっていても、名前さえ一致していればプログラムは作成できるのです。もちろんこの場合、作成されたプログラムを実行すると、実行時エラーとなってしまうのですが。
また、このプロトタイプ定義はプログラマーにとってはかなりの負担になります。実際のサービス・プログラムの定義通りにプロトタイプ定義をコーディングしておかなければ実行時エラーの原因になるからです。しかも通常は複数の関数を使用することになるため、プログラムごとのプロトタイプ定義は相当のステップを占めることにもなります。このプロトタイプ定義のコーディングの煩雑さと正しさを保証する難しさは、RPG からサービス・プログラムを利用する際の大きな障壁になっているように思います。この問題はどのように解決していけば良いのでしょうか。
まず、コーディングの煩雑さに関しては定義を一箇所にまとめ、それを各プログラムがコピーすることで解決します。定義はサービス・プログラムの作成者が行うべきです。作成者ですから関数名はもちろん、引数や戻り値の情報を熟知しているからです。そこで通常は以下の手順でこの問題を解決します。
- サービス・プログラム作成者が、全関数のプロトタイプ定義だけをまとめたソースを作成する
- 関数を使用するプログラムの作成者が、各モジュールのコーディング上で上記のソースを /copy や /include で記述する
こうすることにより、プログラマーはサービス・プログラムの作成者が記述した個別のプロトタイプ定義を、意識することなくコンパイル時にソースに組み込むことが可能になり、個別にコーディングする負荷がなくなります。それでは、プロトタイプ定義のみを含んだ prototype.rpgle を Orion で作成しましょう。
これは、プログラム・ソースにコンパイル時にコピーするものなので、単独でコンパイルはしません。それでは、icafe023 を以下のように修正して、prototype.rpgle を組み込むように変更しましょう。
/include を記述すると、その箇所に指定したファイル(./prototype.rpgle)がコンパイル時に挿入されます。ファイル名の前の「./」はカレントディレクトリを意味します。この共通ファイルをどこに置くかでこの記述方法を変更しなければならない点については注意しておきましょう。
それではプログラムを再作成します。前回の記事を参考に icafe023 を再作成しましょう。
- モジュール icafe023 をメニューのオプション 12 で作成
- プログラム icafe023 をメニューのオプション 14 で作成(サービス・プログラム icafesrv を指定)
問題なくモジュールのコンパイルができたと思います。もしエラーになった場合は、prototype.rpgle の記述の確認(**free が記述されているかなど)と、/include の記述を再確認しましょう。
次にプロトタイプ定義の正しさを保証する点について考えましょう。実は、プロトタイプ定義における関数名、引数や戻り値のコンパイル時のチェックは、関数を呼び出す側のプログラムだけでなく、ユーザー関数を含むモジュールのコンパイル時にも本来は必要なものです。しかし、コンパイル時にプロトタイプ定義がなければ、dcl-pi 定義から内部的にプロトタイプ定義を生成したのちコンパイルを行うので、icafesrv.rpgle にプロトタイプ定義がなくてもコンパイルはできていたのです。
プロトタイプ定義をまとめた prototype.rpgle を作成したので、サービス・プログラムを構成するモジュールにもこれを組み込むようにすれば、プロトタイプ定義と実際の関数の定義の乖離を極力防ぐことができるようになります。手順は以下の通りです。
- ユーザー関数を設計する(名前、引数の数と個々の属性および戻り値の決定)
- プロトタイプ定義を専用ファイル(今回の例ではrpgle)に記述する
- rpgle を /include で組み込んでユーザー関数を含むモジュールをコンパイルする
・この時点で実際の関数とプロトタイプ定義が一致する
- ユーザー関数を含むモジュールでサービス・プログラムを作成する
- 各プログラムにrpgle を /include で組み込んでモジュールおよびプログラムを作成する
・プロトタイプ定義は実際の関数と一致しているので実行時のエラーを防げる
因みに、そのモジュール内で使われていない関数に対するプロトタイプ定義が存在してもコンパイルには何ら影響しませんので、プロトタイプ定義用ファイルにはすべてのプロトタイプ定義を含めるようにしましょう。
バインド・ディレクトリ(*BNDDIR)
連載第八回までは、プログラムの作成は CRTBNDRPG コマンド(メニューのオプション 1)を使用していました。しかし、サービス・プログラムを使用する icafe023 は、CRTRPGMOD でモジュールを作成し、その後 CRTPGM を使用して作成しました。これは、使用するサービス・プログラムを指定するパラメータが CRTPGM にしかないためです。ILE 言語の本来の作成手順は後者であることを今一度認識しておいてください。
一方で、先ほどのプロトタイプ定義の問題と同様のことが CRTPGM でも発生します。それは、プログラムを作成するプログラマーが、使用するサービス・プログラムがどのライブラリーにあるのか、名前が何かなどの情報を知らないと作成できないということです。プログラマーは使用したいユーザー関数情報は知っていますが(知らなければコーディングできない)、その関数がどのサービス・プログラムにあるのかまでは知らないかもしれません。この問題を解決するために利用可能なのがバインド・ディレクトリです。
CRTPGM 実行時に行われるのは名前解決でしたね。そのプログラムに含む予定のモジュール内で使用されている関数が BNDSRVPGM に指定したサービス・プログラムで EXPORT されているかをチェックすることで実現します。この名前解決に使用するサービス・プログラムを予めリスト化しておき、プログラム作成時に利用するために用意されているのがバインド・ディレクトリというオブジェクト(*BNDDIR)です。関連コマンドは以下の通りです。
- CRTBNDDIR(バインド・ディレクトリの作成)
- ADDBNDDIRE(バインド・ディレクトリ項目の追加)
- DSPBNDDIR(バインド・ディレクトリの表示)
今回は以下のコマンドを使用してすでに皆さんのライブラリーに作成済です。
- CRTBNDDIR BNDDIR(*CURLIB/ICAFEBNDDR)
- ADDBNDDIRE BNDDIR(*CURLIB/ICAFEBNDDR)
OBJ((*LIBL/ICAFESRV *SRVPGM))
DSPBNDDIR BNDDIR(*CURLIB/ICAFEBNDDR) を実行すると、以下の画面が表示されます。
バインド・ディレクトリは、CRTPGM コマンド(メニューのオプション 14)の BNDDIR で指定します。F9 コマンドを押してすべてのパラメータを表示し、次ページを表示すると以下の画面が表示されます。
バインド・ディレクトリを指定すればサービス・プログラム名を個別に指定する必要はありません。名前解決はバインド・ディレクトリに登録されているサービス・プログラムを順番に検索することで実施します。オブジェクトを探索するのにライブラリー・リストを使用するのと似ています。バインド・ディレクトリはサービス・プログラム作成者が保守し、プログラマーはバインド・ディレクトリ名だけを知っていいれば良いことになりますね。
BNDDIR パラメータは、CRTBNDRPG コマンドにも存在します。つまり、icafe023 を CRTBNDRPG コマンドで作成することもできるわけです。
メニューのオプション 1 で F9 キーを押すと表示される BNDDIR パラメータに ICAFEBNDDR を指定するか、icafe023.rpgle 内の制御ステートメントで以下の記述を追加すれば、プログラムの作成が可能です。実際に icafe023.rpgle を修正してメニューのオプション 1 で作成できることを確認しましょう。
上記 bnddir 内のバインド・ディレクトリ名はライブラリー名の修飾も可能なので引用符で囲む必要があります。大文字小文字を区別しますので、ここでは必ず大文字で定義するようにしてください。
終わりに
前回に引き続き、サービス・プログラムを解説しましたがいかがだったでしょうか。今回は解説が多く、プログラムの新規作成はなかったので分かりづらかったかもしれません。しかし、実際の基幹システムでサービス・プログラムを活用する場合は、今回の解説内容は最低限理解しておかなければならないことだと思っています。何度も読み返してしっかり理解するようにしてください。
サービス・プログラムについては、まだ解説しなければならないことがあります。
- 引数の様々な種類と渡し方
- 業務で利用開始されたサービス・プログラムを変更する場合の考慮点
次回は上記の解説と、日付から曜日を算出するユーザー関数を作成する予定です。お楽しみに!