メインフレームの世界でPL/Iを扱う皆さん、こんにちは!今回は、PL/Iに特徴的ながらも、現代のプログラミング言語では見られない「ENTRYステートメント」について深掘りしていきます。この機能は、当時の開発効率に寄与した一方で、現在のシステム移行や保守においては大きな課題となることがあります。
1. 導入: なぜ今、ENTRYステートメントを学ぶのか?
PL/IにおけるENTRYステートメントは、一つのプロシージャ内に複数の「入口」を定義する機能です。これにより、同じプロシージャを異なる名前と異なる引数リストで呼び出すことが可能になります。例えば、プロシージャの初期化処理とメイン処理を一つのモジュールにまとめたり、状況に応じて異なるパラメータセットを受け入れたりする際に利用されました。
しかし、このENTRYステートメントは、プログラムの制御フローを複雑化させ、現代の「構造化プログラミング」の原則からは逸脱する傾向があります。特に、メインフレームからオープン系へのシステム移行(モダナイゼーション)を検討する際には、このENTRYステートメントが移行作業の大きな障壁となることが少なくありません。既存のシステムを理解し、将来のリファクタリングや移行計画を立てる上で、その特性と課題を理解しておくことは非常に重要です。
2. 基礎知識: ENTRYステートメントとは
PL/Iのプロシージャは通常、`PROC`ステートメントで定義された名前が唯一の入口となります。しかし、ENTRYステートメントを使用すると、そのプロシージャ内に第2、第3の入口を作成できます。
この機能の主な特徴は以下の通りです。
- 複数の入口名: 一つのプロシージャに対して、複数の異なる呼び出し名(エントリポイント)を持つことができます。
- 異なる引数リスト: 各ENTRYポイントは、それぞれ異なる数の引数や型の引数を受け取ることができます。
- 内部スコープの共有: 同じプロシージャ内で定義された変数(自動変数や静的変数)は、どのENTRYポイントから呼び出されても共有されます。これにより、初期化処理をメインのエントリポイントで行い、別のENTRYポイントから本処理を開始するといった使い方が可能でした。
当時の開発者にとっては、関連する処理を一つのソースファイルにまとめ、コードの重複を避けるための強力なツールでした。しかし、これによりプログラムのどこから実行が開始されるのか、どの変数がどの状態で共有されているのかが分かりにくくなり、可読性や保守性が低下する原因にもなりました。
3. 実装/解決策: ENTRYステートメントの構造
ENTRYステートメントは、PL/Iプロシージャ内の任意の実行可能ステートメントの前に記述できます。通常、メインのエントリポイントが何らかの共通処理を行い、その後に`GO TO`ステートメントでENTRYポイント以降の処理をスキップする、あるいはENTRYポイントに直接到達しないように制御されることが一般的でした。
構文の例は以下のようになります。
MYPROC: PROC(A);
/ ここはMYPROCが呼び出された際の開始点 /
...
/ 共通の初期化処理など /
...
/ ALT_ENTRYという名前で別の入口を定義 /
ENTRY ALT_ENTRY(B, C);
/ ここはALT_ENTRYが呼び出された際の開始点 /
...
/ ALT_ENTRY固有の処理 /
...
/ 別のENTRYも可能 /
ENTRY ANOTHER_ENTRY(D);
...
END MYPROC;
この構造では、MYPROCが呼び出されると先頭から実行が始まり、ALT_ENTRYが呼び出されるとENTRY ALT_ENTRY(B, C);の行から実行が始まります。MYPROCから開始した場合にALT_ENTRYの処理をスキップするためには、通常、条件分岐やGO TOステートメントが使われます。
4. サンプルプログラム
ENTRYステートメントの動作を確認するための簡単なPL/Iプログラムです。
MY_PROGRAM: PROC(MAIN_PARAM) OPTIONS(MAIN);
/ このプロシージャは、MY_PROGRAM または SUB_ENTRY から呼び出せます /
DCL MAIN_PARAM CHAR(20) VARYING; / MY_PROGRAMの引数 /
DCL COMMON_DATA FIXED BIN(31) INIT(0); / ENTRY間で共有される共通データ /
/ MY_PROGRAMが呼び出された際の処理開始点 /
PUT SKIP LIST('--- MY_PROGRAM が開始されました ---');
PUT SKIP LIST('MAIN_PARAM (MY_PROGRAM): ' || MAIN_PARAM);
COMMON_DATA = 100; / 共通データに値を設定 /
PUT SKIP LIST('共通データ (MY_PROGRAM): ' || COMMON_DATA);
/ SUB_ENTRYへの到達を防ぐため、処理をジャンプ /
GO TO END_OF_COMMON_PROCESS;
/ --- SUB_ENTRY の定義 --- /
/ このENTRYポイントは、異なる引数リストを持ちます /
ENTRY SUB_ENTRY(SUB_PARAM1, SUB_PARAM2);
DCL SUB_PARAM1 CHAR(20) VARYING; / SUB_ENTRYの第1引数 /
DCL SUB_PARAM2 FIXED BIN(31); / SUB_ENTRYの第2引数 /
PUT SKIP LIST('--- SUB_ENTRY が開始されました ---');
PUT SKIP LIST('SUB_PARAM1 (SUB_ENTRY): ' || SUB_PARAM1);
PUT SKIP LIST('SUB_PARAM2 (SUB_ENTRY): ' || SUB_PARAM2);
/ MY_PROGRAMで設定された共通データが参照可能 /
PUT SKIP LIST('共通データ (SUB_ENTRY - 変更前): ' || COMMON_DATA);
COMMON_DATA = COMMON_DATA + SUB_PARAM2; / 共通データを更新 /
PUT SKIP LIST('共通データ (SUB_ENTRY - 変更後): ' || COMMON_DATA);
END_OF_COMMON_PROCESS: ; / 共通の処理終了ラベル /
PUT SKIP LIST('--- プロシージャの実行が終了しました ---');
END MY_PROGRAM;
/
このプログラムをJCLなどから呼び出す例(疑似コード):
// EXEC PGM=MY_PROGRAM,PARM='Hello from Main' -- MY_PROGRAMが呼び出される
// EXEC PGM=MY_PROGRAM,PARM='SUB,World,50' -- SUB_ENTRYを呼び出す場合の例(実際はリンケージエディタ設定やCALL文に依存)
-- この例ではPL/IのCALL文で直接SUB_ENTRYを指定して呼び出すイメージ
-- CALL SUB_ENTRY('World from Sub', 50);
/
5. 応用・注意点: 現代への移行とリファクタリング
ENTRYステートメントは、現代のオブジェクト指向プログラミングや関数型プログラミングの概念とは大きく異なります。そのため、既存のPL/Iシステムを現代的な言語(Java, C#, Pythonなど)へ移行する際には、このENTRYステートメントが最も困難な部分の一つとなります。
主な問題点と対応策は以下の通りです。
- 制御フローの複雑化: ENTRYステートメントとGO TO文の組み合わせは、プログラムのどこからどこへ処理が飛ぶのかを追跡することを非常に難しくします。これは「スパゲッティコード」の典型例となり、デバッグやテストを困難にします。
- 対応策: 各ENTRYポイントを独立したサブルーチンやメソッドとして分離します。共通ロジックは別のプライベートな関数として抽出し、各サブルーチンから呼び出すようにリファクタリングします。
- 引数管理の困難さ: 各ENTRYポイントで異なる引数リストを持つため、呼び出し元と被呼び出し側のインターフェースが多岐にわたり、システム全体の引数管理が複雑化します。
- 対応策: 各サブルーチン(旧ENTRYポイント)に適切な引数を定義し、不要な引数共有を避けます。必要であれば、引数を構造体やクラスとしてまとめ、可読性を高めます。
- テストの困難さ: 複数の入口を持つプロシージャは、特定のENTRYポイントからの動作だけをテストすることが難しく、全体的な結合テストに依存しがちになります。
- 対応策: 各機能を単一責任の原則に従って分解することで、単体テストの範囲を明確にし、テスト容易性を向上させます。
メインフレームの現役エンジニアの皆さんにとって、ENTRYステートメントは長年の経験で「当たり前」の機能かもしれません。しかし、将来のシステムを考慮する際には、この機能が持つ「構造化を壊す」という側面を認識し、計画的なリファクタリングや分解を進めることが、より保守しやすく、テストしやすいシステムへの第一歩となります。

コメント