【C#】命名って大事だね

命名って大事だよね、ということはコードを書く人であればだいたい同意していただけると思う。

というかコードを書かない人も「memo.txt」と「〇月〇日までに買う予定リスト.txt」だったら、ファイルの内容が同じでも後者の方が圧倒的にわかりやすいとは思っていただけると思う。

毎度毎度弊社の困ったちゃんのことを書くのは忍びないが、だけど書くが、困ったちゃんははっきり言って命名がヘタクソである。私も別に命名に自信ニキという訳ではないが、まぁ困ったちゃんよりは上手く命名していると思うし、自分の命名で困ったこともあまりないので書いてみる。

以降、サンプルコードにC#を用いるが別にどの言語でも共通する話だと思う。難しいことは書かないのでC#がわからなくてもだいたい読めると思う。

なぜ命名が大事か

簡潔に言うと情報量の違いだと思う。

極端な例だが、次のような変数宣言があるとする。

decimal price = 100;

priceは価格・値段といった意味だが、はっきり言ってpriceという文字だけでこれが何に使われるのか正確にはわからない。

具体的には以下のような情報が欠けている。

  • 単価?小計?合計?
  • 税込み?税抜き?
  • (キャンペーンなどがある場合)割引適用前?適用後?
  • 単位は日本円?ドル?

こういった情報はできるだけ変数名に明示的に盛り込むべきだと考える。

// 単価なら
decimal unitPrice = 100;

// 小計なら
decimal subTotal = 100;

// 合計なら
decimal total = 100;

// 税込みなら
decimal taxIncludedPrice = 100;

// 税抜きなら
decimal taxExcludedPrice = 100;

// 割引適用前なら
decimal undiscountedPrice = 100;    // または originalPrice

// 割引適用後なら
decimal discountedPrice = 100;

// 単位が縁
decimal price_yen = 100;

// 単位がドル
decimal price_dol = 100;

といった具合に変数名に情報量を持たせることで可読性を上げることができる。

この考え方は一部、アプリケーションハンガリアンといわれる考え方と一致する。

たとえば、

decimal total = price_yen + price_dol;

のようなコードはドルと日本円を加算しており、明らかに間違いだということが見ただけで分る。正しくは

// ドルは円に換算して可算する
decimal total = price_yen + ConvertToYen(price_dol);

としなければならないと判断できる。これが命名が持つ強力なメリットであり、命名にこだわる理由である。

こういった考え方が活きる場面はたくさんある。

// パスワード
string password = "XXXXX";
// パスワードを更新
user.updatePassword(password);

これだと、passwordで更新しているが、うっかりpasswordが平文のままだったら平文がそのまま登録されているかもしれない。

もし変数名をplainPasswordなどとしていれば平文のパスワードということが見ただけで分かるので、登録前にハッシュ化させる必要があると気づけるだろう。

// 平文のパスワード
string plainPassword = "XXXXX";
// ハッシュ
string hashedPassword = plainPassword.Hash();
// ハッシュ化されたパスワードで更新
user.updatePassword(hashedPassword);

また、コーディングミスにより

// 誤って平文で更新するコードになっている
user.updatePassword(plainPassword);

と書いてしまっていたとしても、「平文で更新するっておかしいよね?」と気づくきっかけになるハズだ。

このような「これは税計算必要なの?」「これは平文?」などの情報はよくコードを読めば分かることではある。だがその「よくコードを読む」作業を意識から切り離して、本来考えたいアルゴリズムや可読性などに集中できるのが大事だ。そしてそれは命名の工夫をするだけで改善できることでもある。

汎用性の高い接頭辞や接尾辞もあるので、それらはいつでも使えるようになっておくとベストだ。

xxxFlgという変数名

真偽値などでxxxFlgなどという命名をしているのをたまに見かけるが、これもあまり優しくないと思う。

例えばdeleteFlgという変数名があるとする。これは以下のように様々な解釈が可能である。

  • 削除済みのデータであることを示すフラグ
  • 削除できる状態のデータであることを示すフラグ
  • 削除しなければならないデータを示すフラグ
  • 削除される予定のデータであることを示すフラグ

などだ。deleteとしか書かれていないのだから如何様にも解釈できる。trueで削除済みなのかfalseで削除済みなのかも、変数名からは分からない。個人開発ならともかく、チームで開発するプロジェクトにおいて「そんなつもりで命名した訳じゃない」「普通に考えたらこう」なんていうのは通用しない。何故ならそのコードは自分だけではなく、他の誰かが読んだり修正したりするかもしれないからだ。個人開発だとしても、何日も経って忘れたころに見直すこともあるだろう。だからこそ、直感的に分かりやすい命名をする必要がある。

真偽値の命名にはis, has, can(もしくはis~able), shoiuldなどを使うと可読性が上がると言われている。それらはY/N質問として読めるし、意味のはき違えようがないからだ。

// 削除済み
bool isDeleted = true;

// 削除できる
bool canDelete = true;
bool isDeletable = true;

// 削除しなければならない
bool shouldDelete = true;

これだと、isDeletedは「削除済みか?」に対してtrue(削除済み)かfalse(削除済みではない)とハッキリするのである。isDeletedという変数名で「trueだから『削除しなければならない』ってことだな!」「trueだから未削除なんだな!」と解釈する人はまずいないだろう。それは考え方とかじゃなくて英語力の問題だから…。

コメントで補えるのでは

命名をシンプルにしてコメントで補えるのでは、と思うかもしれない。確かにそういう手もあるが、個人的には望ましくないと考える。なぜか。

それはコメントはメンテしないといけないからだ。

// 税抜き価格
decimal price = 100;

というコメントをつけたコードがあったとする。これが税込み価格を反映するという修正が必要になった場合、

// 税抜き価格
decimal price = 110;

と修正するだけでなく、コメントも

// 税込み価格
decimal price = 110;

と直さなければならなくなる。修正の工数がしっかり確保されており、コメントも含めてレビューされるような環境であればまだいいかもしれないが、多くの場合修正レビューはテストが通るかどうかと差分の比較であり、コメントは忘れ去られる。動作は自動テストが保障してくれるかもしれないが、コメントが合っているかどうかは人の目でみないと分からない。

もしコメントのメンテを怠ると、そのコメントは嘘になる。コメントに嘘が書いてあるくらいなら、コメントがない方が100倍ましだ。そうやって嘘のコメントが積み重なって保守不可能な過去の遺産になっていく。コメントで意味を補足するくらいなら、taxExcludedPricetaxIncludedPriceなどの具体的な意味を持った命名をした方が良い。

システムハンガリアンは…

前述した「アプリケーションハンガリアン」とは別に「システムハンガリアン」という考え方もあるが、これは現代ではもはや推奨されていないシーンの方が多い。以前は私も使っていたが、最近はまず使わないようになった。

string sName = "あぱしょに";
int nAge = 27;

システムハンガリアンは、string型ならsstr、int型ならnintのように、各変数の型を識別できる接頭辞を入れるというものだ。

これも同様にコードを見ただけでエラーが分かるようになるというハックの一つだが、静的型付け言語ではたとえば

string sName = "あぱしょに";
int nAge = 27;
int hoge = nAge * sName; // エラー

などと間違えて書いても

のように教えてくれるので、変数を見ただけで型の不一致がわかるというメリットは少ない。動的型付け言語であれば違うと思うが…。

また、システムハンガリアンは型を変更した際に変数名も修正しなければならない(そうでないとシステムハンガリアンのメリットが正しく発揮されない)。たとえばnUserIdint型からstring型に変更した場合は、型だけでなく変数名もsUserIdと変更しなければならず、これは大変な手間である。

そういった理由で、システムハンガリアンは現代ではほとんど見られなくなった(少なくとも私の観測範囲では)。これからも私が積極的に使うことはないだろう。昔の案件の修正などで既にコーディングスタイルが指定されていれば従うが。

英語にこだわらなくていい

個人的には無理して英語にこだわって意味不明な変数名になるよりは、別にローマ字でもいいから意味が分かるよう正確に命名してほしいと思っている。

また、英語に自信がなければcodicDeepLなど便利なサービスもあるので、それらも活用したい。特に前者はネーミングに特化しているだけでなく、camelCasesnake_casePascalCaseなど自由にスタイルをカスタマイズできる。私はこれらのサービスで色んな語を知った。