この記事を書くに至った経緯
筆者はこれまでオブジェクトというのは、連想配列で処理を書ける部分をわざわざ別なクラスで定義して本処理で行うという無駄な行為ではあるものの、過去の人の作法蔑ろにしないための儀式だと認識していました。
しかし、いろいろな経験を経てオブジェクトのありがたみがわかったためこの記事を書くこととしました。
※「下記のどっちがいい?」って効かれたら連想配列の方が余計なこと考えずに済みそうに見えませんか?
// 連想配列の使い方
testArray["greeting"] = 'hello';
echo testArray["greeting"] . 'world!' // hello world!
// オブジェクトの使い方
class testObject // ←こいつ作る意味あります?と思っていた
{
private string $greeting;
public function setGreeting(string $greeting): void
{
$this->greeting = $greeting;
}
public function getGreeting(): string
{
return $this->greeting = $greeting;
}
}
testObject = new testObject::class;
testObject->setGreeting('hello');
echo testObject->getGreeting() . 'world!' // hello world!
結論
さっそく結論ですが、なぜ概念が分かれているのかというと、楽に使えてほしい場合と多少面倒でも安全に使えてほしい場合があるからでした。
具体的な例で言うと連想配列には下記のようなデメリットがあり、そこを補えるのがオブジェクトという概念だったのです。
testArray["greeting"] = 'hello';
// 途中で配列以外の処理が挟まる
$GREETING_ARRAY=['good morning','hello','good night','bye'];
echo 'エッホエッホ';
testArray["greeting"] = 1; // ← 型を変えているためバグの温床になる
echo 'いい子は' . $GREETING_ARRAY[testArray["greeting"]] . 'everybody!';
echo 'エッホエッホ';
echo testArray["greeting"] . 'world!' // 1 world! // ← 本当は hello world! にしたかった
testArray["greting"] = 'hello'; // ← エラーの原因になっていると気付きにくい
echo testArray["greeting"] . 'world!' // Notice: Undefined index // ← エディタの静的解析が効かず、本番でエラーになっていると気づくことがままある
オブジェクトだとどのように防ぐことができるのか
先ほど述べた連想配列のデメリットを、オブジェクトを利用する下記のように防ぐことができます。
class testObject
{
private string $greeting;
public function setGreeting(string $greeting): void
{
$this->greeting = $greeting;
}
public function getGreeting(): string
{
return $this->greeting = $greeting;
}
}
testObject = new testObject::class;
testObject->setGreeting('hello');
// 途中で配列以外の処理が挟まる
$GREETING_ARRAY=['good morning','hello','good night','bye'];
echo 'エッホエッホ';
testObject->setGreeting(1); // ← エディタに`invalid argument error`の表示が出てこの使い方ができないことに気づける。呼び出した画面やコンソールでもエラーが出て間違いに気づける。
echo 'いい子は' . $GREETING_ARRAY[testObject->getGreeting();] . 'everybody!';
echo 'エッホエッホ';
echo testObject->getGreeting() . 'world!' // このままだとこの処理に到達しないが、hello world! が守られる
class testObject
{
private string $greeting;
public function setGreeting(string $greeting): void
{
$this->greeting = $greeting;
}
public function getGreeting(): string
{
return $this->greeting = $greeting;
}
}
testObject = new testObject::class;
testObject->setGreting('hello'); // ← エディタで赤波線がでてきて間違いに気づける
echo testObject->getGreeting() . 'world!' // エラーになるが、コーディング中に気づきやすくなる
このように、記述量は増えるし実装中は型の部分でよく怒られるので実装中はイラッとするのですが、実は壊れにくいシステムを作ることができるのがオブジェクトという概念なのです。
それぞれのメリット・デメリットの比較表
項目 | 連想配列 | オブジェクト |
---|---|---|
実装コスト | ◯ | ✕ |
型安全 | ✕ | ◯ |
エディタによる補完 | ✕ | ◯ |
補足
わざわざこんな小さい部分にこだわるのかと思われる方が多いかなと思うので、自分の経験を少し書かせていただきます。
今参画している現場でecho testArray["greeting"] . 'world!' // 1 world!
と同じ理由の現象(型違い)でエラーが起きました。それは大きい処理を実行するバッチで、暫定対応に1時間ほどかかるものでした。
そのためこのロジックが直るまでは毎日1時間取られるようなことが起きました。当日やるはずだった業務が間に合わなくなるのでもちろんそれらは残業対応でこなしました。
一見なんともないただのエラーなのですが、特にDB周りの処理を行う部分だとその後のエラー対応が死ぬほど大変になるので、小さいエラーを見過ごさないのも大事であることを知っていただけたらと思っています。
あと、バッチエラーが出ると(しょうがないことではありますが)責任者の方の機嫌が悪くなるのも結構辛くて、他の仕事が進めにくくなることもままありました。ともあれツケが回ってくるというのを身をもって知ることになるので、それが嫌な方はバグが起きないための細かい気配りとしてオブジェクトは有効であると知っておいていただけると良いかなと思います。
あと名誉のために言わせてもらうと、筆者が埋め込んだバグではないです(重要)
まとめ
- 連想配列は素早い実装に有効だけど、型とキー名ミスがバグの温床になる
- オブジェクトは実装がめんどくさいけどバグりにくい
- バグった時の対応工数はオブジェクト実装の100倍かかるから、長く使うならオブジェクトにしておいた方が良い