【第24回】RPGのプロトタイプの威力 | IBM i 総合情報サイト

【第24回】RPGのプロトタイプの威力


投稿日:2021年2月26日

2018年4月 | ジョン・パリス、スーザン・ガントナー

プロトタイプは、ほとんどのRPGプログラマーが少しは知っている話題です。なぜなら、プロトタイプはフリーフォームRPGでのプログラムコーディングとプロシージャの呼び出しに必要だからです。しかし、プロトタイピングとその真価を多くの人が十分に理解していないことに私たちは気付いています。私たちは、このところしばらくプロトタイプについて特別号に記事を書き、プロトタイプを賛美しました。それは、2001年に始まった“RPG IV Prototypes: What are they and why should you care?”と題した議論から始まりました。この記事では、プログラム呼び出しのため(だけ)のプロトタイプの基礎について説明しました。“The Case of the Missing Parameters”という記事を含め、このテーマに関して後の記事でいくつかの他のパラメータ・キーワードを取り上げました。その中で、オプションのパラメータを扱ういくつかの追加のキーワードを使用することで、呼び出されるルーチンをもっと柔軟にする方法について説明しました。 私たちは、バインドされたプロシージャまたは関数呼出しと一緒にプロトタイプを使用することに関する他のいくつかの記事でプロトタイプを継続的に取り上げました。

以前に説明したのに、今更なぜこの話題を取り上げる必要があるのだろうと思うかもしれません。主な理由は以下の3つです。

  1. 最近、RPGプログラマーとフォーラムや電子メールでやり取りしたり、私たちが教えている協議会やクラスで直接会話したりするとき、多くのRPGプログラマーがプロトタイプの仕組みや、それがプログラムコードを簡潔かつ頑健にするのにどれ程役立つかを十分に理解していないことに気付きました。
  2. こうした会話をするときに、ユーザーがその話題についてもっと知ることができるいくつかの記事を紹介できることをしばしば嬉しく思いますが、彼らにこうした初期の記事を紹介するのを心苦しくも思います。というのもコーディングスタイル(その多くはフリーフォームRPGが利用可能になる前に書かれた)が読み辛く、新しいRPGプログラマーには理解するのが難しいかもしれないからです。
  3. これまでの年月の間に、プロトタイプをサポートするために多くの機能拡張が行われたので、いくつかの最新情報を含められるようにしたいのです。

ですから、私たちは、それらいくつかの古い記事の例を改善し、それらの中のより重要な点のいくつかを見直そうと決めました。私たちは、それら初期の記事からすべての詳細をカバーするつもりはありません。あなたはまだそれらを読みたいと思うかもしれません(古いコーディングスタイルに取り組むために奮起してください!)。

これは複数回にわたるシリーズ記事になるでしょう。この第1回の記事では、最初の2つの記事の例を見直して更新します。

基本事項についての簡単なレビューと最新情報

まず、プロトタイプの最初の記事に戻ってコード例を更新し、“なぜプロトタイプを重要視するべきか”という問いに対する答えの要点を再検討してみましょう。

この例では、3つのパラメータを渡し、PX027Cという税計算プログラムを呼び出しています。最初のプロトタイプと呼出しは、現代のRPGコーディングスタイルでは次のようになります。

       Dcl-PR TaxCalc ExtPgm('PX027C');
         GrossPay   zoned(9:2);
         Allowances zoned(7:2);
         NetPay     zoned(9:2);
       End-PR;    

       TaxCalc( Gross : Allow : Net); 

これにはParmまたはPListを使用した旧式の呼出しよりも、いくつかの大きな利点があります。

  • ・ まず、通常課せられる命名標準よりも読みやすいように、プログラムに意味のある名前を付けることができます。
  • ・ 名前は文書化のためだけのものなので、プロトタイプのパラメータに意味のある記述的な名前を使用することもできます。
  • ・ 最も重要なことは、呼出しで渡すパラメータが、プロトタイプで指定されたものとデータ型とサイズが一致するかどうかを、コンパイラが検査するということです。

上記の例では、Grossというフィールドが実際にはパック十進数フィールドまたは9:2ではなく7:2の長さのゾーン十進数フィールドであった場合、コンパイルエラーが発生します。コンパイル時にパラメータ不適合エラーを修正する方が、後工程でテスト中に、あるいはもっと悪いことに本番環境で何が間違っているのかを突き止めようとするよりもずっと容易なので、このコンパイル時のエラーは良いことでしょう。

当然、元の記事が書かれた時代に比べ、現代のプロトタイプにはさらに大きな利点があります。フリーフォームRPGのロジックからのプログラム呼出しが容易になるのです。

プロトタイプは食い違いを修正できる

2つの非常に小さな機能強化により、プロトタイプはさらに強力になります。

       Dcl-PR TaxCalc ExtPgm('PX027C');
         GrossPay    zoned(9:2) Const;
         Allowances  zoned(7:2) Const;
         NetPay      zoned(9:2);
       End-PR;    

Const(定数)キーワードを入力パラメータに追加することにより、発生する可能性のある小さなパラメータの不一致を“修正”する権限をコンパイラに与えます。この場合、フィールドGrossのサイズが間違っているか、packedなど数値型が異なっているならば、コンパイラはこれに対処します。コンパイラは、正しいタイプとサイズの一時フィールドを作成し、Grossの値をそこにコピーし、その一時的な値をパラメータとして渡すために必要なコードを生成します。加えて、Constキーワードはさらに踏み込んで、プログラマーが、定数、リテラル、または式を渡すことを可能にします。少し後で簡単な例を示します。

Constは、十分に利用されていないと私たちが感じているプロトタイプ機能の1つです。渡されるデータの型やサイズを変更するために、値を別のフィールドに移動するだけの目的で何故追加のコードを書く(そして永遠に維持する)のでしょうか? たとえば文字列の長さを指定する必要があるAPIへのパラメータとして%Len()を使用するなど、組み込み関数をパラメータに使用できるというのも便利です。

呼び出されるルーチンによって変更されるべきでないパラメータには常にConstを指定します。もちろん、呼び出されるルーチンによって値が変更される必要があるもの(この例のNetPayなど)では、Constを指定することはできません。その結果、こうしたパラメータはサイズやタイプを正確に一致させる必要があります。その理由は、Constパラメータに対して生成されるかもしれない余分なコードは、呼出し前に一時フィールドに値を移動するだけなので、呼出しから戻ったときに一時フィールドを元のフィールドにコピーし直さないからです。Constパラメータを“読取り専用”と呼ぶ人もいますが、実際はConstパラメータの値を変更できないという絶対的な保証はありません。Constを使う場合、基本的に呼び出されるルーチンが値を変更するとは予期していないと言っているに過ぎません。

もっと柔軟なパラメータが必要?

多くの場所から呼び出されるプログラムやプロシージャがあると仮定して、追加のパラメータが必要な新しい機能を追加するとしましょう。昔に戻ってそのコードを現在呼び出しているすべての箇所を修正する代わりに、プロトタイプはいくつかのパラメータが選択的であることを明記するオプションを提供します。これは、Optionsキーワードの値を介して行われます。

*NoPassオプションは、パラメータが渡されるかどうかを意味します。パラメータにこのオプションが指定された場合、その後ろのすべてのパラメータにも*NoPassの指定が必要です。*Omitオプションは、パラメータ・リストのどこででも使用できます。しかし、*Omitを指定した場合、呼出し時にパラメータ値のプレースホルダとして特殊値*Omitを指定しなければなりません。*NoPassを指定した場合は、必要のないパラメータを単に省略するだけです。ただし、ひとたびパラメータを1つ省略すると、残りのパラメータも省略しなければならないことに注意してください。

これらの機能を利用する最新の例を見てみましょう。ルーチンLastDayOfMonthは、月の最終日の日付を返します。元のバージョンでは、単一の日付パラメータを渡す必要がありました。パラメータが渡されない場合(または特殊値*Omitが使用されている場合)、現在の日付が使用され、現在の月の最終日が返されるように機能を拡張しました。 単一のパラメータ(日付)が渡された場合、元のバージョンと同様に、渡された日付の月の最終日が返されます。しかし、両方のパラメータ(日付と月数)が渡された場合、返される日付は、渡された日付から指定した月数後の月の最後の日です。

以下に示すプロトタイプでは、それに続くすべての呼出しが有効です。


       Dcl-Pr LastDayOfMonth Date(*USA);
         InpDate Date(*USA) Const Options(*NoPass : *Omit);
         Months  Packed(3)  Const Options(*NoPass);
       End-Pr;                

       Day = LastDayOfMonth();
       Day = LastDayOfMonth(MyDate);
       Day = LastDayOfMonth(*Omit : MonthsToAdvance); 
       Day = LastDayOfMonth(%Date(NumericDate:*YMD) : 3);

*NoPassが指定されているので、1番目と2番目の呼出しのように、2つのパラメータの一方または両方を(完全に)省略できることに注意してください。3番目の呼出しでは、第2パラメータを渡す必要があるため、渡したくないパラメータのプレースホルダとして*Omitを指定しなければなりません。4番目の呼出しでは、すべての可能なパラメータを使用しています。組み込み関数%Dateとリテラル数値 3を渡すことができるようにConstをうまく利用しています。

これらのパラメータ・オプションを使用する上で理解するべき最重要事項の1つは、呼び出されるルーチンが値を受け取らない状況を認識し、これを適切に処理するようにコーディングしなければならないということです。呼び出されるルーチンが、渡されなかったパラメータの値を絶対に使用しない(または参照さえしない)ようにすることが重要です。そこに空白やゼロなどの特別な値があることに頼ることはできません。ランタイムエラーが発生することに頼ることさえできません。再度念を押しますが、エラーを検知する可能性はありますが、エラーを検知しないかもしれないのです。

これらのオプションのパラメータをLastDayofMonthプロシージャで適切に利用するロジックは、以下で強調されています。プロシージャ・インターフェース(PI)は、上記のプロトタイプを反映して、次のようになります。

Dcl-PI LastDayofMonth Date(*USA);
        	InpDate Date(*USA) Const Options(*NoPass: *Omit);
         	Months  Packed(3)  Const Options(*NoPass);
End-PI;     

パラメータが*NoPassか*Omitを使用するかどうかによって、パラメータが渡されたかどうかを検査するための様々な方法を使用します。(例にあるように)パラメータで*NoPassまたは*Omitのいずれかを使用できる場合、両方のシナリオを検査する必要があります。

LastDayOfMonthプロシージャのロジックは次のとおりです。(もしあるなら)どのパラメータが渡されたのかを確定するために使用されるメソッドについて以下で説明します。

< B >  If  (%Parms < %ParmNum(InpDate)) or (%Addr(InpDate) = *Null);
         DateToUse = %Date();   // 現在の日付を使う
       Else;
         DateToUse = InpDate;   // 受け取った値を使う
       EndIf;

       WorkDate = DateToUse - %Days(%SubDt(DateToUse:*D) - 1);

< A >  If %Parms < %ParmNum(Months);     
         LastDay = (WorkDate + %Months(1) - %Days(1));
       Else;
         LastDay = (WorkDate + %Months(Months + 1) - %Days(1));
       EndIf;

       Return LastDay;  

<A>では、Monthsパラメータの値を受け取ったかどうかを検査しています。%Parmsは、何個のパラメータを受け取ったかを確定するために使用します。この例では%Parmsは0,1,2の値を返す可能性があります。2個未満のパラメータを受け取った場合、Monthsは渡されなかったことが分かります。これは、あらゆる*NoPassパラメータの処理方法の基本です。

%Parmsと比較している数値をコード内で直接指定することもできますが、V7.1からは遥かに望ましくかつ安全な方法である%ParmNum()が使用できます。この組み込み関数により、その数値をソフトコーディングすることができます。上記のコードでは、%ParmNum(Months)は2を返します。何故なら、Monthsは現在プロシージャ・インターフェースの第2パラメータだからです。こうすれば、将来Monthsが第1パラメータになるようなパラメータの順序の変更が必要になった場合、コンパイラがそれを処理するので変更は不要です。V7.1になる前の“悪しき昔”であれば、元に戻って定数2を1に変更することを忘れてはならなかったでしょう。

<B>では、InpDateの値を受け取ったかどうかを検査します。InpDateは*NoPassパラメータなので、%Parmsを使用できます。しかし、*Omitパラメータでもあるため、特殊値*Omit(これはヌルポインタに変換されます)が渡されたかどうかを検査する必要があります。それ故、ヌルポインタを検査するために%Addrを使います。

InpDateパラメータを受け取らなかった場合は現在の日付を、受け取った場合はその値を使って、後のロジックで使用されるDateToUseフィールドを設定します。

オプションパラメータを使用するには、呼び出されるルーチンに追加のコードが少し必要ですが、既に多くの場所から呼び出されている既存のルーチンに追加機能を付加する場合、プロトタイプを使うことで削減される時間のことを考慮してください。*NoPassパラメータを使う場合(パラメータ・リストの最後に新しいパラメータを置く限り)、どのような既存のコードであれそれがどこで使われていようと、変更する必要はありません。

プロトタイプができることはこれですべてか?

いいえ、絶対そのようなことはありません。ここでは、データ構造やファイルなどのより複雑なパラメータを渡す機能については触れていません。また、これまでの議論は、プログラムまたはプロシージャの呼出しに使用できるキーワードに制限しています。バインドされたプロシージャ呼出しに限り利用可能なその他のものがあります。今後の記事で、それらの機能を組み込むためのプロトタイプの能力について、この議論を続けます。

Copyright © IGUAZU