コメント入門
今回はコメントについて学びましょう
コメントは実行時の動作に影響を与えないので疎かにされがちですが、コメントは別に無駄に付いている機能ではありません
コメントはプログラムのコードだけでは表せないプログラマの意図を表現するために存在するもので、実はとても重要なものなのです
さて、ドキュメント/リファレンス/マニュアル、呼び方は何でもいいんですが、まず覚えてほしいのは「コメントはドキュメントではない」ということです
ここがとても重要な点です
コメントにはコメントの、ドキュメントにはドキュメントの役割というものがあって両者はまったく別のものです
コメントには何でも書けるのでドキュメントに書くべき内容をそのままコメントとしてソースコード中に書いてしまう人も多いかと思いますが、ドキュメントに書くべきことはソースコードとは別にドキュメントとして書くべきです
ソースコード中のコメントにはドキュメントとは別の役割があります
ではその役割とは何かというと、それは実装の詳細を明確にするということです
一方ドキュメントの役割はというと、それは仕様を明確にすることです
コメントの役割について説明する前にまずはドキュメントの役割を確認しましょう
仕様を明確にするとは、関数や変数や構造体など、それは一体何のためにあるのかということを明らかにすることです
またそれらを使うにはどうすればいいのか、何か気をつけるところはあるのかといったことを明らかにすること。これもドキュメントの役割です
入力パラメータに何を渡したら戻り値や出力パラメータに何が返ってくるか。その関数を呼び出す前にやっておくべきことはあるか。呼び出してはいけない状況はあるか。渡してはいけない値はあるか
細かく言うなら例えば、ポインタに NULL を渡していいのか悪いのか。サイズを指定するところに 0 や負数を渡していいのか悪いのか。などといったことです
こういった情報はドキュメントに書くべき情報なのでコメントに書く必要はありません
具体的にはこういったコメントです
Fig.1
struct foo_t { u32 dwSize; // この構造体のサイズ == sizeof( foo_t ) char name[FOO_NAME_MAX]; // これは名前 . . . };
Fig.2
/* ** load_module ** モジュールをロードします ** パラメータ ** module_name ** モジュールのファイル名を指定します ** NULL や空文字列は指定できません ** 戻り値 ** モジュールハンドルを返します ** 必要なくなったハンドルは free_module で解放してください ** ロードに失敗したときは NULL を返します ** 注意 ** ロード済みのモジュールをロードしようとしたときにもハンドルは正しく取得できます ** この場合実際にはモジュールをファイルから再度ロードし直すようなことはしません ** モジュールの指すインスタンスのリファレンスカウンタが増加するだけです ** しかし使う側はモジュールがロード済みかどうか気にする必要はありません */ module_handle_t load_module( const char* module_name ){ . . . }
こういったコメントはソースコード中に在る意味がありません。ドキュメントに分離することを検討しましょう
「ソースとドキュメントを両方メンテナンスすると食い違いが生じやすい」とか「 Doxygen でドキュメントを生成するから別にはできない」とかいう人は無理に分離しなくてもいいですけれど
ではドキュメントの役割を確認したところで次にコメントの役割を説明しましょう
コメントの役割は実装の詳細を明確にすることだと言いました
ここでいう実装の詳細とはそのコードが何をやっているかというようなコードの細かい内容のことではなくて、プログラマが何を意図してそのコードをそのように書いたのかという詳細のことです
もちろん「何をやっているか」を掴みづらいような処理では「何をやっているか」ということをコメントに書くこともとても重要なことです
しかし「何をやっているか」ということはコードに書いてありますからどんなに複雑でも読めば書いてあります
「なぜそうしているか」ということはコードには書いてありませんから、それをコードから読み解くということはそのコードが何をしているかということを把握するよりもずっと大変なことです
例えば下記のように case の最後で break しない場合を考えてみましょう
Fig.3
switch( foo ){ . . . case FOO: . . . // break しない case BAR: . . . break; . . . }
case の最後で break せずに次の case に処理を流すような場合にはそこで break しないことをコメントすることはとても重要なことです
このようなコメントがあればこのコードを全く知らないプログラマやあるいは未来の自分がこのコードを読むときにもここに break が無いのはミスではなく意図であることは伝わります
しかしこれで十分かというとそうではありません
この「break しない」というコメントは「何をしているか」を書いているだけです
確かにこのコメントでここに break が無いのはミスでなく意図であることは伝わるでしょう
しかしでは「その意図とは何か」ということは伝わったでしょうか
ここに書くべきコメントは正にその「意図」です
どういう理由でここに break を書かずに次の case に処理を流しているか、その理由をこそコメントに書くべきなのです
また別の例を挙げれば、下記のようなコードがあるときにコメントに「l1 が l2 より小さいとき」なんて書くのは無意味です
「何をしているか」を書いているだけのコメントで、そんなことはコードを見ればわかります
Fig.4
// l1 が l2 より小さいときは何もしない if( l1 >= l2 ){ . . . }
ではどんなコメントを書けばいいでしょうか
今この l1, l2 が文字列 s1, s2 の長さだとするならコメントも例えば「文字列s1 が文字列s2 よりも短いとき」などとした方がより良いでしょう
またコメントだけでなく変数名も l1, l2 などよりは例えば cch_s1, cch_s2 などの方がより良いでしょう
Fig.5
size_t cch_s1 = strlen( s1 ); size_t cch_s2 = strlen( s2 ); // 文字列s1 が文字列s2 よりも短いときは何もしない if( cch_s1 >= cch_s2 ){ . . . }
しかしこれでもまだそんなことはコードを見れば分かるというようなコメントなので十分ではありません
そもそも s1, s2 が何を意味しているのでしょうか
ここで s1, s2 が被検索文字列と検索文字列であるとしてみると、それならコメントも「被検索文字列s1 が検索文字列s2 よりも短いとき」などにした方がずっと分かりやすいでしょう
Fig.6
const char* strstr( const char* s1, const char* s2 ){ const char* s = NULL; size_t cch_s1 = strlen( s1 ); size_t cch_s2 = strlen( s2 ); // 被検索文字列s1 が検索文字列s2 よりも短いときは何もしない if( cch_s1 >= cch_s2 ){ . . . } return s; }
さあこれで十分かというと実はこれでもまだ十分ではありません
考えてみてください
なぜここで検索文字列が被検索文字列より長いかどうかを調べているのでしょうか
この場合それは被検索文字列よりも長い文字列が被検索文字列の中にあるわけがないからですね
それならコメントもそのまま書けばいいのです
例えば「検索文字列が被検索文字列より長いときは見つかるわけがない」などというコメントの方がいいのではないでしょうか
重要なのは「l1 が l2 より小さいとき」などというコードそのままのことを書くのではなくて「そのコードが何を意味しているのか」を言葉にするということです
つまり「なぜここで l1 が l2 より小さいかどうかを判定しているか」ということを言葉で書いたものがコメントだということです
「何をやっているか」はコードに書いてありますが、「なぜそれをしているか」はコードに書いてありませんから、それを残すのがコメントの役割なのです