プログラマーがより良いコードを書くために役立つリソースは数多い。だが、より良いコメントを書くためのリソースはほとんどない。プログラム内のコメント量を測定することは簡単だが、コメントの品質を測定することは難しい。ではどうすればよいのか。
この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。
開発者向けQ&Aサイト「Stack Overflow」は2021年12月23日(米国時間)、コードコメントを書くためのベストプラクティスを紹介した。これはミルズ大学のコンピュータサイエンスの教授であるエレン・スパータス氏による寄稿記事だ。
この記事は2021年7月5日に掲載されたもので、同ブログで2021年に人気を博した記事のトップ10の1つとして再掲載された。
スパータス氏は、「コメントは、出来の悪いコードの言い訳や修正として書くものではなく、(コードが表すものと)異なるタイプの情報を提供することで、良いコードを補完するものだ」との見解を示した。その理由としてシステムアーキテクトのピーター・ヴォーゲル氏が2013年に投稿したコメントに関する3つの事実を挙げた。
(1)コメントを書いて、維持するには費用がかかる
(2)コンパイラはコメントを無視するため、コメントの内容が正しいかどうか機械的に判断する方法はない
(3)コンピュータは(意図した内容とは異なっていても)コードに記述した通りに動作する
そこでスパータス氏はコードコメントを書くためのベストプラクティスを9つのルールにまとめた。
「プログラマーはこれらのルールに従うことで、自分とチームメイトの時間を節約し、フラストレーションを軽減できるだろう」と同氏は述べている。
プログラミング初心者の多くは、コメントを書き過ぎる。入門コースの講師から、そうするように教育されたからだ。だが、コメントを書こうとする意識が強過ぎると、追加情報が全くない無意味なコメントを書いてしまいがちだ。典型的な悪い例を以下に2つ示す。
if (x > 3) { ... } // if
i = i + 1; // iに1を加える
追加情報がないこうしたコメントは、次に挙げるようなマイナスの価値しか生み出さず、無駄なメンテナンスコストを必要としてしまう。
・視覚的に邪魔になる
・書いたり読んだりするのに時間がかかる
・コメントの内容が古くなってしまうことがある
コメントの誤った使い方は他にもある。それは、コードに含まれているべき情報を提供することだ。次に示す簡単な例では、1文字の変数名を使い、その変数の目的をコメントで説明している。
private static Node getBestChildNode(Node node) { Node n; // 最良の子ノード候補 for (Node node: node.getChildren()) { // 現在の状態がより良い場合はnを更新 if (n == null || utility(node) > utility(n)) { n = node; } } return n; }
次のように適切な変数名を選んでおけば、先ほどのコメントは不要になる。
private static Node getBestChildNode(Node node) { Node bestNode; for (Node currentNode: node.getChildren()) { if (bestNode == null || utility(currentNode) > utility(bestNode)) { bestNode = currentNode; } } return bestNode; }
ブライアン・カーニハン氏とフィリップ・ジェームス・プローガー氏が書籍『The Elements of Programming Style』(邦題『プログラム書法』)に記しているように、「悪いコードに対しては、コメントを付けるのではなく、コードを書き換える」必要がある。
UNIXのソースコードで最も悪名高いコメントは、「You are not expected to understand this」(これを理解することは期待されていない)というものだ。このコメントは、難解なコンテキストスイッチのコードに付いている。
C言語やUNIXを開発したデニス・リッチー氏は、後になってからこのコメントについて、「上から目線で挑発したわけではなく、『ここは試験には出ない』という親切心で書いた」と説明している。だが、残念なことに、同氏と同僚のケン・トンプソン氏自身がこのコードを理解していなかったことが後ほど判明し、書き直しを余儀なくされた。
スパータス氏はここで「カーニハンの法則」を引用している。「デバッグはコードを一から書き下すよりも、2倍の労力がかかる。従って、コードをできるだけ巧妙に書いたとしても、定義上、デバッグできるほどあなたは賢くはない」
あるコードについて、読み手をけむに巻くようなコメントしか書けない場合は、自分が理解し、説明できるコードに書き換えるべきだ。シンプルで分かりやすいコードに書き換えられれば、なお良い。
悪いコメントについては、スティーブン・レビー氏の著書『Hackers: Heroes of the Computer Revolution』(邦題『ハッカーズ』)に書かれている次のエピソードを抜きにしては語れない。
「ピーター・サムソン氏が作成し、広く配布されていたあるプログラムでは、アセンブリ言語の数百の命令が並んでいた。その中で『1750』という数字を含む命令の横に、『RIPJSB』というコメントが1つだけあった。人々がこのコメントを解読しようと知恵を絞った結果、18世紀に活躍した作曲家の大バッハが亡くなったのが1750年であり、『RIPJSB』は、『Rest In Peace Johann Sebastian Bach』の略語であることが分かった」
優れたハッキングは高く評価すべきだが、このコメントは立派なハッキングとはいえない。コメントが混乱を解消するのではなく、混乱を引き起こすのであれば、削除しなければならない。
他の人が不要または冗長と考える可能性があるコードにコメントを付けるのは、良いアイデアだ。そうしたコードの一例として、「MIT App Inventor」(この記事における良い例のソース)に含まれる次の例がある。
final Object value = (new JSONTokener(jsonString)).nextValue(); // Note that JSONTokener.nextValue() may return // a value equals() to null. if (value == null || value.equals(null)) { return null; }
コメントがないと、コードを「単純化」したり、謎めいているが必要不可欠な呪文として捉えたりしてしまう人がいるかもしれない。なぜそのコードが必要なのかをコメントとして書いておくことで、コードを将来読む人の時間を節約したり、不安を軽減したりできる。
ほとんどのプログラマーは、オンラインで見つけたコードを使うことがあるだろう。そうしたコードのソースを示すことで、コードの将来の読み手が次のような文脈を完全に把握できる。
・どのような問題を解決したのか
・誰がそのコードを提供したのか
・その解決策が推奨される理由
・コメント欄での評価
・それがまだ機能しているかどうか
・どうすれば改良できるか
例えば、次のコメントについて確認してみよう。
/** DrawableをBitmapに変換する。以下を利用。https://stackoverflow.com/a/46018816/2219998. */
リンクをたどると、次のことが分かった。
・このコードの作者はStack Overflowの上位3%に入っているTomáš Procházka氏だ
・あるコメントが最適化の方法を提案しており、既にリポジトリに取り入れられている
・別のコメントでは、エッジケースを回避する方法が提案されている
良くない例もある。
// StackOverflowの投稿から引用、人の視覚認識に関係するとされる // 魔法の公式 return (int) (0.3 * red + 0.59 * green + 0.11 * blue);
このコードを理解しようとするなら、数式を検索しなければならない。URLを貼り付けておけば、そうした作業は不要になる。
自分でコードを作成しなかったことを恥ずかしく思う必要はない。コードの再利用は賢明な策であり、時間を節約するからだ。さらに「多くの目玉」がそのコードの課題を解決してくれる。もちろん、理解できないコードを貼り付けてはならない。
なお、コードの再利用については別の注意点がある。コードを再利用する際には、元々のライセンスを守らなければならない。例えばStack Overflowの投稿にあるコードはクリエイティブ・コモンズのライセンスが適用されており、帰属表示が必要だ。ソースを示すコメントを付ければ、その要件を満たすことができる。
Stack Overflow以外にも、次の例のように、規格や他のドキュメントを参考にしてコードを書く場合がある。
// http://tools.ietf.org/html/rfc4180 は、次のことを提案している。 // CSV行はCRLFで、つまり \r\n で終了させる。 csvStringBuilder.append("\r\n");
規格や文書などへのリンクは、コードが解決している問題を読み手が理解する際に役立つ。この情報は設計ドキュメントのどこかにあるかもしれない。だが、コメントを適切に配置すれば、読み手は、最も必要とする時点で、この情報を参照できる。このケースでは、コメントに含まれるリンクをたどると、RFC 4180がRFC 7111によって更新されたことが分かる。
最初にコードを書くときだけでなく、コードの修正時、特にバグを修正するときにも、コメントを追加する必要がある。次のコメントについて考えてみよう。
// 注意:少なくともFirefox 2では、ユーザーがWebブラウザのウィンドウの外をドラッグすると、 // マウス移動(さらにはマウスダウン)イベントは、ユーザーがウィンドウ内を // ドラッグするまで、受信されない。この問題の回避策は // onMouseLeave()の実装に存在する。 @Override public void onMouseMove(Widget sender, int x, int y) { ... }
このコメントは、現在のメソッドと参照されているメソッドのコードを読み手が理解するのに役立つだけでなく、そのコードが現時点でもまだ必要なのかどうか、どのようにテストすればよいのかを判断する助けとなる。
また、イシュートラッカーを参照するのも有益だ。
// プロパティにタイトルが含まれていなかった場合は、その名前をタイトルとして使う (issue #1425)
「git blame」コマンドを使えば、追加または変更されたコミット行を見つけることができる。だが、コミットメッセージは簡潔になる傾向があり、最も重要な変更(イシュー#1425の修正など)が最新のコミット(メソッドをあるファイルから別のファイルに移動するなど)に含まれていないこともある。
ときには、コードに問題があることが分かっていても、コードをチェックインしなければならない場合がある。こうした場合、To Doコメントなどで問題を明示するのが望ましい。
// TODO(hal): 携帯電話のロケールにかかわらず、小数点以下の区切り文字を // ピリオドにしている。小数点以下の区切り文字としてカンマを // 許容する方法を考える必要があるが、そのためには、 // 数値解析や、数値を文字列に変換する場所(FormatAsDecimalなど)を // 更新する必要がある。
標準化されたフォーマットでこうしたコメントを書くことは、技術的負債を測定し、対処するのに役立つ。イシューをトラッカーに追加し、コメントでそのイシューを参照すればなお良い。
Copyright © ITmedia, Inc. All Rights Reserved.