「同じ処理を何度も書いている気がする・・・」というのは、ソースコードの見通しが悪くなってくる前兆ですよね。
同じ処理を何度も書くことの問題
同じ処理を何度も書くというのは、2つの理由で問題があります。
- 単純な繰り返し作業なので、時間の無駄
- 変更の際に、重複のすべてを漏れなく変更しないといけない
厄介なのは、2つ目です。
2〜3行くらいの処理でも、いろいろなところにコピーされてしまうと、「どれか1つだけに書き間違いがある」なんてことが発生します。
ソースコードの見通しを良くするテクニック
この本を読みました。
現場で役立つシステム設計の原則 ~変更を楽で安全にするオブジェクト指向の実践技法
オブジェクト指向をうまく使って良い設計をするテクニックがたくさん書かれています。
その中でも「データとロジックを一体にする」というのが、良い考え方だなと思いました。
データとロジックが別れている状態とは?
その前にデータとロジックが別れている状態を考えてみます。
例として、商品の価格と個数を指定して、合計金額を出す、という処理を考えます。
価格と個数をもつ購入クラスを次のように定義します。これはデータベースから取得されるモデルクラスだと思ってもよいです。
class Purchase { public $price; public $amount; public function __construct($price, $amount){ $this->price = $price; $this->amount = $amount; } }
次にこの購入データを使って、合計金額を表示します。
$purchase = new Purchase(100, 5); $totalPrice = $purchase->price * $purchase->amount * 1.08; echo "total price: ${totalPrice}\n";
合計金額は、価格に個数をかけて、消費税をかけると計算できます。
この例のうち、$priceと$amountがデータで、「$price * $amount * 1.08」という計算がロジックです。
データとロジックの分離の問題
このようなソースコードは良くあると思いますが、問題はロジックがいろいろなところに散らばることです。
別の場所でも合計金額が必要になったら、その場でまた同じロジックが書かれます。
たとえば、複数の購入の合計金額を計算したくなった場合に、また同じロジックが記述されることになります。
$totalPrice = 0; foreach($purchases as $purchase) { $totalPrice += $purchase->price * $purchase->amount * 1.08; } echo "total price: ${totalPrice}\n";
最悪なのは、ロジックを変更しなければならなくなった時です。
たとえば消費税が上がって10%になった場合、このロジックを書いたすべての場所を、漏れなく変更しなければなりません。
ロジックの重複をなくすためのメソッド抽出
こういう場合、よくある対策は、重複する処理を抽出することです。
function totalPrice($purchase) { return $purchase->price * $purchase->amount * 1.08; }
こうすれば、ロジックが1箇所にまとまるので、何度も同じ処理を書く必要はなくなります。
$purchase = new Purchase(100, 5); $totalPrice = totalPrice($purchase); echo "total price: ${totalPrice}\n";
消費税が変更されても、変更しなければならないのは、totalPrice関数のみです。
データとロジックを一体にする
これで良くなったように思いますが、もしtotalPrice関数の存在を知らないプログラマがいたら、どうでしょうか?また同じロジックを書いてしまうかもしれません。
となると、正しいプログラムを書くために、全ての関数を理解している必要がありますが、それは無理があります。
そこで、データとロジックを一体にする、という話になります。
class Purchase { private $price; private $amount; public function __construct($price, $amount) { $this->price = $price; $this->amount = $amount; } public function totalPrice() { return $this->price * $this->amount * 1.08; } }
クラスにはメソッドが書けるので、そこにロジックを入れれば良いです。
$purchase = new Purchase(100, 5); $totalPrice = $purchase->totalPrice(); echo "total price: ${totalPrice}\n";
この場合、Purchaseクラスを使うプログラマは、全ての関数を知る必要はありません。Purchaseクラスのメソッドだけを知っていれば十分になります。
オブジェクト指向ってこういうことか!
データとロジックを一体にすることで、オブジェクト指向らしいソースコードになったと思います。
クラスを書いていれば何でもオブジェクト指向みたいに思っていた頃もありましたが、データにふるまい(ロジック)を組み合わせられるのが、オブジェクト指向の良さだなと、最近は思うようになりました。
他にもいろいろなテクニックが書かれているので、おすすめです。
現場で役立つシステム設計の原則 ~変更を楽で安全にするオブジェクト指向の実践技法