2023-07-15

抽象に依存しなくても良くない?

抽象に依存するとかインターフェースを使うとか、そういった話や現実の実装を見てきて、そのメリットもそれなりに理解しているものの、実はそんなに必要ないんじゃないかと思っていたりする。Clean Architectureっぽいコードや過剰に設計を意識してそうなコードを読むとインターフェースを多用していることが多い。抽象化に加えて多階層だったりするとより読みづらいし書きづらい。

抽象化することによる可読性と保守性の問題

実装を読むときに抽象型が引数だったりすると、IDEの定義ジャンプのときに抽象型の定義に飛んでしまったりする。IDEや設定によってはその抽象型を実装する具象型に飛ぶこともできるけど、抽象型の定義に飛ぶのか具象型の定義に飛ぶのかという選択が必要となる。読むだけではなく書くときもやることが増える。新しくメソッドを定義したりメソッドの引数を変更する場合は抽象型と具象型の両方で対応する必要がある。変更に強くなるはずの抽象化をすることで、変更に弱くなっている…?

ポリモーフィズムのようにビジネスロジック的に抽象型が必要な場合は、利用する具象型が一意に決まらないので抽象に依存する他ない。ただ、if文/switch文の方が逆に読みやすいケースもあるので、可読性や保守性を考えて抽象化を適用した方が良さそうではある。

将来的にビジネスロジック的に必要になるかもしれないから抽象化しているという言い分もありそうだが、そのときにリファクタリングすれば良いじゃん、そのためにテストを書いておけば良いじゃん、と思ったりする。「変更のためにコードを書き換える必要がない」ではなく「変更のためにコードを書き換えても安全」という方が現実的かなという感じ。

テストと抽象化

ビジネスロジックではなくテストだけのモチベーションで抽象化したい場合は、テストに必要な最低限のコードをモックできるよう抽象化すれば良い。ただ、基本的にモックはあまりしない方がよいと思っていて、DBなどCIで準備しやすいものは現物を使ってテストをした方がリグレッションテストとしても望ましい。テストの速度は当然遅くなり、肥大化すれば開発者体験を損なう可能性もあるが、本物のテストをするためには必要なコストとも思うし、肥大化したときは並列化など対応策がある。

ここで言っている抽象化すべき最低限のコードというのは、外部APIの呼び出しなどコントロールできないコンポーネントのことである。これらはローカル開発環境やCI環境で準備できないが、抽象化することでモックを使ってテストができるようになる。この場合も、むやみに抽象化すると認知負荷を上げる要因となるため、テスタビリティを意識して必要な要素だけ抽象化する方が良い。

余談: PHPはテストがしやすい

ただし、PHPだとその言語特性が故に抽象型を使わなくてもモックができてしまう。PHPUnitのモックや、streamWrapper を使った PHP-VCR など、動的にクラスを定義したりコードを書き換える黒魔術によりテストが困難なアプリケーションであっても後付けでテストできることが多い。つまり外部API呼び出しなど一般的には抽象化しないとテストができないものに関しても、PHPでは抽象化する必要無くテストができる。

逆に静的型付け言語の場合は抽象型を使わないとモックができない場合が多い。PHPの場合はそうであるだけの話で、それがPHPのメリットでもある。モックをする箇所は外部APIなど大抵I/Oの処理なので、PHPはI/Oがあるアプリケーションにおいてテストがしやすいということになる。Easyな思想でテストがしやすい、それでありながら型付けができる柔軟性の高い言語、PHP。

理想と現実

自分は現実主義なので、理想は理想で理解しようと試みつつ現実の課題を最大限解いていきたい。技術はそれ自体のメリットだけではなく学習コスト・認知負荷・サービスやチームの状況も考えて導入する必要がある。抽象化も当たり前のようにやる技術というよりは、このあたり考えて導入した方がよい技術なのではないかと思ったり思わなかったり

このエントリーをはてなブックマークに追加