COBOLからJavaへの移行という文脈で、平坦なテキスト文書と、レガシーコードから立ち上がる豊かに相互接続されたグラフ構造とを対比させたビジュアル表現。
Artificial IntelligenceSoftware EngineeringTechnology

AIは30年分のCOBOLを完璧に翻訳した。そしてデータベースを破壊した。

Ashutosh SinghalAshutosh Singhal2026年2月5日16 min

それは火曜日の夜のことで、私はまったく意味の通らないスタックトレースをじっと見つめていた。

私たちは、あるコアな取引モジュールをCOBOLからJavaへ移行しようとしている金融サービスチームと仕事をしていた。AIは自らの仕事をやり遂げていた——少なくとも、私たちはそう思っていた。生成されたJavaコードはきれいで、よく構造化され、エラーひとつなくコンパイルできた。単体テストも通った。通話に参加していた全員が、慎重ながらも楽観的だった。ところが、それをテスト環境にデプロイした途端、最初の電信送金でデータベースが破損した。

バグはJavaにあったのではない。Javaは構文的に完璧だった。バグは、AIが決して見なかったものの中にあった。

ある変数、TRN-LIMIT は、AIが翻訳したソースファイルではなく、実行チェーンのはるか数千行前にインクルードされたCOPYBOOKで定義されていたのだが——この変数にはREDEFINES 句が含まれていた。これはCOBOLの構文で、同じメモリアドレスが、まったく別のモジュールで設定されたフラグに応じて2つの異なるデータ型として解釈されるというものだ。AIはTRN-LIMIT を単純な数値フィールドと見なした。だが実際は違った。それは実行時のコンテキストに応じて整数を装うパック10進数だったのだ。AIは標準的な定義をハルシネーションで作り出し、Javaアプリケーションは破損したバイナリデータをデータベースの列に書き込んだ。

その夜、会議室でチームとともに何が問題だったのかを解剖するように調べながら、私はVeriprajnaで私たちが築いてきたすべてを作り変えることになる、あることに気づいた——AIが失敗したのは、それが愚かだったからではない。目が見えなかったから失敗したのだ。

誰も語りたがらない1兆5,200億ドルの問題

2025年の世界経済における、居心地の悪い現実がこれだ。銀行システムの43%はいまだにCOBOLで動いており、それらのシステムは全ATM取引の95%を処理している。フォーチュン500企業を動かすソフトウェア? そのおよそ70%は20年以上前に書かれたものだ。米国だけでも、技術的負債は推定1兆5,200億ドルにまで膨れ上がっている。

そして、このコードを書いた人々は退職しつつある。「いつか退職するかもしれない」ではない——彼らは今まさに去りつつあり、数十年分の組織的知識を持ち去っている。その一方で、連邦政府のIT予算の80%はレガシーシステムを生き永らえさせることに費やされ、新しいものに回せるのはわずか20%しかない。

私は、自社のモダナイゼーションの状況を、土台が崩れかけた家について語るように説明するCTOたちと、テーブルを挟んで向き合ってきた。直さなければならないのはわかっている、放っておけば悪化するのもわかっている。だが、試みたどの業者も、問題を実際に解決することなく、コストを膨らませただけだった。

数字がそれを裏付けている。レガシーモダナイゼーションのプロジェクトの70%から80%は、目標を達成できずに終わる。しかもそれが真実だったのは、生成AIが登場する以前のことなのだ。

なぜ誰もがGPTでこれを解決できると考えたのか?

気持ちはわかる。本当によくわかる。GPT-4が登場したとき、ソフトウェアコンサルティング市場は一気に過熱した。突如としてどの会社も「COBOLマイグレーション・アクセラレーター」を持つようになった——だが中身を覗いてみれば、それは基盤モデルをくるんだだけの薄いラッパーだった。COBOLの段落を貼り付ければ、Javaのメソッドが返ってくる。まるで魔法だ。

私の共同創業者と私は、これらのツールの評価に何週間も費やした。クライアント環境の実際のレガシーコードを入力し、その出力を確認するのだ。構文はほぼ常に正しかった。コードはコンパイルできた。そしてそれから、診断が信じられないほど難しい形で失敗するのだ。というのも、コードのは正しく見えても、その意味が間違っていたからだ。

最も危険なバグは、システムをクラッシュさせるものではない。誰かが気づくまでの半年間、データを静かに破壊し続けるものだ。

この問題はアーキテクチャに起因するもので、突き詰めれば大規模言語モデルがどのように情報を処理するかに行き着く。LLMは、入力のさまざまな部分の重要度を評価するためにアテンション機構を用いる。最新のモデルは最大100万トークンものコンテキストウィンドウを誇る。しかし研究によって、ある現象が実証されている。それは「中間での見失い(Lost in the Middle)」効果と呼ばれるものだ。LLMはU字型の性能曲線を示し、プロンプトの冒頭と末尾の情報はよく想起するものの、中間にあるものについては性能が著しく低下する。

モダナイゼーションのプロジェクトでは、1本のCOBOLプログラムが数千行に及ぶことがあり、しかもそれ自体が数千行に及ぶコピーブックを参照している。もしMAX-TRANSACTION-LIMIT の定義が、その巨大なコンテキストの中間に位置していたら、AIは統計的にそれを見落とす可能性が高い。そして何かを見落としたとき、AIは立ち止まって尋ねたりはしない。ハルシネーションを起こすのだ。もっともらしい定義をでっち上げ、先へ進んでしまう。

コードをテキストのように扱うと何が起きるか?

標準的なベクトルRAG検索が重要なコードの依存関係を見落とすのに対し、グラフベースの検索がファイルをまたいで論理的なつながりをたどる様子を、並べて比較した図。

これこそ、私が「AIラッパー」のエコシステム全体が犯していると見ている核心的な誤りであり、初期のころにある投資家候補と繰り返し交わした論争でもある。彼は私たちのアプローチ——コードリポジトリのナレッジグラフを構築するというもの——を見て、こう言った。「もっと大きなコンテキストウィンドウを使えばいいのでは? GPT-5がこれを解決してくれる」

私はノートパソコンでCOBOLプログラムを開いた。「ACCOUNT-BALANCE の定義を見つけてくれ」と私は言った。

彼はそのファイルを検索した。見つからなかった。なぜなら、それはそのファイルにはなかったからだ。それはコピーブックの中にあり、47行目の文でインクルードされていて、そのコピーブック自体が、まったく別のチームが保守する共有データ部を参照していた。

「では、あなたがLLMだと想像してみてほしい」と私は言った。「あなたは『決済処理』に関連するコードを、ベクトル類似度検索で探している。『payment(決済)』という語に言及した5つのチャンクは見つかるだろう。だが、GlobalVarDef.cbl という、決済ロジックで使われる税率を定義したファイルは、完全に見落とすことになる——なぜなら、そのファイルはどこにも『payment』という語に言及していないからだ」

標準的なRetrieval-Augmented Generation、すなわちRAG——ほとんどのAIコーディングツールがLLMに知識を追加するために用いる手法——は、テキストの類似度に基づいてコンテキストを取得する。コードをベクトルに変換し、類似したベクトルを見つけるのだ。これはFAQチャットボットでは見事に機能する。だがコードに対しては、致命的なほど不十分だ。

コードは自然言語ではない。「猫がマットの上に座った(The cat sat on the mat)」は、50ページ前に何を読んだかに関係なく、ほぼ同じ意味を持つ。しかしx = y + 1 は、何も意味しない——xy の定義、型、そして現在の状態を知らなければ。これらは別のファイル、別のモジュールで定義されているかもしれないし、親クラスから継承されているかもしれない。

私はこの構造的な問題について、私たちの研究のインタラクティブ版で詳しく書いた。手短に言えば、こうだ。ソフトウェアはテキストではない。ソフトウェアはグラフなのだ。

より良いラッパーを作るのをやめた夜

ある瞬間があった——今でもはっきり覚えている——チームがアーキテクチャについて議論していたときのことだ。私たちの前には2つの道があった。第一の道は、より賢いRAGパイプラインを構築すること。より良いチャンク分割、より良い埋め込み、より良いプロンプト。ラッパー方式を、十分に機能するようになるまで繰り返し改良していく。第二の道は、テキストベースのパラダイムを完全に捨て去り、コードを本来あるべき姿——論理の関係システム——として扱うこと。

第一の道のほうが速かった。第一の道は投資家に理解される道だった。第一の道には、すでに市場の需要を証明している競合が十数社もいた。

私の主任エンジニアはホワイトボードを引き出し、COBOLプログラムをグラフとして描いた。変数、関数、コピーブック、データベーステーブルをノードとして。エッジにはCALLSREADSUPDATES_TABLEIMPORTS_COPYBOOK を。それから彼女は依存関係の連鎖をたどってみせた。モジュールAがモジュールBを呼び出し、それが変数Xを変更し、その変数はまったく別のディレクトリにあるモジュールCによって読み取られる。

「その連鎖を、ベクトル検索に見つけさせてみて」と彼女は言った。

誰にもできなかった。

その夜、私たちは今で言うところのリポジトリ認識型ナレッジグラフ(Repository-Aware Knowledge Graph) ——コードの静的構造(抽象構文木、コールグラフ)と、ビジネスロジックの意味的な内容(ドキュメント、コメント、変数の意図)とを統合した、ひとつのグラフデータベース——を構築することを決意した。私たちはより優れた翻訳機を作ろうとしていたのではない。地図を作ろうとしていたのだ。

30年分のCOBOLを、どうやって地図に変えるのか?

ナレッジグラフの構築プロセスを示す4段階のパイプライン図。構造的パース、エンティティ/関係の抽出、リポジトリ横断のシンボル解決、そして推移閉包の計算。

このプロセスには4つの段階があるが、実装の詳細についてはここでは省く——それらは私たちの完全な技術的詳説で見つけられる。だが、その概念こそが重要だ。なぜなら、それがこのアプローチがラッパーの失敗するところで機能する理由を説明してくれるからだ。

第一に、私たちはコードをテキストとしてではなく、構造として解析する。 標準的なRAGパイプラインは「素朴な分割(naive splitting)」を用いる——500トークンごとにファイルを切り分けるため、関数のシグネチャがその本体から切り離されてしまうことがよくある。私たちはTree-sitterのようなパーサーを使って抽象構文木を生成し、それはコードの論理的な境界を尊重する。関数は、テキストのランダムな一区切りではなく、論理の完結した単位として扱われる。

第二に、私たちはエンティティと関係を抽出する。 クラス、段落、変数、データベーステーブル、APIエンドポイント——これらがノードになる。それらの間のエッジ——CALLSUPDATES_TABLEDEFINES_VARIABLE ——が、それらをつなぐ結合組織になる。こうして私たちはグラフに問い合わせできる。「CUSTOMER-ID フィールドを更新するすべての段落を見せてくれ」。正確な結果が、瞬時に返ってくる。grepで同じことをやってみるといい。

第三に——そしてここからが面白くなるのだが——私たちはリポジトリ全体にわたってシンボルを解決する。 標準的なパーサーは、ファイルAのACCT-NUM と、ファイルBのACCT-NUM を、2つの異なる文字列として見なす。私たちのシステムは、その両方が共有コピーブック内の同じエントリを指していると判断し、それらをひとつのノードに統合する。私たちはドキュメントとコードも統合する。もしPDFの要件定義書が「User API」を説明していて、コードにUserAPI という名前のクラスが含まれていれば、システムは意図と実装を結びつける。

第四に、私たちは推移閉包を計算する。 あの銀行の失敗を覚えているだろうか? AはBに依存し、BはCに依存する。そしてAIはAは見たがCは見落とした。私たちのグラフは深くたどっていく——AからBへ、BからCへ——ことで、すべての変数の根本の定義を特定する。AIがモジュールAのコードを生成するとき、たとえモジュールCがまったく別のディレクトリやリポジトリにあっても、モジュールCから正しい定義をインポートするのだ。

なぜ標準的なRAGはコードマイグレーションで失敗するのか?

人々はいつもこれに反論してくる。「RAGはコードでも問題なく機能する」と彼らは言う。「もっと良い埋め込みを使えばいいだけだ」と。

ベクトル類似度検索が完全に破綻する3つのシナリオを挙げてみよう。

ある開発者がAccountAcct に名前変更する。ロジックはまったく同じなのに、意味的な類似度は下がってしまう。FNC-001 という名前の関数は利息計算を行うが、コメントを一切含んでいない——「interest calculation(利息計算)」で検索しても、それが見つかることは決してない。そして最もよくある失敗がこれだ。ベクトルRAGは「payment(決済)」に言及する単体テストとUIコメントは取得するのに、変数名がクエリの語と一致しないために、中核となるビジネスロジックを見落としてしまう。

グラフベースの検索は「どのテキストが似ているか?」とは問わない。「何が論理的につながっているか?」と問うのだ——そしてその違いこそが、コンパイルが通るだけのコードと、実際に動くコードの違いなのだ。

私たちがGraphRAG と呼ぶものは、類似度ではなく構造に基づいて動作する。誰かが「決済ロジックをリファクタリングして」と頼むと、システムはベクトル検索を使って入口——たとえばProcessPayment という段落——を見つける。だがそこで止まる代わりに、グラフのエッジをたどっていく。CALLS エッジ経由でサブルーチンを、READS エッジ経由で変数定義を、INCLUDES エッジ経由でコピーブックを引き込む。これらのつながった断片は、テキストとしては似ていないかもしれないが、論理的には切り離せない。

研究によれば、GraphRAGはマルチホップ推論——数ステップ隔たった事実どうしを結びつけること——において、ベクトルRAGを大きく上回る。ソフトウェアでは、深刻なバグのほぼすべてがマルチホップ推論の失敗である。もし私がモジュールAの金利ロジックを変更したら、モジュールZのどの帳票画面が壊れるのか? ベクトルRAGはこれに答えられない。だがグラフは答えられる。両者を結ぶ関数呼び出しの連鎖をたどるからだ。

GOTO問題(あるいは:なぜCOBOLはAIにループを幻覚させるのか)

COBOLのGOTOジャンプが、制御フローグラフのエッジとしてマッピングされ、次いで適切なJavaの制御構造(ループ、条件分岐、リターン)へとパターンマッチングされる様子を示す図。

私たちをあやうく打ちのめしかけた、ある特定の技術的課題について話したい。というのも、それはこの仕事が人々の想像よりもはるかに難しい理由を、よく物語っているからだ。

COBOLにはGOTO 文がある。Javaにはない。GOTO は、プログラムの実行を任意の場所——前へ、後ろへ、別のブロックの真ん中へ——ジャンプさせることを可能にする。それは、あらゆる計算機科学の教授が警告する「スパゲッティコード」を生み出す。そしてGOTO を翻訳することは、構文の問題ではない。トポロジー(位相)の問題なのだ。

私たちは、3つの異なる商用AIツールが、GOTO を多用したCOBOLモジュールの翻訳を試みる様子を見守った。1つ目は、本番環境でStackOverflowError を引き起こしていたであろう再帰的な関数呼び出しを生成した。2つ目は、while(true) という、終了条件のないループを生み出した。3つ目——個人的にお気に入りだ——は、元のコードには存在しなかった制御フローを、ただでっち上げた。それはもっともらしく見えた。そして完全に間違っていた。

私たちのアプローチはこうだ。GOTO のジャンプ先を、制御フローグラフのエッジとしてマッピングする。それからグラフ上でパターン認識を行う。GOTO が前のラベルへ後戻りする? それはループだ。GOTO がブロックを飛び越す? それは条件分岐だ。GOTO が終了段落へ向かう? それはreturn文だ。グラフの構造に導かれて、AIはこれらのジャンプをwhile ループ、if/else ブロック、あるいはbreak/continue 文へとリファクタリングする。

グラフがなければ、AIは当てずっぽうをしている。グラフがあれば、それはエンジニアリングだ。

チャットボットとエージェントの違い

私たちはチャットボットを作らない。この点ははっきりさせておく必要がある。というのも、市場は「コードベースとチャットできる」ツールであふれているが、それらは同じものではないからだ。

チャットボットは、あなたの質問を受け取り、それをGPT-4に送り、返ってきたものをそのまま返す。出力が間違っていれば、あなたが手作業でデバッグする。それが、市場に出回るあらゆるAIラッパーのワークフローだ。

私たちが展開するのは、計画し、実行し、自己修正する自律型エージェントだ。エージェントは対象のCOBOLファイルのASTを解析し、依存関係を特定し、ナレッジグラフに問い合わせ、Javaコードを生成し、それからサンドボックス内でそれをコンパイルする。もしコンパイラがエラー——「変数が見つかりません」——を投げれば、エージェントはそのエラーを読み、欠けている依存関係をグラフに問い合わせ、再生成する。それから、元のCOBOLの実行トレースから導き出した単体テストを実行して、振る舞いが等価であることを検証する。

このコンパイル・修正ループは、検証の負担を人間からシステムへと移す。だが——そしてこれは規制産業においては極めて重要なのだが——ナレッジグラフは完全な解釈可能性を提供する。開発者は、AIがすべての判断を下した理由を正確に見ることができる。「AIがcom.bank.logic をインポートしたのは、COPYBOOK-X への依存を見つけたからだ」と。「私を信じて、私はAIだから」ではない。その代わりに、このロジックの引用の連鎖がここにある、というわけだ。

銀行業務では、コードの一行一行が監査可能でなければならない。「たぶん」正しくやったブラックボックスを本番投入することはできない。自らの過程を示せるシステムが必要なのだ。

デッドコードはどうなのか?

私を驚かせたことのひとつ。レガシーシステムは、もう誰も使っていないコードであふれている。古いキャンペーン、廃止された製品、1997年のデバッグルーチン。テキストベースのAIは、与えられたものをすべて移行してしまう——稼働中のコードとデッドコードを区別できないのだ。

私たちのコールグラフは、到達不能なノード——入ってくるエッジのない、つまり何からも呼び出されていない段落やファイル——を特定する。私たちはこのデッドコードを、マイグレーションが始まるに、削除対象として印を付ける。私たちの経験では、これによってコードベースは通常20〜30%削減される。それは些細な最適化ではない。作業の4分の1と、攻撃対象領域の4分の1を排除することなのだ。

「コンテキストウィンドウをもっと大きくすれば解決するのでは?」

私は今でも絶えずこの質問を受ける。その前提は、GPT-5やClaude 4が1,000万トークンを扱えるようになれば、「中間での見失い」問題は消えてなくなる、というものだ。

消えはしない。その理由がこれだ。

たとえアテンションの劣化が改善しても——そして改善はするだろう——あなたは依然としてテキスト検索をしている。論理的なつながりをたどる代わりに、依然として似た文字列を探しているのだ。100万トークンのコンテキストウィンドウも、必要な変数が、翻訳対象のファイルとキーワードをひとつも共有しないファイルで定義されていたら、何の役にも立たない。問題はウィンドウの大きさではない。問題は、ウィンドウが間違ったものを見ているということなのだ。

私が耳にするもうひとつの反論はこうだ。「ナレッジグラフは構築にコストがかかる」。確かにそうだ。リポジトリ全体を解析し、シンボルを解決し、推移閉包を計算する——それは相当な初期投資だ。だが、代替案を考えてみてほしい。大規模なCOBOLシステムの手作業による移行は、数千万ドルの費用がかかり、何年もかかる。ラッパーベースのAI移行は初期費用こそ安いが、高コストな人手によるデバッグを要するハルシネーション由来のバグを、絶え間なく生み出し続ける。グラフベースのアプローチは、初期構築コストは高いが、手戻りコストは劇的に低い。マッキンゼーのデータによれば、生成AIはコーディング作業を50%削減できるが、それは正しく導入された場合に限る。私たちは、標準的なAIツールと比べて、開発者の生産性が2倍から3倍向上するのを見てきた。とりわけ、開発者が変数がどこで定義されているかを探すために何時間も費やすのをやめられるからだ。

地図こそが資産だ

最初に理解しておきたかったことがこれだ。ナレッジグラフは、単なる移行のためのツールではない。それは永続的な資産なのだ。

いったんコードベースがグラフとして存在すれば、それはシステムの生きた表現であり続ける。新しいJavaコードが進化するにつれ、グラフも更新される。常に最新の、自動化されたドキュメントが手に入る。アーキテクチャの逸脱検出も手に入る——新しいコードが、あなたの定義したモジュール性のルールに違反すれば、システムが警告してくれる。必要なときに影響分析も手に入る。「このメソッドを変更したら、何が壊れるのか?」

モダナイゼーションは一度きりの出来事ではない。それはライフサイクルだ。それを——開始日と終了日のある——プロジェクトとして扱う組織は、5年後には元の場所に逆戻りし、新世代の技術的負債に溺れることになる。

コードはテキストではない

私が何度も立ち返る教訓——あの火曜日の夜、スタックトレースを見つめていたときのもの——は、一見単純に思える。コードはテキストではない。そして、それをテキストとして扱うツールは、正しく見えて誤って振る舞う結果を生み出す。

「AIラッパー」経済の全体が、カテゴリー錯誤の上に築かれている。それは、LLMが言語処理に卓越しているのだから、コード処理にも卓越しているに違いない、と思い込んでいる。だがコードは言語ではない。コードはグラフだ——依存関係、データフロー、そして状態変化からなる、密で相互接続された、複数の次元に同時に存在するシステムなのだ。それをテキストベースのツールで近代化しようとするのは、通りの名前のリストだけを頼りに、地図なしで都市を歩き回るようなものだ。あなたは「中間で見失う」ことになる。

私たちは地図を作った。そしてそれは機能する——私たちが基盤モデルを作るチームより賢いからではなく、私たちが別の問いを立てたからだ。彼らはこう問うた。「どうすればAIにもっとうまくテキストを理解させられるか?」私たちはこう問うた。「そもそも問題はテキストではないとしたら?」

レガシーモダナイゼーションの未来は、より大きな言語モデルではない。それは、ソフトウェアが実際に機能するそのままに——文字列としてではなく、構造として——ソフトウェアを理解するシステムだ。

それが、私たちがVeriprajnaで賭けたものだ。日々、また別の組織が、AIの生成したJavaが見事にコンパイルできて、破滅的に失敗することに気づいている。日々、構文的な翻訳と意味的な理解のあいだのギャップは、無視するにはますます高くつくものになっていく。そのギャップを埋める組織は、単にコードを近代化するだけではない。彼らはついにそれを理解することになる——その多くは、初めて。

Related Research

Also Published On