モダンなCLプログラミング技法を試す
かつてないほど高速なCLベース・アプリケーションの作成と保守
IBM i オペレーティング・システムの CL は、OS/400 オペレーティング・システムが最初にリリースされた 1988 年当時の CL とは雲泥の差があります。残念ながら、私が話をしたことがある多くの CL 開発者は、いまだに 1980 年代や 1990 年代の技法だけを使用して、新しい CL プログラムを作成したり、既存のプログラムを拡張している有様です。このシリーズでは、CL ベース・アプリケーションの開発とサポートにおいて、生産性を飛躍的にアップさせる、i 5.3 から 7.1 にかけて最近追加された CL プログラミング機能についていくつかお話します。
20 世紀や 21 世紀の CL プログラミング技法を再確認し、それらと対照させるため、複数のバージョンの CL アプリケーション・プログラムを作成しました。それぞれ異なる OS リリースで作成し、そのリリースで使用できる新機能を採用しています。CL プログラムは、任意のサブシステムの概要がどのように定義されているかを表示しています。図 1 は、ライブラリー QSYS でサブシステム QUSRWRK のアプリケーションが表示する内容を示しています。ディスプレイ・ファイルのソース DSPSBSDSPF (サブシステム・ディスプレイ・ファイルの表示) を 図 2 に示します。図 3 は Display Subsystem Definition (DSPSBSDFN) コマンドのソースを示しています。DSPSBSDSPF ディスプレイ・ファイルの作成に必要な手順、メッセージ・ファイル OURMSGS 中の DSPSBSDFN コマンドのメッセージ・プロンプト、および DSPSBSDFN コマンドは 図 4 に示しています。
DSPSBSDFN というアプリケーション・プログラムは、フォーマットが SBSI0100 の QWDRSBSD (サブシステム情報の取得) API を使用して、図 1 に示すサブシステム関連情報にアクセスします。この API の資料一式は IBM インフォメーション・センター (tinyurl.com/QWDRSBSD) にあり、API パラメーターと SBSI0100 フォーマット定義はそれぞれ図 5 と 図 6 で繰り返し示しています。アプリケーション・プログラムと関連 API は、読者が日々の運用で使用すると考え、選びませんでしたが、QWDRSBSD は OS/400 V2R1 で使用でき、現代の CL の機能をデモンストレーションする助けになる特性を備えているため、このようにしました。
バージョン 2 の CL
V2R1 で使用できる CL 機能だけを使用した DSPSBSDFN アプリケーションの作成を引き受けてくれた場合、少なくとも CL だけではプログラムが作成されることはほぼないと考えてもいいでしょう。V2R1 の CL にはバイナリー・データという概念がなく、不可能ではないにしても、DSPSBSDFN の作成は時間が掛かるだけの作業になってしまうでしょう。
API の呼び出しと文字データの処理はそれほど難しくはありません。1 つのアプローチ方法を図 7 に示します。唯一変わった項目としては、CL 変数の初期化、図 7 の A に示す &Len_SBSI (受信側変数 &SBSI0100 の長さ)、と図 7 の B に示す &EC_BytPrv (API エラー・コード・バイト提供フィールド) です。両変数とも 4 バイト文字変数 (Binary(4) データ長に対応する) として定義されており、16 進表記で初期化されています。&Len_SBSI の場合は、x'00000168' は 10 進数値 360 (&SBSI0100 変数の宣言長) に対応し、&EC_BytPrv の場合は、 x'00000000' は 10 進数値 0 (API で検出されたエラーがエスケープ・メッセージとして返される必要があることを示す) に対応しています。 &SBSI0100 受信側変数の長さの値 360 は、80 (図 6 に示す固定フィールド Bytes returned から Number of storage pools の長さの組み合わせ) を 10 (サブシステムに定義できるストレージ・プールの最大数) と 28 (ストレージ・プールそれぞれに返される固定フィールド Pool ID から Pool activity level までの長さの組み合わせ) の積に追加して決定されました。この記事では、受信側変数の最初の 80 バイトのみ利用していますが、続編ではストレージ・プール情報を使用する予定です。
プログラムの残り部分はシンプルです。(正確には、自己文書化コードではありませんが。)プログラムは、%substring (%sst) 組み込み関数を使用してサブシステムに関連付けられた文字データにアクセスし、SNDRCVF (ファイルの送受信) コマンドで結果を表示し、Enter キーを押した場合は最新の状態を表示する、あるいはコマンド・キー 3 を押した場合はプログラムを終了します。ここまでは、大丈夫ですね。
しかし、フォーマットが SBSI0100 の binary(4) ベース・データ (&MaxActJob―アクティブ・ジョブの最大数、&CurActJob―現在のアクティブ・ジョブ数、&NbrPools―ストレージ・プール数) の処理になると、多少面倒になってきます。図 8 は、これらの数値を表示するあるアプローチ方法の開始を示しています。
図 8 の B では、アプリケーション・プログラムは、サブシステムでサポートされるアクティブ・ジョブの最大数を表示する値を決定しています。サブシステムが MAXJOBS(*NOMAX) を指定して定義されている場合、インジケーター 30 を「オン」 (インジケーター 30 の条件は、図 2 の DSPSBSDSPF ディスプレイ・ファイルの定数値) に設定して定数 *NOMAX を表示します。そうでない場合、&MaxActJob の MAXJOBS 数値を表示します (インジケーター 30 が「オフ」)。難しいのは、API がアクティブ・ジョブの最大数をバイナリー数で返す点で、&Len_SBSI and &EC_BytPrv の初期化ですでに説明したように、V2R1 CL はバイナリー値を直接サポートしていないため、16 進数値を指定して、API から返された値を決定する必要があります。
QWDRSBSD API では、MAXJOBS(*NOMAX) が有効な場合に最大アクティブ・ジョブ数 -1 を返すと記載しているため、図 8 の B にある最初の IF テストでは -1 が返されたかどうかを決定します。単純に「If Cond(%sst(SBSI0100 69 4) *EQ x'FFFFFFFF')」と指定できたら素晴らしいと思います。ここで x'FFFFFFFF' は -1 の 16 進数値ですが、これを試してみれば、COND キーワードでは 16 進数リテラルを指定できないことはすぐにわかります (エラー「CPD0126-Operand not valid or operator missing in COND (オペランドが有効でない、または演算子が COND で見つからない)」になります)。この制限を回避するため、プログラムは図 8 の A で定義されている変数 &Neg1 を使用します。&Neg1 は負のバイナリー 1 の 16 進数値に設定されています。図 8 の B にある最初の IF テストが true の場合、インジケーター 30 は「オン」に設定され、現在のジョブ数 (&CurActJob) の戻り値が処理されます (図 8 の C)。
図 8 の B にある最初の IF テストが false の場合、インジケーター 30 は「オフ」に設定され、ユーザーが指定したジョブの最大数の値が決まります。これは、CL 変数 &Pos1、&Pos2、&Pos3 などに返されたジョブの最大数をテストして行います。これらの CL 変数は、&Neg1 の定義方法と同様に 図 8 の A で定義されており、正のバイナリー 1、正のバイナリー 2、正のバイナリー 3 など の 16 進数値を表しています。&Pos5 の DCL と &Pos3 の IF テストの両方 (さらにプログラム中の他の数箇所) の後ろにコメント「/* etc */」が存在する点に注意してください。こうするのは、最大 1000 個返される可能性があるそれら DCL と IF テストすべてを入力し、公開する気が全くないためです。返される可能性があるすべての値それぞれを宣言し、テストすることが要件であるため、V2R1 では前述の「不可能ではないにしても、DSPSBSDFN の作成は時間が掛かるだけの作業になってしまうでしょう」ということになります。現実的には、RPG のような言語で作成された 1 つのプログラムを呼び出して 、API が返したバイナリー値を CL が直接処理できるパック 10 進数値に変換することになります。あるいは、RPG や COBOL などの言語を使用して 単にCPP 全体を作成してしまう可能性も少なくありません。
バージョン 2.2 の CL
幸い、IBM は V2R2 でこの問題を取り除くことができました。V2R2 から CL は %binary、つまり %bin 組み込み関数で機能拡張されました。これにより CL 開発者は*CHAR 変数または *CHAR 変数のサブストリングを、バイナリー数値を含んでいるかのように処理することができます。図 9 は、%bin 組み込み関数が CL を使用して、いかに DSPSBSDFN CPP の実装に必要な努力を削減しているかを示しています。図 7、8、9 のプログラムを比較すると、生産性が大きく向上していることがわかると思います。図 7 の A および B で行った 16 進数の初期化は、実際の 10 進数値の 360 と 0 を使用して 図 9 の C にある CHGVAR コマンドと置き換えられます。しかし、さらに重要な点は、図 8 の A にある &Neg1、&Zero および各種 &Pos* 変数のすべての DCL は、図 8 の B にある IF と CHGVAR コマンドとともにどこかに行ってしまいました。それらは、CHGVAR を使用して、適度に簡単な IF テストと正しい値の &MaxActJob、&CurActJob、および &NbrPools 変数の直接的な割り当てと置き換わっています。
DSPSBSDFN CPP は、CRTCLPGM PGM(DSPSBSDFN) または CRTBNDCL PGM(DSPSBSDFN) のいずれかを使用してコンパイルできます。さらに、図 4 の手順を完了したと想定すると、QUSRWRK など適切なサブシステム名または QHTTPSVR/QHTTPSVR などの修飾サブシステム名を指定した DSPSBSDFN コマンドを使用してプログラムを実行できます。
残念ながら、%bin 組み込み関数は、バージョン 2 の残り、バージョン 3 および 4 すべて、バージョン 5 の半分に対する最後の CL 機能改善 (いずれにしてもこの記事に関連) でした。また、多くの CL 開発者が現在でも使っている習慣 (つまり、%sst と %bin 組み込み関数を使用すること) をつけたのもこの頃でした。しかし i 5.3 以降、IBM は、さまざまな場合で、これらの組み込み関数の代わりに使用すべき非常に強力な CL 機能を提供しました。
バージョン 5 の CL
DSPSBSDFN アプリケーションに対して、それ自体には大きな生産性向上はありませんが、i 5.3 では *INT (整数) データ・タイプが CL DCL に追加されました。図 10 の A および B でおわかりのように、この機能拡張のおかげで CL 開発者は、バイナリー/整数変数 &Len_SBSI および &EC_BytPrv を直接宣言して初期化し、図 9 の A および B の DCL を単純化して、図 9 の C で CHGVAR を使用する必要がなくなっています。この記事には直接関係ありませんが、i 5.3 では DOFOR、DOWHILE、DOUNTIL の各コマンドのサポートも行われました。図 10 の C では、DOUNTIL により DSPSBSDFN CPP の過去のバージョンで見られた LOOP ラベルや関連の GOTO コマンドの必要性がなくなっている様子がわかると思います。
この 5.3 機能拡張に続いて、i 5.4 では、CL、つまり *DEFINED ストレージを使用してどのようにデータを宣言できるかについて大きく変更されました。*DEFINED ストレージの影響を 図 11 に示します。定義されたストレージを使用して、RPG データ構造または COBOL 従属データ・エレメントの定義に似た方法で、より大きな変数のサブフィールドを定義できます。この場合、図 11 の A は、受信側変数 &SBSI0100 の QWDRSBSD API で返された情報を異なる CL 変数として再定義する方法を示しています。例えば、サブフィールド (つまりサブ変数) &SBS_Name は変数 &SBSI0100 の位置 9 の開始点として定義され、長さ 10 の *CHAR 変数を表しています。これは、図 6 に示す Subsystem description name フィールドと等しいです。この再定義機能を使用して QWDRSBSD API の呼び出し結果を処理する場合、図 11 の B にある %sst および %bin 組み込み関数が削除されます。図 11 の B にある IF コマンドと CHGVAR コマンドの方が、私にとっては、5.4 以前で使用されていた %sst および %bin メソッドより簡単で (間違いを犯しにくく) 把握しやすいです。*DEFINED サブフィールドの DEFVAR キーワードの名前と位置の情報は、%sst および %bin 組み込み関数に提供する情報と同じですが、プログラムの処理ロジック全体に分散している場合とは異なり、プログラムの DCL ですべて見つかります。おまけに、CHGVAR で *DEFINED 変数を使用する方が、%sst または %bin 組み込み関数のいずれかを使用するよりも、わずかに処理速度が速くなっています。読み取りやすい、デバッグしやすい (例えば、システム・デバッグ機能を使用する場合、&SBSI0100 よりもサブフィールド &SBS_CurJob の評価)、また処理速度が速いなどの機能をこのように組み合わせると、*DEFINED ストレージは、私が利用したくなるような機能になります。読者の皆さんも同じように感じていただけると思います。
*DEFINED ストレージは素晴らしいと同時に、%sst および %bin 組み込み関数は引き続き、開発ツールキットのツールであることを指摘しておく必要があります。*DEFINED ストレージでは、コンパイル時に、大きいほうの CL 変数内のサブフィールドの位置を認識しておく必要があります。一方、組み込み関数を使用して、位置をランタイム時に値が決まる CL 変数として指定できます。パラメーターのレイアウトや API 受信側変数など多くの場合、あらかじめ位置がわかっている必要があります。しかし、アプリケーションによっては、大きいほうの CL 変数の内容を実際に処理するまでは、この位置情報がわからない場合があります。
バージョン 6 の CL
図 11 に示すソース・コードの量が図 10 のソース・コード量より多いことにお気付きかと思います。*DEFINED ストレージに関連付けられた DCL は、DSPSBSDFN プログラム内の実行可能コードを表していませんが、名前別に直接参照するのに必要な量より多いソース・コードを示しています。INCLUDE CL コマンドがサポートされ 6.1 で多少緩和されているものの、喜んでこの交換条件に応じたいと思います。
DSPSBSDFN から変数 &Len_SBSI および &SBSI0100 の定義を取得し、簡単に言えば QWDRSBSD という個別のソース・メンバーに配置する場合、DSPSBSDFN を 図 12 に示す内容に変更できます。図 12 の A の INCLUDE コマンドには、コンパイル時に DCL に関連付けられた &SBSI0100 が含まれています。INCLUDE コマンドを使用して、DSPSBSDFN で見つかったソース・コードの量を減らすことができます。さらに、QWDRSBSD 定義が複数のプログラムで使用されている場合、エラーを修正し、変更をピックアップするために組み込みメンバーを利用してすべてのプログラムを再コンパイルするために、ソース・メンバー QWDRSBSD で見つかった 1 つの共通定義を変更できるでしょう。この方が、Browse/Copy アプローチで複数のプログラムのソースに物理的にコピーできる共通のソースを変更するより明らかに生産的です。
最新のアプローチを試す
20 世紀のツールを使用して相変わらず CL アプリケーションを作成しているなら、この記事で確認した最新の機能をいくつか試してみてはいかがでしょうか。次のようなモダンな機能を利用してみます。
- *DEFINED データ
- *INT (整数) データ・タイプ (符号なし整数の *UINT とともに)
- DOFOR、DOWHILE、および DOUNTIL などのプログラミング制御フロー構造体
- INCLUDE コンパイル時処理のサポート
すると、今までにないほど早く CL ベース・アプリケーションを作成して、保守することができるようになります。
モダンな CL についてお話する次号では、さらに多くの機能についてお話します。QWDRSBSD API で DSPSBSDFN に返されているストレージ・プール情報、現在は使用されていない情報にアクセスして、これらの機能のデモンストレーションを行います。