補助スクリプトを使って処理の流れをわかりやすく書く

[JavaScript Advent Calendar 2015](http://qiita.com/advent-calendar/2015/javascript) 5日目の記事です（大嘘）。

文章と内容についてつっこみ期待しています。よろしくお願いします。

## 前書き

### 自己紹介

派遣JSコーダです。100行未満の小さいコードを書くことが多いです。雰囲気駆動開発です。

GitHub Pages + Middlemanで[ブログ](https://cosmology233.github.io/)を更新しています。

### この記事で書くこと

この記事では補助スクリプトの書き方について書きます。補助スクリプトは小さい自作ライブラリ（モジュール）のようなものです。

* （[補助スクリプトの例](https://github.com/cosmology233/cosmology233.github.io/blob/master/javascripts/helpers/helper-gmap.js) / [JSDoc](https://cosmology233.github.io/works/jsdoc/#Gmap) / [補助スクリプト使用例](https://cosmology233.github.io/works/map/?msg=%E9%96%A2%E8%A5%BF%E7%A9%BA%E6%B8%AF&mlat=34.436&mlng=135.243&zoom=10)）

### この記事で書かないこと

JavaScriptの仕様のことは書きません。例えば、`push`メソッドはこんな動きをしますよ、といったことは書きません。

### 解決したい課題

* この記事では下のコードをわかりやすく書きたい。
何をしているのか、と同時に何がしたいのか、もわかりやすくしたい。

~~~ html
<script>
  !function() {
    location.search.split(/\?|\&/).forEach(function(item) {
      console.log(item.split("=")[0], item.split("=")[0]);
    });
  }();
</script>
~~~

* 一度作った補助スクリプトは資産として他のプロジェクトにも流用したい。

* コンセプトとしては以下の課題を解決したい。
  * **コードの肥大による可読性の低下**（プログラム的な動きについては補助スクリプト内のメソッドに内包して、主となる処理の流れを自然言語に近く、簡潔にする）
  * **外部APIの仕様変更など部分的な変更やチューニングへの対応**（補助スクリプト内のメソッドのInput/Outputを固定することで、補助スクリプトのアップデートを気にしないコーディングをする）

## ステップ

### 1.こんな感じで書きたいな、という願望を日本語で書く。

~~~ html
<!-- index.html -->

<script>
  !function() {
    // URLのクエリを取得してコンソールに出す。
  }();
</script>
~~~

#### ポイント

* 基本的に処理の全体を無名関数で包む（グローバル汚染対策）。

### 2.こんな感じで書きたいな、という願望をJavaScriptで書く。

~~~ html
<!-- index.html -->

<script>
  !function() {

    // Main Process
    // ==================================================

    // URLのクエリを取得してコンソールに出す。
    getUrlQuery()
    .then(function(queries) {
      Object.keys(queries).forEach(function(key) {
        console.log(key, queries[key]);
      });
    });
  }();
</script>
~~~

#### ポイント

* なるべく自然言語に近い形で書く
* できるだけ略語を使わない（`getUQ`ではなく`getUrlQuery`と書く）

### 3.補助スクリプトを読み込むことを考慮して修正する。

~~~ html
<!-- index.html -->

<script>
  !function() {
    var queryHelper = new mynamespace.queryHelper();

    // Main Process
    // ==================================================

    // URLのクエリを取得してコンソールに出す。
    queryHelper.get()
    .then(function(queries) {
      Object.keys(queries).forEach(function(key) {
        console.log(key, queries[key]);
      });
    });
  }();
</script>
~~~

#### ポイント

* 複数の補助スクリプトを読み込むことを考慮してネームスペースを定義する

### 4.補助スクリプトを作成する

~~~ javascript
/* queryHelper.js */

var mynamespace = mynamespace || {};

mynamespace.queryHelper = function() {
  var self = this;

  return self;
};

/**
 * URLからクエリを取得してPromiseを返す
 * Resolve時に取得したクエリをObjectで参照する
 *   @param: -
 *   Returns: {Promise}
 */
mynamespace.queryHelper.prototype.get = function() {
  var self = this;

  return new Promise(function(resolve, reject) {
    var queries = {};

    location.search.split(/\?|\&/).forEach(function(queryString) {
      if(queryString == "") {
        return;
      }

      queries[queryString.split("=")[0]] = queryString.split("=")[1];
    });

    resolve(queries);
  });
};
~~~

#### ポイント

* メソッドはI/Oについて先に定義してコメントを残しておく
* I/OはInput/Outputの略で、引数と戻り値のことを言っている
* I/Oさえコメント通りであれば関数内部はどうでもよい
* I/Oを変更する場合は新しいメソッドを作成したほうが良い
* `var self = this;`を冒頭につけると何かと便利
* 特に戻す値がない場合は`return self;`をつけると何かと便利（チェインしやすい）
* 関数内部は次のように書くと見やすい（ステップ５で例示する）
  1. エラー処理（前後する場合もある）
  2. ローカル変数定義
  3. メイン処理
  4. 内部変数定義

### 5.補助スクリプトを読み込む

~~~ html
<!-- index.html -->

<script src="queryHelper.js"></script>
<script>
  !function() {

    // Error Handlers（1. エラー処理）
    // ==================================================

    if(!mynamespace.queryHelper) {
      return;
    }

    // Local Variables（2. ローカル変数定義）
    // ==================================================

    var queryHelper = new mynamespace.queryHelper();

    // Main Process（3. メイン処理）
    // ==================================================

    queryHelper.get()
    .then(function(queries) {
      loopObj(queries, function(key, value) {
        console.log(key, value);
      });
    });

    // Functions（4. 内部変数定義）
    // ==================================================

    function loopObj(obj, callback) {
      if(typeof obj != "object") {
        console.error("Error(loopObj):Illegal Argument{Object}", obj);
        return false;
      }

      if(typeof callback != "function") {
        console.error("Error(loopObj):Illegal Argument{Function}", callback);
        return false;
      }

      Object.keys(obj).forEach(function(key) {
        callback(key, obj[key]);
      });
    };
  }();
</script>
~~~

#### ポイント

* 最終的な目標としてはコメントがなくてもぱっと見で処理の流れがつかめるコードを目指す

## 完成形


~~~ html
<!-- index.html -->

<script src="queryHelper.js"></script>
<script>
  !function() {

    // Error Handlers
    // ==================================================

    if(!mynamespace.queryHelper) {
      return;
    }

    // Local Variables
    // ==================================================

    var queryHelper = new mynamespace.queryHelper();

    // Main Process
    // ==================================================

    queryHelper.get()
    .then(function(queries) {
      loopObj(queries, function(key, value) {
        console.log(key, value);
      });
    });

    // Functions
    // ==================================================

    function loopObj(obj, callback) {
      if(typeof obj != "object") {
        console.error("Error(loopObj):Illegal Argument{Object}", obj);
        return false;
      }

      if(typeof callback != "function") {
        console.error("Error(loopObj):Illegal Argument{Function}", callback);
        return false;
      }

      Object.keys(obj).forEach(function(key) {
        callback(key, obj[key]);
      });
    };
  }();
</script>
~~~

~~~ javascript
/* queryHelper.js */

var mynamespace = mynamespace || {};

mynamespace.queryHelper = function() {
  var self = this;

  return self;
};

/**
 * URLからクエリを取得してPromiseを返す
 * Resolve時に取得したクエリをObjectで参照する
 *   @param: -
 *   Returns: {Promise}
 */
mynamespace.queryHelper.prototype.get = function() {
  var self = this;

  return new Promise(function(resolve, reject) {
    var queries = {};

    location.search.split(/\?|\&/).forEach(function(queryString) {
      if(queryString == "") {
        return;
      }

      queries[queryString.split("=")[0]] = queryString.split("=")[1];
    });

    resolve(queries);
  });
};
~~~