ソースコード中のコメントは多いほうがよいのか、少ないほうがよいのか?

会社の新人教育のような場では、課題のソースコードを提出するとまず「コメントを書け」と指摘されて、次に提出すると今度は「コメント多すぎ」と指摘されたりする。というか自分が教育担当だと、こういう新人からしたら理不尽に思われるだろう発言をしてしまう気がする(これはひとえに「何をどうコメントするべきか? 逆に、何をどうコメントするべきでないのか?」という点を適切に指導できていないだけ。大いに反省するところだ)。

ソースコード中のコメントの適切な量はどの程度なのだろうか? 言い換えれば、コメントは多いほうがよいのだろうか、それとも少ないほうがよいのだろうか?

難しい問いだ。私なら「両方」と答える。(少なくとも手続き型の系譜のプログラミング言語を使用しているのなら)コメントは多めに書くべきだし、一方で減らすべき対象でもある。

ネットでは「コメントは少なくするべき」的な主張が目立っている印象がある。個人的に、この主張には明文化されていない前提条件があるように思う。その前提こみでの「コメントは少なくするべき」という考えには賛同するので、コメントは「減らすべき対象」だ。一方で、前提条件を逸脱した範囲においては「コメントは多めに書くべき」という指針に従ったほうがよいケースある、と考えている。

※以下、関数/メソッド/プロシージャなどをまとめて「サブルーチン」と表記し、クラスやオブジェクトなどのモジュール機構となりうる機能をまとめて「モジュール」と表記する。

コメントが多すぎた例

ある会社の新人教育での話だ。

新人教育期間が終了して、次年度に向けて受講者にアンケートを行ったところ、「プログラミング課題の解答例のソースコードのコメントが少ない」という指摘があった。曰く、コメントが少なすぎて何をやっているか分かりにくい、とか。

解答例を書いたのは、当時2〜4年目の若手たちだった。C言語で書かれていて、ソースコードには定数やスコープの広い変数の役割や各関数の仕様についてのコメントが書かれていた。関数の中には、内部変数の役割や処理のフェーズ(関連する複数行のステートメント)ごとの1行コメントが書かれている程度だった。

アンケートの指摘を受けてコメントが追加された。追加したのは、当時2〜3年目の(解答例の実装者とは別の)若手だった。

コメント追加版のソースコードのチェックを依頼された私が目にしたのは、各ステートメントごとに右横にぎっしりと書かれた大量のコメントだった。典型的な「不要なコメント」だ。構造化アセンブラではないアセンブラならともかく、高水準アセンブラと揶揄されるとはいえ一応高水準言語であるC言語を使っているというのに。

しかし、なぜこのような大量のコメントが追加されたのだろうか? 私が作業をやり直そうとしてコメント追加前のソースコードを見たとき、受講者の「コメントが少なすぎて何をやっているか分かりにくい」という指摘が正しくないことに気づいた。不適切な名前、不要な型や定数、深いネスト、一貫性のない妙なモジュール分割、(新人向けの解答例としては)長すぎる関数、言葉足らずな元々のコメント――受講者たちは汚いソースコード*1を解読する手がかりを求めていた。そして、私の前にコメントを追加した若手は、汚いソースコードを新たなコメントで補完しようとしていた。

しかし、実際の仕事で「汚い、しかしテスト済みで実績のある、単体テストモジュールの類が現存しない既存のソースコード」に遭遇したのならともかく、新人教育の解答例で「汚いソースコード」を前提に物事を進めること自体が誤りだ。受講者に見せるべきなのはきれいなソースコードで、私がするべきなのはコメントの追加ではなくソースコード全体のリファクタリングだった。

私は解答例のリファクタリングを開始して、ある程度行ったところで作業を切り上げた。リファクタリング後のソースコードのコメントの量は、元々のソースコードのそれとほぼ同じか、やや少ない程度になった――リファクタリングされたコードと同様に、コメントの内容も書き換わっていたが。

作業を切り上げた後、私は解答例を一から書き直した。リファクタリングでもそこそこきれいにはなったが、これ以上きれいにするには無理があった。

「コメントが多すぎた例」からの教訓

  • コメントを書く前に、ソースコードをきれいにすること。下手なコードの不備を補うコメントは害にしかならない。
  • きれいなソースコードの上に、コードからは分からない/分かるまで時間や手間がかかる内容についてのコメントを載せること。

汚いソースコードは、それを補うために「不要なコメント」を生み出す。汚いソースコードを排除すれば、その分だけコメントは減る。

「コメントを少なくするべき」という主張は、しばしば「きれいなソースコードを書く」という前提を伴っている。まあしかし、この手の指摘は幾度となくなされてきている。「明文化されていない前提条件」というほどではない。

一方で、減らないコメントもある。今回の例では、元々のソースコードには定数やスコープの広い変数の意味や役割についてコメントが書かれていたし、各関数には仕様を記述したヘッダコメントが書かれていた。これらのコメントは、リファクタリング中に内容こそ書き直したものの、分量としては元のソースコードと同じか少し減った程度ぐらいは残った。

実はここから「明文化されていない前提条件」を読み取ることができる。「コメントを少なくするべき」というとき、その「コメント」は「ステートメントに密着したコメント」であることが多い。

きれいなソースコードを記述すると、「ステートメントに密着したコメント」の量は少なくて済む。ステートメント自体の可読性が高くなれば、ステートメントを解読するための補助的なコメントがなくても容易に解読できるからだ。一方でデータ・データ構造といったステートメントではないものについてのコメントや、サブルーチンのヘッダコメントのようなステートメント寄りだけど幾分と抽象化された内容のコメントは、(何をどう書くべきか、という点で議論はあるものの)きれいなコードを書いても単純には減らないものだ。

コメントが少なくて困る例

これは現在進行形の話だが、他人のソースコードを読むようになってから、幾度となく「ソースコードの中身がなかなか理解できなくて困る」という現象に遭遇している。汚いコードならともかく、比較的きれいなコードでもそういうことがある。

そういったソースコードを観察して気づいたのは、ソースコードの一部分をちょっと読むだけでも、中身を自力でいちいち細部まで解析しなくてはならない部分が非常に多い、ということだ。

例えばスコープの広い変数について、その変数名をオウム返しした程度のコメントしか書かれていないか、又はコメントが書かれていなかったりする。変数名から分かる程度の情報で十分に足りるのならともかく、それで不十分ならばソースコードを駆け回ってどこでどのように使われているか解析しなくてはならなくなる。参考になる変数名なら、まだ解析する時の助けになるだろう。しかし変数名すら参考にならない場合は、一から解析しなくてはならない。

これが1つ2つの変数なら何とかなるが、実際にはちょっとしたモジュールでさえ10個や20個の変数が解読対象となったりする。とてもやっていられない。

同じことはサブルーチンについてもいえる。外部に公開するサブルーチンならともかく、モジュール内部でのみ使用するサブルーチンの場合はヘッダコメントが付けられていないことも多い。その場合、サブルーチンの役割や引数の意味を正確に知るには中身のコードを読む必要があるし、時には実際の使われ方(そのサブルーチンがどのような場所でどのような値を引数に指定して呼び出されているか)まで調べることになる。さらに言えば、例えばそれらの情報からサブルーチンの役割や制約を推測したとして、その推測が本当に正しいのか(実装した人の本来の意図と同じなのか)確証を得るのは難しい。難しいが、確証を持てなければ、とてもではないが怖くてコードに手を入れることができない。意図と実装が合致しているのか食い違っているのか、合致しているものの意図そのものが誤っているのか──この違いによって、手の入れ方が変わってくるのだ。

ヘッダコメントとしてサブルーチンの仕様が書いてあるコードでも油断はできない。というのも、サブルーチン名や仮引数名をオウム返しした程度の内容しか書かれていないことも多いのだ。本当に知りたいのは、例えば「そのサブルーチンをいつ呼び出すべきか」とか「引数として許容する値の範囲」とか「引数の値によって、どのように挙動が変化するか」とか「エラー発生時の挙動」とか「実装上の特異な点」とか、サブルーチン名や仮引数名だけでは伝えることができない、ソースコードを解析しないと分からないような情報なのだ。しかし大抵は、ヘッダコメントに「サブルーチン名」のような役に立たない項目はあっても*2、より重要な情報は書かれていない。

変数と同様に、サブルーチンにおいても自力で解析しなくてはならない代物が大量にでてくる。やはり、とてもではないがやっていられない。

加えて、デバッガやICEを使用して動的に解析できるのならまだ楽だが、私の場合はレビュー対象のソースファイルしか手元になくて自力で静的に解析する以外に手がないケースも多い。脳内デバッガで静的解析するとなると、作業コストはどうしても大きくなってしまう。こんな状態でモジュール全体の動的構造を解析する*3なんて無理がありすぎる。

*4や変数、サブルーチン、仮引数などには適切な名前をつけるべきだし、適切な名前をつけることでソースコードの見通しが良くなるのは確かだ。しかしどれだけ良い名前をつけても、どれだけ自然なインタフェースを採用しても、あとでそのソースコードを読んだ他人がそれらのみから得られる情報は多くない。もう少しだけ詳しい情報を知りたいだけなのに、ソースコードという「最も詳細な仕様」を解析して、解析結果を積み上げて要約しなくてはならなくなる。しかし時間は有限だ。納期という制約に縛られているプログラマとしては、本当に細部の細部まで知る必要性が生じるまでソースコードの解析作業を遅延させられるほうがうれしい。

名前やインタフェースそのものからは分からない、ソースコードそのものよりは抽象度が高い情報をコメントとして記述しておけば、(そのコメントが正しい限り)後でそのソースコードを他人が読む時に、最初から詳細を追わずとも大まかな流れを把握することができる。詳細を解析した結果を要約して大まかな流れを見出すよりも、先に概略を把握した上で細部に迫っていくほうが楽だ*5

でも現実的には、この手のコメントは書かれていないか、一見して書かれているようでも内容をみるとあまり使い物にならない代物であることが多いものだ。

「コメントが少なくて困る例」からの教訓

  • データ構造やスコープの広い変数についてのコメントを充実させるべき。許容する値だとか、値の変化について書いてあるとなお良し。これが書かれていないと、その値の役割やとりうる変化について、ソースコード中を駆け回って動的に解析しなくてはならなくなる。
  • サブルーチンやモジュール等のインタフェース仕様となるコメントを充実させるべき。このコメントがおざなりだと、ソースコードの一部を読もうとしただけでも、例えば読もうとしている範囲にて呼び出しているサブルーチン全ての中身を読み、実装から仕様を解読して理解し、それらを全て記憶しておくことになる――しかし十中八九途中で忘れてしまい、再び中身を読む羽目になる。
    • サブルーチンの場合はその役割、各引数の役割と許容する値、戻り値として返す値とその意味、(例外を返すなら)発生しうる例外、特筆すべき挙動、実装の特異な点などについてコメントを書くこと。他人に公開するAPIで且つ時間的余裕があるのなら、使用例も書いておくとポイントが高い。
    • コメントに書いた仕様を元に単体テストを書き、実行すること。
    • 仕様が変化したなら、コメント文の仕様も修正したうえで、新たな仕様を元に単体テストを書くこと。

前提として、採用するデータ構造やインタフェース仕様について十分に吟味すること。データ・データ構造・サブルーチン/モジュール等のインタフェースは、(少なくともモジュールという単位においては)設計の根幹となる重要な要素だ。これらが間違っていると、どんなにきれいなコードを書いて必要なコメントのみを充実させても、全体として全くもって見当違いな代物になってしまう。

データやデータ構造はモジュール設計における重要な要素だ。外界からどのようなデータがいつインプットされるのか? モジュール内ではどのデータをどのように変換・保持するのか? アウトプットするデータは何で、どのような形式か? これらの要素はどのように変化していくのか? モジュールは「データ群と、それらを操作するアルゴリズムの塊」と見なせるし、設計においてはその視点で考えるほうが上手くいくことが多い。クラスを「モジュール化技法の1つ」とみなせば、クラスにおいても同じ事がいえる。

「データとデータの変換」を核としてモジュールを設計するとき、最初は大雑把な視点でデータの取扱いと変化を考え、次第に処理を細分化していき、最終的には細部まで考えられたソースコードに到達する。この時、概略を考えてコメント化しておけば、実装の際に基本的な流れはコメントに沿って記述すればよいし、実装中に躓いたときに概略の段階に戻って全体の整合性を保ちながら変更することが容易になる*6。また、後でソースコードを読むだろう他人も、全体の概略が記述されていればソース解析が楽になるはずだ。

サブルーチンのヘッダコメントは、(その中身である複数行のステートメントの塊についての)要約であり、仕様であり、制約条件であり、外部との契約条件であるべきだ。もちろん外部に公開するサブルーチンとモジュール内部でのみ使用するサブルーチンとでは、書くべき情報の密度は異なるだろう。使用する言語や静的解析ツールによっては、例えば「外部との契約条件」のようにコメントではなく言語機能で記述した方がよいものもあるだろう。とはいえ、赤の他人がソースコードを読む可能性を考えれば、例えモジュール内部のサブルーチンであっても「ヘッダコメントを書かなくてもよい」という結論に達することはありえない。後でソースコードを読む人が間抜けである可能性は(自分自身が間抜けであるのと同様に)否定できないものだ。

もしサブルーチンのヘッダコメントが不要であると考えているのなら、いささか極端ではあるけど、例えばMSDNライブラリやAppleiOS Developer Library、Linuxのman 2やman 3が存在せず、ヘッダファイルとソースファイルが全公開されている世界を想像してほしい。もちろんヘッダファイルやソースファイルにヘッダコメント的なものが書かれていないものとして。随分と規模は違うし、前提条件も大分異なるものの、「サブルーチンにヘッダコメントが書かれていない」というのは、ある意味それと同じだ。

「不要なコメント」と「足りないコメント」再考

観察してみると、「不要なコメント」とされるものの多くは(ウソ・間違いや全く意味のないコメントを除けば)各ステートメントに密着したものだ。これらは適切な名前やシンプルなアルゴリズム、適度の抽象化を用いることで不要になることが多い。

またステートメントの変更は比較的頻繁に発生する。なのでステートメントに関するコメントもまた、頻繁に変更が発生する。コメントの変更忘れが発生する要因となる。

一方で「足りないコメント」は何か? 観察した範囲では、データやデータ構造に関するコメントや、インタフェース仕様についてのコメントが足りていないか、一見して足りているようでも不要な内容ばかりで必要な内容が欠けていることが多い。これらの不足は、後で他人がソースコードを読む時に余計な解析が発生する原因となることが多い。

データやデータ構造は、ステートメントよりも変化が少ない傾向にある。なのでコメントの変更忘れが発生する可能性は低くなる。データやデータ構造に変化が発生する時は、モジュールの広範囲に変化が波及することが多い。どのみちソースコードを大きく書き換えることになるから、コメントに変更が発生するか否かなどたいした問題ではなくなる。

インタフェース仕様に関するコメントは、ステートメント寄りの部分なのでデータやデータ構造よりも変更が発生する可能性が高い。しかしそれでもコメントを書くべきだ。筋のよいサブルーチンやモジュールを作るには、ある程度仕様を練る必要がある。仕様をコメントとして記述しながら練っていき、最終的な仕様を明確にした上で、仕様に基づいて実装する、という手法は有効だ。または実装から出発して徐々に成長させていき、ある程度形が見えてきた時点でそれを仕様として書き出しながら再検討して、検討結果をもとにソースコードを整理する、という方法もある。どちらの方法も、仕様を練る分だけ筋のよいインタフェースを得られる可能性が高まるはずだ。それに仕様が適切に書かれていれば、後でソースを読む人がいちいち実装の細部に立ち入る回数が減るだろう。コメント文の仕様を読むだけで済むのなら、本当に必要になるまでサブルーチンやモジュールのコードを読まなくても何とかなるはずだ。

まとめ

ステートメントに関するコメントは少なくするべきだ。その前提として、きれいなソースコードを書くこと。

データやデータ構造の解説や何らかのインタフェースの仕様に関するコメントは、(何をどう書くべきか吟味した上で)充実させるべきだ。*7

*1:実際には、汚いソースコードではあったものの、まだまだ決定的に汚い状態には至っていなかった。とはいえ、解答例ごとの差もまちまちだったものの、その中で一番マシなソースコードであっても、いきなり新人に見せるには抵抗を覚える部分が1〜2ヶ所は存在した。その他のソースコードは――「いや、これは新人に見せても解読できないよ」ないし「なんだ、この明らかなバグ入りは!」だった。

*2:基本的に、サブルーチンのヘッダコメントはサブルーチンの宣言や定義の直前に書かれているので、わざわざヘッダコメントにサブルーチン名を書かなくても、宣言や定義そのものを見れば名前は分かる。なのになぜ、ヘッダコメントにサブルーチン名が書かれていることが多いのだろう?

*3:データやデータ構造は大抵はモジュール全体にまたがるものなので、例えばモジュール全体実行時にどのように変化していくかを明らかにしないと、モジュール変数の値の変化を明白にすることはできない。

*4:構造体のようなユーザ定義の複合データ型やクラス、C言語のtypedefなど。

*5:この主張には一応傍証的なものがある。『UNIXカーネルの設計』という本があり、その本ではカーネルが提供する機能の大まかなアルゴリズム擬似コードを使って解説しているのだが、序文にこんな記述がある。「本書の内容と構成は1983年から1984年にかけてAT&Tベル研究所での教育コース用に準備した資料から生まれてきた。コースはソースプログラムを読むことを目的としていたのだが、ここで、ひとたびシステムの概念とアルゴリズムが理解されているとプログラムの理解は簡単になるという事実を痛感した。UNIXの簡潔さと美しさを少しでも反映するように、アルゴリズムの記述はできるだけ簡潔にした。したがって、本書はシステムの内容の各行を解説したものではなく、それぞれのアルゴリズムの全体的な流れを説明したものである」。

*6:下手にコードの一部のみ変更すると、全体の整合性がとれなくなる可能性がある(もちろん、そのモジュールなりアプリなりのつくりに依存する話だけど)。なのでどこかで一旦全体を見直す必要がある。全体を見直すとき、大まかな概略が書かれているほうが作業は楽になる。

*7:補足:具体的なコメントの量は、使用する言語やライブラリにも依存するだろう。例えばC言語を使うにしても、手順/手続きとしてゴリゴリとコードを書くよりも、構造体の静的配列によるテーブルで宣言的に記述したほうが分かりやすい場合がある。ならば、解くべき問題によってはC言語よりも抽象度の高い言語で宣言的に記述した方が読みやすいコードになり、その影響でコメントも少なくて済むだろう。同様に、例えばC++で自前でリスト構造を弄りながら問題を解くためのコードを並行して記述するよりも、リストの部分をstd::listに置き換えてしまったほうがスッキリするだろうし、異なる抽象度のコードが混ざってごちゃごちゃしているコードでは必要になるだろう注釈が不要になるはずだ。