
既存コードを安全に直すためのLLM解析のコツ
はじめに
こんにちは、DIVX エンジニアの渡邊です。
今回は、引き継ぎや長期運用など、「コメントや仕様書が薄い・古い・散在している」既存コードを前提に、Claudeを用いて安全に直す際の解析フェーズでのコツを扱います。
既存の保守で生成AIを小さく併用してリスクを抑えたい方向けに、ClaudeなどのLLMでコードを読む際に効いた工夫をまとめました。
背景
既存サービスの保守に、LLMを導入してバグの修正を行おうとしたところ、古いコメントに引っ張られ、頼んでいない箇所の正しい実装を「コメント準拠」に書き換えるような提案をされることがありました。
実例はお示しできませんが、参考となる類似パターンを簡潔に紹介します。秒とミリ秒を取り違えたケースです
初期状態(実装は秒、docstringはミリ秒と記載)
def fetch(url: str, timeout_s: float = 0.5):
"""Timeout in milliseconds."""
return http_get(url, timeout=timeout_s)
ライブラリは秒指定で受け取る一方、docstringにはミリ秒と書かれています(ここが矛盾)。このような矛盾があると、LLMは次のような修正を提案しやすくなります。
誤った修正(docstringに合わせて実装をミリ秒化してしまう)
def fetch(url: str, timeout_s: float = 0.5):
"""Timeout in milliseconds."""
return http_get(url, timeout=timeout_s / 1000.0)
docstringを仕様と認識し、引数をミリ秒として扱うように実装を変更(http_getへ渡す前に秒へ変換)してしまっています。
こういった際に効くコツを紹介していきます。
結論
仕様の優先順位を決め、優先順位を明示した上で、ドキュメント化して読ませる
例: 別紙ドキュメント > 実行時挙動/既存テスト > コードコメント > 命名 など
上記が最も正確でしたが、そこまで工数を割くことなく実行可能なのは以下になります
解析プロンプトに「根拠の引用」を入れる
近くに“実行可能な仕様”を置く(doctest・サンプルI/O・型などを関数直上に)
具体例
根拠の引用を必須にする
プロンプトに「根拠の引用」「両案提示」「diff必須」を明示します。
次を守って修正案を出してください:
1) 仕様の根拠として該当箇所を最大3点まで引用({file}:{line}-{line} + 原文)
2) 矛盾があれば「実装修正案」と「コメント更新案」を両方提示
3) 変更は必ず unified diff 形式
4) 公開API・外部I/F・入出力フォーマットは変更しない(必要な場合は別案として提案だけ)
5) 不明点は推測せず「不明」と明記し、追加で必要な情報を列挙
6) 影響範囲(関係する関数/呼び出し元)と最小テストケースを併記
近接に“実行可能なヒント”を置く
関数直上に短いExamples/doctestや型注釈を置きます
def fetch(url: str, timeout_s: Seconds = Seconds(0.5)):
"""
Timeout in seconds.
Examples:
>>> # 0.75秒をそのまま透過して http_get に渡す
>>> called = {}
>>> def http_get(url, timeout):
... called['timeout'] = timeout
... return "ok"
...
>>> fetch("https://example.com", Seconds(0.75))
'ok'
>>> called['timeout']
0.75
"""
return http_get(url, timeout=timeout_s)
モデルは近接する機械可読な手掛かり(型、doctest、サンプルI/O)を強く参照するため、仕様解釈がぶれにくくなります。
効果
主観ベースにはなりますが、以下のような効果がありました
根拠の明示により、推測ベースの修正混入が減った
議論が「どの根拠に沿うか」に収束し、レビューの合意形成が速くなった
関数近傍のdoctest/型情報が誤読のブレーキとして機能し、単位・境界条件の取り違えが減った
さいごに
「解析のしかた」を少し整えるだけで、LLMの提案は扱いやすくなります。
大掛かりな整備が難しい現場こそ、プロンプトと近接のヒントで小さく効かせる。そんなやり方で、LLMの提案はもっと味方になります。



