PR レビューをしていて、ある関数の docstring に書かれた使用例が実際の挙動と食い違っていることに気づきました。docstring には「この入力ならこう返ります」と例が載っているのに、同じ入力をランタイムで実行すると、まったく別の値が返ってきたのです。
問題なのは、その docstring を AI が**「仕様の正」として信じて**実装やテストを書こうとしていた点です。古いドキュメントが残っているだけなのに、AI はそれを最新の仕様だと誤認していました。
本記事は、この「docstring の drift(実装とドキュメントの乖離)」に気づいた経験から、「コード変更時にドキュメントの整合を機械的に検査する」運用にした記録です。
事の発端:docstring の例が False を返す関数
レビュー対象に is_feature_enabled という関数がありました。その docstring には、こんな使用例が書かれていました。
1>>> is_feature_enabled('entity_x')2True「entity_x を渡すと True が返る」と読めます。ところが、実際に同じ呼び出しをランタイムで実行してみると、返ってきたのは False でした。docstring の例が、実装の現在の挙動と一致していなかったのです。
実機で確認したところ、実態はこうでした。
| 入力 | docstring の例 | 実際の戻り値 |
|---|---|---|
entity_x | True | False |
| 別の entity | (記載なし) | True |
つまり、entity_x はもう有効ではなく、別の entity が有効になっている。にもかかわらず、docstring の例は古い時代の True を指したまま放置されていました。これは「docstring が古いまま、実装側の挙動だけが変わった」典型的な drift です。
さらに調べると、別の関数の docstring にも同じ問題がありました。そちらは「対象となる値の一覧」を列挙していたのですが、その列挙に entity_x が含まれているのに、実際の値の集合には entity_x が含まれていなかったのです。drift は1箇所ではなく、2箇所目も見つかりました。
なぜ「ドキュメントの drift」が危険なのか
コメントや docstring がコードと食い違うこと自体は、よくある話です。しかし AI 駆動開発では、これが特別に危険な意味を持ちます。
AI はドキュメントの「権威性」を高く見積もる
人間のレビュアーなら、docstring と実装が矛盾していたら「どちらかが古いな」と疑います。ところが AI は、自然言語で明示的に書かれた docstring を**「人間が意図を込めて書いた仕様」**として高く評価する傾向があります。コードの実際の挙動を1行ずつ追うより、docstring の説明文を信じるほうが、AI にとっては「楽で、もっともらしい」のです。
その結果、古い docstring を正と誤認し、そこに書かれた古い挙動を踏襲した実装やテストを書いてしまうリスクがあります。今回のケースでも、もし drift に気づかず進めていたら、「entity_x が True を返す」という古い前提でテストが書かれ、実装が後退していたかもしれません。
drift は静かに伝播する
docstring の例は、コピーされて別の場所のサンプルになったり、テストの期待値の元ネタになったりします。1つの古い例が正として扱われると、そこから派生する実装・テスト・別のドキュメントすべてが、古い前提を引きずります。drift は1箇所で止まらず、静かに下流へ伝播するのが厄介な点です。
学び:doctest で「例」と「実態」を機械的に突合する
このとき効いたのが doctest でした。docstring の中に書かれた >>> の例は、ただの飾りではなく、doctest として実際に実行して戻り値を照合できる形式です。
今回も doctest を走らせることで、docstring の例が実態と一致しているかを機械的に検証できました。古い例のままだと doctest は失敗し、実態に合わせて修正すると doctest が通る(ok になる)。「人間が目で見て気づく」ではなく、「ツールが落ちて教えてくれる」状態に変えられたわけです。
ここで重要なのは、矛盾が見つかったときにどちらを正とするかを決めておくことです。原則はシンプルで、こうなります。
docstring とコードが矛盾したら、**コード(実際の挙動)を唯一の真実(SoT)**とする。
docstring は「意図」を語りますが、実際にユーザーやシステムが体験するのは「挙動」です。だから矛盾時はコードを正とし、ドキュメントのほうを実態に合わせて直す。AI にもこの優先順位を明示しておかないと、AI は逆に「ドキュメントが正しいはず」とコードを書き換えにいってしまいます。
パターン化:ドキュメント整合検査の3つの仕掛け
この経験を一般化すると、対策は3つの層に整理できます。
| 層 | 仕掛け | 効果 |
|---|---|---|
| 検証 | docstring の例を doctest 化し CI で実態と突合 | drift をビルドで自動検出 |
| 運用 | コード変更時に関連 docstring/コメントの更新を必須化 | drift の発生そのものを抑える |
| 指示 | AI に「矛盾時はコードを SoT とせよ」と明示 | AI の誤った権威付けを矯正 |
1. 検証:例を doctest にして CI で落とす
docstring に「こう呼ぶとこう返る」と書くなら、それを doctest として実行可能にしておきます。CI で doctest を走らせれば、実装が変わって例とずれた瞬間にビルドが落ち、drift を着手前に検出できます。説明文を、実行可能な検査に格上げするのがポイントです。
2. 運用:コード変更時にドキュメント更新を必須化
関数の挙動を変えたら、その関数の docstring とコメントの更新を変更セットの一部として必須にします。「コードだけ直してドキュメントは後で」が drift を生む温床なので、レビューのチェックリストに「関連 docstring を更新したか」を入れておきます。
3. 指示:AI への明示的な優先順位付け
AI に実装やレビューを任せるときは、プロンプトに次の一文を入れておきます。
docstring・コメントと実際のコード挙動が矛盾した場合は、コード(実際の挙動)を正とし、ドキュメント側を実態に合わせて修正せよ。古いドキュメントを仕様として踏襲してはならない。
この一文があるかないかで、AI が「古い例を信じて後退する」か「実態に合わせて直す」かが分かれます。
運用Tips:レビューで drift を見つけるための小さな習慣
最後に、レビュー時に drift を拾うための実務的なコツです。
- docstring に具体的な戻り値の例が書いてあったら、レビュー中に一度はランタイムで実行して照合する。目で読むだけでは drift は見抜けません。
- 「値の一覧」「対象の集合」を列挙している docstring は、その集合を実際に生成して差分を取る。今回の2箇所目はこれで見つかりました。
- 例が doctest 化されていない docstring を見つけたら、その場で doctest 化を提案する。検査されないドキュメントは、いずれ必ずずれます。
この対策の価値は、削減率や節約時間といった数値以前に、「AI が古いドキュメントを正と信じて後退する」という事故そのものを着手前に防げる点にあると感じています。
まとめ:ドキュメントではなく挙動を真実とする
AI 駆動開発でコードを速く変えていくと、ドキュメントの更新は必ず後回しになり、docstring とコメントは静かに古びていきます。そして AI は、その古いドキュメントを「人間が書いた仕様」として高く評価し、正として踏襲してしまいます。
だから、
- docstring の例を doctest 化して、CI で実態と機械的に突合する
- コード変更時に関連 docstring/コメントの更新を必須にし、drift の発生を抑える
- AI に「矛盾時はコード(実際の挙動)を SoT とせよ」と明示する
「ドキュメントに書いてあるから正しい」は、AI 時代にはむしろ危険な前提です。唯一信じてよいのは、実際に動いているコードの挙動だけ——doctest はその当たり前を、機械が見張ってくれる形に変えてくれます。