💬

Claude が書く長いコメントは、Claude 自身の役に立っていなかった

に公開1

はじめに

株式会社Sally 所属エンジニアの @wellPicker です。
弊社では、スマホやパソコンでマダミスを遊べるアプリであるウズや、マダミス情報・予約管理サイトマダミス.jp、マダミス開発ツールウズスタジオを開発しています。
マダミスについてはこちらをご覧ください。

Claude Code に実装させていると、コメントがやたら長くなります。気になって本人に聞いてみたところ、想定と違う答えが返ってきました。そこからルールを整備したのですが、思ったほど効かず、原因を調べたら「削れない理由」がはっきりしました。

その一連の記録です。

まず実物

弊社バックエンド(Go)の、WebSocket の圧縮設定です。Claude が書きました。

// WebSocket フレーム圧縮 (RFC 7692 permessage-deflate) を有効化する。
// クライアント (Dart graphql v5.2.3 + dart:io WebSocket) は CompressionOptions.compressionDefault で
// 既に Sec-WebSocket-Extensions: permessage-deflate を advertise しているため、
// サーバー側で受け入れるだけで subscription レスポンスがフレーム単位で gzip 相当に圧縮される。
//
// context takeover は使わない。ContextTakeover は接続ごとに
// flate.Writer 1.2MB + sliding window 32KB を固定で保持し続けるため、
// long-lived な subscription 接続数に比例して heap が膨らむ
// (coder/websocket compress.go の doc も long lived かつ書き込み頻度が低い接続では
// NoContextTakeover を推奨している)。NoContextTakeover なら flate.Writer は
// sync.Pool から借りて返すので、接続数に対する固定コストが無くなる。
//
// gqlgen v0.17.92 で gorilla/websocket から移行する前は gorilla の
// EnableCompression: true だったが、gorilla は context takeover 自体を
// 実装しておらず (doc.go: "this package does not support compression with
// context takeover") 常に no context takeover 相当だった。移行時に
// ContextTakeover へ変えたことで prd の backend-graph が memory limit 2Gi に
// 到達して OOMKill するようになったため、移行前と同じ挙動へ戻す。
CompressionMode: coderws.CompressionNoContextTakeover,

設定 1 行に対してコメント 18 行です。しかも、書いてあることに嘘はありません。ライブラリの仕様も、本番で OOMKill が起きた経緯も事実です。

この「嘘ではない」というのが、後で効いてきます。

本人に聞いてみた

長いコメントは AI 自身の読解を助けているのかもしれない、という可能性を考えて、そのまま聞いてみたところ、以下のような回答が返ってきました。

正直に言うと、助かってません。むしろ邪魔になっている方が多いです。

私がファイルを読むときに実際に見ているのはコードそのものです。// ユーザー ID を取得する の下に final userId = await getUserId(); があっても、情報量はゼロで、その分だけ画面(コンテキスト)を占有して周囲のコードが見えにくくなる。長いファイルを読むときは特に、コメントが多いほど「実際のロジックの密度」が下がって全体構造が掴みにくくなります。

読み手として得をしていないどころか、コンテキストを圧迫して不利になっているという話でした。限られたコンテキストに冗長な行が混ざれば、その分だけ実際のコードが入らなくなるので、言われてみれば当然です。

「ではなぜ書くのか」の説明も続きました。

理解のためではなく、出力の癖です。正直な内訳としては:

  • 説明的で丁寧な出力が好まれるように訓練されている
  • 「ちゃんと考えて書きました」を可視化する方向に流れやすい
  • 自信がない箇所ほど、言葉で埋めて補強しようとする(これが一番よくない。不確実さがコメントの分量に化けている)

3 つ目が収穫でした。コメントの分量が、その箇所の不確実さの指標になっている。 本来なら「この実装で合っているか自信がない」と申告してほしい場面で、代わりに長い説明が生えているということです。丁寧な説明を「よく理解して書かれたコード」の証と読むのは、むしろ逆でした。

どういうコメントなら価値があるのか

同じ回答で、例外として挙がったのが「コードから復元できない情報」でした。過去の事故、外部制約、直感に反する挙動。これらはコードをいくら読んでも出てきません。

そこで、この基準をプロジェクトのルールファイル(.claude/rules/coding-principles.md)に書きました。

## コメントの分量

コメントには、コードから復元できない情報だけを書く。実装の実況(`// ユーザー ID を取得する` の類)は
情報量がゼロで、周囲のコードの見通しを下げるだけなので書かない。

| 書く | 書かない |
|---|---|
| なぜその実装にしたか(他の選択肢を採らなかった理由) | 何をしているか(コードを読めば分かること) |
| 外部制約・過去の事故・仕様の由来 | 関数名・変数名の言い換え |
| 直感に反する挙動、踏みやすい罠 | 「〜を初期化する」系の実況 |

実装に自信がない箇所を、コメントを盛って補強しない。不確実さはコメントの分量ではなく、
PR description や回答本文で「ここは未確認」と明示して伝える。

これで解決したつもりでいました。

ルールは半分しか効かなかった

体感が変わらなかったので、実際に測ってみました。ルールを追加したコミットの前後の一定期間で、自分がマージした PR 27 件の diff を集計したものです(自動生成ファイルは除外)。

変更前 変更後
追加コード 2,909 行 6,448 行
追加コメント 777 行 624 行
コメント比率 21.1% 8.8%
4 行以上の連続コメントブロック 52 個 50 個
ブロックの最大 / 平均 17 行 / 7.3 行 18 行 / 6.2 行

比率は半分以下に下がった一方、「1 箇所に固まった長いブロック」はまったく減っていません。 個数は 52 個から 50 個、最大に至っては 17 行から 18 行へ増えています(冒頭の実例がその 18 行です)。

体感が変わらなかった理由もこれで説明がつきます。読んでいて目に付くのは全体の比率ではなく、スクロール中に現れる十数行の塊の方だからです。散らばった 1 行が減っても、印象は変わりません。

なぜ長いブロックだけ残ったのか

冒頭の 18 行に、ルールを当ててみると理由が分かります。

内容 「コードから復元できない情報」か
1〜4 クライアント側が既に圧縮を advertise しているので、サーバーは受け入れるだけでよい 該当する(クライアント実装を見ないと分からない)
6〜11 context takeover は接続ごとに固定メモリを持つ。ライブラリの doc も long-lived な接続では非推奨としている 該当する(ライブラリの内部仕様)
13〜18 移行前は gorilla を使っており、その実装では context takeover が無効だった。移行時に変えたら OOMKill した 該当する(過去の事故の経緯)

18 行すべてが「コードから復元できない情報」に当てはまります。 つまりこのルールでは、1 行も削れません。

これが「効かなかった」の正体でした。AI が書くコメントは、無意味だから消せるのではなく、有用だから消せない。実況コメントのように情報量がゼロなら誰でも消せますが、事実として正しく、しかもコードには書かれていない情報が 18 行並んでいると、消す根拠が作れないわけです。

基準を「有用か」から「置き場所」に変えた

そこで判断軸を変えました。その情報が有用かどうかではなく、そこがその情報の置き場所かどうかで切ることにしたわけです。

もう一度 18 行を見ると、後半 6 行(13〜18 行)は「移行前は gorilla だった」「移行時に変えたら壊れた」という変更の履歴です。これは有用ですが、置き場所は git log と PR です。半年後にこのコードを読む人が必要なのは「なぜ今 NoContextTakeover なのか」であって、「何から何に変えたか」ではありません。

同じ観点で、(UZU-1234) のようなタスク ID 参照も外しました。有用そうに見えますが、リンク先の issue が閉じられ、内容が要約されずに残るだけの識別子になります。必要な内容は、コメントに直接書くべきです。

現在はこの基準を、プロジェクトのルールではなくグローバルの ~/.claude/CLAUDE.md に置いています。

## コードコメント

コードコメントには非自明な WHY だけを書く。書くのは隠れた制約・workaround を入れた理由・
読み手が驚く挙動といった、コードから復元できない情報に限る。

書かないもの:

- WHAT (コードを読めば分かること。`// ユーザー ID を取得する` の類)
- 変更履歴 (「〜を追加した」「旧実装では〜だった」等)
- タスク ID 参照 (`(UZU-XXXX)` 等)

docs / README も同様に、issue 参照・経緯・マイグレーション履歴は書かず、
最新仕様のスナップショットだけを書く。

変えたのは 3 点です。

  1. 禁止リストの形にした — 「書く/書かない」の表は判断を委ねる形式なので、表にない項目の扱いが曖昧になる。禁止する対象を名指しする方が、AI にとっても人間にとっても解釈の幅が小さい
  2. 実際に出てきた違反を、そのまま項目として足した — 変更履歴とタスク ID 参照。抽象的な原則を 1 つ置くより、具体的な違反を列挙する方が効く(という仮説)
  3. グローバルに移した — どのリポジトリでも同じことを求めているため

この置き方が効いているかは、まだ判断できていません。移してから日が浅いので、次は同じ集計を回して「4 行以上のブロック数」が減ったかを見る予定です。少なくとも、体感で判断してもう一度失敗することは避けられます。

まとめ

  • AI が書く長いコメントは、AI 自身の読解に役立っていない。むしろコンテキストを圧迫して不利になる
  • 長くなる理由は理解のためではなく出力の癖。特に自信がない箇所ほど言葉で埋めようとするため、コメントの分量は不確実さの指標になっている
  • 「コードから復元できない情報だけを書く」というルールを入れたら、コメント比率は 21.1% → 8.8% に下がった。しかし 4 行以上の長いブロックは 52 個 → 50 個でほぼ不変だった
  • 減らなかったのは、そうしたコメントが無意味だからではなく、有用だから。設定 1 行に付いた 18 行は、全行が「コードから復元できない情報」に該当していて、このルールでは 1 行も削れなかった
  • 判断軸を「有用か」から「そこがその情報の置き場所か」に変えた。変更履歴は git log、タスク ID は issue にあるので、コードには書かない
  • ルールを書いた後は、出力が実際に変わったかを測る。体感では「効いた/効かない」を取り違える
UZU テックブログ

Discussion

ShintaroAmaikeShintaroAmaike

/doctorコマンドを実行すると整理してくれてよいですよ。

5