モジュールの深さについて
Click here for English version
この記事は、スタンフォード大学のJohn Ousterhout教授の著書「A Philosophy of Software Design」にて説明されているdeep moduleという概念について、本人の許可を得てまとめたものです。ただし本書の内容を詳しく紹介するのではなく、主に私の考察を書き綴ったものです。
@nakabonne Sure, feel free to write an article about the benefits of deep modules. The more people that hear about this, the better!
— John Ousterhout (@JohnOusterhout) January 14, 2019
最初に私の考える良いモジュールを定義付け、次にそれを実現するdeep moduleという概念について説明します。
良いモジュールとは
良いモジュールとはなんでしょうか。ここでいうモジュールというのは、クラス・構造体やサブシステム、さらにマイクロサービス等の分化されたソフトウェアを指し、言語は問いません。
小さいモジュール?
「Small is beautiful」というUnix哲学があるように、小さいことは価値になります。モジュールを小さく分割することでモジュールの仕事が明確になりやすく、結果的に保守が容易になります。
確かにそれは大切なことですが、そもそもなんのためにモジュールを作るのでしょうか?
主な理由の一つとして、関係性の高い要素が集まることで実装の複雑さを下げるというものが挙げられます。それを踏まえた上でこのコードを見て下さい。
Javaでファイルを開く際、以下のように3つの小さなクラスが必要です。
FileInputStream fileStream = new FileInputStream(fileName);
BufferedInputStream bufferedStream = new BufferedInputStream(fileStream);
ObjectInputStream objectStream = new ObjectInputStream(bufferedStream);
FileInputStreamは初歩的なI/Oのみを提供し、BufferedInputStreamは、FileInputStreamで得られたバイト入力ストリームをバッファリング読み込み付きのバイト入力ストリームに変換します。
そしてObjectInputStreamはシリアライズされたオブジェクトを操作する機能を提供します。
見ての通りファイルを開くためだけに3つもオブジェクトを生成しなければなりません。クラスを小さくしたことで、各クラスの仕事は明確になりました。しかし関連性の高いFileInputStreamとBufferedInputStreamが独立してしまっていることで、もしユーザーがBufferedInputStreamを作り忘れた場合はバッファリングされずに遅くなってしまいます。つまり実装の複雑さが外部に漏れてしまっているということです。
実装の複雑さが漏れることが必ずしも悪いとは限りません。ただ以下のことは認識しておくべきです。
- モジュールが小さいということは、持っている情報量が少ないということ
- 持っている情報量が少ないと、実装の複雑さが外部に漏れやすい
- 実装の複雑さが漏れていると、モジュールの実装変更が周りのモジュールに影響を与えやすくなる
Small is beautifulであることは間違いないのですが、関連性の高い要素を分割してしまうと保守性が下がってしまうことが分かりました。
なので、小ささを意識しすぎると良いモジュールは作れないことが分かります。
高凝集?疎結合?
関連性の高い要素が集まり(高凝集)、それぞれが依存し合わない(疎結合)ことは非常に大切です。上記コードは低凝集・密結合の良い例です。
関連性の高いたくさんの要素を隠し持つメリットはたくさんあります。
“関連性の高い"要素を持つことで設計が明確になり、保守と拡張が容易になり、再利用性が高まります。
また、“たくさんの"要素をもつことでモジュールの機能が強力になります。
さらに、“隠し"持つことで疎結合化も促進することが出来ます。
よって、高凝集というのは非常に大事な概念だということが分かります。
また、モジュールの疎結合化がソフトウェアに良い影響を与えるのは言うまでもないでしょう。ただ疎結合を保つためには、モジュールを繋ぐインターフェースを良いものにする必要があります。
良いインターフェースとは
「インターフェースはシンプルに保て」とよく言われますが、このシンプルという言葉は曖昧なので噛み砕いていきます。
まずはモジュールのインターフェースに何が求められているのかを理解する必要があります。
モジュールが他のモジュールを利用するためには、インターフェースという名の契約を結びます。各モジュールは契約を前提に実装されるので、契約が変わるとその契約に関わっている全てのモジュールの実装を変更しなければなりません。しかしソフトウェアというものは、ほとんどの場合変更されます。
この矛盾の中でも崩れないことがインターフェースには求められています。すなわち良いインターフェースとは、不変(追加以外の変更を許さない)なインターフェースだと考えています。
そして可能な限り、インターフェースを利用するユーザーにとって使いやすくあるべきです。
しかしこの「使いやすさ」に関しての私の考えは、Clojure作者であるRich HickeyがSimplicity Mattersで述べていた考え方に影響を受けています。使いやすさというのは相対的な概念で、何が便利なのかはユーザーによると考えています。
ユーザーをユースケースの観点で大きく2つに分けると、エッジケースで利用するユーザーと一般的なケースで利用するユーザーに分かれます。
インターフェースが複雑になってしまう主な理由として、エッジケースを許容してしまうということが挙げられます。そのため、一般的なユースケースに絞って使いやすさを追求するべきです。
先程のJavaのコードをもう一度見て下さい。ファイル操作時にはバッファリングするのが一般的なケースのはずです。にも関わらずBufferedInputStreamクラスを独立させることで、バッファリングしないというエッジケースを許容していました。そのためインターフェースが複雑化してしまったということです。
改めて良いインターフェースをまとめると、「一般的なケースで使いやすく不変なインターフェース」と考えることが出来ます。
結論
上記を踏まえて、私の考える良いモジュールの定義は**「関連性の高いたくさんの要素を隠し持ち、一般的なケースで使いやすく不変なインターフェースによって強力な機能を提供するもの」**です。
次章から、それを実現するdeep moduleという概念について説明します。
deep module
たくさんの要素を隠し持つためには、ある程度実装は複雑になります。しかし良いモジュールにするためには、実装の複雑さを感じさせないインターフェースで強力な機能を提供しなければなりません。そんなモジュールのことを、John Ousterhout教授はdeep moduleと呼んでいます。本書では以下のように書かれています。
“they allow a lot of functionality to be accessed through a simple interface.”
(John Ousterhout, A Philosophy of Software Design.)
上図は本書に記載されている図を参考に作成したものです。
長方形が一つのモジュールを表しています。上端の横の長さがインターフェースの複雑さを表していて、縦の長さが機能の強力さ(モジュールがもたらす利益)を表しています。横に長ければ長いほどインターフェースが複雑で、縦に長ければ長いほど機能が強力だということです。John Ousterhout教授は左の縦長な長方形のようなモジュールをdeepと表現しており、最も良いと考えています。
つまり、モジュール設計の際には以下の視点が必要だということです。
- 機能は強力だけど、インターフェースも負けないくらい複雑になっていないか?
- 逆にインターフェースは簡単だけど、機能も負けないくらい貧弱ではないか?
インターフェースの複雑さというのはコストで、機能の強力さというのは利益です。利益がコストを上回っていれば、それはdeep moduleといえます。 ただこの見極めが難しいので、良い例をたくさん見ることが大切です。
例:ファイルI/O
deep moduleを体現している例として、Unix系OSのファイルI/Oに関わるシステムコールが挙げられます。
標準的ファイルI/Oモデルから外れなければ、使うシステムコールは基本的に以下5つのみです。
- open()
- read()
- write()
- close()
- lseek()
Unix I/Oの実装は長年に渡って進化し続けているのにも関わらず、この5つのシステムコールは変わっていません。これは理想的なdeep moduleといえます。
カーネルは本来ユーザアプリケーションを安定して動作せるために設計されているため、インターフェースがほとんど変わりません。そのため、カーネル周辺のインターフェースをじっくりと眺めてみると参考になると思います。
まとめ
モジュール設計者は、たくさんの視点を持ちながら設計することを求められます(e.g. 凝集度、結合度、可逆性、直交性…etc)。
しかし、抽象的な世界をコードに落とし込む上に常に変わり続ける「ソフトウェア」というものに、全ての視点を取り入れるのは困難だということは誰もが感じているはずです。
そこで、「このモジュールは深いか?」というたった一つの問いを投げかけてみてはいかがでしょうか?
John Ousterhout教授の素晴らしい概念であるdeep moduleについて、一人でも多くの方が理解して頂けたら幸いです。
また、興味を持った方は本書を購入することを強くオススメします。
余談ですが、deep moduleについて知った時、以下ツイートのようなことを考えました。これについては別エントリでまとめるかもしれません。
これを読んでたら「良いモジュールは簡単なインターフェースで多くの機能を提供してくれる」と強調されており、任天堂の宮本さんの「アイデアとは複数の問題を一気に解決するもの」という言葉を思い出した。
— Ryo Nakao (@nakabonne) January 6, 2019
一般論かもだけど、複数の複雑な問題をシンプルに解決するというのがモノづくりの本質なのか https://t.co/bqOOHMx10s
*間違いやご意見等ありましたら、Twitter等で連絡して頂けると大変助かります。