ぼくがかんがえる書くべきコメント/書かないべきコメント


定期的にコメントを書くべきか書かないべきかの議論が話題になりますね。最近でも話題になっていました。

【プログラミング】退職した先輩が書き残していったコメントがひどすぎる・・・ - 私の戦闘力は53万マイクロです

プログラムにコメント書かない文化もあるよって話 - NZ MoyaSystem

この流れに乗じて、「コメントに何を書くべきか、書かないべきか」に関する個人的な考えをまとめてみます。

で、お前誰よ?

ネイティブアプリや Web アプリ開発をやっています。ここ数年は Java や Swift を使ったネイティブアプリや、Java のAPIサーバーを書くことが多かったです。チームは割と少人数で、2〜3人です。こういった状況でのコメントの書き方の方針だと思ってもらえればと思います。

基本的には「コードを簡潔にする事は重要」というスタンスで、また「可能であればコメントを書かなくて済むコードを書くべき」だと思っています。

そもそも何のためにコメントを書くのか?

コメントは主に以下の目的を満たすのに有効だと思っています。

  • コードの概要を手っ取り早く把握できるようにする
  • 「この実装を書き換え/削除したいが問題ないのか?」という疑問に答える

目的1: コードの概要を手っ取り早く把握できるようにする

ソースの冒頭(Java のように 1ソース = 1クラスの場合はクラス宣言の直前)にソースの概要を書くと、わざわざ中身を読む必要を省けるので有効だと思います。

またメソッドの実装が長くなってしまった場合には、コメントで大雑把な流れを書くのは有効な手段だと思います。例えば長い計算をした上での更新をするメソッドの場合は、以下のようにフェーズ毎にコメントを書いています。

// (Java での例)

public void calculateAndUpdate(...) {
    //
    // 1. 更新する値を計算
    //
    int result = ...;
    ...

    //
    // 2. 結果を更新
    //
    this.result = result;
}

さらに Objective-C や Swift では、コードにセクション分離をする構文が用意されています。例えば、Swift では以下のようにコードを書けます。

class MyClass {
    ...

    // ----------------------------------------------------------------
    //     MARK: 初期化処理
    // ----------------------------------------------------------------

    init(param: Int) { ...(他の初期化)... }

    convenience init() { self.init(param: 0) }

    ...


    // ----------------------------------------------------------------
    //     MARK: 主要な操作
    // ----------------------------------------------------------------

    func method1() { ... }

    ...


    // ----------------------------------------------------------------
    //     MARK: プライベート
    // ----------------------------------------------------------------

}

IDE(Xcode) で見るとセクションを分離してブラウズできます。

このように書くことでクラスが提供する操作の概要がわかりやすくなりますし、該当箇所のメソッドを探すのが簡単になります。またメソッドを追加したい場合に、どこにコードを追加するべきかが分かりやすくなります。

Objective-C や Swift 以外ではこの分類するための記法を見たことはないのですが、特別な記法がない言語でも分類する事は有効だと思っています。

目的2: 「この実装を書き換え/削除したいが問題ないのか?」という疑問に答える

開発をするにあたって、特別な要件を満たしたりバグを回避するためのコードが出てくると思います。そういったコードは多くの場合汚かったり規約に合わなかったりするだけでなく、後々その目的が忘れられる事が多いです。不自然だけど必要不可欠なコードに対してはきちんとコメントで補足をするべきだと思います。

コメントに関しては以下の事がわかるようにするのが望ましいです。

  • 該当コードが必要な理由
  • 代替案と、それを選ばなかった理由
  • 該当コードがいつ不要になるのか
  • 補足資料へのリンク

似たようなケースとして、設定値の記述箇所に対しても、その設定値にした理由を(理由が明白である場合を除いて)書くべきだと思います。「デフォルトだから」とか「なんとなく」でも立派な理由です。何も無いよりずっと良いです。

よく言われる「コメントに書くべき項目」について

一方で、コメントに書くべき項目として、クラスやメソッドの概要や引数の説明、事前条件/事後条件/不変条件などが(一部で)言われると思います。ただ有効かどうかは割とケースバイケースかなというのが感想です。以下個人的な意見を書いていみます。

「クラスやメソッドの宣言箇所の直前に概要を書く」

多くのプログラミング言語ではクラスやメンバの宣言の直前に Javadoc などの形式で概要を書く事をサポートしています。コメントの是非に関しては、この部分に説明を書くべきかどうかが議論に上ることが多いように思います。

個人的には、自ずと分かるような名前にする努力をする事は前提として、(Java 等の入れ子クラスも含め)クラス概要は書く事をルールにしても良いんじゃないかと思います。しっくりした名前を作るのは結構難しいですし、名前だけでは中々伝えきれないケースも多いです。コメントを書く負荷も比較的低いです。

一方で、メソッドなどに関しては必ずしも書かなくても良いのではないかと思います。クラスが分かっていれば割と文脈が絞られるので、伝わる名前にするのは比較的簡単ですし、量も多いのでコメントをメンテナンスする負荷も高くなります。

「引数や返り値の説明を書く」

Javadoc では @param@return などのメソッドの引数や返り値を説明するための構文がありますが…特に必要な場合を除いて書く必要はないのではないかと思います。Java などの IDE が発達している言語だと自動生成で作られたりしますが、使わない場合は消した方が良いと思っています。自動生成された時点での引数リストがメンテナンスされずに残ってしまうケースが多くあるように思います。

引数が多すぎる場合、引数に関する説明がないと把握しきれないケースが出てくる事が多いですが、そもそも設計を改善することを検討するべきだと思います。例えば、Java で記事を更新するメソッド宣言の場合、以下のようなコードがあるとします。

public class ArticleRepository {
    ...
    public void update(int articleId, int userId, String contentHtml) { ... }
}

articleId は記事の ID、userId は記事の ID、contentHtml は HTML というルールがあります。こう言った事前条件をコメントで書くこともできるのですが、特別な事情がない限り以下のようなコードにする事を検討した方がいいと思います。

public final class UserId {
    public final int value;
    ...
}

...

public final class ArticleId {
    public final int value;
    ...
}

...

public final class ArticleContent {
    public final String html;
    ...
}

public class ArticleRepository {
    ...
    public void update(ArticleId article, UserId editor, ArticleContent content) { ... }
}

このように基本型などの値をクラスでラップすれば、渡す引数の意味は分かりやすくなりますし、articleId と userId の順番を間違える事はコンパイラが防いでくれます。また ArticleContent のコンストラクタなどに HTML のバリデーションを通す事で中身の正しさを保障する事ができます。

「事前条件」「事後条件」および「不変条件」

メソッド呼び出し渡す引数などに対する事前条件や、呼び出しの結果起こる事後条件や不変条件は設計の際に常に意識するべきだとは思いますが、コメントに全てを書くのはコストが大きすぎるのではないかと思います。

また、上記のように設計や規約で代替できる事も多いのではないかと思います。例えば前述の UserId クラスなどは、「〜Id クラスは不変」という規約を設ければ、わざわざ UserId や ArticleId に不変条件をコメントに記載する必要はないと思います。

ダメなコメント

一方で、逆にコメントとして相応しくないものは以下のものがあると思います。

書かれているコードの翻訳

/** ユーザーのサービス */
public class UserService {
    ...
    /** ユーザーを削除する。 */
    public void delete(int userId) {
        // DB からユーザーを削除する
        db.execute("delete from users where id = ?", userId);
    }
}

実装の中やメソッド名の場合、コードの翻訳にしかならないコメントは役に立たないので消しましょう。

クラスやメンバの宣言の前にコメントを書くルールにした場合は書く事がなくて翻訳になってしまうケースも出てくるとは思いますが、もう少し気の利いた情報を書けないか検討した方が良いと思います。

終了する具体的な計画のない TODO

// TODO xxxの状況にも対応する

チームとして必要な TODO なら課題管理システムなどで管理しましょう。個人タスクなら自分のタスク管理ツールやメモ帳などに残しましょう。

削除する具体的な計画の無いコメントアウト

// あとで消す
// boolean xxflag = ...
// ...
//

コメントアウトしたコードが必要になっても、バージョン管理していれば最悪復帰できます。本当に残したいコードであれば、その理由を書きましょう。

自動生成されたままのコメント

// TODO Auto-generated method stub

IDE でメソッドなどを自動生成した場合、上のようなコメントが表示されます。メソッドの実装の書き忘れを防ぐためのものなので、実装を書く時に一緒に消しましょう。