「コメントの書き方」にもある種の理論・思想があり、理論・思想にもとづく「型」があると思っているのだが、「『コメントの書き方』についてそこまで突っ込んだ理解をしている人は驚くほど少ない」と考えておいたほうが無難だろう。
Code Kataは既出だが、Comment Kataは無い。しかし「型」はある。見落とされているだけだ。
――と、下記エントリとその周辺を見ていて思った。
- 【プログラミング】退職した先輩が書き残していったコメントがひどすぎる・・・ - 私の戦闘力は53万マイクロです
- http://nzmoyasystem.hatenablog.com/entry/no_comment_culture
それはおそらく、コードは動作するが、コメントは動作しないからだろう。コードは主役であり、コメントはパセリだ。ともなれば、我々はコードの品質向上については気にかけるが(なんたって主役なんだから!)、コメントについてはなおざりになりがちであるし、時にはおざなりにしてしまうこともある。
なおざりになりがちだからこそ、新人は先輩/教育担当から「コメントを書け!」としか言われない。で、書き方に悩んだ挙句、ソースコードの右端にアセンブラのごとくステートメントごとに細かくコメントを書いて提出すると、そこで初めて「ここは○○にして、そこは××で……」と個々の案件について具体的な注文が返ってくる。だが往々にして「なぜ○○にするべきか?」という理由は明かされないし、その背後にあるだろう理論・思想はもっと見えてこない。なぜならば、「コメントについて明確な理論・思想をもち、それらにもとづく「型」を明示できる」という水準に達している人が非常に少ないからだ。明確な体系を持たないから、経験と勘にもとづいて個別に指摘するしかできないのだ。明確な体系があるならば、それを提示すれば済むし、体系にもとづく「型」があるのならば、まずは「型」に沿ったテンプレートを使わせるところから訓練を始めてもよい話なのだ。非効率な話だと、常々思う。
(「『リーダブルコード』でも読め」と手渡されるほうが、よっぽど親切だ)
また例えば、手元の和書にて、コメントの書き方について「型」といえるほど偏執的に書かれているのは『CODE COMPLETE 第2版 下 完全なプログラミングを目指して』の第32章「読めばわかるコード」だけだ。1章まるごと、50ページほどコメントの書き方について具体例付きで論じている。
他に『プログラミング作法』『Code Craft ~エクセレントなコードを書くための実践的技法~』『リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)』を持っているし、『プログラム書法 第2版』も読んだことがある。どれも良い本ではあるが、こと「コメントの書き方」に限定するなら、どれも『CODE COMPLETE 第2版 下』には一歩及ばない。『リーダブルコード』や『Code Craft』はコメントの良し悪しに関する理論・思想についてしっかり記述されていて、結構いい線いっているのだが(さらに言えば『リーダブルコード』は安価なので薦めやすいのだが)、しかしまだ少し簡潔すぎる。「型」といえるほど具体的な記述は少ない。
和書を見渡しても、これらの本ぐらいだろうか? 「コメントの書き方」に限っていえば、体系的かつ具体的な記述がなされた文書は少ない。
情報が少なく、また優先順位が一段低いとなれば、良いコメントの書き方の技術に詳しい人は少なくなる。ほぼ確実に、「良いコードの書き方の技術に詳しい人」よりも少ないだろう。
そもそも「コメントが無くても読めるようなプログラムを書け」という格言の意味を、どれだけの人が正しく理解できているだろうか?
「コメントが無くても読めるようなプログラム」は「コメントの無いソースファイル」を意味しない。「(ソースファイルにコメントを書かずに済むように、ありとあらゆる合法的かつ妥当な手法を駆使した結果)(そのコードを共有する文化圏の基準で)最小限のコメントのみが、メンテナンスしやすいスタイルで記述されているソースファイル」のことだ。
コメントの量を減らすには、まず「コメントすべきでないもの」を取り除き、次に「コメントの代替となる何か」に置き換えることになる。この結果、ソースファイルには「コメントすべきもので、かつ代替となるものが存在しないもの」のみがコメントとして残ることとなる。
では「コメントすべきでないもの」から見ていこう。
コメント以前に、ソースファイルはas is、つまり「現在の姿」を映し出すものでなくてはならない。ソースファイルの構成要素はソースコードとコメントだ。コメントも「現在の姿」を映し出すものでなくてはならない。
つまり、ソースファイルの内容とは無関係のコメントは書いてはならない。先に挙げた記事でいうなら、「その場しのぎの処理」は(文章表現は別として)セーフだろうが*1「仕方がない」はアウトだ。それはアンタの感想にすぎないだろう。
また、変更履歴のような「過去の姿」に関するコメント、特に「// ○○対応のため追加(2016-04-25:eel3)
」のようなコメントも書いてはならない。この手のコメントは、驚くほど早く劣化する。下手すると次のコミットにて邪魔な過去の遺物と化してしまうだろう。そういう情報はソースファイル中に書くのではなく、バージョン管理システムのコミット・ログや、プロジェクト管理ツールのチケットへのコメントなど、履歴の関する情報を管理するための適切なツールを用いて記録するべきだ。個人的には、分散型バージョン管理システム(というかGit)の普及で、ようやく撲滅できる環境が整ってきたと考えている*2。
「コメントすべきでないもの」は、実はこれだけだ。世の中で「コメントすべきでない」とされているもの*3の大半は、実際には「『コメントの代替となる何か』に置き換える」ことが可能なコメントに該当する。
鍵となるのはDRY(Don't Repeat Yourself)だ。コメントを書くことで、ソースファイル中の特定のソースコードとの間に「記述の二重化」が発生するのなら、そのコメントを削除できないか、一度は検討するべきだろう。
なぜならば、「記述の二重化」が発生することによって、ソースコードを変更する際に、対となっているコメントも変更して同期させる必要性が生じるからだ。2ヶ所メンテするよりも1ヶ所の方が楽だし、もう片一方を変更し忘れることもない。
この時、コメントの代替――すなわち対となるソースコードの表現、特にステートメントの表現を見直すことは、鉄板ネタだ。世の中の大概の「コメントを減らすべき」という主張は、ステートメントの表現の見直しを指している。
というのも、ソースコードの構成要素をあえて「式とステートメント」と「データ構造」に分けるとすると、「式とステートメント」についての変更は頻繁に発生する。なので、もしステートメントAに密着したコメントaが存在するなら、Aを変更する際にaも変更しなくてはならない。そんな作業を何度も繰り返すのは無駄以外の何者でもないのだ。
かくして、ありとあらゆる手段でステートメントの可読性を向上させることで、ステートメントに密着したコメントを取り除こうということになる。適切な名前・適切な順序・シンプルなアルゴリズムの採用……典型的な「良いコードの書き方の技術」だ。
コメントの書き方でよく言われる「HowよりWhat、WhatよりWhy」的なアドバイス*4でいえば、ステートメントに密着したコメントは「How」に該当する。ステートメントの可読性を向上させることで解読補助用のコメントが減るため、「How」のコメントも減る。
ステートメントについての「What」は、基本的に要約コメントだ。関連するステートメントの塊について、何をしているのか、ドメインに着目して書く。要約コメントの典型例が「ファイルのヘッダ・コメント」と「関数/メソッドのヘッダ・コメント」で、要約コメントの中では最も多く書かれている。関数/メソッドが長くなった場合、中身のフェースごとに1〜2行ずつ要約コメントを足すことがあるが、稀な部類に入る。何を行っているかぱっと見では分かりにくいステートメントの塊について、ステートメントの可読性向上だけでは対応できない場合に、要約コメントを足すこともある。
関数/メソッドのヘッダ・コメントには濃淡がある。古きよきJavaのgetterやsetterのようなルーチンは、名前から役割が明白であるし、中身もシンプル極まりないので、ヘッダ・コメントは書かれない。モジュール/クラス内部でのみ用いられるルーチンでは、概要を述べた1行コメントで済ますことも多い。外部に公開されるルーチンには、もう少し詳しい情報を提示するかもしれない。不特定多数の人が利用するライブラリともなると、さらに厳しくなる。
特に外部公開インタフェースに関しては、「コメントではなくテストコードやサンプルコードで」という主張もあるが、それでは不十分であるケースもある点は押さえておくべきだろう。例えばシステムコール由来のエラーが発生した際のルーチンの振る舞いについて、テストコードやサンプルコードにて、どのように読者に伝えればよいのだろうか?
思い起こしてほしい。我々はUnixシステムコールを叩くとき、サンプルコードだけ読むだろうか? 否。manとサンプルコードの双方を読むはずだ。Unixのmanは、不特定多数が利用するライブラリのドキュメントの典型例だといえる(その良し悪しは別として)。
外部公開インタフェースの解説について、テストコードやサンプルコードで済ますことが可能なのは、そのコードの作者と利用者の双方に「暗黙の共通認識」が存在するケースであることが多い。
これは外部公開インタフェースに限らず、例えばモジュール/クラス内部でのみ用いられるルーチンのヘッダ・コメントでも同様だ。そのソースファイルの読者が限られていて、共に共通するバックグラウンドを持っている場合、内部ルーチンのヘッダ・コメントは1行コメントであることが多いし、時にはヘッダ・コメントが書かれないこともある。一方、不特定の人に読まれ、メンテナンスされていくことを前提としたソースファイルの場合、内部ルーチンであってもそこそこ詳細なヘッダ・コメントが書かれることがある。
ステートメントについての「Why」は、意外な要素や注意すべき処理などの「意図(Why)」を説明するコメントだ。基本的には、シンプルなアルゴリズムを採用し、トリッキーなコードは避けるべきであるが、そこそこの大きさのアプリケーションを書いていると、どうしても他の読者の不意を突いてしまう部分が1〜2ヶ所は出てきてしまうものだ。チーム・レビューで他のメンバーからより良い代替案が出ることも多いのだが、どうにもならない場合には、注意を促すコメントを残すことになる。
ステートメントについてのコメントの重要度は「How < What < Why」だ。実際のコメントの分量は、多くの比較的マシな環境では「How < Why < What」だろう。ソースファイルの作者と読者に共通するバックグラウンドがあり、暗黙知によるコメントの省略が可能な環境では、コメントの分量は「How < What < Why」に近づいていくだろう。
では「データ構造」についてはどうだろうか? データ構造は、一度適切な構造が採用されてしまえば、あまり変更は発生しない。データ構造に変更が発生する際は、関連するソースコードを巻き込んで大々的に実装し直す必要があることが多い。例え適切なインタフェースで抽象化することでモジュール/クラス外部への変更の波及を抑えられたとしても、モジュール/クラス内部はアレコレ手を入れなくてはならないだろう。つまり、データ構造に変更が発生した時点で、コメントによる「記述の二重化」とか悠長なことを言っている暇など無くなってしまう。どうせ大掛かりに変更することになるのだから、実はあまり二重化の弊害について気にする必要はない。
むしろ現実には、データやデータ構造についてのコメントは足りないことが多い。
『プログラム書法』に曰く、「データの割り付けかたについての解説をつけよう」である。
プログラムに解説をつけるためにも、もっとも効果的な方法の一つは、単にデータの割り付けかたをくわしく説明する、というものである。おもな変数について、その値としてはどんなものが可能かを示し、それが変って行くようすを説明すれば、それだけでプログラムの解説は、ずいぶん進んだといってよい。
この指針に従うと、例えばクラスや構造体によって抽象化されたデータ構造の中身や、インスタンス変数のような生存期間の長いデータについて、HowやWhat(ときにはWhy)レベルのコメントが足されることになる。
ステートメントとは異なり、データ構造(または生存期間の長いデータ)については、HowやWhatのコメントを書いても許される側面がある。というのも、データやデータ構造の変化の様子は、システムやモジュール/クラスのライフサイクルにもとづく「計算の動的構造」をつまびらかにしなくては分からない要素だ。調べるためには、データやデータ構造を参照・変更する全てのステートメントを時系列順に確認する必要がある。はっきりいって、非常に面倒だ。しかしコメントで記述されていれば(そしてそれが正しいなら)、ステートメントを確認する必要はなくなる。
つまり、データやデータ構造については、例えHowレベルの内容であっても、実質的にはWhatレベルの要約コメントとして働くのである。
データやデータ構造についてのコメントの量は、モジュール/クラスの粒度や切り分け方、または使用する言語によって変化しやすい。
例えばモジュール/クラスの粒度が小さく、単一のコンテクストのみ扱うシンプルなものであるなら、その内部のデータ・データ構造についてのコメントは少なくなるだろう。しかし粒度が大きく、複数のコンテクストを抱えたモジュール/クラスでは、データ・データ構造についてのコメントは多くなるだろう。
Fileクラス、CSVパーサ・クラス、vectorのような動的配列クラス、これら3つを用いたデータファイル読み込みクラス――という風に4つに分かれているなら、それぞれの中身は比較的シンプルだろう。しかしこの4つの処理を全て自前で行おうとする単一のクラスは複雑だ。内部のデータ・データ構造のコメントも多くなるだろう。つまり、そういうことだ。
ステートメントもデータ・データ構造も、HowやWhatのコメントについては、コメント以外の他のメディアで代替される余地がある。例えば、独自の通信プロトコルについて、仕様書が整備されているなら、わざわざ詳細をコメントに書く必要はないだろう。参照すべきドキュメントについてコメントするだけで十分だ。もちろん、仕様書が無いのならコメントでプロトコル形式(もしくはデータの例)を書いておくべきだろう。
ここまでの議論を元に、ソースファイルのコメントを削ったとしよう。後には次のようなコメントが残ることになる。
- 「ファイルのヘッダ・コメント(What)」
- 「関数/メソッドのヘッダ・コメント(What)」
- ステートメントに関するコメント(What/Why)」
- データ・データ構造に関するコメント(What/Why)」
さて、ここからさらにコメントを削ることは可能だろうか?
ここからは、使用する言語の抽象度・そのソースファイルの性質・作成者と読者の間の「暗黙知」の問題となってくる。
例えば、解くべき問題に合致している抽象度の言語を用いている場合、コメントを削りやすい傾向にある。タブ区切りファイルの第2フィールドの数値を加算した結果を求めるAWKスクリプトなら、ファイルのヘッダ・コメント1行と、処理するレコードのフォーマット(もしくはレコードの例)についてのコメント1行で十分だろう。しかし、C言語で自前で解析するというのなら、いくつかの関数を定義・実装することになり、各関数のヘッダ・コメントが1行ずつ必要となるだろう。
ソースファイルの性質の影響は、例えば「自社開発アプリケーションのコンポーネントの一部で、概ね固定されたメンバーで管理しているソースファイル」なのか、「内製ライブラリで、ライブラリ開発部隊がメンテしていて、社内の複数のプロジェクトにて組み込まれるソースファイル」なのかによって、公開インタフェース(CやC++でいうヘッダファイル)のコメントの書き方は異なるし、内部ルーチンのコメントのスタイルも違ってくるだろう。メンテナンスする人が限られていて、メンバーも固定されがちで、外部への露出が少ないならば、コメントは簡素となる。一方で、不特定多数の人に公開されるライブラリのソースファイルでは、問い合わせを減らすためにドキュメントを充実させる傾向にあるので、コメントをドキュメント代わりにしている環境では、コメントも充実した内容(≒API仕様文書)となる。
ソースファイルの性質の件は、ソースファイル作成者と読者の間の「暗黙知」の問題と絡んでくる。メンバーがある程度固定された環境では、明示的・暗黙的にかかわらず、例えば「○○の場合は××のスタイルで書け!」のようなお作法があったりする。この環境に適応すると、ソースコード中の「××のスタイル」を目にした時に「ああ、ここは○○なのだな」と逆引きが容易になる。
名前の付け方やインタフェース定義の流儀など、明快かつ合理的な作法(型)が存在し、皆が作法に従っているなら、コメントするまでもなく暗黙のうちに理解できてしまう部分が生じるだろう。*5
「暗黙知」が許されるか否かや、どこまで許されるかという問題は、当然ながら環境に依存する。メンバーが固定されていて、レベル差がそれほど大きくなく、書かれたコードがメンバー同士での利用に留まるなら、暗黙知にもとづいてコメントを削ってもあまり問題にはならないだろう。むしろメンバー的には冗長性が排されるので歓迎されるかもしれない。なぜなら、彼らからすれば「当たり前の常識」なのだから、わざわざ書くほどのことではないからだ。しかし、不特定多数の人が読むなど、どうしても読者のレベル差が広くなり、それに対処する必要があるのなら、逆に「暗黙知」をコメントで提示することになるかもしれない。
コメントの少ないソースファイルについて、個人的に良い例だと思っているのが、以下の『プログラミング作法』のサンプルコードだ。
- http://www.cs.princeton.edu/~bwk/tpop.webpage/csv.h
- http://www.cs.princeton.edu/~bwk/tpop.webpage/csvgetline2.c
- http://www.cs.princeton.edu/~bwk/tpop.webpage/csvgetlinec++.c
- http://www.cs.princeton.edu/~bwk/tpop.webpage/eprintf.h
- http://www.cs.princeton.edu/~bwk/tpop.webpage/eprintf.c
PythonやRubyなどからすれば低水準なCやC++を使っている、という側面はあるが……お仕事で書くコードで、メンバーが少数かつ固定された面子で、外部への公開をあまり考えなくてよいのなら、ここまでコメントを削っても許されるだろうと思う。
一方、私は普段、不特定の人が使用するライブラリの開発に携わることが多い。なので、コメントは冗長気味に書く癖がついている。以下のコードが、その雰囲気を表している(英語は間違っているので注意)。
- https://github.com/eel3/hexdecode/blob/master/hexdecode.c
- https://github.com/eel3/hcasl/blob/master/c/hcasl.c
- https://github.com/eel3/hcasl/blob/master/cpp/hcasl.cpp
(大御所のコードと比較すると、自分のコードの下手さ加減がよく分かる)
もちろん、自分用の小ツールなら、コメントは少ない(以下、Go言語)。
時には細かいコメントがほとんどないソースファイル(以下、Perl)もあるが……実は、暗黙のうちに「SNTPのパケットフォーマットの知識」を読者に要求している。
コメントを減らすことは、ある水準までは技術の問題だ。しかし、ある時点から次第に技術ではなく環境・文化の問題となっていく。
(本当に開発者のためになる)仕様書が存在し、しっかりメンテナンスされている環境ならば、開発対象についての高水準の記述について、コメントで書かれることはない。仕様書を参照すれば済むからだ。しかし、そうではない環境では、仕様書の代わりにコメントで記述されるかもしれない。
不特定多数の人が利用するライブラリを開発していて、API仕様書を書かないのならば、API仕様をコメントで記述することになるだろう。もしかしたら、JavaDocやDoxygenを利用して、コメントからAPI仕様書を生成しているかもしれない。一方で、少人数で開発中のコンポーネントの一部で、作者も使用者もチーム・メンバーで、特に外部に公開することもないのなら、インタフェース部分のコメントは簡素となるだろう。それで困る人はいない。
さて、あなたはどの環境の人ですか? 相手はどの環境ですか? で、あなたの批評は、相手の環境に合致してますか?
*1:システムの肝の部分のコードを覗いた人への警告にはなるので。とはいえ、おそらく私でも「もうちょっとなんとかならんかったものか……」とため息をつくと思う。
*2:バージョン管理システムが無い場合、ChangeLogのような「別のファイル」とdiff(≒ソースの差分を参照できる何か。patchでも、ソース一式のアーカイブでもよい)で履歴を管理すべきだが、手動での管理が面倒なため、差分の粒度が大きくなってしまいがちだ。CSVやSubversionのような集中型バージョン管理システムの場合、運用など諸々の都合でコミットの粒度が大きくなってしまうことも多い。Gitのように比較的気兼ねなくコミットできる環境が整ったことで、細かな変更単位でコミットしてログを残すことが可能となってきたように思う。
*3:私自身の過去の記事での記述も含む。
*4:これ、『リーダブルコード』では若干嫌われている気がしないでもない(P.68)のだが、使ってしまうことにする。