CICSオンラインの闇:「EXEC CICS HANDLE CONDITION」の有効範囲とスタック制御を制する者がレガシーを制す
メインフレームの現場で長く生きていると、ある日突然、身の毛もよだつようなオンライン障害に遭遇する。夜間のバッチならまだリトライやロールバックでリカバーの余地もあるが、昼間の子画面(3270エミュレータ)の向こう側で、数百人のユーザーが泣き叫ぶCICSオンラインの突然のアベンド(ABEND)は、アーキテクトにとって冷や汗ものの一幕だ。
特に、PL/Iで書かれたCICSアプリケーションの保守や、Java/C#へのマイグレーション(リライト)プロジェクトを牽引する立場にあるテックリードなら、一度は頭を抱えたことがあるはずだ。「なぜ、想定していたエラー処理ルーチンに制御が飛ばず、タスクがいきなり異常終了(ASRAやAEIVなど)したのか?」と。
今回は、PL/Iの基本構文における識別子の特性やデータ制御の話から一歩踏み込み、CICS特有の例外制御機構である `EXEC CICS HANDLE CONDITION` のコンパイラ挙動、そしてその背後にある深い落とし穴について、実務の現場目線で徹底的に解き明かしていこう。
—
1. PL/Iの言語特性とCICSマクロの危うい共存
PL/Iという言語は、非常に懐が深い。FORTRANの数値計算能力、COBOLの事務処理能力、そしてALGOLの構造化プログラミングの概念をいいとこ取りした、IBMメインフレームの至宝である。識別子(変数名)の命名規則においても、最大31文字までの長名を許容し、COBOLのような煩雑なピリオドによる修飾を必要としない洗練されたブロック構造を持つ。
しかし、この「自由度の高さ」と「強力な最適化コンパイラ」が、CICSのプリプロセッサ(DFHEAP1Iなど)を介して展開されるマクロ命令と組み合わさった瞬間、予測不能な牙を向くことがある。
その最たるものが、`EXEC CICS HANDLE CONDITION` だ。
1
/ PL/I & CICS 例外制御の基本形 /
DCL WS-EMP-ID CHAR(5);
DCL WS-MSG CHAR(40);
/ NOTFND(レコード未検出)時のジャンプ先を指定 /
EXEC CICS HANDLE CONDITION
NOTFND(ERROR-NOT-FOUND)
DUPREC(ERROR-DUPLICATE);
EXEC CICS READ FILE(‘EMPFILE’)
INTO(EMP-RECORD)
RIDFLD(WS-EMP-ID);
/ 通常処理フロー /
…
RETURN;
ERROR-NOT-FOUND:
WS-MSG = ‘社員マスタに該当データが存在しません。’;
/ 警告画面の返却処理など /
EXEC CICS SEND TEXT FROM(WS-MSG) …;
RETURN;
ERROR-DUPLICATE:
WS-MSG = ‘既に登録済みの社員番号です。’;
…
RETURN;
このコードを一見すると、「`NOTFND` が発生したら自動的に `ERROR-NOT-FOUND` ラベルへ制御が飛ぶのだな」と、非常に直感的に理解できる。COBOLの `INVALID KEY` 句よりも広範囲なエラーを捕捉できるため、多くのプログラマが安易に多用しがちだ。
だが、ここにCICSアーキテクチャの最大の罠が潜んでいる。
—
2. HANDLE CONDITIONの「有効範囲(Scope)」とスタックの罠
基幹システムの設計において、最も恐ろしいのは「バグが表面化するまでにタイムラグがあること」だ。`HANDLE CONDITION` は、一度宣言されると、そのタスク(Task)内、かつそのプログラムの実行スタック(あるいは呼び出し階層)において永続的に有効となる。
ここでテックリードが知るべきCICSの内部挙動の核心を述べよう。
1. 暗黙のグローバル的影響範囲:
`HANDLE CONDITION` は、CICSのタスク管理ブロック(TWAやCSAの周辺領域)に「どの例外に対してどこに飛ぶか」というポインタのテーブルを登録する。つまり、これはプログラム内の静的なジャンプではなく、動的なコンテキストスイッチなのだ。
2. サブルーチン(CALL)呼び出し時の悲劇:
メインプログラムで `HANDLE CONDITION` を有効にしたまま、別のサブプログラム(PL/Iの `PROCEDURE` または別モジュール)を `CALL` し、その中で何気なく `EXEC CICS READ` を実行したとする。サブプログラム側でエラーが発生した場合、親プログラム側のラベルへ制御が戻ろうとする。しかし、そのラベルはすでにスコープ外(あるいはスタックの巻き戻しにより消滅)している場合があり、最悪の場合、タスクの異常終了(ASRAアベンド)や無限ループを引き起こす。
さらに悪質なのは、PL/Iコンパイラの最適化オプション(`OPTIMIZE(FULL)` や `INLINE`)が効いている場合、コンパイラが「到達不能コード」や「未使用のラベル」と誤認し、ジャンプ先のコード構造を最適化の名の下に改変してしまうケースがあることだ。これにより、例外発生時のレジスタ退避が正常に行われず、パックデシマル(`FIXED DECIMAL`)の符号反転バグや、ポインタ(`POINTER`)の指すアドレスが狂うといった、極限のエッジケース踏み抜き事故に繋がる。
—
3. モダンな設計への回帰:PUSH/POPとNOHANDLEの活用
では、このレガシーな時限爆弾にどう立ち向かうべきか。
IBM公式マニュアルや最新のCICSプログラミングガイドでも強く推奨されている通り、現代の堅牢なCICSオンラインシステムでは、`HANDLE CONDITION` の裸での使用は原則禁止、あるいは厳格なカプセル化が求められる。
① PUSH/POP によるスコープの厳密な管理
サブルーチンやモジュール境界を跨ぐ場合は、必ず現在の例外処理状態をスタックに退避・復元させるべきである。
1
/ サブルーチン内での安全な例外制御の例 /
SAFE_SUB: PROC;
/ 現在の状態をCICSスタックにプッシュし、このブロック専用のエラー処理を定義 /
EXEC CICS PUSH CONDITION;
EXEC CICS HANDLE CONDITION
NOTFND(SUB-NOTFND);
EXEC CICS READ FILE(‘DEPTFILE’) …;
/ 処理終了後は必ずポップして元の状態に戻す /
EXEC CICS POP CONDITION;
RETURN;
SUB-NOTFND:
/ このサブルーチン固有のエラーハンドリング /
EXEC CICS POP CONDITION;
/ エラーフラグをセットして親へ戻すなどの処理 /
RETURN;
END SAFE_SUB;
② 現代的アプローチ:NOHANDLEとRESPコードの明示的チェック
JavaやC#等のモダン言語で例外処理(Try-Catch)に慣れたエンジニアであれば、暗黙のジャンプよりも、戻り値(ステータсコード)を評価する方式の方が圧倒的にコードの可読性が高く、バグを混入させにくいことがわかるはずだ。
CICSには、暗黙のジャンプを無効化し、自前で結果をハンドリングするための `NOHANDLE` および `RESP`(Response)オプションが用意されている。
1
DCL DUMP-CODE F 35; / 応答コード用フルワード整数 /
DCL RESP2-CODE F 35;
/ NOHANDLEを指定することで、暗黙のHANDLE CONDITIONをすべて無効化・無視する /
EXEC CICS READ FILE(‘EMPFILE’)
INTO(EMP-RECORD)
RIDFLD(WS-EMP-ID)
RESP(DUMP-CODE)
RESP2(RESP2-CODE)
NOHANDLE;
/ 戻り値を厳密に評価する(Javaの例外キャッチに近いアプローチ) /
SELECT (DUMP-CODE);
WHEN (DFHRESP(NORMAL))
/ 正常処理 /
CALL PROCESS_NORMAL;
WHEN (DFHRESP(NOTFND))
/ レコード未検出のハンドリング /
CALL PROCESS_NOTFND;
OTHERWISE
/ 予期せぬシステムエラーの捕捉とログ出力、ABEND発行 /
CALL HANDLE_CRITICAL_ERROR(DUMP-CODE, RESP2-CODE);
end;
この書き方であれば、PL/Iコンパイラの最適化によって制御フローが破壊されるリスクも極めて低く、のちのJava/C#へのマイグレーション時にも、`try-catch` やステータスチェックのロジックへそのまま1対1で翻訳(リライト)することが可能となる。
—
4. マイグレーションとアーキテクトへの提言
レガシーシステムのモダナイゼーションやオープン系移行プロジェクトにおいて、最も工数が膨らみ、かつバグの温床となるのは、こうした「CICSの暗黙の制御フロー(HANDLE CONDITIONやAbend Exit)」を新しいアーキテクチャへどうマッピングするかという問題だ。
COBOLやPL/Iで書かれた古いプログラムに散らばる `HANDLE CONDITION` を、安易にそのままの構造でJavaのフレームワーク(Spring Boot等)へ持ち込もうとしてはならない。それは古い遺物をそのまま新しい大地に埋めるようなものであり、のちの保守フェーズで必ず致命的な障害を引き起こす。
システムアーキテクトとして私たちが取るべきロードマップは明確だ:
1. 現状分析フェーズ: ソースコード解析ツールを用い、各プログラムにおける `HANDLE CONDITION` の宣言位置と、スコープの生存期間を完全に可視化する。
2. リファクタリングフェーズ: 移行前であっても、可能であれば該当コードを `RESP` / `NOHANDLE` 方式へと書き換え、メインフレーム上で十分にテストを重ねる(これにより、将来のオープン系移行時の動作差異リスクをゼロに近づけられる)。
3. ターゲットアーキテクチャの設計: Java/C#側のトランザクション管理ミドルウェア(Tmax OpenFrameや独自フレームワーク等)において、CICSのタスクライフサイクルと例外スタックがどのようにエミュレートされるかを深く理解し、単体テストで網羅的な検証を行う。
基幹システムの信頼性は、こうした細部――コンパイラの挙動、ミドルウェアのスタック制御、そして言語仕様の裏側――をどれだけ深く理解し、コントロールしているかによってのみ担保される。
子画面の向こうでシステムを待つユーザーのために。今日も私たちは、コードの細部に宿る神(あるいは悪魔)と向き合い続けている。
