自由課題

学んだり、考えたり、試したりしたこと。

NetBeansにおけるアーキテクチャの質問(日本語訳)

はじめに

インターフェイスを設計するために読んだ技術書まとめ - 自由課題 の記事でも紹介した書籍

APIデザインの極意 Java/NetBeansアーキテクト探究ノート

APIデザインの極意 Java/NetBeansアーキテクト探究ノート

内に、NetBeansコンポーネントを設計・実装する際のアーキテクチャの質問(チェックリスト)のURLが紹介されていました。興味があったので実際確認したのですが、ぱっと見面白そうだったのですが日本語訳がないようだったので訳してみました。基本的にはJavaを基盤に構築されたNetBeansに対する質問なのですが、NetBeansJavaでなくても参考になる質問も多数含まれています。

お堅くいうとISO/IEC9126もしくは25010の品質特性に包含される話なのだと思いますが、インターフェイスの設計・実装に特化したもっとカジュアル/わかりやすいチェックリストとして活用できそうです。

アーキテクチャの質問

これはNetBeansプラットフォームの上で記載されたモジュールのアーキテクチャの早期のレビューのために用意された現状の質問の概要です。この質問は作成したモジュールの自己レビューを通じてみなさんを導き、人々にAPI開発の側面を認識させることを助け、より簡単にメンテナンスできるようにするために様々な種類の間違いを防ぎ、javadocドキュメントの回答形式の有用な部分のプロジェクトのドキュメントの土台のためのものです。

プロジェクトを開始する前に

これは新しくAPIを提供するモジュール、もしくは他の一般的な機能性の作成を開始することを検討するときに解答すべき基礎的な質問です。これらの質問の解答を作成するために、XMLの解答及びAntスクリプトを作成するために下記のコマンドを実行してください

ant -Darch.when=init

これはこれらの質問への解答を含むHTMLページを生成します。

質問(arch-what): このプロジェクトは何が長所なのでしょうか?
ヒント: ここにプロジェクトについて数行記載してください。解決すべき課題、ドキュメントへのリンク、仕様、など。

質問(arch-overall): アーキテクチャの全体を記述してください。
ヒント: クライアントに対するAPIはどのようなものにするか、APIは何をサポートするのでしょうか? どの部分がプラグイン化されているのでしょうか? どうやってプラグインを登録するのでしょうか? APIを記述するために<api type="export" />を使用し、各APIの安定性に関するカテゴリを指定してください。可能なら単純な図を提供してください。

質問(arch-usecases):
ヒント: この回答の内容はhttp://www.netbeans.org/download/dev/javadoc/usecases.htmのページの一部として表示されます。<usecase name="name> 普通のhtmlの記載 </usecase>のタグを用いることができ、もしURLを使用したいのであれば@TOP@を新しいAPIの主要ユースケースに対応するjavadoc記述の先頭に付与することができます。

質問(arch-time): この仕事に必要な時間の見積もりは?
ヒント: 設計、実装、安定化のために費やす時間の見積もりを表現してください。実装するのにどれくらいの人数が必要で、いつ準備ができていなければならないかということにより、何が予測マイルストーンになるでしょうか?

質問(arch-quality): あなたのコードの品質はどのようにテストされるでしょうか? どう将来のリグレッションは防止されるのでしょうか?
ヒント: どのようなテストの種類を用いたいと思っていますか? どれくらいの機能性、領域がテストによりカバーされるべきでしょうか? プロジェクトが成功しているということをどうやって確認しますか?

質問(dep-nb): 他の何かのNetBeansプロジェクトやモジュールに依存しますか?
ヒント: 他のNetBeansプロジェクトに依存することはユーザがモジュールを有効化/無効化することによりユーザ自身のNetBeansの正式バージョンをカスタマイズする能力に影響します。多すぎる依存はこの種のカスタマイズを制限します。もしそのようになっている場合は、機能性を個々に有効化することが可能なautoload、eagerとregularモジュールの断片に分割することが望ましいかもしれません。通常この質問に対する回答はあなたのproject.xmlファイルをもとに生成されますが、正確に推測できない場合は<defaultanswer generate="none"/>を指定して生成を抑制し、あなた自身が回答を記載することができます。<api name="identification" type="import or export" category="stable" url="where is the description" />を用いてAPIをインポートすることによりそのようなプロジェクトを記述してください。このようにすることによりJavadocの概要ページに情報が記載されるようになります。

質問(dep-non-nb): NetBeansではないプロジェクトで依存しているものは何でしょうか?
ヒント: サードパーティのライブラリに依存することは常に問題があります。特にそれらがオープンソースでないときは特にそうであり、NetBeansのライセンススキームを複雑化することになります。ここに外部依存を列挙し、プロジェクトの法的な影響を一から正確に理解できるようにしてください。いくつかのNetBeansではないプロジェクトはNetBeansモジュール(ライブラリ(訳注:訳作成時点でデッドリンク)を参照)としてパッケージ化されており、さらに多くのモジュールが依存しサードパーティのライブラリを共有する場合はこの手法が望ましいことに注意してください。

質問(dep-platform): モジュールはどのプラットフォームで動作するでしょうか?動作可能な各プラットフォーム上では同様の方法で動作するでしょうか?
ヒント: もしOSへの何らかの依存、もしくはネイティブコードの何らかの使用を計画しているのであれば、その理由とコードのポータビリティを強制方法を記載してください。autoload/eagerモジュールと同時に条件付きでOSごとに有効化されるモジュールのためのサポートがあり、サポートされないOS上での互換性ブリッジを提供することに対して、この方法はあるOSにおいてはOSを意識したサポートを提供するための最良の方法です。サポートされているOSやHWプラットフォームをリストし、プロジェクトを動作させるために必要な好ましいJDKのバージョンについても触れてください。

質問(deploy-packages): あなたのモジュールのパッケージはpublicとして宣言しないことによりアクセス不可としているでしょうか?
ヒント: デフォルトでは、NetBeansのビルドハーネスはすべてのパッケージはprivateであるとみなします。もしパッケージをexportする - publicもしくはフレンドパッケージのどちらか、の場合は明確な理由が必要です。もしこの理由がドキュメントの別のどこかに記載されている場合は、この質問は無視して構いません。

質問(compat-standards): モジュールは何らかの標準を実装、もしくは定義していますか? 実装は正確でしょうか?もしくは何か標準から逸脱しているでしょうか?

質問(compat-deprecation): プロジェクトの紹介は製品の以前のバージョンで提供されていた機能性にどのように影響しているでしょうか?
ヒント: もし既存のAPIのいずれかを非推奨/削除/変更することを計画しているのであれば、それらのAPIをなぜそうするのかを説明する理由とともに列挙してください。

質問(lookup-lookup): モジュールは通信する何らかのコンポーネントを発見するためのorg.openide.util.Lookupもしくは何らかの同様な技術を使用しているでしょうか?どれを使っていますか?
ヒント: NetBeansはlookupと呼ばれるサービスの汎用レジストリを中心に構築されています。これは可能であればサービス登録・探索に使用するための好ましい選択肢です。"The Solution to Comunication Between Components"(訳注:訳作成時点でデッドリンク)を参照してください。もしlookupの使用を計画しておらず他の手法の使用を主張するのであれば、なぜlookupは使用できないのかを記載してください。アーキテクチャドキュメントの最終版を埋めたとき、検索するインターフェイスを記載してください。どこで定義されているのか、ただ一つを検索するのか、複数を検索するのか、順序が重要な意味を持つのか、等。そのようなAPI契約の安定性についても分類してください。この情報がJavadocの概要ページに列挙されるように、<api group=&lookup& />タグを使用してください。

質問(exec-threading): スレッドモデルはどうなっていますか?もし何らかあるのであればモジュールはそれを遵守しているでしょうか?スレッド処理に関してこのプロジェクトはどのように振る舞うでしょうか?
ヒント: APIはスレッドセーフでしょうか?任意のスレッドからアクセス可能でしょうか?もしくはあるスレッドからのみアクセス可能でしょうか?AWT及びAWTのイベントディスパッチスレッドと特別な関係がありますか?もしモジュールが特定のスレッドモデルを持つ外部のAPIを呼び出す場合、どうそれらのAPIに適応したマルチスレッドアクセス(同期、ミューテックス等)の要件に従っているのかを示してください。もしモジュールが何らかのAPIを定義する、もしくは複数のスレッドから使用される複雑な内部構造を持つ場合には、同時アクセス、競合、デッドロック等に対してデータをどうやって保護するのか、そのルールが実行時警告、エラー、アサーション等々で強制されるのかどうかを明らかにしてください。例: (Javaコレクションのように)スレッドセーフではないクラス; 完全にスレッドセーフである(内部ロックを使用); mutexを通じてアクセスすることが必要(で、クライアントメソッドに代わってmutexを自動確保する、もしくはしない); イベントキュー内でのみ実行可能; など。何らかのイベントが発火した時についても記載してください: 同期処理、非同期処理など。発想はThreading Recommendations(執筆中)を参照。

質問(perf-scale): どんな外部の指標(エディタ内のファイルサイズ、メニュー内のファイルの数、ソースディレクトリ、等)がプログラムのパフォーマンスに影響し、コード規模はどれくらいでしょうか?
ヒント: ある程度の見積もりを含め、回答すべき他の詳細な質問は実装フェーズにあります。

質問(perf-limit): あなたのコードが扱う要素に何らかハードコードされた、もしくは実際の制限値、もしくはサイズはありますか?
ヒント: 大部分のアルゴリズムは操作するデータのサイズに対応して必要なメモリが増加し、複雑さが増大します。速度もしくは必要なメモリに関連してボトルネックだと思われるクリティカルな部分は何でしょうか?プロジェクトでテストしているデータの実際のサイズはどれくらいでしょうか?観測可能なパフォーマンスの問題の原因となる潜在的なデータサイズの推定値はどれくらいでしょうか?そのような状況を検知し、"強烈な"クラッシュを防ぐためのある種の検知機構はありますか? - 例えばエディタ内で開かれるファイルのサイズCloneableEditorSupportチェックであり、もし1Mbの場合ユーザに判断させることを可能とするダイアログを表示します - 操作をキャンセルするか、プログラム自体が強制終了することに同意するかのどちらかです。

質問(perf-spi): プラグインされるコードのパフォーマンスはどう強制されるのでしょうか?
ヒント: もしモジュールに外部コードをプラグインすることを許可しているのであれば、外部コードが適切・迅速に動作し、モジュールそのもののパフォーマンスに悪影響がないようにどう強制しているでしょうか?

実装中

プロジェクトの実装を設計するときに、下記の質問を心に留めておいてください。もし生成されたHTMLの回答を確認したいのであれば下記のコマンドを実行すると、

ant -Darch.when='init impl'

最初の質問とこの質問の両方が生成されます。

質問(arch-where): モジュールのソースはどこで入手できますか?
ヒント: http://www.netbeans.org/download/source_browse.htmlCVSウェブクライアントへのリンク、もしくは単にdefaultanswer generate='here'タグを使用してください。

質問(deploy-jar): 単にモジュールのJARファイルをデプロイするのでしょうか?もしくは他のファイルも同様にデプロイしますか?
ヒント: 通常モジュールは(おそらくClass-Path拡張と共に)単一のJARファイルとこのファイルを有効化する設定ファイルから構成されます。もし他のファイルがあるのであれば、<api group="java.io.File" name="yourname" type="export" category="friend">...</api>を使用してファイルのlocationと名前及び安定性(もちろん"yourname"と"friend"の部分は必要に応じて変更してください) もし一つ以上のJARを使用するのであれば、それらのファイルがどこに存在しているか、互いにどう参照しているかを記載してください。もしモジュールの(複数の)JARと他のファイルから構成される場合は、それぞれのファイルの目的、他のファイルが必要な理由を記載してください。インストール/アンインストールした場合にインストール前の状態にシステムを復元するように注意してください。

質問(deploy-nbm): アップデートセンター経由でNBM(訳注: NetBeansのモジュールファイル)をデプロイすることができますか?
ヒント: できない場合は理由を記載してください。

質問(compat-i18n): モジュールは正確に国際化(I18N)されていますか?
ヒント: 正確な国際化とは、NetBeansIの18Nページの手順に従っている、ということを意味します。

質問(compat-version): モジュールは、以前のバージョン及び未来のバージョンのものと共存できますか? 全ての古い設定を正確に読み込むことができますか? 未来のバージョンのモジュールは現在の設定を読み込むことができますか? 未来のバージョンにより格納された設定を読み取る、もしくは丁寧に無視することはできますか?
ヒント: 設定を読み取る助けとなるのはバージョン番号を記録しておくことであり、このことにより未来のバージョンはどうやって設定を読み取り/変換するかを決定でき、古いバージョンは新しい設定を無視することができます。

質問(exec-property): コードの実行は何らかの環境変数もしくはJavaシステムプロパティに影響されますか? 同様の着目点として、java.util.logging.Loggerに渡す何か興味深いパラメータはありますか?もしくは、他のログの内容を監視しますか?
ヒント: コードの振る舞いを変更可能なプロパティがあるのであれば、誰かがそれを使用したいと思うでしょう。それが何をするか、及びAPI安定性カテゴリを記述するべきです。

           <api type="export" group="property" name="id" category="private" url="http://...">  
                description of the property, where it is used, what it influence, etc.  
            </api>

を使用することもできます。

質問(exec-component): コードの実行はあなたの作成したいずれかのコンポーネントの何らかの(文字列)プロパティに影響されますか?
ヒント: しばしばJComponent.getClientPropertyAction.getValueもしくはPropertyDescriptor.getValue等がコードの振る舞いに影響するために使用されます。これはもちろんドキュメント化されるべきインターフェイスを形成します。もしオブジェクト(instanceof Runnableであるコンポーネント)がAPIを形成するインターフェイスに依存する場合も同様です。

質問(exec-ant-tasks): 他人が使用することができる何らかのantタスクを定義もしくは登録していますか?
ヒント: もしユーザが利用可能なantタスクを提供する場合、その文法や振る舞いに関して慎重である必要があります。それはほとんどエンドユーザ向けのAPIを形成しており、エンドユーザーが多数である場合、そのようなAPIが破壊された場合の反応は相当のものとなるからです。

質問(exec-classloaderf): コードは自身の(複数の)クラスローダを作成しますか?
ヒント: 少し普通ではないので、そうする理由を説明してください。

質問(exec-reflection): コードは他のコードを実行するためにJavaのリフレクションを使用していますか?
ヒント: これはシステムの他の部分のAPIが失われている、もしくは不十分であることを示しています。もし他の部分があなたの依存性に気づいていない場合、この契約は容易に破壊される可能性があります。

質問(exec-process): モジュールから外部プロセスを実行しますか? どう異なるプラットフォーム上で同じ結果を保証しますか? 出力をパースしますか? 結果コードに依存しますか?
ヒント: もし入力を投げ込み、出力をパースする場合はAPIとして明らかにしてください。

質問(exec-introspection): モジュールは何らかの種類の実行時情報(instanceofjava.lang.Classを使用する等)を使用しますか?
ヒント: Aの種類のオブジェクトを所有し、このオブジェクトがBの種類でもある可能性があり、特別な処理を行う場合を確認してください。これはドキュメント化される必要があります。メタレベルの操作(Class.isInstance(...)Class.isAssignableFrom(...)、等)を適用する場合も同等です。

質問(format-types): (もしあれば)どのプロトコル、ファイルフォーマットをディスクに読み書き、もしくはネットワークを通じて送受信しますか? antビルドスクリプトを生成しますか? それは編集及び変更可能でしょうか?
ヒント: ファイルは他のプログラム、モジュールにより読み書きすることができます。もしそれらが振る舞いに影響するのであれば、フォーマットをドキュメントに記載した上で、それを(<api>タグを用いて)private APIであると主張しておいてください。

もしantビルドファイルを生成するのであれば、これはエンドユーザにより将来見られ、編集しようとされる可能性が高いです。そのような準備をしておき、ドキュメントに対するリンクを提供する必要があります。ドキュメントにはその目的を記述し、次のリリース時までそのようなファイルをどのように理解するのか、このフォーマットが(ありがちな)わずかに変更されるタイミングについても触れる必要があります。

質問(format-dnd): (もしあれば)コードはDrag & Dropの間にどのプロトコルを理解しますか?
ヒント: しばしばノードはNode.dragNode.getDropTypeの使用によりクリップボードを扱います。これらのメソッドをオーバライドのためのコードを確認してください。ところで、もしメソッドがオーバライドされない場合は、Node.clipboardCopyNode.clipboardCut及びNode.pasteTypesにデフォルトで移譲されます。

質問(format-clipboard): (もしあれば)コードは(java.awt.datatransfer.Transferableメソッドを呼び出す方法でクリップボードにアクセスすることにより)どのdata flavorをクリップボードとやりとりしますか?
ヒント: しばしばノードはNode.clipboardCopyNode.clipboardCut及びNode.pasteTypesの使用によりクリップボードを扱います。これらのメソッドをオーバライドするコードを確認してください。

コミットの前に

モジュールの開発が最終段階に達し、使用する準備ができたと主張する前に、上記の全ての質問を眺めて、それらがまだ有効であることを確認した上で、下記の質問を確認してください。

質問(dep-jre): どのバージョンのJRE(1.2、1.3、1.4等)が必要ですか?
ヒント: モジュールが1.xで動作するのであれば、1.x+1で動作することが期待されます。もし違うのであれば記載してください。異なるバージョンのJRE上で異なるコードが実行される場合は理由とともにここに記載してください。

質問(dep-jrejdk): JDKが必要でしょうか?それともJREで十分でしょうか?

質問(deploy-shared): 共有場所のみにインストール可能でしょうか? もしくはユーザディレクトリのみでしょうか? もしくはどこにインストールされても動作可能でしょうか?
ヒント: インストール場所は不問であるべきですが、もしそうなければ理由を説明してください。InstalledFileLocatorが助けになるかどうかを検討してください。

質問(deploy-dependencies): 通常のモジュール依存定義(例えばrequireに対するトークン)に対して付加、もしくは代替してモジュールに対する依存を明示するために他のモジュールは何をすべきでしょうか?
ヒント: 依存を明示するためにモジュールマニフェストに付加すべき実際の行の例を提示してください。例えば、OpenIDE-Module-Requires: some.tokenのようにです。もし他のモジュールがこのモジュールに依存すべきでない場合、もしくは単に単純な通常のモジュール依存を使うべきであれば、単に"なし"と回答してください。もし実装依存を使用して意図して半安定したAPIをクライアントに開示するのであれば、ここに記載すべきです(しかし、使用方法の例を提示する必要はありません)。

質問(resources-file): モジュールはjava.o.Fileを直接使用していますか?
ヒント: NetBeansorg.openide.filesystems.FileObjectと呼ばれるプレーンファイルに対する論理ラッパーを提供しており、そのようなリソースに対する統一的なアクセスを提供しています。このラッパーは使用されるべき好ましい方法です。しかしこの選択肢が適していない状況もあり得ます。

質問(resources-layer): モジュールは自身のレイヤを提供していますか? 何らかのファイルやフォルダを作成しますか? それにより何をどのコンポーネントとやりとりしようとしていますか?
ヒント: NetBeansはモジュールレイヤにより自動かつ宣言的なリソースのインストールを可能としています。モジュールは適切な場所にファイルを登録し、他のコンポーネントはタスク(メニューやツールバー、ウィンドウレイアウト、テンプレートのリスト、オプション、等々のビルド)を実行するためのそのような情報を利用します。

質問(resources-read): モジュールはレイヤから何らかのリソースを読み取りますか? 何の目的のためでしょうか?
ヒント: これはある種のモジュール内依存であるため、APIの一種です。リソースを記載し、共通の安定性カテゴリに対応してそれらを分類してください。

質問(resources-mask): モジュールは他のモジュールによって提供された何らかのリソースをレイヤ内でマスク/隠蔽/オーバライドしますか?
ヒント: もし他のモジュールによって提供されたファイルをマスクするのであれば、おそらくそれに依存があり(例えば)ファイルの名前を他のモジュールに変更されたくはないでしょう。そのモジュールは従ってある安定性カテゴリのAPIとしてファイルを利用可能とすべきです。

質問(resources-preferences): モジュールはPreference APIを通じてプリファレンスを使用していますか? モジュールはNbPreferencesもしくは通常のJDKプリファレンスを使用していますか? 読み取り、書き込み、もしくは両方ですか? プリファレンスを他のモジュールと共有しますか? そうしているのであれば、理由は何ですか?
ヒント: <api type="export" group="preferences" name="preference node name" category="private">個々のキー記述であり、どこでそれが使用されるか、何に影響するか、モジュールが読みこむか書き込むか等の記述を行う</api>を使用します。XML IDの制限により、/org/netbeans/modules/fooではなくorg.netbeans.modules.fooとして"name"を与えます。NbPreferencesを使用する場合、この名前はモジュールのコード名のベースと同じである必要があります。

質問(lookup-register): 他のコードが発見できるようにlookupに何か登録しますか?
ヒント: レイヤファイルもしくはMETA-INF/サービスを用いて登録しますか? 誰がコンポーネントを発見すると予想されますか?

質問(lookup-remove): lookupから他のモジュールのエントリを削除しますか?
ヒント: 理由は? もちろん、それは可能ですが危険かもしれません。リソースをマスクしたモジュールはあなたのしたことを気にするでしょうか?

質問(exec-privateaccess): システムの他の部分がリフレクションを使用して幾つかのメソッドを呼び出すことを意識しますか?
ヒント: もしそうなら、APIとして"契約"を記述してください。おそらくprivateもしくはfriendだと思いますが、APIではあるため修正することを検討してください。

質問(security-policy): 機能性は標準ポリシーファイルに対する変更を必要としていますか?
ヒント: コードは信用されたドメインからでないサードパーティのコードに対する制御を渡すでしょう。これはネットワークを通じてサードパーティのコードをダウンロードしたり、NetBeansにバンドルされていないライブラリからのコードである可能性があります。どのドメインに対して権限を与えるためにパーミッションを必要としますか?

質問(security-grant): コードは他の何かのコードに対する追加の権利を与えますか?
ヒント: 本当に必要でない限りロードされたコードに余分なパーミッションを付加するクラスローダを使用することは避けてください。API実装はAccessController.doPrivileged()の呼び出しにより攻撃コードに対する不要なパーミッションを露出しうることも注意してください。

質問(perf-startup): モジュールはスタートアップに何らかのコードを実行しますか?

質問(perf-exit): モジュールは終了時に何らかのコードを実行しますか?

質問(perf-mem): コンポーネントはどれくらいのメモリを消費しますか? ウィンドウの数などに関連付けて見積もってください。

質問(perf-wakeup): コードの何らかの部分は定期的に起床して、システム全体がアイドル(ユーザインタラクションがない)時にも何かを行いますか?

質問(perf-progress): モジュールは何らかの長時間実行タスクを実行しますか?
ヒント: 長時間実行タスクはUI反応をひどく悪くするためAWTスレッドをブロックすべきではありません。ネットワーク接続、大量データの演算、コンパイルのようなタスクは(例えばRequestProcessorを用いて)非同期に行われ、最終的にはAWTスレッドをブロックすべきではありません。

質問(perf-huge_dialogs): モジュールはコンボボックスやリスト、ツリー、もしくはテキストエリアのような大量のGUIコントロールを含むダイヤログやウィザードを含みますか?

質問(perf-menus): モジュールは動的に更新されるコンテキストメニュー、もしくはコンテキストを意識した複雑で低速な有効かロジックを持つアクションを用いていますか?
ヒント: もし標準もしくはコンテキストメニューにアクションを追加するときに多くのトリックをしているのであれば、ユーザがアクションを使用しない場合でさえもメニューの表示を明らかに低速にする可能性があります。メインメニューバーと外部ノードもしくはコンポーネントコンテキストメニューにアクションを追加する際は注意してください。もしアクションが条件付きで有効かされるか、表示を動的に変更するのであれば、パフォーマンスへの影響を確認することが必要です。あるケースではダイアログ内で実際に実行する場合に詳細な確認を行い、項目はいつも有効化する単純なアクションにすることがより適しているかもしれません。