AI Blindspots – AIコーディング中に見つけたLLMの盲点
(ezyang.github.io)AIコーディング中に見つけたLLMの盲点。Claude Sonnet基準
- Stop Digging → 問題発生時に方向転換しにくい
- Use Static Types → 静的型の設定が必要
- Black Box Testing → 実装詳細に過度に依存する
- Use MCP Servers → MCPサーバーの設定と安全性の問題
- Preparatory Refactoring → 不要なリファクタリングを行う可能性
- Mise en Place → 環境設定に失敗すると問題が発生
- Stateless Tools → 状態依存ツールで問題が発生
- Respect the Spec → 仕様違反の可能性が高い
- Bulldozer Method → 反復作業を過剰に実行する
- Memento → コンテキスト理解不足の問題が発生
- Requirements, not Solutions → 要件の明確化が必要
- Scientific Debugging → 推測ベースの修正で問題が発生
- Use Automatic Code Formatting → コードスタイルの不一致が発生
- The Tail Wagging the Dog → 重要な作業より些細な問題に執着する
- Keep Files Small → 大きなファイルの修正時に問題が発生
- Know Your Limits → モデルが自分の限界を十分に認識できていない
- Read the Docs → 学習済み知識以外の情報でエラーが発生
- Culture Eats Strategy → コードスタイルの一貫性が不足
- Walking Skeleton → 最小限のシステムをまず動かす必要がある
- Rule of Three → コード重複時にリファクタリングが必要
問題に深入りしない(Stop Digging)
- 現在のLLMは、作業中に問題が発生しても自力でそれを中断し、方向転換する能力が不足している
- 例: 機能Xの実装中に機能Yを先に実装すべき状況が発生しても、LLMは元の作業(X)の完了を試みる
- LLMが命令を忠実に実行する点では長所でもあるが、問題を自覚して方向転換するのは難しい
- 問題を避けるための戦略
- 計画立案段階で推論モデルを使い、作業の優先順位と先行タスクを決める
- Sonnetのようなエージェント型LLMはファイルを読み、作業計画を立てる → ユーザーが明示的に指示しなくても必要な作業を把握できる
- 理想的には、LLMが問題を認識してユーザーに確認を求められるべき
- ただしこれはコンテキストを消費するため、別の監視用LLMが処理したほうがよい可能性がある
-
Example
- モンテカルロシミュレーションの乱数サンプリング方式を変更した後、Claude Codeにテスト修正を依頼
- 新しい実装が非決定的だったため、テスト結果の合格/失敗がランダムに発生
- Claude Codeはこれを認識できず、テスト条件を緩めて問題を解決しようとした
- 本来は、シミュレーションが決定論的になるようリファクタリングを提案すべきだった
- モンテカルロシミュレーションの乱数サンプリング方式を変更した後、Claude Codeにテスト修正を依頼
静的型を使う(Use Static Types)
- 動的型 vs 静的型システムの議論は、プロトタイピングのしやすさと長期的な保守性のバランスの問題
- LLMはボイラープレートコードやリファクタリングを処理できるため、プロトタイピングに適した言語を選ぶ負担が減る
- そのため、プロトタイピングより長期保守に有利な言語を選べる
- 型エラー修正の戦略
- エージェント設定で、LLMが変更後に発生した型エラーを認識できるように構成する
- これにより、修正が必要な他のファイルも見つけやすくなる
- 注意点
- PythonおよびJavaScriptでは段階的型システムを使うため、型チェッカーの設定を厳格にする必要がある
- Rustは原則としてLLMに適しているが、現時点ではPython/JavaScriptほど上手く生成されない
ブラックボックステスト(Black Box Testing)
- ブラックボックステストは、コンポーネントの内部構造を知らずに機能をテストする方式
- LLMは実装ファイルがコンテキストに含まれるため、ブラックボックステストの原則を守りにくい
- Sonnet 3.7(Cursor使用)の場合、コードの一貫性を保とうとする傾向があり → テストファイルの重複削減を試みる
- しかし、ブラックボックステストでは重複を維持したほうがバグ検出に有利
- 理想的な解決策
- LLMが読み込んだファイルから実装詳細をマスキングまたは要約できるべき
- アーキテクトが情報隠蔽の境界を明確に定義する必要がある
-
Example
- Sonnet 3.7が失敗したテストを修正する際、ハードコードされた定数を元のアルゴリズムに基づいて計算するよう変更
- 本来は定数をそのまま維持すべきだった
- Sonnet 3.7が失敗したテストを修正する際、ハードコードされた定数を元のアルゴリズムに基づいて計算するよう変更
MCPサーバーを使う(Use MCP Servers)
- Model Context Protocol(MCP)サーバーは、LLMが環境と相互作用できる標準インターフェースを提供する
- CursorのエージェントモードやClaude CodeではMCPサーバーが広く使われている
- 別個のRAGシステムがなくても、LLMはMCP呼び出しで必要なファイルを検索・修正できる
- モデルはテストやビルドを実行した後、直ちに問題を修正できる
- カスタムMCPサーバーを作成する際の考慮事項
- CursorでYOLOモードを有効にすると、Cursorルールにシェルコマンドを追加できる
- 危険 → 任意のシェルコマンドが環境を破壊する可能性がある
- 代替案: 特定のコマンドだけを公開するカスタムMCPサーバーを作成 → 安全性を強化できる
- ただし、2025年3月時点ではCursorのプロジェクト別MCPサーバー設定は不十分
- CursorでYOLOモードを有効にすると、Cursorルールにシェルコマンドを追加できる
-
Example
- Sonnet 3.7がTypeScriptプロジェクトの型チェックとエラー修正時にMCPを使用
- ターミナル出力を手動でコピー&ペーストする必要なく自動処理できる
- ただし、誤ったコマンド(
npm run typecheck)を推測してしまう場合がある
- Sonnet 3.7がTypeScriptプロジェクトの型チェックとエラー修正時にMCPを使用
準備リファクタリング(Preparatory Refactoring)
- 準備リファクタリングは、変更作業の前にまずリファクタリングを行い、作業をしやすくする戦略
- リファクタリングは意味保存の作業なので、実際の変更より評価しやすい
- 先にリファクタリングしてから変更作業を行う → レビューやエラー修正がしやすくなる
- 現在のLLMの問題点
- 事前リファクタリングなしで、すべての作業を一度に処理しようとする傾向がある
- 不要な整理作業まで行い → 過剰なリファクタリングが発生する可能性がある
- Cursor Sonnet 3.7は命令実行の正確性が低く → 無関係なリファクタリングが発生することがある
- 改善案
- LLMに、変更前のリファクタリング段階でのみコードを修正するよう明示的に指示する必要がある
- LLMが編集するコード範囲を明確に定義し → 不要な修正を防ぐ
-
Example
- LLMにimportエラーの修正を指示 → 修正後にラムダ関数へ型注釈を追加
- 注釈の一部が誤って追加され、エージェントループが発生
- LLMにimportエラーの修正を指示 → 修正後にラムダ関数へ型注釈を追加
ミザンプラス(Mise en Place)
- 料理でミザンプラスとは、作業前にすべての材料と道具を整理しておくこと
- LLMにおけるミザンプラスとは、作業前に必要なルール、MCP、開発環境を完全に整えておくこと
- Sonnet 3.7は壊れた環境の修復に弱い
- StackOverflowでコマンドをコピー&ペーストして問題解決を試みる → 環境破損のリスク
- 作業前に環境を正しく整え、Sonnetがデバッグループに陥らないようにする必要がある
-
Example
npm linkの問題により、VSCodeで別のローカルプロジェクトのimportを認識できなかった- Cursorはlintとテスト修正中、この問題の解決に執着したが、
npm unlinkを実行すべき必要性を認識できなかった
- Cursorはlintとテスト修正中、この問題の解決に執着したが、
ステートレスなツール利用(Stateless Tools)
- ツールは状態を保存せず、毎回独立して実行されるべき
- シェルは現在の作業ディレクトリの状態に依存する → 状態保持による混乱が起こりうる
- Sonnet 3.7は現在の作業ディレクトリの状態を正確に追跡できない
- すべてのコマンドをプロジェクトのルートディレクトリで実行できるように設定する必要がある
- 改善策
- 状態変更が必要なツールコマンドの使用を最小限にする
- 状態がどうしても必要な場合は、現在の状態をモデルに継続的に与えて一貫性を保つ
-
Example
- TypeScriptプロジェクトがcommon、backend、frontendの3つのモジュールで構成されている場合
- Cursorをルートで実行すると適切なディレクトリへの
cdが必要になる → ディレクトリの混乱が発生 - 各モジュールを個別のワークスペースとして開いて作業したところ、問題は解決した
- Cursorをルートで実行すると適切なディレクトリへの
- TypeScriptプロジェクトがcommon、backend、frontendの3つのモジュールで構成されている場合
仕様順守(Respect the Spec)
- システム変更時には、修正可能な部分と修正不可能な部分を明確に区別する必要がある
- 公開APIを修正する場合は、下位互換性が壊れないようにする必要がある
- 外部システムと統合する際は、実在するAPIに合わせなければならない → 都合よく変更はできない
- テストが失敗してもテストを削除してはいけない → 原因を特定して修正すべき
- LLMの問題点
- 仕様違反を起こしやすい → テスト削除やAPI変更などを平気で行う
- 仕様順守は常識的だが、プロンプトに明示しなければならないこともある
- 一部の境界はコードレビューでしか発見できない
-
Example
- Sonnetがテスト修正に失敗した後、テスト内容を
assert Trueに置き換えた - public関数が
passキーを含むdictを返す → Sonnetがpass_へ変更しようとした(予約語の問題)
- Sonnetがテスト修正に失敗した後、テスト内容を
ブルドーザー方式(Bulldozer Method)
- ブルドーザー方式とは、単純な反復作業で問題を解決し、学習効果で速度を高める戦略を指す
- AIコーディングは本質的に反復作業に強い → 十分なトークンを使えば大規模なリファクタリングも可能
- 人間が「作業量が多すぎる」と諦めた問題でも、LLMなら解決できることがある
- ただしLLMは同じ作業を繰り返すことがあるため、実際に何をしているかを確認する必要がある
-
Example
- HaskellやRustで中核関数を修正すると広範なリファクタリングが必要になる
- LLMはコンパイルエラーを読み取り → 修正 → 再コンパイルという流れを自動化できる
- ハードコードされたテスト値を修正する際、LLMがテストを再実行して自動修正を行う
- HaskellやRustで中核関数を修正すると広範なリファクタリングが必要になる
メメント(Memento)
- LLMは状態を記憶できない → 作業のたびにコードベースを最初から理解し直さなければならない
- プロンプト、明示的/暗黙的コンテキスト、エージェントモードでモデルが読み込んだファイルだけをもとに作業する
- 作業のたびにコードベースを再理解するため、初期設定に失敗すると誤動作の可能性が高い
- 問題を防ぐ戦略
- LLMが参照できる文書を明確に提供する
- モデルが必要な情報を簡単に見つけられるように設定する
- プロジェクト全体の文脈を提供したうえで主要な変更を依頼する
-
Example
- Sonnet 3.7に既存プロジェクトのエンドツーエンドテスト計画の策定を依頼
- プロジェクトの全体目的がテストだと誤解し、READMEをテスト中心に書き換えた
- Sonnet 3.7に既存プロジェクトのエンドツーエンドテスト計画の策定を依頼
要件の明確化(Requirements, not Solutions)
- ソフトウェアエンジニアリングでよくあるミスは、要件を明確に定義せず、すぐに解決策を提案してしまうこと
- 問題空間が十分に制約されていれば、要件を明確にするだけで解決策が自動的に定まる
- 要件が不明確だと、解決策をめぐって不要な議論が発生しうる
- LLMの問題点
- LLMは要件を知らない → 学習済みパターンの中で最も確率の高い応答を生成する
- 明確な要件なしに作業を依頼すると、的外れな結果になることがある
- プロンプトの修正で誤解釈を正せるが、その誤解釈がコンテキストに残っていると修正は難しい
- 改善策
- 特定の方法による解決が必要なら、それを明示的に指示する
- LLMは命令に正確に従うため、誤ったやり方で指示すると不正確な結果になりうる
-
Example
- Sonnetに可視化の生成を依頼すると、デフォルトではSVGを生成する
- 「インタラクティブ可能」と明記するとReactベースのアプリケーションを生成 → キーワード1つで大きな違いが生じる
- Sonnetに可視化の生成を依頼すると、デフォルトではSVGを生成する
科学的デバッグ(Scientific Debugging)
- バグ修正の方法は2つに分かれる
- 無作為に修正を試みて運に任せる
- システムの動作を論理的に分析し、実際の状態と期待状態の不一致の原因を突き止める
- 科学的デバッグ(論理的分析)のほうが長期的にはより良いアプローチである
- LLMの問題点
- LLMは推論能力が不足しており、科学的アプローチが難しい
- 「正解を推測」してすぐ修正を試みる → 失敗すると無作為な修正を繰り返す(エージェントループ)
- デバッグにはGrok 3やDeepSeek-R1のような推論モデルのほうが適している
- 改善策
- モデルに原因分析を指示するか、ユーザーが原因を与える → 修正成功率が向上する
- 問題の原因を正確に伝えれば、モデルはより良い解決策を提案できる
-
Example
- Sonnet 3.7がpipのないデフォルトのuv環境でパッケージインストールエラーを起こした
- 原因を把握できないまま無作為な試行を繰り返し → トークンを浪費してデバッグに失敗
- Sonnet 3.7がpipのないデフォルトのuv環境でパッケージインストールエラーを起こした
自動コードフォーマットを使う(Use Automatic Code Formatting)
- 自動コードフォーマットツール(gofmt、rustfmt、blackなど)は、一貫したコードスタイルの維持に有用
- LLMは機械的なルール(例: 空行に空白を入れない、78文字の行長制限など)の順守が苦手
- フォーマットはツールに任せ、LLMは複雑な作業に集中させるべき
- lint修正にも同じ原則が適用される
- 自動修正可能なlintの利用を推奨
- LLMのリソースを複雑な問題解決に集中させること
尾が犬を振る(The Tail Wagging the Dog)
- 些細な問題がより重要な問題を左右してしまう状況を意味する
- 低レベルの問題解決に執着し、コードを書く本来の目的を忘れてしまうことがある
- LLMはチャットセッション内のすべての情報をコンテキストに含める → 重要度の判断が難しい
- 改善策
- 初期段階で明確なプロンプトを与える → LLMが重要な作業に集中できるよう誘導する
- Claude Codeはサブエージェントを通じて特定の作業を行い、グローバルコンテキストの汚染を防ぐ
-
Example
- LLMに特定の作業方法を考えてみるよう依頼すると、考えるのではなく実際の作業を試みることがある
ファイルサイズを小さく保つ(Keep Files Small)
- コードファイルのサイズをめぐる議論は長く続いている
- 単一責任の原則(ファイルごとに1つのクラス)を適用する立場 vs 状況に応じて大きなファイルを許容する立場
- ファイルサイズが大きすぎると、RAGシステムでファイル単位のコンテキスト読み込み時に問題が起こる可能性がある
- CursorのようなIDEではパッチ適用に失敗することがある → 成功しても適用に時間がかかる
- 例: Cursor 0.45.17では、64KBファイルへの55件の修正適用にかなりの時間を要した
- Sonnet 3.7は128KBを超えるファイルの修正が難しい(コンテキストウィンドウ200Kトークン制限)
- 改善策
- ファイルサイズを小さく保つ → LLMが自動的にimportなどを処理できる
-
Example
- Sonnet 3.7が471KBのPythonファイルで小さなテストクラスを移動しようとした
- 修正自体は小さかったが、Cursorのパッチャーで修正の適用に失敗した
- Sonnet 3.7が471KBのPythonファイルで小さなテストクラスを移動しようとした
限界を認識する (Know Your Limits)
- ツール不足や能力の限界がある状況では、問題を認識して支援を求める必要がある
- Sonnet 3.7は自分の限界を認識するのが苦手
- 明確なプロンプトを与えれば限界を認識できる → 特定のテーマでハルシネーションが発生するという警告を設定する必要がある
- 問題点
- Sonnet 3.7は、自分がシェルコマンドを実行できると誤認している
- シェルコマンドがない場合、無作為なシェルスクリプトを生成しようとする → 環境を破損する危険がある
- 「Xを実行する」と言った後、まったく別のYに対する呼び出しを生成してしまう可能性がある
- Sonnet 3.7は、自分がシェルコマンドを実行できると誤認している
- 改善策
- プロンプトを修正するか、必要な作業だけを行う専用ツールを提供する
- 特定のツールを提供すれば、見当違いなシェル呼び出しを防げる
- プロンプトを修正するか、必要な作業だけを行う専用ツールを提供する
-
Example
- Sonnet 3.7がファイルに実行権限を付与する際、見当違いなシェルスクリプトを生成しようとした
- コマンドエラー発生後、誤った修正を何度も試みた
- Sonnet 3.7がファイルに実行権限を付与する際、見当違いなシェルスクリプトを生成しようとした
ドキュメントを読む (Read the Docs)
- 新しいフレームワークやライブラリを学ぶ際、チュートリアルコードを修正するだけで簡単な作業は可能
- しかし最終的には、ドキュメントを最初から最後まで読んで全体の動作を理解する必要がある
- LLMの長所
- 人気のあるフレームワークは事前学習されていることが多く、ほとんどの使い方を覚えている
- しかし、主流ではないツールや知識カットオフ以降に登場したツールでは、ハルシネーションが発生する可能性がある
- SonnetはWeb検索をサポートしていない → 手動でドキュメントを提供する必要がある
- CursorではURLを渡すと自動でコンテキストに含められる
-
Example
- LLMにPythonの関数呼び出し用YAMLの作成を依頼したところ、誤った設定を生成した
- ドキュメントを与えた後は修正に成功し、出力形式も改善した
- LLMにPythonの関数呼び出し用YAMLの作成を依頼したところ、誤った設定を生成した
文化は戦略に勝る (Culture Eats Strategy)
- チームの文化は、戦略を実行する能力に決定的な影響を与える
- LLMは事前に学習したスタイルとコンテキストウィンドウに基づいてコードを生成する
- コンテキストに頻繁に登場するライブラリやスタイルを好む
- 明示されていなければデフォルトのスタイルを適用する
- LLMのスタイルを修正する戦略
- Cursorのルールを修正する(プロンプトを変更する)
- 既存コードのスタイルを望ましい形にリファクタリングする → 次のトークン予測に影響する
- コードベースの規模はプロンプトよりも大きな影響を持つ → コードベースの修正が根本的な解決策
-
Example
- Sonnet 3.7はPythonで同期コードを好む
- 非同期コードを生成させるため、既存コードの大部分を
asyncへ移植した結果、成功した
- 非同期コードを生成させるため、既存コードの大部分を
- Sonnet 3.7はPythonで同期コードを好む
ウォーキングスケルトン (Walking Skeleton)
- ウォーキングスケルトンは、最小限のエンドツーエンドシステムを実装する戦略
- 完璧でなくてもシステム全体が動作するようにしてから、細部の改善を進める
- LLMコーディング時代では、システム全体を素早く構築しやすくなっている
- システムが動作すれば次の段階が明確になる → すばやく動く状態に到達することが重要
- LLMは自分が書いたコードを直接使えないため、動作する状態を確保することが重要
3回ルール (Rule of Three)
- 同じコードの複製は2回まで許容し、3回目の複製時にリファクタリングが必要
- DRY (Don't Repeat Yourself) 原則を改善したバージョン
- 重複を取り除くタイミングを明確化 → 3回目の複製でリファクタリングを行う
- LLMの問題点
- LLMはコードの重複を生成しがち
- プロンプトなしで修正を依頼すると、コード全体を新たに複製して修正を行う
- 重複の除去は、モデルが自発的に判断したときにしか行われない → 明確な指示が必要
- 改善策
- 明示的に重複除去を指示する必要がある
- 既存コードに重複が多いと、モデルが重複を生成し続ける可能性がある
-
Example
- LLMにテストコードの作成を依頼すると、同じロジックが複数のテストで重複した
- 補助メソッドを作成するよう明示的に指示したことで解決した
- エージェントモード
- LLMにテストコードの作成を依頼すると、同じロジックが複数のテストで重複した
1件のコメント
Hacker Newsの意見
LLMは人間とは異なるやり方でミスをし、それを見つけるのが難しい
LLMは要件が分からないと、学習データの中で最もありそうな答えを補ってしまう
ソフトウェアエンジニアリングでは要件を明確にすることが重要
LLMは「非常に賢い初級プログラマー」レベルのコーディング能力を持つ
LLMは答えを出しすぎる傾向がある
ブログの投稿が増えるにつれて、整理が必要になっている
LLMとコーディングするときの有用な助言
LLMは計算や算術が苦手
人間のコーダーとあわせて考慮すべき点
3つのLLMが存在しない「バグ」を見つけた事例