|
JavaScript 2.0
根拠
バージョン管理
|
10/09/2001 (Tue)
パッケージのバージョンアップに伴い、大抵の場合エクスポート済みインターフェイスに変更が必要になる。このような変更には (トップレベルやクラスのメンバの) 定義の削除や名前の変更も含まれるが、ほとんどは新しい定義の追加である。全ての JavaScript ソースが同じソースから構成されるような単一の環境ではこれは問題ではないが、パッケージが複数のソースから動的にリンクされる場合はおそらくバージョン管理の問題が発生するだろう。
最もよく知られた問題に定義の衝突がある。この問題を解決しない限り、ライブラリの作者はそのライブラリの将来のバージョンに新しい定義を1つも追加できない。これは新しい定義名が既にクライアント、或いはクライアントがリンクしている他のライブラリで使われているかもしれないからである。この問題はグローバルスコープ、クライアントが継承可能なクラススコープの両方で起こる。
以下はこのような衝突がいかにして起こるかを示す例である。或るライブラリの作者がクラス Data をエクスポートするライブラリ
BitTracker を作成したとしよう。このライブラリの出来は素晴らしいものであったので BrowsersRUs 社全てのウェブブラウザに搭載されることとなった:
package BitTracker;
public class Data {
public field author; // 訳注: field は今は使われていない古いメンバ修飾子である
public field contents; // 以下の method も同様
function save() {...}
};
function store(d) {
...
storeOnFastDisk(d);
}
今誰か他の人間が BitTracker を使ったウェブページ W を書いている。クラス Picture
は Data から派生したもので、2つのメンバを追加している。その内 size というメソッドは画像の寸法を返す:
import BitTracker;
class Picture extends Data {
public method size() {...}
field palette;
};
function orientation(d) {
if (d.size().h >= d.size().v)
return "Landscape";
else
return "Portrait";
}
BitTracker ライブラリの作者は W を見ておらず、顧客の要求に応える形で Data
オブジェクトデータのバイト長を返す size というメソッドの追加を決めてしまう。彼は改良した新しい BitTracker
ライブラリをリリースし、BrowsersRUs は最新のブラウザ NavigatorForInternetComputing 17.0 にこのライブラリを組み込む:
package BitTracker;
public class Data {
public field author;
public field contents;
public method size() {...}
function save() {...}
};
function store(d) {
...
if (d.size() > limit)
storeOnSlowDisk(d);
else
storeOnFastDisk(d);
}
何も知らないユーザ U は手持ちの BrowsersRUs の古いブラウザを最新の NavigatorForInternetComputing 17.0
にアップグレードするが、その1週間後 W が動かないのを見てがっかりする。U の孫娘 Alyssa はコンピュータ使いであり、size
メソッドで名前の衝突が起こったことを U に説明するが、U には何のことだか分からない。U
は W の作者に連絡しようとするが、彼女は自己発見の伝道のためにサハラ以南のアフリカに行ってしまっている。結局 U は BrowsersRUs に文句を言い、その怒りの矛先は BitTracker の作者に向けられることとなる。
BitTracker の作者はどうすればこの問題を避けることができたのだろうか? 単純に size
以外の名前を選択してもうまくいかない。他のページ W2 がその新しい名前と衝突する可能性があるのである。考えられるアプローチとしては次のようなものがある:
edu_mit_length メソッドを使い、Netscape のオブジェクトが全て com_netscape_length
メソッドを使うようにすると、同じ名前にするのは不可能である。最後のアプローチは、使用するパッケージをインポートするだけかインポート文で現在のバージョンしか指定しない腰掛け気分のユーザの負荷を最小にするため最も望ましいものである。パッケージの開発者は更新したパッケージをリリースする際に、以前のバージョンでアクセス可能な定義セットを乱さないように注意すべきである。しかし動的リンクが可能なパッケージの作者には言語の上級ユーザが多く、更新されたパッケージの構成を自動的にチェックするツールを使うのかもしれないが。
JavaScript 2.0 では安全なバージョン管理のために名前空間を使う。1つのパッケージは複数の名前空間をエクスポートでき、パッケージの内容についてそれぞれ異なるアクセス性を提供する。各名前空間はパッケージ API の1つのバージョンに対応する。
バージョンはパッケージの API を表す。リリースとはコードも含めたパッケージ全体を指す。1つのリリースはその API の複数のバージョンをエクスポートできる。パッケージの開発者は、バージョン V をエクスポートするパッケージの複数のリリースが、バージョン V において同じ定義セットを正しくエクスポートするかどうかを確認すべきである。
例としてソートのためのパッケージ Sorter を書いている開発者がいるとしよう。このパッケージの最初のバージョンには
sort と merge という関数が含まれており、バブルソートを使っていた。次のリリースでプログラマは
stablesort という関数を追加し、バージョン V2 に含めた。次の V3 リリースでは
sort のアルゴリズムをクイックソートを使ったものに変更し stablesort
をサブルーチンとして呼び出すようにした。また permute という関数も追加した。このパッケージの最終リリースは次のようになるだろう:
package Sorter {
explicit namespace V2;
explicit namespace V3;
internal const V2and3 = V2 V3;
public var serialNumber;
public function sort(compare: Function, array: Array):Array {...}
public function merge(compare: Function, array1: Array, array2: Array):Array {...}
V2and3 function stablesort(compare: Function, array: Array):Array {...}
V3 function permute(array: Array):Array {...}
}
ここでいずれも Sorter をインポートするクライアントパッケージ C1
、C2 が現れたとする。実行される Sorter のインスタンスは1つだけ
— つまり最終リリースのものだけである。デフォルトでは C1
と C2 はいずれも Sorter の最初の API にしかアクセスできない。しかし
C2 が Sorter が拡張されたことを知っており、新しい機能も使いたいという場合は
Sorter をインポートした後に次のようなおまじないを使う。
use namespace(Sorter.V2);
これで C2 からは stablesort 関数にもアクセスできるようになる。この例では両方のクライアントは同じ
sort 、merge 関数と、同じ変数 serialNumber にアクセスできる
(C1 が serialNumber を書き換えると C2
からも値が変更されたことが分かる) が、stablesort 関数にアクセスできるのは C2
だけだということに注意していただきたい。2つのクライアントは両方ともクイックソートを使った sort のリリースを得る。仮に
C1 が自身で stablesort という関数を定義しても Sorter の
stablesort とは衝突しない。また、Sorter の sort はその内部サブルーチン呼び出しで
Sorter の stablesort を参照し続ける。
Sorter の最初のリリースしか利用できなければ Sorter.V2 は未定義となり、C2
ではエラーが発生する。sort はクイックソートではなくバブルソートを使うのだがそれでも
C1 は正常に動く。
この例ではパッケージのグローバルメンバに対するバージョン管理について示した。このテクニックは既存のクラスにメンバを追加するのにも使用でき、より有用なものととなる。
|
Waldemar Horwat 最終更新: 2001年10月9日 (火) |