概要
最近Code Completeを読んで、そうそうコレだよ。コレを言って欲しかったんだと触発されて記事を書いてみました。メンバはprivateにすべき等の基本的なことには触れませんのでクラスが書ける、継承が使えるくらいの人が対象です。
継承を使う事で自体は悪化する
継承は、プログラマの第一の責務である複雑さへの対処にマイナスに働く傾向がある
上記はもっと知られて良いと思います。継承に関係した守るべき暗黙のルールは山のようにありますが、それを全部理解した上で利用を躊躇するのが正しい判断だと思います。
とりあえず初心者は継承を使いがちです。C++やるんなら継承でしょって感じで使います。駄目です。継承はそんな気軽なものではありません。
どうしても継承するのなら設計し、文書化する
Code Completeには設計の話も出てきます。詳しく知りたい人は読んでみてください。
設計はコーディングとは別の所にあります。コーディングしながら、そうだBaseクラスを作ろうなんてのは本当にやめてください。
ある派生クラスにこういう機能を付けたいから基底クラスを拡張するなんて行き当たりばったりで手を出して良いものではないのです。
責務を考えた時に正しい抽象化であり、その継承により十分な利益が出ると事前に判明した場合は仕方ありません。継承でしか実現できない強力な動作は確かに存在するからです。
それでも十分な設計に甘えず、第三者が見ても十分に理解できるような文書も作成しましょう。
そこまでのコストを払うだけの価値がないのなら、その継承は使うべきではありません。
実装が手間になっても読みやすいコードを書く
1引数を受け取り2変数を返す関数が必要になった時以下のようなコードを書きがちです。
void Expand(int value, int& begin, int& end) {
begin = value - 1;
end = value + 1;
}
上記の駄目な実装に行き着く理由
- 新しいクラスの宣言、命名が面倒
プログラミングで一番大事な部分なので頑張りましょう。 - プリミティブ型以外は戻り値としては不適。コピーコストが気になる
C++に慣れて来た人にありがち。
そんな微小なコストが気になる環境なら定期的に速度テスト回してボトルネックを検出できるようにしているだろうから、そのコピーコストが致命的と判明してから修正しましょう。
基本的にコピーコストよりも可読性が失われることによるコストの方が会社にダメージを与えます。
struct Range {
int begin;
int end;
};
Range Expand(int value) {
Range range;
range.begin = value - 1;
range.end = value + 1;
return range;
};
上記の形式が良いのではなく、可読性が高いコードが良いのです。
なので、下記のようなコードは駄目です。
Range Expand(int value) {
return { value - 1, value + 1 };
}
スタイリッシュで一見良さそうですが、こんなコードを書かれたらRangeの定義まで見に行かないと落ち着きません。
既存のprotectedメンバを利用する必要がある時はprivate継承を使う
覚書。この程度で酷いクラスに対する蓋になる気がしませんが、まだ試せていないので有効か判断できてません。
メンバの数は7プラスマイナス2個
他の作業を行いながら人が覚えられる項目は、だいたい「7プラスマイナス2」個であることがわかっている(Miller 1956)
根拠を付けてくれると説得しやすくて良いですね。無限にメンバを増やしていくのはやめましょう。
ディープコピー優先
確かに、大きなオブジェクトのコピーをいくつも作成するのは、見た目が良くないかもしれないが、パフォーマンスに無視できないほどの影響を及ぼすことはめったにない。
パフォーマンスが改善するという確証がないのに複雑さを増大させることは、良い打開策であるとは言えない。
ディープコピーとシャローコピーを選択する良い方法は、シャローコピーを使用すべき根拠が明確になるまでは、ディープコピーを使用することだ。
根拠なしに可読性を捨てる人があまりに多く感じています。ただ、
void DoSomething(const std::vector<int>& values){ ... }
のようなconst参照は可読性に悪影響を与えないので積極的に使った方が良いです。
おわりに
まだまだ読んでる途中ですが、非常に共感したので記事をサクッと書いてしまいました。
C++はゼロコスト抽象化をメインコンセプトの1つとしている言語なのでコピーコスト等を少しでも減らしたい気持ちは良く分かります。
しかし、そのためにコードを読むコストを増大させたのでは本末転倒です。早過ぎる最適化を避け、可読性を優先したコードを書きましょう。