モジュール化と選択的無知の実現、そして後方互換性の維持へ
APIデザインの極意 Java/NetBeansアーキテクト探究ノート
NetBeans開発プロジェクト10年超の蓄積!API設計の経験や考察をまとめた一冊この記事は、書籍『APIデザインの極意 Java/NetBeansアーキテクト探究ノート』の内容を、Think IT向けに特別公開しているものです。
アプリケーションのモジュール化
モジュール方式のアプリケーションは、広く分散したチームにより開発された個々のコンポーネントから構成されます。それらのコンポーネントは、たいていAPIを提供しますが、適切に機能するために実行環境において何らかのAPIすなわち機能に依存しています。たとえば、Tomcatサーバは、Javaランタイムの実装を必要とします。同様に、標準C++テンプレートライブラリは、printfを呼び出す必要があるのでlibcが必要です。コンポーネントの数が多くなれば、最も重要な問題の1つは、全体像を見渡すことができるかです。それができてから、システム全体を「理解」することができ、コンポーネント相互間のやり取りすべてを満足させられることになります。前回では、コンポーネントAPIのおかげで、私達が何が重要かを選択できることを説明しました。つまり、ほとんどの場合、内部を無視し、APIに集中するだけです。しかし、数百、数千のコンポーネントがシステム内にあれば、情報の量があまりにも多すぎて、手がかりなしでは扱うことはできません。そのため、これらのコンポーネントの理解を最小限にしながら、機能するシステムを組み立てる能力を向上させるために可能な方法をこれから見ていきます。
真っ先に学ぶべき教訓は、コンポーネントには名前が必要だということです。名前は一意で、システム内でコンポーネントを特定でき、そして、説明的でなければなりません。カーネル(kernel)は、Linuxカーネルにとっては良い名前です。libcは基本Cライブラリ用の申し分のない名前です。org.netbeans.api.projectsは、プロジェクトを扱う方法を記述しているNetBeansコンポーネント用の完璧な名前です。一般に、すべての既存コンポーネントは名前を持っていますので、コンポーネントが名前を持つ必要があると期待することは自然なことに思えます。しかし、この点をもっと注意深く見てみると、これらの名前は、コンピュータよりも人にとって重要だと分かります。自動処理だけを意図しているのであれば、単純に0xFE970A3C429B7D930Eなどのような16進形式を使用することができます。コンポーネントがたいていは人が理解できる名前を持っているという事実は、名前が主に対象としているのは人間だという証しです。名前は、顧客やエンドユーザにとって有益であり、名前を使用して、プロバイダから引き出す機能を発見します。名前は、プロバイダやコンポーネントを使用してアプリケーションを構築する人達にも有益です。
動作させる個々のコンポーネントを特定する方法が分かったら、次に、各コンポーネントにとって必要な環境を調べることになります。真空の中に存在するコンポーネントはありません。つまり、コンポーネントは必ず周りからのサービスを必要とします。ここでも、各コンポーネントが実際に必要としていることを完全に理解することは可能です。それは、内部の実装を細かく調べることでできますが、もっと良いのは、実行させてコンポーネントが環境から何を必要とするかを観察することで理解できます。しかし、これは、選択的無知という作業形態からはかけ離れています。なぜなら、組み立てる人は、新たに作成するアプリケーション用のビルディングブロックとしてライブラリを使用する前に、個々のライブラリのほぼすべての詳細を知っていることになるからです。詳細を知っている必要があるのであれば、それは、ライブラリを受け入れる大きな阻害要因になります。実際、ライブラリのほとんどのユーザは、ライブラリ内で何が行われているかほとんど分かっていません、そうあるべきなのです。ユーザは最低限の理解だけをして、それでも、自分の仕事を完了できなければなりません。適切に作成し、特徴を記述した個々のコンポーネントによって、自分の仕事を達成することができます。個々のコンポーネントが、自動的に処理可能な方法で、必要な環境の情報を持っていれば、組み立てる人はできる限り無知になることができます。なぜなら、人が介入しなくてもコンパイラ、リンカー、あるいは、組み立て時のツールが、その必要な環境を推定できるからです。
モジュール方式のシステム内の個々のコンポーネントは、必要とする他のすべてのコンポーネントに関する情報を保持しています。コンポーネントの作成者は、その情報を一度だけ指定する必要があります。あるいは、場合によっては、パッケージ化ツールが自動的に推定してくれるかもしれません。たとえば、ディストリビューションの個々のパッケージを作成するためにFedora、Mandriva、SUSEなどのLinuxディストリビューションで使用されているrpmbuildは、個々の動的ライブラリを調べて、ライブラリが使用している他のライブラリを見つけて、それらのライブラリを提供するパッケージへの依存関係を自動的に調整します。自動で行われるか手動で行われるかに関係なく、これは、一度だけ行われます。その作業は、個々のコンポーネントを作成している開発者が行うことができます。その場合、その開発者は、内部を知っていて、必要とする環境を理解し、依存関係を正しく指定できることになります。これは、選択的無知の別の例です。1人のエンジニアが真剣に考えて、コンポーネントの依存関係を指定するために時間を費やすのです。それから、そのコンポーネントのユーザ、つまり、最終アプリケーションを組み立てる人達やそのコンポーネントが存在することに依存しているだけの開発者達は、名前でそのコンポーネントへの依存を指定するだけで、残りの作業を自動システムに任せることができます。
クラスパス組み立ての悪夢
すべてのJavaアプリケーションがJavaの標準ライブラリだけで作成された日々は、過去のものです。Java用の有益なオープンソースライブラリ群は、巨大であり日々大きくなっています。結果として、今日作成されているほとんどすべてのJavaアプリケーションは、Apache Commons、HttpClient、JUnit、Swingウィジット、他のライブラリなど、すでに作成されパッケージ化されているJARに依存しています。実際、このようなアプリケーションを起動するためには、クラスパスを正しく設定する必要があります。直接使用されているライブラリをすべて含めることは簡単ですが、個々のライブラリにも満たすべき依存性がたいていはあります。その依存先にも依存しているものがあり、かなりひどい悪夢になる可能性があります。
最近、私は、ライブラリとしてNetBeansソースに、素晴らしいテンプレートエンジンであるFreeMarkerを含める機会がありました。freemarker.jarを含めることは簡単でしたが、そのすべてのクラスがうまくリンクされることを検証しようとした時に、非常に驚きました。そのJARは、Apache Ant、Jython、JDOM、log4j、Apache Commons Loggingを含む他の多くのプロジェクトを参照していたのです。FreeMarkerにとって、これらのプロジェクトすべてが必要な環境なのか。あるいは、FreeMarkerは、これらのライブラリがなくても動作するのか。後者であれば、どの機能を使用するとそれらのクラスを呼び出して、その結果、実際に実行がクラッシュするのか。私には分かりませんでしたし、実際、知りたくもありませんでした。無知な状態になれないかと願いました。しかし、なれませんでした。私はソースを調べて、普通にFreeMarkerを使用する限りこれらのクラスが使用されていないことを確認する必要がありました。FreeMarkerが、必要な依存性を指定するために、NetBeansランタイムコンテナが使用しているような何らかのモジュール方式のシステムを使用してくれていたらと思ったのです。