Claude CodeでCOBOLを移す — GnuCOBOLの出力突き合わせを検証ループに固定
Claude CodeでCOBOLを読み解き移すとき、GnuCOBOLのcobcで元の出力を取り、移行後の出力とdiffで突き合わせる検証ループと、ずれの原因を絞る実行時設定をCLAUDE.mdに固定する手順をまとめます。
Claude CodeにCOBOLを読ませて別の言語へ移すなら、合格条件は「元のCOBOLを実際に動かして取った出力と、移行後の出力が一致すること」に置きます。COBOLのコンパイラにはGnuCOBOLのcobcが使えます。この記事では、cobcで元の出力を固定する手順、diffで突き合わせる検証スクリプト、ずれたときに原因を絞る実行時設定、それらをCLAUDE.mdとhooksに載せる書き方を順に示します。
COBOLの移行は、読んだ内容の要約が正しいかどうかを、人が目で確かめにくい作業です。要約が読みやすいほど、間違いに気づきにくくなります。そこで、読解の正しさは文章でなく実行結果で判定する形にします。言語を問わない進め方はClaude Codeのコードモダナイゼーションにあり、ここではCOBOL固有のコマンドと設定だけを扱います。
GnuCOBOLのcobcを「元の挙動の物差し」にする
GnuCOBOLは、COBOLのソースからネイティブ実行ファイルを作るフリーのコンパイラです。公式サイトによれば、Linux、BSD、macOS、Windowsなどで動き、現行リリースは3.2です。コンパイラ本体のコマンドはcobcで、検証に使うのは次の最小形です。
cobc --version
cobc -x -o build/billing src/billing.cob
./build/billing-xは、メインの関数を含めた実行ファイルを作るオプションです。-oを省くと、実行ファイル名はソースの拡張子を除いた名前になります。-jを付ければコンパイル後にそのまま実行まで進みます。
ソースの書式は最初に確かめる点です。既定は固定形式で、7桁目が指示領域、8〜72桁がプログラム本文です。自由形式のソースは-freeを付けないと読めません。Claudeに「コンパイルが通りません」と報告させる前に、どちらの形式で書かれているかをCLAUDE.mdに書いておきます。
| 書式 | cobcのオプション | 見分け方 |
|---|---|---|
| 固定形式(既定) | cobcのオプションなし、または-fixed | 見分け方先頭6桁が連番領域、7桁目が*や-などの指示 |
| 自由形式 | cobcのオプション-free | 見分け方1桁目から本文が始まり、*>でコメントを書く |
コンパイル時の警告も、読み違いの温床を拾う手段になります。-Wallは「避けやすいのに疑わしい書き方」の警告をまとめて有効にし、-Wextraはさらに方言に依存しない警告をすべて加えます。ただし桁あふれに関わる-Wtruncate、72桁より後ろの文字を拾う-Wcolumn-overflow、到達できない文を示す-Wunreachableは-Wallに含まれず、個別に指定します。cobc -x -Wall -Wtruncate -Wcolumn-overflowで警告を出し、ログに残った箇所は移行前にClaudeへ読ませておきます。
出力を突き合わせる検証ループの形
ループは、移行前のゴールデン出力を先に取り、移行後の出力と比べる2段で組みます。
検証ループの流れ
- 1
元のCOBOLをcobcでコンパイルする
警告を見落とさないよう、
cobcの出力は捨てずにログへ残します。 - 2
固定した入力で実行し、ゴールデン出力を保存する
出力ファイルと標準出力の両方を
golden/に置き、以後は書き換えません。 - 3
移行後のプログラムを同じ入力で実行する
出力は
actual/に分けて置きます。 - 4
diffで突き合わせ、差分だけをClaudeに読ませる
一致なら次のプログラムへ進み、差分があれば原因を絞ってから直します。
この流れを1本のスクリプトにまとめた例です。COBOL側のSELECT文がASSIGN TO "INFILE"とASSIGN TO "OUTFILE"の形だと仮定しています(後述の環境変数の仕組みを使います)。
#!/usr/bin/env bash
# scripts/verify-cobol.sh <プログラム名>
set -euo pipefail
p=$1
mkdir -p build golden actual
# 1. 元のCOBOLをコンパイル(警告はログに残す)
cobc -x -o "build/$p" "src/cobol/$p.cob" 2> "build/$p.cobc.log"
# 2. ゴールデン出力は初回だけ作る(既存なら触らない)
if [ ! -f "golden/$p.out" ]; then
DD_INFILE="testdata/$p.in" DD_OUTFILE="golden/$p.out" "./build/$p"
fi
# 3. 移行後のプログラムを同じ入力で実行
./migrated/run.sh "$p" "testdata/$p.in" "actual/$p.out"
# 4. 突き合わせ
diff -u "golden/$p.out" "actual/$p.out"migrated/run.shは、移行先の言語に合わせて書き換える部分です。JavaならJARの起動、Pythonならスクリプトの起動を1行で呼べる形にします。diffが差分を返すと終了コードが非0になり、set -eでスクリプトが止まります。Claudeはその出力をそのまま読んで、どの行がずれたかを特定できます。
差分が出たとき、まず疑う環境側の設定
移行のミスだと思い込んで修正を始める前に、COBOL側の実行環境の設定で出力が変わっていないかを確かめます。GnuCOBOLの設定には、出力の見た目を変えるものがあります。ここを固定しないと、同じプログラムでも環境によって差分が出ます。
| 設定 | 効果 | ずれ方の例 |
|---|---|---|
DD_<名前>環境変数 | 効果ASSIGN TO "名前"のファイル名を実行時に解決する | ずれ方の例入出力が意図しないファイルを指す |
COB_FILE_PATH | 効果データファイルを置く既定の場所。既定は現在のディレクトリ | ずれ方の例別の場所にある同名ファイルを読む |
COB_LS_FIXED | 効果LINE SEQUENTIALを固定長にするか、末尾の空白を削るか。既定はfalse | ずれ方の例行末の空白の有無で差分が出る |
COB_LS_VALIDATE | 効果LINE SEQUENTIALの読み書き時にデータを検証する。既定はtrue | ずれ方の例状態コード09(読み込み)や71(書き込み)で止まる |
-fbinary-truncate | 効果2進項目をPICTUREに合わせて切り詰める。既定はyes | ずれ方の例桁あふれの結果が変わる |
-fnotrunc | 効果-fbinary-truncateとは逆に、2進項目をPICTUREに合わせて切り詰めない | ずれ方の例桁あふれした値がそのまま残る |
-fsign=ASCII / -fsign=EBCDIC | 効果符号付き表示項目の符号の持ち方。既定はASCIIのマシンでASCII | ずれ方の例符号付きの数字の末尾1桁が文字化けしたように見える |
COB_LS_SPLIT | 効果LINE SEQUENTIALで想定より長い行をどう扱うか。既定はtrue(複数レコードに分割して状態コード06) | ずれ方の例falseなら切り詰めて状態コード04、行数が変わる |
COB_LS_NULLS | 効果LINE SEQUENTIALにDISPLAY以外のデータが入るとき、前にx"00"を挿入して退避するか。既定はfalse | ずれ方の例COMP項目を含む行の中身が食い違う |
ファイル名の解決は、設定が有効なときDD_DATAFILE、dd_DATAFILE、DATAFILEの順に環境変数を探し、どれも無ければ文字列そのものをファイル名にします。先のスクリプトでDD_INFILEとDD_OUTFILEを渡していたのはこのためです。検証の入出力は必ず環境変数で渡し、パスを決め打ちしない形にしておくと、ゴールデン出力と移行後で同じ入力を使い回せます。
COBOLの実行環境がメインフレームだった場合は、互換のための-std=ibmや-fmove-ibm(MOVEをIBM流の左から右、1バイトずつの動きにする)があります。EBCDICからの変換表は-febcdic-tableで選びます。これらは元の環境の挙動に近づけるためのものなので、どのオプションで取ったゴールデン出力かを、ファイルと一緒に残します。何も付けずに取った出力と、-std=ibm付きで取った出力は、別物として扱います。
境界値の入力でゴールデン出力の網羅度を上げる
突き合わせが守れる範囲は、入力データの範囲で決まります。代表的な1本の入力だけで一致しても、COBOLの実行時設定が効く場面は通っていません。testdata/には、次のような入力を足しておきます。
- 空ファイル(0レコード)
- 項目の桁数いっぱいまで埋めた行
- 想定より長い行(
COB_LS_SPLITの挙動が出る) - 符号付きの数値の正・負・ゼロ
- 桁あふれする計算になる値(
-fbinary-truncateの差が出る)
ゴールデン出力は入力ごとに取り、golden/<名前>.<ケース>.outのように対で置きます。ケースを足したら、元のCOBOLから取り直したものだけを追加し、既存のゴールデン出力は触りません。
ずれの原因を絞るデバッグオプション
差分が環境でなく処理に起因すると分かったら、COBOL側で何が起きたかを見ます。cobcには、実行時の検査と追跡のオプションがあります。
# 実行時の検査をすべて有効にしてコンパイル
cobc -x --debug -o build/billing src/cobol/billing.cob
# 実行した段落・節のログを出す(コンパイル時に -ftrace)
cobc -x -ftrace -o build/billing src/cobol/billing.cob
COB_SET_TRACE=true COB_TRACE_FILE=build/billing.trace ./build/billing--debugは実行時の検査を全部有効にします。-ftraceは実行された段落と節をログに出し、-ftraceallは文の単位まで出します。トレースは実行時にCOB_SET_TRACEで有効にし、出力先はCOB_TRACE_FILEで決めます。標準出力の比較を壊さないよう、出力先はファイルへ逃がしておくのが安全です。
トレースをClaudeに渡すと、移行後のコードと同じ分岐を通っているかを、段落名で突き合わせられます。差分が出た行がどの段落の処理かが分かれば、読み解きの誤りが入った箇所を絞れます。
CLAUDE.mdに固定する内容
ここまでの手順をCLAUDE.mdに書き、毎回の指示を省きます。CLAUDE.mdは、毎回のセッションで読み込まれる指示ファイルです。ファイルが長くなるほど守られにくくなるため、公式は1ファイル200行以内を目安にしています。COBOL固有の内容だけを短く置きます。
CLAUDE.mdの断片(例)
## COBOL移行の検証ルール
- 元のCOBOLは `src/cobol/`。書式は固定形式(自由形式のものは `-free` を付ける)
- 元の挙動の基準は GnuCOBOL の cobc。`cobc -x -o build/<名前> src/cobol/<名前>.cob` でビルドする
- 入出力は環境変数 DD_<名前> で渡す。ファイル名を決め打ちしない
- `golden/` 以下は元のCOBOLの出力。編集・上書き・削除をしない
- 変換後は `scripts/verify-cobol.sh <名前>` が exit 0 になるまで直す
- diff が出たら、まず COB_LS_FIXED と -fbinary-truncate の設定差を疑い、
次に `-ftrace` のログで通った段落を比べる
- 期待値を移行後のコードを見てから作らない「golden/を触らない」を文章で書くだけでなく、設定でも守らせます。Claude Codeの権限設定でgolden/への書き込みを許可しない運用にすると、期待値を書き換えて一致させる近道を塞げます。
編集のたびにコンパイルを回すhooks
PostToolUseイベントをEdit|Writeのmatcherで使うと、ファイルを編集するツールの実行後だけフックが動きます。公式のガイドは、編集されたファイルのパスをjqで取り出してPrettierに渡す例を載せています。同じ形で、移行先のファイルが書き換わるたびに検証を回せます。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "scripts/verify-changed.sh"
}
]
}
]
}
}verify-changed.shは、変更されたファイルから対応するプログラム名を割り出し、verify-cobol.shを呼ぶ薄いスクリプトにします。コマンドは標準入力でイベントのJSONを受け取るので、パスの取り出しにはjq -r '.tool_input.file_path'が使えます。hooksの終了コードの扱いはイベントごとに違い、終了コード2が「実行を止める」意味を持つ場面もあれば、メッセージの表示にとどまる場面もあります。細部はClaude Codeのhooksでテストを自動実行すると公式のhooksリファレンスで確かめます。
つまずきやすい点
落とし穴
ゴールデン出力を取った条件を残さない
ゴールデン出力だけ保存して、取ったときのcobcのオプションと環境変数を残さないと、あとで再現できません。-std=ibmの有無、COB_LS_FIXEDの値、-fsignの指定が違えば、同じソースでも出力は変わります。golden/にはオプションと環境変数を書いた記録ファイルを一緒に置きます。移行後のコードで期待値を作ると検証が空になる点は、上のCLAUDE.md断片に書いたとおりです。
もう一つ、COBOLの実行ファイルが読む入力ファイルは、固定長のレコードを前提にしたものが少なくありません。COB_LS_FIXEDや行末の改行の扱いが違うと、同じ内容でも差分が出ます。差分の最初の数行は、中身でなく空白と改行の違いだけではないかを先に疑ってください。diff -uで目立たないときは、cat -etで行末の文字を表示して比べます。
同じ検証ループの考え方は、言語をHaskellに替えたstack buildとhlintを検証ループに固定する手順にもあります。コンパイラが返す結果を人の目でなく機械に読ませる、という点で共通です。CLAUDE.md全般の書き方はCLAUDE.mdのパターン集にまとまっています。
まとめ
COBOLの移行で、Claudeの読解を信用する根拠になるのは、cobcで取った元の出力との一致だけです。実行ファイルの作り方、書式の違い、入出力を渡す環境変数、出力を変える実行時設定、トレースのログを先にCLAUDE.mdへ固定しておけば、差分が出たときの調べ方が毎回同じ順になります。ゴールデン出力は、取ったときのオプションと環境変数、境界値の入力とセットで残すと、あとから条件を変えて再検証できます。