モジュラリティを考える
ソースコードの分かりやすさは、「単一責務の原則」と「関心毎の分離」により適切に構造を分割した、バランスのいいところにあると前のエントリに書いた。では、そこにモジュラリティが加わるとどうなるだろうか。
モジュラリティとは、システムを幾つかのモジュール*1に分離し、さらに分離したモジュールを組み合わせて新しいシステムを組むソフトウェア開発法、とここでは定義する。分かりやすい例はEclipseだ。例えばPyDevを導入すればPythonの開発環境が手に入る。このように言語環境ごとに作られたプラグインを導入すれば、それぞれの言語で開発ができるIDEが構築できる。EclipseはIDEとして必要な機能の全てをプラグインという形のモジュールに分離し、組み合わせる事でモジュラリティを実現している。*2
モジュラリティの利点と欠点を上げてみよう。
利点
モジュール毎に独立して開発できる
モジュールに分割できるのであれが、独立して開発ができるため、それぞれのモジュール毎に開発チームを分離し、開発を進める事ができる。システム内の知識を共有するのであれば、そのシステム全体においてソースコードが読める状態である事が望ましいが、その全てを読んで理解しきれるエンジニアはそう多くないだろう。エンジニアが開発しているモジュールのソースコードが読むべき範囲が限定され、明示されていれば、新規に参加する場合でも参加しやすい。
また、独立しているので、JavaであればJARなど、代替できるものがあれば、ビルドにはシステム全体のソースコードがなくてもコンパイルできる。要するにソースコードの公開範囲を調整できる。例えばライセンス管理など、重要な部分のソースコードを公開する事なく、それ以外のソースは公開する、と言う事も可能だ。*3
言い換えれば、システムをモジュール毎に分割統治されている、と言う事だ。
モジュール毎に責務を割り当てられる
モジュール毎に責務が割り当てられるので、うまく関心毎に分離できていれば、システムがどういう要素で構成されているのか、分かりやすい。モジュールに分離できるべき関心毎については後で述べる。
新規開発する機能をモジュールに分離できる
新しい機能は一度実装すればOKと言う事はほとんどない。作ったものを使ってみて、調整する必要があるので、たくさん変更されるし、もし作ってみて微妙な機能であれば、前の状態に戻せる事が望ましい。別のモジュールに分離して開発すれば、簡単に破棄できるので、プロトタイプ実装と言った実験的な機能を試しやすい。また、既存のソースを書き換える部分も同一モジュールとして開発を進めるよりも限定される。
ソースコードの変更による影響の予測が立てやすい
モノシリック*4なシステムでは、変更したソースコードによる影響がどこまで波及するか、予測を立てづらいためソフトウェア全体のテストが毎度必要だった。しかし、ソースコードを変更していないモジュールには、機能に変更がない事は自明だ。*5そのため、毎回ユニットテストを行う必要がない。(ただし、システム全体の統合テストは毎回実施した方がよいだろう。)
他のシステムへモジュールを再利用できる
開発したモジュールは、次第に安定し、特殊な用途でなければ他のシステムへ転用をしたくなる事があるだろう。モノシリックなシステムでは、その部分のみ分離した後の管理が非常に面倒な事になるが、元々モジュールに分離され、適切にバージョンの運用がなされていれば、他のシステムに転用後も、時期を見て新しいバージョンへ更新できるだろう。
欠点
アプリケーションとしての構造が複雑に
モジュールに分離すると、それぞれのモジュールは元のシステムより小さいので単純になるが、アプリケーションとしての構造は複雑になる。なぜならば、必要なモジュールが不足していたり、モジュールの組み合わせや、モジュールフレームワークへ登録された順番によって不具合がおこるなど、モノシリックなシステムだった頃は起きない障害に悩まされる事がある。出来るだけ他のモジュールへ依存しないように設計したり、組み合わせを考えたりする必要がある。
また、システムの構成を任意に変更できるため、システムの開発者や設計者が意図していない機能が追加される恐れがある。要するにうまく作らなければ脆弱性がシステムに同梱してしまうのだ。
新たなモジュールが必要になった時や、モジュールの変更時になんらかの作業が必要になる。
大した時間ではないが、モノシリックなシステムと比べると、ひと手間かかる。特にモジュールの変更時に適切にバージョンを書き換える必要がある。Maven2などのツールを用いれば、自動で更新する運用ができるが、リリース時にバージョンを固定するなど、やっぱりひと手間かかる。
以上をまとめておく。
利点
- モジュール毎に独立して開発できる。
- モジュールを開発チーム毎に担当できる
- 開発時に理解すべきソースコード量を縮小できる
- モジュールごとのソースを非公開にできる。
- ライセンス管理などをモジュールに分離→重要な部分のソースを隠蔽しつつ、それ以外のソースを公開できる
- モジュール毎に責務を作成できる。
- モジュール毎に関心毎を分離できる、と言い換えてもいい。
- 新規に開発している部分をモジュールに分離して開発できる
- 実験的な機能を試しやすく、実装した機能が微妙だった場合に捨てるのが簡単
- プロトタイプの実装と、ドッグフード的な導入が楽
- 既存のコードへの変更を最小限に限定できる
- 実験的な機能を試しやすく、実装した機能が微妙だった場合に捨てるのが簡単
- ソースコードの変更による影響の予測が立てやすい
- 他のシステムへのモジュールの再利用ができる
欠点
- 個々のモジュールは責務毎に分割できるので、単純にできるが、アプリケーションとしての構造は複雑になる。
- モジュールの依存関係が不十分で動作しない場合などの状況が生まれうる
- 他のモジュールから自由に機能を追加できるようにも作成できるので、モジュールの組み合わせや、登録順序により、副作用が生まれ、不具合がおこる事がある。
- モジュールの導入により、任意に機能を追加できるため、脆弱性を生みやすい
- (大した時間ではないが、)モジュールの新規作成分、手間がかかる。
システムをモジュールに分割する場合、モジュールが持つ責務(関心)をどうやって分割するかが鍵になる。以下にどういう責務があるか、例をあげる。*7
- 関心毎にシステムが持つ機能を分割
- ドメインモデル
- 他のシステムでも使い回せる汎用的なサービス
- システム内を操作するシェルサービス
- HTTPサービス
- スクリプト言語サービス
- 拡張機構(extender)
- DIを行うサービス
- モジュール内の設定ファイルを読み、システムを拡張する機構
- モジュールの成長(変更)頻度の多さ(例:UI部と、それ以外)
- UIはHTML+JavaScript or FlashとSwing、SWTなど画面を描画するライブラリが異なれば別に分けておく
- テスト容易性が確保できる単位で分割(システム境界)
- インタフェースを公開すればテスト
- データベース
- ファイルなどのリソースへのアクセス
- スレッド や Jobを実行するサービス
- 他のシステムとの連携
- メール送信・受信サービス
- Twitterなどの外部Webサービス
大体の責務の大きさが伝わると幸いだ。
*2:基盤のモジュールフレームワークにはEclipseの場合はOSGi実装を利用しているが、モジュールフレームワークも含めて全てのプラグインがモジュールだ。
*3:これは既存のシステムでも、もちろん同等の事ができるが、分離されている分よりやりやすい。
*4:モジュールに分割せず、一枚岩なソフトウェア開発の事
*5:不安であれば毎回ユニットテストを行っても良いが、それにより失敗してしまうテストがあるのであれば、そのモジュールが変更したモジュールから影響されている事が分かり、モジュールとして望ましくない状態である事がわかるので、たまに実施してもよい。
*6:不安な場合はもちろん行っても良いが、テストを行って他のモジュールの変更による影響を受けたテストは、ユニットテストとしてあまり良くないテストと言える。あくまでユニットテストは対象クラスのロジックのみをテストすべき。