まえがき
タイトルは、言語そのものの違いは得意げに人に指摘するくせに、自分自身はその言語の本質的な部分を何一つ理解できていない人々への皮肉となっています。
攻めたタイトルなので「ギクッ」や「ムカッ」となった方もいるかもしれませんが、そういう方こそどうかこの記事を読んでいたくと、きっと思い当たる節や発見があるかもしれません。
執筆のモチベーション
エンタープライズのシステムを開発していると、オブジェクト指向と手続き型などと言ったものの違いを理解せずに、脳死で手続き型プログラミングしかしていない人々が多いなーと感じました。
なので本記事もそういう人々への処方箋のような感じで書いてます。
(決して手続き型プログラミングそのものを悪とはしてはいません。)
よって、本記事で問題として取り上げるのはCみたいなJavaというより、手続き型みたいなオブジェクト指向プログラミング全般ということになります。
また、別にオブジェクト指向 is Justice!をモットーとしているわけでもなく、最近はDXの名のもとに業務アプリケーションではなく、事業そのもののデジタル化が叫ばれているおり、
エンタープライズの界隈でも複雑な業務ドメインをシステム(プログラム)で表現することも増えてきたと思います(少なくとも筆者は一から銀行を作りました。)
なので、業界の流れ的な意味でも今一度設計に関して考え直すキッカケの一つにもなってほしいなー…という側面もあります。
(設計界隈ではちょうどミノ駆動本も最近話題ですし)
手続き型プログラミングとオブジェクト指向プログラミング
本題に入る前に両者の違いを簡単に説明。
ぐぐればいくらでも情報が出てくるので詳細は省いて、本記事を読むにあたってそれぞれどのようなものとして扱っているかだけ書いておきます。
手続き型
プログラムを「選択と繰り返しの順次実行のみ」で表現する手法です。
業務ロジックをそのままプログラムに翻訳したような構成をとっており、基本的にはプログラムを上から脳死で読んでいても大体何をしているかはわかる。(GO TO的なものは一旦考えない)
設計書の内容をロジックに置き換える能力とプログラム言語の基本構文の理解さえ有していれば、開発の再現性が高いため工業的な面では優れていると言える。
オブジェクト指向
プログラムを「オブジェクトの振る舞いの組み合わせ」で表現する手法です。
クラスを責務と呼ばれるコンテキストで分け、その責務に基づいたプロパティと処理をもたせる。
カプセル化や継承(最近は移譲がベターだが)を駆使し、冗長性を排した再利用性やテスタビリティの高いプログラムを作り出せることから、常に変更を伴うような継続的な開発において真価を発揮する。
反面、手続き型と比べると言語特性の理解やアプリケーション設計のスキルが開発者に求められる。
手続き型みたいなオブジェクト指向プログラミングの症例
症例①: アプリケーションルールとビジネスルールが混在したクラス
具体例:MVCに忠実すぎる3層アーキテクチャやトランザクションスクリプトモデル
用語の定義:
- アプリケーションルール:そのアプリケーションの振る舞いを保証するルール(選択、順次、繰り返し…等の制御)
- ビジネスルール:アプリケーションの根幹となるビジネスの振る舞いや制約を保証するルール
カレーの例:
- 「火をつける」はアプリケーションルール(カレーを作るために必要なステップ)であるが、その火をどのようにつけるかは「点火」に関するビジネスルールである
- ガスコンロなのかIHなのか焚き火なのかは、アプリケーション目線ではないため「どのように火をつけるか?」はビジネスルールとして別責務のオブジェクトに隠蔽すべきである。
- 「野菜を準備する」も同様で、その野菜を切る必要があるのか?カット済の野菜を使用するのか?はアプリケーション目線では重要ではない。アプリケーション目線で担保すべきは「どの野菜を使用するのか?」と「その野菜がいつ必要になるのか?」である。
問題点:複数の機能で用いられるようなビジネスルールがプログラム内に存在している場合、そのビジネスルールが至る箇所に記述されてしまう。
障害や仕様の変更の際にそのビジネスルールに手が加えられるときに、漏れの原因になったりする。
また、そもそもロジックの中で業務上意味のある知識が埋もれてしまうことになり、開発者の業務知識のキャッチアップ難易度が上がる。
症例②: 限定的な機能(メソッド)を使用するためだけの責務を無視した依存関係
具体例:とあるServiceクラスのメソッドを利用するために、利用側のクラスが利用先のクラスを継承したり、インスタンスを介して機能を利用している。
筆者が本当に見たことのある例:
※ https://speakerdeck.com/iwaseshi/sohutoueawozuo-ruji-niyi-shi-sitehosiize-wu-toyi-cun-xing?slide=16 より抜粋
めちゃくちゃ責務過多なモジュールがあります。
このモジュールにはOTPに関するロジックを脳死で集約しており、様々なモジュールがこのモジュールに依存している。
また、このモジュール自身もユースケースを満たすために他のモジュールに依存している。
問題点:コードの可読性が低く、障害や仕様の変更の際の影響範囲の特定がめんどくさい。
またプログラムの構成として至るところに処理が散らばっていることから、暗黙の循環参照などを起こしやすかったり、クラスがただの処理のラベル付けのような扱いになってしまう。
GOTO文の悪夢の再来を招いてしまう。
症例に対する処方箋
上述の症例ですが、ぶっちゃけると原因は「開発者のスキル不足」です。
なので、これらの症状を起こしてしまう開発者に身につけてほしいスキルをそのまま処方箋として記述します。
処方箋①: 業務や事業を理解しプログラムのコンテキストを理解する。
アプリケーションは「そのアプリケーションが担う機能」と「アプリケーションが実現するビジネス知識」で成り立っています。
自分が実装しようとしているロジックは上記のうちどちらに分けられるのか?どんなモノ(オブジェクト)が実現すべきなのか?を常に意識することが重要です。
よく手段としてドメイン駆動設計が挙げられます。
症例を例に具体案を出すと、「野菜を準備する」や「火をつける」といった手順を定められた順序で呼び出すアプリケーションルールを担うクラス(CookCurryRiceInteractorとかですかね)と「野菜の準備」に関連したビジネスルール処理を担うクラスに分割します。「点火」も同様です。
また、ビジネスルールで取り扱う処理もそのビジネスで扱うオブジェクト特有の振る舞いやそのオブジェクトにまつわる処理に関しては、そのオブジェクトに閉じ込めたりすると、さらによくなります。
処方箋②:プログラムの依存関係を明確にする。
依存関係の方向性を一方方向にするだけで、プログラムの可読性やメンテナンス性は格段に向上します。
またこれはプログラムに限った話ではなく、マイクロサービスやジョブネットといったアーキテクチャを考える中でも重要です。
症例のようにアプリケーションアーキテクチャの工夫で改善できる場合、よく手段としてクリーンアーキテクチャが挙げられます。
ググれば専門の記事があふれるほど出てくるので詳細は省きますが、
軽く図の補足説明をすると、アプリケーションが持つビジネス知識(ドメイン知識)を最下層に、実行基盤などのアプリケーション外の要素を最上層に考え、依存方向を外から内への一方向になるようにする方法です。
こうすることで、各モジュールの責務が明確になったりアプリケーションの作りが複雑になりにくくなります。
おわりに
今回書いた処方箋はぶっちゃけただの心持ちです。
具体的なテクニックは設計界隈の大御所の方々が体系化してくださっていますが、
やはり「なんでそんなことせないかんの?」や、「そもそもどういう意識を持たないといけないの?」といった感覚があると、悪習に気づかなかったり改善しようとする意欲も沸かないと思います。
なので、この記事がそのへんの入口になればいいなーと思ってます。
(メタ的なことかくと、実際に筆者が質問されたので記事に書き残しておいた感じです。)
ここまで読んでくださりありがとうございました。
最後にLGTMや拡散をしていただけると筆者の励みになりますので、心に余裕がある方はよろしくおねがいします。
また、Twitterもやっているので良かったらフォローお願いします。