DIPの定義を考える

ソフトウェアの設計原則には分かりづらいものが多い。主な原因は、以下だと考える。

  • 原則が生まれた当時の内容のまま、現代に伝わっている
  • 原則の説明が十分に抽象化、本質化されていない

最近TwitterでDIPに触れているtweetを目にした。DIPを十分に理解している方も多くなってきていると思うが、それでもやはりこの「原則」は、依然分かりづらいままになっているのだと思う。これを機にDIPの整理に着手してみることにする。 

DIPの定義

DIP:Dependency Inversion Principle(依存関係逆転の原則)は、Robert C. Martinの1996年の論文によると次のように定義されている。

A. High-level modules should not depend on low-level modules. Both should depend on abstractions.(上位レベルのモジュールは下位レベルのモジュールに依存すべきではない。両方とも抽象(abstractions)に依存すべきである。)
B. Abstractions should not depend on details. Details should depend on abstractions.(抽象は詳細に依存してはならない。詳細が抽象に依存すべきである。)

Dependency inversion principle - Wikipedia

これが定義だと言われても分かりづらい(分かりづらいからなのか、書籍Clean Architectureではこの定義は使われておらず、サラッとした説明のみとなっている)。この定義から、言わんとしていることをフワッと理解できても、本質的に何のことを言っているのか捉えづらいだろう。違和感を生む単語も混ざっている。論文が提出された時代には、プログラマたちに共通する教養として「構造化分析・設計」があった。その時代であれば、上位レベル、下位レベルといった言葉には共通認識があった。しかし現代のソフトウェア構造は複雑になり、その複雑な構造を扱うための新たなアーキテクチャも登場してきている。現代のソフトウェア構造において、上位下位といった形を前提に語ってしまうと、そこで混乱を生む。また、抽象や詳細といった言葉についても、対象を限定しすぎている。

そして何より、この原則の定義方法の弱さが最も大きな問題だろう。定義が「異常状態と正しい状態」を併記する形でしか書かれていない。この書き方ゆえに、DIPを「ソースコードのある種の状態を、良いか悪いか判定するためのルール」だと解釈してしまうのだと思う。

DIPは、状態判定のためのルールではない。悪い状態から良い状態への「変更操作そのもの」を表している、と見た方がしっくりくる。その変更操作を行えば、ソフトウェアの状態を改善できると言っているのだ。整数の加算や乗算に可換則(交換可能)が成り立つというのと同じようなニュアンスになる。つまり、法則なのだ。

まとめると、DIPは、ソフトウェアのモジュールが別のモジュールに依存している時、依存の特徴だけを抽出したより安定度の高いモジュールに両者を依存させるように書き換えることができる、という法則だ。

この「書き換え可能性」がDIPの本質だと考える。そして、この書き換え可能性は、さまざまな目的に利用できる。ソフトウェア構造全体としての安定性を高められるし、OCPが言うようなソフトウェアらしいモジュール性を実現する際にも役立つ。

Robert C. Martinは1996年の論文の結びで、次のように書いている。

The principle of dependency inversion is at the root of many of the benefits claimed for object-oriented technology.

Robert C. Martin "The Dependency Inversion Principle"

DIPは方法である。そしてそれは、ソフトウェア開発における偉大な発明だ。

安定度とは何か

さて、私のDIP定義では「安定度」という言葉を使った。安定度を別の言い方にすると、その要素が「変わらない度合い」だ。プログラミング言語のコア要素であるほど、一般的には安定度が高い。プログラミング言語が提供するユーザー定義要素の場合は、特徴によって高い安定度を持ちやすいものと、そうでないものとがある。また、ユーザー定義要素においては別の視点として、そこに含まれる可変要素の数も安定度に影響する。要素の数が多ければ、客観的可能性として変更が発生する確率が高くなり、安定度は低いといえる。

昨今のクラス指向プログラミング言語であれば、おおよそ次のような順に安定度が高いといえる。

  1. プログラミング言語に内蔵された言語要素
  2. プログラミング言語に内蔵された標準ライブラリ
  3. ユーザー定義のインターフェイス
  4. ユーザー定義のクラス、ユーザー定義の関数、ユーザー定義の抽象クラス

「ユーザー定義の抽象クラス」はよく議論の俎上にあがる。抽象クラスは、高い安定度が求められる位置づけにあるにも関わらず、サブクラスの要件を一手に引き受けてしまい、結果として変更頻度が高くなることがある。これでは、低い安定度しか持ちえない。これは下手な事例だとしても、一般的に抽象クラスの安定度を高く保つのは難しい。だから基本方針として抽象クラスを使うことを避け、interfaceといった、制約が強く、高い安定度を保証しやすい要素を使う方向へ時代は進んだ。

DIPは、「具象ではなく、抽象へ依存するように書き換えること」というルールだと理解されていることが多い。具象クラスへ依存している箇所を、interfaceを介する形に書き換えるということだ。もちろん、この書き換えは可能で、それによってソフトウェア構造の安定度を高めることはできる。しかし、一律にinterfaceを使って書き換えればよいという理解では、本質を見失ってしまう。肝要なのはあくまで「ソフトウェア構造全体としての安定性」を達成することだ。

おわりに

DIPについては、緻密に考えれば他にもいくつか議論になるポイントがある。特に、2つのモジュールを仲介する安定度の高い存在を、どのように定義するのか、どこに所属させるべきなのかといったあたりだ。 このような議論も緻密に検討していくことで、設計原則の働きを具体的に捉えることができる。引き続き検討してみたい。

ところで、今回行ったDIPの定義の改善では、もともとの定義文にあった「具象要素」を、DIPの表現により適切と思われる抽象要素に置き換えた。これは、日本語の文章に対して「具象ではなく抽象に依存せよ」というルールを適用したことに相当する。DIPはこの操作のソフトウェア版に相当するとも言える。安定した構造を生み出す普遍的な法則の共通性として興味深い。

 

参考

at-grandpa.hatenablog.jp