天使の取り分

ソフトウェア開発に関するよもやま

Clean Architectureを読む(6)

9章 LSP : THE LISKOV SUBSTITUTION PRINCIPLE

リスコフの置換原則に関して。 インタフェースに対して依存することで、実装が置換可能となります。
Javaのような静的型付け言語のインタフェースに限らず、Rubyのような動的型付け言語におけるシグネチャベースのインタフェース(ダックタイピング)も含め、重要な原則となります。

10章 ISP : THE INTERFACE SEGREGATION PRINCIPLE

インタフェース分離原則は、大きなインタフェースを作るのではなく、適切なサイズのインタフェースに分離せよというものです。

11章 DIP : THE DEPENDENCY INVERSION PRINCIPLE

変わりやすい具象に依存させてはならず、安定した抽象に依存せよという原則です。要はインタフェースに対してプログラミングせよということですね。
依存の反転とは何を指しているかというと、実行時の制御フローの流れと、ソースコードの依存の方向が反対となるということです。

続いて12章からは PART IV COMPONENT PRINCIPLES に入ります。コンポーネントレベルの原則についてですが、そもそもコンポーネントとは何か、どのような要素から成り立ち、どのようにシステムを構成するべきかについて書かれています。

12章 COMPONENTS

ここで著者は、コンポーネントとはデプロイ単位であると主張します。例えばJAR、DLL、ファイルなどです。
コンピュータの大幅な性能向上により、実行時に動的にライブラリをリンクすることが可能となったことは、ソフトウェアシステムにプラガビリティをもたらしました。 プラグイン・アーキテクチャ は本書における重要な主題のひとつとなります。

13章 COMPONENT COHESION

この章ではコンポーネント設計に関する3つの原則が述べられています。

REP : THE REUSE/RELEASE EQUIVALENCE PRINCIPLE

再利用の単位=リリースの単位となるようにする。

CCP : THE COMMON CLOSURE PRINCIPLE

同じ理由で変更する(可能性のある)クラス群をコンポーネントにまとめる。(SRPのコンポーネント版)

CRP : THE COMMON REUSE PRINCIPLE

一緒に再利用するクラス群を同一のコンポーネントにする。逆に、関係がないものを同じコンポーネントに含めない。

これらの3つの原則は互いに相反する部分もあるため、バランスを取らねばならないと著者は述べています。

今日はここまで。

Clean Architectureを読む(5)

7章 SRP : THE SINGLE RESPONSIBILITY PRINCIPLE

7章からは第3部 DESIGN PRINCIPLESということでSOLIDと呼ばれる5つの設計原則についての説明です。 まずは単一責任の原則ですが、あるモジュールを変更する理由が複数あってはいけない、というものです。あるいは、モジュールはただ一つのアクターに対してのみ責任を持つべきとされています。
例えば従業員クラスに対して、会計部門の要求に基づくメソッド、人事部門の要求に基づくメソッド、情報システム部門の要求に基づくメソッドが混在しているべきではない、分離すべきだというものです。

8章 OCP : THE OPEN-CLOSD PRINCIPLE

ソフトウェアは拡張に対して開いていて、変更に対して閉じているべきだというやつです。要は、なるべく少ない変更で拡張できる作りにしろよというものです。
例えばレポートをWeb画面に表示するか、紙で出力するかというUI要件は詳細であって、変わりやすいものです。どのように計算をするかといったビジネスルールを実装するコンポーネントがそのような詳細に依存することはあってはならず、依存関係は逆でなければいけません。 ハイレベルなポリシーを最上位に置き、他の何にも依存しないように保つことで、詳細レベルの変更から保護すべきだと述べられています。

Clean Architectureを読む(4)

6章 FUNCTIONAL PROGRAMMING

3章で、関数型言語は割当てに規律を課す(Functional programming impose discipline on assignment.)と述べられているのはどういうことでしょうか。

関数型言語における変数(variable)は変わりません。mutableな変数がなければ、競合状態やデッドロックなどの並行性に関する諸問題は発生しません。なので関数型言語で記述されたプログラムは一般に並行実行性が高いです。しかしながら、Haskellのような純粋関数型言語を用いていない限り、すべての変数をimmutableにするというのは現実的に困難です。ですから、妥協案としてはimmutableなコンポーネントと、mutableなコンポーネントとをしっかり分けて設計すべきだと述べられています。

また、イベントソーシングの考え方についても言及されています。イベントソーシングとは、状態は永続化して管理せずトランザクションのみを永続化し、状態はこれまでのトランザクションの積み重ねとして導出しようという考え方です。(例えば口座の残高は、すべての入出金を順番に計算していけば求められる)。イベントソーシングにおいてはCRUDのUとDは存在しないため、必然的に同時更新の問題は発生しないというわけです。

というわけで、関数型言語は変数への値の割当てに規律を課すのだと捉えることができます。純粋関数型言語であれば変数の値を変えることはできませんし、一般的な関数型言語であっても、引数の値が同じであれば関数の呼び出し結果は必ず同一となる性質(参照透過性)があるので、並行実行の問題にとどまらずプログラムの安全性が増します(例えば、状態に依存した再現性の低いバグの混入を防ぐことができるなど)。

Clean Architectureを読む(3)

4章 STRUCTURED PROGRAMMING

4章は構造化プログラミングについて述べられています。
ソフトウェアの複雑さに立ち向かう方法として、ダイクストラは数学的証明の手法を取り入れようとしました。大きな問題を小さな問題に分割して一つ一つ解決していく、いわゆるdivide and conquerのアプローチです。ところが、プログラムにgoto文があると分解することが難しくなります。そこでダイクストラgoto文は有害であると主張し、10年に渡る論争があったそうです。
結果的にgoto文はプログラミング言語から排除され、無秩序な制御の移転は許されなくなりました。ただし、ダイクストラの目的であった、数学的証明をプログラミングに取り込むことは実現されませんでした。結局のところ、ソフトウェアは数学というよりむしろ科学であると筆者は考えています。科学の法則は数学のように正しさを証明することはできず、実験や観測によって誤っていることを示す(falsify)ことしかできない、ソフトウェアもテストによって誤りを発見することはできても正しいことを証明することは不可能なのだと。テストで誤りを発見するためには、テストができるように小さな関数にプログラムを分割する必要があり、それが構造化プログラミングの手法ということです。
というわけで、4章で構造化プログラミングはdirect transer of controlに規律を与えると書かれていましたが、これはgoto文を許容せず、小さな単位に分解可能な構造をソフトウェアにもたらすものであると理解しました。

5章 OBJECT-ORIENTED PROGRAMMING

OOの定義はいろいろありますが、よく言われる以下の3つの特徴があります。

  1. カプセル化 (encapsulation)
  2. 継承(inheritance)
  3. ポリモーフィズム(多態性polymorphism)

それぞれについて、以下のように述べられています。

  • カプセル化はCのヘッダファイルでも実現できていたし、むしろそちらの方が情報隠蔽できている。従ってカプセル化はOOの決定的要素ではない。
  • 継承はOO以前でも実現する技法が存在した。ただしOOによって便利になった。
  • これもOO以前でも実現できたが、関数の実体を指すポインタを自前で管理する必要があり、危険だった。OOによって安全に実現できるようになった。

ポリモーフィズムがOOの中で最も重要な特徴であると筆者は主張しています。なぜ重要かというと、それによって依存関係の逆転(dependency inversion)が可能となるからです。
通常、制御の流れとソースコードの依存関係の方向は同じになります。上位レベルのプログラムが使用するプログラムはimportなどで指定するため、上位→下位へのソースの依存関係が生じます。ところがOOのインタフェース(あるいは抽象クラス)を使用すれば、ソースコード上では下位のプログラムが上位のプログラム(インタフェース)に依存するように反転できるのです。 ポリモーフィズムあらゆるソースコードの依存性の方向を完全に制御する能力を与えてくれるのだと言います。
この能力によってプラグインアーキテクチャを構築することが可能となります。ハイレベルやポリシーを表すビジネスルール(あるいはドメインロジックと言ってよいかもしれません)がローレベルな詳細であるUIやDBに依存するのではなく、その逆であるアーキテクチャ(ビジネスルールが主役のアーキテクチャ)であれば、UIやDBを必要に組み替えることすら可能であるということです。

ということで、3章で述べられていた、OOはindirect transfer of controlに規律を与えるというのは、(関数ポインタを自前で管理する危険なやり方ではなく)安全にポリモーフィズムを実現する能力こそがOOの大きな力であるということになります。

5章はClean Architecture って何?という命題に対する答えのヒントとなる重要な章のようですので、しっかり理解して先の章へ進んでいきたいと思います。

Clean Architectureを読む(2)

2章 A TALE OF TWO VALUES

2章では、ソフトウェアの2つの価値、振る舞い(Behavior)構造(Structure)について述べられています。
振る舞いとはソフトウェアが要求仕様に従って正しく動作すること。構造とはソフトウェアの形であり、それによって要求の変更にどれだけ容易に対応できるかが決まります。
ビジネスの人々はソフトウェアが正しく動くこと(振る舞い)に価値を置きますが、いくら正しく動作していたとしても変更が容易でないソフトウェアは、将来のいずれかの時点において変更にかかるコストが変更により生み出される価値を上回ることになってしまうと述べられています。
ソフトウェアがソフトウェアたる所以はソフト(変更しやすい)からであり、開発者の立場からは構造にこそ価値を置かねばならないのだと。開発者はソフトウェアのステークホルダーの一員として、長いスパンで価値を生み出す構造がないがしろにされないようにセーフガードの役割を果たさねばならないと主張されています。
とりわけアーキテクトにとっては、開発しやすく、変更が容易で、拡張性の高い構造(アーキテクチャ)を作り出し、維持していくことがその職務の中心となります。

3章 PARADIGM OVERVIEW

3章はソフトウェアの3つのパラダイムについての導入です。
どのパラダイムプログラマができることを削ったもので、何をすべきではなくて何をしてはならないかについて規律(discipline)を与えるのだと述べられています。具体的には、構造化プログラミングは直接的な制御の移行(direct transfer of control)に対して、オブジェクト指向プログラミングは間接的な制御の移行(indirect transfer of control)に対して、関数型プログラミングは割当て(assignment)に対してそれぞれ規律を課すということです。具体的にどういうことかというのは4章以降で明らかになるでしょう。
面白いのは、オブジェクト指向プログラミングは構造化プログラミングよりも前に発見されたということ、さらに関数型プログラミングはプログラミング以前に発見された理論(アロンゾ・チャーチのラムダ計算)に基づいたものである、というところですね。どれも1958-1968に発見されたものであり、この3つ以外のパラダイムは出てこないだろうと筆者は述べています。

Clean Architectureを読む(1)

ボブおじさんことロバート・C・マーチンのClean Architectureを買ったので、ちょっとずつまとめていきます。

1章 WHAT IS DESIGN AND ARCHITECTURE?

デザインアーキテクチャの差は実はないと言っています。ハイレベルな構造と、ローレベルの詳細を切り離して考えることはできないのだ、と。
また、一般論として年月を重ねソフトウェアの規模が大きくになるにつれソフトウェアの開発生産性は落ちていく、それはなぜかというと「後できれいにできる」という過信があるからだと述べられています。実際には後からきれいにすることなんてできないので、後回しにせず最初からよいソフトウェアを作っていくことが重要なのだと。
ではよいソフトウェアっていったい何なの、というのがこの本のテーマ(Clean Architecture)ということです。