パターン認識:モダンRPGプログラミングを容易にする
プログラムを書く際、皆さんが今まで使用してきた基本的な構造について思い出してみてください。そして、ILE RPGではそれとどう異なるアプローチを取ればよいのか見つけてください。
おそらく皆さんはILE RPGの研修を受講したり関連する書籍や記事を読んだりしたことがおありでしょう。プロシージャやサービス・プログラム、束縛ディレクトリ、アクティベーション・グループなどが何であるかも学んだことでしょう。こうしたツールの準備ができ、机に向かって最初のILEアプリケーションを書くことになったのに、何も上手く組み合わせることができない・・・。
「これでは複雑すぎる!」とイライラして叫ぶことになるでしょう。「ファイルへのアクセスはどうやってやるのだろう? ルーチンが別の場所で使用されるときのエラー処理はどうしたらよいのだろう? プログラムをプロシージャに分割するのはどんな場合だろう?」などと・・・。
これが自分の話のように聞こえたとしたら、同じような混乱をしているのはあなただけではないとわかれば、少しは安心するでしょう。ILEは、その概念を理解しただけでは不十分で、もう少し学ばなければならないことがあります。新しいデザイン・パターンも学ばなければなりません。実感されているかどうかわかりませんが、プログラマとしての経験が長いのであれば、皆さんは自分独自のデザイン・パターンを作り上げているのです。こうしたデザイン・パターンを使用したプログラミングが得意なのはそれを長年使ってきたからです。あるプログラミング・パラダイムから別のプログラミング・パラダイムに切り替えようとするときは、必ず新しいパターンを学ばなければなりません。
ILEで強力な新しいデザイン・パターンを築き上げ、そのパターンを使用したプログラミングに熟達し、再利用性と保守性の高さを活用することで、ILEプログラムは従来のプログラミング手法と比較して会社(またはお客様)に対して、より価値のあるものになります。
私が学んだパターン
そもそもパターンについての本を読んだりコンピューターの設計理論の記事を読んだりすると、形式パターンや正しいオブジェクト指向プログラミングの設計などに関するさまざまな用語に出くわすでしょう。しかし本稿はそのようなものとは異なり、皆さんがプログラムを書く際に今まで使用してきた基本的な構造について考えていただき、その上でILE RPG環境では、それとは異なるアプローチをどのように取ったらよいのかを考えていただきたいのです。
たとえばRPG/400の時代には、私は対話型アプリケーションをたくさん書いていましたが、しばらくするとユーザーに対して画面を表示するときに、いつも同じステップを使っていることに気づきました。すなわち、画面上に表示するデータを読み込む。
画面を表示し、ユーザーからの入力を待つ。
(もしエラー・メッセージが画面に表示されていれば)エラー・メッセージをクリアする。 コマンド・キーが押されていないかをチェックする。
ユーザーが入力したデータが正しいかどうかチェックする。
データが正しくなければ上記ステップ2に戻る。
ユーザーからの入力に基づいて処理を実行する。
このパターンを使用して私が作ったRPG/400プログラムの古い例を図1に示しました。まずは画面を表示し(図1のA)、次にエラー・メッセージ用のフィールドをクリアし(B)、ファンクション・キーが押されていないかチェックし(C)、ユーザーの入力に誤りがないかどうかをチェックします(D)。
すべて問題なければ入力値を処理します。この例では顧客管理プログラムの一部を紹介しています。このルーチンはユーザーに顧客番号を入力するように要求し、問題がなければ2番目の画面を表示します。この2番目の画面ではユーザーが顧客のマスター・レコードのいろいろなフィールドの値を変更することができます。
2番目の画面でも同じパターンを使用しています(ここではコードを載せていませんが、SystemiNetwork.com/codeからダウンロードできます)。ここでも同じステップに従っています。つまり、画面を表示し、エラー・メッセージをクリアし、ファンクション・キーを処理し、フィールドの値が正しいことをチェックし、すべて問題がなければファイルを更新します。
どこか間違っていますか?
このパターンはコード化しやすく、また読みやすいものです。とてもわかりやすいパターンなので、初めて対話プログラムを書こうというプログラマにとっては良い例です。しかし現実世界ではそう簡単にはいかないようです。書きやすいコードなのですが保守するのが大変なのです。顧客データを読み書きする必要のあるプログラムは、何十あるいはひょっとしたら何百という単位で多くの企業に存在するでしょう。もし私が自分の顧客ファイルを変更しようとした場合、上記のように書かれたプログラムをすべて分析し、変更し、もう一度テストしなおさなければなりません。
別のプログラムから顧客データを編集したいとしたらどうなるでしょうか。おそらく発注プログラムで発注が行なわれると、編集画面が自動的に表示され、オペレータが顧客の名前と住所を確認する(そして必要に応じて変更する)ことができます。さて、このプログラムを修正して顧客番号を2番目の画面でユーザーに入力させるのではなく、パラメータとして受け取らなければならなくなったとします。 あるいは、このプログラムとは別に全く新しい「編集」プログラムを書くことになるかもしれません。そうすると、ビジネス戦略が変わった場合、私は2つのプログラムを保守し、2つのプログラムを修正しなければならなくなります。
顧客がウェブから注文できるようにしようと会社が決めたらどうなるでしょう。さらにまた別の顧客データ編集モジュールを書くことになります。
保守プログラムの他にも顧客の名前や住所を取り出す必要のあるプログラムがあります。毎回そうしたプログラムを書くのではなく、私のプログラムの中からそうした処理をする部分を再利用できれば良いと思いませんか。
もちろんあるプログラムの一部のコードをコピーして別のプログラム中にペーストすることもできますし、/COPYというコンパイラ・ディレクティブを使用して別々のプログラムを結合することもできます。しかし、結局同じコードを使用したたくさんのプログラムができてしまうことになります。コードの1箇所を変更したら他の箇所も変更しなければなりません。そうしないとビジネス・ルールの一貫性を保つことができなくなります。/COPYコンパイラ・ディレクトリの場合でも、コードを変更したらすべてテストしなおさなければなりません。さもないとプログラムが障害を発生する可能性があるからです。
十分な分析やテストをしなおさなければならないような変更をする場合には問題となります。このようなシステムは柔軟性に欠けているのです。既存のコードを壊してしまうのではと心配するあまり、ビジネスを改善または成長させるような変更ができなくなります。そしてある日突然、ビジネス・ルールに準拠したITシステムを構築するのではなく、ITシステムに準拠したビジネス・ルールを規定しなければならなくなってしまいます。
新しいパターンを学ぶとき
ILEの利点を生かすILEプログラムを試用するのであれば、古いパターンを使用しつづけるのは困難です。古いデータを使っているとILEの悪口を言い始め、ファイル・アクセスはどこで書いたら良いのか、コードをどこで分割すれば良いのか、エラー情報をどのように渡せば良いのかわからなくなってしまいます。つまり新しいパターンを見つけ出す必要があるのです。
新しいアプリケーションを書くときはモデル-ビュー-コントローラー (MVC: Model-View-Controller)戦略の採用を推奨する専門家が多いようです。このアーキテクチャでは「モデル」とはすべてのビジネス・ロジックを含んだモジュールのことを指します。「ビュー」はユーザーとのインタフェース用のモジュールです。「コントローラー」はプログラムの流れを制御し、ビジネス・データをユーザー・インタフェースに渡す、あるいはユーザー・インタフェースからビジネス・データを受け取るためのコードです。また、モデルからは独立しているモジュールである「データベース」を持つことを勧める専門家が多いようです。そうすることで必要に応じてデータの格納用に異なるメカニズムを実装することができます。
このモデルのキーとなっているのは、いずれのコンポーネントも他のコンポーネントがどのように動作しているのかについて何も知らないし、何も前提とはしていないということです。それぞれのモデルは入力が緑色の画面から来たものなのか、ウェブのページから来たものなのか、バッチ・プログラムから来たものなのかについて知る必要もないし、気にする必要もないのです。また出力もサブファイルに表示されるのか、レポートに印刷されるのか、グラフを作成するのに使用されるのか気にしてはいけないのです。
同様に、ビューはモデルがどのように動作しているのかについて知る必要もないし気にする必要もないのです。税金がどのように計算されているのか、製品の価格を会社がどのように決定しているのかを知る必要はありません。ビューの仕事はデータをユーザーに提示することなのです。ビューのことについてだけ知っていればいいのです。
コントローラーはモデルとビューのプロシージャを正しい場所で呼び出す方法さえ知っていれば良いのです。モデルとビューについて多少は知っていなければなりませんが、その詳細については知る必要もなければ、気にする必要もありません。たとえば、ビューがウェブ・ページであることは知っており、したがって入力を受け取って出力を返し、モデル・ルーチンとビュー・ルーチンを正しい順序で呼び出します。しかしその出力がHTMLなのかXMLなのかについての詳細は知る必要はありませんし、サブファイルのレコードをループ中で書かなければならないということも知る必要はありません。それはビューが対処する問題なのです。同様に、モデルが返してくる情報をモデルがどのように作成したのかについても知る必要はありません。それはモデルが対処する問題だからです。
データベース・モジュールは通常モデルだけが直接使用し、他のコンポーネントにとっては完全に見えない存在です。RPGのレコード・レベルのアクセス(RLA)からたとえばSQLへ切り替えたり、データをデータベースではなくXMLで格納したりするとしたら、別のデータベース・モジュールが役に立つかもしれません。あるいはデータを別のシステム中に格納したいとしたら、データベース・モジュールだけを変更し、それ以外のコンポーネントはこの変更に全く気づかないまま今まで通りに動作させることができます。
今までと違うこの柔軟なアプローチを採用するには、今まで使用してきた同じパターンの代わりに新しいパターンを作り出す必要があります。こうしたパターンが得意になれば新しい柔軟性の高いアプリケーションを、今までと同様に簡単に開発することができるようになります。
ビジネス・ロジック・モジュール(「モデル」)
前述のRPG/400のパターンの保守を難しくしている要因の1つは、フィールドと画面の表示を1つのループで一緒に実行している点です。画面ロジック(「ビュー」)を他のプログラムで再利用できるようにするには、ビジネス・ロジック部分から切り離す必要があります。同様に、ビジネス・ロジックを再利用するには、表示ロジック部分から切り離す必要があります。コントローラー・ロジックは画面ロジックとビジネス・ロジックの間でデータを渡します。全体のロジックは同じでも、パターンによりがらりと変わります。
新しい設計のうち、もっとも再利用できる部分はビジネス・ロジックです。私はビジネス・ロジックを書く際に次のようなパターンに従います。
ビジネス・ロジックはたくさんの個々のルーチンで構成される。
個々のルーチンにはすべて明確に定義されたパラメータ・リストがある。グローバル変数はビジネス・ロジックの外部からは決してアクセスできないようになっている。このようにすることで、パラメータ・リスト自体を変更する場合にのみ呼び出しプログラムを変更したりリセットしたりすればよい。
Init()プロシージャやDone()プロシージャはサービス・プログラムに対してそれぞれ設定と後処理を行なう。このプロシージャは初期化や後処理を行なうために同じモジュール内の他のルーチンから常に呼び出される。 また、サービス・プログラムのファイルがクローズされて内部変数がリセットされたときに、呼び出し側がコントロールする必要があれば、Init()プロシージャとDone()プロシージャが呼び出される。
普通は、こうした処理はアクティベーション・グループを使用して行なうが、性能上の理由から呼び出し側が直接コントロールすることができるようにする必要があるときもあるので、前述のサービス・プログラムでは、Init()ルーチンとDone()ルーチンを呼び出せるようにしてある。
その他のルーチンは1度に1つのビジネス機能を処理する。顧客データに対してどんな処理をするか。メモリー中に読み込んで、情報を読み、情報をセットしてディスクに保存する。こうしたアクションの1つ1つがそれぞれ独立して呼び出せるルーチンになっている。私はこの同じビジネス・ロジックを、顧客データを処理するシステム上のすべてのプログラムで使用している。こうした理由により、私は本稿で紹介している保守プログラムが使用しているルーチンだけでなく、サービス・プログラム中の他のルーチンも作ることになる。たとえば、顧客の現在の残高を計算したり顧客が最後に発注した日付を計算したりするルーチンが必要になるかもしれない。
私はモジュールを書くとき、いつでもその機能に関連した名前を付けるようにしています。今回紹介している例は顧客に関連しているので、CUSTと呼ぶことにします。私のチームでは、すべてのモジュール名はそのモジュールが記述されている言語を識別できるように、2文字の接尾語を付けるという厳しい標準を設けています。したがって私が作ったモジュール名はCUSTR4となっています。
コードをしばらく書いていると、さまざまなサービス・プログラムの中でたくさんのルーチンを書く羽目になってしまいます。気をつけていないと2つのモジュール間でルーチンが同じ名前を持つことになるという競合が発生することがあります。それでは保守が大変なことになります。したがって、すべてのサブ・プロシージャを始め、モジュールの外部から呼び出しが可能なものすべてに対してモジュール名を接頭語として付け、言語識別用の接尾語はつけません。今回の例でいえば、すべてのサブ・プロシージャ名、データ構造などの名前はcust_で始まります。つまり、モジュールの初期化用にはcust_init()、後処理用にはcust_done()、新しい顧客情報をメモリーに呼び出すプロシージャにはcust_load()、顧客名を取り出すプロシージャにはcust_getName()などといったように名前を付けています。
図2に私のモジュールの初期化および後処理用のルーチンを示します。この例では、このルーチンを使用してモジュール中で使用するファイルをオープンしています。もっと複雑なモジュールではたくさんのオープン文があり(図2のA)、各オープン文がそれぞれ別のファイルをオープンしています。すべてのファイルをオープンすると、CEE4RAGE API (B)を呼び出し、アクティベーション・グループが終了したときにcust_done()プロシージャを自動的に呼び出すようにオペレーティング・システムに伝えます。このようにして、コントローラーがモジュールの後処理をしたくないとき、あるいはコントローラーが何らかの理由で後処理を実行することができないとき、オペレーティング・システムが後処理ルーチンを呼び出してくれます。
顧客データをメモリーに読み込むところを図3に示します。私のサービス・プログラム中のすべてのルーチンは、図3に示すパターンと同様のパターンに従っています。 モジュールが初期化されていない場合は、Init()プロシージャを呼び出してモジュールを初期化する(図3のA)。 ルーチンが実行することになっているビジネス・ロジックを実行する。 エラーが発生したら「最後に発生したエラー」とそれに対応するエラー・コードを格納するプロシージャを呼び出す(B)。これについては後述。 サブ・プロシージャ中でエラーが発生した場合は、そのサブ・プロシージャが何らかのエラー・コードを呼び出し側に返して知らせなければならない。通常は、処理が成功した場合は*ONを、失敗した場合は*OFFをインジケータに返す。 ビジネス・ロジックと表示ロジックの間で「フィールド」を交換するときは、いつも私はサービス・プログラムのフィールドに直接スアクセスするのではなく、「getter」または「setter」ルーチンを使用します。こうすることでフィールドに値がセットされたときにその値が正しいかをチェックすることができ、そのためのチェック・ロジックをビジネス・モジュール側に置くことができます。顧客名のgetter (A)およびsetter (B)の例を図4に示します。
getterやsetterはアプリケーション中のフィールドに対して保護のレイヤーを設けているだけでなく、下位互換性に対するコントロールができるようにしてくれます。たとえば、顧客名のフィールドを50文字に拡張したい場合、図5に示すようにgetterとsetterを書き直すことができます。古いgetName()ルーチンとsetName()ルーチンを呼んでいる既存のプログラムは(再コンパイルの必要もなく)今まで通りに動作します。一方、50文字に広がった顧客名を活用したい新しいプログラムはgetNameLong()ルーチンとsetNameLong()ルーチンを使用することができます。こうした戦略を取ることで、フィールドのレイアウトを変えたときに再コンパイルする必要のあるプログラムは、CUSTR4サービス・プログラムだけです。
getterルーチンやsetterルーチンは必ずしも1つのフィールドだけを扱っているわけではありません。やり取りされるデータを何らかの論理単位にグループ化します。たとえば、図6のcust_getAddress()プロシージャとcust_setAddress()プロシージャはパラメータとしてデータ構造を受け取ることができます。このデータ構造のおかげでアドレス全体を1つのパラメータとして渡すことができます。私はそのデータ構造に対してテンプレートを定義し、サブ・プロシージャのプロトタイプと一緒にコピーブックの中に置き、LIKEDSキーワードを使用して必要な場合にそのデータ構造のローカル・コピーを作成します。
エラー処理
私が記述したモジュールにはすべてSetError()サブ・ルーチンがあります。このサブ・ルーチンは内部ルーチンによって呼び出され、エラー番号(特定の状況を処理するプログラムに有用)とエラー・メッセージ(ユーザーに対して表示するのに有用)の2つのフィールドに値を設定します。このようなSetError()ルーチンを呼び出す方法の例を図3の呼び出しBに示します。エラー番号は常に名前つき定数(これもこのモジュールのコピーブック内に含まれている)で定義されており、後を引き継いだプログラマがエラー番号の意味を容易に理解できるようにしています。図7の呼び出しAはエラー情報の設定の仕方の例を示しています。Aは単にパラメータを受け取ってモジュールのグローバル変数に値を設定しているだけです。この変数を他のモジュールからも使用できるようにするためにcust_error()という特別なgetterプロシージャがあります。このプロシージャ(図7中のB)はメッセージを返し、オプションでエラー番号をパラメータの中に渡すこともできます。
このエラー処理方法は私が自分のアプリケーションの中で繰り返し使用しているもう1つのパターンです。エラー情報をあるモジュールから別のモジュールへ、各プロシージャのパラメータ・リストを散らかすことなく伝えるための簡単で効果的な方法です。
表示ロジック・モジュール(「ビュー」)
本稿で紹介した例は、緑色の画面プログラム用です。表示ロジックは簡単です。ユーザーが編集可能ないろいろな項目用のパラメータと、ユーザーが要求可能ないろいろな関数を表現しているインジケータを受け付けます。顧客番号の入力を促す画面を表示するルーチンを図8に示します。
表示用のパラメータ・リストは別のユーザー・インタフェース・タイプにも適用できるように汎用的になっていなければなりません。今回の場合は緑色の画面を使用したので、F3キーとF10キーを押すとそれぞれExitパラメータとAddパラメータをセットします。しかしGUIアプリケーションでは、ファンクション・キーの代わりにOK、追加、キャンセルなどのボタンを用意したほうが良いかもしれません。バッチ・プログラムでは、こうした機能は別のプログラム・ロジックでコントロールされることになるでしょう。つまりポイントは、プロシージャのインタフェースを汎用的にしておけば、同じパラメータ・リストを他の「ビュー」モジュールでも使用できるということです。
本稿では表示モジュール用のコードのほんの一部しか載せていませんが、関連するソース・コードはすべてSystemiNetwork.com/codeからダウンロードできます。
コントローラー・ロジック
すべてを1つにまとめるのがコントローラー・ロジックです。最初の画面を表示して、顧客番号を入力させる部分のコントローラー・ロジックを図9に示します。このロジックは表示ロジックを呼び出して顧客番号を取得し、次にビジネス・ロジックを呼び出して入力された値を検証し、その顧客番号に相当する顧客情報をメモリーに読み込みます。コントローラーが次に行うのは(ここには載せてありませんがSystemiNetwork.com/codeから入手できます)、getterおよびsetterを表示ロジックと組み合わせて呼び出し、ユーザーが顧客のマスター・レコードを編集できるようにすることです。
本当にすべてが必要なのか
すべては必要でないかもしれません。プログラムを設計する際にモジュール・パターンを使用することの最大の利点は、表示ロジックとビジネス・ロジックを分離する点にあります。本稿で示した例では、コントローラー・ロジックも切り離しておきましたが、それが必要でない場合もあります。私の経験では、コントローラー・ロジックと表示ロジックが同じモジュール内にあったとしても、MVCによる設計の約95%の恩恵を受けることができます。
私は通常1つのプログラムに対して2つのモジュールしか作りません。1つのモジュールにはコントローラーとビューが結合されており、もう1つのモジュールにはビジネス・ロジックとデータベース・ロジックが組み合わされています。このアーキテクチャは小規模で簡単なプロジェクトに向いています。大規模で複雑なプロジェクトの場合は、3つあるいは4つのモジュールを使用してMVCアーキテクチャを本格的に使用することをお勧めします。
パターンに沿って
モジュール・プログラミングは最初は戸惑うかもしれませんが、自分のパターンというものを確立してしまえば、ずっと簡単になります。その最初の手がかりとして本稿で述べた例のコードをダウンロードし、自分のアプリケーションのテンプレートとして使用してください。気づかぬうちにこうした新しいデザイン・パターンに熟達していることでしょう。