継承はできるだけ避ける。名付けと凝集度「CODE COMPLETE」まとめ
2020年5月11日
🔖プログラミング🔖読書メモ
「神は細部に宿る」という言葉があるように、洗練されたコードは細部まで無駄がない。
無駄がないから誰にとっても読みやすいし、保守もしやすい。
洗練されたコードを書きたい人にとって、「CODE COMPLETE」は古いが色褪せないエッセンスに溢れている。
コードのある部分で作業しているときに無視しても問題のない部分をできるだけ増やすことが、優秀なプログラマになるための鍵である。
このエントリでは、「CODE COMPLETE」の6章「クラスの作成」と7章「高品質なルーチン」の内容をまとめる。
名付け
クラス
- 格納媒体に関する名付けをしてはならない
HogeFile
など- ファイルからメモリになるなど格納媒体が変わった場合に整合性が崩れる
- クラスの名前を動詞にしない
関数
- 関数が行うことをすべて説明する
- 副次効果があって名前が長くなることがある
- その場合は設計を検討する
- 意味のない動詞、曖昧な動詞、どっちつかずの動詞を使わない
- 関数名には戻り値の説明を反映させる
PrintDocument()
- オブジェクト指向言語の場合は、オブジェクト自体が呼び出しに含まれるため、オブジェクトの名前を関数名に含める必要はない
document.PrintDocument()
はかえって冗長document.Print()
で良い
- 正確な反意語を使用する
- add/remove
- increment/decrement
- open/close
- begin/end
- insert/delete
- show/hide
- create/destroy
- lock/unlock
- source/target
- first/last
- min/max
- start/stop
- next/previous
- up/down
- get/set
- old/new
包含
- 包含は「has a」関係
- 複数のクラスが共通のデータを持つが振る舞いを共有しないという場合は、これらのクラスに包含できるクラスを作成する
継承
- 継承は「is a」関係にのみ使用する
- 継承はプログラムの複雑さを増大させる危険なテクニックである
- リスコフの置換原則(LSP)に従う
リスコフの置換原則
派生クラスは、ユーザーが基底クラスとの違いに気付かずに、基底クラスのインターフェイスを通じて使用できるものでなければならない
- 派生クラスが1つしかないクラスを疑う
- 「いつか必要になるかもしれない」層の基底クラスは設計しない方が良い
- 深い継承ツリーを避ける
- 継承はプログラマの第一の責務である複雑さへの対処にマイナスに働く傾向がある
- 複雑さを抑えるには、継承に対して大きな偏見を抱くべきである
- 複数のクラスが共通の振る舞いを持つがデータを共有しないという場合は、共通の関数を定義する共通の基底クラスを継承する
抽象化
- うまく抽象化されたクラスインターフェイスは、一般に凝集度が強い
何が起きているのかを理解するために基本実装を調べなければならないとしたら、それは抽象化ではない。
- インスタンスが1つしかないクラスを疑う(Singletonパターンを除く)
- インスタンスが1つしか存在しないということは、オブジェクトとクラスを混同した設計かもしれない
- クラスではなくオブジェクトを生成するだけで済ませることができないか考える
クラス
- クラスを作成する最も重要な理由は、プログラムの複雑さを低減することである
- 情報を隠蔽して、その情報について考えないで済むようにしよう
- クラスを作成することで複雑になってしまわないように設計には細心の注意を払うこと
- クラスが呼び出すルーチンの種類をできるだけ少なくする
- 他クラスへの間接的なルーチン呼び出しをできるだけ少なくする
- 基本的にObjectAは自身のルーチンを呼び出す(デメテルの法則)
例
account.ContactPerson().DaytimeContactInfo().PhoneNumber()
account.ContactPerson()
までは良いが account.ContactPerson().DaytimeContactInfo()
は良くない
- 可能であればコンストラクタで全てのメンバデータを初期化する
- 「is a」関係をモデリングする場合を除き、通常は継承よりも包含の方が望ましい
関数
- 関数を作る最も重要な理由は、プログラムを頭で理解しやすくことである
- 関数の名前はその品質を表す
- 関数の目的は1つに絞る
- マジックナンバーを使用しない
- 引数の値を変更すべきではない
- 引数が多すぎない
- すべての引数を使用する
- 複数の似たような引数を使用する場合は、それらの順序を統一する
- 処理順序を隠蔽する
- 複雑な論理評価を単純にする
- 評価を関数として独立させると、コードの基本的な流れと評価そのものがすっきりする
凝集度
- 関数にとっての凝集度とは、関数の処理がどれだけ密に関連しているかを表す
Cosine()
という関数は凝集度が高いCoseineAndTa()
は凝集度が低い
機能的凝集度
- 一番良いとされる凝集度
- 関数が1つのことに関する処理を実行する
Cosine()
GetCustomerName()
EraseFile()
など
- これ以外の凝集度は弱く理想的ではない