たくさん JavaScript のコードを書いてみて、ようやく自分なりのスタイルが確立されようとしているので、記しておきます。なお、クラスの定義には、prototype.js 1.6 系を使います。
Crockford先生のおしえ
まず大前提として、Crockford先生のおしえをなるべく守るようにします。この教えを守れば、JSLintの祝福があります。JavaScript のコードを書いたら、必ず JSLint に通し、満点をもらうまで頑張りましょう。
「if の後は、たとえ一文しかなくともブロック({})を使え」と書かれていますね。自分のスタイルに合わないかもしれませんが、我慢我慢。我を通すよりも JSLint に愛される方が大切です。
ただ、僕は一つ教えを破ることにしました。プライベートな変数や関数は、"_" で始めます。これについては、後述します。
名前空間
グローバルな名前空間はなるべく消費しないようにしましょう。小さいプログラムなら、消費する名前は一つで十分です。
名前を予約するには、その名前を持つ変数に、空のオブジェクトを代入します。
var FOO = {};
そして、その名前の下にわらわらとクラスを定義します。
FOO.Model = Class.create( ... ); FOO.View = Class.create( ... ); FOO.Controller = Class.create( ... );
変数名と関数名
JavaScript では、特殊な方法を使わない限り、変数も関数もすべてパブリックであり、どこからでもアクセスできます。しかし、頑健なコードを書くには、情報隠蔽がかかせません。以下では、名前付けによって問題点を発見する方法を考えます。パブリックやプライベートと言った場合、それはコードを書いた人が、そう思っているという意味です。
僕のスタイルでは、パブリックなインスタンス変数は使いません。インスタンス変数は必ずプライベートにし、パブリックな getter/setter を提供して、クラス外からはそれらを使ってアクセスします。
プライベートな変数名/関数名は、"_" で始めます。パブリックな変数名/関数名は、アルファベットの小文字で始めます。単語の区切りでは、大文字を使います。("_" は使いません。)
FOO.Model = Class.create({ initialize: function(id) { this._id = id; this._keyword = null; this._results= null; }, getResults: function() { return this._results; }, // Controller への API inputKeyword: function(keyword) { this._keyword = keyword; this._search(); }, _search: function() { // サーバに問い合わせるコード }, // JSONP への API update: function() { // サーバからの応答を受け取るコード this._results = ...; } });
こうすると、プライベートな変数と関数をクラス内から参照するときは、かならず "this._XXX" という形をとることになります。
"this" 以外の変数に "._" が続く場合は、プライベートな変数/関数にクラス外からアクセスしていることになるので誤りです。コードから "this" 以外の変数に "._" が続くパターンを取り出せば、不正なアクセスを見つけることができます。
"this." の後に "_" が続かないのは、パブリックなインスタンス関数をクラス内から参照する場合です。コードから "this." の後に "_" が続かないパターンを抽出してみましょう。それがパブリックなインスタンス関数であれば OK です。しかし、プライベートなインスタンス関数であれば、名前付けが間違っていることになります。つまり、プライベートなのにアルファベットで始めています。パブリックなインスタンス関数をプライベートに直したときに犯しそうな間違いですね。
以下の Python のコードを使えば、上記2種類の誤りを発見できます(Python は初心者なので、変かもしれません。)。
#!/usr/bin/python import sys import re argv = sys.argv[1:] illegalAccess = re.compile('([$_a-zA-Z0-9]+)\._') illegalName = re.compile(r'\bthis\.[^_]') if len(argv) != 1: def printLine(file, num, line): print "%s(%d):%s" % (file, num, line) else: def printLine(file, num, line): print "%d:%s" % (num, line) def jscheck(file, f): i = 0 for line in f: line = line.rstrip() i += 1 m = illegalAccess.search(line, 0) while m: if m.group(1) != 'this': printLine(file, i, line) break m = illegalAccess.search(line, m.end(1)) if illegalName.search(line): printLine(file, i, line) for file in argv: try: f = open(file) jscheck(file, f) f.close() except IOError: print "Can't find file:", file
JavaScript はアクセス制御機構がないので、このように運用?で頑張ります。まぁ、悪いところは発見できるのですから、コンパイラーが見つけてくれるのか、お手製のスクリプトが見つけてくるのかぐらいの違いしかないのかもしれません。
クラス変数とクラス関数
クラス変数とクラス関数は、必要であれば以下のようにして定義します。
Foo.View.NumberOfItems = 16; Foo.View.manipulateFormat = function() { ... };
クラス変数とクラス関数は、なるべく使わないようにしましょう。これらは本質的にグローバル変数/関数です。他のクラスから利用されると、クラス間の結合力が強くなってしまいます。使う場合は、説得力のある理由が必要でしょう。以下は、そのよい例になっています。
静的な関数名
HTML から呼び出す関数名が欲しいなら、クラス関数と同じ方法で定義します。
FOO.run = function() { var controller = new FOO.Controller(); controller.run(); };
これを HTML の body タグに書きます。
<body onload="Foo.run();"> </body>
静的なインスタンス名
インスタンスに静的な名前を付けたくなることがあります。たとえば、JSONP がよい例でしょう。インスタンスに名前を付け、そのインスタンス関数を JSONP で指定します。
FOO.model = new FOO.Model(); // new は別のところでやってもよい
こうしておいて JSONP には、たとえば callback=FOO.model.update のように指定します。
無名関数
JavaScript が呼び込まれた際に実行したいコードがあれば、無名関数を使って実行します。
(function() { var controller = new FOO.Controller(); controller.run(); })();