Tell, Don’t Ask は読みやすく、変更しやすいコードを書くための標語の一つです。DRY (同じコードを繰り返し書かない)、低結合(他モジュールへの依存を少なくする)、高凝集(関連のある情報をまとめる)といった原則を実現するするための足掛かりになります。
TellDontAsk
Tell, Don’t Ask
この標語に関する悪い例、良い例が次です。
// 悪い例 /** @var User $user */ if(isset($user->address)){ $streetName = $user->address; }else{ $streetName = '通り名がありません'; } class User { // 何か色々 }
// 良い例 $streetName = $user->streetName(); class User { public funtion getStreetName() { if(isset($user->address)){ $streetName = $user->address; }else{ $streetName = '通り名がありません'; } return $streetName; } // 何か色々 }
悪い例は User インスタンスの変数である $user のプロパティを元にその場で条件分岐を使って変数 $streetName を生成しています。良い例は $user の元クラスである User の中に $streetName の元になるメソッド getStreetName を持っており、それを用いて変数 $streetName を作っています。
Don’t Ask というのはオブジェクトに対してあれこれと聞いて手元でロジックを組み立てることをするな、Tell はオブジェクトに対してロジックの結果を聞け、ということです。
Tell, Don’t Ask に従うことによって同じロジックを複数回用いる時、毎回インスタンスからそのロジックの結果を聞くコードを書くことになります。例えば次の様になります。
public function showProfile(User $user) { $streetName = $user->streetName(); // 何か色々 } /** * @param array<User> $users */ public function dumpUsers(array $users) { foreach($users as $user){ $streetName = $user->streetName(); // 何か色々 } }
もし悪い例のまま↑の例を実現しようとした場合、別の場所に同じロジックが記述されることになります。こうなると変更漏れを起こしやすくなる他、コードが長くなって読みにくくなります。
Tell, Don’t Ask はあるロジックの分岐に関わる条件の元になるデータをどのクラスが持つかを判断する基準にも使えます。例は単に $user->address の有無での分岐でしたが、分岐の条件が複雑になった時は便利です。これが働くとあるクラスと他クラスの依存が減り、あるクラスに関わるべきデータが一か所に凝集されます。
Tell, Don’t Ask は原則の実現のための標語の一つ程度であり、原則のためにより良い方法がある時も少なくなく、破った方がお得な場合がわりとあります。とはいえ何んとなしに好き勝手コーディングするよりは整理されたコードになりやすいので頭の片隅にでも覚えておくと良さそうです。