Processingで学ぶ 実践的プログラミング専門課程

第29回リファクタリング(3) 制御フラグの削除、クラスの抽出

導入

前回作成したビンゴマシンを使ってみましたか? 音や画面のエフェクトが全くありませんから、つまらないと思うかもしれません。その場合には、⁠つまらない」は必要の表れだと考えてください。使う必要があるのに、つまらないので使う気が起こらない。ならば、どうであれば使いたくなるのか。そこを工夫すれば、あなたの必要に応え、使いたくなるアプリケーションになります。

さて、そんな使って便利で楽しいアプリに発展させるために、さらにコードを取り扱いやすくしましょう。前回に引き続いて、別の視点からビンゴマシンのリファクタリングに取り組みます。

展開

制御フラグの削除

参考文献に挙げている『新装版 リファクタリング』⁠マーチン・ファウラー 著)でも『Java言語で学ぶリファクタリング入門』⁠結城浩 著)でも紹介されている小規模なリファクタリングに「制御フラグの削除」があります。

制御フラグの削除(Remove Control Flag)

論理型の制御フラグでコードの流れを制御すると、読みにくいコードになりやすい。その場合は制御構造の構文(break,return)を使って制御フラグを削除する。

Remove Control FlagMartin Fowler Refactoring Catalog

今回作成したビンゴマシンのコードでは、テストモードと実行モードの切り替えにTEST_MODEというフラグを使用しています。リファクタリングの意図は,、このフラグを削除して改善することです。

今回は、次の手順で作業しましょう。

  1. 問題の制御フラグが使用されている箇所をすべて見つける。
  2. この制御フラグが使用されている箇所のうち一ヵ所を、breakで置き換える。
  3. テストを実行し、この置き換えで問題が発生しないか確認する。
  4. 問題がなければ2.に戻る。すべての箇所を置き換える。
  5. 制御フラグを削除する。

練習として次のコードで考えてみます。このような小さなコードの場合には制御フラグによる分岐や条件判断も「読みにくさ」を発生させませんが、もっと長いコードで同じ構造を使ってしまうと大変読みにくいコードになります(そもそもfor文を使うべきかもしれませんが、そこはサンプルとして目をつぶってください⁠⁠。

無駄な制御フラグがあるコード
boolean done = false;
int i = 0;
while( ! done ){
  if ( i < 10 ){
    i++;
    println("i = " + i);
  } else {
    done = true;
    println("done!");
  }
}

このコードに対して作業すると次のようになります。

制御フラグを完全に取り去ったコード
int i= 0;
while( true ){
  if ( i < 10 ){
    i++;
    println("i = " + i);
  } else {
    break;
  }
}
println("done!");

注意したいことは、制御フラグそのものが完全に悪者ではないということです。コードを読みにくく、わざわざ複雑にしているような場合に削除しましょう。

では、BingoMachine.pdeから制御フラグを削除するべきでしょうか。コードの一部を見てみましょう。

BingoMachine.pdeの一部
static final boolean TEST_MODE = true; //テストモード
//static final boolean TEST_MODE = false;  //アプリモード

void setup(){
  if (TEST_MODE == true) {
    runTest();
  } else {
    runBingoMachineApp();
  }
}

制御フラグを用いて、テストモードとアプリモードを切り替えています。setupメソッドだけではなく、draw, mousePressedといったシステムから呼ばれるメソッドにも同様の構造があります。

BingoMachine.pdeに現れる制御フラグTEST_MODEstatic finalで宣言された定数で、実行中に変更されません。実行中に状況に応じて値が変更される制御フラグとは意味が異なります。また、TEST_MODEを削除し、breakcontinuereturnといった制御構造命令で置き換えることでコードはシンプルになりません。かえって複雑になりそうです。

Processingには今のところJUnitのようなテストフレームワークがありません。ですから、テストコードとアプリケーションとしてのコードの切り替えを手軽に行おうと思えば、このような形を取らざるをえません。残念ながら、このリファクタリングは適用できません。

ここで一つの教訓として覚えてもらいたいのは、⁠リファクタリングのためにコードがあるのではない」ということです。無理をしてリファクタリングにある項目を適用し、かえってわかりにくいコードができあがったのでは元も子もありません。必要なところに、必要な技術を使いましょう。

クラスの抽出

ビンゴ数列を発生して、これを一つずつ取り出す仕事は、一つのクラスにまとめるべきでしょう。というのも、アプリケーションとして機能を追加し発展させていく際に、部品化したほうがコードが読みやすいからです。部品化すると、注目したい機能の部分のみに集中してコードを読めます。

ファウラーはこのリファクタリングを「2つのクラスでなされるべき作業を一つのクラスで行っている。」⁠P.149)という悪臭と表現しています。結城さんは同じことを「1つのクラスがたくさんの責任を持ちすぎている」⁠P.121)という言葉で表しています。

クラスの抽出(Exctact Class)

一つのクラスが責任を多く負いすぎている場合、まとまりのあるフィールドやメソッドを切り出して別のクラスにする。

Extract ClassMartin Fowler Refactoring Catalog

現状のビンゴマシンで機能が足りて、今後改変の必要を感じないのなら、このリファクタリングを適用する必要はありません。しかし、今後末長く愛されるアプリケーションにしたければ、今の時点で香りの良いコードにしておきましょう。そのために、このリファクタリングは有効です。

そこで、生成時にビンゴ数列を発生し、オブジェクト内に保管して、取得メソッドを呼ぶと数列の要素を次々と返すようなクラスをBingoProgressionとして切り出しましょう。

まずはこの程度の定義で考えてみます。⁠次々と値を取得する」という機能が必要なので、これはIteratorパターンの出番であることが想像できます。そこで、Java言語に付属のIteratorインターフェイスを実装しましょう。ここではnext, hasNextを実装し、removeは実装しないでおきましょう。removeが呼ばれたら、Java言語のリファレンスに書かれている通り、例外UnsupportedOperationExceptionを返すようにします。

リファクタリングの実施

今回のリファクタリングは「クラスの抽出」の一件だけです。しかし、新しいクラスを作成するにあたって「適切な名称を選ぶ」という大切な作業が含まれます。名称がしっくりこなければ、随時「名称の変更」を実施してください。

テストの作成

リファクタリングのはじめに、忘れずにテストコードを用意します。今回は既存のテストコードへ、次のようにtestBingoProgressionClassメソッドの行を追加します。

加筆されたテストコード
void runTest(){
  println("runTest called.");
  createBingoProgression(bingoProgression);
  assert testBingoProgression(bingoProgression) == true
         : "ビンゴ数列にもれかだぶりがあります" ;
  assert testBingoProgressionClass() == true 
         : "ビンゴ数列発生クラスにエラーがあります"; // 追加された行
  println("runTest finished.");
}

そして、testBingoProgressionClassメソッドを次のように実装します。

testBingoProgressionClassメソッドの実装
boolean testBingoProgressionClass(){
  println("testBingoProgressionClass called.");
  BingoProgression bp = new BingoProgression();
  ArrayList <Boolean> listForCheck 
    = new ArrayList<Boolean>();
  for( int i = 0; i < BINGO_MAX_NUMBER; i++ ){
    listForCheck.add(false);
  }
  for( int i = 0; i < BINGO_MAX_NUMBER; i++ ){
    Integer currentElement = bp.next();
    println("["+(i+1)+"] = " + currentElement);
    if ( listForCheck.get( (int)currentElement -1 ) == true ){
      return false;
    } else {
      listForCheck.set((int)currentElement-1,true);
    }
  }
  println("testBingoProgressionClass finished.");
  return true;
}

テストに合格したら、アプリケーションとして使用可能と判断し、アプリケーションモードのコード部分の置き換えに入りましょう。

なお、testBingoProgressionメソッドのコードと重複部分が多いので、メソッドにまとめるリファクタリングの候補として覚えておきましょう。

演習

演習1(難易度:middle)

BingoMachine.pdeにリファクタリング「クラスの抽出」を適用し、ビンゴ数列を作成し、取得するBingoProgressionクラスを抽出しましょう。リファクタリングの前後でアプリケーションの振る舞いに違いを生じさせないでください。

まとめ

  • リファクタリング「制御フラグの削除」「クラスの抽出」を学習しました。

学習の確認

それぞれの項目で、Aを選択できなければ、本文や演習にもう一度取り組みましょう。

  1. 「制御フラグの削除」の意図と効果が理解できましたか?
    1. 理解できた。
    2. 意図は理解できたが、効果を感じない。
    3. 理解できない。
  2. 「クラスの抽出」の意図と効果が理解できましたか?
    1. 理解できた。
    2. 意図は理解できたが、効果を感じない。
    3. 理解できない。

参考文献

  • 『新装版 リファクタリング―既存のコードを安全に改善する―(OBJECT TECHNOLOGY SERIES⁠⁠マーチン・ファウラー 著、オーム社
    • かつてピアソン・エデュケーション社から出版されていたものの新装版です。リファクタリングのバイブルですから、必携です。なお、本文中に示した引用のページ位置はピアソン・エデュケーション社版のものです。
  • 『Java言語で学ぶリファクタリング入門』⁠結城浩 著、ソフトバンククリエイティブ
    • ファウラーのバイブルと併せて読むことをお勧め。著者の解説は一級です。

演習解答

  1. リファクタリングを施したコードを次に示します。

    実装を簡略化するために、定数BINGO_MAX_NUMBERを共有せず、それぞれのコードで定義しました。先々、さらに大きな数や小さな数のビンゴに対応するためには、主となるコード側でのみこの定数を定義し、BingoProgressionクラス側へはコンストラクタの引数で渡すようにすればさらにコード変更に対応しやすくなります。あるいは各クラスで共通して利用する定数をstaticなフィールドとして持つクラスを用意しても良いでしょう。

    また、ビンゴ数列のテストコードは、せっかくIteratorインターフェイスを持つBingoProgressionクラスを使っているのに、従前のfor文のままのコードです。ここは拡張for文を使えばコードがシンプルにならないでしょうか。ロジックを少々変更する必要がありますが、取り組む価値がありそうです。

    では、主となるsketchのmousePressedメソッド内のビンゴ数列要素取得コード部分はいかがでしょうか。ここもIteratorインタフェイスのメソッドを呼んでいます。Iteratorインターフェイスのオブジェクトを作成してアクセスするほうが良いですか? 無駄ですか? 応用課題として検討してみましょう。

おすすめ記事

記事・ニュース一覧