2011年7月13日水曜日

動けばいいってもんじゃない!(ソフトウェア設計の話)

こんにちは。
オブジェクト指向プログラミングの本質(の一つ)は、「クラスやメソッド等の言語要素を持ち込んで、コードを冗長にするコストを払ってでも、コードを読みやすくする」ことなのですが、このことをあまり理解せずに実装しているのを今だに良く見かけます。最近見かけた例を紹介しましょう。

この例では、「2つの異なるトリガーによって起動される処理Aをどう実装するか」がテーマです。
具体的には以下のような要件でした。
(1)トリガー1:タイマーがタイムアウト
(2)トリガー2:ユーザが終了ボタンをクリック
(3)処理A:ファイルを閉じてプログラムを終了する。
(4)シナリオ:トリガー1又はトリガー2が発生したら、処理Aを実行する。

これを某プログラマさん(外注さんです。すみません。)は下記のように実装していました。動作としてはバグはなく、一応要件を満たしてはいましたが、コードを見ると、動作シナリオが読み取りにくいものになっていました。(言語はJavaで、関係のない細かいところは省略しています。)どこが良くないか分かりますか?

// アプリメイン(よくない例)
class App implements TimeoutListener, OnClickListenr {
  ...
  // タイムアウト時のコールバック
  public void onTimeout() {
    // 全てのファイルを閉じる
    closeAllFiles() ;
    // アプリを終了する。
    exitApp() ;
  }
  // ボタンクリック時のコールバック
 public void onClick( Button btn ) {
    if( btn == this.btnCommandQuit ) { // 終了ボタンが押された・・
       onTimeout() ;//XXX
    }
  } 
}
答えは、//XXXというコメントをつけた行です。終了ボタンのクリックを検知したら、onTimeout()を呼び出していますよね? このソースを普通に読み下すと「終了ボタンがクリックされたら、タイムアウトコールバックを呼び出す」となりますが、意味的に要件と直結しないので、「どうしてこうなっているのだろう?」と読者を悩ませることになってしまいます。

私なら以下のようにコーディングしたいところです。
// アプリメイン(改良版)
class App implements TimeoutListener, OnClickListenr {
  ...
  // タイムアウト時のコールバック
  public void onTimeout() {
    // 処理Aを実行
    procedureA();
  }
  // ボタンクリック時のコールバック
 public void onClick( Button btn ) {
    if( btn == this.btnCommandQuit ) { // 終了ボタンが押された・・
      // 処理Aを実行
      procedureA();
    }
  }
  // 処理A
 protected void procedureA() {
    // 全てのファイルを閉じる
    closeAllFiles() ;
    // アプリを終了する。
    exitApp() ; 
  }
}
上記のコードならば、要件を直接対応づけて、容易にコードを読むことができるはずです。

「これぐらい大した違いじゃない」と思われる方もいるかも知れません。しかし、大規模なプログラムになってきて、こうした問題があちこちに現れてくると、一気に解読コストが上がっていきます。「ちりも積れば・・・」のことわざ通りです。派生開発や保守・引き継ぎ等で非常に骨の折れるコードは、大抵こういった問題を抱えたものが多いのです。

読みやすいコードを書いておけば、拡張がしやすく、引き継ぎもしやすくなり、最終的には自分の身を助けることにつながります。設計力の向上にもつながるので常に意識しておきたい課題です。

投稿者:寺田

0 件のコメント:

コメントを投稿