Chapter 2. Essentials
2010/12/27(月) 25:41 Javascript親記事へこのエントリーをはてなブックマークに追加

Writing Maintainable Code

  • Time to relearn and understand the problem
  • Time to understand the code that is supposed to solve the problem
チームだど必ずしもバグを発見する人が自分ではない場合もあるという問題も存在する。
どちらにしても書かれたコードを理解する時間を減らすことはとても重要で、レガシーコードをメンテしてるより新しいものをいじってたほうが楽しいでしょ。
その場しのぎで書かれたコードは後で問題が起きることがよくあるので、メンテナンス性に優れたコードを書くことが成功するアプリケーションにつながるのです。
メンテナンス性に優れたコードとは
  • Is readable
  • Is consistent
  • Is predictable
  • Looks as if it was written by the same person
  • Is documented

Minimizing Globals

The Problem with Globals

グローバルな名前空間は衝突することがあります。
Webページでは開発者以外によって書かれたコードが動いてることもよくあるためグローバル空間は意識する必要があります。
  • サードパーティなJavaScriptライブラリ
  • 広告スクリプト
  • アクセス解析スクリプト
  • Wigetsやバッチやボタンなど

Side Effects When Forgetting var

varをつけて作られたものはvariableなのでdeleteできません。
逆にvarをつけずに作られたものはvariableではなくグローバルのプロパティであるのでdeleteすることが可能です。
// define three globals
var global_var = 1;
global_novar = 2; // antipattern
(function () {
    global_fromfunc = 3; // antipattern
}());
// attempt to delete
delete global_var; // false
delete global_novar; // true
delete global_fromfunc; // true
// test the deletion
typeof global_var; // "number"
typeof global_novar; // "undefined"
typeof global_fromfunc; // "undefined"
variableはdeleteできないけど、プロパティはdeleteできる

Access to the Global Object

Webページだとグローバルオブジェクトはwindowだが、他の実行環境だと異なる場合もある。
そのため、グローバルオブジェクトを取得しようした場合には次のように書ける。
var global = (function () {
    return this;
}());
このときのthisはグローバルオブジェクトを示している。JavaScriptが実行される環境に左右されないコードを書くためには必要となるかもしれない。

Single var Pattern

一つのvarだけで複数の変数を宣言するパターン
function func() {
    var a = 1,
        b = 2,
        sum = a + b,
        myobject = {},
        i,
        j;
    // function body...
}
利点としては
  • 一カ所に変数をまとめて置ける
  • 宣言される前に変数が参照されてエラーになるのを防げる
  • 宣言された変数を覚えやすい、見やすい
  • コードを小さくするのに役立つ
またsingle varでも、宣言しながら処理も可能だ
下のように、var a,bとしたときJavaScriptでは左から順番に実行していくのでelを宣言して次の,でelを利用した変数を宣言することができる。
function updateElement() {
    var el = document.getElementById("result"),
        style = el.style;
    // do something with el and style...
}

Hoisting: A Problem with Scattered vars

関数内でvarを宣言したとき、まるで関数の先頭で宣言されたような振る舞いはhoistingという名前で知られています。
// antipattern
myname = "global"; // global variable
function func() {
    alert(myname); // "undefined" <= ここでundefinedになってしまってる
    var myname = "local";
    alert(myname); // "local"
}
func();
このプログラムの実際の動作は以下と同じ
myname = "global"; // global variable
function func() {
    var myname; // same as -> var myname = undefined;
    alert(myname); // "undefined"
    myname = "local";
    alert(myname); // "local"
}
func();
このような動作になっているのはコードの扱いに2つのステージが存在しているからです。
1つめはvarやfunctionなどの宣言を作るステージ
2つめはコードを実行するステージ
そのため実行ステージより前に変数の宣言ステージがあるため、実行時にコードの並び順とは異なった結果になることがあります。
=> single varパターンだとこういうのに遭遇しにくいよってことでもある

for Loops

forループを書く際に、以下のように書いてしまうとループごとにlengthプロパティへアクセスしてしまい低速になる。
for (var i = 0; i < myarray.length; i++) {
    // do something with myarray[i]
}
特にこれがHTMLCollectionオブジェクトである顕著になる。
なので、最初にlengthの値をとっておくのが定石
function looper() {
    var i = 0,
        max,
        myarray = [];
    // ...
    for (i = 0, max = myarray.length; i < max; i++) {
        // do something with myarray[i]
    }
}
さらにここから最適化することができる。
  • maxという変数を使わない
  • 0へのカウントダウン方式に変える
カウントダウン方式、つまり--デクリメントに変える事で、毎回0以上であるかの比較を行わなくてすむためより高速になる。
だけどこれはノードリストについてのみ使うもので、通常の配列だとfalseなものがくると強制的に終わってしまう。
var i, myarray = [];
for (i = myarray.length; i--;) {
    // do something with myarray[i]
}
// whileなら
var myarray = [],
    i = myarray.length;
while (i--) {
    // do something with myarray[i]
}

for-in Loops

for-inループによる列挙。
prototype汚染されてる時にhasOwnPropertyでフィルタする方法
// the object
var man = {
    hands: 2,
    legs: 2,
    heads: 1
};
// cloneというメソッドがObjectにprototype拡張されている
if (typeof  Object.prototype.clone === "undefined") {
    Object.prototype.clone = function () {};
}


// antipattern:
// clone: function() というもの列挙されてしまう
for (var i in man) {
    console.log(i, ":", man[i]);
}

// for-in loop 
for (var i in man) {
    if (man.hasOwnProperty(i)) { // filter
        console.log(i, ":", man[i]);
    }
}
// => 違う書き方
// Object.prototype.hasOwnPropertyとcallを使う
var i,
    hasOwn = Object.prototype.hasOwnProperty;
for (i in man) {
    if (hasOwn.call(man, i)) { // filter
        console.log(i, ":", man[i]);
    }
}
Object.prototype.hasOwnPropertyとcallを使う方法は初めてみたかも

(Not) Augmenting Built-in Prototypes

Object(),Array(),Function()のようなBuilt-inコンストラクタのprototype拡張は避けるべきである。理由は上のfor-in列挙の時に困ることがあるから。
追加しないことがベストではあるが、次の条件をクリアしたときにはBuilt-inなものも拡張していいかもしれない
  • 将来のECMAScriptで実装される予定であるBuilt-inメソッド
  • そのカスタムプロパティ、メソッドがまだ存在していないことを確認できた
  • チームに対して追加したことを伝える、ドキュメント化する
これらすべての条件を満たしたときに追加することが許される.
if (typeof Object.protoype.myMethod !== "function") {
    Object.protoype.myMethod = function () {
        // implementation...
    };
}

switch Pattern

switchパターンを使うときは以下のことに気をつける。
  • caseとswitchを並べる
  • caseごとにインデントする
  • caseの最後はbreak;で終わる
  • (故意的にbreakさせたい時以外は)fall-throughsを避ける。(breakがないときに次のcaseへ移行するやり方を避ける)
  • switchの最後は必ずdefault:を作る。
var inspect_me = 0,
    result = '';
switch (inspect_me) {
case 0:
    result = "zero";
    break;
case 1:
    result = "one";
    break;
default:
    result = "unknown";
}

Avoiding Implied Typecasting

暗黙のキャスト変換は避けるべきである。
要はいつも === か !== で比較するべきである。
var zero = 0;
if (zero === false) {
    // not executing because zero is 0, not false
}
// antipattern
if (zero == false) {
    // this block is executed...
}
加えて、typeofなどstringを返すと決まっているものについて===ではなく==を使っても問題はないのでは?という人たちもいますが、JSLintでは厳密比較演算子===を使えと警告が出る。
ここで言いたいのは、一貫性を持ったコードを書くことが読みやすいコードへつながるということ。

Avoiding eval()

“eval() is evil.”
evalを使う事はユーザー入力に対してのセキュリティ問題が伴います。
JSONのレスポンスをパースする時は ネイティブのJSON.parse()かライブラリを使うべきである。
またsetInterval(), setTimeout(), Function()などのstringから関数を実行できるものも避けるべきである。(普通に関数名を指定するとかした方がいい)
new Function()はeval()とアプローチが似ていますが、evalを使うよりはnew Function()を使った方がよいでしょう。どのような違いがあるかというとnew Function() でvarを使い変数を宣言した際にそれが自動的にグローバルにはならないという違いがあります。
console.log(typeof un);    // "undefined"
console.log(typeof deux);  // "undefined"
console.log(typeof trois); // "undefined"
var jsstring = "var un = 1; console.log(un);";
eval(jsstring); // logs "1"
jsstring = "var deux = 2; console.log(deux);";
new Function(jsstring)(); // logs "2"
jsstring = "var trois = 3; console.log(trois);";
(function () {
    eval(jsstring);
}()); // logs "3"
console.log(typeof un);    // "number"
console.log(typeof deux);  // "undefined"
console.log(typeof trois); // "undefined"
またevalは実行されるスコープが現在のスコープであるため、ローカル変数はもちろんグローバル変数にもアクセスが可能です。
しかしFunctionの場合はローカル変数にはアクセスできません。
(function () {
    var local = 1;
    eval("local = 3; console.log(local)"); // logs 3
    console.log(local); // logs 3
}());
(function () {
    var local = 1;
    Function("console.log(typeof local);")(); // logs undefined
}());
おまけ:(あってる?)
Functionがグローバルで実行されているように見えて、グローバル変数を定義できない(varで宣言してもグローバルにない)のは、実際にはグローバルスコープで実行されているわけではないからです。
Functionで関数スコープができて、それがグローバルに対してチェインしてるので、グローバルな変数宣言はできないけど、グローバルな値は参照できる。
なのでsandbox的になるためevalよりは安全ではある。
// => 動作的にはオブジェクト内で動く
new function () {  // オブジェクト(this)が生成される
// 処理
};                 // thisは破棄される

Number Conversions with parseInt()

parseInt()を使って文字列から数値を得ようとする場合の注意。
06といった0から始まるものをパースするトキに第二引数でちゃんと10進数であることを指定しいないと、8進数として扱ってしまいバグの原因となる。
var month = "06",
    year = "09";
month = parseInt(month, 10);
year = parseInt(year, 10);
別の方法として下のような書き方もある。
+"08" // result is 8
Number("08") // 8

Coding Conventions

コーディングルールについて

Indentation

インデントは可読性のためにも重要。
この本では4-spaceでインデントしてる(JSLintのデフォルト)

Curly Braces

{}のこと。
forとかifで略すな

Opening Brace Location

どちらの書き方にするか。正解は上の書き方。
if (true) {
    alert("It's TRUE!");
}
// もしくは
if (true)
{
    alert("It's TRUE!");
}
なぜそうするのかというと、JavaScriptには自動的にセミコロンを入れるメカニズムが存在するからだ。下のように書くと想定外の値が返ってきてしまう。
function func() {
    return
    {
        name: "Batman"
    };// undefinedを返してしまう
}
そのため、一つ前のステートメントと同じ行に{をおいた方がよいことになる。

White Space

+, -, *, =, <, >, <=, >=, ===, !==, &&, ||, などの前後にはスペースを挟むべき。

Naming Conventions

命名規則について

Capitalizing Constructors

newキーワードで呼び出すコンストラクタ関数は大文字から開始する。
function MyConstructor() {...}
function myFunction() {...}

Separating Words

JavaScriptではcamel caseが使われることが多い。
ECMAScriptではlower camel caseが使われている。
アンダースコア区切りもみやすいんでないという話first_name

Other Naming Patterns

変化しない定数は大文字でアンダースコア区切り。
var PI = 3.14,
    MAX_WIDTH = 800;
privateメンバは_から始める
var person = {
    getName: function () {
        return this._getFirst() + ' ' + this._getLast();
    },
    _getFirst: function () {
        // ...
    },
    _getLast: function () {
        // ...
    }
};
これにはいくつかパターンがあって
  • 末尾につけるタイプgetElements_()
  • _protectedと__privateにするタイプ
  • Firefoxみたいに__proto__と前後に2個つけるタイプ

Writing Comments

将来読み手がみたときに、関数名やプロパティ名とコメントを見ただけでそれはどのようなことをやっているかを理解できるように書く。
コードの目的についての説明なら1行程度で書けるはず。
逆に複雑な正規表現などはコードよりコメントの方が多くなる場合もある。

Writing API Docs

javadocのようなものはJavaScriptにもある。

Writing to Be Read

コメントはAPIドキュメントを作成するためだけではなく、コードのクオリティを向上するためという目的がある。
最初から完璧なコードは書けることは希だ。

Peer Reviews



査読(さどく、Peer Review ピア・レビュー)とは、研究者が学術雑誌に論文を掲載する前にとりおこなわれる研究者仲間による吟味や検証のことである。
査読 - Wikipedia




Peer Reviewを行う事で、コードはよりクリアになる。
チームでじゃなかったらブログなりで公開することで世界があなたのreviewerになる。

Minify…In Production

Minificationはスペースやコメントを除去して転送量を減らしたりするプロセスのこと。
Yahoo! YUICompressor や Google’s Closure Compiler などのツールがある。

Run JSLint

JSlintもまたJavaScriptで書かれているため、プラットフォームを問わずWSHやJSCやRhinoでも動作する。
JSlintはグレートなツールなので使うといい。

Summary

  • グローバルは減らす
  • 一つのfunctionに一つのvarにする、Single var パターン
  • for,for-in,switch,eval,オブジェクト汚染
  • コーディングルール
この著者はJSLint大好きなようだ。

1: tkykmw 2011年01月06日(木) 午後5時28分

参考にさせていただいています。
ループの最適化についてですが、本文中に出てくるのはループ変数をデクリメントすることで0になる(falseになる)のを待つ方式なので、要素がfalseと判定されて止まってしまう問題はありませんね。欠点を挙げるとしたら列挙が逆順になってしまうことでしょうか。
Functionコンストラクタについては自分のブログにまとめてみたので、よろしければ参照ください(トラックバックをうってあります)。


名前:  非公開コメント   

  • TB-URL  http://efcl.info/adiary/089/tb/
  • Functionコンストラクタと関数式(function expression)の違いなど へびにっき
    JavaScript Patterns 2章ではevalの代替としてFunctionコンストラクタを使う方法が紹介されています。Functionコンストラクタは、普通にJavaScriptを書いている分にはなかなか使うことがないので、馴染みがない人も多いだ...