【C#】コメントに書くこと、書かないこと

※C#としているがC#以外でも同じで、サンプルコードをC#で書いているのでタイトルにC#とつけた。

プログラミングを行う上で、コメントの重要性は大きい。適切なコメントを書けばコードを読む人の助けになる。逆に、どうでもいいコメントや嘘のコメントを書けばそれは悪影響をもたらす。

コメントに関しては宗教のようなものなので初めに私のポリシーを明記しておく。この記事の内容もこれに沿って展開する。

  • コードを読んだら秒でわかることはコメントに書かない
  • コードを読んだらわかるが読むのに時間がかかりそうな時は書く
  • なぜこのようなロジックになっているかを書く
  • 拡張・修正する際に、罠になりそうな箇所の警告を書く
  • コメントで説明するくらいなら命名を見直す
  • 変更前のソースをコメントアウトして残さない(論外)

この記事の内容はあくまで私が考えていることの表明であって、「私の意見が絶対的に正しい」とか「コメントはこう書かれるべきである」という趣旨の内容ではない。念のため書いておく。

コードを読んだら秒でわかるようなことはコメントに書かない

正確に言うと、書く必要がない。なにせ読んだら秒でわかるのだから。

極端な例だが、以下のようなコメントは不要だ。

// 〇〇さん(xx)
var nameAndAge = $"{name}さん({age})";

これは、変数を「〇〇さん(xx)」の形に整形しているが、そんなもん見たらわかるのである。

見たらわかることをいちいちコメントに書く必要はない。何故か?

それは、ソースを修正する時にコメントも修正する必要が出てくるからだ。

上記コードを以下のように修正したとしよう。

// 〇〇さん(xx)
var nameAndAge = $"{name}さん({age}歳)";

コードは修正したが、コメントの修正を忘れたとする。するとどうなるか。

ソースを見ると「〇〇さん(xx歳)」となっているが、コメントは「〇〇さん(xx)」となっている。これを見た人は、「どっちが正しい仕様なんだろう?」と頭を悩ませるのである。そして仕様を確認するという手間が発生するし、コメントを修正する(あるいは修正を指示する)という手間も発生することだろう。

書いていなければ「ふーん、『〇〇さん(xx歳)』の形式ね。」とスムーズに読めるのに、コメントがコードの内容と違っているばかりに余計な手間がかかる。これは完全に悪である。

「いやいや、普通はコードと一緒にコメントも修正するでしょ」と言う人もいるかもしれないが、そういう普通の人と一緒に働いてみたい。特にコード変更に対して充分な時間が与えられないことは往々にしてあり、急いで修正、テスト通るかのチェックをしてレビュアーに投げるというものだろうが、経験上ほとんどのレビュアーは差異が発生している部分しかチェックしない。

コメントが実装と違っていてもテストは通るし、人の目のチェックも通る時は通るのだ。だからこそ、コメントは常に正確に書いていなければそもそも書く意味がないのだ。コードを変えたならコメントもそれに合わせて変えなければならないのだ。

でも人間はそんな完璧じゃないから絶対にどこかで修正漏れが発生する。だったら初めから「コードを読んだら秒でわかる」ような箇所のコメントは不要なのだ。

コードを読んだらわかるが読むのに時間がかかりそうな時は書く

以下のようなコード。

public static List<string> Nabeatsu()
{
    var nabe = new List<string>();

    foreach (var n in Enumerable.Range(1, 40))
        if ((n % 3 == 0) || $"{n}".Contains("3"))
            nabe.Add("アホ");
        else
            nabe.Add($"{n}");

    return nabe;   
}

これは、「1から40までの自然数のうち、3の倍数または3のつく数字の時は「アホ」を、そうでない時はその数字を文字列型のListに入れて返す」という挙動のNabeatsuというメソッドである。

上記のことは、コードを読めば分かるが意図を正確に把握するにはを全て読まなければならない。

こういうコードは処理が簡単に書いてあれば便利だ。メソッドであればXMLコメントとして残すべきだ。

Visual Studioを使っているならば、メソッドの上に///と入力するだけでXMLコメントの雛形が自動で作成される。

/// <summary>
/// 
/// </summary>
/// <returns></returns>
public static List<string> Nabeatsu()
{
    var nabe = new List<string>();

    foreach (var n in Enumerable.Range(1, 40))
        if ((n % 3 == 0) || $"{n}".Contains("3"))
            nabe.Add("アホ");
        else
            nabe.Add($"{n}");

    return nabe;   
}

あとは、コメントを書いていくだけだ。

/// <summary>
/// 1から40までの自然数のうち、3の倍数または3のつく数字の時は「アホ」を、そうでない時はその数字を文字列型のListに入れて返す
/// </summary>
/// <returns>そのまま使えるナベアツリスト</returns>
public static List<string> Nabeatsu()
{
    var nabe = new List<string>();

    foreach (var n in Enumerable.Range(1, 40))
        if ((n % 3 == 0) ** $"{n}".Contains("3"))
            nabe.Add("アホ");
        else
            nabe.Add($"{n}");

    return nabe;   
}

このようにXMLコメントを書いておくと、

のように、呼び出し元でメソッド名をマウスオーバーすると参照できるようになる。

なぜこのようなロジックになっているかを書く

コードを読んでいると、「ここはなんでこう書いてるの?」と思うことがある。その疑問に先回りして「なぜか」が書いてあるコメントは良いコメントだと思う。

やや乱暴な例だが、例えば以下のようなコード。

user.UpdatePasswordTo(plainPassword);

これは恐らく、「パスワードを引数の文字列で更新している」という処理だろう。

しかしplainPasswordは見るからに平文のパスワードだ。こんなもんそのまま更新する訳にはいかない。

このコード、実はUpdatePasswordTo内部で引数の文字列はハッシュ化するという処理が実行されることになっている。だからここは別に平文で渡しても問題ないのだ。

だがそんなことはどこにも書いてない。UpdatePasswordToの実装を見るまでは分からないのである。

そこで、

user.UpdatePasswordTo(plainPassword);   // 中でハッシュ化するから平文でOK

というコメントがあったとしたらどうだろうか。それなら問題ないな、と疑問がすぐ解決するのではないだろうか。

もちろん、そのコメントが嘘であってはいけない(つまり本当は中でハッシュ化などしていない)が、こういうコメントはそれが事実である限りとても役に立ち、コードリーディングを助けるものになる。

拡張・修正する際に、罠になりそうな箇所の警告を書く

これも余りいい例が思い浮かばないのでちょっと強引な例だが書いてみる。

あるシステムがあり、利用者には個別のuserIdを自分で設定してもらう。userIdは”@”以外の文字列で任意に設定できるが、実は使用しているライブラリのバグで”@”から始まる文字列を登録しようとすると実行時エラーになる。が、現在の仕様だとそもそも”@”は入ってこないため特に問題はなかったとする。

ここで”@”をIDに含めたいというユーザーの要望により、ユーザーIDに”@”を含められるように仕様変更することになった。”agasahakase”というユーザーがカッコつけて”@g@s@h@k@se”というIDにしたいと言い出したのだ。

仕様変更しリリースすると、”agasahakase”さんは”@g@s@h@k@se”にIDを変更していた。別のユーザーで、TwitterライクなIDにしたいと思い”apashoni”から”@apashoni”に変更しようとしたユーザーは更新に失敗してしまった。

急いで原因を調査するが、何も分からない。コードを書いた人は既に転職しており誰も原因がわからない。三日三晩寝ずに調べて、とうとう”@”で始まる文字列はIDに設定できないということが判明した。

もしここで、

user.UpdateIdTo(newId); // 現行仕様では関係ないが、ライブラリのバグで"@"で始まるIDは登録できない

などと一言添えてあればそれを見越したリリース案内が出来たのである。

まぁ上記のはかなり強引な一例であり実際にここまで書くかは議論の分かれるところかもしれないが、「罠になりそうあ箇所の警告を書く」とは上記のような話だ。

現行仕様や近々に何かに影響する訳ではないが、将来的に役立つかもしれないことを書いておくのも良い。

繰り返すが上記はかなり強引な例えだ(いい例が思いつかなかった)。

コメントで説明するくらいなら命名を見直す

【C#】命名って大事だねという記事でも書いたが、不足情報を補うくらいなら命名やロジックで直感的に説明しコメントは書かないという方がよほど親切だ。理由は前述した通り、コメントもメンテナンスが必要になるため。

例えば以下のようなコード。

// 単価
decimal price = 100;

// 単位はmm
decimal height = 1700;

これらは

decimal unitPrice = 100;

decimal height_mm = 1700;

という命名にすれば、単価だということや単位がmmだということは一目瞭然だ。コメントを書く前に、まずこれらができないか試すべきだと個人的には思う。

変更前のソースをコメントアウトして残さない(論外)

これ、最近のエンジニアさんはおとぎ話くらいに思うのだろうか…?

これらは、バージョン管理ソフトが普及していなかった頃の名残だ(と思う)。昔はバージョン管理ソフトなんていう便利なものはなかったので、こうやって修正前のコードを残しておく必要があった。

しかし現在はだいたいどのチームでもgitやSubVersionなんかを使ってバージョン履歴管理やソースコードの共有などをしているのであり、現代ではほとんど不要なハックと言える。読んでいて邪魔になるだけだし、そもそもコメントアウトを外してもまともに動作しない。

残念ながら未だにこういうコードは見かけるし、こういう風にコメントを残す人もいる。特に大昔からコード書いている人なんかは、よくこういうコメントアウトをしていらっしゃる。恐らくバージョン管理ソフトの使い方などを分かっていないんだと思うが、だったら勉強とかちゃんとしてほしいなと正直思う。勉強できない古のエンジニアはとっとと解雇して私の給料増やしてちょと思うのだがなかなかそうもいかないので難しいところだ。

まとめ

以上が、私がコメントを書くときに気を付けているような内容だ。ここには書いていないが意識していることも多分あると思う。

繰り返しになるが、あくまで私個人の考えで合ってこれが正しいという訳ではないが、まぁ別にそこまで変なことを書いているつもりもないので参考になるところをつまみ食いで参考にしてもらえればいいなと思う。