モジュール性

この章はプログラムを整理するプロセスを扱う。小さなプログラムでは、整理が問題になることはまれだ。プログラムが大きくなるにつれて、しかしながら、その構造と解釈が追跡しづらいサイズにまで達することがある。簡単に、そのようなプログラムは皿の中のスパゲッティのように見え始め、全てが他の全てに繋がった不定形の塊となる。

プログラムを構造化するとき、2つのことを行う。それぞれが個別の役割を持つ、モジュールと呼ばれる、より小さい部品に分割すること、およびそれらの部品の間の関係を設定することである。

8章で、飼育器を開発するとき、6章で説明したいくつもの関数を使用した。8章では、cloneとかDictionary型のような、飼育器と何の関係も無いいくつかの新しい概念も定義した。これらの全ては行き当たりばったりに環境に追加された。このプログラムをモジュールに分割する1つの方法は:

モジュールがもう1つのモジュールに依存するとき、そのモジュールからの関数や変数を使うことができ、そしてこのモジュールがロードされたときのみ働く。

依存が循環しないように確実にするのは良いアイデアだ。循環的な依存は実用上の問題(もしモジュールAとBが互いに依存し合っていたら、どちらを先にロードすれば良いだろうか?)であるだけでなく、モジュール間の関係もわかりにくくなり、モジュール化の結果も先ほどのスパゲッティのようになるかもしれない。


多くの現代的なプログラミング言語は何らかの種類の組み込みのモジュールシステムを持っている。JavaScriptはそうではない。もう一度、我々自身で何かを発明しなければならない。全てのモジュールを異なるファイルに入れることから始めるのがもっとも明白なやり方だ。これでどのコードがどのモジュールにあるかが明白になる。

ブラウザは、src属性を持つ<script>タグをウェブページのHTMLに見つけたときにJavaScriptファイルをロードする。通常は拡張子.jsがJavaScriptのコードを含むファイルに使われる。コンソール上では、ファイルのロードのショートカットがload関数によって提供されている。

load("FunctionalTools.js");

いくつかの場合、間違った順番でloadコマンドを与えると結果がエラーになる。もしモジュールがDictionaryオブジェクトを作ろうとしたが、Dictionaryモジュールがまだロードされていなかったら、コンストラクターを見つけることができず、作成に失敗する。

ある者はこれを解決するのは簡単だと思うだろう。それが依存しているモジュールをファイルの冒頭でloadするようにするだけだ。残念ながら、ブラウザの動作により、loadの呼び出しはロードするように与えられたファイルの順番にはなされない。現在のファイルの実行が終了するまでファイルのロードは実行されないのだ。それでは遅すぎる、通常は。

多くの場合で、実用的な解決は手作業で依存を管理することでしかない。HTML文書上のscriptタグを正しい順番で並べるのだ。


(部分的に)自動的な依存管理を行う2つの方法がある。1つはモジュール間の依存に関する情報をファイルに分割しておくことだ。これは最初にロードされ、ファイルがロードされる順番を明示するのに使われる。2つめの手段はscriptタグを使わず(loadは内部的にそのようなタグを作って追加する)、ファイルのディレクトリ(14章を参照)の内容を読みとって、それからeval関数を使って実行する。これでスクリプトが即座にロードされるので、取り扱うのはより簡単になる。

evalは’evaluate’の短縮形で、面白い関数だ。文字列の値を与えると、その文字列の中身をJavaScriptのコードとみなして実行するのだ。

eval("print(\"I am a string inside a string!\");");

evalは面白いことに使えると想像できるだろう。コードによってコードを組み立て、実行することができる。多くの場合では、しかしながら、evalの創造的な使用で解決できる問題は無名関数の創造的な使用によっても解決することができ、そして後者の方がおかしな問題を引き起こすようなことが少ない。

evalが関数の中で呼び出されたとき、全ての新しい変数がその関数ローカルなものとなる。それで、loadのバリエーションがevalを内部で使用したとき、Dictionaryモジュールのロードによって、load関数の中でDictionaryコンストラクターが作られ、関数から返ったときには失われてしまう。これを動かす方法はあるが、むしろ余計に不細工なものとなる。


依存を管理する1つめの方法をざっと見てみよう。依存の情報のために特別なファイルを必要とし、それはこのようなものになる。:

var dependencies =
  {"ObjectTools.js": ["FunctionalTools.js"],
   "Dictionary.js":  ["ObjectTools.js"],
   "TestModule.js":  ["FunctionalTools.js", "Dictionary.js"]};

dependenciesオブジェクトは、他のファイルに依存しているそれぞれのファイルをプロパティに持つ。プロパティの値はファイル名の配列である。ここでDictionaryオブジェクトを使えないことに注意しよう、なぜならDictionaryモジュールはまだロードされていないからである。このオブジェクトの全てのプロパティは".js"で終わっているため、__proto__hasOwnPropertyのような隠されたプロパティに邪魔されるようなこともなく、正規のオブジェクトは正しく動くだろう。

依存性マネージャーは2つのことをなさねばならない。1つめは、ロードされるファイルが依存しているファイルをそれ自身より先にロードすることにより、ファイルが正しい順序でロードされるのを確実にすることである。そして2つめは、同じファイルを2回ロードしないことである。同じファイルのロードは問題を引き起こすかも知れず、また明らかに時間の浪費である。

var loadedFiles = {};

function require(file) {
  if (dependencies[file]) {
    var files = dependencies[file];
    for (var i = 0; i < files.length; i++)
      require(files[i]);
  }
  if (!loadedFiles[file]) {
    loadedFiles[file] = true;
    load(file);
  }
}

require関数はファイルとそれが依存しているものをロードするのに使われる。依存(そして依存の依存の可能性)を扱うための再帰の呼び出し方に注意しよう。

require("TestModule.js");
test();

良い、小さいモジュールのセットとしてプログラムを組み立てることは、プログラムが多くの異なるファイルを使うことをしばしば意味する。ウェブのプログラミングをするとき、1つのページにたくさんの、小さいJavaScriptファイルがあることはページのロードを遅くする。それでもこれは問題にはならない。数多くの小さいファイルとしてプログラムを書きテストでき、その上でウェブに公開するときに単一の大きなファイルにまとめれば良いのである。


オブジェクト型のように、モジュールもインターフェースを持つ。FunctionToolsのような単純な関数のコレクションのモジュールでは、インターフェースは通常、モジュールで定義されている全ての関数から成り立つ。他の場合では、モジュールのインターフェースは中で定義されている関数のほんの一部のみである。例えば、6章の文書HTML化システムは一つの関数、renderFileのインターフェースのみ必要とする。(HTML組み立てのサブシステムは別のモジュールに分割される。)

Dictionary型のように、単一の型のオブジェクトしか定義しないモジュールでは、オブジェクトのインターフェースはモジュールのインターフェースと同一である。


JavaScriptでは、’トップレベル’の変数は1つの場所でともに生きている。ブラウザでは、この場所はwindowという名のオブジェクトとして見つけることができる。この名前は奇妙であり、environmenttopの方がより理にかなっていただろう、しかしブラウザがJavaScript環境をウインドウ(または’フレーム’)に結びつけてしまったため、誰かがwindowが論理的な名前であると決定してしまった。

show(window);
show(window.print == print);
show(window.window.window.window.window);

3行目は、windowという名前は、ただこの環境オブジェクトのプロパティであり、それ自体を指している、ということを示している。


多くのコードが1つの環境にロードされると、トップレベルの変数名が多く使われる。一度自分が本当に把握できる以上にコードが大きくなると、他のところで既に使われている名前を誤って使ってしまうことが簡単に起こる。これは元のコードが持っていた値を破壊する。トップレベルの変数の増加は名前空間の汚染と呼ばれ、JavaScriptではむしろシビアな問題だ。 ― 存在している変数を再定義しても言語は警告を発しないのだ。

この問題を完全に解決する手段は無いが、可能な限り汚染が引き起こされる範囲を狭めることはできる。1つ挙げるとすれば、モジュールは外部へのインターフェースの一部でない値にはトップレベルの変数を使うことの無いようにすべきだ。


モジュールの中のどこにでも、内部の関数と変数を任意に定義できないことは、もちろん、あまり実用的では無い。幸運にも、これに関するトリックが存在する。モジュールの全てのコードを関数の中に書くことができ、それから最終的にwindowオブジェクトに、モジュールのインターフェースの一部である変数を追加するのである。同一の親関数の中で作られているため、モジュールの全ての関数は互いを参照する事ができ、しかし、モジュールの外には参照させないということができる。

function buildMonthNameModule() {
  var names = ["January", "February", "March", "April",
               "May", "June", "July", "August", "September",
               "October", "November", "December"];
  function getMonthName(number) {
    return names[number];
  }
  function getMonthNumber(name) {
    for (var number = 0; number < names.length; number++) {
      if (names[number] == name)
        return number;
    }
  }

  window.getMonthName = getMonthName;
  window.getMonthNumber = getMonthNumber;
}
buildMonthNameModule();

show(getMonthName(11));

これは、月の名前とその数(Dateにおいてと同様、1月は0である)を変換するとても単純なモジュールだ。しかしbuikdMonthNameModuleはまだモジュールのインターフェースの一部でないトップレベル変数であることに注意しよう。また、インターフェース関数の名前を3回も繰り返している。ぐふっ。


1つめの問題はモジュール関数を無名で作り、直接呼び出すことで解決できる。これを行うため、関数の値を1組の括弧で囲んで、JavaScriptがそれを直接呼び出すことのできない正常の関数定義だと考えるようにしよう。

2つめの問題はprovideというヘルパー関数で解決できる。windowオブジェクトにexportしなければならない値を含むオブジェクトをそれに与える。

function provide(values) {
  forEachIn(values, function(name, value) {
    window[name] = value;
  });
}

これを使って、モジュールをこのように書く事ができる。:

(function() {
  var names = ["Sunday", "Monday", "Tuesday", "Wednesday",
               "Thursday", "Friday", "Saturday"];
  provide({
    getDayName: function(number) {
      return names[number];
    },
    getDayNumber: function(name) {
      for (var number = 0; number < names.length; number++) {
        if (names[number] == name)
          return number;
      }
    }
  });
})();

show(getDayNumber("Wednesday"));

最初からこのように正しいモジュールを書くことを私はおすすめしない。コードの部品の作業をやっている間は、全てをトップレベルに置く、これまでのような単純なアプローチをただ使う方が簡単だ。その方法で、ブラウザでモジュール内部の値を見る事ができ、テストできる。一度モジュールが完成すれば、それを関数でラップする事は難しくない。


モジュールが多くの変数をエクスポートするだろう時に、全てをトップレベルの環境に置く事が良くないアイデアである場合もある。このようなケースの場合、標準のMathオブジェクトがやるように、モジュールをプロパティが関数とそれがエクスポートする値である単一のオブジェクトとして表現することができる。例えば…

var HTML = {
  tag: function(name, content, properties) {
    return {name: name, properties: properties, content: content};
  },
  link: function(target, text) {
    return HTML.tag("a", [text], {href: target});
  }
  /* ... many more HTML-producing functions ... */
};

そのようなモジュールの内容が必要なときに、絶えずHTMLとタイプすることはしばしば面倒であるので、provideを使っていつでもトップレベルの環境に移動することができる。

provide(HTML);
show(link("http://docs.sun.com/source/816-6408-10/object.htm",
          "This is how objects work."));

モジュールの内部の変数を関数の中に入れ、そしてこの関数がその外部へのインターフェースを含むオブジェクト返すことで、関数とオブジェクトのアプローチを連携することができる。


ArrayObjectのような標準のプロトタイプにメソッドを追加したとき、名前空間の汚染と同様の問題が起きる。もし2つのモジュールがArray.prototypemapメソッドを追加しようと判断したら、問題を抱えることになる。もしこれら2つのバージョンのmapが正確に同様の効果を持っていたら、物事は働き続けるだろうが、それは全くの幸運によるものだ。


モジュールやオブジェクト型のインターフェースの設計はプログラミングの微妙な面の1つだ。一方では、あまり多くの詳細を晒そうとは思わないだろう。モジュールを使う時だけその手段を得られる。そのもう一方で、あまり単純で一般的にしすぎたく無いとも思う。なぜならそれによってモジュールを、複雑な、あるいは特別な状況で使うのが不可能になるかもしれないからだ。

時折、その解決は、複雑な物事のための詳細な’低レベル’のもの、およびまっすぐな状況のための単純な’高レベル’のもの2つのインターフェースを提供することである。2つめのものは、通常、1つめのものによって提供されたツールを使って簡単に組み立てることができる。

他の場合においては、インターフェースをベースに正しいアイデアを探すしかない。これと、8章で見たいろいろな継承のアプローチを比較しよう。むしろコンストラクターより、プロトタイプを中心の概念とすることによって、物事をかなり素直に作ることができる。

良いインターフェース設計の価値を学ぶ最善の手は、残念ながら、悪いインターフェースを使うことだ。一度これらを与えられたら、それを改善する手を考え出し、その過程で多くを学ぶだろう。質の悪いインターフェースを’こうするしかない’ものであると仮定しないこと。それを直すか、あるいは新しいインターフェースでラップする方が良い。(12章でその例を見よう)


多くの引数を必要とする関数がある。時々これはただ悪いデザインであることを意味し、複数の適度な関数に分割することで簡単に改善できる。しかし、他の場合においては、そのような手段が無い。典型的には、これらの引数のいくつかはふさわしい’デフォルトの’値を持っている。例えば、rangeのまた別の拡張版を書くことができる。

function range(start, end, stepSize, length) {
  if (stepSize == undefined)
    stepSize = 1;
  if (end == undefined)
    end = start + stepSize * (length - 1);

  var result = [];
  for (; start <= end; start += stepSize)
    result.push(start);
  return result;
}

show(range(0, undefined, 4, 5));

length引数が使われるとき、2つめの引数としてundefinedを渡すことの面倒さに言及することなく、その引数が何を示すか覚えておくのは難しいだろう。それらを1つのオブジェクトにラップすることで、この関数へ渡す引数をより包括的なものにすることができる。

function defaultTo(object, values) {
  forEachIn(values, function(name, value) {
    if (!object.hasOwnProperty(name))
      object[name] = value;
  });
}

function range(args) {
  defaultTo(args, {start: 0, stepSize: 1});
  if (args.end == undefined)
    args.end = args.start + args.stepSize * (args.length - 1);

  var result = [];
  for (; args.start <= args.end; args.start += args.stepSize)
    result.push(args.start);
  return result;
}

show(range({stepSize: 4, length: 5}));

defaultTo関数はデフォルト値をオブジェクトに追加するのに有益だ。その1つめの引数に2つめの引数のプロパティをコピーし、既にその値が存在しているときはスキップする。


1本より多くのプログラムにとって有益になり得るモジュールまたはモジュールのグループは、通常ライブラリと呼ばれる。多くのプログラミング言語では、良質のライブラリの巨大なセットが利用可能である。これはプログラマーがいつも一から作るところから始めなくても良く、その方がより生産的だということを意味する。JavaScriptでは、残念ながら、利用可能なライブラリの数はあまり大きくない。

しかしこれは最近改善されているようだ。mapcloneのような’基本的な’ツールの良いライブラリが数を多くある。他の言語では、このように明らかに有益なものは組み込みの標準として提供される傾向にあるが、JavaScriptではこれらのコレクションを自分自身で組むか、ライブラリを使わねばならない。ライブラリを使うことを勧める:楽だし、ライブラリのコードは通常、自分で書いたものより多くのテストを受けている。

これらの基本をカバーし、(他に比べて)’ライトウェイトな’ライブラリがある。prototypemootoolsjQuery、そしてMochiKit。より大きな’フレームワーク’も利用可能であり、基本的なツールの集合を提供する以上のことをしてくれる。YUI(Yahoo製)、Dojoがそのジャンルの最も人気のあるもののようだ。これらの全てがダウンロード可能であり、金銭の支払なしに使うことができる。私の個人的なお気に入りはMochikitだが、しかしこれは好みの問題だ。本格的なJavaScriptプログラミングに取りかかるときに、これらそれぞれの文書をざっと眺めておき、それがどのように働くかの全般的なアイデアとそれが提供するものを見ておくと良いだろう。

事実上、これらの基本的な道具箱は、平凡以上のJavaScriptプログラムのためにほぼ必須のものとなっており、多くの異なる道具箱の組み合わせが、ライブラリの作成者にちょっとしたジレンマをもたらしている。ライブラリを1つの道具箱に依存するようにするか、基本的な道具は自分で書いてライブラリに入れるかしなければならない。1つめのオプションはライブラリを異なる道具箱を使う人々にとって使いにくいものにするし、2つめのオプションでは本質的でないたくさんのコードがライブラリに追加される。このジレンマが似たような幅広く使われるJavaScriptライブラリが存在する理由の一つになっているかもしれない。可能性としては、将来的に、ECMAScriptの新しいバージョンとブラウザの変更が、道具箱の必要性を減らし、それで(部分的には)この問題が解決されるかもしれない。