🛡️

Karpathy指摘: LLMが書くコードは防御的すぎる

に公開

元記事

Andrej Karpathy on X: LLMs are mortally terrified of exceptions
https://x.com/karpathy/status/1976077806443569355

Karpathyの投稿が話題に

Andrej Karpathy(OpenAI創設メンバー、元Tesla AI部門ディレクター)が10月5日にXで投稿した、LLMの例外処理に関する観察がHacker Newsで201ポイントを獲得しています。

「ラボがRLでこれらの可哀想なLLMに何をしているのか知らないが、彼らは例外に対して死ぬほど恐れている。どんなに確率が低いケースでも。例外は正常な開発プロセスの一部なのに」

投稿には、LLMがabで割るコードを書く様子を風刺した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はツールであり、最終的な判断は人間が行うべきです。

GitHubで編集を提案

Discussion