PL/Iの「RETURNS」を甘く見るな ― 関数型PROCEDUREの戻り値と暗黙の変換が招く罠
メインフレームの保守現場で、ふと目にする「なんとなく動いている古いPL/Iコード」。その中には、長年放置された爆弾が眠っていることがあります。特に、`PROCEDURE`が値を返す際の`RETURNS`属性の指定や、呼び出し元との型不一致。これらは、コンパイラが「気を利かせて」くれるおかげで、一見正常に動いているように見えます。
しかし、その「気を利かせた暗黙の変換」こそが、基幹バッチの夜間運用で予期せぬアベンドや、精度落ちという形で牙を剥くのです。今日は、ベテランの視点から、この「関数型PROCEDURE」の作法を深掘りしていきましょう。
—
1. RETURNS属性は「契約」である
PL/Iにおいて、`PROCEDURE`に`RETURNS`を付けるということは、呼び出し元に対する「この型で必ず返す」という厳格な契約です。
1
/ 関数定義の基本:RETURNS属性による戻り値の明示 /
CALC_TOTAL: PROCEDURE(P_AMT, P_TAX) RETURNS(FIXED BIN(31));
DCL P_AMT FIXED BIN(31) PARM;
DCL P_TAX FIXED BIN(31) PARM;
DCL W_TOTAL FIXED BIN(31);
W_TOTAL = P_AMT + P_TAX;
RETURN(W_TOTAL); / ここで指定した型で戻す /
END CALC_TOTAL;
ここで重要なのは、`DCL`での宣言を怠らないこと。もし呼び出し元でこの関数が宣言されていない場合、PL/Iコンパイラは`RETURNS(FLOAT BIN(21))`(デフォルト)と勝手に解釈し、とんでもないメモリ領域を汚染したり、計算結果が狂ったりします。「関数は使う前に必ずDCLでRETURNSを宣言せよ」。これは、マイグレーション現場での鉄則です。
—
2. 暗黙的変換が引き起こす「悪夢」
現場で最も恐ろしいのは、呼び出し元の`DCL`と、関数側の`RETURNS`が微妙に食い違っているケースです。
例えば、関数側が `RETURNS(FIXED DEC(15, 2))` と宣言しているのに、呼び出し元が `DCL FUNC ENTRY RETURNS(FIXED BIN(31))` としている場合。コンパイラはせっせと型変換コードを生成しますが、これが重い処理の中、何万回とループするバッチ内で走ると、CPUコストはバカになりませんし、何より「精度落ち」の温床になります。
現場で見かける「やってはいけない」例
1
/ 悪い例:呼び出し元と定義の不一致 /
DCL GET_VALUE ENTRY RETURNS(FIXED BIN(31)); / 実際はFIXED DECを返す関数 /
/ このような暗黙の変換は、コンパイラのバージョンアップや /
/ 最適化オプションの変更で、突然挙動が変わることがある。 /
/ デバッグの際、レジスタの中身を見て「なぜ値が変わる?」と /
/ 悩む時間は、最も無駄なエンジニアリングだ。 /
—
3. 実践:VSAMアクセスとONユニットを絡めた設計
実務では、単なる計算ではなく、VSAMファイルからのデータ取得とセットで関数化することが多いはずです。ここで`ONCODE`や`ONUNIT`を適切に制御しつつ、値を返す設計を紹介します。
1
GET_CUST_LIMIT: PROCEDURE(CUST_ID) RETURNS(FIXED BIN(31));
DCL CUST_ID CHAR(5) PARM;
DCL VSAM_LIMIT FIXED BIN(31);
/ 入出力エラーのトラップ /
ON ENDFILE(CUST_FILE) BEGIN;
/ エラー発生時は0を返して呼び出し元で制御させる /
VSAM_LIMIT = 0;
END;
READ FILE(CUST_FILE) INTO(CUST_REC) KEY(CUST_ID);
VSAM_LIMIT = CUST_REC.LIMIT_VAL;
RETURN(VSAM_LIMIT);
END GET_CUST_LIMIT;
このような実装では、呼び出し元で必ず「戻り値が0かどうか」を判定するロジックを入れます。ここで重要なのは、「関数内でログを出して異常終了させるのか、呼び出し元に判断を委ねるのか」という責任分界点を明確にすることです。
—
ベテランからのアドバイス:保守性を高めるために
最後に、大規模改修の際に私が必ず徹底させているポイントを伝授します。
1. ENTRY宣言はINCLUDEメンバに集約せよ:
関数定義と呼び出し元で同じ宣言文を共有しないと、仕様変更の際に必ず片方だけ修正を忘れます。`%INCLUDE`を使い、定義の単一化を図ってください。
2. BUILTIN関数の活用:
型変換が必要な場合は、`FIXED()`や`FLOAT()`といった組込み関数を明示的に呼び出し、「意図的な変換であること」をソースコード上で宣言してください。`DCL X FIXED DEC(10) = Y;` と書くより、`X = FIXED(Y, 10);` と書く方が、後から読む人間には親切です。
3. コンパイラ・リスティングの警告を無視するな:
「`IEL0364I`:引数とパラメータの型が一致しません」といった警告を、「動いているから」と放置していませんか? その警告は、将来のバグ予備軍からの警告です。
PL/Iは、書き手が仕様を掌握していれば、これほど堅牢で信頼性の高い言語はありません。機械に「勝手にやらせる」のではなく、「コンパイラに自分の意図を正確に伝える」こと。これが、一流のメインフレーム・アーキテクトへの近道です。
さあ、次のバッチ改修では、`RETURNS`の型定義をもう一度、厳格に見直してみてください。あなたの書いたコードが、次の10年、夜間の基幹バッチを支えることになるのですから。
