Shin x Blog

PHPをメインにWebシステムを開発してます。Webシステム開発チームの技術サポートも行っています。

ソフトウェア設計原則は変更容易性に通ず

ソフトウェア設計、開発には多くの原則や方法論がある。例えば、DRY 原則や SOLID 原則、デザインパターンにレイヤードアーキテクチャ、クリーンアーキテクチャなどある。さらに DDD にも多くの原則や方法論が含まれている。これらを変更容易性を高めるための手段として原則や方法論を捉えるというのが本エントリの論旨である。

原則や方法論の捉え方

原則や方法論は我々開発者によってとても有用なものではある。しかし、こうしたものに対する態度、思い入れというのは人それぞれだ。原則に傾倒しすぎて、教条的になり、それを遵守することが目的化している場面もまま見受けられる。さらに、それを絶対視するがあまり、適用していない人やシステムに大して攻撃的になったり、さらにそうした場面を見た人が引け目を感じてしまうこともある。一方、全くこういったことに関心が無く、ただ面倒なもの、小うるさい人が言っているものという捉え方とする人もいるだろう。

私は、原則というのものはこれさえ適用すれば上手くいくといった絶対的なものとは考えていないので、学びつつ良いと思えるところは利用するというスタンスを取っているつもりだ。*1しかし、人から見れば教条的に振る舞っている面もあるかもしれないし、反対に軽んじてるようにも見えるかもしれない。

いずれにしても大切なのが、何を目的に原則を利用するのかという点である。学習目的以外では、原則にいかに従うかということをゴールに開発するということはない。また原則にどこまで忠実に従っているかということを客観的に計るのも難しい。多くの場合、原則を利用する上のは何か目的があり、その手段として原則を用いるというのが通常の考え方だろう。この目的が共有できていれば、原則に対するスタンスが異なっても、それを達成するための手段として効果的かどうかという尺度で捉えられる。

変更容易性

完ぺきに動作するが、変更できないプログラムを与えられたとする。要件が変更されると機能しなくなる。修正することもできない。したがって、このプログラムはいずれ役に立たなくなる。

動作しないが、変更が簡単なプログラムを与えられたとする。要件が変更されても修正は可能なので、動かし続けることができる。したがって、このプログラムはこれからも引き続き役に立つ。

Clean Architecture 達人に学ぶソフトウェアの構造と設計

このエントリでは、仕様変更による修正や機能追加はもちろんのこと、技術的なコード変更、さらにバグ修正といったものも含めて「変更」とする。この変更はシステムを運用して価値を提供して続ける上で避けて通れない。運用していく中で必ず変更は発生する。*2 そして運用を続けていくと変更に変更を重ねていくことになる。機能が増え、コードベースが大きくなり、関わる開発者も増えた結果、コードは複雑になっていき、変更容易性は低下していく。場合によっては、どこを変更すれば良いかを把握するのも大変で、実際変更してみると予期しないところが壊れるなど、まさにもぐらたたきゲームのような状態に陥る。さらに変更は運用しているシステムだけに限らない。初期リリース前の開発時も変更は日常的に発生する。これは朝令暮改のような要求がコロコロ変わるという状態ではなく、試行錯誤を繰り返しながらより精度を上げていくような開発スタイルであれば変更が発生することは良くあることだ。

このように変更は避けざるものであり、さらに無秩序に変更を続けていくと複雑度が増し、変更するのがより困難になっていく。こうした特性があることを前提にすると、変更容易性はシステムを開発運用していく上で重要な要素となる。変更容易性が低いシステムを変更した経験を持つ人にとっては(実感を伴って)その重要性は理解できるだろう。痛みを知っているがゆえ、改善したいまたは同じ轍を踏むまいと、原則や方法論にたどり着くというケースもあるのではないだろうか。変更容易性を改善するという目的から原則を利用するというのは分かりやすいストーリーだ。ただ、痛みを知るがゆえにそれを克服する特効薬として原則に過大な期待を持ってしまう場面もあるのだが。

一方、こうした経験が無い人の中には、変更容易性は余計なもの、面倒なもの、過剰なものとして捉えるケースがある。これは致し方ない面もある。私自身も書籍などで見聞きしていたものが、実体験を伴った(痛みを経験した)ことでその重要性に気付いたという経験が何度もある。こうした変更容易性の重要性が分からない状態で原則を駆使すると、ともすれば形だけ適用してピントを外したものにもなりかねない。例えば、クリーンアーキテクチャを採用したのに、ただ手数が増えるばかりで効果が実感できないというようなことが起こり得る。

本質的な変更と副次的な変更

システムの変更は、本質的な変更と副次的な変更の 2 つに分けられる。本質的な変更は、ビジネス要求の変更や追加要望などシステムドメインに関する変更である。これはシステムの存在意義に関するものなので、変更を行うのは当然のことだろう。一方、副次的な変更は、プログラミング言語やランタイム、フレームワークやライブラリのバージョンアップなどの技術的な要素に起因する変更である。こちらはシステムを運用していく上で必要だが、多くの場合対応してもユーザにもたらす価値には寄与しない。しかし価値を低減させないように必要な変更でもある。

本質的な変更に対する容易性を向上させるのも大切だが、特に効果が大きいと考えるのが副次的な変更に対する容易性の確保だ。本来システムドメインとしては不要なものであるだけにここに大きな手間を要することは避けたい。良く知られる方法としては、本質的な変更のみを受けるレイヤと副次的な変更を受けるレイヤに分離する方法だ。レイヤードアーキテクチャやクリーンアーキテクチャなどレイヤを分離するアーキテクチャパターンは多くあるが、いずれもドメインレイヤのように本質的な変更のみ行うものと副次的な変更を受けるレイヤとに分離し、その依存方向をコントロールすることで副次的な変更がドメインレイヤに波及しないようにしている。独立したコアレイヤパターンはまさにこれを狙ったパターンでもある。コアレイヤは本質的な変更のみで副次的な変更はアプリケーションレイヤのみで受けるようにしている。こうしたパターンを採用した際は、副次的な変更がドメインレイヤやコアレイヤといった本質的な変更しか受けないレイヤに波及しないかは一つのチェックポイントとして見ておくと良いだろう。

副次的な変更に影響を受ける箇所は限定的にしておくことで、その影響範囲を小さくできる。これはレイヤ分けしなくてもクラス設計の工夫で十分に対応できる。例えば、外部ライブラリをアプリケーション全体で広く使うのではなく、それを内包したクラスを実装し、アプリケーションではその内包したクラスを利用するという方法だ。これであれば、もし外部ライブラリの変更があっても、その影響は内包したクラスに限定できる。この時の注意点としては、内包したクラスのクライアントは、内部で外部ライブラリのインスタンスを持っているということを前提にしてはいけないということだ。これは後述する情報隠蔽にも通じる話だが、あくまで内包したクラスが公開している振る舞いにのみ依存して利用するようにしたい。

外部変更容易性と内部変更容易性

変更容易性には、外部変更容易性と内部変更容易性が考えられる。外部変更容易性は、クラスやモジュールの組み合わせを変えることで変更しやすくする特性である。内部変更容易性は、クラスやモジュール内部の実装を変更しやすくする特性である。

外部変更容易性を向上させるには、クラスの組み合わせを変えられるポイントを作っておき、状況に応じてそこに必要なクラスを差し替えられるようにしておく。例えば、DI(依存性注入)やOCP(オープンクローズド原則)、さらに GoF のデザインパターンにこういった例がある。この方法は有効である場面はある一方、方法によってはあらかじめ変更できる箇所を予測して実装しておく必要があり、構成としてもやや複雑になるという面がある。

一方、内部変更容易性を向上させるには、クラスやモジュールの内部実装を変更しやすい状態にしておく。有効なもの一つに情報隠蔽がある。情報隠蔽によって、クラス内部の構造を隠し、必要最低限のインターフェイス(メソッド)のみを公開するようにしておく。公開しているメソッドの動きが変わらなければ、クラス内部のコードをいかように変更しても良い。これはつまり、公開しているメソッドを変更するには、その動作が変わっていないことを確認しないとならないということだ。公開メソッドは最小限にして内部構造を変更しやすい状態に保っておくと良い。なお、プロパティを private にしたところで、public な getter や setter で外部から操作できれば内部実装が漏れることになり、変更容易性を下げる原因となりうる。利用されている箇所が限定されているコードは変更の影響範囲も把握しやすいため、変更が容易になる。いかにコードの利用範囲を限定するかというのは一つの指標になるだろう。*3

変更容易性と聞くと外部変更容易性の方を思い浮かべることが多いように感じる。このため、変更容易性のために特別な実装が必要と考えて敬遠されるケースがあるように思う。しかし、情報隠蔽のように多くの場面で適用でき、かつ特別な仕組みが不要な方法もある。日頃から意識の持ち方を変えるだけで変更しやすい実装にする方法があるのは知っておくと良い。

原則を適用する指針

「Easier To Change」(変更をしやすくする)。これがETC原則です。 (略) 我々が知る限り、この世の中のあらゆる設計原則はETC原則を特殊化したものとなっています。

達人プログラマー(第2版)

達人プログラマーにあるように変更容易性は原則や方法論を包括するものとも言える。これを利用して、原則や方法論を適用する指針にできるのではと考える。

例えば、設計や実装を理解しやすいものにするというのは誰もが知っていることだろう。リーダブルコードを読み、日々理解しやすいコードを書くのに心を砕いている人も多いと思う。しかし、どういった状態が理解しやすいものかというのは読む人にも依存する。そこで変更容易性を一つの指針にするというのはどうか。理解しないといけないのは、変更する必要があるからであり、そうであれば変更しやすいように理解できれば良いということになる。では、変更しやすいような理解というのはどこまでかというのはまた別の問題ではあるのだが、ただ理解するという広い目的より、範囲を限定することができるかもしれない。

他にも凝集度の強度を計る時に機能的凝集であれば最善だが、それが難しい場合にどの凝集度を選択するかといった際にもこの凝集度だから良いということではなく、変更容易性に寄与するものを選択するということも考えられる。

さいごに

ソフトウェア設計の原則や方法論を変更容易性を確保するための手段として捉えてみた。文中でも触れたとおり、原則や方法論を学び、活用していくことは我々開発者に多くのメリットをもたらす。これを上手く活用していくには、なぜそれが必要なのかという視点が重要だ。システムにおいて変更は不可避のものであれば、変更容易性を高めるために原則を利用するというのは的を得ているのではないだろうか。

これまで原則や方法論が変更容易性に寄与するというのは理解していたつもりだが、今回あらためて捉え直すことで腑に落ちる部分が多かった。本エントリを読んだ方も知識としては認識されている人が多いと思うが、ぜひ一度変更容易性の観点で原則を捉え直すという思考を試してもらいたい。

*1:ただ、このスタンスは一見バランスが取れているように見えても良し悪しがあり、自分が有用性を認識できない概念があると避けてしまう可能性もある。

*2:もし初期リリースのまま、数年間放置して価値を提供しているシステムがあれば、それはそれですごいですが、ランタイムやライブラリなどの更新も何も行われていない状態ですよね...

*3:開発者は全てのコードが見えるため情報隠蔽は軽視されてしまうことがある。開発者としての視点とコードの利用者としての視点を持つと良い。