Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
rinkou_13
Search
torii
March 31, 2026
13
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
rinkou_13
torii
March 31, 2026
More Decks by torii
See All by torii
rinkou_12
storii
0
4
rinkou_15
storii
0
8
Featured
See All Featured
Typedesign – Prime Four
hannesfritz
42
3.2k
Being A Developer After 40
akosma
92
590k
Crafting Experiences
bethany
1
380
Exploring the relationship between traditional SERPs and Gen AI search
raygrieselhuber
PRO
4
4.4k
How To Speak Unicorn (iThemes Webinar)
marktimemedia
1
600
State of Search Keynote: SEO is Dead Long Live SEO
ryanjones
0
320
How to audit for AI Accessibility on your Front & Back End
davetheseo
0
600
Fireside Chat
paigeccino
43
4.1k
"I'm Feeling Lucky" - Building Great Search Experiences for Today's Users (#IAC19)
danielanewman
230
23k
Bridging the Design Gap: How Collaborative Modelling removes blockers to flow between stakeholders and teams @FastFlow conf
baasie
0
730
Building Better People: How to give real-time feedback that sticks.
wjessup
370
20k
Build The Right Thing And Hit Your Dates
maggiecrowley
39
3.5k
Transcript
Chapter 13 Comments Should Describe Things that Aren't Obvious from
the Code 1
Chapter 13 コメントはコードから読み取れない内容を記述すべき である 2
コメントを書く理由とは? 「プログラミング言語だけでは、開発者が書く際に考えていた重 要な情報がが全て捉えられないから」 3
コメント作成における原則は? 「コードを見ただけでは明らかではない事柄を記述すること」 4
なぜそのコードが必要なのか? なぜその方法で実装されたのか? 実行順などの暗黙のルールは存在しないか? コード全体を読めばわかることでも、苦痛を伴う且つ間違いも起こりやすいため、コ メントで明確に残そう 5
コメントが必要な理由の1つが「抽象化」 。 抽象化の目的は、物事をシンプルに考えるための手段の提供だが、コードそのものは 非常に詳細である。 コードを読むだけではその抽象的な概念を捉えるのは困難なため、コメントによって シンプル且つ高レベルな視点を提供できる。 6
13.1 規約を定める コメントを書くための第一歩は、何にコメントをするか、どのような形式でコメント するかの「規約」を決めること。 ドキュメント生成ツールが存在する言語(Java の Javadoc、JS の JSDoc 等)を使用し
ている場合は、それらのツールの規約に従う。 7
規約の目的 1. 一貫性を担保すること コメントが読みやすく、理解しやすくなる 2. コメントの書き漏らしを防ぐこと 何をどのように書くかが明確でないと、結局何も書かずに終わってしまいがち 8
コメントは以下のカテゴリに分類される インターフェース データ構造のメンバ 実装コメント モジュール間コメント 9
インターフェース データ構造、関数、メソッドなどの宣言の直前に置かれるコメントブロックで、 モジュールのインターフェースについて記述する。 クラスの場合は、そのクラスが提供する全体的な抽象化について説明する。 メソッドや関数の場合は、その全体的な振る舞い、引数と戻り値、副作用や発生 しうる例外、呼び出し側が満たすべき事前条件などを記述する。 10
データ構造のメンバ クラスのインスタンス変数や静的変数など、データ構造のフィールド宣言に添え られるコメント 実装コメント メソッドや関数の内部に記述されるコメントで、コードが内部的にどのように動 作しているかを説明する モジュール間コメント モジュールの境界を越える依存関係を記述するコメント 11
最も重要なのはインターフェースとデータ構造のメンバ すべてのクラス、すべてのクラス変数、すべてのメソッドにインターフェースコメ ントを記述すべき 稀に、変数やメソッドの宣言があまりに明白で、コメントで付け加えるべき 有益な情報が無い場合もあるが、それは例外的なケースのため、コメントが 必要かどうかで悩んでエネルギーを消費するくらいなら、すべてにコメント を付けたほうが簡単。 12
13.2 コードをなぞらない コメントがコードの内容を繰り返しているだけのものは役に立たない 例① ptr_copy = get_copy(obj) # ポインタのコピーを取得 if
is_unlocked(ptr_copy): # objは解放されているか? return obj # 現在のobjを返す if is_copy(ptr_copy): # すでにコピーか? return obj # objを返す thread_id = get_thread_id(ptr_copy) if thread_id == ctx.thread_id: # 現在のコンテキストによってロックされている return ptr_copy # コピーを返す 13
if thread_id == ctx.thread_id: # 現在のコンテキストによってロックされている 全コメントのうち、上記コメント以外に有用な情報が無い。 注目すべきは、全コメントがコードとほぼ同じ詳細度で書かれている点。 コード1行に対してコメントが1行あり、その行の内容を説明している。 このようなコメントが役に立つことはめったにない。
14
例② // 水平スクロールバーを追加 hScrollBar = new JScrollBar(JScrollBar.HORIZONTAL); add(hScrollBar, BorderLayout.SOUTH); //
垂直スクロールバーを追加 vScrollBar = new JScrollBar(JScrollBar.VERTICAL); add(vScrollBar, BorderLayout.EAST); // キャレット位置に関連する値を初期化 caretX = 0; caretY = 0; caretMemX = null; 1,2つ目のコメントはコード自体が十分に明確であるため、不要。 3つ目のコメントは、現状の内容では役に立つほどの情報が含まれていないため、無く てもよい。 15
コメントを書き終えたら、自分自身にこう問いかけてみてください。 「そのコードを一度も見たことがない人が、隣にあるコードを見ただけで、そのコメ ントを同じように書くことができるだろうか?」 もし答えが「はい」なら、そのコメントはコードの理解を助けていない。 16
もうひとつのよくある間違いは、ドキュメント化の対象となるエンティティ(メソッ ドや変数など)の名前に使われている単語を、そのままコメントに使うことである。 以下の例は、説明対象の名前を構成する単語をそのまま使ったコメントである。 例① /** REQから正規化されたリソース名を取得する。 */ private static String[]
getNormalizedResourceNames(HTTPRequest req) ... 正規化されたリソース名とは何なのか? getNormalizedResourceNames が返す配列の要素は何なのか? 17
例② /** PARAMETERをTYPEにダウンキャストする。 */ private static Object downCastParameter(String parameter, String
type) ... ダウンキャストとは何を意味するのか? 例③ /** テキストの各行の水平パディング。 */ private static final int textHorizontalPadding = 4; パディングの要素は何か? それは片側なのか両側なのか? 18
優れたコメントを書くための第一歩は、説明対象の名前に含まれる単語とは異なる単 語を使うこと。 単に名前を繰り返すのではなく、そのエンティティの意味について付加的な情報を提 供する単語を選ぶ。 19
例③の修正版 /** * テキストの各行の左右に残す空白の量(ピクセル単位)。 */ private static final int textHorizontalPadding
= 4; このコメントには、単位(ピクセル)や、パディングが各行の両側に適用されること など、宣言自体からは分からない付加的な情報が含まれている。 また、 「パディング」という言葉に馴染みがない読者のために、その用語を繰り返すの ではなく、それが何を意味するのか(空白の量)を説明している。 20
13.3 下位レベルのコメントで正確性を高める コメントには大きく分けて2つの方向性がある 1. コードよりも下位の、より詳細なレベルの情報を提供するもの コードの厳密な意味を明らかにすることで 「正確性」 を高める 2. コードよりも上位の、より抽象的なレベルの情報を提供するもの
コードの背後にある意図や、コードに対するよりシンプルで抽象的な考え方な ど、 「直感的な理解」 を助ける この節では下位レベル、次節では上位レベルのアプローチについてそれぞれ解説する 21
「正確性」 が最も威力を発揮するのは、クラスのインスタンス変数、メソッドの引 数、戻り値などの変数宣言にコメントを添える場合である。 コメントは次のような欠落している詳細を埋めることができる。 22
この変数の単位は何か? 境界条件は、その値を含むか、含まないか? nullが許可されている場合、それは何を意味するのか? 変数が、最終的に解放やクローズが必要なリソースを指している場合、誰がその責 任を負うのか? 「このリストには常に1つ以上のエントリが含まれる」といった、その変数につい て常に成立すべき性質(不変条件)はあるか? 23
こうした情報の一部は、その変数が使われている箇所全てを調べれば判明するかもし れないが、それは時間がかかり、ミスも起きやすい。 宣言時のコメントは、そうした調査を不要にするほど明快かつ完全であるべき。 ※ コメントは「コードから明らかではないこと」を記述すると説明してきたが、ここで 言う「コード」は、そのコメントのすぐ隣にあるコード(宣言文)を指し、 「アプリケ ーション内すべてのコード」のことではない。 24
変数のコメントで最もありがちな問題は、内容が曖昧すぎることである。 例① // respBuffer内の現在のオフセット uint32_t offset; // ドキュメント内のすべての行の幅と出現回数を保持する private TreeMap<Integer,
Integer> linewidths; 現在とは何を指す? TreeMap のキーが行の幅、値が出現回数であるという点がはっきりしない 幅の単位がピクセルなのか、文字数なのか? 25
例①の修正版 // このバッファにおいて、まだクライアントに返されていない // 最初のオブジェクトの位置 uint32_t offset; // 行の長さに関する統計を <長さ,
カウント> の形式で保持する。 // 「長さ」は行の文字数(改行文字を含む)であり、 // 「カウント」はちょうどその長さを持つ行の数である。 // 特定の長さの行が存在しない場合、その長さのエントリは存在しない。 private TreeMap<Integer, Integer> numLinesWithLength; 2つ目は、より多くの情報を伝える長い名前を採用。また width を length にするこ とで、単位が文字数であることを連想させやすくする。 さらに、各エントリの詳細だけでなく、エントリが存在しない場合にそれが何を 意味するかまで記述している。 26
変数のドキュメントを作成する際は、 「動詞」ではなく「名詞」で考える。 つまり、変数がどのように操作されるかではなく、その変数が「何を表しているか」 に焦点を当てる。 27
例② /* FOLLOWER変数:ReceiverスレッドとPeriodicTasksスレッドが、 * フォロワーのエレクションタイムアウト期間内にハートビートを受信したかどうかを * 通信するためのインジケータ変数。 * 有効なハートビートを受信したときにTRUEに切り替わる。 *
エレクションタイムアウト期間がリセットされるときにFALSEに切り替わる。 */ private boolean receivedValidHeartbeat; このドキュメントは、クラス内の複数の箇所でこの変数がどのように変更されるかを 説明している。 コードの構造をなぞるのではなく、変数が何を表しているかを記述すれば、コメント はより短く、かつ有用になる。 28
例②の修正版 /* エレクションタイマーが最後にリセットされてから、ハートビートを受信した * かどうか。ReceiverスレッドとPeriodicTasksスレッド間の通信に使用される。 */ private boolean receivedValidHeartbeat; このように記述されていれば、ハートビート受信時に
true になり、タイマーリセット 時に false になることは容易に推測できる。 29
13.4 上位レベルのコメントで直感的な理解を促す コメントがコードを補完する2つ目の方法は、直感的な理解を提供することである。 こうしたコメントはコードよりも高い抽象度で記述される。 細部をあえて省略することで、読み手がコードの全体像や意図、構造を理解するのを 助けり。 このアプローチは、メソッド内のコメントやインターフェースのコメントでよく用いら れる。 30
例① // もしassignPosが指すPKHashと同じセッションを使用しているLOADING状態のreadRpcがあり、 // かつそのreadRPCの最後のPKHashが現在割り当て中のものより小さい場合、 // 割り当て中のPKHashをそのreadRPCに追加する。 int readActiveRpcId =
RPC_ID_NOT_ASSIGNED; for (int i = 0; i < NUM_READ_RPC; i++) { if (session == readRpc[i].session && readRpc[i].status == LOADING && readRpc[i].maxPos < assignPos && readRpc[i].numHashes < MAX_PKHASHES_PERRPC) { readActiveRpcId = i; break; } } このコメントは抽象度が低すぎて、詳細に寄りすぎており、このコードが全体として 何を目的としているのか、あるいは含まれているメソッドの中でどのような役割を担 っているのかを説明していない。 31
例①の修正版 // 送信前の既存のRPCの中に、対象のサーバーへ向かうものがあれば、 // 現在のキーハッシュの追加を試みる。 このコメントには詳細な記述は一切無く、コードの全体的な機能を高いレベルで説明 している。 この上位レベルの情報があれば、読み手はコード内で起きていることのほとんどを説 明できる。 32
上位レベルのコメントを書くには、細部から一歩引いてシステムを見る必要がある。 つまり、システムのどの側面が最も重要かを判断し、低レベルな詳細を無視して、最 も根本的な特性だけでシステムを捉える必要がある。 それこそが抽象化の本質であり、上位レベルのコメントを書く際に求められることで もある。 33
例② if (numProcessedPKHashes < readRpc[i].numHashes) { // このリクエストでは、一部のキーハッシュを検索できなかった // (サーバーに保存されていない、サーバーがクラッシュした、
// またはレスポンスメッセージの容量が不足していた、などの理由による)。 // 未処理のハッシュをマークし、新しいRPCに再度割り当てられるようにする。 for (size_t p = removePos; p < insertPos; p++) { if (activeRpcId[p] == i) { if (numProcessedPKHashes > 0) { numProcessedPKHashes--; } else { if (p < assignPos) assignPos = p; activeRpcId[p] = RPC_ID_NOT_ASSIGNED; } } } } 34
例②の抜粋 // このリクエストでは、一部のキーハッシュを検索できなかった // (サーバーに保存されていない、サーバーがクラッシュした、 // またはレスポンスメッセージの容量が不足していた、などの理由による)。 // 未処理のハッシュをマークし、新しいRPCに再度割り当てられるようにする。 このコメントは2つの役割を果たしている。2つ目の文は、コードが何をするかを抽象
的に説明している。1つ目の文はそれとは異なり、なぜそのコードが実行されるのかを 上位レベルの言葉で説明している。 「どのようにしてここ(この処理)に至ったか」と いう形式のコメントは、コードを理解する上で非常に役立つ。 35
13.5 インターフェースのドキュメント 抽象化をドキュメント化する第一歩は、 「インターフェース・コメント」と「実装コメ ント」を分けることである。 インターフェース・コメント そのクラスやメソッドを利用するために知っておくべき情報を提供するもの であり、抽象化を定義する 実装コメント その抽象化を実現するためにクラスやメソッドが内部でどのように動作する
かを記述する この節ではインターフェース・コメントについて解説する 36
例 /** * このクラスは、HTTPプロトコルに対するシンプルなサーバー側のインターフェースを * 実装する。このクラスを使用することで、アプリケーションはHTTPリクエストを * 受信し、処理して、レスポンスを返すことができる。 * このクラスの各インスタンスは、リクエストを受信するために使用される特定の
* ソケットに対応する。現在の実装はシングルスレッドである。 */ public class Http {...} このコメントは、実装の詳細や個々のメソッドの細部には触れず、クラス全体の機能 を説明している。また、クラスの各インスタンスが何を表しているのかも記述してい る。最後に、クラスの制限事項(複数のスレッドからの並行アクセスをサポートして いないこと)についても触れている。これらは、そのクラスの使用を検討している開 発者にとって重要な情報となる。 37
インターフェースコメントの書き方① コメントは呼び出し側から見たメソッドの振る舞いを説明する1、2文で始まる。 これがハイレベルな抽象化にあたる。 各引数と(もしあれば)戻り値を記述しなければならない。これらの説明は正確 である必要があり、引数の値に対する制約や、引数間の依存関係についても記述 する必要がある。 メソッドに「副作用」がある場合は、それをインターフェース・コメントに記載 しなければならない。副作用とは、メソッドの実行結果の一部ではないものの、 システムの将来の動作に影響を与えるあらゆる結果を指す。例えば、メソッドが 内部データ構造に値を保持し、それが将来のメソッド呼び出しで取得できる場合
や、ファイルシステムへの書き込みを行う場合などは副作用にあたる。 38
インターフェースコメントの書き方② メソッドから発生する可能性のある例外についても記述しなければならない。 メソッドを呼び出す前に満たしておくべき「事前条件」がある場合は、それらを 記述する必要がある(例えば、別のメソッドを先に呼び出しておく必要がある、 あるいは二分探索メソッドであれば、探索対象のリストがソート済みである必要 がある、など) 。事前条件は最小限に抑えるのが理想的だが、すべて文書化しなけ ればならない。 39
13.6 実装コメント: 「どのように」ではなく「何」と「なぜ」を書 く 実装コメントとは、メソッドの内部に記述され、その動作を理解する助けとなるコメ ントのこと。 実装コメントの主な目的は、コードが「どのように」動いているかではなく、 「何」を しているのかを読み手に理解させること。 短いメソッドであれば、コードが行うことは1つだけであり、それはインターフェー
ス・コメントですでに説明されているため、実装コメントを付ける必要は無い。 40
一方で長いメソッドには、メソッド全体のタスクを構成する、異なる役割を持った複 数のコードブロックが存在する。 このような場合は、主要なブロックの前に、そのブロックが何をするのかを抽象的な レベルで説明するコメントを追加する。 例 // フェーズ1:アクティブなRPCをスキャンし、完了したものがあるか確認する 41
また、実装コメントは「何をしているか」に加えて、 「なぜそうしているか」を説明す るのにも役立つ。 コードを読んだだけでは分からないようなトリッキーな箇所がある場合は、それをド キュメント化すべきである。 例えば、バグ修正のために一見すると目的が不明瞭なコードを追加したなら、なぜそ のコードが必要なのかをコメントで補足する。 42
13.7 モジュールを横断する設計判断 複数のモジュールを横断する設計の場合、どこにドキュメントを設置するのが良いだ ろうか? 例えば、ネットワークプロトコルの設計では送信側と受信側の双方に影響するが、こ れらは通常別々の場所で実装される。 こうしたモジュールを横断する設計は複雑なためバグの原因となるため、優れたドキ ュメントを作成することが重要。 43
モジュール横断的なドキュメントにおける最大の課題は、開発者が自然に見つけられ る場所にそれを配置することである。 そのようなドキュメントを置くべき「明らかな中心地」が存在する場合もある。 たとえば、RAMCloudストレージシステムには、各リクエストの成否を示す Status という値が定義されている。 開発者が新しいステータス値を追加する際に必ず訪れる場所は、 Status 列挙型 (enum)の宣言場所である。
そこを「中心地」としてドキュメントを残す。 44
typedef enum Status { STATUS_OK = 0, STATUS_UNKNOWN_TABLET = 1,
STATUS_WRONG_VERSION = 2, ... STATUS_INDEX_DOESNT_EXIST = 29, STATUS_INVALID_PARAMETER = 30, STATUS_MAX_VALUE = 30, /* * 注意:新しいステータス値を追加する場合は、以下の更新も行うこと: * (1) STATUS_MAX_VALUE を最大のステータス値に更新し、 * リストの最後にあることを確認する。 * (2) Status.cc 内の "messages" および "symbols" テーブルにエントリを追加する。 * (3) ClientException.h に新しい例外クラスを追加する。 * (4) ClientException::throwException に新しい "case" を追加し、 * ステータス値を固有の例外サブクラスへマッピングする。 * (5) Javaバインディングの ClientException.java に例外用の静的クラスを追加する。 * (6) ClientException.java でステータスコードに応じた例外を投げる case を追加する。 */ } Status; 45
しかし、多くの場合、モジュール横断的なドキュメントを置くべき明確な中心地は見 当たらない。 ひとつの方法は、依存する各場所にドキュメントを複製して記述することだが、シス テムの進化に合わせてすべてのドキュメントを最新状態に保つのは困難である。 どこか一箇所にドキュメントを置くという手もあるが、開発者がその存在に気づかな かったり、どこを探せば良いかわからなくなる可能性が高くなる。 46
そこで筆者が試しているのは、 designNotes という中心ファイルを作成し、そこにま とめて記述するアプローチである。 そしてそこに記述されている内容に関連する各箇所に、 designNotes ファイルを参照 するようにコメントを記述する。 この手法は、ドキュメントが1箇所にまとまるため、更新が楽になり、開発者から比較 的見つけやすくなる。
一方で、ドキュメントが実際のコードから離れた場所に存在するため、更新漏れが発 生しやすくなるというデメリットもある。 47
13.8 結論 コメントの目的は、システムの構造と動作を読み手にとって自明なものにすることで ある。 これにより、読み手が必要な情報をすぐに見つけ出し、変更が正しく機能するという 確信を持ってシステムに手を加えられるようになる。 「コメントはコードから見て自明でないことを記述すべきである」という原則に従う 際、 「自明」かどうかを判断するのは、あなた自身ではなく、そのコードを初めて読む 人の視点である。
48