✍️

結局、どんなコメント書いたらええの?

に公開

みなさん、こんにちは!

AGENTS.md に全ての AI prompt が統合されてほしいな~」と密かに願っているフロントエンドエンジニアの @nyaomaru です!

前回の記事では、「なんでコメントを書くのか?」について書きました。

https://zenn.dev/nyaomaru/articles/why-should-i-write-comment

今回は、「じゃあ、どんなコメント書いたらええねん?」について

僕が最近愛読している A PHILOSOPHY OF SOFTWARE DESIGN (by JOHN OUSTERHOUT) の 13 章の記述をベースに考えてみたいと思います。

ほんなら、一緒に見てこな!

はじめに

If users must read the code of a method in order to use it, then there is no abstraction.

— JOHN OUSTERHOUT(12.1 Good code is self documenting, P.97)[1]

  • ユーザーがメソッドを使用するためにそのコードを読まなければならない場合、抽象化は存在しない。

One of the purpose of comments is to make it unnecessary to read the code.

— JOHN OUSTERHOUT(12.6 A different opinion: comments are failures, P.97)[1:1]

  • コメントの目的の一つは、コードを読む必要がなくなるようにすることである。

ってあるように、メソッドを利用する ユーザー目線で何が書いてあったら嬉しいかなぁ~? と考えてコメントを書いてくのがええと思うで。

じゃあ実際にどんなコメントを書くんか?を具体例で見ていこかぁ~

🏷️ コメント書くときの基本

まずは基本的なことを見ていこかぁ~

大きくわけると、以下の3つが大事やと思うで~

  • 繰り返しを避ける
  • 詳細を追加する
  • 直感を高める

🔄 繰り返しを避ける

If the information in a comment is already obvious from the code next to the comment, then the comment isn't helpful

— JOHN OUSTERHOUT(13.2 Don't repeat the code, P.104)[1:2]

  • コメント内の情報がコメントの隣のコードからすでに明らかな場合、そのコメントは役に立ちません。

既にコードに書いてあることを、単純にコメントで書いてもあんまり意味ない。

例えば

// 状態のコピーを取得する
const copyState = getCopy(state);

// copyState がある場合
if (copyState) {
  // 新しい状態をコピーにする
  newState = copyState;
} else {
  // 新しい状態を null にする
  newState = null;
}

というコメントがある。確かに、日本語で理解する分には意味があるかもしれへん。やけど、コードに書いてある以上の情報が書いてへんから冗長になってもうてる。

これやったらコメント書かへんほうが、シュッとしてて見やすい。

つまり、単純な繰り返しはアンチパターンやな。

🖊️ 詳細を追加する

Comments augment the code by providing information at a different level of detail.

— JOHN OUSTERHOUT(13.3 Lower-level comments add precision, P.105)[1:3]

  • コメントは、異なる詳細レベルで情報を提供することにより、コードを拡張します。

コメントはコードを補完できる。コードからは読み取りにくい詳細を注釈できる。

例えば

// state を保存する
save(state);

// 3 秒待つ
await sleep(3000);

ってコメントがあっても、前項で説明した通り、繰り返しのアンチパターンになってもうてる。

でも、実装には意図があって何かしらの目的があるはずや。

ほんならこうしてみたらええんちゃうかな

// state を保存する。
// 背景: この関数は DB のトランザクション内でのみ呼び出される前提。
// 例外発生時には上位レイヤーで rollback されることを期待している。
save(state);

// API のレート制限を回避するため、3 秒待機する。
// FIXME: バックオフ戦略が改善されたらこの sleep は削除予定。
await sleep(3000);

実装コードからだけやと読み取れへん詳細が記載されたことで、 なんでこの実装をしたのか? がすっと入ってくる。

もちろん、全てのコードを追っていくと理解できるはず。

せやけど、それは後続の開発者に全てのコードを読むことを強要させてるだけや。

時間は有限やし、バグ対応や障害対応でそんな時間あらへんときに、こんなコメントが書いてあると理解しやすくてとても助かるなぁ~

どんな詳細がある?

ほな、どんな詳細を書けばええねん?ってなったとき、これを参考にしてほしい。もちろん、これだけやなくって、いろんな詳細があると思うで~

  • What are the units for this variable?
  • Are the boundary conditions inclusive or exclusive?
  • If a null value is permitted, what does it imply?
  • If a variable refers to a resource that must eventually be freed or closed, who is responsible for freeing or closing?
  • Are there certain properties that are always true for the variable (invariants), such as "this list always contains at least one entry"?

— JOHN OUSTERHOUT(13.3 Lower-level comments add precision, P.106)[1:4]

  • この変数の単位は何ですか?
  • 境界条件は包含的ですか、それとも排他的ですか?
  • null 値が許容される場合、それは何を意味しますか?
  • 変数が最終的に解放またはクローズする必要があるリソースを参照する場合、解放またはクローズの責任は誰にありますか?
  • 「このリストには常に少なくとも 1 つのエントリが含まれています」など、変数に常に当てはまる特定のプロパティ(不変条件)はありますか?

つまり、利用者が何を知りたいだろうか?もし自分が数年後にこのコードを見返したとしたら、パッと見で理解するにはどんなコメントがいいだろうか?

って考えると、良い詳細コメントが書けるんちゃうかな

🔥 直感を高める

抽象度の高いコメントを書くことで、コードを読まなくても何となくわかるようにできる。

  • Ask yourself
    • What is this code trying to do?
    • What is the simplest thing you can say that explains everything in the code?
    • What is the most important thing about this code?

— JOHN OUSTERHOUT(13.3 higher-level comments enhance intuition, P.108)[1:5]

  • 自問自答してみましょう
    • このコードは何をしようとしているのでしょうか?
    • コードのすべてを説明できる最もシンプルな言葉は何でしょうか?
    • このコードで最も重要なことは何でしょうか?

例えば

// 配列をソートして、重複を取り除く
const uniqueSortedUsers = Array.from(new Set(users.sort()));

ってコメントがあっても、単純に実装の説明しかしていない。でも、

// ユーザー一覧を「名簿」として扱えるように正規化する
const uniqueSortedUsers = Array.from(new Set(users.sort()));

って書いてあると、なるほど。ユーザーの名簿を作りたかったんだなと直感でわかる。

そのあとに実装を読むことで、なるほどなるほど~、ソートして重複も消してるんだな~ってなる。

良いコメントは、直感を高めて理解を深める。

ほなもっと具体的に

これまで、基本的な考え方に触れてきたけど、もっと実務寄りにコメントを書くときには、大きく以下の3つになると思うで~

  • インターフェースのコメント
  • 実行に関するコメント
  • モジュールを跨ぐコメント

これらのコメントを組み合わせて、かつ前述の基本を守ることで、より有益なコメントを書くことができるんやな。

ほな、一個ずつみていこか~

😃 インターフェースコメント

  • One of the most important roles for comments is to define abstractions.
  • If you want code that presents good abstractions, you must document those abstractions with comments.

— JOHN OUSTERHOUT(13.5 Interface documentation, P.109 / P.110)[1:6]

  • コメントの最も重要な役割の一つは、抽象化を定義することである。
  • 優れた抽象化を実現するコードを望むなら、それらの抽象化をコメントで文書化しなければならない。

インターフェースコメントとは、クラス・関数・メソッド・データ構造など、実装の詳細を理解するために抽象化されたコメントのことや。

例えば

/**
 * カートの商品をループして合計金額を計算し、税率をかけて税込金額を返す
 */
function calculateTotalPrice(cart: Cart): number {
  let total = 0;
  for (const item of cart.items) {
    total += item.price * item.quantity;
  }
  return total * TAX_RATE;
}

これだけでも、確かに雰囲気は理解できる。やけど、実装をなぞっているだけで、利用者が知りたい抽象化情報がない。

でも、こうやって書いてみると

/**
 * カートの金額を集計し、最終的にユーザーへ請求する金額を計算する。
 *
 * 背景: 値引きやキャンペーンは別処理で適用される前提。
 *       計算結果は課税済みの金額となる。
 *
 * @param cart ユーザーが購入しようとしているカート
 * @returns 請求額(税込)
 */
function calculateTotalPrice(cart: Cart): number {
  let total = 0;
  for (const item of cart.items) {
    total += item.price * item.quantity;
  }
  return total * TAX_RATE;
}

「あ~、カートの金額を計算しているんだけど、課税のみで、他の値引きやキャンペーンは考慮されていないんだな。利用するときは気を付けないと。あと、引数にカート渡したら、請求額が返ってくるんだな。なるほどなるほど~」

ってな具合で、抽象化された情報、背景、パラメータ、返却値が書いてあると、利用者側の理解度はさらに深まる。

これがインターフェースコメントの威力や

🏃 インプリメンテーションコメント

  • The main goal of implementation comments is to help readers understand what the code is doing (not how it does it)

— JOHN OUSTERHOUT(13.6 Implementation comments: what and why, not how, P.116)[1:7]

  • インプリメンテーションコメントの主な目的は、読者がコードが何をしているか(どのようにしているかではない)を理解するのを助けることです。

実装の中で説明が必要なコードがある。そこで、実装がどのように動作するのか?をコメントするんじゃなくて、なぜこれを書いたのか? と、これは何か? が書いてあると、理解しやすい。

例えば

async function saveOrder(order: Order) {
  // 注文オブジェクトをデータベースに挿入する処理を呼び出す。
  // この処理では、order の各フィールドを DB のカラムにマッピングして、
  // SQL の INSERT 文を生成して実行する。
  await db.insert(order);

  // 在庫を減らす処理を呼び出す。
  // まず order.items の配列をループして、それぞれの商品の quantity を読み取り、
  // inventory テーブルの在庫数からその分を減算して UPDATE する。
  await inventory.decrease(order.items);

  // 注文確認メールを送信する処理を呼び出す。
  // 内部的には SMTP サーバーに接続し、From/To/Subject/Body を組み立てて、
  // メール送信キューに投入する。
  await mailer.sendOrderConfirmation(order.userEmail);
}

こんな感じで書いてあると、内部の処理の詳細が含まれているけど、そんなに知らなくていい情報がたくさん混じってて読みにくいし、無駄に複雑に感じてまう。

async function saveOrder(order: Order) {
  // 注文の永続化処理
  // NOTE: DB に保存できなければ以降の処理はすべて失敗させる。
  await db.insert(order);

  // 在庫の更新
  // この時点で在庫を引き当てることで、他ユーザーによる二重注文を防ぐ。
  await inventory.decrease(order.items);

  // 注文完了通知の送信
  // NOTE: メールサーバーの障害時にはリトライが発生する。
  await mailer.sendOrderConfirmation(order.userEmail);
}

これなら呼び出す側も「この関数を叩けば注文処理が一貫して行われるんやな~」と理解できる。

実装の「手順」より「意図と役割」を書くのが、インプリメンテーションコメントを活かすコツや

何をするのか? / なぜなのか? / 背景 がセットで書いてあると、短い文章でも実行に必要な情報を理解できる。しかし、これらは単にコードの詳細を読んでいくだけやと、めちゃ時間かかる。つまり、将来の開発者を助けることになる。

これがインプリメンテーションコメントの威力や

✖️ クロスモジュールコメント

  • Cross-module decisions are often complex and subtle, and they account for many bugs, so good documentation for them crucial.

— JOHN OUSTERHOUT(13.7 Cross-module design decisions, P.117)[1:8]

  • モジュール間の決定は複雑かつ微妙であることが多く、多くのバグの原因となるため、適切なドキュメント化が重要です。

npm package として公開したり、別の service class から利用される関数は、モジュールを跨いだ制約や挙動が潜んでいることがある。

コメントで補強しないと、後から参照した人が誤解してバグを生むんやな。

  • The biggest challenge with cross-module documentation is finding a place to put it where it will naturally be discovered by developers.

— JOHN OUSTERHOUT(13.7 Cross-module design decisions, P.117)[1:9]

  • クロスモジュールドキュメントの最大の課題は、開発者が自然に見つけられる場所を見つけることです。

ってあるように、どこに何を書くかが難しい。

例えば DB に数値で保存されるステータスを見てみよか~

export type DBStatus = 'pending' | 'running' | 'done';

一見ただの union type。でも実際には DB に int として保存されていて…

export type DBStatus = 'pending' | 'running' | 'reject' | 'done';

この状態で「reject」を途中に挟んだら、既存の done(2) が reject(2) に化けて大障害になる。

なぜかというと、

-- 既存システムでは status を int として保存:
-- 0 = pending, 1 = running, 2 = done
INSERT INTO orders (id, status) VALUES (123, 0);

SQL の query 発行箇所がこうなってたとする。これは順番を意識して実装しないといけない。けど、TS の型チェックでは一切エラーにならないから実装時点で気づきにくい。

やからこう書く

/**
 * NOTE:
 * - DB には数値 (0,1,2) として保存されるため、**順序を変えたり再割当てするのは禁止**。
 * - 新しい状態は必ず末尾に追加すること。
 * - 内部的には string union だが、DB/外部APIでは int enum として扱われる。
 * - 詳細は DESIGN_NOTES.md「DBStatus」参照。
 */
export type DBStatus = 'pending' | 'running' | 'done' | 'reject';

って書いてあると、「reject を追加したい?なら必ず末尾やで」と次の開発者に自然に伝わるから、安心して末尾に reject を追加できるわな。

DESIGN_NOTES.md に逃がす

ほんで、詳細の設計をコメントに書きすぎると読みづらくなるから、docs/DESIGN_NOTES.md に書いて、参照させるのがええで~

## DBStatus(Cross-module decision)

- 数値は外部永続化されるため途中挿入・再割当禁止。(**Append-only**)
- 既存コードは `status >= Running` のような数値比較に依存している。
- 追加手順:
  1. 新値は末尾に追加
  2. マイグレーションが必要か検討
  3. リリースノートに “enum append-only” を明記

こうすることで、コードのコメントは簡潔に契約を示す → 詳細は DESIGN_NOTES で深掘り、の二段構えになって、モジュールを跨いだ事故を防げるわけやな。

🎯 まとめ

  • When writing comments, try to put yourself in the mindset of the reader and ask yourself what are the key things he or she will need to know.

— JOHN OUSTERHOUT(13.8 Conclusion, P.120)[1:10]

  • コメントを書く際は、読者の立場に立って、相手が知る必要がある重要なポイントは何なのか自問してみてください。

コメントを書くことで、未来の自分や他の開発者を救うことができる。それも、実装のコストよりもだいぶ安い。実装したときに、将来の読者が何に困るだろうか? って考えて、真心を残す。

コメントはコードの一部。 PR 時に“コード+コメント”をレビュー対象に含める癖をつける

これだけで、みんなハッピーになる。ステキやん?

せやからワイよ、ちゃんとコメント書こな。

ほんで、みんなもな 😹

あとがき

AGENTS.md に、この記事の内容を英訳したりして貼り付けるだけでも効果あるで~!

引用元

脚注
  1. JOHN OUSTERHOUT「A PHILOSOPHY OF SOFTWARE DESIGN」Yaknyam Press, Palo Alto, CA., https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=&ved=2ahUKEwi7o9PBicSPAxUsslYBHVkqFvoQFnoECCsQAQ&url=https%3A%2F%2Fwww.amazon.co.jp%2FPhilosophy-Software-Design-John-Ousterhout%2Fdp%2F1732102201&usg=AOvVaw2GVzba-cqAlczi1R8Ju4aR&opi=89978449, 参照日: 2025-09-28 ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

Discussion