top / index / prev / next / target / source

2007-07-03 diary: FizzBuzz 問題 - リファクタリング版

いがぴょん画像(小) 日記形式でつづる いがぴょんコラム ウェブページです。

old-v2

FizzBuzz 問題 - リファクタリング版

普通のプロのプログラマーは、ソフトウェアが一定規模を超えた時点でリファクタリングの実施を検討する場合があります。

FizzBuzz 問題 - リファクタリング版

2007.07.05執筆

普通のプロのプログラマーは、ソフトウェアが一定規模を超えた時点でリファクタリングの実施を検討する場合があります。自動テストの実現容易性のためにも、ここでは メッセージ取得の処理を別メソッドとして切り出してみましょう。 FizzBuzzRefactoring01.java

/**
 * FizzBuzz: イギリスの学校の子供たちの遊び
 * 
 * 1から100までの数をプリントする。<br>
 * ・3の倍数のときは数の代わりに「Fizz」とプリントする。<br>
 * ・5の倍数のときは数の代わりに「Buzz」とプリントする。<br>
 * ・3と5両方の倍数の場合には数の代わりに「FizzBuzz」とプリントする。
 */
public class FizzBuzzRefactoring01 {
        /**
         * エントリポイント。
         * 
         * @param args
         *            コマンドライン引数。このプログラム内では利用されない。
         */
        public static void main(final String[] args) {
                // TODO:基本的なリファクタリングを実施しました。
                // TODO:マジックナンバーは追放されていません。
                for (int number = 1; number <= 100; number++) {
                        // 生成された文字列をプリントする。
                        System.out.println(getFizzBuzzMessage(number));
                }
        }

        /**
         * 与えられた数に対応する FizzBuzzメッセージを取得する。
         * 
         * ・3の倍数のときは「Fizz」を戻す。<br>
         * ・5の倍数のときは「Buzz」を戻す。<br>
         * ・3と5両方の倍数の場合には「FizzBuzz」を戻す。<br>
         * ・いずれにも該当しない場合には数を戻す。
         * 
         * @param argNumber
         *            対象となる数。
         * @return メッセージ。
         *         3の倍数のときは「Fizz」、5の倍数のときは「Buzz」、3と5両方の倍数の場合には「FizzBuzz」、いずれにも該当しない場合には数。
         */
        public static String getFizzBuzzMessage(final int argNumber) {
                // 3の倍数であるかどうか。
                final boolean isMultipleOf3 = (argNumber % 3 == 0);
                // 5の倍数であるかどうか。
                final boolean isMultipleOf5 = (argNumber % 5 == 0);

                if (isMultipleOf3 && isMultipleOf5) {
                        // 3と5両方の倍数の場合には「FizzBuzz」を戻す。
                        return "FizzBuzz";
                } else if (isMultipleOf3) {
                        // 3の倍数のときは数の代わりに「Fizz」を戻す。
                        return "Fizz";
                } else if (isMultipleOf5) {
                        // 5の倍数のときは「Buzz」を戻す。
                        return "Buzz";
                } else {
                        // いずれにも該当しない場合には数を戻す。
                        return String.valueOf(argNumber);
                }
        }
}

メッセージ取得メソッドが切り出されることにより、自動テストが実施しやすくなるという嬉しい副作用があります。さらに本体の mainメソッドの内容は極めて単純な内容となり すっきりします。

さて、自動テストが実施しやすくなった時点で、普通のプロのJavaプログラマーは自動テストの実装を まず最初に思いつきます。業務屋に限らず 多くの Javaプログラマーは そのように訓練されています。以下は JUnitによる自動テストの例です。 FizzBuzzRefactoring01Test.java

import junit.framework.TestCase;

/**
 * 単体試験の自動化によって品質を担保。
 */
public class FizzBuzzRefactoring01Test extends TestCase {
        public void testGetFizzBuzzMessage() throws Exception {
                assertEquals("3の倍数かつ5の倍数", "FizzBuzz", FizzBuzzSwitch01.getFizzBuzzMessage(0));
                assertEquals("普通の数字", "1", FizzBuzzSwitch01.getFizzBuzzMessage(1));
                assertEquals("普通の数字", "2", FizzBuzzSwitch01.getFizzBuzzMessage(2));
                assertEquals("3の倍数", "Fizz", FizzBuzzSwitch01.getFizzBuzzMessage(3));
                assertEquals("普通の数字", "4", FizzBuzzSwitch01.getFizzBuzzMessage(4));
                assertEquals("5の倍数", "Buzz", FizzBuzzSwitch01.getFizzBuzzMessage(5));
                assertEquals("3の倍数", "Fizz", FizzBuzzSwitch01.getFizzBuzzMessage(6));
                assertEquals("普通の数字", "7", FizzBuzzSwitch01.getFizzBuzzMessage(7));
                assertEquals("普通の数字", "8", FizzBuzzSwitch01.getFizzBuzzMessage(8));
                assertEquals("9の倍数", "Fizz", FizzBuzzSwitch01.getFizzBuzzMessage(9));
                assertEquals("5の倍数", "Buzz", FizzBuzzSwitch01.getFizzBuzzMessage(10));
                assertEquals("普通の数字", "11", FizzBuzzSwitch01.getFizzBuzzMessage(11));
                assertEquals("3の倍数", "Fizz", FizzBuzzSwitch01.getFizzBuzzMessage(12));
                assertEquals("普通の数字", "13", FizzBuzzSwitch01.getFizzBuzzMessage(13));
                assertEquals("普通の数字", "14", FizzBuzzSwitch01.getFizzBuzzMessage(14));
                assertEquals("3の倍数かつ5の倍数", "FizzBuzz", FizzBuzzSwitch01.getFizzBuzzMessage(15));

                assertEquals("普通の数字", "-1", FizzBuzzSwitch01.getFizzBuzzMessage(-1));
                assertEquals("普通の数字", "-2", FizzBuzzSwitch01.getFizzBuzzMessage(-2));
                assertEquals("3の倍数", "Fizz", FizzBuzzSwitch01.getFizzBuzzMessage(-3));
                assertEquals("普通の数字", "-4", FizzBuzzSwitch01.getFizzBuzzMessage(-4));
                assertEquals("5の倍数", "Buzz", FizzBuzzSwitch01.getFizzBuzzMessage(-5));
                assertEquals("3の倍数かつ5の倍数", "FizzBuzz", FizzBuzzSwitch01.getFizzBuzzMessage(-15));
        }
}

※この試験内容は「ざっくり」書かれたものに過ぎません。本来は 境界値 異常値、そして試験密度などを もっと適切に実施して試験をおこなうものです。2007.07.06更新 試験内容を少しだけ追加 + assertEqualsのパラメータ順序が間違っていたので修正。 次に 普通のプロの Javaプログラマーは mainメソッドに処理が書かれているのを嫌う傾向にあります。ほとんどの場合、以下のように 別メソッド化することが多いでしょう。 FizzBuzzRefactoring02.java

/**
 * FizzBuzz: イギリスの学校の子供たちの遊び
 * 
 * 1から100までの数をプリントする。<br>
 * ・3の倍数のときは数の代わりに「Fizz」とプリントする。<br>
 * ・5の倍数のときは数の代わりに「Buzz」とプリントする。<br>
 * ・3と5両方の倍数の場合には数の代わりに「FizzBuzz」とプリントする。
 */
public class FizzBuzzRefactoring02 {
        /**
         * エントリポイント。
         * 
         * @param args
         *            コマンドライン引数。このプログラム内では利用されない。
         */
        public static void main(final String[] args) {
                new FizzBuzzRefactoring02().process();
        }

        /**
         * このクラスの主処理。
         */
        public void process() {
                // TODO:主処理をmainメソッドから移動しました。
                // TODO:マジックナンバーは追放されていません。
                for (int number = 1; number <= 100; number++) {
                        // 生成された文字列をプリントする。
                        System.out.println(getFizzBuzzMessage(number));
                }
        }

        /**
         * 与えられた数に対応する FizzBuzzメッセージを取得する。
         * 
         * ・3の倍数のときは「Fizz」を戻す。<br>
         * ・5の倍数のときは「Buzz」を戻す。<br>
         * ・3と5両方の倍数の場合には「FizzBuzz」を戻す。<br>
         * ・いずれにも該当しない場合には数を戻す。
         * 
         * @param argNumber
         *            対象となる数。
         * @return メッセージ。
         *         3の倍数のときは「Fizz」、5の倍数のときは「Buzz」、3と5両方の倍数の場合には「FizzBuzz」、いずれにも該当しない場合には数。
         */
        public static String getFizzBuzzMessage(final int argNumber) {
                // 3の倍数であるかどうか。
                final boolean isMultipleOf3 = (argNumber % 3 == 0);
                // 5の倍数であるかどうか。
                final boolean isMultipleOf5 = (argNumber % 5 == 0);

                if (isMultipleOf3 && isMultipleOf5) {
                        // 3と5両方の倍数の場合には「FizzBuzz」を戻す。
                        return "FizzBuzz";
                } else if (isMultipleOf3) {
                        // 3の倍数のときは数の代わりに「Fizz」を戻す。
                        return "Fizz";
                } else if (isMultipleOf5) {
                        // 5の倍数のときは「Buzz」を戻す。
                        return "Buzz";
                } else {
                        // いずれにも該当しない場合には数を戻す。
                        return String.valueOf(argNumber);
                }
        }
}

これで 基本的なプログラミングは 一通り終わりました。しかし クラス中にマジックナンバーや表示メッセージが埋め込まれているなどの点は まだ改善の余地があるようにも思われます。これはソースコードが置かれた状況によって 更に手を入れていったり そのままOKになったりと 変わりうるものです。

性能面で有利な実装

この時点の実装内容は、意外なことにも 実行性能での優位性があります。getFizzBuzzMessageメソッドの中で文字列結合を行っていないところが一般的な JavaVMの性能面において 優位性を持っているのです。更に getFizzBuzzMessageメソッドは短いので JavaVMによってインライン展開をされることを期待できます。するとメソッド呼び出しのコストが減ることが期待できます。

関連する日記


この日記について