Immutableについて
昨日チーム内でImmutableについての話をしたので今日はその辺りを書きたいと思います。
Immutableとは
Immutableとは一般的に不変と訳され、状態変更ができないことを意味します。
逆に状態変更ができることをMutable(可変)と言います。
Mutableなクラスだけで実装するとクラスの外部からでもクラス内の変数を書き換えることが可能になります。
簡単に書き換えられてしまうと思わぬところで値が変わっていて想定と異なる挙動をすることに繋がり、バグの温床となります。
1つ悪い例を挙げてみます。
class Car { public Body $body; public function __construct(Body $body) { $this->body = $body; } } class Body { public $color = 'red'; }
上記のようなCar
クラスとBody
クラスがあるとします。
$body = new Body(); $myCar = new Car($body); echo $myCar->body->color . PHP_EOL;
新しく$myCar
というインスタンスを生成します。
これを実行するとBody
クラスをデフォルトのまま変えていないので、
red
という文字列が出力されます。
ここでもう1つインスタンスを生成します。
$yourCar = new Car($body); $yourCar->body->color = 'blue';
もう1つインスタンスはボディの色を青にします。
ここで
echo $myCar->body->color . PHP_EOL;
とすると、
blue
と出力されてしまいます。
$yourCar
の色を変えたつもりが$myCar
まで色が変わってしまいました。
修正すべき点
同じ$body
を使いまわしていることも原因の1つですが、そもそもCar
もBody
もMutableなクラスです。
どちらもプロパティがpublic
になっている為、外部からアクセス可能になっています。
その為、上記のコードのように後から変更することが可能になってしまいます。
これを防ぐにはプロパティにはコンストラクタで値を入れ、後から変更ができないようにすることです。
例えば、
class Body { private string $color; public function __construct(string $color) { $this->color = $color; } }
このようにプロパティをprivate
にし、外部からアクセスできないようにした上でコンストラクタで値を入れる。
一旦値を入れたらそのインスタンスは後から変更することができません。
こうすることで不変性を担保することができ、思わぬバグが生まれにくくなります。
同様にCar
クラスも$body
プロパティをprivate
にすることで後から変更できないようにします。
class Car { private Body $body; public function __construct(Body $body) { $this->body = $body; } }
また、Car
インスタンスの生成にFactoryパターンを使うとよりベターです。
class CarFactory { public function createNewCar(string $color = 'red'): Car { return new Car(new Body($color)); } }
こうすることでBody
インスタンスの使い回しも防ぐことができ、より堅牢なコードにすることができます。
まとめ
以上簡単ではありますが、Immutableについて書いてみました。
プログラミングの世界ではImmutableがデファクトスタンダードになってきているように感じます。
JavaScriptでも変数宣言の際、以前はvar
しかありませんでしたが、ES6以降はconst
やlet
を使うことがもはや常識となっています。
他にもRustではImmutableがデフォルトになっているようです(触ったことは無いですが)。
バグを生まないためにも、Immutableなクラスを作ることを意識して実装することが重要だと思います。
Mutableな構造、つまり再代入や変更が可能な構造になっている場合にはImmutableな構造にできないか再検討した方が良いように思います。