名前付け

名前は、プログラマ同士の会話にも使われる。
良い名前をつけると、プログラムだけでなく人間のやりとりもスムーズに進む。

短く

できれば短い単語で1単語。
上限は、だいたい15文字以内・3単語以内くらい。

長いキーワードの弊害は、

  • ソースにたくさん並ぶと読みにくい
  • 記憶しにくいので、頭の中でプログラムの構造をイメージしにくくなる
  • 似たようなキーワードが増えて、混乱する
    • 長い名前はだいたい、ほとんど同じでどれか1単語のみ違ったりする

いずれも作業効率を落とす。

良い名前の付け方

  • 概念に名前をつける
    • 頻繁に登場する概念は、頻繁に入力する文字列になる
      • 簡潔で、概念を的確に表現する名前を慎重に選ぶ
    • できれば短い一単語で
      • 概念のクラスには派生クラスが作られがちで、そのとき後ろに1単語ずつ増えていくことが多いので

  • メソッド名
    • 処理の内容を要約する
    • 何をするメソッドなのか、ユーザー視点で表現する。内部実装で表現しない。
      • △ SetOriginalValue
      • ○ ResetValue

良い名前が付かない理由

良いコードは、良い名前の付け方を自然に実践できる。

名前付けに困るときは、たいてい良いコードが書けていないのが原因。

  • クラスの役割が絞りこめていない
  • 1つのメソッドに複数の機能を持たせている

もちろん、自分のボキャブラリが足りないこともある。
Rubyのライブラリは1単語で明確な名前がついてることが多く、参考になる。
Java, C#も悪くないけど、ちょっと冗長。

イディオム

あまり世間で広まっていないモノを中心に。
  • Utility
    • 便利なstaticメソッドを集めたクラス
    • インスタンスメソッドにする必要がないものを外部に出しておくと、概念と実装が分かれて読みやすくなる

NGワード

次の単語はできるだけ使わない。
  • object, data, info, description, instance
    • 特に意味もないのになんとなく付けて、冗長になる
    • JavaのObjectクラスとか、struct名にはDataをつけるとか、明確な意味を持たせる場合はOK
  • manager
    • HogeManagerという名前をつけると、なんでもやるクラスになりがち
    • クラスの役割を明確にすれば、もっと曖昧さのない単語が浮かぶはず
  • get, set
    • コードがget/setだらけになるから
  • is
    • コードがisだらけになるから
    • 英語的に自然な is〜 という名前をつけるのは日本人には難しく、創作英語が生まれる要因だから
  • enable, flag
    • 様々なHogeに対して「Hogeを有効にする」という言い方ができるので、乱用するとenableHoge()だらけになる
    • 「HogeFlagを立てる」も同様。
    • 単に「Hogeをする」など、簡潔な言い換えを意識する
  • (メソッド名) try
    • tryHoge()は、canHoge()とhoge()が混ざったメソッドなので、分離するべき
    • Mutex::tryLock()のような、本質的にatomicなメソッドはOK
    • Matrix::inverse()のような、判定と実行を分けると余分なコストがかかる場合もOK
  • (メソッド名) and, or, if
    • ひとつのメソッドが複数の処理をしている証拠だから
    • メソッドを分けるか、一連の処理に新たな名前をつけるべき
  • (集合の接尾辞) collection, vector, (array), (group)
    • スペルが長いから
    • 複数形にして表現。setも可
    • setだと、std::set型と誤解するという人もいるかなぁ
  • 日本人の多くが知らない単語
    • コードを読みながら辞書も引くのは、理解を妨げる
  • 日本人の多くがスペルが自信ない単語
    • typoが増えたり、打つのに時間がかかる
    • よく見るのはvisibility, referenceなど。けどこれらは代用が思いつかない…

便利な単語

英単語は日本語訳で覚えない。ニュアンスで覚える。
使おうとしている単語が適切かどうかは、英語としてのニュアンスで判断する。
和訳してから日本語で考えると、いまいちハマらない名前を選んでしまう。

以下、単語とそのニュアンスを列挙

  • Resolve
  • Parse

名前を簡潔にするテクニック

DRY原則の実践でもあります。

  • 名前空間・クラス・メソッドで同じ単語を含まないようにする
    • info::HogeInfo::createHoge() -> info::Hoge::create()
    • これを怠ると、どんどん名前が長くなる気がする
    • ディレクトリとかXMLのタグとか、階層構造を持つものならなんでも応用できる
    • 広い範囲に影響するので、設計初期から方針を貫かないとどんどん崩れるので注意
  • 引数の型をメソッド名から省く。オーバーロードも活用
    • addHoge(Hoge& hoge) -> add(Hoge& hoge)
    • findFooByBar(Bar& bar) -> findFoo(Bar& bar)
  • 目的語をthisにする
    • processHoge(Hoge& hoge) -> Hoge::process()
  • よく使われる操作に、独自の名前をつける
    • isNullOrEmpty() -> isBlank() (railsにあったような)
  • メソッドのユーザーが何をしたいか考え、ユーザー視点で名前をつける
    • Queue::addLast() -> Queue::enqueue()
    • deleteAndSetNull() -> safeDelete()
    • ここでいい名前を思いつけるように、日頃からボキャブラリを多く持つ
  • 内部実装をほどよく隠蔽し、メソッド名に必要以上の情報を盛り込まない

コメント

書くべきは、意図・要約・理由・言い訳。

コメントがなくても読めるコードが理想。
よくないコードを見たら、フォローするコメントを書くよりリファクタしたほうがいい。
日本語で挙動を表現するのはそもそも難しい。日本語に悩む時間があったら、コードをわかりやすくするのに使った方がいい。

次のようなコメントは書かない。

  • 言語の常識レベルの機能の説明
    • 「コンストラクタ」・「デストラクタ」
    • 「インクルード」・「関数宣言」・「関数定義」
    • などなど
  • DRY原則違反のもの
    • クラス名、メソッド名、変数名を日本語訳・カタカナ表記しただけ
    • 処理の内容をほぼ1対1で記述
    • メソッドのコメントに、そのメソッドの名前を含める
      • クラス・ファイルも同様
    • 基底クラスのコメントを、派生クラスにコピペ
      • 派生クラス特有の内容があれば、それだけを書く。
    • 罫線
      • ハイフンのコピペなので、DRY原則違反(半分冗談)

上記のようなことしか書くことがなかったら、何も書かなくていい。
すべての関数や引数にコメントをつけろ、というルールは、無意味なコメントを書くことを強制するのでよくない。
すべてにコメントをつけると統一感はあるけど、特に実益はないと思う。
それより、コメントを書くコストと、ノイズによってコードが読みにくくなることを重く見たほうがいい。

リファレンスマニュアルを自動生成する場合は、公開関数にコメントを義務づけるのはしかたないかも。
それはコードではなくドキュメントを書くコストと見なしていい。

未来に残す言葉

良いコードが書けていないときは、メンテナンスする人向けのガイドを添えておく。
1ヶ月後の自分が助かることもよくある。

  • どこどこにコピペしてあります。変更時は内容を同期してください。
  • なになにの場合は、どこどこで処理しています
  • @todo 未実装
  • @todo リファクタ(イメージがあればそれも書く)
  • @todo こういうケースでバグるので対応する

詳細を書く余裕がないときは、@todo の一言だけでも価値がある。
それを見て意図が分からなかったら、svn blameして書いた人に聞きに行けばいい。

具体的に書きすぎない

  • 汎用的な処理に、現時点で何に利用しているかを書かない
    • 他に処理が増えたとき、コメントが古くなる
    • 「例えば、こういう利用のために存在する」のように書く

コピペ

  • 原則禁止
  • やむを得ないときは、1回(同じモノが2つ存在)まで
    • その際は、できるだけ内容を変えずにコピペする
      • コピペ後に微妙に書き換えてしまうと、後で関数化などするときに面倒になる。
    • また、両方にどこにコピペしたかをコメントで書く

  • 例: 内容を変えずに済むように書き換えてからコピペする
// before
int fooSize = foo.size();
for(int i=0;i<fooSize;i++)
  foo[i].process();

int barSize = bar.size();
for(int i=0;i<barSize;i++)
  bar[i].process();

// after
{
  int size = foo.size();
  for(int i=0;i<size;i++)
    foo[i].process();
}

{
  int size = bar.size();
  for(int i=0;i<size;i++)
    bar[i].process();
}

  • C++では、クラスの宣言と定義はコピペの一種といえる
    • なので、上記の方針に従って考える
    • メンバの宣言とコンストラクタのメンバ初期化リスト
      • 順番を揃える
      • メンバ初期化リストで、デフォルトコンストラクタで済むメンバも省略しない
    • hとcppで関数の順番を揃える
    • 引数のnamespace名を、cpp側もhに合わせて完全修飾名で書く
    • 関数のコメントはhかcppのどちらか一方に書く
      • 私的にはh側。hがそのまま関数リファレンスになるから。
      • 双方にコピペしたり、片側のみ情報量を増やさない
      • 両方に書いてあると、doxygenはcpp側のみを採用する。そういう意味でもよくない

  • (悩みどこ)初期化と解放もコピペの一種
    • 通常は、初期化の逆順で解放すると安全
    • ただ、一対一対応の場合は、これもコピペの一種といえる
    • なので、同じ順序に揃えた方がメンテナンスしやすい
      • 特に配列の場合、ループを逆順にするのはバグの元
    • 依存関係がないことが明確な場合は、同じ順序にしてもいいかも

  • enumの定義とそれを使うswitchもコピペ
    • 順番を揃える。一部の要素を省略しない
    • defaultでassert(false)して、それらを機械的にチェックする
      • 順番は無理だけど

  • インターフェイスの宣言とその実装もコピペ
    • できるだけ純粋仮想関数にして、同期を取るのを強制する
    • 関数コメントはインターフェイスの側にのみ書き、特に追加事項がないなら実装側には書かない

  • コピーコンストラクタ、代入演算子もコピペ
    • メンバ変数を追加・削除するたびに同期を取らないといけない
    • 極力コンパイラに生成させるか、Noncopyableにする
    • これらを量産しないといけない状況を見たら、そもそも設計を見直したほうがいい

統一感

既存のコードとの統一感は読みやすさに貢献する。なので重要。
ライブラリに手を入れるときなど、ポリシーを曲げても統一感を重視した方がいいときもある。

ただし乱用禁止。考えなしのコピペや、リファクタさぼりの言い訳にしない。

また、統一感という言葉でコーディング規約をいたずらに正当化しない。
統一するにはコストがかかる。そのコストに見合うリターンがあるのか、ちゃんと考える。
実益のない統一感を維持する必要はない。それは下手すれば悪しき慣習。積極的に見直すべき。

  • 守るべき統一感
    • 関数の引数で、func(src, dst)かfunc(dst, src)か
  • 守る価値がない統一感 (宗教論争にならない例募集)
    • if と ( の間にスペースを入れる/入れない
    • = が揃うようにスペースを入れる

拡張性

拡張性をほどよく備えたコードはメンテナンスしやすい。
拡張性を過剰に備えたコードはかえって冗長で難解になる。

なので、あとで高確率で拡張されそうな部分にのみ、最初から拡張性を持たせておく。
そうでない部分は今求められているものだけ実装し、拡張時はまるごとリファクタする。

例えば、具象クラスが1つしかないのにインターフェイスクラスを作らない。
2つになるのが目に見えているなら作ってもいい。
わからないなら今は作らず、2つになってから初めて作る。

抽象的な部分と具象的な部分のメリハリをつける、といってもいいかも。

汚れ仕事をする人を立てる

全体が美しい設計が理想。でも現実はどこかにしわ寄せが来る。

なので、しわ寄せを特定のクラスや名前空間に集め、ダーティな処理をそこに閉じ込める。
で、他の部分を美しく保つようにする。

例えば、ファイルのコンバータで、入力・出力ファイルのフォーマットは徹底的に綺麗に保つ。
そのためなら、コンバータの内部は汚くなってもいい。

同じことを1つのプログラムの中でもやる感じ。
肝心な部分のインターフェイスやデータ構造は汚さない。

同じノリで、雑多なユーティリティ関数の置き場所を用意するといい。
置き場所に困ったときに、とりあえずprivateメンバ関数に追加してしまうと、存在を忘れられて再利用されない。
最初の1個が出たときに、困ったらとりあえずここ、という場所を作る。
分類とか考えなくていい。溜まってきてから整理すればいい。

nullを伝播させない

nullを伝播させるとnullチェックのコードも増殖し、どんどん読みにくくなる。
また、nullならとりあえずreturn、みたいなコードが増えがち。
その結果、肝心なケースでassertせず動いてるフリをするプログラムができる。

  • nullならとりあえずこうする、みたいなコードは、バグを隠蔽する
  • nullを返すのはネットワークとかファイルI/Oとかfind()系のような、失敗が前提のメソッドのみにする
    • で、失敗ケースはすぐ上の層で処理し、できるだけ上の層にnullを伝播させない

設計

設計する前に、規模・労力を最小化する。
もっとも楽に実現するにはどうするかを先に考える。

問題を抽象化して、本質は何かを見極めて、単純な問題に帰結させる。
言語・ライブラリなど、利用できる資産はすべて利用する。

それでも「書かざるを得ない」コードを、どれだけ美しく書くかを考えるのが設計。

問題が複雑なまま設計を考えると、設計も複雑になってしまう。

設計は戦術。コーディングは戦い。なのでその前の戦略が大事。
とか言うとかっこいいかも。

シンプル

シンプルにすることに理由はいらない。複雑にするには理由が必要。

複雑にするということは、コストがかかるということ。
なぜ複雑にするか、それに見合うリターンはあるのか、説明できないといけない。

抽象化したり、拡張性を持たせるときには、それは必要な複雑さなのか意識する。

コードを消す

コードを消すことに喜びを覚えよう。例え自分が書いたコードでも。
同じ仕様を少ないコードで実現できるのはすばらしいこと。

assert

NULLチェック

ポインタのNULL assertは、単純に考えるとポインタアクセスすべてに対して書くことになる。
さすがにこれは非生産的。
それに、NULLなら確実にその場で落ちるので特にメリットもない。

なので、「問題を早い段階で見つける」ためにassertを書く。
できるだけ上層でassertして、下層にはNULLが渡らず、チェックが必要ない設計にする。
末端のコードまで行ってから落ちるより、早い段階で落ちた方が格段にデバッグしやすい。

具体的には、

  • 他モジュール・ライブラリとのインターフェイス部分
  • ファイルアクセス
  • 入力データのロード直後
  • コンストラクタ

など、「検疫」が必要なところでやっておくといい。