1年目から身につけたい! チーム開発 6つの心得

第3章 細かな粒度で実装しよう―単純なパーツを組み合わせた見通しの良い設計

この記事を読むのに必要な時間:およそ 7 分

クラスやモジュールを分けよう

問題を適切に切り分けられれば,あとは実装を問題に合わせて切り分けるだけです。リスト2は,前項で切り分けた問題の単位でリスト1のモジュールを分割した例です。

リスト2 モジュールを分割した状態

// 起動時の処理
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);
    ...
  }
};

モジュール数は増えましたが,実装が性質ごとに分かれているため,調査や変更のために見るべきコードの範囲はむしろ狭くなります。たとえば,⁠認証が完了するまでの間,UIを非表示にするのではなくウィンドウを最小化するようにしたい」というときはUIControllerモジュールの実装だけを見ればよいですし,⁠認証を求める処理を一時的に無効化できるようにしたい」というときはAuthenticatorモジュールの実装だけを見れば済みます。このように,全体的な見通しは良くなっています。

実装の切り分け基準を問題の切り分け基準と合わせると,実装を細かく分けすぎたり,本来1ヵ所にまとまっているべき実装が離れたり,といったことの発生を防ぎやすくなります。細かく分けることだけにとらわれずに,「この実装はどんな問題を解決するためのものか?」を常に意識するようにしましょう。

なお,この例では認証の処理だけをAuthenticatorモジュールに切り分けるにあたって,コールバック関数注3を使うようにしています。⁠認証する処理」そのものと「認証の結果を受けて行う処理」のように,依存関係にあるもののそれぞれは本質的に別のことである処理どうしを切り分けるためには,コールバック関数がよく使われます。

注3)
メソッドや関数に引数として与えられて,何らかの処理が行われたあとに,その結果を引数として実行される関数のことです。

メソッドを分けよう

クラスやモジュールだけでなく,メソッドも分けることができます。リスト2の実装はよく見ると,AuthenticatorモジュールのtryAuthメソッドに突出して多くの処理が集中していますので,これもより細かい単位に分けましょう。

このメソッドは「認証対象のサーバを収集して,それぞれのサーバで認証し,すべてのサーバの結果が集まったらコールバック関数を実行する」という問題を解決しています。これはさらに次のように小さな問題へと切り分けられます。

  • 認証対象のサーバを収集する
  • 各サーバで認証する
  • すべてのサーバの認証結果が集まった段階でコールバック関数を実行する

もとのtryAuthメソッドを,これらの各問題を解決する小さなメソッドへとさらに分割した例がリスト3です。serververifyLogonメソッドは第1引数として受け取ったリスナオブジェクト注4OnStopRunningUrlメソッドに認証の結果を渡していますが,リスト1の③ではリスナをループのたびに新しく生成していました。リスト3の③ではAuthenticatorモジュール自身をリスナとして使うようにしており,OnStopRunningUrlメソッドが④の役割を果たす「サーバの認証が終わった段階で処理を行う」メソッドとなっています。また,tryAuthメソッドに渡されたコールバック関数や成功/失敗のカウンタをこれらのメソッド間で共有する必要があるため,コールバック関数やカウンタはAuthenticatorモジュールのプロパティ(メンバ変数)として保持するようにしています。

リスト3 メソッドを分割した状態

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);
  }
};
注4)
監視対象のイベント発生時にメソッドが実行される,コールバック関数と同様の働きをするオブジェクトのことです。

著者プロフィール

結城洋志(ゆうきひろし)

株式会社クリアコード所属。Firefox黎明期からアドオン開発を手がけ,業務上もMozilla製品の技術サポートを担当。代表作は「ツリー型タブ」「テキストリンクなど。また,日経Linux誌にて「シス管系女子」を連載中。

コメント

コメントの記入