Karpathy指摘: LLMが書くコードは防御的すぎる
元記事
Andrej Karpathy on X: LLMs are mortally terrified of exceptions
Karpathyの投稿が話題に
Andrej Karpathy(OpenAI創設メンバー、元Tesla AI部門ディレクター)が10月5日にXで投稿した、LLMの例外処理に関する観察がHacker Newsで201ポイントを獲得しています。
「ラボがRLでこれらの可哀想なLLMに何をしているのか知らないが、彼らは例外に対して死ぬほど恐れている。どんなに確率が低いケースでも。例外は正常な開発プロセスの一部なのに」
投稿には、LLMがa
をb
で割るコードを書く様子を風刺したPOV(Point of View)動画が添付されていました。LLMは過度に防御的なエラーハンドリングを追加し、例外が発生しないように必死で回避しようとします。
前にLLMにコード生成を頼んだ時、関数の中でひたすらif
文とtry-catchが連続していて、「そこまでチェックする必要あるか?」と思いました。Karpathyの指摘を見て、あれは自分の指示の問題じゃなくて、LLM全体の傾向だったと分かりました。
過度な防御プログラミングの実態
LLMが書くコードには、いくつかの特徴的なパターンがあります。
1. 不要なチェックの連続
通常のプログラミングならif (a && b)
で済む箇所を、LLMは以下のように書きます:
if a is not None:
if b is not None:
if isinstance(a, int):
if isinstance(b, int):
if b != 0:
result = a / b
実務ではa / b
を直接書いて、例外が出たら出たでキャッチすればいいです。でもLLMは、ありとあらゆる可能性を事前に排除しようとします。
2. 例外を飲み込む
もっと厄介なのが、例外を捕捉してそのまま無視するコードです。
try:
result = some_function()
except Exception:
pass # ← 何もしない
これは「例外を出さない」という目標は達成していますが、実際のエラーを隠蔽しています。デバッグが地獄になります。
3. チュートリアル的なエラーハンドリング
LLMが書くコードは、チュートリアルや教科書に載っているような「丁寧すぎる」エラーハンドリングが多いです。実務では、エラーメッセージは簡潔に、必要な情報だけ出せばいいです。でもLLMは、ユーザーに優しいメッセージを出そうと、長文のエラーメッセージを生成します。
前にLLMが生成したエラーメッセージが、実際のエラー箇所を示さず「問題が発生しました。もう一度お試しください」みたいな無意味なメッセージになっていて、デバッグに時間がかかりました。
開発者の反応
Hacker Newsのコメント欄では、複数の開発者が似たような経験を共有していました。
「トラウマ的に過剰訓練されている」: あるコメントでは、LLMが「traumatically over-trained」(トラウマ的に過剰訓練されている)と表現されていました。強化学習のプロセスで、例外を出すたびにペナルティを受けた結果、例外を恐れるようになったという見方です。
「ユーザーを幸せにすることに集中しすぎ」: LLMは「コードの品質」よりも「ユーザーが満足する結果」を優先します。エラーが出るとユーザーが不快に感じる→だから例外を徹底的に回避する、という論理です。
「Fail Fast原則の無視」: 多くの開発者は「早く失敗する(Fail Fast)」を推奨しています。問題が起きたら即座に例外を出して停止する方が、後でデバッグしやすいです。でもLLMは、できる限りエラーを隠して実行を続けようとします。
実際、自分もLLMが生成したコードをレビューする時、まず最初に「余計なtry-catchを削る」作業をします。コードが冗長になるだけでなく、本当に必要なエラーハンドリングが見えにくくなります。
RLでの報酬設計が原因か
Karpathyの投稿では、強化学習(RL)での訓練が原因と示唆されています。
LLMは、コード生成後にそのコードが実行され、エラーが出るとマイナスの報酬を受け取ります。結果として、LLMは「例外を出さないコード」を書くことを学習します。
ただ、現実の開発では、例外は正常な処理フローの一部です。ファイルが存在しない、ネットワークが切断される、入力が不正な形式。こういうケースで例外を出すのは当然です。
問題は、RLの報酬設計が「例外 = 悪」という単純化された評価をしている可能性があることです。本来は「適切な例外処理」を評価すべきですが、それを自動評価するのは難しいです。
前にLLM生成コードで、ファイルが存在しない時に空のデータを返すコードがありました。「例外を出さない」という意味では成功ですが、実際は「ファイルがない」というエラー情報が失われて、後続処理が誤動作しました。
実務での対応策
LLMが生成したコードを実務で使う場合、以下の点を確認する必要があります。
1. 過度な防御コードの削除
LLMが追加した不要なチェックを削ります。特に、型チェックやNoneチェックが連続している箇所は、本当に必要か見直します。
2. 例外を飲み込むコードの修正
except Exception: pass
のようなコードは、最低限ログを出力するか、適切にre-raiseします。エラーを無視するのは最悪です。
3. エラーメッセージの簡潔化
ユーザー向けのエラーメッセージは簡潔に。デバッグ情報は、ログに出力します。
4. プロンプトでの指示
LLMにコード生成を依頼する時、「シンプルなエラーハンドリングで」「過度な防御コードは不要」と明示的に指示します。ただし、これでもLLMの基本的な傾向は変わらないことが多いです。
実務では、LLM生成コードを「ドラフト」として扱い、必ずレビューとリファクタリングを行います。そのまま本番に投入するのは危険です。
懸念点
この傾向が続くと、いくつかの問題が起きそうです。
1. コードの可読性低下
防御的なコードが増えると、本質的なロジックが埋もれます。コードレビューの負担が増えます。
2. デバッグの難易度上昇
エラーが隠蔽されると、問題の原因を特定するのが難しくなります。特に、例外を飲み込むコードは、後で重大なバグを引き起こします。
3. 開発者のスキル低下
LLM生成コードをそのまま使う開発者が増えると、「適切なエラーハンドリング」を学ぶ機会が減ります。過度に防御的なコードが「正しい書き方」だと誤解される可能性があります。
前にジュニア開発者が、LLMが生成した過度に防御的なコードを見て、「こういう書き方が推奨されているんですか?」と聞いてきました。LLMの影響で、誤った「ベストプラクティス」が広まるリスクがあります。
今後の改善に期待
Karpathyの投稿は、LLMのコード生成における重要な問題を指摘しています。
理想的には、LLMの訓練時に「適切な例外処理」を評価する仕組みが必要です。例外を出すこと自体は悪ではなく、「どこで例外を出すべきか」「どう処理すべきか」を学習する必要があります。
ただ、これを自動評価するのは難しいです。コードの実行結果だけでは、「適切なエラーハンドリング」を判断できません。コンテキストを理解し、設計意図を考慮する必要があります。
Karpathyが投稿で「LLM福祉請願書に署名しよう」と冗談めかして言っていますが、これは「報酬設計の見直しが必要」という本質的な指摘です。LLMがコード生成で本当に実用的になるには、こういった細かい挙動の改善が不可欠です。
あと、開発者側も、LLM生成コードを盲目的に信用せず、「なぜこのエラーハンドリングが必要なのか」を常に問う姿勢が重要です。LLMはツールであり、最終的な判断は人間が行うべきです。
Discussion