コメントについて
コーディングする上で、注釈として読む人に対してアシストするコメントは重要である。
コメントするべきではない内容
コメントを読むとその時間分だけソースコードを読む時間が減る。
// 各種インポート
import com.example.test;
// ゲームクラスを定義
class Game {
// 変数
private String name;
// コンストラクタ
public Game(String name){
this.name = name;
}
// ゲームをスタートさせる
public bool start(){
// スタート処理
return true;
}
// ゲームをストップさせる
public bool stop(){
// ストップ処理
return true;
}
}
新しい情報でもなく、読む人がより理解しやすくなるわけでもないため、価値がない。
鍵となる考え
コードからすぐにわかることをコメントに書かない。
『リーダブルコード』(Boswell, Foucher, 2012, p.58)
そして、その「すぐ」が重要である。
name = line.split('*').slice(0, 2).join('*');
このコードは、すぐには理解しづらい。
新しい情報ではないが、コードを理解するよりコメントを読んだ方が早いため、これには価値がある。
// 2番目の'*'以降を全て削除する
name = line.split('*').slice(0, 2).join('*');
コメントのコメントによるコメントのためのコメント
// 与えられたsubtreeに含まれるnameとdepthに合致したNodeを見つける。
Node* FindNodeInSubtree (Node *subtree, string name, int depth);
これだと、関数宣言以上の情報がなく、価値がない。
コメントをつけるならば、このようにするとよい。
// 与えられた'name'に合致したNodeかNULLを返す。
// もし depth <= 0ならば、'subtree'だけを調べる。
// もし depth == N ならば、'subtree'とその下のN階層まで調べる。
Node* FindNodeInSubtree (Node* subtree, string name, int depth);
名前の説明でコメントを使わない
// Replyに対してRequestで記述した制限を課す
// 返却値は合計バイト数
function cleanReply(request: Request, reply: Reply){
// 処理
}
このコメントは、clean
の意味をわかりやすく説明しているだけである。
「制限を課す」という意味を関数名内に入れる方がよい。
もしコメントを書くならこのようになる:
// `reply`を`request`にある項目数やバイト数の制限に合わせる
function enforceLimitsFromRequest(request: Request, reply: Reply){
// 処理
}
すなわち、関数名のためのコメントはしない。
コメントでドキュメンタリー
// 1からNまでの合計を求める
// for文で計算するより早かった
return ( n * (n + 1) ) >> 1;
なぜこの実装を選んだのかを記述することで、今後のメンテナンス性が向上する。
コードの欠陥にコメントを
コードは絶えず進化している。
その過程で欠陥が発生することもある。
アノテーションコメントを書くとよい:
// TODO: メモリ領域の計算も行う
// TODO: UTF-8以外にも対応させる
他にもいろいろな記法がある:
記法 | 意味 |
---|---|
TODO | 後で追加・修正するべき機能 |
FIXME | 既知の不具合がある箇所 |
HACK | あまりキレイではない解決策 |
XXX | 危険!大きな問題がある |
NOTE | このコードに関する説明を残す |
REVIEW | コードレビューが必要 |
BUG | バグがある箇所 |
OPTIMIZE | 最適化が必要な箇所 |
REFACTOR | リファクタリングが必要な箇所 |
DEPRECATED | 非推奨の機能 |
DEBUG | デバッグ用のコード |
定数に思想コメントをつける
定数を定める上で、必ずしもこの値ではなくてもよい時がある:
NUM_THREADS = 9 // >= 2 * num_processors あれば十分
// 合理的な限界値。人間はこんなに読めない。
const int MAX_RSS_SUBSCRIPTIONS = 1000;
相手の立場になる
どこにコメントをつけるか判断する際には、このプロジェクトを熟知していない他の人が見たときに、どのように見えるか想像するとよい。
// キャッシュの更新は10分に1回までに制限している
// 理由:サーバー負荷を減らすため
if (timeSinceLastUpdate > 10 * 60 * 1000) {
updateCache();
lastUpdateTime = currentTime;
}
上のコードは他の人が見た時に、なぜ10分という制限があるのかという疑問が生まれる。
しかし、サーバー負荷を軽減するためにこのロジックになっている。
あらかじめ疑問が生まれそうな部分にコメントを残すことが重要である。
ハマりそうな罠
このコードを見てびっくりすることは何かを考え、どんなふうに間違えて使う可能性があるかを考えるとよい。
例えば、ユーザーにメールを送るコード:
sendMail(to, subject, body);
実際は、外部のAPIを使用していて、接続には1秒以上かかる。
それを知らないエンジニアが、ループ内で呼び出してパフォーマンス問題を引き起こしてしまうかもしれない。このような場合は次のようなコメントが有効である:
// NOTE: この関数は外部APIを呼び出すため1秒以上かかることがある
// 大量のメールを送信する場合は非同期処理を検討すること
sendMail(to, subject, body);
要約コメント
// VIPの全プレイヤーにスペシャル武器を1つ付与する。
for( Area area : areas ){
for( Player player : area.getPlayers() ) {
if( player.hasPermission("vip") ){
player.giveItem(SPECIAL_WEAPON, 1);
}
}
}
コメントが全くないと、読むのが苦になる。
コードの塊ごとにコメントを書くことも効果的である。
function launchApp(params) {
// 初期化
// ........
// .....
// アプリ情報をDBから読み込む
// ........
// .....
// スプラッシュ画面表示
// ........
// .....
// ユーザー情報読み込み
// ........
// .....
}
関数内の処理を箇条書きでまとめることも有効である。特に複雑なアルゴリズムや長い関数では、処理の流れを示すコメントが理解を助ける:
/**
* ユーザー認証プロセス
* 1. 入力値の検証
* 2. データベースからユーザー情報を取得
* 3. パスワードの照合
* 4. セッションの作成
* 5. ログイン履歴の記録
*/
function authenticateUser(username, password) {
// 実装...
}
また、ファイルの先頭にはそのファイルの目的や全体像を説明するコメントを書くとよい:
/**
* ユーザー認証モジュール
*
* このモジュールはログイン、ログアウト、パスワードリセットなどの
* ユーザー認証に関わる機能を提供する。
*
* 主な機能:
* - ユーザー認証(Basic認証、OAuth対応)
* - セッション管理
* - アクセス制御
*/
まとめ
コメントは適切に使用することで、コードの理解を助け、メンテナンス性を高める重要な要素である。
NGコメント
- コードからすぐに読み取れる内容
- ひどいコード(関数名など)を補うためのコメント → コメントを書かずにコードを書き直すべきである
- 古くなった情報を含むコメント
Goodコメント
- なぜ他のやり方ではなくこれを選んだのかの説明
- TODO、FIXME、XXXなどの将来の改善点や既知の問題点
- 定数の値にまつわる背景や制約条件
Bestコメント
- コードを読む人が疑問に思う場所を予想したコメント
- ファイルやクラスに全体像を説明するコメント
- ブロックや関数ごとの要約コメント
- アルゴリズムの説明や特定の選択の背景
コメントは「なぜ」そのコードが存在するのかを説明するものであり、「何を」しているかではない。優れたコメントは読み手の時間を節約し、コードベースへの理解を深める手助けとなる。
最後に、優れたコードには適切なコメントが必要であるが、コメントの必要性を減らすためにも、まずはコード自体をクリアで読みやすいものにするよう努めるべきである。
参考文献
- 『リーダブルコード』(Dustin Boswell, Trevor Foucher 著、角征典 訳、オライリー・ジャパン、2012年)