コードの見た目がそろったら、
せ、
難しく考える必要はないさ。たしかにデザインパターンみたいな有名な設計技法はいろいろあるけど、
粒度?
![]()
大きな実装は適切な粒度に分割しよう
放っておくと実装は肥大化する
プログラムを開発していると、
リスト1は、
var ForceAuthAtStartup = {
  // Thunderbird が起動したあとにこのメソッドが実行されると仮定する
  onMailStartupDone: function() {
    // ① UI を非表示にする
    document.documentElement.style.visibility = "hidden";
    // ②認証が必要な受信サーバを収集する
    var allServers = MailServices.accounts.allServers;
    var servers = [];
    for (let i = 0, maxi = allServers.length, server; i < maxi; ++i) {
      let server = allServers.queryElementAt(i, Ci.nsIMsgIncomingServer);
      if (server.type != "none")
        servers.push(server);
    }
    // ③各受信サーバに認証を試行する
    var successCount = 0;
    var failureCount = 0;
    servers.forEach(function(server) {
      server.verifyLogon({
        OnStartRunningUrl: function() {},
        OnStopRunningUrl: function(url, exitCode) {
          // ④個々の受信サーバで認証に成功したかどうかを判別する
          if (Components.isSuccessCode(exitCode))
            successCount++;
          else
            failureCount++;
          // ⑤すべてのサーバの処理が終わったら、全体の成否を判定する
          if (successCount + failureCount == servers.length) {
            // ⑥全体の成否の判定結果に応じて処理を行う
            // ⑦すべて成功ならUI を再表示する
            if (successCount == servers.length)
              document.documentElement.style.visibility = "";
            else // ⑧そうでないならThunderbird を終了する
              Cc["@mozilla.org/toolkit/app-startup;1"]
                   .getService(Ci.nsIAppStartup)
                   .quit(Ci.nsIAppStartup.eAttemptQuit);
          }
        }
      }, MailServices.mailSession.topmostMsgWindow);
    }
  }
};
リスト1の実装は、
しかし、
思いつくまま作業を進めた場合や、
このような肥大化した実装をより良い設計に改める方法としては、
解決しようとしている問題を切り分けよう
プログラムの中での実装の単位の大きさは、
しかし、
初級者と中・
問題を切り分ける基準としてわかりやすいのは、
- Thunderbirdの起動時に全体の処理を開始する
 - UIの状態を変える
(表示・ 非表示の切り替え、 終了など)  - 受信サーバで認証する
(認証を試行して、 成功したか失敗したか判断する)  
クラスやモジュールを分けよう
問題を適切に切り分けられれば、
// 起動時の処理
var StartupHandler = {
  onMailStartupDone: function() {
    UIController.deactivate();
    Authenticator.tryAuth(function(succeeded) {
      // ⑥全体の成否の判定結果に応じて処理を行う
      if (succeeded)
        UIController.activate();
      else
        UIController.exit();
    });
  }
};
// UI の状態を変更する処理
var UIController = {
  deactivate: function() {
    // ① UI を非表示にする
  },
  activate: function() {
    // ⑦ UI を再表示する
  },
  exit: function() {
    // ⑧ Thunderbird を終了する
  }
};
// 認証に関係する処理
var Authenticator = {
  tryAuth: function(aCallback) {
    // ②認証が必要な受信サーバを収集する
    // ③各受信サーバに認証を試行する
    ...
    // ④個々の受信サーバで認証に成功したかどうかを判別する
    ...
    // ⑤すべてのサーバの処理が終わったら、全体の成否を判定する
    if (successCount + failureCount == servers.length)
      aCallback(successCount == servers.length);
    ...
  }
};
モジュール数は増えましたが、UIControllerモジュールの実装だけを見ればよいですし、Authenticatorモジュールの実装だけを見れば済みます。このように、
実装の切り分け基準を問題の切り分け基準と合わせると、
なお、Authenticatorモジュールに切り分けるにあたって、
メソッドを分けよう
クラスやモジュールだけでなく、AuthenticatorモジュールのtryAuthメソッドに突出して多くの処理が集中していますので、
このメソッドは
- 認証対象のサーバを収集する
 - 各サーバで認証する
 - すべてのサーバの認証結果が集まった段階でコールバック関数を実行する
 
もとのtryAuthメソッドを、serverのverifyLogonメソッドは第1引数として受け取ったリスナオブジェクトOnStopRunningUrlメソッドに認証の結果を渡していますが、Authenticatorモジュール自身をリスナとして使うようにしており、OnStopRunningUrlメソッドが④の役割を果たすtryAuthメソッドに渡されたコールバック関数や成功/Authenticatorモジュールのプロパティ
var Authenticator = {
  collectAuthServers: function() {
    // ②認証が必要な受信サーバを収集する
    var allServers = MailServices.accounts.allServers;
    var servers = [];
    ...
    return servers;
  },
  tryAuth: function(aCallback) {
    this.callback = aCallback;
    // ③各受信サーバに認証を試行する
    this.successCount = 0;
    this.failureCount = 0;
    this.servers = this.collectAuthServers();
    this.servers.forEach(function(server) {
      server.verifyLogon(this, MailServices.mailSession.topmostMsgWindow);
    });
  },
  OnStartRunningUrl: function() {},
  OnStopRunningUrl: function(url, exitCode) {
    // ④個々の受信サーバで認証に成功したかどうかを判別する
    if (Components.isSuccessCode(exitCode))
      this.successCount++;
    else
      this.failureCount++;
    // ⑤すべてのサーバの処理が終わったら、全体の成否を判定する
    if (this.successCount + this.failureCount == this.servers.length)
      this.callback(this.successCount == this.servers.length);
  }
};
パラメータの数を減らそう
関数やメソッドの引数、
引数の順番は間違えやすい
たとえば、
//「文字列」「変換元」「変換先」という順になっている
function convertEncoding(sourceString, fromEncoding, toEncoding) {
  ...
}
では、UTF-8からShift_へ変換する必要がある処理を実装することになりました。そういえばそんな処理を前に実装したんだった、var name = convertEncoding(...と書き始めたところで、
関数やメソッドを定義したときには覚えていても、
- 「文字列の」
変換なんだから、 文字列が最初に来るはず  - 文字列の
「エンコーディング」 の変換なんだから、 エンコーディングが最初に来るはず  - 変換元、
変換先、 という順番で並ぶのが自然だ  - 得られる結果の文字エンコーディングが重要なんだから、
変換先エンコーディングが最初に来るはず  
どれもそれなりに妥当そうです。実際に、convertEncodingに似たAPIを採用していますが、
var encoding = require("encoding");
//「文字列」「変換先」「変換元」という順になっている
var sjisBuffer = encoding.convert(utf8Buffer, "Shift_JIS", "UTF-8");
また、 このように、 実装の粒度が小さくなると、 JavaScriptの文字列は内部的にはUTF-16でエンコードされているものとして扱われます。このことを前提として、 実際のところ、 また、 ここまで、 実際に、 このように、var Iconv = require("iconv").Iconv;
var converter = new Iconv("UTF-8", "Shift_JIS");
var sjisBuffer = converter.convert(utf8Buffer);引数の数を減らそう
var iconvLite = requrie("iconv-lite");
var unicodeString = iconvLite.decode(utf8Buffer, "UTF-8");
var sjisBuffer = iconvLite.encode(unicodeString, "Shift_JIS");var decoder = new TextDecoder("UTF-8"),
var unicodeString = decoder.decode(utf8BytesArray);
var encoder = new TextEncoder("Shift_JIS");
var sjisBytesArray = encoder.encode(unicodeString);クラスの機能にしよう
utf8_string = " 日本語の文字列"
sjis_string = utf8_string.encode("Shift_JIS")
eucjp_string = sjis_string.encode("EUC-JP")
1つのプログラムを1人だけで専任で開発していると、
チームのみんなで開発しようと思っても、
ああ。そうならないように、
