Claude が書く長いコメントは、Claude 自身の役に立っていなかった
はじめに
株式会社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 点です。
- 禁止リストの形にした — 「書く/書かない」の表は判断を委ねる形式なので、表にない項目の扱いが曖昧になる。禁止する対象を名指しする方が、AI にとっても人間にとっても解釈の幅が小さい
- 実際に出てきた違反を、そのまま項目として足した — 変更履歴とタスク ID 参照。抽象的な原則を 1 つ置くより、具体的な違反を列挙する方が効く(という仮説)
- グローバルに移した — どのリポジトリでも同じことを求めているため
この置き方が効いているかは、まだ判断できていません。移してから日が浅いので、次は同じ集計を回して「4 行以上のブロック数」が減ったかを見る予定です。少なくとも、体感で判断してもう一度失敗することは避けられます。
まとめ
- AI が書く長いコメントは、AI 自身の読解に役立っていない。むしろコンテキストを圧迫して不利になる
- 長くなる理由は理解のためではなく出力の癖。特に自信がない箇所ほど言葉で埋めようとするため、コメントの分量は不確実さの指標になっている
- 「コードから復元できない情報だけを書く」というルールを入れたら、コメント比率は 21.1% → 8.8% に下がった。しかし 4 行以上の長いブロックは 52 個 → 50 個でほぼ不変だった
- 減らなかったのは、そうしたコメントが無意味だからではなく、有用だから。設定 1 行に付いた 18 行は、全行が「コードから復元できない情報」に該当していて、このルールでは 1 行も削れなかった
- 判断軸を「有用か」から「そこがその情報の置き場所か」に変えた。変更履歴は git log、タスク ID は issue にあるので、コードには書かない
- ルールを書いた後は、出力が実際に変わったかを測る。体感では「効いた/効かない」を取り違える
Discussion
/doctorコマンドを実行すると整理してくれてよいですよ。