オブジェクト指向よくわからんと思っている方達も「ThoughtWorksアンソロジー」の第5章「オブジェクト指向エクササイズ」にて紹介されている9つのルールを取り入れることで、こいつオブジェクト指向が分かっている!!と思われるようなコードが書ける。はず。
今まで手続き型で実装しオブジェクト指向に慣れていない方にとってはとても異様で非現実的なルールと思われるでしょう。
しかしオブジェクト指向で実装する場合はどれも当たり前に使われるルールとなります。
すぐにでも守ることのできる簡単なルールもあるので是非、今すぐ取り入れてオブジェクト指向完全理解したったって周りにそれっぽい顔していきましょう。
※ サンプルコードはPHPで書いてます。
- 1つのメソッドにつきインデントは1段階までにすること
- else 句を使用しないこと
- すべてのプリミティブ型と文字列型をラップすること
- 1行につきドットは1つまでにすること
- 名前を省略しないこと
- すべてのエンティティを小さくすること
- 1つのクラスにつきインスタンス変数は2つまでにすること
- ファーストクラスコレクションを使用すること
- Getter, Setter, プロパティを使用しないこと
1つのメソッドにつきインデントは1段階までにすること
インデントは一つの処理単位となる。
それ以上深くなる場合は、メソッドやクラスに抽出しよう。
else 句を使用しないこと
ガード節や早期リターンを使いelse句を無くす。
と、あるが else if は使用しないとして else は処理によっては使用しても良いかなと思う。
逆に else(ではない時) が分離されることで可読性が落ちてしまうこともあるなーと個人的には思う。
すべてのプリミティブ型と文字列型をラップすること
その値専用のクラスを作ろう。DDD的に言うと「値オブジェクト」
その値に関わる判断/加工/計算するロジックをそのクラスに置いてあげることでコードの重複を防ぐことや変更の影響範囲を閉じ込めることができる。
NG
class User {
/** @var String */
private $email;
/** @var String */
private $password;
// EmailやPasswordに関わる判断/加工/計算メソッドをここに持つ
}
OK
class User {
/** @var Email */
private $email;
/** @var Password */
private $password;
}
class Email {
/** @var String */
private $email;
// Emailに関わる判断/加工/計算メソッドをここに持つ
}
class Password {
/** @var String */
private $password;
// passwordに関わる判断/加工/計算メソッドをここに持つ
}
小さな小さなクラスが沢山できていく。
1行につきドットは1つまでにすること
ドットは、phpで言うとアロー演算子(->)になる。
オブジェクトを返すメソッドを呼び出し、その返却されたオブジェクトのメソッドをそのまま呼び出すコードなどドットが繋がっている場合、オブジェクトが他のオブジェクトを深く掘り進んでいる事になる。つまりこれはカプセル化に違反している。
ドット一つごとに変数に格納し一行になっているのを別の行に分けていく。
名前を省略しないこと
プログラミングの習慣として、数量(Quantity)をqやqtyなどと省略することがある。
人によって解釈は異なる為、ソフトウェアをわかりやすく保つには省略をせず明確な単語を使う。
ただ、まぁ習慣として一般的なものであればそれは使っても良いだろうと個人的に思う。
すべてのエンティティを小さくすること
オブジェクト指向は短いメソッドと小さなクラスでプログラムを組み立てる技術。
大きくなったメソッドやクラスなどは小さく分解する。
適切な名前で小さいクラスを作ることでどこに何の処理が書いているかが分かり変更もしやすくなる。
エンティティを小さく保つガイドライン
ここら辺を守ると良さそう
要素が30を超えるサブ要素で構成されている場合、重大な問題がある可能性が高くなります。
a)メソッドのコード行は平均30行を超えてはなりません(行スペースとコメントはカウントされません)。
b)クラスには平均30未満のメソッドが含まれている必要があり、結果として最大900行のコードになります。
c)パッケージには30を超えるクラスを含めることはできません。したがって、最大27,000のコード行で構成されます。
d)パッケージが30を超えるサブシステムは避けてください。このようなサブシステムは、最大810,000行のコードで最大900クラスをカウントします。
e)したがって、30のサブシステムを持つシステムは、27,000のクラスと2,430万のコード行を所有します。
1つのクラスにつきインスタンス変数は2つまでにすること
インスタンス変数が増えると、クラスの意図がだんだんぼやけてくる。
その結果、複数の目的に使われはじめ、巨大なクラスとなってしまう。
ファーストクラスコレクションを使用すること
配列やコレクション(ListやMap等)の操作は、コードが複雑になりがち。
その配列やコレクションをインスタンス変数に持つクラスとして抜き出す。
NG
class Profile {
/** @var String */
private $fullName;
/** @var array */
private $phoneNumbers;
}
OK
class Profile {
/** @var String */
private $fullName;
/** @var PhoneNumbers */
private $phoneNumbers;
}
class PhoneNumbers {
/** @var array */
private $phoneNumbers;
}
Getter, Setter, プロパティを使用しないこと
データクラスは、ただの入れ物ではなく何らかの判断/加工/計算を行うメソッドを持つ為、インスタンス変数をそのまま返すだけの getter は不要。
setter を用意すると自分以外の実装者または3ヶ月後の自分が別の処理で値を書き換えてしまう可能性があり、バグを生み出す原因となる。生成の時にインスタンス変数に詰める(完全コンストラクタパターン)などの方法を取り不変にすると良い。
まとめ
あくまでエクササイズなのでこれを絶対のルールにするという訳では無い。
一度エクササイズをしたら、緩めてガイドラインとして使うと良い。
参考
「ThoughtWorksアンソロジー ―アジャイルとオブジェクト指向によるソフトウェアイノベーション」「第5章 オブジェクト指向エクササイズ」
「現場で役立つシステム設計の原則: 変更を楽で安全にするオブジェクト指向の実践技法」「第10章 オブジェクト指向設計の学び方と教え方」