世界を動かす技術を、日本語で。

理解の負債:LLM生成コードの時限爆弾

概要

  • LLM生成コード の修正・理解にかかる時間が増加傾向
  • 「comprehension debt」 という新たな技術的負債の発生
  • コードレビューや再作業による 生産性の鈍化
  • 未読・未検証のコード がリポジトリに増加
  • 今後のソフトウェア保守性への 深刻な影響

LLM生成コードと「comprehension debt」の増大

  • Large Language Models(LLM) によるコード生成の普及
  • LLM生成コードの 修正・理解に要する時間の増加
  • レガシーシステム保守時と同様、 コードの意図や動作の把握 が前提条件
  • LLMは 膨大な未読コード を高速で量産
  • 品質重視チームは レビュー・再作業 を徹底、結果的に 時短効果の相殺
  • 一方で、 未読・未検証のままコードを投入 するチームも増加傾向
  • 理解が追いつかない速度で生成されるコード による「comprehension debt(理解負債)」の蓄積

comprehension debtのリスクと現状

  • ソフトウェアが利用され続ける限り、 生成コードの修正ニーズ は必然
  • 一部では「 ツールで再生成すればよい」との楽観論
  • 実際には、 LLMで100%修正できるケースは少数
  • 「doom loop」 :LLMで修正を試みても解決できず、何度もやり直す悪循環
  • 最終的には人間による直接編集が不可避
  • comprehension debt は、 コード理解にかかる追加コスト として確実に増加
  • 理解負債の山 が急速に拡大中

今後の対応と課題

  • コード生成の効率化保守性のバランス が重要課題
  • レビュー体制の強化LLM活用ルールの策定 の必要性
  • 理解負債の可視化・管理 による長期的な品質維持
  • 人間によるコード理解・編集能力 の再評価
  • LLM依存リスク に対する組織的な意識の醸成

Hackerたちの意見

これはもともとあった問題だけど、LLMに頼ることでさらに悪化してるね。Naur(https://gwern.net/doc/cs/algorithm/1985-naur.pdf)が「理論構築」と呼んでたけど、>「プログラムの死は、その理論を持つプログラマーチームが解散するときに起こる。死んだプログラムは、コンピュータで実行され続け、有用な結果を生むこともある。実際の死の状態は、プログラムの修正要求に対して知的に答えられなくなったときに見えるようになる。プログラムの復活は、新しいプログラマーチームによってその理論を再構築することだ。」ラモートは「プログラミング ≠ コーディング」と呼んでいて、プログラミングは「達成したいこととその方法」で、コーディングは「コンピュータにどうやってやらせるか」って言ってる。これにはすごく同意するよ。たとえ開発チームが理論構築やモデリングのフェーズをスキップしても、コードを打ち込むときにモデルの一部を受動的に吸収してると思う。LLMが置き換えるのは、その偶発的なモデル構築の最後の手段だと思う。モデルや理論が必要だと思わないプログラマーと、LLMが自分たちを速くしていると報告している人たちの間には強い相関関係があるんじゃないかな。

「モデルや理論が必要だと思わないプログラマーと、LLMが自分たちを速くしていると報告している人たちの間には強い相関関係があると思う。」私は、コンピュータや関連技術から完全に離れているときに、ソフトウェアプロジェクトでより多くの価値を生み出せるという経験がある。自分が何を求めているのかが明確だと、コードがどれだけ早く進むかは驚くべきことだよ。この時点でLLMは非常に役立つことができる。なぜなら、その幻覚がすぐに自分の視点でフラグを立ててくれるから。もし自分が何を求めているのかわからなければ、これがどう機能するのかは見えない。私は、毎日何時間も真っ白なキャンバスを見つめる理由が本当に理解できない。LLMはあなたを解放して正しい方向に進ませるかもしれないけど、むしろ無駄な追跡に引き込まれる可能性が高いと思う。私が解決した最後の10/10の難しい問題は、たぶんキッチンで玉ねぎを切っているときに起こった。

「理論構築」 LLMが生成したコードの「理解の負債」をプログラミングを理論構築と結びつけたのは洞察に満ちているね。これはプログラミングの活動を超えて、思考や理解のプロセス全般に当てはまると思う。LLMが生成したコンテンツ、文章や視覚芸術も含めて、これはコードに相当するもので、表面的には最終結果として人々が見るものだ。でも、もし人がその生産に関与していないと、何を意味し、どう機能するのかの理論を構築することはできない。詳細を通じて全体をまとめることができないと、表面的な理解しか得られない。たとえLLMが進化してこの「理論構築」を自分で行えるようになったとしても、人間が関与しない人工的な理解に何の意味があるのか?それは非常に役立つかもしれないし、価値もあるかもしれないけど、最終的には機械に考えさせる方が便利になって、人々は理解するスキルを失っていくかもしれない。

これが、AIコーディングで他の同僚よりも少し成功している理由の一部だと思う。LLMが出る前のワークフローは、何かのクソみたいなバージョンを素早く作って、理解を深めるために再構築する(場合によってはプロトタイプを捨てることも)って感じだった。多くの思想的リーダーがこの一般的なアプローチ(迅速なプロトタイピング、継続的なリファクタリングなど)について語っているけど、多くのエンジニアは抵抗を示して、アプローチを考えた上で「正しく」作りたいと思ってる。あるいは、何かをさっと作り出して捨てずに、最初のクソみたいなものを修正することに苦労している。AIを使うと、このループがずっと楽になる。何かの3つの並行実装を作るのも安上がりだし、システムが面白いと思う機能を追加するのを許可する別のものを作ることもできる。これを比較して、要件や関心の分離、大きなシステムとの統合方法を考えた「プログラムの理論」を構築するのに使える。AIにそれを作らせて、出力をしっかりレビューすれば(何を作るべきか大体わかっていれば、時間もかからない)、すごくうまくいくよ。

完全に同意だね。かつて、元の開発者たちが突然消えて、新しいチームが引き継いだプロジェクトに関わったことがあるんだけど、すべての制度的知識が失われてしまったんだ。元の設計を理解するのに、無駄に時間をかけてしまった。理解が深まるまでにかなりのバグを導入してしまったけど、頭をぶつけながらも多くの設計問題を修正したよ。結局、ほとんど書き直されて、元々計画されていなかったことをするように拡張されたんだけど、そのプロセスは本当に苦痛だった。

プログラミングは「達成したいこととその方法」だね。線形プログラミングや動的プログラミングのように。 > モデルや理論が必要だと思わないプログラマーと、LLMが彼らを速くしていると報告している人たちの間には強い相関関係があると思う。これは面白い予測だね。根本的な原因に関係なく相関が得られると思うよ。ほとんどのプログラマーはモデルや理論が必要だと思っていないし、ほとんどのプログラマーがLLMが速くしていると報告しているから。でも、それを考慮に入れると、逆のことが真実である理由もいくつかあるかもしれない。LLMによって最も速くならないと感じるプログラマーは、主にコードを書くことが貢献だと感じている人たちかもしれないし、正しいモデルを見つけることを仕事と考える人たちは、コードを正しい順序に整えるという雑務がなくなるから、より速くなる可能性がある。

そうだね。業界はドメイン特有の知識やコード特有の知識の必要性、開発者が残ることの価値を軽視してたと思う。開発者が「理論構築」や共有に時間を使うこともね。全てのコーディングチームを入れ替えて、同じ開発ペースで進めると思ったら大間違いだよ。コードが比較的良くて、新しい開発者がそれなりにスキルがあってもね。特に「アーキテクチャモデル」レベルのドキュメントが不足してるときは。まあ、LLMがそれをめちゃくちゃなレベルに押し上げてるけど。もし全てのコーダーが自閉症の天才幼児で、毎月新しい幼児チームに入れ替わったらどうなるんだろう。

LLMのおかげで、僕たちには良くなったよ。ジュニア開発者がコミットするコードの質が向上した。

キャリアの中でいくつかのレガシーアプリケーションに関わってきたけど、プログラミングの他の多くの問題と同じように、この問題の最も重要な解決策は良いモジュール化だと思う。そうすれば、新しいチームがモジュール同士の相互作用を高いレベルで理解できるし、変更が必要なときは関わるモジュールだけを理解すればいい。理想的には一つずつね。だから、全体のアプリケーションの詳細な理論を一度に作る必要はないんだ。LLMを使ってみて分かったのは、良いモジュール化の原則と実践を守れば、LLMがあればあまり知らないコードベースでも作業を始めやすくなるってこと。ナビゲートしたり、「必要な分だけ」理解したり、特定の変更をするのにすごく助けてくれる。でも、これはLLMが自動でやることじゃない、少なくとも僕の経験ではね。やっぱり人間が良い、一貫したモジュール化を強制する必要がある。

プログラマーがモデルや理論が必要だと思わないことと、LLMが彼らを速くしていると報告していることの間には強い相関関係があると思う。僕もランポートに強く同意するけど、なぜAIが元のチームやプロジェクトを引き継ぐチームの「理論構築」プロセスに役立つと思わないのか、ちょっと気になるな。つまり、コードベースやアルゴリズムを理解することについてね?全ての知識を置き換えるわけではないけど、ギャップを埋めることはできると思う。

実際、LLMによって開発者がスピードアップしている(つまり、単にコードの行数ではなく、保守可能な成果物の出力を増やすこと)というのは、彼らが取り組んでいるシステムの理論をしっかり持っている人たちだと思う。少なくとも今のところ、LLMは「どうやって」の部分では素晴らしいけど、「何を」「なぜ」を理解するためのコンテキストが欠けていることが多い(それが書かれていなかったり、彼らのトレーニングデータにあまり含まれていなかったりするから)。

私の経験では、LLMは時々動く解決策を見つけるけど、必要以上に複雑なことが多い。元々コードを作成するときが一番、その複雑さを認識して取り除くのが簡単なんだ。なぜなら、その時点で著者は解決しようとしている問題を最もよく理解しているはずだから。でも、これには余分な時間と労力が必要なんだよね。複雑すぎるコードがコミットされると、その複雑さが必要ないと認識するのがずっと難しくなる。コードの読者やメンテナンスをする人は、既存のコードが現実の問題を解決していると仮定することが多いから、もっとシンプルな解決策が機能することに気づくための十分なコンテキストがないんだ。

LLMを使うと、複雑すぎる解決策を避けるのは簡単だよ。まず、プロンプトは直接的にして、LLMが無駄に複雑なものを作らないようにすること。次に、常に問題をできるだけシンプルに解決するためのルールや学習、コンテキストを加えるべきだね。最後に、生成後にLLMに解決策の複雑さを減らすように促すことができるよ。

LLMは本当にデバッグが難しいコードを大量に生み出す。これは本当に問題だよ。でも「品質を重視するチームは、LLMが生成したコードをレビューして理解するための時間を取る」っていうのは、すでに失敗してる。言うのは簡単だけど、生成されるコードを読むより早く生成されることはないから。ボトルネックになっちゃう(それじゃ意味がない)か、ゴム印を押すことになる(負債を生む)。どっちを選ぶかだね。みんなこれにレビューのプロセスを追加しようとしてるけど、それは間違ったレイヤーだ。これは、学ぶジュニア開発者にコーチする方法だよ。AIは学ばない。ずっと同じ7つの問題について議論してることになる。これらのものはコンテキストを求めるけど、ほとんどの人は何も与えない。「私の問題を解決する関数を書いて」なんて通用しない、驚きだね。私たちは異なるプリミティブが必要だ。「LLMが書いたものをすべて注意深く読む」なんて方法で、なぜ、動機、議論、先行技術を与えるのは無理だよ。そうしないと、誰も理解できないコードの山を作ることになる。

「ボトルネックになっちゃう(それじゃ意味がない)」 [...] ボトルネックがレビューだけになる方が良いよね、コーディングとレビューの両方じゃなくて。これのためにたくさんのツール(リンティング、ファジング、テストなど)が開発されてると思う。今起こっているのは、プロジェクト全体を設計するのが苦手で、コードをすぐに読み取ったり分析したりするのが苦手な人たちが、もっと上手くならなきゃいけなくて、文句を言ってるってことだと思う。私はそういう仕事が好きだけどね。彼らは適応するよ、そんなに難しくないから。

エージェント用のさまざまな指示.mdファイルを使って、共通の問題や避けるべき落とし穴を更新してるよ。それにコーディングスタンダードのドキュメントへのリンクもね。GeminiやClaudeは少なくともそれとうまく連携してるみたいだけど、時々間違いもする(例えば、c++のautoを使わないのはよくあること。コンテキストのマークダウンファイルには使わないように明記されてるのに)。モデルが改善されて指示処理が上手くなるにつれて、もっと良くなると思う。これが「解決策」だとは言わないけど、ある程度は役立つよね。「バイブコーディング」から離れて、コードの一般的な構造やユニットの相互作用にもっと気を使うべきだと思う。AIには生の構文を埋め込んだり、文字を打ったりすることだけを任せればいい。これでもまだ大きな生産性の向上だけど、エンジニアとしては機能ごとに細かく指示を出してる感じ。ちょうどいい妥協点って感じだね。

そうだね、「LLMが生成したコードをレビューして理解する時間を取るだけ」ってのは、新しい「悪いコードを書かなければバグは出ない」っていうのと同じだよ。業界全体で、バグを出したくないのに何年もバグを書いてきたから、この作業はスケールするのが不可能だってみんな知ってる。AIのコードを全部レビューして良いコードか確認するのも、同じようにスケールしないよ。うまくいかないし、業界がそれを理解するのに5~10年かかるだろうね。

私も vibe coding をやったことがあるけど、OPに完全に同意するよ。雰囲気でコードを書くと、コードが何をしているのかの必要なメンタルモデルが構築できないから、コードを生成するのに時間を節約しても、トリッキーなバグにぶつかったときに、そのメンタルモデルを構築するために時間を失うことになる。「計画を最初に全部やればいい」なんて現実では通用しない。要求は毎分変わるからね。そして、もし誰かが「受け入れられた行数」を開発者の生産性や節約された時間の指標として使っているのを見かけたら、あまり真に受けない方がいいよ。

「私は vibe coding をやったことがある」 なんで?冗談半分で言ったつもりだったけど、誰も本気でコードをレビューしなくてもいいとは思ってないよ。すぐにスパゲッティ状態になっちゃうから、誰も「vibe coding」を数時間以上続けられるとは思えない。今、LLMが返してくれたものをレビューして、慎重に形を整えて次に進むのは、私のプログラミングにすごく役立ってる。でも、LLMが正しいコンテキストを持っていたかどうかを慎重に確認する必要があるね。そうじゃないと、盲目的にLLMが渡してくれたものを受け入れることになる、いわゆる「vibe coding」になっちゃう。

最近、友達がレビューしているLLMを使ったPRの話をしてたんだけど、ほとんど技術的じゃないマネージャーが出したもので、外から見ると機能しているように見えたんだ。でも、生成された何千行ものコードを調べてみると、実際にはバックエンドを更新せずに、応答キャッシュシステムをハッキングしているだけだった。マネージャーにこれがマージする準備ができていないことを納得させるのに、彼はすごく苦労したみたい。実際に動いているように見える「バイブコード」のソフトウェアがどれだけあるんだろうね?

その技術的じゃないエンジニアリングマネージャーたちはどこにいるんだろう?どうやってビジネスに残ってるのかも不思議だ。15年以上、本当に技術的じゃないマネージャーを見たことがないよ。

マネージャーをCTOやCEO、ビジネスオーナーや投資家に報告するかな。

これが俺とチームにとって次の大きな仕事の波だと思ってる。オフショアチームからレガシーコードを救うことで、5〜8年はビジネスを維持してきたんだ。中小企業が契約開発者を再度国内に戻す中でね。今は需要が落ち着いてるけど、これらの企業がLLMに「コードを書く」ことを頼り始めてるからなんだ。でも、次の18ヶ月を乗り切れば、これらのビジネスが「あなたのコードは今、プロダクション準備完了です」と言うクラウドに頼って蓄積した技術的負債の重さを感じ始めると、大きなチャンスがあると思ってる。

カーニハンの法則 - デバッグは最初にコードを書くのの2倍難しい。だから、できるだけ賢くコードを書いたら、定義上、デバッグするほど賢くないってことだ。現代の補足:もしLLMにコードを生成させたら、デバッグするためには2倍賢いものが必要だよ。

それをデバッグするには、2倍賢いものが必要だよ。 もしかしたら、LLMの「中間」と「高」レベルの思考モードの違いかもね。ちなみに、存在しなきゃいけない複雑な関数については、LLMに意図やアプローチを説明するコードコメントを書かせてるよ。

完全にそうとは言えないけど、運転手になる必要があるね。主にClaude Codeを使ってるけど、時々バカなミスをすることがある。大抵は問題を修正するように頼むと直してくれるけど、たまに「間違った方向に進んでるよ」と言って、書いたコードのどこに問題があるか教えなきゃいけないこともある。つまり、デバッグは同じ「知能」レベルでできるけど、LLMは自分が何をしているのか本当に理解してないから、自分では理解できないエラーを起こすことがある。経験的には、ジュニアプログラマーと一緒に働いてる感じだね。彼らはコードを書けるけど、何が間違ってるのか分からないっていう。

ほとんどのプログラマーは低レベルのアセンブリや機械語を理解してないよね。高水準言語は、人間が理解し合ったり協力したりするためのレイヤーになってる。LLMはそのレイヤーを自然言語や仕様駆動の開発に向かって押し進めてる。大きな違いは、高水準プログラミング言語はまだ決定論的だけど、自然言語はそうじゃないってこと。プログラムの動作を指定するのに必要な情報量は、数十年の進化を経てプログラミング言語でほぼ最適に表現される地点に達したんじゃないかな。自然言語の領域にもっと抽象化が進むと、情報が失われちゃうし、低レベルのコードに抽象化を減らすと冗長になっちゃう。

もう一つ大きな違いがあるんだ:自然言語にはあいまいさが組み込まれてる。プログラミング言語に解析のあいまいさがあったら、それは大きなバグと見なされるけど、自然言語ではそれがほぼ特徴になってる。詩や含み、その他の微妙なコミュニケーションを可能にするんだよね。

ほとんどのプログラマーは低レベルのアセンブリや機械語を理解してない。 生計を立てるためにJavaScriptを書くほとんどのプログラマーは、JavaScriptでアプリケーションをスケールさせる方法を本当に理解してない。スケールできる機能を実現するためには、抽象化のレイヤーに依存しているんだ。彼らはブラウザの主要APIであるDOMを全く理解してないし、多くの人はブラウザ外のNode APIも理解してない。外部の観察者から見ると、まさに「オフィス・スペース」の質問が浮かぶよね:ここで何をしていると思う? ソフトウェアとは全く関係ない人に説明するのは変な感じだ。ソフトウェアにいる私たちは、これに慣れすぎていて、この狂気を避けられない現実として当然に受け入れてる。皮肉なことに、あなたのコメントの観点から言うと、JavaScript開発者にこの基本的な知識の欠如を指摘すると、アセンブリとの比較がよく出てくる。まるでJavaScriptを書くことが機械語を書くことと同じように思えるけど、その職業の多くの人にとっては、両者は遠く離れた現実なんだ。LLMの導入は完全に理にかなってる。誰もこのコードがどう動くか知らないなら、機械に書かせることに害はないよ。なぜなら、根本的な認識に違いがないから。

自然言語の仕様が役立つと思うけど、厳密な意味論を持つ中間的な記述レイヤーが必要だと思う。例えば、Rustを使ったLLM駆動のコーディングでは、強い型システムがより緩やかまたは未型付けの言語で発生する無効な状態を防いでくれるから、成功することが多い。時間はかかるけど、LLMはコンパイルできる状態に達するためにcargo checkサイクルを繰り返す必要があることが多い。でも、一度コンパイルできれば、変更はほとんど正しいことが多い。Rustコミュニティには「コンパイルできれば、おそらく動く」という言葉がある。もちろん論理バグはまだたくさんあるけど、間違いの可能性の範囲は狭い。理想的なのは、LLMが実装しなければならないアプリケーションの意味論を非常に厳密に定義したもので、それが実装と照らし合わせてチェックできることだね。つまり、依存型を持つ非常に厳格なプログラミング言語で、前提条件や後提条件が散りばめられているようなもの。LLMは自然言語の説明を形式的な仕様に変換するのを手伝うことができるけど、その仕様が実装を駆動するべきだと思う。

違いは、自然言語での高い抽象化へのジャンプだけじゃないんだ。根本的に違う何かなんだよ。以前のツール(アセンブラ、コンパイラ、フレームワーク)は、チェックできて数学的に検証もできるハードコーディングされたロジックに基づいて作られてた。だから、立っているものを信頼できた。でも、LLMでは、安全に構築された塔から不確実性、推測、幻覚の世界に飛び込むことになる。

なんだか、多くの非常に賢いAI起業家が、ロスレスデータ圧縮の限界という概念を理解していない。もしアイデアが情報を失わずにさらに削減できないなら、どんなAIでもそれを圧縮することはできない。だから、Slackやメール、Jiraの効率に関する失敗したスタートアップがたくさんあるんだ。半分の時間、重要な情報を見逃したかどうかわからないから、ソースに戻る必要があって、成功裏に要約された情報で得た利益を無駄にしてしまうんだ。

最近、Dwarkesh Patelのポッドキャストを聞いてたんだけど、ゲストのアグスティン・レブロンがヴァーナー・ヴィンジの「A Deepness In The Sky」って本について言及してたんだ。読み始めたら、重要なプロットポイントがあって、何千年も前のコンピュータシステムが登場するんだ。その主要キャラクターの一人は「冷凍睡眠」してたから、隠されたバックドアを知ってるのは彼だけなんだよ。そのレガシーな知識が大いに活用されるんだ。レガシーコードベースにおける制度的知識の素晴らしいフィクションとして、めっちゃおすすめだよ(ストーリーもすごくいいし)。

適切なエージェントの指示を使えば、部分的には対処できるよ。例えば、SOLIDに従うとか、コンストラクタインジェクションを使うとか、可変性を避けるとか、デュアルテストを書くとか、明示的な型を使う(適用可能な場合)とかね。でも、モデルのデザインは本当にひどいから、あんまり助けにはならない。結局、すべてをしっかりレビューして(できれば)人間が書き直さないとダメだね。