コードは機械のために、コメントは人間のために?
プログラミング言語を学ぶとき、コメントは最初に習う項目のひとつです。そして、プログラムであればコメントを含んでいることが普通です。ある研究によれば、ソースコードの平均19%がコメントだそうです。
コードを書くとき、私たちは機械とコミュニケーションを取ることを意識しています。機械はコードを認識してコンパイルしたり実行してくれます。解釈できなければ教えてくれます。プログラマは、コンパイラのためにデータ型を明示するコードを書いたりもします。
一方、コメントは人間とコミュニケーションするために書かれます。近接するコードについての補足情報を他のプログラマに伝えることができます。コメントは機械に解釈されません。*1 コメントが正しいかどうかは人が読んでみるよりありません。
コードとコメントは以上のような違いがあるため、コードは機械のために書き、コメントは人間のために書くと考える人もいるかもしれません。しかし、人間はコメントだけでなく、コードも読むことができます。コメントとコード、つまりソースコード全体でコミュニケーションを取っているのです。
グライスの公理
ソースコード全体で円滑なコミュニケーションを成立させるためにはどのような点に注意したらいいでしょうか? グライスの公理(Grice’s Maxims)は、円滑なコミュニケーションが満たすべき4つの条件を説明しています。
- 量の公理(Maxim of Quantity): 情報を過不足なく提供する。
- 質の公理(Maxim of Quality): 根拠のないことを述べない。嘘をつかない。
- 関係性の公理(Maxim of Relation): 関係のないことは述べない。
- 様式の公理(Maxim of Manner): 簡潔に理路整然と述べる。
この公理は、人が日本語や英語などの自然言語で会話するとき、会話が効率よく行われるための観点を述べたものですが、プログラマはコードやコメントから意図を伝えたり汲み取ったりコミュニケーションしているわけですから、ソースコードにおいてもこの公理が当てはまります。
アンチパターンコメントの特徴
ソースコードの可読性を低めたり、誤解を与えるようなコメントを見かけます。そのようなコメントは、グライスの公理に反しているものが多く、ソースコード上の円滑なコミュニケーションを阻害します。
- 量の公理に違反: コメントを書きすぎている/コメントが説明不足
- 質の公理に違反: コメントが間違っている
- 関係性の公理に違反: コードと関係のないコメント
- 様式の公理に違反: ストレートではなく、まわりくどい、曖昧なコメント
ここでは、上記それぞれに該当するコメントのアンチパターンとその回避策や工夫を見ていきます。
コメントを書きすぎている/コメントが説明不足
ステップの説明書き
私はまだプログラミングに慣れていないとき、各ステップごとにそのステップがどのような処理なのかコメントを書いていました。「○○する」「○○のとき」「○○でないとき」のような各処理をもう一度説明するようなコメントを書いていたわけです。例えば、下記のようなコードです。
php
<?php
// ファイル一覧を返す関数
function get_files($dirname)
{
// ディレクトリを開く
$dir = new DirectoryIterator($dirname);
// 戻り値用配列を宣言する
$filenames = array();
foreach ($dir as $file) {
// ドットファイルのとき
if ($file->isDot() === true) {
// 次へ
continue;
}
// もしディレクトリなら
if ($file->isDir() === true) {
// 次へ
continue;
}
// 戻り値用配列に追加する
$filenames[] = strval($file);
}
return $files;
}
グライスの公理に当てはめて考えると、このコメントは「量の公理」に違反していることになります。プログラミングの教科書で行ごとにコメントを書いているのを真似たのですが、教科書は入門者に解説するためにわざとコメントを入れています。練習でこのようなコメントを残すことは備忘としてはいいかもしれませんが、ソースコードを見れば明白ですので、書かなくてもいい情報です。
もし、ステップを説明するコメントが無ければ、難読なコードになってしまうとしたら、変数名や関数の切り出し・クラスの分割などを検討してみるといいでしょう。
ブロックの終了
if
や for
などのブロックの終わりにコメントを残すパターンをたまに見かけます。
javascript
var janken = function(you, opponent)
{
if (you === 'rock')
{
if (opponent === 'scissors')
{
return true;
} // end if opponent === scissors
else
{
return false;
} // end if opponent !== scissors
} // end you === rock
else if (you === 'paper')
{
if (opponent === 'rock')
{
return true;
} // end if opponent === rock
else
{
return false;
} // end if opponent !== rock
} // end you === paper
else if (you === 'scissors')
{
if (opponent === 'paper')
{
return true;
} // end if opponent === paper
else
{
return false;
} // end if opponent !== paper
} // end you === scissors
else
{
throw new Error("your hand is unexpected: " + you);
} // end else
};
ブロックの終了は、コードですでに表現されていますから、コメントであらためて表現するのは冗長です。必要以上の情報を提供しているので、量の公理に反しています。
ブロック終了コメントは、特に、数十から数百行に及ぶような長いブロックやネストが3段以上になるようなコードで見られることがあります。長い分岐や深いネストは、コードを分割することで読みやすくなりコメントが不要になります。
変数名を説明する
コード側で工夫できるのは、変数名でも同じです。
$p['ctg'] = $ctg; // 製品のカテゴリID
このような、変数名を説明するためにコメントを書くよりも、下記のように変数名を明瞭なものに変えるだけで、コメントが不要になります。
$product['category_id'] = $categoryId;
マジックナンバー
コード上で意味を持った数字のことをマジックナンバーと言います。マジックナンバーは数字を直接書いてしまうと、どのような意味かわかりにくくなります。
php
$freeTrialExpire = 60 * 60 * 24 * 7 * 2;
$cacheExpire = 21600;
気の利くプログラマであれば、下記の例のように、コメントで意味を説明するかも知れません。
php
$freeTrialExpire = 60 * 60 * 24 * 7 * 2; // 試用期間は2週間
$cacheExpire = 21600; // 6時間
マジックナンバーは定数や変数として定義すると、コメントがいならないほどわかりやすくなります。
php
const WEEK = 604800;
const HOUR = 3600;
$freeTrialExpire = 2 * WEEK;
$cacheExpire = 6 * HOUR;
読み手にとって意外なコード
コメントが必要ないくらい明瞭なコードを書くことは何よりも大切です。しかし、時には問題を回避するために通常とは異なった迂回するような処理や、読み手にとって意外なコードを書かなければならない場合があります。このときは、その部分について説明するコメントを書く必要があります。
コードを書いた本人でさえ、どうしてそのような処理を書いたのか忘れてしまうことがあります。「なぜそのような処理にしたのか」といった理由をコメントで残しておくことは非常に重要です。
コメントが間違っている
コメントの劣化
コメントはコードと違い、機械がチェックしてくれません。コードは保守されても、コメントは保守されないかも知れません。そうなってくると、実装とコメントが乖離していき、コメントが劣化していきます。劣化したコメントは、読み手に間違った情報を伝えたり、混乱の原因になります。
コメントもコード同様に保守されることが望ましいですが、コメントの数が多くなると保守が難しくなります。そういった状況になる前に、コメントは必要最小限に留めておくことも有効な方法です。
クラスや関数の使い方
クラスや関数の定義部分にコメントでその使い方を解説することがあります。そのコメントを解釈してドキュメントを生成してくれるものドキュメント生成ツールがあるので、私もサンプルコードの提示をコメントで残していました。
この方法に問題があるとしたら、サンプルコードが実装と乖離するかもしれないという点です。誰が書いたとも知れない、書かれてから何年も経っているかもしれないコメントの中のサンプルコードが本当に動くか保証できるでしょうか? 幸いにもクラスや関数の使い方を示すのにぴったりな場所があります。テストコードです。
テストコードにサンプルコードとして含めておき、継続的インテグレーションなどで頻繁にそのサンプルコードを実行しておけば、実装と乖離する心配がなくなります。つまり、常に正しい情報を伝えることができるわけです。
コードと関係のないコメント
改修履歴コメント
改修履歴コメントとは、変更履歴をコメントで残すパターンです。下記のPHPコードはその例で、開催イベントの一覧を取得する処理です。参加者人数を表示したり、開催日ではなく開催期間を表示する改修があり、そのたびに履歴としてコメントが残される結果となりました。例を見るとわかりますが、改修コメントは蓄積するとコードの見通しを損ねます。
php
<?php
function getEventList($eventType)
{
// add start 2011-12-11 for #201
// 2012/05/11 Izawa mod start for 開催期間 #442
// $result = $this->db->find('SELECT event_id, title, detail, date FROM events WHERE event_type = '.intval($eventType));
$result = $this->db->find('SELECT event_id, title, detail, start_date, end_date FROM events WHERE event_type = '.intval($eventType));
// 2012/05/11 Izawa mod end for 開催期間 #442
$events = array();
foreach ($result as $row) {
// 2012/05/03 takeda mod start for 参加者人数カウント
$participants = $this->db->find('SELECT COUNT(*) FROM participants WHERE event_id = '.$row['event_id']);
// 2012/05/03 takeda mod start for 参加者人数カウント
$events[] = new Event(
$row['event_id'],
$row['title'],
$row['detail'],
// 2012/05/11 Izawa mod start for 開催期間 #442
// date('Y年m月d日', $row['date']),
date('Y年m月d日', $row['start_date']),
date('Y年m月d日', $row['end_date']),
// 2012/05/11 Izawa mod end for 開催期間 #442
// 2012/05/03 takeda mod start for 参加者人数カウント
$participants
// 2012/05/03 takeda mod start for 参加者人数カウント
);
}
return $events;
// add end 2011-12-11 for #201
}
改修履歴コメントは、「関係性の公理」に違反しています。人がソースコードを理解する上で関係のない情報だからです。改修履歴はSubversionやGitなどのバージョン管理システムで残すことができます。
TODO
改修コメントはコードの過去について述べており、今のコードとは関係ない情報になっていますが、一方でコードの未来について述べるコメントもあります。TODOやバグ修正を必要とする旨のコメントです。
// TODO >> 「戻る」をクリックした場合、文言が合わない
// このコードはPostCommonFetchからコピペしたものなので、あとでリファクタリングする
こうしたコメントはソースコードにではなく、チームであれば課題管理表・イシュートラッカーやバグトラッカーに、個人であればTODOリストなどに残す必要があります。ソースコードに書かれたTODOは見つけられるまで忘れられてしまうことがあるからです。
不要になった行のコメントアウト
使わなくなった関数やステップをコメントアウトしてとって置くことがあります。コメントアウトされたコードは、今のコードと関係のないものになっていることが多いです。そのため、コードの可読性が著しく損なわれたり、grep検索にノイズとして引っかかって作業の妨げになったりします。
戻すことが当分ないと思ったら、思い切って削除してしまうことです。削除された事実はSubversionやGitなどのバージョン管理システムで残すことができます。いざとなれば、バージョン管理システムから復元することもできます。ソースコードは「今」の状態を保つように心がけることが、読みやすいコードのカギです。
ストレートではなく、まわりくどい、曖昧なコメント
曖昧なコメント
曖昧なコメントは避けるべきです。例えば、次の例のように「暫定的なコード」とコメントがあったとします。
// 暫定的なコード
このコメントは解釈の多義的な曖昧さがあります。「仮実装なのか?」「削除予定のコードなのか?」それとも「不具合を回避するための暫定コードなのか?」と複数の解釈ができてしまいます。コメントは出来る限り曖昧性を排除して表現する必要があります。
仮実装であれば、「未実装」、削除予定であれば「非推奨」、不具合を回避するための応急処置であれば、どのような不具合があり、そのための暫定的なコードであるとストレートに表現したほうがいいでしょう。
批判・疑問・意見
私は新米プログラマのときに、時間もない中ひどいスパゲッティコードに悩まされて、「どうしてここでこうしてるの!?」「これいらなくない?」と実装に対して批判的なコメントを残したことがあります。冷静になって振り返ると、批判や疑問・意見をソースコードに残すことはマナー違反だったなと思いました。
コメントとして残すのではなく、実装に対する課題であれば、課題管理システムにチケットを作るべきですし、疑問点であれば先輩に質問したり、意見があるのであればパッチを作ったり、コードビューのときに提案することができます。
コメント以外で表現できないか考え、そうでなければコメントを残す
良いコメントは、コードと協調しながらグライスの公理を守ります。良いコメントの特徴をまとめると次のようになります。
- 質の公理: コードと一致したコメント
- 量の公理: 必要十分なコメント
- 関係性の公理: コードと関係があるコメント
- 様式の公理: 短く、簡潔で、直接的な表現のコメント
Code Craftでは、良いプログラマと悪いプログラマのコメントの書き方の違いを次のようにまとめています。これはグライスの公理とも共通しています。
良いプログラマーは
- 少数の本当に優れたコメントを書くように務める
- 理由を説明するコメントを書く
- 大量のコメントを書くことよりも、優れたコードを書くことのほうに集中する
- 理にかなった有用なコメントを書く
悪いプログラマーは
- 優れたコメントとそうでないコメントの違いがわからない
- 方法を説明するコメントを書く
- コメントが自分以外には理解できないものであっても気にしない
- 不適切なコードを支える目的で多数のコメントを書く
- ソースファイルに冗長な情報(改版履歴など)を大量に盛り込む
アンチパターンコメントの例を踏まえて、どのようなときにコメントを書くかをまとめると 「コメント以外で表現できないか考え、そうでなければコメントを残す」ということになります。コメント以外というのは、コードやツールです。コードの変数・クラス・関数の名前を明瞭にしたり、冗長なコメントを取り除きます。
また、コードと直接関係のない情報は、バージョン管理システム・バグトラッキングシステム・設計図・テストコードなどのツールで表現することができます。
コメントはできるだけ書かないことを心がけた上で、どうしてもコメントでないと表現できない情報についてのみコメントとして残します。特に、読み手にとって意外なコードは「なぜそのような実装になっているのか」について理由を書きます。
まとめ
コードは機械に、コメントは人間に向けて書く考える人もいるかもしれません。しかし、私たち人間はコードとコメント、つまりソースコード全体で意図を汲み取ることができます。コードで意図を明瞭に表現する、関係のないものは取り除くなど コメント以外で表現する方法を考え、どうしてもコメントでしか表現できないものをコメントとして残す ようにする。そうすることで、ソースコードがとても読みやすいものになります。
参考文献
- Code Craft ~エクセレントなコードを書くための実践的技法~ Pete Goodliffe
- Cooperative principle – Wikipedia
- プログラムのコメントについて – Qixil
- The Comment Density of Open Source Software Code
*1 C言語などのプログラミング言語ではプリプロセッサに指示を与える目的でコメント書くことがあります。
自動UIテストを支援するTaaS「ShouldBee」の開発者。「エンジニアリングは創造的で楽しくあるべき」のために、テスト・運用など退屈な作業の自動化やドメイン駆動設計を実施する。
twitter: @suin
ブログ: http://blog.craftsman-software.com/