今回はリーダブルコード6章より「良いコメント」を書くための方法を解説します。
内容紹介
6章 コメントは正確で簡潔に
- 6章 コメントは正確で簡潔に
- 6.1 コメントを簡潔にしておく
- 6.2 あいまいな代名詞を避ける
- 6.3 歯切れの悪い文章を磨く
- 6.4 関数の動作を正確に記述する
- 6.5 入出力のコーナーケースに実例を使う
- 6.6 コードの意図を書く
- 6.7 「名前付き引数」コメント
- 6.8 情報密度の高い言葉を使う
- 6.9 まとめ
前章(5章)ではコメントを書くこと自体に対する注意点を解説しました。
6章では実際にコメントを書くにあたり、より正確な情報を、より分かりやすい表現で、より簡潔に短く書くための方法を解説しています。 本章はコメントの書き方の解説ですが、2章や3章で取り扱っていたコードの書き方とリンクする内容が沢山あります。
コードにしろ、コメントにしろ、重要な点は一緒だということですね!
本章はリーダブルコードに具体例が豊富に記載されているため、それを引用しながら内容を深掘りしていきます。
6.1 コメントを簡潔にしておく
「コメントを簡潔にして分かりやすくする」ということですが、さすがにこれだけですと抽象的過ぎてちょっと難しいです。
もう少し具体的に考えると、ここで言う「コメント」とは「コード上に記載するコメント」です。 つまり、そのプログラミング言語にマッチする良い感じの表現が色々とあるものです。 プログラミング言語が持つ表現をコメントにもぜひ活用しましょう。
リーダブルコードの例
// intはCategoryType // pairの最初のfloatは'score' // 2つめは'weight' typedef hash_map<int, pair<float, float>> ScoreMap;
C++の言語表現を活用すると1行でいい感じに各要素の意味を記述できます。
// CategoryType -> (score, weight) typedef hash_map<int, pair<float, float>> ScoreMap;
6.2 あいまいな代名詞を避ける
「あれ」「これ」「それ」を使わずに、代名詞が指す名詞を利用しましょう。
代名詞が利用されている場合、コメントを読んだ人が代名詞に合致する名詞を判断する必要があり、意味の取り違えが起きます。
リーダブルコードの例
// データをキャッシュに入れる。ただし、先にそのサイズをチェックする。
上記コメントの場合、「そのサイズ」は「データのサイズ」を表すのか、「キャッシュのサイズ」を表すのか分かりません。では名詞を代入してみましょう。
// データをキャッシュに入れる。ただし、先にデータのサイズをチェックする。
一目瞭然ですね!!
6.3 歯切れの悪い文章を磨く
「歯切れが悪い」という言葉をブレイクダウンすると、コメントには「計画」よりも「行動」を書きましょう。
コードが実際に処理している行動をコメントに書くことで、より明確な情報となります。
リーダブルコードの例
ウェブクローラにコメントを記載する場合
// これまでにクロールしたURLかどうかによって優先度を変える。
「計画」ではなく、このコードが実際に行っている「行動」を書いてみると
// これまでにクロールしていないURLの優先度を高くする。
直接的かつ明確に表現できます。「クロールしてないURLの優先度が高くなる」という実際の行動も読み取れます。これは「優先度を変える」という計画からは読み取れない情報ですね。
6.4 関数の動作を正確に記述する
実装にコメントを行う場合は「やりたい事」ではなく「やっている事」を書きましょう。
全体像を把握するためのコメントとして「やりたい事」を書くことは非常に有効です。前回の記事(「全体像」のコメント)で触れておりますのでご参照ください。
それに対して、具体的な実装に関するコメントを書く際は「やっている事」を書きます。
「やりたい事」を解決する手段は複数あるものです。そのため、コメントで「やりたい事」を読んでも、結局どの手段を選んだのか?は分かりません。実装にコメントを書くのであれば、コードで実際に「やっている事」を書く必要があります。
リーダブルコードの例
ファイルの行数を数える関数にコメント書く場合
// このファイルに含まれる行数を返す。
「行数を数えたい」という「やりたい事」が書いてあります。しかし、このコメントでは以下の疑問が解消しません。
- 空ファイルは、0行なのか1行なのか
- "hello"は、0行なのか1行なのか
- "hello\n"は、1行なのか2行なのか
- "hello\n world"は、1行なのか2行なのか
- "hello\n\r cruel\n world\r"は、2行なのか3行なのか4行なのか
「やっている事」をコメントすることで、上記の疑問は解消します。
// このファイルに含まれる改行文字('\n')を数えて返す。
こうすると改行文字('\n')が無い場合は、0を返すことが分かりますし、キャリッジリターン('\r')が無視されることも分かります。
6.5 入出力のコーナーケースに実例を使う
関数のコメントに「やっている事」をちゃんと書いても、まだちょっと理解するのが難しいなと感じた場合は実例を書きましょう。
どんな入力を入れたらどんな出力が返ってくるか?が実例で記載されていると、呼び出したときのイメージが掴みやすいです。
「テストコードは後世の人にとってドキュメントになる」とされている理由がまさしくこれですね。テストコードが実例となりますので、関数がどのように動くかを理解する大きな手助けとなります。
リーダブルコードの例
文字列の一部を除去する関数の場合
// 'src'の先頭や末尾にある'chars'を除去する String strip(String src, String chars) { ... }
このコメントで何をするかは分かりますが、細かい部分でどう動くのかがイメージできず不安が残ります。
- charsは、除去する文字列なのか、除去する文字列集合で順序は関係ないのか?
- srcの末尾に複数のcharsがあった場合はどうなるのか?
このような疑問を解消する実例をコメントに書いておきましょう。
// 'src'の先頭や末尾にある'chars'を除去する // 実例 : strip("abba/a/ba", "ab") は "/a/"を返す。 String strip(String src, String chars) { ... }
実例があることで、どのような入力を行ったら、どんな出力が返ってくるかがイメージできるようになります。
6.6 コードの意図を書く
コードを見たままのことをコメントに書くのではなく、なぜそのようなコードを書いたのか意図を表すようにしましょう。
これは前回の記事「5.2 自分の考えを記録する」とリンクする内容です。 意図をコメントに書くことで、後世の人がコードを読んだときに「何をしているかは分かるが、何でしているのかが分からん」という状態を防ぐことができます。
リーダブルコードの例
以下の例はコードの動作をそのままコメントしています。
prices.sort(comparePrice); // 逆順にイテレートする for(ListIterator it = prices.listIterator(prices.size()); it.hasPrevious(); ) { ... }
意図を記載することで、何で逆順にイテレートするのかが分かるようになります。
prices.sort(comparePrice); // 値段の高い順番に表示するため逆順にイテレートする for(ListIterator it = prices.listIterator(prices.size()); it.hasPrevious(); ) { ... }
値段が高い順番に処理したいという意図が分かりますので、自然とfor文の直前に行っている「prices.sort(comparePrice);
」が値段の昇順ソートであることも分かります。
もし「prices.sort(comparePrice);
」が値段の降順ソートであるならば、for文の書き方が間違っていることも分かります。
コメントに意図を書き出すことが難しいと感じたときは、以下を試してみてください。
- 書いたコメントの先頭に「のために」を付けたす
- 書いたコメントの末尾に「なぜなら」を付けたす
これをやることで、自然と意図まで書くことができるようになります。前述した例は「書いたコメントの先頭に『のために』を付けたす」パターンですね。
6.7 「名前付き引数」コメント
名前付き引数に対応していない言語では、名前付き引数のようなコメントを入れることでコードが読みやすくなることがあります。
java言語は現時点(2023年)では名前付き引数の機能を持っていません。IDEを利用していればjavadocをすぐに参照できますのでそこまで大きな問題と感じていない人も多いかと思います。
それでも、以下のような場合には名前付き引数コメントを利用すると、より読みやすいコードになるかもしれません。
リーダブルコードの例
名前付き引数が利用できるpythonで接続処理を呼び出す場合
Connect(10, False)
Connect(timeout = 10, use_encryption = False)
名前付き引数を利用した方が、引数の意味が分かりやすくなります。
同じような処理がjavaにあったとして
voic connect(int timeout, boolean useEncription) { ... } ... connect(/* timeoutMs = */ 10, /* useEncription = */ false);
名前付き引数のようなコメントを入れることで情報を追加することができます。 この例のポイントは「第一引数の名前に単位が入っていないのでコメントで単位を補っている」ことです。 「javadocを見ても引数名が分かりにくい」場合に情報を追加する形ですね。
オーバロードを利用している場合の例としては
find(String src, String chars) find(String src, String chars, boolean backwordsSearch) find(String src, String chars, int fromIndex) find(String src, String chars, int fromIndex, boolean backwordsSearch)
オーバーロードされているメソッドの中から、同じ引数だけど引数の型が異なるメソッドを呼び出してみます。
find("hello world", "o", 3); find("hello world", "o", true);
第三引数の型が異なるため、オーバーロードされていることは分かりますが、第三引数の意味はさすがに分かりません。 気になった方はIDEでjavadocを確認するでしょう。
find(/* src = */ "hello world", /* chars = */ "o", /* fromIndex = */ 3); find(/* src = */ "hello world", /* chars = */ "o", /* backwordsSearch = */ true);
コメントで情報を追加すると、javadocを見なくても第三引数の意味が分かりますね!
よくわからない引数名に対して情報を追加してあげたいとき、最もコストがかからず簡潔に実現する方法ですので「ちょっとわかりにくいな」と思ったときに活用ください。
6.8 情報密度の高い言葉を使う
IT業界やプログラミングの世界で利用される専門用語、業界用語を活用しましょう。
専門用語や業界用語は外部の人には全く理解できないという問題はありますが、代わりに言葉が持つ情報量が高いです。
上手く専門用語、業界用語を活用することで、短く簡潔なコメントで明確な意味を沢山持たせることができます。
リーダブルコードの例
// このクラスには大量のメンバがある。同じ情報はデータベースにも保管されている。ただし、 // 速度の面からここにも保管しておく。このクラスを読み込むときには、メンバが存在してい // るかどうかを先に確認する。もし存在していればそのまま返す。存在しなければ、データベー // スから読み込んで、次回のためにデータをフィールドに保管する。
4行に渡るコメントに対して、専門用語を活用すると以下の1文で表現できます。
// このクラスの役割は、データベースのキャッシュ層である。
一度取得したデータをメモリ上に保管しておいて、二度目以降の利用時に性能を改善するパターンが「キャッシュ」になります。
こう見ると「キャッシュ」の5文字にものすごい情報量が詰まっていますね!
まとめ
本記事では、リーダブルコード6章の内容を解説しました。
良いコメントを書く技術を説明しましたが、何よりも大事なことは「コメントを書く」ことそのものです。
前回の記事にもあった「ライターズブロック」を乗り越えること。これが一番大事です。
もし、この記事を読んで「うわっ、コメント書くのめんどくさいな!」と思うことがありましたら、一旦この記事の内容はすべて忘れてください。
コメントを書くことに慣れて「どうせ書くならもうちょっと良いもの書きたいな」と思ったときに振り返っていただけましたら幸いです!