テスト駆動開発から始めるRuby入門 ~6時間でオブジェクト指向のエッセンスを体験する~

# エピソード3

[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/hiroshima-arc/tdd_rb/tree/episode-3)

## 初めに

この記事は [テスト駆動開発から始めるRuby入門 ~2時間でTDDとリファクタリングのエッセンスを体験する~](https://qiita.com/k2works/items/83741e3e2d2579d748d6) の続編です。

前提として エピソード１を完了して、[テスト駆動開発から始めるRuby入門 ~ソフトウェア開発の三種の神器を準備する~](https://qiita.com/k2works/items/385dc16333e065d69bd6) で開発環境を構築したところから始まります。 別途、[セットアップ済み環境](https://gitpod.io/#https://github.com/hiroshima-arc/tdd_rb/tree/episode-3) を用意していますのでこちらからだとすぐに始めることが出来ます。

本記事は一応オブジェクト指向プログラム入門者向けとなっていますが、入門者の方は用語についてはわからなくても結構です、コードを繰り返し写経することで感覚を掴んでもらえば自ずと書いてあることはわかるようになってきますので。あと、概要はオブジェクト指向プログラム経験者に向けて書いたのものなので読み飛ばしてもらって結構です（ネタバレ内容です）、経験者の方からのツッコミお待ちしております。

### 概要

本記事では、 **オブジェクト指向プログラム** から **オブジェクト指向設計** そして **モジュール分割** を **テスト駆動開発** を通じて実践していきます。

#### オブジェクト指向プログラム

エピソード1で作成したプログラムの追加仕様を **テスト駆動開発** で実装します。 次に **手続き型コード** との比較から **オブジェクト指向プログラム** を構成する **カプセル化** **ポリモフィズム** **継承** という概念をコードベースの **リファクタリング** を通じて解説します。

具体的には **フィールドのカプセル** から **setterの削除** を適用することにより **カプセル化** を実現します。続いて、 **ポリモーフィズムによる条件記述の置き換え** から **State/Strategyによるタイプコードの置き換え** を適用することにより **ポリモーフィズム** の効果を体験します。そして、 **スーパークラスの抽出** から **メソッド名の変更** **メソッドの移動** の適用を通して **継承** の使い方を体験します。さらに **値オブジェクト** と **ファーストクラス** というオブジェクト指向プログラミングに必要なツールの使い方も学習します。

#### オブジェクト指向設計

次に設計の観点から **単一責任の原則** に違反している `FizzBuzz` クラスを **デザインパターン** の1つである **Commandパターン** を使ったリファクタリングである **メソッドオブジェクトによるメソッドの置き換え** を適用してクラスの責務を分割します。オブジェクト指向設計のイデオムである **デザインパターン** として **Commandパターン** 以外に **Value Objectパターン** **Factory Methodパターン** **Strategyパターン** を **リファクタリング** を適用する過程ですでに実現していたことを説明します。そして、**オープン・クローズドの原則** を満たすコードに **リファクタリング** されたことで既存のコードを変更することなく振る舞いを変更できるようになることを解説します。

加えて、正常系の設計を改善した後 **アサーションの導入** **例外によるエラーコードの置き換え** といった例外系の **リファクタリング** を適用します。最後に **ポリモーフィズム** の応用として **特殊ケースの導入** の適用による **Null Objectパターン** を使った **オープン・クローズドの原則** に従った安全なコードの追加方法を解説します。

#### モジュールの分割

仕上げは、**モノリシック** なファイルから個別のクラスモジュールへの分割を **ドメインオブジェクト** の抽出を通して **ドメインモデル** へと整理することにより **モジュール分割** を実現することを体験してもらいます。最後に **良いコード** と **良い設計** について考えます。

##### Before

![diag-c63943e73aed75ba31adf85779eaf481.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20035/beac3f11-f8c8-e8a5-7e09-ea0dda7e6693.png)

##### After

![diag-84a49e2f281dfc169055d0bfc4b4aeb6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20035/b4adb851-0e0d-a5eb-f490-cddd272822cc.png)

## オブジェクト指向から始めるテスト駆動開発

### テスト駆動開発

エピソード1ので作成したプログラムに以下の仕様を追加します。

仕様

    1 から 100 までの数をプリントするプログラムを書け。
    ただし 3 の倍数のときは数の代わりに｢Fizz｣と、5 の倍数のときは｢Buzz｣とプリントし、
    3 と 5 両方の倍数の場合には｢FizzBuzz｣とプリントすること。
    タイプごとに出力を切り替えることができる。
    タイプ１は通常、タイプ２は数字のみ、タイプ３は FizzBuzz の場合のみをプリントする。

早速開発に取り掛かりましょう。エピソード2で開発環境の自動化をしているので以下のコマンドを実行するだけで開発を始めることができます。

``` bash
$ rake
```

`guard` が起動するとコンソールが使えなくなるのでもう一つコンソールを開いておきましょう。もしくは `.` を使うことで `guard` 内でコンソールのコマンドを呼び出すことができます。

``` bash
[1] guard(main)> . ls
coverage  Gemfile.lock  lib      provisioning  README.md  tmp
Gemfile   Guardfile     main.rb  Rakefile      test       Vagrantfile
[2] guard(main)> . pwd
/workspace/tdd_rb
[3] guard(main)> . git status
```

#### TODOリスト作成

まずは追加仕様を **TODOリスト** に落とし込んでいきます。

TODOリスト

  - タイプ1の場合
    
      - 数を文字列にして返す
        
          - 1を渡したら文字列"1"を返す

#### タイプ1の場合

**テストファースト** **アサートファースト** で最初に失敗するテストから始めます。テストを追加しましょう。

ここでは既存の `FizzBuzz.generate` メソッドにタイプを **引数** として追加することで対応できるように変更してみたいと思います。まず、 `fizz_buzz_test.rb` ファイルに以下のテストコードを追加します。

``` ruby
...
  end

  describe 'タイプごとに出力を切り替えることができる' do
    describe 'タイプ1の場合' do
      def test_1を渡したら文字列1を返す
        assert_equal '1', FizzBuzz.generate(1, 1)
      end
    end
  end

  describe '配列や繰り返し処理を理解する' do
...
```

``` bash
...
05:32:51 - INFO - Running: all tests
Coverage report generated for MiniTest to /workspace/tdd_rb/coverage. 4 / 11 LOC (36.36%) covered.
Started with run options --guard --seed 37049

ERROR["test_1を渡したら文字列1を返す", #<Minitest::Reporters::Suite:0x00005623e6a24260 @name="タイプごとに出力を切り替えることができる::タイプ1の場合">, 0.0019176720088580623]
 test_1を渡したら文字列1を返す#タイプごとに出力を切り替えることができる::タイプ1の場合 (0.00s)
Minitest::UnexpectedError:         ArgumentError: wrong number of arguments (given 2, expected 1)
            /workspace/tdd_rb/lib/fizz_buzz.rb:6:in `generate'
            /workspace/tdd_rb/test/fizz_buzz_test.rb:74:in `test_1を渡したら文字列1を返す'

  25/25: [=================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00796s
25 tests, 26 assertions, 0 failures, 1 errors, 0 skips
...
```

`ArgumentError: wrong number of arguments (given 2, expected 1)` **引数** が違うと指摘されていますね。 `FizzBuzz.generate` メソッドの引数の変更したいのですが既存のテストを壊したくないのでここは **デフォルト引数** 使ってみましょう。

> メソッドの引数にはデフォルト値を指定する定義方法があります。これは、メソッドの引数を省略した場合に割り当てられる値です。
> 
> —  かんたんRuby 

``` bash
...
class FizzBuzz
  MAX_NUMBER = 100

  def self.generate(number, type = 1)
    is_fizz = number.modulo(3).zero?
    is_buzz = number.modulo(5).zero?

    return 'FizzBuzz' if is_fizz && is_buzz
    return 'Fizz' if is_fizz
    return 'Buzz' if is_buzz

    number.to_s
  end
...
```

``` bash
...
05:32:52 - INFO - Inspecting Ruby code style: test/fizz_buzz_test.rb Guardfile
 2/2 files |====================================== 100 =======================================>| Time: 00:00:00

2 files inspected, no offenses detected
05:32:54 - INFO - Inspecting Ruby code style: coverage/assets/0.10.2/colorbox/loading_background.png coverage/assets/0.10.2/colorbox/controls.png coverage/assets/0.10.2/colorbox/loading.gif coverage/assets/0.10.2/colorbox/border.png
 0/0 files |====================================== 100 =======================================>| Time: 00:00:00

0 files inspected, no offenses detected
05:37:29 - INFO - Inspecting Ruby code style: lib/fizz_buzz.rb
lib/fizz_buzz.rb:6:29: W: [Corrected] Lint/UnusedMethodArgument: Unused method argument - type. If it's necessary, use _ or _type as an argument name to indicate that it won't be used.
  def self.generate(number, type = 1)
                            ^^^^
 1/1 file |======================================= 100 =======================================>| Time: 00:00:00

1 file inspected, 1 offense detected, 1 offense corrected
05:37:31 - INFO - Inspecting Ruby code style: lib/fizz_buzz.rb
 1/1 file |======================================= 100 =======================================>| Time: 00:00:00

1 file inspected, no offenses detected
[1] guard(main)>
05:39:37 - INFO - Run all
05:39:37 - INFO - Running: all tests
Coverage report generated for MiniTest to /workspace/tdd_rb/coverage. 4 / 11 LOC (36.36%) covered.
Started with run options --guard --seed 8607

  25/25: [=================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00723s
25 tests, 27 assertions, 0 failures, 0 errors, 0 skips
...
```

ちなみにここでは 引数に `type=1` と入力したのですがコードフォーマットによって以下のように自動修正されます。

``` bash
...
class FizzBuzz
  MAX_NUMBER = 100

  def self.generate(number, _type = 1)
    is_fizz = number.modulo(3).zero?
    is_buzz = number.modulo(5).zero?

    return 'FizzBuzz' if is_fizz && is_buzz
    return 'Fizz' if is_fizz
    return 'Buzz' if is_buzz

    number.to_s
  end
...
```

**case式** を使って **引数** を判定できるように変更しましょう。ちなみに `_type` をメソッド内で変数として使うと警告されるので `type` に変更しています。

``` ruby
...
class FizzBuzz
  MAX_NUMBER = 100

  def self.generate(number, type = 1)
    case type
    when 1
      is_fizz = number.modulo(3).zero?
      is_buzz = number.modulo(5).zero?

      return 'FizzBuzz' if is_fizz && is_buzz
      return 'Fizz' if is_fizz
      return 'Buzz' if is_buzz

      number.to_s
    end
  end
...
```

``` bash
...
Started with run options --seed 51330


Progress: |=============================================================|

Finished in 0.00828s
25 tests, 27 assertions, 0 failures, 0 errors, 0 skips
04:27:12 - INFO - Inspecting Ruby code style: lib/fizz_buzz.rb
 1/1 file |=================== 100 ====================>| Time: 00:00:00

1 file inspected, no offenses detected
04:27:13 - INFO - Inspecting Ruby code style: coverage/assets/0.10.2/colorbox/loading_background.png coverage/assets/0.10.2/colorbox/controls.png coverage/assets/0.10.2/colorbox/loading.gif coverage/assets/0.10.2/colorbox/border.png
 0/0 files |=================== 100 ===================>| Time: 00:00:00

0 files inspected, no offenses detected
...
```

テストは無事通りました。ここでコミットしておきます。

``` bash
$ git add .
$ git commit -m 'test: タイプ1の場合'
```

追加仕様の取っ掛かりができました。既存のテストを流用したいので先程作成したテストを削除して以下のように新しいグループ内に既存テストコードを移動しましょう。

``` ruby
...

class FizzBuzzTest < Minitest::Test
  describe '数を文字列にして返す' do
    describe 'タイプ1の場合' do
      def setup
        @fizzbuzz = FizzBuzz
      end

      describe '三の倍数の場合' do
        def test_3を渡したら文字列Fizzを返す
          assert_equal 'Fizz', @fizzbuzz.generate(3)
        end
      end

      describe '五の倍数の場合' do
        def test_5を渡したら文字列Buzzを返す
          assert_equal 'Buzz', @fizzbuzz.generate(5)
        end
      end

      describe '三と五の倍数の場合' do
        def test_15を渡したら文字列FizzBuzzを返す
          assert_equal 'FizzBuzz', @fizzbuzz.generate(15)
        end
      end

      describe 'その他の場合' do
        def test_1を渡したら文字列1を返す
          assert_equal '1', @fizzbuzz.generate(1)
        end
      end

      describe '1から100までのFizzBuzzの配列を返す' do
        def setup
          @result = FizzBuzz.generate_list
        end

        def test_配列の初めは文字列の1を返す
          assert_equal '1', @result.first
        end

        def test_配列の最後は文字列のBuzzを返す
          assert_equal 'Buzz', @result.last
        end

        def test_配列の2番目は文字列のFizzを返す
          assert_equal 'Fizz', @result[2]
        end

        def test_配列の4番目は文字列のBuzzを返す
          assert_equal 'Buzz', @result[4]
        end

        def test_配列の14番目は文字列のFizzBuzzを返す
          assert_equal 'FizzBuzz', @result[14]
        end
      end
    end
  end
...
```

テストコードが壊れていないことを確認したらコミットしておきます。

``` bash
$ git add .
$ git commit -m 'refactor: メソッドのインライン化'
```

TODOリスト

  - タイプ1の場合
    
      - 数を文字列にして返す
        
          - ~~1を渡したら文字列"1"を返す~~
    
      - 3 の倍数のときは数の代わりに｢Fizz｣と返す\_
        
          - ~~3を渡したら文字列"Fizz"を返す~~
    
      - 5 の倍数のときは｢Buzz｣と返す\_
        
          - ~~5を渡したら文字列"Buzz"を返す~~
    
      - 3 と 5 両方の倍数の場合には｢FizzBuzz｣と返す\_
        
          - ~~15を渡したら文字列"FizzBuzz"を返す~~

  - タイプ2の場合
    
      - 数を文字列にして返す
        
          - 1を渡したら文字列"1"を返す
    
      - 3 の倍数のときは数を文字列にして返す
        
          - 3を渡したら文字列"3"を返す
    
      - 5 の倍数のときは数を文字列にして返す
        
          - 5を渡したら文字列"5"を返す
    
      - 3 と 5 両方の倍数の場合には数を文字列にして返す
        
          - 15を渡したら文字列"15"を返す

  - タイプ3の場合
    
      - 数を文字列にして返す
        
          - 1を渡したら文字列"1"を返す
    
      - 3 の倍数のときは数を文字列にして返す
        
          - 3を渡したら文字列"3"を返す
    
      - 5 の倍数のときは数を文字列にして返す
        
          - 5を渡したら文字列"5"を返す
    
      - 3 と 5 両方の倍数の場合には｢FizzBuzz｣と返す
        
          - 15を渡したら文字列"FizzBuzz"を返す

#### タイプ2の場合

TODOリスト

  - ~~タイプ1の場合~~

  - タイプ2の場合
    
      - 数を文字列にして返す
        
          - 1を渡したら文字列"1"を返す
    
      - 3 の倍数のときは数を文字列にして返す
        
          - 3を渡したら文字列"3"を返す
    
      - 5 の倍数のときは数を文字列にして返す
        
          - 5を渡したら文字列"5"を返す
    
      - 3 と 5 両方の倍数の場合には数を文字列にして返す
        
          - 15を渡したら文字列"15"を返す

  - タイプ3の場合
    
      - 数を文字列にして返す
        
          - 1を渡したら文字列"1"を返す
    
      - 3 の倍数のときは数を文字列にして返す
        
          - 3を渡したら文字列"3"を返す
    
      - 5 の倍数のときは数を文字列にして返す
        
          - 5を渡したら文字列"5"を返す
    
      - 3 と 5 両方の倍数の場合には｢FizzBuzz｣と返す
        
          - 15を渡したら文字列"FizzBuzz"を返す

続いて、タイプ2の場合に取り掛かりましょう。

``` ruby
...
    end

    describe 'タイプ2の場合' do
      def setup
        @fizzbuzz = FizzBuzz
      end

      describe 'その他の場合' do
        def test_1を渡したら文字列1を返す
          assert_equal '1', @fizzbuzz.generate(1, 2)
        end
      end
    end
...
```

``` bash
...
FAIL["test_1を渡したら文字列1を返す", #<Minitest::Reporters::Suite:0x00005555ec747100 @name="数を文字列にして返す::タイプ2の場合::その他の場合">, 0.002283181995153427]
 test_1を渡したら文字列1を返す#数を文字列にして返す::タイプ2の場合::その他の場合 (0.00s)
        Expected: "1"
          Actual: nil
        /workspace/tdd_rb/test/fizz_buzz_test.rb:75:in `test_1を渡したら文字列1を返す'

  24/24: [=================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00437s
24 tests, 26 assertions, 1 failures, 0 errors, 0 skips
...
```

まだ **引数** に2を渡した場合は何もしないので **case式** に2を渡した場合の処理を追加します。

``` ruby
...
class FizzBuzz
  MAX_NUMBER = 100

  def self.generate(number, type = 1)
    case type
    when 1
      is_fizz = number.modulo(3).zero?
      is_buzz = number.modulo(5).zero?

      return 'FizzBuzz' if is_fizz && is_buzz
      return 'Fizz' if is_fizz
      return 'Buzz' if is_buzz

      number.to_s
    when 2
      number.to_s
    end
  end
...
```

``` bash
...
Started with run options --seed 19625


Progress: |=============================================================================|

Finished in 0.00894s
24 tests, 26 assertions, 0 failures, 0 errors, 0 skips
...
```

テストが通ったのでテストケースを追加します。ここはタイプ1の場合をコピーして編集すれば良いでしょう。

``` ruby
...
   end

    describe 'タイプ2の場合' do
      def setup
        @fizzbuzz = FizzBuzz
      end

      describe '三の倍数の場合' do
        def test_3を渡したら文字列3を返す
          assert_equal '3', @fizzbuzz.generate(3, 2)
        end
      end

      describe '五の倍数の場合' do
        def test_5を渡したら文字列5を返す
          assert_equal '5', @fizzbuzz.generate(5, 2)
        end
      end

      describe '三と五の倍数の場合' do
        def test_15を渡したら文字列15を返す
          assert_equal '15', @fizzbuzz.generate(15, 2)
        end
      end

      describe 'その他の場合' do
        def test_1を渡したら文字列1を返す
          assert_equal '1', @fizzbuzz.generate(1, 2)
        end
      end
    end
  end
...
```

``` bash
...
Coverage report generated for MiniTest to /workspace/tdd_rb/coverage. 4 / 13 LOC (30.77%) covered.
Started with run options --guard --seed 898

  27/27: [=================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00900s
27 tests, 29 assertions, 0 failures, 0 errors, 0 skips

06:27:40 - INFO - Inspecting Ruby code style of all files
test/fizz_buzz_test.rb:11:3: C: Metrics/BlockLength: Block has too many lines. [70/62]
  describe '数を文字列にして返す' do ...
  ^^^^^^^^^^^^^^^^^^^^^^^^
 7/7 files |====================================== 100 =======================================>| Time: 00:00:00

7 files inspected, 1 offense detected
...
```

テストは通りましたが何やら警告が表示されるようになりました。　
[Metrics/BlockLength:Block has too many lines.](https://rubocop.readthedocs.io/en/latest/cops\_metrics/\#metricsblocklength) これは `数を文字列にして返す` テストケースのコードブロックが長いという警告のようですがテストコードはチェックの対象から外しておきたいので `.rubocop_todo.yml` に以下コードを追加してチェック対象から外しておきます。

``` yml
...
# Offense count: 2
# Configuration parameters: CountComments, ExcludedMethods.
# ExcludedMethods: refine
Metrics/BlockLength:
  Max: 62
  Exclude:
    - 'test/fizz_buzz_test.rb'
...
```

ちなみに `guard(main)>` にカーソルを合わせてエンターキーを押すと自動化タスクが実行されます。

``` bash
[1] guard(main)>
02:03:15 - INFO - Run all
/home/gitpod/.rvm/rubies/ruby-2.6.3/bin/ruby -w -I"lib" -I"/workspace/.rvm/gems/rake-13.0.1/lib" "/workspace/.rvm/gems/rake-13.0.1/lib/rake/rake_test_loader.rb" "./test/fizz_buzz_test.rb"
/home/gitpod/.rvm/rubies/ruby-2.6.3/bin/ruby -w -I"lib" -I"/workspace/.rvm/gems/rake-13.0.1/lib" "/workspace/.rvm/gems/rake-13.0.1/lib/rake/rake_test_loader.rb" "./test/fizz_buzz_test.rb"
Started with run options --seed 47335


Progress: |==============================================================================|

Finished in 0.00781s
27 tests, 29 assertions, 0 failures, 0 errors, 0 skips
Started with run options --seed 47825


Progress: |==============================================================================|

Finished in 0.00761s
27 tests, 29 assertions, 0 failures, 0 errors, 0 skips
02:03:17 - INFO - Running: all tests
Coverage report generated for MiniTest, Unit Tests to /workspace/tdd_rb/coverage. 13 / 13 LOC (100.0%) covered.
Started with run options --guard --seed 17744

  27/27: [===========================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00789s
27 tests, 29 assertions, 0 failures, 0 errors, 0 skips

02:03:17 - INFO - Inspecting Ruby code style of all files
 7/7 files |=========================== 100 ============================>| Time: 00:00:00

7 files inspected, no offenses detected
02:03:19 - INFO - Inspecting Ruby code style: coverage/assets/0.10.2/colorbox/controls.png coverage/assets/0.10.2/colorbox/border.png coverage/assets/0.10.2/colorbox/loading.gif coverage/assets/0.10.2/colorbox/loading_background.png
 0/0 files |=========================== 100 ============================>| Time: 00:00:00

0 files inspected, no offenses detected
[1] guard(main)>
```

警告は消えたのでコミットしておきます。

``` bash
$ git add .
$ git commit -m 'test: タイプ2の場合'
```

TODOリスト

  - ~~タイプ1の場合~~

  - タイプ2の場合
    
      - 数を文字列にして返す
        
          - ~~1を渡したら文字列"1"を返す~~
    
      - 3 の倍数のときは数を文字列にして返す
        
          - ~~3を渡したら文字列"3"を返す~~
    
      - 5 の倍数のときは数を文字列にして返す
        
          - ~~5を渡したら文字列"5"を返す~~
    
      - 3 と 5 両方の倍数の場合には数を文字列にして返す
        
          - ~~15を渡したら文字列"15"を返す~~

  - タイプ3の場合
    
      - 数を文字列にして返す
        
          - 1を渡したら文字列"1"を返す
    
      - 3 の倍数のときは数を文字列にして返す
        
          - 3を渡したら文字列"3"を返す
    
      - 5 の倍数のときは数を文字列にして返す
        
          - 5を渡したら文字列"5"を返す
    
      - 3 と 5 両方の倍数の場合には｢FizzBuzz｣と返す
        
          - 15を渡したら文字列"FizzBuzz"を返す

#### タイプ3の場合

TODOリスト

  - ~~タイプ1の場合~~

  - ~~タイプ2の場合~~

  - タイプ3の場合
    
      - 数を文字列にして返す
        
          - 1を渡したら文字列"1"を返す
    
      - 3 の倍数のときは数を文字列にして返す
        
          - 3を渡したら文字列"3"を返す
    
      - 5 の倍数のときは数を文字列にして返す
        
          - 5を渡したら文字列"5"を返す
    
      - 3 と 5 両方の倍数の場合には｢FizzBuzz｣と返す
        
          - 15を渡したら文字列"FizzBuzz"を返す

続いて、タイプ3の場合ですがやることは同じなので今回は一気にテストを書いてみましょう。

``` ruby
...
    describe 'タイプ3の場合' do
      def setup
        @fizzbuzz = FizzBuzz
      end

      describe '三の倍数の場合' do
        def test_3を渡したら文字列3を返す
          assert_equal '3', @fizzbuzz.generate(3, 3)
        end
      end

      describe '五の倍数の場合' do
        def test_5を渡したら文字列5を返す
          assert_equal '5', @fizzbuzz.generate(5, 3)
        end
      end

      describe '三と五の倍数の場合' do
        def test_15を渡したら文字列FizzBuzzを返す
          assert_equal 'FizzBuzz', @fizzbuzz.generate(15, 3)
        end
      end

      describe 'その他の場合' do
        def test_1を渡したら文字列1を返す
          assert_equal '1', @fizzbuzz.generate(1, 3)
        end
      end
    end
  end
...
```

``` bash
...
 FAIL["test_1を渡したら文字列1を返す", #<Minitest::Reporters::Suite:0x00005642171ea5a0 @name="数を文字列にして返す::タイプ3の場合::その他の場合">, 0.003375133004738018]
 test_1を渡したら文字列1を返す#数を文字列にして返す::タイプ3の場合::その他の場合 (0.00s)
        Expected: "1"
          Actual: nil
        /workspace/tdd_rb/test/fizz_buzz_test.rb:123:in `test_1を渡したら文字列1を返す'

 FAIL["test_5を渡したら文字列5を返す", #<Minitest::Reporters::Suite:0x000056421723af78 @name="数を文字列にして返す::タイプ3の場合::五の倍数の場合">, 0.003832244998193346]
 test_5を渡したら文字列5を返す#数を文字列にして返す::タイプ3の場合::五の倍数の場合 (0.00s)
        Expected: "5"
          Actual: nil
        /workspace/tdd_rb/test/fizz_buzz_test.rb:111:in `test_5を渡したら文字列5を返す'

 FAIL["test_3を渡したら文字列3を返す", #<Minitest::Reporters::Suite:0x0000564217297340 @name="数を文字列にして返す::タイプ3の場合::三の倍数の場合">, 0.0043466729985084385]
 test_3を渡したら文字列3を返す#数を文字列にして返す::タイプ3の場合::三の倍数の場合 (0.00s)
        Expected: "3"
          Actual: nil
        /workspace/tdd_rb/test/fizz_buzz_test.rb:105:in `test_3を渡したら文字列3を返す'

 FAIL["test_15を渡したら文字列FizzBuzzを返す", #<Minitest::Reporters::Suite:0x00005642174dec98 @name="数を文字列にして返す::タイプ3の場合::三と五の倍数の場合">, 0.006096020006225444]
 test_15を渡したら文字列FizzBuzzを返す#数を文字列にして返す::タイプ3の場合::三と五の倍数の場合 (0.01s)
        Expected: "FizzBuzz"
          Actual: nil
        /workspace/tdd_rb/test/fizz_buzz_test.rb:117:in `test_15を渡したら文字列FizzBuzzを返す'

  31/31: [=================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00650s
31 tests, 33 assertions, 4 failures, 0 errors, 0 skips
...
```

**case式** に処理を追加します。

``` ruby
...
class FizzBuzz
  MAX_NUMBER = 100

  def self.generate(number, type = 1)
    case type
    when 1
      is_fizz = number.modulo(3).zero?
      is_buzz = number.modulo(5).zero?

      return 'FizzBuzz' if is_fizz && is_buzz
      return 'Fizz' if is_fizz
      return 'Buzz' if is_buzz

      number.to_s
    when 2
      number.to_s
    when 3
      is_fizz = number.modulo(3).zero?
      is_buzz = number.modulo(5).zero?

      return 'FizzBuzz' if is_fizz && is_buzz

      number.to_s
    end
  end
...
```

``` bash
...
Started with run options --seed 12137


Progress: |=============================================================================|

Finished in 0.01662s
31 tests, 33 assertions, 0 failures, 0 errors, 0 skips
05:06:44 - INFO - Inspecting Ruby code style: coverage/assets/0.10.2/colorbox/loading_background.png coverage/assets/0.10.2/colorbox/controls.png coverage/assets/0.10.2/colorbox/loading.gif coverage/assets/0.10.2/colorbox/border.png lib/fizz_buzz.rb
lib/fizz_buzz.rb:6:3: C: Metrics/CyclomaticComplexity: Cyclomatic complexity for generate is too high. [10/8]
  def self.generate(number, type = 1) ...
  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
lib/fizz_buzz.rb:6:3: C: Metrics/PerceivedComplexity: Perceived complexity for generate is too high. [8/7]
  def self.generate(number, type = 1) ...
  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 1/1 file |=========================== 100 ============================>| Time: 00:00:00

1 file inspected, 2 offenses detected
...
```

テストは通りましたが新しい警告が表示されるようになりました。とりあえずコミットしておきます。

``` bash
$ git add .
$ git commit -m 'test: タイプ3の場合'
```

処理の追加により一部重複が発生しました。ここは、 **ステートメントのスライド** を適用して重複をなくしておきましょう。

> ステートメントのスライド
> 
> 旧：重複した条件記述の断片の統合
> 
> —  リファクタリング(第2版) 

> 重複した条件記述の断片の統合
> 
> 条件式のすべて分岐に同じコードの断片がある。
> 
> それを式の外側に移動する。
> 
> —  新装版 リファクタリング 

``` ruby
...
class FizzBuzz
  MAX_NUMBER = 100

  def self.generate(number, type = 1)
    case type
    when 1
      is_fizz = number.modulo(3).zero?
      is_buzz = number.modulo(5).zero?

      return 'FizzBuzz' if is_fizz && is_buzz
      return 'Fizz' if is_fizz
      return 'Buzz' if is_buzz

      number.to_s
    when 2
      number.to_s
    when 3
      is_fizz = number.modulo(3).zero?
      is_buzz = number.modulo(5).zero?

      return 'FizzBuzz' if is_fizz && is_buzz

      number.to_s
    end
  end
...
```

``` ruby
...
class FizzBuzz
  MAX_NUMBER = 100

  def self.generate(number, type = 1)
    is_fizz = number.modulo(3).zero?
    is_buzz = number.modulo(5).zero?

    case type
    when 1
      return 'FizzBuzz' if is_fizz && is_buzz
      return 'Fizz' if is_fizz
      return 'Buzz' if is_buzz

      number.to_s
    when 2
      number.to_s
    when 3
      return 'FizzBuzz' if is_fizz && is_buzz

      number.to_s
    end
  end
...
```

警告は消えていませんがプログラムは壊れていないことが確認できたのでコミットしておきます。

``` bash
$ git add .
$ git commit -m 'refactor: ステートメントのスライド'
```

TODOリスト

  - ~~タイプ1の場合~~

  - ~~タイプ2の場合~~

  - タイプ3の場合
    
      - 数を文字列にして返す
        
          - ~~1を渡したら文字列"1"を返す~~
    
      - 3 の倍数のときは数を文字列にして返す
        
          - ~~3を渡したら文字列"3"を返す~~
    
      - 5 の倍数のときは数を文字列にして返す
        
          - ~~5を渡したら文字列"5"を返す~~
    
      - 3 と 5 両方の倍数の場合には｢FizzBuzz｣と返す
        
          - ~~15を渡したら文字列"FizzBuzz"を返す~~

#### それ以外のタイプの場合

追加仕様には対応しましたがタイプ1,2,3以外の値が **引数** として渡された場合はどうしましょうか？ 現状では `nil` を返しますがこのような例外ケースも考慮する必要があります。

TODOリスト

  - ~~タイプ1の場合~~

  - ~~タイプ2の場合~~

  - ~~タイプ3の場合~~

  - それ以外のタイプの場合

**例外処理** を追加します。まず、例外のテストですが以下の様に書きます。

> 例外とは記述したプログラムが想定していない値を受け取ったり、何らかの障害が発生した場合に処理を中断して、例外オブジェクトを生成して呼び出し元のメソッドに処理を戻す機構です。
> 
> —  かんたんRuby 

``` ruby
    describe 'タイプ3の場合' do
...
    end

    describe 'それ以外のタイプの場合' do
      def setup
        @fizzbuzz = FizzBuzz
      end

      def test_例外を返す
        e = assert_raises RuntimeError do
          @fizzbuzz.generate(1, 4)
        end

        assert_equal '該当するタイプは存在しません', e.message
      end
    end
...
```

``` bash
...
 FAIL["test_例外を返す", #<Minitest::Reporters::Suite:0x0000558a26888e60 @name="数を文字列にして返す::それ以外のタイプの場合">, 0.003033002998563461]
 test_例外を返す#数を文字列にして返す::それ以外のタイプの場合 (0.00s)
        RuntimeError expected but nothing was raised.
        /workspace/tdd_rb/test/fizz_buzz_test.rb:134:in `test_例外を返す'

  32/32: [=================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00609s
32 tests, 34 assertions, 1 failures, 0 errors, 0 skips
...
```

**case式** に該当しないタイプが指定された場合は **例外を発生させる** ようにします。

> 例外を明示的に発生させるには「raise」を使います。raiseには発生させたい例外クラスを指定するのですが、何も指定しない場合はRuntimeErrorオブジェクトが生成されます。
> 
> —  かんたんRuby 

``` ruby
...
class FizzBuzz
  MAX_NUMBER = 100

  def self.generate(number, type = 1)
    is_fizz = number.modulo(3).zero?
    is_buzz = number.modulo(5).zero?

    case type
    when 1
      return 'FizzBuzz' if is_fizz && is_buzz
      return 'Fizz' if is_fizz
      return 'Buzz' if is_buzz

      number.to_s
    when 2
      number.to_s
    when 3
      return 'FizzBuzz' if is_fizz && is_buzz

      number.to_s
    else
      raise '該当するタイプは存在しません'
    end
  end
...
```

``` bash
...
07:04:53 - INFO - Running: all tests
Coverage report generated for MiniTest, Unit Tests to /workspace/tdd_rb/coverage. 16 / 16 LOC (100.0%) covered.
Started with run options --guard --seed 32508

  32/32: [=================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00600s
32 tests, 35 assertions, 0 failures, 0 errors, 0 skips
...
```

テストが通ったのでコミットしておきます。

``` bash
$ git add .
$ git commit -m 'test: それ以外のタイプの場合'
```

TODOリスト

  - *タイプ1の場合*

  - *タイプ2の場合*

  - *タイプ3の場合*

  - *それ以外のタイプの場合*

**TODOリスト**
をすべて完了しました。追加仕様を満たすプログラムは出来ましたがまだ改善の余地がありそうですね。以降ではオブジェクト指向アプローチによるコードのリファクタリングを解説していきたいと思います。

### オブジェクト指向

#### 手続き型プログラム

**オブジェクト指向** の解説の前に以下のコードを御覧ください。いわゆる **手続き型** で書かれたコードですが、これも追加仕様を満たしています。

``` ruby
MAX_NUMBER = 100
type = 1
list = []

MAX_NUMBER.times do |i|
  r = ''
  i += 1
  case type
  when 1
    if i % 3 == 0 && i % 5 == 0
      r = 'FizzBuzz'
    elsif i % 3 == 0
      r = 'Fizz'
    elsif i % 5 == 0
      r = 'Buzz'
    else
      r = i.to_s
    end
  when 2
    r = i.to_s
  when 3
    if i % 3 == 0 && i % 5 == 0
      r = 'FizzBuzz'
    else
      r = i.to_s
    end
  else
    r = '該当するタイプは存在しません'
  end

  list.push(r)
end

puts list
```

処理の流れをフローチャートにしたものです、実態はコードに記述されている内容を記号に置き換えて人間が読めるようにしたものです。

![diag-c465147b957dba9fdfaa1a1196d29378.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20035/964a4749-a319-a323-5b6b-3e9cc31d72e4.png)


#### オブジェクト指向プログラム

続いて、これまでに作ってきたコードがこちらになります。上記の **手続き型コード** との大きな違いとして `class` というキーワードでくくられている部分があります。

> クラスとは、大まかに説明すると何らかの値と処理（メソッド）をひとかたまりにしたものです。
> 
> —  かんたんRuby 

``` ruby
class FizzBuzz
  MAX_NUMBER = 100

  def self.generate(number, type = 1)
    is_fizz = number.modulo(3).zero?
    is_buzz = number.modulo(5).zero?

    case type
    when 1
      return 'FizzBuzz' if is_fizz && is_buzz
      return 'Fizz' if is_fizz
      return 'Buzz' if is_buzz

      number.to_s
    when 2
      number.to_s
    when 3
      return 'FizzBuzz' if is_fizz && is_buzz

      number.to_s
    else
      raise '該当するタイプは存在しません'
    end
  end

  def self.generate_list
    # 1から最大値までのFizzBuzz配列を1発で作る
    (1..MAX_NUMBER).map { |n| generate(n) }
  end
end
```

[UML](https://ja.wikipedia.org/wiki/%E7%B5%B1%E4%B8%80%E3%83%A2%E3%83%87%E3%83%AA%E3%83%B3%E3%82%B0%E8%A8%80%E8%AA%9E) を使って上記のコードの構造をクラス図として表現しました。

![diag-c63943e73aed75ba31adf85779eaf481.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20035/beac3f11-f8c8-e8a5-7e09-ea0dda7e6693.png)


更にシーケンス図を使って上記のコードの振る舞いを表現しました。

![diag-4cda3860e38bd75023756d182d6db0b7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20035/d4b24b58-1d0b-1ffe-f0ab-76968012a0f3.png)


**手続き型コード** のフローチャートと比べてどう思われましたか？具体的な記述が少なくデータや処理の概要だけを表現しているけどFizzBuzzのルールを知っている人であれば何をやろうとしているかのイメージはつかみやすいのではないでしょうか？だから何？と思われるかもしれませんが現時点では **オブジェクト指向** において **抽象化** がキーワードだという程度の認識で十分です。

オブジェクト指向の理解を深める取り掛かりにはこちらの記事を参照してください。

  - [オブジェクト指向のいろは](https://qiita.com/nrslib/items/73bf176147192c402049)

オブジェクト指向の詳細は控えるとして、ここでは **カプセル化** **ポリモフィズム** **継承** というオブジェクト指向プログラムで原則とされる概念をリファクタリングを通して体験してもらい、オブジェクト指向プログラムの感覚を掴んでもらうことを目的に解説を進めていきたいと思います。

### カプセル化

#### フィールドのカプセル化

![diag-c63943e73aed75ba31adf85779eaf481.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20035/beac3f11-f8c8-e8a5-7e09-ea0dda7e6693.png)

まず、データとロジックを１つのクラスにまとめていくためのリファクタリングを実施していくとします。`FizzBuzz` クラスにFizzBuzz配列を保持できるようして以下のように取得できるようにしたいと思います。

``` ruby
...
          fizzbuzz.generate_list
          @result = fizzbuzz.list
...
```

まず、 **インスタンス変数** 追加します。次に `self` キーワードを外して **クラスメソッド** から **インスタンスメソッド** に変更します。

> クラスメソッドはいくつか定義方法がありますが、どの方法を使ってもクラスメソッドとして定義されれば「クラス名.メソッド名」という形で呼び出せます。
> 
> —  かんたんRuby 

> インスタンスメソッドはコンストラクタと同じようにクラス内でdefキーワードを使ってメソッドを定義するだけで作成できます。
> 
> —  かんたんRuby 

``` ruby
class FizzBuzz
  MAX_NUMBER = 100

  def self.generate(number, type = 1)
    is_fizz = number.modulo(3).zero?
    is_buzz = number.modulo(5).zero?

    case type
    when 1
      return 'FizzBuzz' if is_fizz && is_buzz
      return 'Fizz' if is_fizz
      return 'Buzz' if is_buzz

      number.to_s
    when 2
      number.to_s
    when 3
      return 'FizzBuzz' if is_fizz && is_buzz

      number.to_s
    else
      raise '該当するタイプは存在しません'
    end
  end

  def self.generate_list
    # 1から最大値までのFizzBuzz配列を1発で作る
    (1..MAX_NUMBER).map { |n| generate(n) }
  end
end
```

``` ruby
class FizzBuzz
  MAX_NUMBER = 100

  def list
    @list
  end

  def generate(number, type = 1)
    is_fizz = number.modulo(3).zero?
    is_buzz = number.modulo(5).zero?

    case type
    when 1
      return 'FizzBuzz' if is_fizz && is_buzz
      return 'Fizz' if is_fizz
      return 'Buzz' if is_buzz

      number.to_s
    when 2
      number.to_s
    when 3
      return 'FizzBuzz' if is_fizz && is_buzz

      number.to_s
    else
      raise '該当するタイプは存在しません'
    end
  end

  def generate_list
    # 1から最大値までのFizzBuzz配列を1発で作る
    @list = (1..MAX_NUMBER).map { |n| generate(n) }
  end
end
```

``` bash
...

ERROR["test_15を渡したら文字列FizzBuzzを返す", #<Minitest::Reporters::Suite:0x00005613555ed120 @name="数を文字列にして返す::タイプ3の場合::三と五の倍数の場合">, 0.0041351839900016785]
 test_15を渡したら文字列FizzBuzzを返す#数を文字列にして返す::タイプ3の場合::三と五の倍数の場合 (0.00s)
Minitest::UnexpectedError:         NoMethodError: undefined method `generate' for FizzBuzz:Class
            /workspace/tdd_rb/test/fizz_buzz_test.rb:117:in `test_15を渡したら文字列FizzBuzzを返す'
...
```

FizzBuzz配列を **インスタンス変数** `@list` に **代入** して **インスタンス変数**経由で取得できるように変更しました。変更にあたり **クラスメソッド** `FizzBuzz.generate` と `FizzBuzz.generate_list` を **インスタンスメソッド** に変更しています。それに伴ってテストが失敗して ``NoMethodError: undefined method `generate'`` と表示されるようになってしまいました。**インスタンスメソッド** が使えるようにするため　`new` メソッドを使ってFizzBuzzクラスの **インスタンス** を作りFizzBuzz配列を **インスタンス変数** 経由で取得するようにテストコードを変更します。

> クラスとして定義された情報を元に具体的な値を伴ったオブジェクトを作成することをインスタンス化と呼び、生成されたオブジェクトのことをインスタンスと呼びます。
> 
> —  かんたんRuby 

``` ruby
...
class FizzBuzzTest < Minitest::Test
  describe '数を文字列にして返す' do
    describe 'タイプ1の場合' do
      def setup
        @fizzbuzz = FizzBuzz.new
      end
...
      describe '1から100までのFizzBuzzの配列を返す' do
        def setup
          fizzbuzz = FizzBuzz.new
          fizzbuzz.generate_list
          @result = fizzbuzz.list
        end
...
    end

    describe 'タイプ2の場合' do
      def setup
        @fizzbuzz = FizzBuzz.new
      end
...
    end

    describe 'タイプ3の場合' do
      def setup
        @fizzbuzz = FizzBuzz.new
      end
...
    end

    describe 'それ以外のタイプの場合' do
      def setup
        @fizzbuzz = FizzBuzz.new
      end
...
    end
  end
...
```

``` bash
...
07:17:36 - INFO - Running: all tests
Coverage report generated for MiniTest, Unit Tests to /workspace/tdd_rb/coverage. 5 / 17 LOC (29.41%) covered.
Started with run options --guard --seed 7701

  32/32: [=================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00616s
32 tests, 35 assertions, 0 failures, 0 errors, 0 skips
...
```

テストが直りました。**クラスメソッド** **インスタンスメソッド** **インスタンス変数** **インスタンス** などいろんな単語が出てきて戸惑ってしまったかもしれませんが、ピンとこないうちは **クラス** に値や状態を保持させるためには **インスタンス化** する必要があってそのためには `new` メソッドを使わないといけないのね程度の理解で十分です。大概のことは手を動かしているうちにピンと来るようになります。

**インスタンス変数** に直接アクセスしているのでここは **アクセッサメソッド** を使って **フィールドのカプセル化** を適用しておきます。

> オブジェクト指向ではクラス内の値をカプセル化することが重要ですが、時には内部で保持しているインスタンス変数を参照や更新できる方が良い場合もあります。複雑な処理ではなく、単にインスタンス変数にアクセスするためのメソッドのことを、アクセッサメソッドと呼びます。
> 
> —  かんたんRuby 

> フィールドのカプセル化
> 
> 公開フィールドがある。
> 
> それを非公開にして、そのアクセサを用意する。
> 
> —  新装版 リファクタリング 

自動実行の結果、以下のように書き換えられている部分を変更します。

``` ruby
class FizzBuz、
  MAX_NUMBER = 100
　attr_reader :list
...
```

``` ruby
class FizzBuzz
  MAX_NUMBER = 100
  attr_accessor :list
...
```

テストが動作して既存のコードが壊れていないことが確認できたのでここでコミットします。

``` bash
$ git add .
$ git commit -m 'refactor: フィールドのカプセル化'
```

![diag-102c44abf73ca5bda6fd8b87cc722ac9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20035/38d2567b-295a-1db9-5878-e410bfb51d2a.png)


引き続き、FizzBuzz配列は保持できるようになりましたがタイプごとに出力される配列のパターンは違います。FizzBuzzクラスにタイプを持たる必要があります。ここでは **コンストラクタ** を使って **インスタンス化** する際に **インスタンス変数** に **代入** するようにします。Rubyでは **initialize** というメソッドを使って初期化処理を実行します。

> クラスをインスタンス化した時に初期化処理を行うシチュエーションはよくあります。このような初期化処理を行うメソッドをコンストラクタと呼び、Rubyではinitializeという特別なメソッドを用意することで実現できます。
> 
> —  かんたんRuby 

``` ruby
class FizzBuzz
  MAX_NUMBER = 100
  attr_accessor :list

  def initialize(type)
    @type = type
  end
...
```

``` bash
...
ERROR["test_3を渡したら文字列3を返す", #<Minitest::Reporters::Suite:0x00005564e21e85b0 @name="数を文字列にして返す::タイプ3の場合::三の倍数の場合">, 0.004276092993677594]
 test_3を渡したら文字列3を返す#数を文字列にして返す::タイプ3の場合::三の倍数の場合 (0.00s)
Minitest::UnexpectedError:         ArgumentError: wrong number of arguments (given 0, expected 1)
            /workspace/tdd_rb/lib/fizz_buzz.rb:7:in `initialize'
            /workspace/tdd_rb/test/fizz_buzz_test.rb:101:in `new'
            /workspace/tdd_rb/test/fizz_buzz_test.rb:101:in `setup'
...
```

テストが失敗して引数が違うというエラーが表示される用になりました。`new` メソッドの **引数** にタイプを渡すようにテストを変更します。

``` ruby
...
class FizzBuzzTest < Minitest::Test
  describe '数を文字列にして返す' do
    describe 'タイプ1の場合' do
      def setup
        @fizzbuzz = FizzBuzz.new(1)
      end
...
      describe '1から100までのFizzBuzzの配列を返す' do
        def setup
          fizzbuzz = FizzBuzz.new(1)
          fizzbuzz.generate_list
          @result = fizzbuzz.list
        end
...
    end

    describe 'タイプ2の場合' do
      def setup
        @fizzbuzz = FizzBuzz.new(2)
      end
...
    end

    describe 'タイプ3の場合' do
      def setup
        @fizzbuzz = FizzBuzz.new(3)
      end
...
    end

    describe 'それ以外のタイプの場合' do
      def setup
        @fizzbuzz = FizzBuzz.new(4)
      end
...
    end
  end
...
```

``` bash
...
07:28:38 - INFO - Running: all tests
Coverage report generated for MiniTest, Unit Tests to /workspace/tdd_rb/coverage. 6 / 19 LOC (31.58%) covered.
Started with run options --guard --seed 46661

  32/32: [=================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00793s
32 tests, 35 assertions, 0 failures, 0 errors, 0 skips
...
```

テストは直りましたがまだ **インスタンス変数** のタイプが使われていないので使うようにプロダクトコードを変更します。

``` ruby
class FizzBuzz
  MAX_NUMBER = 100
  attr_accessor :list

  def initialize(type)
    @type = type
  end

  def generate(number, _type = 1)
    is_fizz = number.modulo(3).zero?
    is_buzz = number.modulo(5).zero?

    case @type
...
```

`FizzBuzz.gnerate` メソッドの **引数** から `type` を削除します。

``` ruby
class FizzBuzz
  MAX_NUMBER = 100
  attr_accessor :list

  def initialize(type)
    @type = type
  end

  def generate(number)
...
```

``` bash
...
ERROR["test_15を渡したら文字列FizzBuzzを返す", #<Minitest::Reporters::Suite:0x0000564e16c14200 @name="数を文字列にして返す::タイプ3の場合::三と五の倍数の場合">, 0.01706391001062002]
 test_15を渡したら文字列FizzBuzzを返す#数を文字列にして返す::タイプ3の場合::三と五の倍数の場合 (0.02s)
Minitest::UnexpectedError:         ArgumentError: wrong number of arguments (given 2, expected 1)
            /workspace/tdd_rb/lib/fizz_buzz.rb:11:in `generate'
            /workspace/tdd_rb/test/fizz_buzz_test.rb:118:in `test_15を渡したら文字列FizzBuzzを返す'
...
```

続いて、`FizzBuzz#generate` メソッドから不要になった **引数** `type`
を削除したところテストが壊れたのでテストコードを修正します。

``` ruby
...
class FizzBuzzTest < Minitest::Test
  describe '数を文字列にして返す' do
  ...
    describe 'タイプ2の場合' do
      def setup
        @fizzbuzz = FizzBuzz.new(2)
      end

      describe '三の倍数の場合' do
        def test_3を渡したら文字列3を返す
          assert_equal '3', @fizzbuzz.generate(3)
        end
      end

      describe '五の倍数の場合' do
        def test_5を渡したら文字列5を返す
          assert_equal '5', @fizzbuzz.generate(5)
        end
      end

      describe '三と五の倍数の場合' do
        def test_15を渡したら文字列15を返す
          assert_equal '15', @fizzbuzz.generate(15)
        end
      end

      describe 'その他の場合' do
        def test_1を渡したら文字列1を返す
          assert_equal '1', @fizzbuzz.generate(1)
        end
      end
    end

    describe 'タイプ3の場合' do
      def setup
        @fizzbuzz = FizzBuzz.new(3)
      end

      describe '三の倍数の場合' do
        def test_3を渡したら文字列3を返す
          assert_equal '3', @fizzbuzz.generate(3)
        end
      end

      describe '五の倍数の場合' do
        def test_5を渡したら文字列5を返す
          assert_equal '5', @fizzbuzz.generate(5)
        end
      end

      describe '三と五の倍数の場合' do
        def test_15を渡したら文字列FizzBuzzを返す
          assert_equal 'FizzBuzz', @fizzbuzz.generate(15)
        end
      end

      describe 'その他の場合' do
        def test_1を渡したら文字列1を返す
          assert_equal '1', @fizzbuzz.generate(1)
        end
      end
    end

    describe 'それ以外のタイプの場合' do
      def setup
        @fizzbuzz = FizzBuzz.new(4)
      end

      def test_例外を返す
        e = assert_raises RuntimeError do
          @fizzbuzz.generate(1)
        end

        assert_equal '該当するタイプは存在しません', e.message
      end
    end
  end
...
```

``` bash
...
07:34:57 - INFO - Running: all tests
Coverage report generated for MiniTest, Unit Tests to /workspace/tdd_rb/coverage. 15 / 19 LOC (78.95%) covered.
Started with run options --guard --seed 59116

  32/32: [=================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00700s
32 tests, 35 assertions, 0 failures, 0 errors, 0 skips
...
```

**インスタンス変数** の `@type` も **アクセッサメソッド** を使って **フィールドのカプセル化** を適用しておきます。

``` ruby
class FizzBuzz
  MAX_NUMBER = 100
  attr_accessor :list

  def initialize(type)
    @type = type
  end
...
```

``` ruby
class FizzBuzz
  MAX_NUMBER = 100
  attr_accessor :list
  attr_accessor :type

  def initialize(type)
    @type = type
  end
...
```

``` bash
...
Started with run options --guard --seed 56315

  32/32: [===========================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.01069s
32 tests, 35 assertions, 0 failures, 0 errors, 0 skips
...
```

コミットしておきます。

``` bash
$ git add .
$ git commit -m 'refactor: フィールドのカプセル化'
```

#### setterの削除

FizzBuzz配列を取得する **アクセッサメソッド** は現在このように定義されています。

``` ruby
class FizzBuzz
  MAX_NUMBER = 100
  attr_accessor :list
  attr_accessor :type
...
```

以下のようにテストコードを変更したらどうなるでしょうか？

``` ruby
...
      describe '1から100までのFizzBuzzの配列を返す' do
        def setup
          fizzbuzz = FizzBuzz.new(1)
          fizzbuzz.generate_list
          @result = fizzbuzz.list
        end
...
```

``` ruby
...
      describe '1から100までのFizzBuzzの配列を返す' do
        def setup
          fizzbuzz = FizzBuzz.new(1)
          fizzbuzz.generate_list
          fizzbuzz.list = []
          @result = fizzbuzz.list
        end
...
```

``` bash
...
 FAIL["test_配列の2番目は文字列のFizzを返す", #<Minitest::Reporters::Suite:0x0000563c29a8a8c0 @name="数を文字列にして返す::タイプ1の場合::1から100までのFizzBuzzの配列を返す">, 0.005137628992088139]
 test_配列の2番目は文字列のFizzを返す#数を文字列にして返す::タイプ1の場合::1から100までのFizzBuzzの配列を返す (0.01s)
        Expected: "Fizz"
          Actual: nil
        /workspace/tdd_rb/test/fizz_buzz_test.rb:58:in `test_配列の2番目は文字列のFizzを返す'
...
```

FizzBuzz配列が初期化されてしまいました。**アクセッサメソッド** に参照のための **getter** と 更新するための **setter** が許可されているため **カプセル化** が破られてしまいました。ここは **setterの削除** を適用して外部からの更新を出来ないようにしておきましょう。

> getterを定義するには、「attr\_reader」を使います。このメソッドにインスタンス変数の「@」を除いた名称をシンボル表現にしたものを列挙します。複数ある場合はカンマで区切って複数の値を指定することができます。
> 
> —  かんたんRuby
> 

> setterを定義するには、「attr\_writer」を使います。このメソッドもattr\_readerと同じくインスタンス変数名の「@」を除いた名称をシンボル表現にしたものを列挙します。複数ある場合はカンマで区切って複数の値を指定することができます。
> 
> —  かんたんRuby
> 

> getter/setterの両方を定義する場合、そのインスタンスは属しているクラス外から自由に参照や更新ができてしまいます。これはカプセル化の観点には反した挙動なので、できる限りattr\_readerだけで済ませられないか検討しましょう。
> 
> —  かんたんRuby
> 

> setterの削除
> 
> setterが用意されているということは、フィールドが変更される可能性があることを意味します。オブジェクトを生成した後でフィールドを変更したくないなら、setterは用意しません（加えて、フィールドを変更不可にします）。そうすることで、フィールドはコンストラクタでのみで設定され、変更させないという意図が明確になって、フィールドが変更される可能性を、たいていは排除できます。
> 
> —  リファクタリング(第2版) 

Rubyでは以下のようにして **インスタンス変数** を読み取り専用にします。

``` ruby
class FizzBuzz
  MAX_NUMBER = 100
  attr_reader :list
  attr_accessor :type
...
```

``` bash
ERROR["test_配列の2番目は文字列のFizzを返す", #<Minitest::Reporters::Suite:0x000055b32efd75f0 @name="数を文字列にして返す::タイプ1の場合::1から100までのFizzBuzzの配列を返す">, 0.008614362974185497]
 test_配列の2番目は文字列のFizzを返す#数を文字列にして返す::タイプ1の場合::1から100までのFizzBuzzの配列を返す (0.01s)
Minitest::UnexpectedError:         NoMethodError: undefined method `list=' for #<FizzBuzz:0x000055b32ee8c678>
        Did you mean?  list
            /workspace/tdd_rb/test/fizz_buzz_test.rb:45:in `setup'
```

更新メソッドは存在しませんというエラーに変わったことが確認できたのでテストを元にもどします。

同様に **インスタンス変数** の `@type` も読み取り専用にします。

``` ruby
class FizzBuzz
  MAX_NUMBER = 100
  attr_reader :list
  attr_reader :type
...
```

``` bash
...
04:32:06 - INFO - Running: all tests
Coverage report generated for MiniTest, Unit Tests to /workspace/tdd_rb/coverage. 22 / 22 LOC (100.0%) covered.
Started with run options --guard --seed 20902

  32/32: [===========================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00920s
...
```

テストが壊れていないことを確認したらコミットします。

``` bash
$ git add .
$ git commit -m 'refactor: setterの削除'
```

![diag-a8b45c800de2a31873f9eba1feac2184.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20035/ee9e4f7d-6d39-04a5-e8fe-3308d7c510d8.png)

### ポリモーフィズム

#### ポリモーフィズムによる条件記述の置き換え 1

![diag-a8b45c800de2a31873f9eba1feac2184.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20035/ee9e4f7d-6d39-04a5-e8fe-3308d7c510d8.png)


リファクタリングによりデータとロジックを１つのクラスにまとめて **カプセル化** を進めることが出来ました。しかし、以下の警告メッセージが表示されたままです。**ポリモーフィズム** を使ったロジックのリファクタリングを実施していきましょう。

``` bash
...
07:53:29 - INFO - Inspecting Ruby code style: test/fizz_buzz_test.rb lib/fizz_buzz.rb
lib/fizz_buzz.rb:11:3: C: Metrics/CyclomaticComplexity: Cyclomatic complexity for generate is too high. [10/8]
  def generate(number) ...
  ^^^^^^^^^^^^^^^^^^^^
lib/fizz_buzz.rb:11:3: C: Metrics/PerceivedComplexity: Perceived complexity for generate is too high. [8/7]
  def generate(number) ...
  ^^^^^^^^^^^^^^^^^^^^
 2/2 files |====================================== 100 =======================================>| Time: 00:00:00

2 files inspected, 2 offenses detected
...
```

[循環的複雑度](https://ja.wikipedia.org/wiki/%E5%BE%AA%E7%92%B0%E7%9A%84%E8%A4%87%E9%9B%91%E5%BA%A6) が高く可読性が低く複雑なコードと警告されているようです。対象となっている　`FizzBuzz#generate` を確認してみましょう。

  - [Metrics/CyclomaticComplexity](https://rubocop.readthedocs.io/en/latest/cops_metrics/#metricscyclomaticcomplexity)

  - [Metrics/PerceivedComplexity](https://rubocop.readthedocs.io/en/latest/cops_metrics/#metricsperceivedcomplexity)

<!-- end list -->

``` ruby
...
  def generate(number)
    is_fizz = number.modulo(3).zero?
    is_buzz = number.modulo(5).zero?

    case @type
    when 1
      return 'FizzBuzz' if is_fizz && is_buzz
      return 'Fizz' if is_fizz
      return 'Buzz' if is_buzz

      number.to_s
    when 2
      number.to_s
    when 3
      return 'FizzBuzz' if is_fizz && is_buzz

      number.to_s
    else
      raise '該当するタイプは存在しません'
    end
  end
...
```

コードの不吉な臭いである **スイッチ文** に該当するコードのようなのでここはリファクタリングカタログに従って **ポリモーフィズムによる条件記述の置き換え** を適用していきましょう。比較的大きなリファクタリングなのでいくつかのステップに分けて進めていきます。

> スイッチ文
> 
> オブジェクト指向プログラミングのメリットして、スイッチ文が従来にくらべて少なくなるということがあります。スイッチ文は重複したコードを生み出す問題児です。コードのあちらこちらに同じようなスイッチ文が見られることがあります。これでは新たな分岐を追加したときに、すべてのスイッチ文を探して似たような変更をしていかなければなりません。オブジェクト指向ではポリモーフィズムを使い、この問題をエレガントに解決できます。
> 
> —  新装版 リファクタリング
> 

> 重複したスイッチ文
> 
> 最近はポリモーフィズムも一般的となり、15年前に比べるとswitch文が単純に赤信号というわけでもなくなりました。また、多くのプログラミング言語が、基本データ型以外をサポートする、より洗練されたswitch文を提供してきています。そこで、今後問題とするのは、重複したswitch文のみとします。switch/case文や、ネストしたif/else文の形で、コードのさまざまな箇所に同じ条件分岐ロジックが書かれていれば、それは「不吉な臭い」です。重複した条件分岐が問題なのは、新たな分岐を追加したら、すべての重複した条件分岐を探して更新指定かなけれけならないからです。ポリモーフィズムは、そうした単調な繰り返しに誘うダークフォースに対抗するための、洗練された武器です。コードベースをよりモダンにしていきましょう。
> 
> —  リファクタリング(第2版) 

> ポリモーフィズムによる条件記述の置き換え
> 
> オブジェクトのタイプによって異なる振る舞いを選択する条件記述がある。
> 
> 条件記述の各アクション部をサブクラスでオーバーライドするメソッドに移動する。元のメソッドはabstractにする。
> 
> —  新装版 リファクタリング 

``` ruby
class FizzBuzz
...
end

class FizzBuzzType01; end
class FizzBuzzType02; end
class FizzBuzzType03; end
```

まず、タイプごとのクラスを定義します。

``` ruby
class FizzBuzz
  MAX_NUMBER = 100
  attr_reader :list
  attr_reader :type

  def initialize(type)
    @type = type
  end

  def self.create(type)
    case type
    when 1
      FizzBuzzType01.new
    when 2
      FizzBuzzType02.new
    when 3
      FizzBuzzType03.new
    else
      raise '該当するタイプは存在しません'
    end

...
```

次に、タイプごとのクラスを **インスタンス化** する **ファクトリメソッド** をFizzBuzzクラスに追加します。この時点では新しいクラスとメソッドの追加だけなのでテストは壊れていないはずです（警告は出ていますが・・・）。ここでコミットしておきますがリファクタリング作業としては [仕掛](https://ja.wikipedia.org/wiki/%E4%BB%95%E6%8E%9B%E5%93%81) なのでWIP(Work In Progress)をメッセージに追加してコミットします。

``` bash
$ git add .
$ git commit -m 'refactor(WIP): ポリモーフィズムによる条件記述の置き換え'
```

![diag-c83e15398192d4cb68c948dfda55870b.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20035/abe9acc4-11e7-a82a-f236-87753512f82e.png)

#### ポリモーフィズムによる条件記述の置き換え 2

続いて、各タイプクラスに **インスタンスメソッド** を実装します。ここでは **case式** の各処理をコピー&ペーストしています。カット&ペーストするとプロダクトコードが壊れたままリファクタリングを進めることになるのでここは慎重に進めていきます。

``` ruby
class FizzBuzz
...
end

class FizzBuzzType01; end
class FizzBuzzType02; end
class FizzBuzzType03; end
```

``` ruby
...
class FizzBuzzType01
  def generate(number)
    is_fizz = number.modulo(3).zero?
    is_buzz = number.modulo(5).zero?

    return 'FizzBuzz' if is_fizz && is_buzz
    return 'Fizz' if is_fizz
    return 'Buzz' if is_buzz

    number.to_s
  end
end
...
```

``` ruby
...
class FizzBuzzType02
  def generate(number)
    number.to_s
  end
end
...
```

``` ruby
...
class FizzBuzzType03
  def generate(number)
    is_fizz = number.modulo(3).zero?
    is_buzz = number.modulo(5).zero?

    return 'FizzBuzz' if is_fizz && is_buzz

    number.to_s
  end
end
```

警告は出ますがテストは壊れていないのでコミットします。

``` bash
$ git add .
$ git commit -m 'refactor(WIP): ポリモーフィズムによる条件記述の置き換え'
```

![diag-c9ffad9b803420dabdb72a8eaf15cb72.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20035/87bc8d77-c195-852c-e517-86272f494ab7.png)

#### ポリモーフィズムによる条件記述の置き換え 3

これで準備は整いましたのでテストコードの `setup` メソッドを **ファクトリメソッド** の呼び出しに変更します。以下の部分は変更してはいけません。理由はわかりますか？

``` ruby
...
      describe '1から100までのFizzBuzzの配列を返す' do
        def setup
          fizzbuzz = FizzBuzz.new(1)
          fizzbuzz.generate_list
          @result = fizzbuzz.list
        end
...
```

``` ruby
...
class FizzBuzzTest < Minitest::Test
  describe '数を文字列にして返す' do
    describe 'タイプ1の場合' do
      def setup
        @fizzbuzz = FizzBuzz.create(1)
      end
...
    describe 'タイプ2の場合' do
      def setup
        @fizzbuzz = FizzBuzz.create(2)
      end
...
    describe 'タイプ3の場合' do
      def setup
        @fizzbuzz = FizzBuzz.create(3)
      end
...
    describe 'それ以外のタイプの場合' do
      def setup
        @fizzbuzz = FizzBuzz.create(4)
      end

      def test_例外を返す
        e = assert_raises RuntimeError do
          @fizzbuzz.generate(1)
        end

        assert_equal '該当するタイプは存在しません', e.message
      end
    end
  end
```

``` bash
...
08:14:14 - INFO - Running: all tests
Coverage report generated for MiniTest, Unit Tests to /workspace/tdd_rb/coverage. 26 / 42 LOC (61.9%) covered.
Started with run options --guard --seed 37585

ERROR["test_例外を返す", #<Minitest::Reporters::Suite:0x000056317940fa28 @name="数を文字列にして返す::それ以外のタイプの場合">, 0.0037079370085848495]
 test_例外を返す#数を文字列にして返す::それ以外のタイプの場合 (0.00s)
Minitest::UnexpectedError:         RuntimeError: 該当するタイプは存在しません
            /workspace/tdd_rb/lib/fizz_buzz.rb:20:in `create'
            /workspace/tdd_rb/test/fizz_buzz_test.rb:132:in `setup'

  32/32: [=================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00685s
32 tests, 33 assertions, 0 failures, 1 errors, 0 skips
...
```

失敗するテストがありますね、該当するコードを確認したところ例外が発生するタイミングが変わってしまったので以下のように変更します。

``` ruby
...
    describe 'それ以外のタイプの場合' do
      def setup
        @fizzbuzz = FizzBuzz.create(4)
      end

      def test_例外を返す
        e = assert_raises RuntimeError do
          @fizzbuzz.generate(1)
        end

        assert_equal '該当するタイプは存在しません', e.message
      end
    end
...
```

``` ruby
...
    describe 'それ以外のタイプの場合' do
      def test_例外を返す
        e = assert_raises RuntimeError do
          FizzBuzz.create(4)
        end

        assert_equal '該当するタイプは存在しません', e.message
      end
    end
...
```

``` bash
...
08:18:08 - INFO - Running: all tests
Coverage report generated for MiniTest, Unit Tests to /workspace/tdd_rb/coverage. 37 / 42 LOC (88.1%) covered.
Started with run options --guard --seed 40171

  32/32: [=================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00559s
32 tests, 35 assertions, 0 failures, 0 errors, 0 skips
...
```

コミットしておきましょう。

``` bash
$ git add .
$ git commit -m 'refactor(WIP): ポリモーフィズムによる条件記述の置き換え'
```

![diag-852794d6dd3e17ad001905a500b520e3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20035/1048a22a-49cd-7f28-f688-69a62e61c5a9.png)

#### ポリモーフィズムによる条件記述の置き換え 4

タイプごとにFizzBuzzを生成するクラスを用意したのでFizzBuzzクラスから呼び出せるようにしましょう。

``` ruby
class FizzBuzz
  MAX_NUMBER = 100
  attr_reader :list
  attr_reader :type

  def initialize(type)
    @type = type
  end
...
  def generate_list
    # 1から最大値までのFizzBuzz配列を1発で作る
    @list = (1..MAX_NUMBER).map { |n| generate(n) }
  end
end
```

まず、**コンストラクタ** から **クラスメソッド** の **ファクトリメソッド** を呼び出して **インスタンス変数** の `type` にタイプクラスの **参照** を **代入** します。

``` ruby
class FizzBuzz
  MAX_NUMBER = 100
  attr_reader :list
  attr_reader :type

  def initialize(type)
    @type = FizzBuzz.create(type)
  end
...
  def generate_list
    # 1から最大値までのFizzBuzz配列を1発で作る
    @list = (1..MAX_NUMBER).map { |n| generate(n) }
  end
end
```

``` bash
ERROR["test_配列の14番目は文字列のFizzBuzzを返す", #<Minitest::Reporters::Suite:0x000055670a343110 @name="数を文字列にして返す::タイプ1の場合::1から100までのFizzBuzzの配列を返す">, 0.006740843993611634]
 test_配列の14番目は文字列のFizzBuzzを返す#数を文字列にして返す::タイプ1の場合::1から100までのFizzBuzzの配列を返す (0.01s)
Minitest::UnexpectedError:         RuntimeError: 該当するタイプは存在しません
            /workspace/tdd_rb/lib/fizz_buzz.rb:42:in `generate'
            /workspace/tdd_rb/lib/fizz_buzz.rb:48:in `block in generate_list'
            /workspace/tdd_rb/lib/fizz_buzz.rb:48:in `each'
            /workspace/tdd_rb/lib/fizz_buzz.rb:48:in `map'
            /workspace/tdd_rb/lib/fizz_buzz.rb:48:in `generate_list'
            /workspace/tdd_rb/test/fizz_buzz_test.rb:44:in `setup'
```

テストが失敗して沢山エラーが表示するようになりましたが落ち着いてください。次に **インスタンスメソッド** `FizzBuzz#generate_list` 内の `FizzBuzz#generate` メソッド呼び出しを **インスタンス変数** `type` が参照するタイプクラスのメソッド `FizzBuzzTypeXX#generate` を呼び出すように変更します。

``` ruby
class FizzBuzz
  MAX_NUMBER = 100
  attr_reader :list
  attr_reader :type

  def initialize(type)
    @type = FizzBuzz.create(type)
  end
...
  def generate_list
    # 1から最大値までのFizzBuzz配列を1発で作る
    @list = (1..MAX_NUMBER).map { |n| @type.generate(n) }
  end
end
```

``` bash
Started with run options --seed 13878


Progress: |=====================================================================================================|

Finished in 0.00960s
32 tests, 35 assertions, 0 failures, 0 errors, 0 skips
05:54:49 - INFO - Inspecting Ruby code style: lib/fizz_buzz.rb
lib/fizz_buzz.rb:24:3: C: Metrics/CyclomaticComplexity: Cyclomatic complexity for generate is too high. [10/8]
  def generate(number) ...
  ^^^^^^^^^^^^^^^^^^^^
lib/fizz_buzz.rb:24:3: C: Metrics/PerceivedComplexity: Perceived complexity for generate is too high. [8/7]
  def generate(number) ...
  ^^^^^^^^^^^^^^^^^^^^
 1/1 file |======================================= 100 ========================================>| Time: 00:00:00

1 file inspected, 2 offenses detected
```

再びテストが通るようになりました。始めのうちはコードを少し変更しただけでなんで動くようになったの？と思うかもしれませんがこれが **ポリモーフィズム** の威力です。この概念を感覚としてつかんで使いこなせるようになることがオブジェクト指向プログラミングの第一歩です。感覚は意識して手を動かしていればそのうちつかめます（多分）。

**ポリモーフィズムによる条件記述の置き換え** が完了したのでWIPを外してコミットします。

``` bash
$ git add .
$ git commit -m 'refactor ポリモーフィズムによる条件記述の置き換え'
```

#### State/Strategyによるタイプコードの置き換え

仕上げは　**State/Strategyによるタイプコードの置き換え** を適用して、警告メッセージを消すとしましょう。

> State/Strategyによるタイプコードの置き換え
> 
> クラスの振る舞いに影響するタイプコードがあるが、サブクラス化はできない。
> 
> 状態オブジェクトでタイプコードを置き換える
> 
> —  新装版 リファクタリング 

![diag-852794d6dd3e17ad001905a500b520e3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20035/1048a22a-49cd-7f28-f688-69a62e61c5a9.png)

```ruby
class FizzBuzz
  MAX_NUMBER = 100
  attr_reader :list
  attr_reader :type

  def initialize(type)
    @type = FizzBuzz.create(type)
  end

  def self.create(type)
    case type
    when 1
      FizzBuzzType01.new
    when 2
      FizzBuzzType02.new
    when 3
      FizzBuzzType03.new
    else
      raise '該当するタイプは存在しません'
    end
  end

  def generate(number)
    is_fizz = number.modulo(3).zero?
    is_buzz = number.modulo(5).zero?

    case @type
    when 1
      return 'FizzBuzz' if is_fizz && is_buzz
      return 'Fizz' if is_fizz
      return 'Buzz' if is_buzz

      number.to_s
    when 2
      number.to_s
    when 3
      return 'FizzBuzz' if is_fizz && is_buzz

      number.to_s
    else
      raise '該当するタイプは存在しません'
    end
  end

  def generate_list
    # 1から最大値までのFizzBuzz配列を1発で作る
    @list = (1..MAX_NUMBER).map { |n| @type.generate(n) }
  end
end
...
```

まず、`FizzBuzz#generate` のメソッド呼び出しを **インスタンス変数** `type` が参照するタイプクラスのメソッド `FizzBuzzTypeXX#generate` に **委譲** するように変更します。

``` ruby
...
  def generate(number)
    @type.generate(number)
  end

  def generate_list
    # 1から最大値までのFizzBuzz配列を1発で作る
    @list = (1..MAX_NUMBER).map { |n| @type.generate(n) }
  end
end
...
```

``` bash
...
Started with run options --seed 49543


Progress: |=====================================================================================================|

Finished in 0.00925s
32 tests, 35 assertions, 0 failures, 0 errors, 0 skips
06:34:27 - INFO - Inspecting Ruby code style: lib/fizz_buzz.rb
 1/1 file |======================================= 100 ========================================>| Time: 00:00:00

1 file inspected, no offenses detected
06:34:29 - INFO - Inspecting Ruby code style: coverage/assets/0.10.2/colorbox/loading_background.png coverage/assets/0.10.2/colorbox/controls.png coverage/assets/0.10.2/colorbox/loading.gif coverage/assets/0.10.2/colorbox/border.png
 0/0 files |======================================= 100 =======================================>| Time: 00:00:00

0 files inspected, no offenses detecte
...
```

警告が消えました。しかもテストは壊れていないようです。実は `FizzBuzz#generate` メソッドはどこからも使われていないためテストも壊れることが無いのですがこれでは不要なメソッドになってしまうので **移譲の隠蔽** を実施して、ロジックを **カプセル化** します。

> 委譲の隠蔽
> 
> オブジェクト指向について最初に教わる時、カプセル化とはフィールドを隠すことだと習うでしょう。しかし経験を積むにつれて、他にもカプセル化できるものがあることに気づきます。
> 
> —  リファクタリング(第2版) 

``` ruby
...
  def generate(number)
    @type.generate(number)
  end

  def generate_list
    # 1から最大値までのFizzBuzz配列を1発で作る
    @list = (1..MAX_NUMBER).map { |n| generate(n) }
  end
end
...
```

テストもFizzBuzzインスタンス経由で実行するように修正しておきます。これですべての呼び出しが `new` メソッド経由となりテストコードに一貫性を取り戻すことが出来ました。

``` ruby
...
class FizzBuzzTest < Minitest::Test
  describe '数を文字列にして返す' do
    describe 'タイプ1の場合' do
      def setup
        @fizzbuzz = FizzBuzz.new(1)
      end
...
      describe '1から100までのFizzBuzzの配列を返す' do
        def setup
          fizzbuzz = FizzBuzz.new(1)
          fizzbuzz.generate_list
          @result = fizzbuzz.list
        end
...
    describe 'タイプ2の場合' do
      def setup
        @fizzbuzz = FizzBuzz.new(2)
      end
...
    describe 'タイプ3の場合' do
      def setup
        @fizzbuzz = FizzBuzz.new(3)
      end
...
    describe 'それ以外のタイプの場合' do
      def test_例外を返す
        e = assert_raises RuntimeError do
          FizzBuzz.new(4)
        end

        assert_equal '該当するタイプは存在しません', e.message
      end
    end
  end
...
```

``` bash
...
08:32:17 - INFO - Running: all tests
Coverage report generated for MiniTest, Unit Tests to /workspace/tdd_rb/coverage. 32 / 32 LOC (100.0%) covered.
Started with run options --guard --seed 63863

  32/32: [=================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00564s
32 tests, 35 assertions, 0 failures, 0 errors, 0 skips

08:32:18 - INFO - Inspecting Ruby code style of all files
 7/7 files |====================================== 100 =======================================>| Time: 00:00:00

7 files inspected, no offenses detected
...
```

**ポリモーフィズム** の感覚がつかめないうちは `FizzBuzz#generate` のコードが一行になったのに既存のテストも壊れず動いていることが不思議に思うかもしれません。しかしコードとしてはFizzBuzzクラスの `generate` メソッドは任意のタイプクラスの `generate` メソッドを呼び出しているだけで処理の詳細は理解しなくても振る舞いを理解できる **抽象化** された読みやすいコードになりました。静的コード解析も可読性が高くシンプルなコードとみなしてくれているようです。さて、警告メッセージもなくなり、テストも壊れていないのでコミットしておきましょう。

``` bash
$ git add .
$ git commit -m 'refactor: State/Strategyによるタイプコードの置き換え'
```

![diag-852794d6dd3e17ad001905a500b520e3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20035/1048a22a-49cd-7f28-f688-69a62e61c5a9.png)

### 継承

分割したタイプクラスのメソッドに重複する処理があるので **継承** を使ってリファクタリングしましょう。ここでは **スーパークラスの抽出**を適用します。

> スーパークラスの抽出
> 
> 似通った特性を持つ２つのクラスがある。
> 
> スーパークラスを作成して、共通の特性を移動する。
> 
> —  新装版 リファクタリング 

#### スーパークラスの抽出

![diag-852794d6dd3e17ad001905a500b520e3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20035/1048a22a-49cd-7f28-f688-69a62e61c5a9.png)

まずは、タイプクラスのスーパークラスとなる `FizzBuzzType` クラスを作成して各タイプクラスに継承させます。

> クラスベースのオブジェクト指向言語の多くはクラスの継承機能を有しています。クラスの継承とはあるクラスを元として、新しいクラスを定義することです。この時、継承元となるクラスを親クラスやスーパークラスと呼び、継承したクラスのことを子クラスやサブクラスと呼びます。
> 
> —  かんたんRuby 

Rubyの **クラスの継承** は以下のように書きます。

``` ruby
class FizzBuzz
...
end

class FizzBuzzType; end

class FizzBuzzType01
...
```

``` ruby
...
class FizzBuzzType; end

class FizzBuzzType01 < FizzBuzzType
...
end

class FizzBuzzType02 < FizzBuzzType
...
end

class FizzBuzzType03 < FizzBuzzType
...
end
```

スーパークラス `FizzBuzzType` を定義して各サブクラスに継承させます。

``` bash
08:42:24 - INFO - Running: all tests
Coverage report generated for MiniTest, Unit Tests to /workspace/tdd_rb/coverage. 33 / 33 LOC (100.0%) covered.
Started with run options --guard --seed 43548

  32/32: [=================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00860s
32 tests, 35 assertions, 0 failures, 0 errors, 0 skips

08:42:25 - INFO - Inspecting Ruby code style of all files
 7/7 files |====================================== 100 =======================================>| Time: 00:00:00

7 files inspected, no offenses detected
```

![diag-df749de89e01204bc0eab92419515a4c.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20035/9e68539c-0b82-d0b7-3ede-4f6892399abf.png)

次に `is_fizz` `is_buzz` 部分を共通メソッドとしてスーパークラスに定義して各タイプクラスで呼び出すように変更します。

``` ruby
...
class FizzBuzzType; end

class FizzBuzzType01 < FizzBuzzType
  def generate(number)
    is_fizz = number.modulo(3).zero?
    is_buzz = number.modulo(5).zero?

    return 'FizzBuzz' if is_fizz && is_buzz
    return 'Fizz' if is_fizz
    return 'Buzz' if is_buzz

    number.to_s
  end
end

class FizzBuzzType02 < FizzBuzzType
  def generate(number)
    number.to_s
  end
end

class FizzBuzzType03 < FizzBuzzType
  def generate(number)
    is_fizz = number.modulo(3).zero?
    is_buzz = number.modulo(5).zero?

    return 'FizzBuzz' if is_fizz && is_buzz

    number.to_s
  end
end
```

``` ruby
...
class FizzBuzzType
  def is_fizz(number)
    number.modulo(3).zero?
  end

  def is_buzz(number)
    number.modulo(5).zero?
  end
end

class FizzBuzzType01 < FizzBuzzType
  def generate(number)
    return 'FizzBuzz' if is_fizz(number) && is_buzz(number)
    return 'Fizz' if is_fizz(number)
    return 'Buzz' if is_buzz(number)

    number.to_s
  end
end

class FizzBuzzType02 < FizzBuzzType
  def generate(number)
    number.to_s
  end
end

class FizzBuzzType03 < FizzBuzzType
  def generate(number)
    return 'FizzBuzz' if is_fizz(number) && is_buzz(number)

    number.to_s
  end
end
```

``` bash
08:50:16 - INFO - Running: all tests
Coverage report generated for MiniTest, Unit Tests to /workspace/tdd_rb/coverage. 33 / 33 LOC (100.0%) covered.
Started with run options --guard --seed 45685

  32/32: [=================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.01073s
32 tests, 35 assertions, 0 failures, 0 errors, 0 skips

08:50:17 - INFO - Inspecting Ruby code style of all files
lib/fizz_buzz.rb:35:7: C: Naming/PredicateName: Rename is_fizz to fizz?.
  def is_fizz(number)
      ^^^^^^^
lib/fizz_buzz.rb:39:7: C: Naming/PredicateName: Rename is_buzz to buzz?.
  def is_buzz(number)
      ^^^^^^^
 7/7 files |====================================== 100 =======================================>| Time: 00:00:00

7 files inspected, 2 offenses detected
```

テストが壊れていないことが確認できたのでコミットしておきます。

``` bash
$ git add .
$ git commit -m 'refactor: スーパークラスの抽出'
```

![diag-119864cf3ef287a3cb00d3a5ae7f7768.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20035/ee6b125d-dc1f-3069-5f0c-c698ffe251be.png)

#### メソッド名の変更

**スーパークラスの抽出** を実施したところまた警告メッセージが表示されるようになりました。

``` bash
08:50:19 - INFO - Inspecting Ruby code styl
e: coverage/assets/0.10.2/colorbox/loading_background.png coverage/assets/0.10.2/colorbox/controls.png coverage/assets/0.10.2/colorbox/loading.gif coverage/assets/0.10.2/colorbox/border.png lib/fizz_buzz.rb
lib/fizz_buzz.rb:35:7: C: Naming/PredicateName: Rename is_fizz to fizz?.
  def is_fizz(number)
      ^^^^^^^
lib/fizz_buzz.rb:39:7: C: Naming/PredicateName: Rename is_buzz to buzz?.
  def is_buzz(number)
      ^^^^^^^
 1/1 file |======================================= 100 =======================================>| Time: 00:00:00

1 file inspected, 2 offenses detected
```

[Naming/PredicateName](https://rubocop.readthedocs.io/en/latest/cops_naming/#namingpredicatename) Rubyのネーミングとしてはよろしくないようなので指示に従って **メソッド名の変更** を実施しましょう。

``` ruby
...
class FizzBuzzType
  def is_fizz(number)
    number.modulo(3).zero?
  end

  def is_buzz(number)
    number.modulo(5).zero?
  end
end

class FizzBuzzType01 < FizzBuzzType
  def generate(number)
    return 'FizzBuzz' if is_fizz(number) && is_buzz(number)
    return 'Fizz' if is_fizz(number)
    return 'Buzz' if is_buzz(number)

    number.to_s
  end
end

class FizzBuzzType02 < FizzBuzzType
  def generate(number)
    number.to_s
  end
end

class FizzBuzzType03 < FizzBuzzType
  def generate(number)
    return 'FizzBuzz' if is_fizz(number) && is_buzz(number)

    number.to_s
  end
end
```

``` ruby
...
class FizzBuzzType
  def fizz?(number)
    number.modulo(3).zero?
  end

  def buzz?(number)
    number.modulo(5).zero?
  end
end

class FizzBuzzType01 < FizzBuzzType
  def generate(number)
    return 'FizzBuzz' if fizz?(number) && buzz?(number)
    return 'Fizz' if fizz?(number)
    return 'Buzz' if buzz?(number)

    number.to_s
  end
end

class FizzBuzzType02 < FizzBuzzType
  def generate(number)
    number.to_s
  end
end

class FizzBuzzType03 < FizzBuzzType
  def generate(number)
    return 'FizzBuzz' if fizz?(number) && buzz?(number)

    number.to_s
  end
end
```

``` bash
Progress: |====================================================================================================|

Finished in 0.01144s
32 tests, 35 assertions, 0 failures, 0 errors, 0 skips
08:53:35 - INFO - Inspecting Ruby code style: lib/fizz_buzz.rb
 1/1 file |======================================= 100 =======================================>| Time: 00:00:00

1 file inspected, no offenses detected
```

作業としては難しくないのでミスタイプしないように（まあ、ミスタイプしてもテストが教えてくれますが・・・）変更してコミットしましょう。

``` bash
$ git add .
$ git commit -m 'refactor: メソッド名の変更'
```

![diag-f06a12d01484b03fa6f2f85b062a3cf0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20035/431fe33d-a0fc-669b-e26d-ec514e9959b5.png)

#### メソッドの移動

`FizzBuzz` クラスの **ファクトリメソッド** ですが **特性の横恋慕** の臭いがするので **メソッドの移動** を実施します。

> 特性の横恋慕
> 
> オブジェクト指向には、処理および処理に必要なデータを１つにまとめてしまうという重要な考え方があります。あるメソッドが、自分のクラスより他のクラスに興味を持つような場合には、古典的な誤りを犯しています。
> 
> —  新装版 リファクタリング
> 

> メソッドの移動
> 
> あるクラスでメソッドが定義されているが、現在または将来において、そのクラスの特性よりも他のクラスの特性の方が、そのメソッドを使ったり、そのメソッドから使われたりすることが多い。
> 
> 同様の本体を持つ新たなメソッドを、それを最も多用するクラスに作成する。元のメソッドは、単純な委譲とするか、またはまるごと取り除く。
> 
> —  新装版 リファクタリング 

``` ruby
class FizzBuzz
  MAX_NUMBER = 100
  attr_reader :list

  def initialize(type)
    @type = FizzBuzz.create(type)
  end

  def self.create(type)
    case type
    when 1
      FizzBuzzType01.new
    when 2
      FizzBuzzType02.new
    when 3
      FizzBuzzType03.new
    else
      raise '該当するタイプは存在しません'
    end
  end

  def generate(number)
    @type.generate(number)
  end

  def generate_list
    # 1から最大値までのFizzBuzz配列を1発で作る
    @list = (1..MAX_NUMBER).map { |n| generate(n) }
  end
end

class FizzBuzzType
  def fizz?(number)
    number.modulo(3).zero?
  end

  def buzz?(number)
    number.modulo(5).zero?
  end
end
...
```

**クラスメソッド** `FizzBuzz.create` をカット&ペーストして `FizzBuzzType.create` に移動します。 `FizzBuzz` の **コンストラクタ** で呼び出している **クラスメソッド** を `FizzBuzzType.create` に変更します。

``` ruby
class FizzBuzz
  MAX_NUMBER = 100
  attr_reader :list

  def initialize(type)
    @type = FizzBuzzType.create(type)
  end

  def generate(number)
    @type.generate(number)
  end

  def generate_list
    # 1から最大値までのFizzBuzz配列を1発で作る
    @list = (1..MAX_NUMBER).map { |n| generate(n) }
  end
end

class FizzBuzzType
  def self.create(type)
    case type
    when 1
      FizzBuzzType01.new
    when 2
      FizzBuzzType02.new
    when 3
      FizzBuzzType03.new
    else
      raise '該当するタイプは存在しません'
    end
  end

  def fizz?(number)
    number.modulo(3).zero?
  end

  def buzz?(number)
    number.modulo(5).zero?
  end
end
...
```

``` bash
08:59:27 - INFO - Running: all tests
Coverage report generated for MiniTest, Unit Tests to /workspace/tdd_rb/coverage. 33 / 33 LOC (100.0%) covered.
Started with run options --guard --seed 19583

  32/32: [=================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00688s
32 tests, 35 assertions, 0 failures, 0 errors, 0 skips

08:59:28 - INFO - Inspecting Ruby code style of all files
 7/7 files |====================================== 100 =======================================>| Time: 00:00:00

7 files inspected, no offenses detected
```

テストが壊れていないことを確認したらコミットします。

``` bash
$ git add .
$ git commit -m 'refactor: メソッドの移動'
```

![diag-c24dbdafdc4766204e9b3fba9938dbba.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20035/f90701c2-c6d1-2ee1-b290-531bef33f282.png)

### 値オブジェクト

![diag-c24dbdafdc4766204e9b3fba9938dbba.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20035/f90701c2-c6d1-2ee1-b290-531bef33f282.png)

#### オブジェクトによるプリミティブの置き換え

`FizzBuzz` クラスを **インスタンス化** するには以下のように書きます。

``` ruby
fizz_buzz = FizzBuzz.new(1)
```

> クラスとして定義された情報を元に具体的な値を伴ったオブジェクトを作成することをインスタンス化と呼び、生成されたオブジェクトのことをインスタンスと呼びます。
> 
> —  かんたんRuby 

**コンストラクタ** の **引数** に渡される `1` は何を表しているのでしょうか？もちろんタイプですが初めてこのコードを見る人にはわからないでしょう。このような整数、浮動小数点、文字列などの基本データ（プリミティブ）型の使い方からは **基本データ型への執着**の臭いがします。 **オブジェクトによるプリミティブの置き換え** を実施してコードの意図を明確にしましょう。

> 基本データ型への執着
> 
> オブジェクト指向のメリットとして、基本データ型とそれより大きなクラスとの境界を取り除くということがあります。プログラミング言語の組み込み（built-in）型と区別できないような小さなクラスを自分で定義することが容易です。
> 
> —  新装版 リファクタリング 

> 基本データ型への執着
> 
> 興味深いことに、多くのプログラマは、対象としているドメインに役立つ、貨幣、座標、範囲などの基本的な型を導入するのを嫌がる傾向があります。
> 
> —  リファクタリング(第2版) 

> オブジェクトによるデータ値の置き換え
> 
> 追加のデータや振る舞いが必要なデータ項目がある。
> 
> そのデータ項目をオブジェクトに変える。
> 
> —  新装版 リファクタリング 

> オブジェクトによるプリミティブの置き換え
> 
> 旧：オブジェクトによるデータ値の置き換え
> 
> 旧：クラスによるタイプコードの置き換え
> 
> —  リファクタリング(第2版) 

``` ruby
class FizzBuzz
  MAX_NUMBER = 100
  attr_reader :list
  attr_reader :type

  def initialize(type)
    @type = FizzBuzzType.create(type)
  end
...
```

``` ruby
class FizzBuzz
  MAX_NUMBER = 100
  attr_reader :list
  attr_reader :type

  def initialize(type)
    @type = type
  end
...
```

**コンストラクタ** で引き渡されるタイプは整数ではなくタイプクラスの **インスタンス** に変更します。

``` bash
...

ERROR["test_1を渡したら文字列1を返す", #<Minitest::Reporters::Suite:0x00005654f32602c0 @name="数を文字列にして返す::タイプ3の場合::その他の場合">, 0.00241121300496161]
 test_1を渡したら文字列1を返す#数を文字列にして返す::タイプ3の場合::その他の場合 (0.00s)
Minitest::UnexpectedError:         NoMethodError: undefined method `generate' for 3:Integer
            /workspace/tdd_rb/lib/fizz_buzz.rb:12:in `generate'
            /workspace/tdd_rb/test/fizz_buzz_test.rb:125:in `test_1を渡したら文字列1を返す'
...
```

テストが失敗しました。 **コンストラクタ** の引数を整数からタイプクラスの **インスタンス** に変更します。

``` ruby
...
class FizzBuzzTest < Minitest::Test
  describe '数を文字列にして返す' do
    describe 'タイプ1の場合' do
      def setup
        @fizzbuzz = FizzBuzz.new(1)
      end
...
      describe '1から100までのFizzBuzzの配列を返す' do
        def setup
          fizzbuzz = FizzBuzz.new(1)
          fizzbuzz.generate_list
          @result = fizzbuzz.list
        end
...
    describe 'タイプ2の場合' do
      def setup
        @fizzbuzz = FizzBuzz.new(2)
      end
...
    describe 'タイプ3の場合' do
      def setup
        @fizzbuzz = FizzBuzz.new(3)
      end
...
    describe 'それ以外のタイプの場合' do
      def test_例外を返す
        e = assert_raises RuntimeError do
          FizzBuzz.new(4)
        end

        assert_equal '該当するタイプは存在しません', e.message
      end
    end
  end
```

ここで注意するのは `それ以外のタイプの場合` ですが例外を投げなくなります。静的に型付けされた言語なら型チェックエラーになるのですがRubyは動的に型付けされる言語のため `FizzBuzz#generate` メソッド実行までエラーになりません。そこで例外を投げる `FizzBuzzType#create` メソッドに変更しておきます。

``` ruby
class FizzBuzzTest < Minitest::Test
  describe '数を文字列にして返す' do
    describe 'タイプ1の場合' do
      def setup
        @fizzbuzz = FizzBuzz.new(FizzBuzzType01.new)
      end
...
      describe '1から100までのFizzBuzzの配列を返す' do
        def setup
          fizzbuzz = FizzBuzz.new(FizzBuzzType01.new)
          fizzbuzz.generate_list
          @result = fizzbuzz.list
        end
...
    describe 'タイプ2の場合' do
      def setup
        @fizzbuzz = FizzBuzz.new(FizzBuzzType02.new)
      end
...
    describe 'タイプ3の場合' do
      def setup
        @fizzbuzz = FizzBuzz.new(FizzBuzzType03.new)
      end
...
    describe 'それ以外のタイプの場合' do
      def test_例外を返す
        e = assert_raises RuntimeError do
          FizzBuzzType.create(4)
        end

        assert_equal '該当するタイプは存在しません', e.message
      end
    end
  end
```

それ以外のタイプの場合は **ファクトリメソッド** 経由でないと **例外** を出さなくなるので注意してください。

``` bash
09:09:40 - INFO - Running: all tests
Coverage report generated for MiniTest, Unit Tests to /workspace/tdd_rb/coverage. 30 / 33 LOC (90.91%) covered.
Started with run options --guard --seed 17452

  32/32: [=================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00687s
32 tests, 35 assertions, 0 failures, 0 errors, 0 skips
```

初めてコードを見る人でもテストコードを見ればコードの意図が読み取れるようになりましたのでコミットします。

``` bash
$ git add .
$ git commit -m 'refactor: オブジェクトによるプリミティブの置き換え'
```

![diag-c24dbdafdc4766204e9b3fba9938dbba.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20035/f90701c2-c6d1-2ee1-b290-531bef33f282.png)

#### マジックナンバーの置き換え

まだプリミティグ型を使っている部分があります。ここは **マジックナンバーの置き換え** を実施して可読性を上げておきましょう。

``` ruby
...
class FizzBuzzType
  def self.create(type)
    case type
    when 1
      FizzBuzzType01.new
    when 2
      FizzBuzzType02.new
    when 3
      FizzBuzzType03.new
    else
      raise '該当するタイプは存在しません'
    end
 end
...
```

``` ruby
...
class FizzBuzzType
  TYPE_01 = 1
  TYPE_02 = 2
  TYPE_03 = 3

  def self.create(type)
    case type
    when FizzBuzzType::TYPE_01
      FizzBuzzType01.new
    when FizzBuzzType::TYPE_02
      FizzBuzzType02.new
    when FizzBuzzType::TYPE_03
      FizzBuzzType03.new
    else
      raise '該当するタイプは存在しません'
    end
  end
...
```

``` bash
09:18:51 - INFO - Running: all tests
Coverage report generated for MiniTest, Unit Tests to /workspace/tdd_rb/coverage. 33 / 36 LOC (91.67%) covered.
Started with run options --guard --seed 41124

  32/32: [=================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00909s
32 tests, 35 assertions, 0 failures, 0 errors, 0 skips
```

テストは壊れていないのでコミットします。

``` bash
$ git add .
$ git commit -m 'refactor: マジックナンバーの置き換え'
```

![diag-be7c345eb3b4de2cc3b4530da0d96f5d.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20035/055ea1c9-54db-543f-9e1d-b0fdfedc2fef.png)

#### オブジェクトによるプリミティブの置き換え

次に **基本データ型への執着** の臭いがする箇所として `FizzBuzz#generate` メソッドが返すFizzBuzzの値が文字型である点です。文字列の代わりに **値オブジェクト** `FizzBuzzValue` クラスを定義します。

> 値の種類ごとに専用の型を用意するとコードが安定し、コードの意図が明確になります。このように、値を扱うための専用クラスを作るやり方を値オブジェクト（ValueObject）と呼びます。
> 
> —  現場で役立つシステム設計の原則 

``` ruby
...
class FizzBuzzValue
  attr_reader :number, :value

  def initialize(number, value)
    @number = number
    @value = value
  end

  def to_s
    "#{@number}:#{@value}"
  end

  def ==(other)
    @number == other.number && @value == other.value
  end

  alias eql? ==
end
```

各タイプクラスの `generate` メソッドが文字列のプリミティブ型を返しているので **値オブジェクト** `FizzBuzzValue` を返すように変更します。

``` ruby
...
class FizzBuzzType01 < FizzBuzzType
  def generate(number)
    return 'FizzBuzz' if fizz?(number) && buzz?(number)
    return 'Fizz' if fizz?(number)
    return 'Buzz' if buzz?(number)

    number.to_s
  end
end

class FizzBuzzType02 < FizzBuzzType
  def generate(number)
    number.to_s
  end
end

class FizzBuzzType03 < FizzBuzzType
  def generate(number)
    return 'FizzBuzz' if fizz?(number) && buzz?(number)

    number.to_s
  end
end
...
```

``` ruby
...
class FizzBuzzType01 < FizzBuzzType
  def generate(number)
    return FizzBuzzValue.new(number, 'FizzBuzz') if fizz?(number) && buzz?(number)
    return FizzBuzzValue.new(number, 'Fizz') if fizz?(number)
    return FizzBuzzValue.new(number, 'Buzz') if buzz?(number)

    FizzBuzzValue.new(number, number.to_s)
  end
end

class FizzBuzzType02 < FizzBuzzType
  def generate(number)
    FizzBuzzValue.new(number, number.to_s)
  end
end

class FizzBuzzType03 < FizzBuzzType
  def generate(number)
    return FizzBuzzValue.new(number, 'FizzBuzz') if fizz?(number) && buzz?(number)

    FizzBuzzValue.new(number, number.to_s)
  end
end
...
```

``` bash
...
 FAIL["test_配列の2番目は文字列のFizzを返す", #<Minitest::Reporters::Suite:0x000055feccc65ab8 @name="数を文字列にして返す::タイプ1の場合::1から100までのFizzBuzzの配列を返す">, 0.012104410998290405]
 test_配列の2番目は文字列のFizzを返す#数を文字列にして返す::タイプ1の場合::1から100までのFizzBuzzの配列を返す (0.01s)
        --- expected
        +++ actual
        @@ -1 +1 @@
        -"Fizz"
        +#<FizzBuzzValue:0xXXXXXX @number=3, @value="Fizz">
        /workspace/tdd_rb/test/fizz_buzz_test.rb:57:in `test_配列の2番目は文字列のFizzを返す'
...
```

変更によりテストが失敗しました。エラー内容を見てみると文字列からオブジェクトを返しているためアサーションが失敗しているようです。ここは、**値オブジェクト** の **アクセッサメソッド** を経由して取得した値をアサーション対象に変更しましょう。

``` ruby
...
class FizzBuzzTest < Minitest::Test
  describe '数を文字列にして返す' do
    describe 'タイプ1の場合' do
      def setup
        @fizzbuzz = FizzBuzz.new(FizzBuzzType01.new)
      end

      describe '三の倍数の場合' do
        def test_3を渡したら文字列Fizzを返す
          assert_equal 'Fizz', @fizzbuzz.generate(3).value
        end
      end

      describe '五の倍数の場合' do
        def test_5を渡したら文字列Buzzを返す
          assert_equal 'Buzz', @fizzbuzz.generate(5).value
        end
      end

      describe '三と五の倍数の場合' do
        def test_15を渡したら文字列FizzBuzzを返す
          assert_equal 'FizzBuzz', @fizzbuzz.generate(15).value
        end
      end

      describe 'その他の場合' do
        def test_1を渡したら文字列1を返す
          assert_equal '1', @fizzbuzz.generate(1).value
        end
      end

      describe '1から100までのFizzBuzzの配列を返す' do
        def setup
          fizzbuzz = FizzBuzz.new(FizzBuzzType01.new)
          fizzbuzz.generate_list
          @result = fizzbuzz.list
        end

        def test_配列の初めは文字列の1を返す
          assert_equal '1', @result.first.value
        end

        def test_配列の最後は文字列のBuzzを返す
          assert_equal 'Buzz', @result.last.value
        end

        def test_配列の2番目は文字列のFizzを返す
          assert_equal 'Fizz', @result[2].value
        end

        def test_配列の4番目は文字列のBuzzを返す
          assert_equal 'Buzz', @result[4].value
        end

        def test_配列の14番目は文字列のFizzBuzzを返す
          assert_equal 'FizzBuzz', @result[14].value
        end
      end
    end

    describe 'タイプ2の場合' do
      def setup
        @fizzbuzz = FizzBuzz.new(FizzBuzzType02.new)
      end

      describe '三の倍数の場合' do
        def test_3を渡したら文字列3を返す
          assert_equal '3', @fizzbuzz.generate(3).value
        end
      end

      describe '五の倍数の場合' do
        def test_5を渡したら文字列5を返す
          assert_equal '5', @fizzbuzz.generate(5).value
        end
      end

      describe '三と五の倍数の場合' do
        def test_15を渡したら文字列15を返す
          assert_equal '15', @fizzbuzz.generate(15).value
        end
      end

      describe 'その他の場合' do
        def test_1を渡したら文字列1を返す
          assert_equal '1', @fizzbuzz.generate(1).value
        end
      end
    end

    describe 'タイプ3の場合' do
      def setup
        @fizzbuzz = FizzBuzz.new(FizzBuzzType03.new)
      end

      describe '三の倍数の場合' do
        def test_3を渡したら文字列3を返す
          assert_equal '3', @fizzbuzz.generate(3).value
        end
      end

      describe '五の倍数の場合' do
        def test_5を渡したら文字列5を返す
          assert_equal '5', @fizzbuzz.generate(5).value
        end
      end

      describe '三と五の倍数の場合' do
        def test_15を渡したら文字列FizzBuzzを返す
          assert_equal 'FizzBuzz', @fizzbuzz.generate(15).value
        end
      end

      describe 'その他の場合' do
        def test_1を渡したら文字列1を返す
          assert_equal '1', @fizzbuzz.generate(1).value
        end
      end
    end

    describe 'それ以外のタイプの場合' do
      def test_例外を返す
        e = assert_raises RuntimeError do
          FizzBuzzType.create(4)
        end

        assert_equal '該当するタイプは存在しません', e.message
      end
    end
  end
...
```

``` bash
08:49:28 - INFO - Running: all tests
Coverage report generated for MiniTest, Unit Tests to /workspace/tdd_rb/coverage. 41 / 46 LOC (89.13%) covered.
Started with run options --guard --seed 25972

  32/32: [==================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00619s
32 tests, 35 assertions, 0 failures, 0 errors, 0 skips

08:49:29 - INFO - Inspecting Ruby code style of all files
 7/7 files |======================================= 100 =======================================>| Time: 00:00:00

7 files inspected, no offenses detected
08:49:30 - INFO - Inspecting Ruby code style: coverage/assets/0.10.2/colorbox/loading_background.png coverage/assets/0.10.2/colorbox/controls.png coverage/assets/0.10.2/colorbox/loading.gif coverage/assets/0.10.2/colorbox/border.png
 0/0 files |======================================= 100 =======================================>| Time: 00:00:00

0 files inspected, no offenses detected
```

テストコードをそれほど変更することなく **値オブジェクト** を返すリファクタリングが出来ました。コミットしておきましょう。

``` bash
$ git add .
$ git commit -m 'refactor: オブジェクトによるプリミティブの置き換え'
```

![diag-719d1727313ce1fe35a0a3eaeca9a624.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20035/230b50d5-9f01-5e41-18fc-998be9c52c46.png)

#### 学習用テスト

**値オブジェクト** の理解を深めるために **学習用テスト** を追加します。

``` ruby
...
  describe 'FizzBuzzValue' do
    def setup
      @fizzbuzz = FizzBuzz.new(FizzBuzzType.create(FizzBuzzType::TYPE_01))
    end

    def test_同じで値である
      value1 = @fizzbuzz.generate(1)
      value2 = @fizzbuzz.generate(1)

      assert value1.eql?(value2)
    end

    def test_to_stringメソッド
      value = @fizzbuzz.generate(3)

      assert_equal '3:Fizz', value.to_s
    end
  end
end
```

``` bash
$ git add .
$ git commit -m 'test: 学習用テスト'
```

### ファーストクラスコレクション

![diag-719d1727313ce1fe35a0a3eaeca9a624.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20035/230b50d5-9f01-5e41-18fc-998be9c52c46.png)

#### コレクションのカプセル化

**値オブジェクト** を扱うFizzBuzzリストですが **コレクションのカプセル化** を適用して **ファーストクラスコレクション** オブジェクトを追加しましょう。

> コレクションのカプセル化
> 
> メソッドがコレクションを返している。
> 
> 読み取り専用のビューを返して、追加と削除のメソッドを提供する。
> 
> —  新装版 リファクタリング
> 

> このように、コレクション型のデータとロジックを特別扱いにして、コレクションを１つだけ持つ専用クラスを作るやり方をコレクションオブジェクトあるいはファーストクラスコレクションと呼びます。
> 
> —  現場で役立つシステム設計の原則 

まず、 **ファーストクラスコレクション** クラスを追加します。

``` ruby
...
class FizzBuzzList
  attr_reader :value

  def initialize(list)
    @value = list
  end

  def to_s
    @value.to_s
  end

  def add(value)
    FizzBuzzList.new(@value + value)
  end
end
```

FizzBuzz配列を **ファーストクラスコレクション** から取得するように変更します。

``` ruby
class FizzBuzz
  MAX_NUMBER = 100
  attr_reader :list
  attr_reader :type

  def initialize(type)
    @type = type
  end
...
  def generate_list
    # 1から最大値までのFizzBuzz配列を1発で作る
    @list = (1..MAX_NUMBER).map { |n| generate(n) }
  end
end
```

``` ruby
class FizzBuzz
  MAX_NUMBER = 100
  attr_reader :list
  attr_reader :type

  def initialize(type)
    @type = type
    @list = FizzBuzzList.new([])
  end

...
  def generate_list
    # 1から最大値までのFizzBuzz配列を1発で作る
    @list = @list.add((1..MAX_NUMBER).map { |n| @type.generate(n) })
  end
end
```

なんだか紛らわしい書き方になってしましました。配列を作るのに以前の配列を元に新しい配列を作るとか回りくどいことをしないで既存の配列を使い回せばいいじゃんと思うかもしれませんが **変更可能なデータ** はバグの原因となる傾向があります。変更可能な **ミュータブル** な変数ではなく 永続的に変更されない **イミュータブル** な変数を使うように心がけましょう。

> 変更可能なデータ
> 
> データの変更はしばし予期せぬ結果結果や、厄介なバグを引き起こします。他で違う値を期待していることに気づかないままに、ソフトウェアのある箇所で値を変更してしまえば、それだけで動かなくなってしまいます。これは値が変わる条件がまれにしかない場合、特に見つけにくいバグとなります。そのため、ソフトウェア開発の一つの潮流である関数型プログラミングは、データは不変であるべきで、更新時は常に元にデータ構造のコピーを返すようにし、元データには手を触れないという思想に基づいています。
> 
> —  リファクタリング(第2版) 

> 値オブジェクトと同じようにコレクションオブジェクトも、できるだけ「不変」スタイルで設計します。そのほうがプログラムが安定します。
> 
> —  現場で役立つシステム設計の原則 

``` bash
...
ERROR["test_配列の14番目は文字列のFizzBuzzを返す", #<Minitest::Reporters::Suite:0x00005561331b7940 @name="FizzBuzz::数を文字列にして返す::タイプ1の場合::1から100までのFizzBuzzの配列を返す">, 0.011710233025951311]
 test_配列の14番目は文字列のFizzBuzzを返す#FizzBuzz::数を文字列にして返す::タイプ1の場合::1から100までのFizzBuzzの配列を返す (0.01s)
Minitest::UnexpectedError:         NoMethodError: undefined method `[]' for #<FizzBuzzList:0x0000556133198ba8 @value=[]>
            /workspace/tdd_rb/test/fizz_buzz_test.rb:66:in `test_配列の14番目は文字列のFizzBuzzを返す'
...
```

**ファーストクラスコレクション** 経由で取得するようになったので **アクセッサメソッド** を変更する必要があります。

``` ruby
class FizzBuzz
  MAX_NUMBER = 100
  attr_reader :list
  attr_reader :type

  def initialize(type)
    @type = type
  end
...
```

``` ruby
class FizzBuzz
  MAX_NUMBER = 100
  attr_reader :list
  attr_reader :type

  def initialize(type)
    @type = type
    @list = FizzBuzzList.new([])
  end
...
```

``` ruby
class FizzBuzz
  MAX_NUMBER = 100
  attr_reader :type

  def list
    @list.value
  end

  def initialize(type)
    @type = type
    @list = FizzBuzzList.new([])
  end
....
```

``` bash
09:12:46 - INFO - Running: all tests
Coverage report generated for MiniTest, Unit Tests to /workspace/tdd_rb/coverage. 53 / 56 LOC (94.64%) covered.
Started with run options --guard --seed 61051

  34/34: [==================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.01285s
34 tests, 37 assertions, 0 failures, 0 errors, 0 skips

09:12:47 - INFO - Inspecting Ruby code style of all files
 7/7 files |======================================= 100 =======================================>| Time: 00:00:00

7 files inspected, no offenses detected
09:12:48 - INFO - Inspecting Ruby code style: coverage/assets/0.10.2/colorbox/loading_background.png coverage/assets/0.10.2/colorbox/controls.png coverage/assets/0.10.2/colorbox/loading.gif coverage/assets/0.10.2/colorbox/border.png
 0/0 files |======================================= 100 =======================================>| Time: 00:00:00

0 files inspected, no offenses detected
```

テストが直ったのでコミットしておきます。

``` bash
$ git add .
$ git commit -m 'refactor: コレクションのカプセル化'
```

![diag-f753577a92fab80607a2e63cf53e8389.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20035/9c7d1661-11c3-f495-e41b-b3ed7f8eb940.png)

#### 学習用テスト

**ファーストクラスコレクション** を理解するため **学習用テスト** を追加しておきましょう。

``` ruby
...
  describe 'FizzBuzzValueList' do
    def setup
      @fizzbuzz = FizzBuzz.new(FizzBuzzType.create(FizzBuzzType::TYPE_01))
    end

    def test_新しいインスタンスが作られる
      list1 = @fizzbuzz.generate_list
      list2 = list1.add(list1.value)

      assert_equal 100, list1.value.count
      assert_equal 200, list2.value.count
    end
  end
end
```

``` bash
$ git add .
$ git commit -m 'refactor: 学習用テスト'
```

### オブジェクト指向設計

![diag-f753577a92fab80607a2e63cf53e8389.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20035/9c7d1661-11c3-f495-e41b-b3ed7f8eb940.png)

**値オブジェクト** 及び **ファーストクラスコレクション** の適用で **基本データ型への執着** の臭いはなくなりました。今度は設計の観点から全体を眺めてみましょう。ここで気になるのが `FizzBuzz` クラスです。このクラスは他のクラスと比べてやることが多いようです。このようなクラスは **単一責任の原則** に違反している可能性があります。そこで **デザインパターン** の１つである **Commandパターン** を使ったリファクタリングである **メソッドオブジェクトによるメソッドの置き換え** 適用してみようと思います。

> SRP:
> 単一責任の原則
> 
> かつて単一責任の原則(SRP)は、以下のように語られてきた。
> 
>     モジュールを変更する理由はたったひとつだけであるべきである
> 
> ソフトウェアシステムに手を加えるのは、ユーザーやステークホルダーを満足させるためだ。この「ユーザーやステークホルダー」こそが、単一責任の原則（SRP）を指す「変更する理由」である。つまり、この原則は以下のように言い換えられる。
> 
>     モジュールはたったひとりのユーザーやステークホルダーに対して責任を負うべきである。
> 
> 残念ながら「たったひとりのユーザーやステークホルダー」という表現は適切ではない。複数のユーザーやステークホルダーがシステムを同じように変更したいと考えることもある。ここでは、変更を望む人たちをひとまとめにしたグループとして扱いたい。このグループのことをアクターと呼ぶことにしよう。
> これを踏まえると、最終的な単一責任の原則（SRP）は以下のようになる。
> 
>     モジュールはたったひとつのアクターに対して責任を負うべきである。
> 
> さて、ここでいう「モジュール」とは何のことだろう？端的に言えば、モジュールとはソースファイルのことである。たいていの場合は、この定義で問題ないだろう。だが、ソースファイル以外のところにコードを格納する言語や開発環境も存在する。そのような場合の「モジュール」は、いくつかの関数やデータをまとめた凝集性のあるものだと考えよう。
> 
> 「凝集性のある」という言葉が単一責任の原則（SRP）を匂わせる。凝集性が、ひとつのアクターに対する責務を負うコードをまとめるフォースとなる。
> 
> —  Clean Architecture 達人に学ぶソフトウェアの構造と設計
> 

> Commandパターン
> 
> 処理の呼び出しが、シンプルなメソッド呼び出しよりも複雑になってきたときはどうすればよいだろうか---処理のためのオブジェクトを作成し、それを起動するようにしよう。
> 
> —  テスト駆動開発
> 

> メソッドオブジェクトによるメソッドの置き換え
> 
> 長いメソッドで、「メソッドの抽出」を適用できないようなローカル変数の使い方をしている。
> 
> メソッド自身をオブジェクトとし、すべてのローカル変数をそのオブジェクトのフィールドとする。そうすれば、そのメソッドを同じオブジェクト中のメソッド群に分解できる。
> 
> —  新装版 リファクタリング 

#### メソッドオブジェクトによるメソッドの置き換え

まず、**値オブジェクト** の `FizzBuzzValue` を返す責務だけを持った **メソッドオブジェクト** を抽出します。Rubyのような動的言語では必要が無いのですが **Commandパターン** の説明のため **インターフェイス** にあたるスーパークラスを継承した **メソッドオブジェクト** を定義します。

``` ruby
...
class FizzBuzzCommand
  def execute; end
end

class FizzBuzzValueCommand < FizzBuzzCommand
  def initialize(type)
    @type = type
  end

  def execute(number)
    @type.generate(number).value
  end
end
```

テストコードを `FizzBuzzValueCommand` を呼び出すように変更します。

``` ruby
...
class FizzBuzzTest < Minitest::Test
  describe '数を文字列にして返す' do
    describe 'タイプ1の場合' do
      def setup
        @fizzbuzz = FizzBuzzValueCommand.new(FizzBuzzType01.new)
      end

      describe '三の倍数の場合' do
        def test_3を渡したら文字列Fizzを返す
          assert_equal 'Fizz', @fizzbuzz.execute(3)
        end
      end

      describe '五の倍数の場合' do
        def test_5を渡したら文字列Buzzを返す
          assert_equal 'Buzz', @fizzbuzz.execute(5)
        end
      end

      describe '三と五の倍数の場合' do
        def test_15を渡したら文字列FizzBuzzを返す
          assert_equal 'FizzBuzz', @fizzbuzz.execute(15)
        end
      end

      describe 'その他の場合' do
        def test_1を渡したら文字列1を返す
          assert_equal '1', @fizzbuzz.execute(1)
        end
      end

      describe '1から100までのFizzBuzzの配列を返す' do
        def setup
          fizzbuzz = FizzBuzz.new(FizzBuzzType01.new)
          fizzbuzz.generate_list
          @result = fizzbuzz.list
        end

        def test_配列の初めは文字列の1を返す
          assert_equal '1', @result.first.value
        end

        def test_配列の最後は文字列のBuzzを返す
          assert_equal 'Buzz', @result.last.value
        end

        def test_配列の2番目は文字列のFizzを返す
          assert_equal 'Fizz', @result[2].value
        end

        def test_配列の4番目は文字列のBuzzを返す
          assert_equal 'Buzz', @result[4].value
        end

        def test_配列の14番目は文字列のFizzBuzzを返す
          assert_equal 'FizzBuzz', @result[14].value
        end
      end
    end

    describe 'タイプ2の場合' do
      def setup
        @fizzbuzz = FizzBuzzValueCommand.new(FizzBuzzType02.new)
      end

      describe '三の倍数の場合' do
        def test_3を渡したら文字列3を返す
          assert_equal '3', @fizzbuzz.execute(3)
        end
      end

      describe '五の倍数の場合' do
        def test_5を渡したら文字列5を返す
          assert_equal '5', @fizzbuzz.execute(5)
        end
      end

      describe '三と五の倍数の場合' do
        def test_15を渡したら文字列15を返す
          assert_equal '15', @fizzbuzz.execute(15)
        end
      end

      describe 'その他の場合' do
        def test_1を渡したら文字列1を返す
          assert_equal '1', @fizzbuzz.execute(1)
        end
      end
    end

    describe 'タイプ3の場合' do
      def setup
        @fizzbuzz = FizzBuzzValueCommand.new(FizzBuzzType03.new)
      end

      describe '三の倍数の場合' do
        def test_3を渡したら文字列3を返す
          assert_equal '3', @fizzbuzz.execute(3)
        end
      end

      describe '五の倍数の場合' do
        def test_5を渡したら文字列5を返す
          assert_equal '5', @fizzbuzz.execute(5)
        end
      end

      describe '三と五の倍数の場合' do
        def test_15を渡したら文字列FizzBuzzを返す
          assert_equal 'FizzBuzz', @fizzbuzz.execute(15)
        end
      end

      describe 'その他の場合' do
        def test_1を渡したら文字列1を返す
          assert_equal '1', @fizzbuzz.execute(1)
        end
      end
    end

    describe 'それ以外のタイプの場合' do
      def test_例外を返す
        e = assert_raises RuntimeError do
          FizzBuzzType.create(4)
        end

        assert_equal '該当するタイプは存在しません', e.message
      end
    end
  end
...
```

``` bash
...
09:56:19 - INFO - Running: all tests
Coverage report generated for MiniTest, Unit Tests to /workspace/tdd_rb/coverage. 60 / 63 LOC (95.24%) covered.
Started with run options --guard --seed 27353

  35/35: [==================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00692s
35 tests, 39 assertions, 0 failures, 0 errors, 0 skips

09:56:20 - INFO - Inspecting Ruby code style of all files
 7/7 files |======================================= 100 =======================================>| Time: 00:00:00

7 files inspected, no offenses detected
09:56:21 - INFO - Inspecting Ruby code style: coverage/assets/0.10.2/colorbox/loading_background.png coverage/assets/0.10.2/colorbox/controls.png coverage/assets/0.10.2/colorbox/loading.gif coverage/assets/0.10.2/colorbox/border.png
 0/0 files |======================================= 100 =======================================>| Time: 00:00:00
 ...
```

`FizzBuzzValueCommand` の抽出ができたのでコミットしておきます。

``` bash
$ git add .
$ git commit -m 'refactor: メソッドオブジェクトによるメソッドの置き換え'
```

![diag-ee5628564b01a6264bda537c84b0458b.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20035/29eee45e-037a-04be-ff2f-e2696e1ab4d3.png)

#### メソッドオブジェクトによるメソッドの置き換え

続いて、**ファーストクラスコレクション** を扱う `FizzBuzzList` を返す責務だけを持った **メソッドオブジェクト** を抽出します。

``` ruby
...
class FizzBuzzListCommand < FizzBuzzCommand
  def initialize(type)
    @type = type
  end

  def execute(number)
    FizzBuzzList.new((1..number).map { |i| @type.generate(i) }).value
  end
end
```

テストコードを **FizzBuzzListCommand** 経由から実行するように変更します

``` ruby
...
        describe '1から100までのFizzBuzzの配列を返す' do
          def setup
            fizzbuzz = FizzBuzz.new(FizzBuzzType01.new)
            fizzbuzz.generate_list
            @result = fizzbuzz.list
          end
...
```

``` ruby
...
      describe '1から100までのFizzBuzzの配列を返す' do
        def setup
          fizzbuzz = FizzBuzzListCommand.new(FizzBuzzType01.new)
          @result = fizzbuzz.execute(100)
        end
...
```

``` bash
01:27:54 - INFO - Running: all tests
Coverage report generated for MiniTest, Unit Tests to /workspace/tdd_rb/coverage. 61 / 66 LOC (92.42%) covered.
Started with run options --guard --seed 62253

  35/35: [=================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00652s
35 tests, 39 assertions, 0 failures, 0 errors, 0 skips
```

テストが通ったのでコミットします。

``` bash
$ git add .
$ git commit -m 'refactor: メソッドオブジェクトによるメソッドの置き換え'
```

![diag-4a094b5dcd7f922c01a8ad5b39f6839d.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20035/d94351bd-23d9-6081-baa4-8f71ab75d3de.png)

#### デッドコードの削除

`FizzBuzz` クラスの責務は各 **メソッドオブジェクト** が実行するようになったので削除しましょう。

``` ruby
class FizzBuzz
  MAX_NUMBER = 100

  def initialize(type)
    @type = type
    @list = FizzBuzzList.new([])
  end

  def list
    @list.value
  end

  def generate(number)
    @type.generate(number)
  end

  def generate_list
    # 1から最大値までのFizzBuzz配列を1発で作る
    @list = @list.add((1..MAX_NUMBER).map { |n| @type.generate(n) })
  end
end

class FizzBuzzType
...
```

``` ruby
class FizzBuzzType
...
```

``` bash
...
ERROR["test_同じで値である", #<Minitest::Reporters::Suite:0x0000562fd34f7848 @name="FizzBuzzValue">, 0.008059715997660533]
 test_同じで値である#FizzBuzzValue (0.01s)
Minitest::UnexpectedError:         NameError: uninitialized constant FizzBuzzTest::FizzBuzz
            /workspace/tdd_rb/test/fizz_buzz_test.rb:225:in `setup'

ERROR["test_to_stringメソッド", #<Minitest::Reporters::Suite:0x0000562fd37694a0 @name="FizzBuzzValue">, 0.01728590900893323]
 test_to_stringメソッド#FizzBuzzValue (0.02s)
Minitest::UnexpectedError:         NameError: uninitialized constant FizzBuzzTest::FizzBuzz
            /workspace/tdd_rb/test/fizz_buzz_test.rb:225:in `setup'

ERROR["test_新しいインスタンスが作られる", #<Minitest::Reporters::Suite:0x0000562fd39be070 @name="FizzBuzzValueList">, 0.028008958004647866]
 test_新しいインスタンスが作られる#FizzBuzzValueList (0.03s)
Minitest::UnexpectedError:         NameError: uninitialized constant FizzBuzzTest::FizzBuzz
            /workspace/tdd_rb/test/fizz_buzz_test.rb:244:in `setup'

========================================|

Finished in 0.03539s
35 tests, 35 assertions, 0 failures, 3 errors, 0 skips
...
```

テストが失敗しました。これは **学習用テスト** で `FizzBuzz` クラスを使っている箇所があるからですね。 **メソッドオブジェクト** 呼び出しに変更しておきましょう。

``` ruby
  describe 'FizzBuzzValue' do
    def setup
      @fizzbuzz = FizzBuzz.new(FizzBuzzType.create(FizzBuzzType::TYPE_01))
    end

    def test_同じで値である
      value1 = @fizzbuzz.generate(1)
      value2 = @fizzbuzz.generate(1)

      assert value1.eql?(value2)
    end

    def test_to_stringメソッド
      value = @fizzbuzz.generate(3)

      assert_equal '3:Fizz', value.to_s
    end
  end

  describe 'FizzBuzzValueList' do
    def setup
      @fizzbuzz = FizzBuzz.new(FizzBuzzType.create(FizzBuzzType::TYPE_01))
    end

    def test_新しいインスタンスが作られる
      list1 = @fizzbuzz.generate_list
      list2 = list1.add(list1.value)

      assert_equal 100, list1.value.count
      assert_equal 200, list2.value.count
    end
  end
end
```

``` ruby
...
  describe 'FizzBuzzValue' do
    def test_同じで値である
      value1 = FizzBuzzValue.new(1, '1')
      value2 = FizzBuzzValue.new(1, '1')

      assert value1.eql?(value2)
    end

    def test_to_stringメソッド
      value = FizzBuzzValue.new(3, 'Fizz')

      assert_equal '3:Fizz', value.to_s
    end
  end

  describe 'FizzBuzzValueList' do
    def test_新しいインスタンスが作られる
      command = FizzBuzzListCommand.new(FizzBuzzType.create(FizzBuzzType::TYPE_01))
      array = command.execute(100)
      list1 = FizzBuzzList.new(array)
      list2 = list1.add(array)

      assert_equal 100, list1.value.count
      assert_equal 200, list2.value.count
    end
  end
end
```

``` bash
...
01:35:22 - INFO - Running: all tests
Coverage report generated for MiniTest, Unit Tests to /workspace/tdd_rb/coverage. 50 / 56 LOC (89.29%) covered.
Started with run options --guard --seed 10411

  35/35: [=================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00704s
35 tests, 39 assertions, 0 failures, 0 errors, 0 skips
...
```

不要なコードを残しておくとメンテナンスの時に削除していいのかわからなくなり可読性を落とし原因となります。削除できる時に削除しておきましょう。後で必要になったとしてもバージョン管理システムを使えば問題ありません。ということでコミットします。

> デッドコードの削除
> 
> コードが使用されなくなったら削除すべきです。そのコードが将来必要になるかもしれないなどという心配はしません。必要になったらいつでも、バージョン管理システムから再び掘り起こせるからです。
> 
> （中略）
> 
> デッドコードのコメントアウトは、かつては一般的な習慣でした。それは、バージョン管理システムが広く使用される以前の時代や、使いづらかった時代には有用でした。現在では、とても小さなコードベースでもバージョン管理システムに置けるため、もはや必要のない習慣です。
> 
> —  リファクタリング(第2版) 

``` bash
$ git add .
$ git commit -m 'refactor: デッドコードの削除'
```

![diag-55cb49d0a54190528d027b3768e4aa29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20035/9a745e4a-4f98-9723-1b94-e116805604fd.png)

#### デザインパターン

**メソッドオブジェクトによるメソッドの置き換え** リファクタリングの結果として **Commandパターン** という **デザインパターン** を適用しました。実はこれまでにも **オブジェクトによるプリミティブの置き換え** では **Value Objectパターン** を **ポリモーフィズムによる条件記述の置き換え** では **Factory Methodパターン** をそして、 **委譲の隠蔽** の実施による **State/Strategyによるタイプコードの置き換え** では **Strategyパターン** を適用しています。

[Command パターン](https://ja.wikipedia.org/wiki/Command_%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3)

![diag-3f9e53f62bd2f1b3bfe0f476521170ca.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20035/742f33e5-d884-2a28-104a-6d311bccd060.png)


> Value Objectパターン
> 
> 広く共有されるものの、同一インスタンスであることはさほど重要でないオブジェクトを設計するにはどうしたらよいだろうか----オブジェクト作成時に状態を設定したら、その後決して変えないようにする。オブジェクトへの操作は必ず新しいオブジェクトを返すようにしよう。
> 
> —  テスト駆動開発 

> Factory Methodパターン
> 
> オブジェクト作成に柔軟性をもたせたいときは、どうすればよいだろうか---単にコンストラクタで作るのではなく、メソッドを使ってオブジェクトを作成しよう。
> 
> —  テスト駆動開発 

[Strategy パターン](https://ja.wikipedia.org/wiki/Strategy_%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3)

![diag-4969f773bcc5408d3afec24f14c006d3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20035/b4fad4c5-8268-6ed7-0338-7a6ff98ea6ce.png)

作成したコードはパターンと完全に一致しているわけではありませんし、Rubyのような動的言語ではもっと簡単な実現方法もありますがここでは先人の考えた設計パターンというものがありオブジェクト指向設計の [イデオム](https://ja.wikipedia.org/wiki/%E3%82%A4%E3%83%87%E3%82%A3%E3%82%AA%E3%83%A0) として使えること。そしてテスト駆動開発では一般的な設計アプローチとは異なる形で導かれているということくらいを頭に残しておけば結構です。どのパターンをいつ適用するかはリファクタリングを繰り返しているうちに思いつくようになってきます（多分）。

> ただ、書籍『デザインパターン』（通称Gof本）の大ヒットは、その反面、それらパターンを表現する方法の多様性を奪ってしまった。Gof本には、設計をフェーズとして扱うという暗黙の前提があるように見受けられる。つまり、リファクタリングを設計行為として捉えていない。TDDにおける設計は、デザインパターンを少しだけ違う側面から捉えなければならない。
> 
> —  テスト駆動開発 

あと、設計の観点から今回 **単一責任の原則** に従って `FizzBuzz` クラスを **メソッドオブジェクト** に分割して削除しました。

![diag-51044325ad9691ebfe6e60879f6c95e7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20035/eeb0db27-d842-1058-09b1-e35244464cf1.png)

もし、新しい処理を追加する必要が発生した場合はどうしましょうか？ `FizzBuzzCommand` インターフェイスを実装した **メソッドオブジェクト** を追加しましょう。

![diag-ea3196c5b1015bd1c10121bc92095fcb.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20035/da6aaae2-b8fb-a5ed-4441-289d52f0ec51.png)

もし、新しいタイプが必要になったらどうしましょうか？ `FizzBuzzType` クラスを継承した新しいタイプクラスを追加しましょう。

![diag-479f7874fa6e2b242ebd5a2f54730e37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20035/ab873b26-19f9-2f3a-b7d0-54c14842c341.png)

このように既存のコードを変更することなく振る舞いを変更できるので **オープン・クローズドの原則** を満たした設計といえます。

> OCP:オープン・クローズドの原則
> 
> 「オープン・クローズドの原則（OCP）」は、1988年にBertrand Maeerが提唱した以下のような原則だ。
> 
>     ソフトウェアの構成要素は拡張に対しては開いていて、修正に対しては閉じていなければならない。
>     　　　　　　　　　　　　『アジャイルソフトウェア開発の奥義　第2版』（SBクリエイティブ）より引用
> 
> 言い換えれば、ソフトウェアの振る舞いは、既存の成果物を変更せず拡張できるようにすべきである、ということだ。
> 
> —  Clean Architecture 達人に学ぶソフトウェアの構造と設計 

### 例外

![diag-55cb49d0a54190528d027b3768e4aa29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20035/9a745e4a-4f98-9723-1b94-e116805604fd.png)

ここまでは、正常系をリファクタリングして設計を改善してきました。しかし、アプリケーションは例外系も考慮する必要があります。続いて、**アサーションの導入** を適用した例外系のリファクタリングに取り組むとしましょう。

> アサーションの導入
> 
> 前提を明示するためのすぐれたテクニックとして、アサーションを記述する方法があります。
> 
> —  リファクタリング(第2版) 

#### アサーションの導入

まず、 **メソッドオブジェクト** の `FizzBuzzValueCommand` にマイナスの値が渡された場合の振る舞いをどうするか考えます。ここでは正の値のみ許可する振る舞いにしたいので以下のテストコードを追加します。

``` ruby
class FizzBuzzTest < Minitest::Test
...
  describe '例外ケース' do
    def test_値は正の値のみ許可する
      assert_raises Assertions::AssertionFailedError do
        FizzBuzzValueCommand.new(
          FizzBuzzType.create(FizzBuzzType::TYPE_01)
        ).execute(-1)
      end
    end
  end
end
```

``` bash
...
ERROR["test_値は正の値のみ許可する", #<Minitest::Reporters::Suite:0x00007fadf30c45d8 @name="例外ケース">, 0.006546000000525964]
 test_値は正の値のみ許可する#例外ケース (0.01s)
Minitest::UnexpectedError:         NameError: uninitialized constant FizzBuzzTest::Assertions
            /Users/k2works/Projects/sandbox/tdd_rb/test/fizz_buzz_test.rb:249:in `test_値は正の値のみ許可する'

  36/36: [=========================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.03159s
36 tests, 39 assertions, 0 failures, 1 errors, 0 skips
...
```

テストを通すためアサーションモジュールを追加します。Rubyでは **モジュール** を使います。

> モジュールはクラスと非常によく似ていますが、以下の二点が異なります。
> 
>   - モジュールはインスタンス化できない
> 
>   - 本章後半可能なのは include や extend が可能なのはモジュールだけ
> 
> それ以外のクラスメソッドや定数の定義などはクラスと同じように定義することができます。
> 
> —  かんたんRuby 

``` ruby
...
module Assertions
  class AssertionFailedError < StandardError; end

  def assert(&condition)
    raise AssertionFailedError, 'Assertion Failed' unless condition.call
  end
end

class FizzBuzzValue
...
```

アサーションモジュールを追加してエラーはなくなりましたがテストは失敗したままです。

``` bash
...
 FAIL["test_値は正の値のみ許可する", #<Minitest::Reporters::Suite:0x00007fdcfc0c2548 @name="例外ケース">, 0.005800000000817818]
 test_値は正の値のみ許可する#例外ケース (0.01s)
        Assertions::AssertionFailedError expected but nothing was raised.
        /Users/k2works/Projects/sandbox/tdd_rb/test/fizz_buzz_test.rb:249:in `test_値は正の値のみ許可する'

============================================================================================================|

Finished in 0.00621s
36 tests, 40 assertions, 1 failures, 0 errors, 0 skips
...
```

追加したモジュールを `FizzBuzzValue` クラスをに **Mix-in** します。そして、**コンストラクタ** 実行時に数値は0以上であるアサーションを追加します。

> Rubyでの継承は一種類、単一継承しか実行できませんが、複数のクラスを継承する多重継承の代わりにMix-inというメソッドの共有方法を提供します。
> 
> —  かんたんRuby 

``` ruby
class FizzBuzzValue
  attr_reader :number, :value

  def initialize(number, value)
    @number = number
    @value = value
  end
...
end
```

``` ruby
class FizzBuzzValue
  include Assertions
  attr_reader :number, :value

  def initialize(number, value)
    assert { number >= 0 }
    @number = number
    @value = value
  end
...
end
```

``` bash
...
Started with run options --seed 37354


Progress: |====================================================================================================|

Finished in 0.01433s
36 tests, 40 assertions, 0 failures, 0 errors, 0 skips
...
```

アサーションが機能するようになりました、コミットしておきます。

``` bash
$ git add .
$ git commit -m 'refactor: アサーションの導入'
```

![diag-31809a5e0bf909bd8ffd6bf80e82857a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20035/bbe4d4ea-87a5-ff7d-c7b5-07867aabff16.png)

次は、**メソッドオブジェクト** の `FizzBuzzListCommand` の実行時に100件以上指定された場合の振る舞いをどうするか考えます。ここでは100までを許可する振る舞いにします。

``` ruby
...
  describe '例外ケース' do
    def test_値は正の値のみ許可する
      assert_raises Assertions::AssertionFailedError do
        FizzBuzzValueCommand.new(
          FizzBuzzType.create(FizzBuzzType::TYPE_01)
        ).execute(-1)
      end
    end

    def test_100より多い数を許可しない
      assert_raises Assertions::AssertionFailedError do
        FizzBuzzListCommand.new(
          FizzBuzzType.create(FizzBuzzType::TYPE_01)
        ).execute(101)
      end
    end
  end
end
```

`FizzBuzzList` にアサーションモジュールを **Mix-in** します。**コンストラクタ** 実行時に配列のサイズは100までというアサーションを追加します。

``` ruby
...
class FizzBuzzList
  include Assertions
  attr_reader :value

  def initialize(list)
    assert { list.count <= 100 }
    @value = list
  end
...
```

``` bash
...
ERROR["test_新しいインスタンスが作られる", #<Minitest::Reporters::Suite:0x00005558ca6e8e80 @name="FizzBuzzValueList">, 0.010412617004476488]
 test_新しいインスタンスが作られる#FizzBuzzValueList (0.01s)
Minitest::UnexpectedError:         Assertions::AssertionFailedError: Assertion Failed
            /workspace/tdd_rb/lib/fizz_buzz.rb:58:in `assert'
            /workspace/tdd_rb/lib/fizz_buzz.rb:88:in `initialize'
            /workspace/tdd_rb/lib/fizz_buzz.rb:97:in `new'
            /workspace/tdd_rb/lib/fizz_buzz.rb:97:in `add'
            /workspace/tdd_rb/test/fizz_buzz_test.rb:259:in `test_新しいインスタンスが作られる'

====================================================================================================|

Finished in 0.01238s
36 tests, 38 assertions, 0 failures, 1 errors, 0 skips
...
```

追加したテストはパスするようになりましたが既存のテストコードでエラーが出るようになりました。該当するテストコードを見たところ100件より多い **学習用テスト** で **ファーストクラスコレクション** を作ろうとしたため `AssertionFailedError` を発生させたようです。テストコードを修正しておきましょう。

``` ruby
...
  describe 'FizzBuzzValueList' do
    def test_新しいインスタンスが作られる
      command = FizzBuzzListCommand.new(FizzBuzzType.create(FizzBuzzType::TYPE_01))
      array = command.execute(100)
      list1 = FizzBuzzList.new(array)
      list2 = list1.add(array)

      assert_equal 100, list1.value.count
      assert_equal 200, list2.value.count
    end
  end
...
```

最初は50件作るように変更します。

``` ruby
...
  describe 'FizzBuzzValueList' do
    def test_新しいインスタンスが作られる
      command = FizzBuzzListCommand.new(FizzBuzzType.create(FizzBuzzType::TYPE_01))
      array = command.execute(50)
      list1 = FizzBuzzList.new(array)
      list2 = list1.add(array)

      assert_equal 100, list1.value.count
      assert_equal 200, list2.value.count
    end
  end
...
```

アサーションエラーはなくなりましたが期待した値と違うと指摘されています。テストコードのアサーションを修正します。

``` bash
 FAIL["test_新しいインスタンスが作られる", #<Minitest::Reporters::Suite:0x0000556b5137c780 @name="FizzBuzzValueList">, 0.003735148988198489]
 test_新しいインスタンスが作られる#FizzBuzzValueList (0.00s)
        Expected: 100
          Actual: 50
        /workspace/tdd_rb/test/fizz_buzz_test.rb:261:in `test_新しいインスタンスが作られる'

  36/36: [=================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00837s
36 tests, 39 assertions, 1 failures, 0 errors, 0 skips
```

``` ruby
...
  describe 'FizzBuzzValueList' do
    def test_新しいインスタンスが作られる
      command = FizzBuzzListCommand.new(FizzBuzzType.create(FizzBuzzType::TYPE_01))
      array = command.execute(50)
      list1 = FizzBuzzList.new(array)
      list2 = list1.add(array)

      assert_equal 50, list1.value.count
      assert_equal 200, list2.value.count
    end
  end
...
```

２つ目のアサーションに引っかかってしまいました。こちらも修正します。

``` bash
 FAIL["test_新しいインスタンスが作られる", #<Minitest::Reporters::Suite:0x0000563a0c4fc2b0 @name="FizzBuzzValueList">, 0.005684088013367727]
 test_新しいインスタンスが作られる#FizzBuzzValueList (0.01s)
        Expected: 200
          Actual: 100
        /workspace/tdd_rb/test/fizz_buzz_test.rb:262:in `test_新しいインスタンスが作られる'

  36/36: [=================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00809s
36 tests, 40 assertions, 1 failures, 0 errors, 0 skips
```

``` ruby
...
  describe 'FizzBuzzValueList' do
    def test_新しいインスタンスが作られる
      command = FizzBuzzListCommand.new(FizzBuzzType.create(FizzBuzzType::TYPE_01))
      array = command.execute(50)
      list1 = FizzBuzzList.new(array)
      list2 = list1.add(array)

      assert_equal 50, list1.value.count
      assert_equal 100, list2.value.count
    end
  end
...
```

``` bash
...
01:58:57 - INFO - Running: all tests
Coverage report generated for MiniTest, Unit Tests to /workspace/tdd_rb/coverage. 61 / 64 LOC (95.31%) covered.
Started with run options --guard --seed 44956

  36/36: [=================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00717s
36 tests, 40 assertions, 0 failures, 0 errors, 0 skips
...
```

仕様変更による反映が出来たのでコミットしましょう。

``` bash
$ git add .
$ git commit -m 'refactor: アサーションの導入'
```

![diag-6390574a6b0b9b04721636e71b66aea3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20035/29ff3f05-d607-1549-19e8-dd10cd9d845a.png)

**アサーションの導入** とは別のアプローチとして **例外** を返す方法もあります。 **例外によるエラーコードの置き換え** を適用してアサーションモジュールを削除しましょう。

> 例外によるエラーコードの置き換え
> 
> エラーを示す特別なコードをメソッドがリターンしている。
> 
> 代わりに例外を発生させる。
> 
> —  新装版 リファクタリング 

#### 例外によるエラーコードの置き換え

アサーションモジュールを削除してアサーション部分を **例外** に変更します。

``` ruby
...
module Assertions
  class AssertionFailedError < StandardError; end

  def assert(&condition)
    raise AssertionFailedError, 'Assertion Failed' unless condition.call
  end
end

class FizzBuzzValue
  include Assertions
  attr_reader :number, :value

  def initialize(number, value)
    assert { number >= 0 }
    @number = number
    @value = value
  end
...
end

class FizzBuzzList
  include Assertions
  attr_reader :value

  def initialize(list)
    assert { list.count <= 100 }
    @value = list
  end
...
end
...
```

``` ruby
...
class FizzBuzzValue
  attr_reader :number, :value

  def initialize(number, value)
    raise '正の値のみ有効です' if number < 0

    @number = number
    @value = value
  end
...
end

class FizzBuzzList
  attr_reader :value

  def initialize(list)
    raise '上限は100件までです' if list.count > 100

    @value = list
  end
...
end
```

``` bash
...
ERROR["test_値は正の値のみ許可する", #<Minitest::Reporters::Suite:0x000055d30f0b8a50 @name="FizzBuzz::数を文字列にして返す::例外ケース">, 0.004186890990240499]
 test_値は正の値のみ許可する#FizzBuzz::数を文字列にして返す::例外ケース (0.00s)
Minitest::UnexpectedError:         NameError: uninitialized constant FizzBuzzTest::Assertions
            /workspace/tdd_rb/test/fizz_buzz_test.rb:143:in `test_値は正の値のみ許可する'

ERROR["test_100より多い数を許可しない", #<Minitest::Reporters::Suite:0x000055d30f114210 @name="FizzBuzz::数を文字列にして返す::例外ケース">, 0.008254560001660138]
 test_100より多い数を許可しない#FizzBuzz::数を文字列にして返す::例外ケース (0.01s)
Minitest::UnexpectedError:         NameError: uninitialized constant FizzBuzzTest::Assertions
            /workspace/tdd_rb/test/fizz_buzz_test.rb:151:in `test_100より多い数を許可しない'

  37/37: [=================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.01731s
37 tests, 39 assertions, 0 failures, 2 errors, 0 skips
...
```

アサーションモジュールを削除したのでエラーが発生しています。テストコードを修正しましょう。

``` ruby
...
  describe '例外ケース' do
    def test_値は正の値のみ許可する
      assert_raises Assertions::AssertionFailedError do
        FizzBuzzValueCommand.new(
          FizzBuzzType.create(FizzBuzzType::TYPE_01)
        ).execute(-1)
      end
    end

    def test_100より多い数を許可しない
      assert_raises Assertions::AssertionFailedError do
        FizzBuzzListCommand.new(
          FizzBuzzType.create(FizzBuzzType::TYPE_01)
        ).execute(101)
      end
    end
  end
end
```

``` ruby
...
  describe '例外ケース' do
    def test_値は正の値のみ許可する
      e = assert_raises RuntimeError do
        FizzBuzzValueCommand.new(
          FizzBuzzType.create(FizzBuzzType::TYPE_01)
        ).execute(-1)
      end

      assert_equal '正の値のみ有効です', e.message
    end

    def test_100より多い数を許可しない
      e = assert_raises RuntimeError do
        FizzBuzzListCommand.new(
          FizzBuzzType.create(FizzBuzzType::TYPE_01)
        ).execute(101)
      end

      assert_equal '上限は100件までです', e.message
    end
  end
end
```

``` bash
...
02:13:46 - INFO - Running: all tests
Coverage report generated for MiniTest, Unit Tests to /workspace/tdd_rb/coverage. 55 / 58 LOC (94.83%) covered.
Started with run options --guard --seed 55179

  37/37: [=================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00738s
37 tests, 43 assertions, 0 failures, 0 errors, 0 skips
...
```

再びテストが通るようになったのでコミットしておきます。

``` bash
$ git add .
$ git commit -m 'refactor:  例外によるエラーコードの置き換え'
```

![diag-cd1dbf997043a35c9bb55b407c0a2af9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20035/946fb41e-b83f-cae4-78d3-effdff059f93.png)

#### アルゴリズムの置き換え

``` bash
02:13:46 - INFO - Inspecting Ruby code style: test/fizz_buzz_test.rb lib/fizz_buzz.rb
lib/fizz_buzz.rb:58:26: C: Style/NumericPredicate: Use number.negative? instead of number < 0.
    raise '正の値のみ有効です' if number < 0
                         ^^^^^^^^^^
 2/2 files |====================================== 100 =======================================>| Time: 00:00:00

2 files inspected, 1 offense detected
```

テストは通りますが警告が表示されるようになりました。 `Style/NumericPredicate: Use number.negative? instead of number < 0.` とのことなので **アルゴリズムの置き換え** を適用しておきましょう。

> アルゴリズムの取り替え
> 
> アルゴリズムをよりわかりやすいものに置き換えたい
> 
> メソッドの本体を新たなアルゴリズムで置き換える。
> 
> —  新装版 リファクタリング 

``` ruby
...
class FizzBuzzValue
  attr_reader :number, :value

  def initialize(number, value)
    raise '正の値のみ有効です' if number < 0
...
```

``` ruby
...

class FizzBuzzValue
  attr_reader :number, :value

  def initialize(number, value)
    raise '正の値のみ有効です' if number.negative?
...
```

``` bash
02:18:31 - INFO - Inspecting Ruby code style: lib/fizz_buzz.rb
 1/1 file |======================================= 100 =======================================>| Time: 00:00:00

1 file inspected, no offenses detected
```

警告が消えたのでコミットします。

``` bash
$ git add .
$ git commit -m 'refactor: アルゴリズムの置き換え'
```

#### マジックナンバーの置き換え

件数に **リテラル** を使っています。ここは **マジックナンバーの置き換え** を適用するべきですね。

> シンボリック定数によるマジックナンバーの置き換え
> 
> 特別な意味を持った数字のリテラルがある。
> 
> 定数を作り、それにふさわしい名前をつけて、そのリテラルを置き換える。
> 
> —  新装版 リファクタリング 

``` ruby
...
class FizzBuzzList
  attr_reader :value

  def initialize(list)
    raise '上限は100件までです' if list.count > 100

    @value = list
  end
...
```

**式展開** を使ってメッセージ内容も定数から参照するようにしましょう。

> 式展開
> 
> 式展開とは、「\#{}」の書式で文字列中に何らかの変数や式を埋め込むことが可能な機能です。これは、ダブルクオートを使用した場合のみの機能です。
> 
> —  かんたんRuby 

``` ruby
class FizzBuzzList
  MAX_COUNT = 100
  attr_reader :value

  def initialize(list)
    raise "上限は#{MAX_COUNT}件までです" if list.count > MAX_COUNT

    @value = list
  end
...
```

テストは壊れていないようですが `MAX_COUNT` を変更したらテストが失敗するか確認しておきましょう。

``` ruby
class FizzBuzzList
  MAX_COUNT = 10
...
```

``` bash
...
ERROR["test_配列の14番目は文字列のFizzBuzzを返す", #<Minitest::Reporters::Suite:0x000055942ab5e230 @name="FizzBuzz::数を文字列にして返す::タイプ1の場合::1から100までのFizzBuzzの配列を返す">, 0.008073228993453085]
 test_配列の14番目は文字列のFizzBuzzを返す#FizzBuzz::数を文字列にして返す::タイプ1の場合::1から100までのFizzBuzzの配列を返す (0.01s)
Minitest::UnexpectedError:         RuntimeError: 上限は10件までです
            /workspace/tdd_rb/lib/fizz_buzz.rb:80:in `initialize'
            /workspace/tdd_rb/lib/fizz_buzz.rb:112:in `new'
            /workspace/tdd_rb/lib/fizz_buzz.rb:112:in `execute'
            /workspace/tdd_rb/test/fizz_buzz_test.rb:45:in `setup'
...
```

想定通りのエラーが発生したのでコードを元に戻してコミットしましょう。

``` ruby
class FizzBuzzList
  MAX_COUNT = 100
...
```

``` bash
...
Started with run options --seed 5525


Progress: |====================================================================================================|

Finished in 0.01262s
37 tests, 43 assertions, 0 failures, 0 errors, 0 skips
...
```

``` bash
$ git add .
$ git commit -m 'refactor: マジックナンバーの置き換え'
```

![diag-cd1dbf997043a35c9bb55b407c0a2af9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20035/946fb41e-b83f-cae4-78d3-effdff059f93.png)

#### 特殊ケースの導入

最後に **ポリモーフィズム** の応用としてタイプクラスが未定義の場合に **例外** ではなく未定義のタイプクラスを返す **特殊ケースの導入** を適用してみましょう。

> ヌルオブジェクトの導入
> 
> null値のチェックが繰り返し現れる。
> 
> そのnull値をヌルオブジェクトで置き換える。
> 
> —  新装版 リファクタリング
> 

> 特殊ケースの導入
> 
> 旧：ヌルオブジェクトの導入
> 
> 特殊ケースの処理を要する典型的な値がnullなので、このパターンをヌルオブジェクトパターンと呼ぶことがあります、しかし、通常の特殊ケースとアプローチは同じです。いわばヌルオブジェクトは「特殊ケース」の特殊ケースです。
> 
> —  リファクタリング(第2版) 

まず、それ以外のタイプの場合の振る舞いを変更します。

``` ruby
...
    describe 'それ以外のタイプの場合' do
      def test_例外を返す
        e = assert_raises RuntimeError do
          FizzBuzzType.create(4)
        end

        assert_equal '該当するタイプは存在しません', e.message
      end
    end
  end
...
```

``` ruby
...
   describe 'それ以外のタイプの場合' do
      def test_未定義のタイプを返す
        fizzbuzz = FizzBuzzType.create(4)

        assert_equal '未定義', fizzbuzz.to_s
      end
    end
  end
...
```

``` bash
...
ERROR["test_未定義のタイプを返す", #<Minitest::Reporters::Suite:0x00005593e21297d0 @name="数を文字列にして返す::それ以外のタイプの場合">, 0.0065623498521745205]
 test_未定義のタイプを返す#数を文字列にして返す::それ以外のタイプの場合 (0.01s)
Minitest::UnexpectedError:         RuntimeError: 該当するタイプは存在しません
            /workspace/tdd_rb/lib/fizz_buzz.rb:17:in `create'
            /workspace/tdd_rb/test/fizz_buzz_test.rb:131:in `test_未定義のタイプを返す'

  37/37: [==================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00780s
37 tests, 41 assertions, 0 failures, 1 errors, 0 skips
...
```

現時点では **例外** を投げるので未定義タイプ `FizzBuzzTypeNotDefined` を作成して **ファクトリメソッド** を変更します。

``` ruby
class FizzBuzzType
  TYPE_01 = 1
  TYPE_02 = 2
  TYPE_03 = 3

  def self.create(type)
    case type
    when FizzBuzzType::TYPE_01
      FizzBuzzType01.new
    when FizzBuzzType::TYPE_02
      FizzBuzzType02.new
    when FizzBuzzType::TYPE_03
      FizzBuzzType03.new
    else
      raise '該当するタイプは存在しません'
    end
  end

  def fizz?(number)
    number.modulo(3).zero?
  end

  def buzz?(number)
    number.modulo(5).zero?
  end
end

class FizzBuzzType01 < FizzBuzzType
...
```

``` ruby
class FizzBuzzType
  TYPE_01 = 1
  TYPE_02 = 2
  TYPE_03 = 3

  def self.create(type)
    case type
    when FizzBuzzType::TYPE_01
      FizzBuzzType01.new
    when FizzBuzzType::TYPE_02
      FizzBuzzType02.new
    when FizzBuzzType::TYPE_03
      FizzBuzzType03.new
    else
      FizzBuzzTypeNotDefined.new
    end
  end
...
class FizzBuzzTypeNotDefined < FizzBuzzType
  def generate(number)
    FizzBuzzValue.new(number, '')
  end

  def to_s
    '未定義'
  end
end

class FizzBuzzValue
...
```

``` bash
...
Started with run options --seed 33939


Progress: |=====================================================================================================|

Finished in 0.01193s
37 tests, 42 assertions, 0 failures, 0 errors, 0 skips
06:46:48 - INFO - Inspecting Ruby code style: lib/fizz_buzz.rb
 1/1 file |======================================= 100 ========================================>| Time: 00:00:00

1 file inspected, no offenses detected
06:46:49 - INFO - Inspecting Ruby code style: coverage/assets/0.10.2/colorbox/controls.png coverage/assets/0.10.2/colorbox/border.png coverage/assets/0.10.2/colorbox/loading.gif coverage/assets/0.10.2/colorbox/loading_background.png
 0/0 files |======================================= 100 =======================================>| Time: 00:00:00

0 files inspected, no offenses detected
...
```

テストが通るようになりました。 **メソッドオブジェクト** から実行された場合の振る舞いも明記しておきましょう。

``` ruby
...
    describe 'それ以外のタイプの場合' do
      def test_未定義のタイプを返す
        fizzbuzz = FizzBuzzType.create(4)

        assert_equal '未定義', fizzbuzz.to_s
      end

      def test_空の文字列を返す
        type = FizzBuzzType.create(4)
        command = FizzBuzzValueCommand.new(type)

        assert_equal '', command.execute(3)
      end
    end
  end
...
```

``` bash
...
06:48:54 - INFO - Running: all tests
Coverage report generated for MiniTest, Unit Tests to /workspace/tdd_rb/coverage. 62 / 65 LOC (95.38%) covered.
Started with run options --guard --seed 18202

  38/38: [==================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00747s
38 tests, 43 assertions, 0 failures, 0 errors, 0 skips
...
```

`FizzBuzzTypeNotDefined` オブジェクトは **Null Objectパターン** を適用したものです。

> Null Objectパターン
> 
> 特殊な状況をオブジェクトで表現するにはどうすればよいだろうか---その特殊な状況を表現するオブジェクトを作り、通常のオブジェクトと同じプロトコル（メソッド群）を実装しよう。
> 
> —  テスト駆動開発 

**オープン・クローズドの原則** に従って未定義のタイプである **Null Object** を安全に追加することができたのでコミットしておきます。

``` bash
$ git add .
$ git commit -m 'refactor: 特殊ケースの導入'
```

![diag-88e1d9090efb8e346a8986204d1decac.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20035/ca6f3d49-ca33-7e5a-5d72-7dd715f0eb12.png)

### モジュール分割

![diag-88e1d9090efb8e346a8986204d1decac.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20035/ca6f3d49-ca33-7e5a-5d72-7dd715f0eb12.png)

クラスモジュールの抽出によってアプリケーションの構造が **抽象化** された結果、視覚的に把握できるようになりました。ここでアプリケーションを実行してみましょう。

``` bash
$ ruby main.rb
Traceback (most recent call last):
main.rb:5:in `<main>': uninitialized constant FizzBuzz (NameError)
Did you mean?  FizzBuzzType
```

エラーが出ています、これはアプリケーションの構成が変わったためです。クライアントプログラムをアプリケーションの変更に合わせて修正します。

``` ruby
# frozen_string_literal: true

require './lib/fizz_buzz.rb'

puts FizzBuzz.generate_list
```

``` bash
# frozen_string_literal: true

require './lib/fizz_buzz.rb'

command = FizzBuzzListCommand.new(FizzBuzzType.create(FizzBuzzType::TYPE_01))
command.execute(100).each { |i| puts i.value }
```

``` bash
$ ruby main.rb
1
2
Fizz
4
Buzz
...
Fizz
```

クライアントプログラムが直ったのでコミットしておきます。

``` bash
$ git add .
$ git commit -m 'fix: プリントする'
```

#### ドメインモデル

`fizz_buzz.rb` ファイル内のクラスモジュールをファイルとして分割していきます。まずは **ドメインオブジェクト** を抽出して **ドメインモデル** として整理しましょう。既存のテストを壊さないように１つづつコピー&ペーストしていきます。

> 関連する業務データと業務ロジックを１つにまとめたこのようなオブジェクトをドメインオブジェクトと呼びます。
> 
> 「ドメイン」とは、対象領域とか問題領域という意味です。業務アプリケーションの場合、そのアプリケーションが対象となる業務活動全体がドメインです。業務活動という問題領域（ドメイン）で扱うデータと業務ロジックを、オブジェクトとして表現したものドメインオブジェクトです。ドメインオブジェクトは、業務データと業務ロジックを密接に関係づけます。
> 
> —  現場で役立つシステム設計の原則 

> このように業務アプリケーションの対象領域（ドメイン）をオブジェクトのモデルとして整理したものをドメインモデルと呼びます。
> 
> —  現場で役立つシステム設計の原則 

    /main.rb
      |--lib/
          |
           -- fizz_buzz.rb
      |--test/
          |
           -- fizz_buzz_test.rb

    /main.rb
      |--lib/
          |
          domain/
               |
               model/
                   |
                   -- fizz_buzz_value.rb
                   -- fizz_buzz_list.rb
               type/
                   |
                   -- fizz_buzz_type.rb
                   -- fizz_buzz_type_01.rb
                   -- fizz_buzz_type_02.rb
                   -- fizz_buzz_type_03.rb
                   -- fizz_buzz_type_not_defined.rb
           -- fizz_buzz.rb
      |--test/
          |
           -- fizz_buzz_test.rb

**値オブジェクトクラス** と **タイプクラス** を `domain` フォルダ以下に配置します。

``` ruby
# frozen_string_literal: true

class FizzBuzzValue
  attr_reader :number, :value

  def initialize(number, value)
    raise '正の値のみ有効です' if number.negative?

    @number = number
    @value = value
  end

  def to_s
    "#{@number}:#{@value}"
  end

  def ==(other)
    @number == other.number && @value == other.value
  end

  alias eql? ==
end
```

``` ruby
# frozen_string_literal: true

class FizzBuzzList
  MAX_COUNT = 100
  attr_reader :value

  def initialize(list)
    raise "上限は#{MAX_COUNT}件までです" if list.count > MAX_COUNT

    @value = list
  end

  def to_s
    @value.to_s
  end

  def add(value)
    FizzBuzzList.new(@value + value)
  end
end
```

``` ruby
# frozen_string_literal: true

class FizzBuzzType
  TYPE_01 = 1
  TYPE_02 = 2
  TYPE_03 = 3

  def self.create(type)
    case type
    when FizzBuzzType::TYPE_01
      FizzBuzzType01.new
    when FizzBuzzType::TYPE_02
      FizzBuzzType02.new
    when FizzBuzzType::TYPE_03
      FizzBuzzType03.new
    else
      FizzBuzzTypeNotDefined.new
    end
  end

  def fizz?(number)
    number.modulo(3).zero?
  end

  def buzz?(number)
    number.modulo(5).zero?
  end
end
```

``` ruby
# frozen_string_literal: true

class FizzBuzzType01 < FizzBuzzType
  def generate(number)
    return FizzBuzzValue.new(number, 'FizzBuzz') if fizz?(number) && buzz?(number)
    return FizzBuzzValue.new(number, 'Fizz') if fizz?(number)
    return FizzBuzzValue.new(number, 'Buzz') if buzz?(number)

    FizzBuzzValue.new(number, number.to_s)
  end
end
```

``` ruby
# frozen_string_literal: true

class FizzBuzzType02 < FizzBuzzType
  def generate(number)
    FizzBuzzValue.new(number, number.to_s)
  end
end
```

``` ruby
# frozen_string_literal: true

class FizzBuzzType03 < FizzBuzzType
  def generate(number)
    return FizzBuzzValue.new(number, 'FizzBuzz') if fizz?(number) && buzz?(number)

    FizzBuzzValue.new(number, number.to_s)
  end
end
```

``` ruby
# frozen_string_literal: true

class FizzBuzzTypeNotDefined < FizzBuzzType
  def generate(number)
    FizzBuzzValue.new(number, '')
  end

  def to_s
    '未定義'
  end
end
```

``` bash
...
07:29:03 - INFO - Inspecting Ruby code style: coverage/assets/0.10.2/colorbox/controls.png coverage/assets/0.10.2/colorbox/border.png coverage/assets/0.10.2/colorbox/loading.gif coverage/assets/0.10.2/colorbox/loading_background.png lib/domain/type/fizz_buzz_type_not_defined.rb lib/domain/type/fizz_buzz_type_03.rb lib/domain/type/fizz_buzz_type_02.rb lib/domain/type/fizz_buzz_type_01.rb lib/domain/type/fizz_buzz_type.rb lib/domain/model/fizz_buzz_list.rb lib/domain/model/fizz_buzz_value.rb
lib/domain/type/fizz_buzz_type_not_defined.rb:3:1: C: Style/Documentation: Missing top-level class documentation comment.
class FizzBuzzTypeNotDefined < FizzBuzzType
^^^^^
lib/domain/type/fizz_buzz_type_03.rb:3:1: C: Style/Documentation: Missing top-level class documentation comment.
class FizzBuzzType03 < FizzBuzzType
^^^^^
lib/domain/type/fizz_buzz_type_02.rb:3:1: C: Style/Documentation: Missing top-level class documentation comment.
class FizzBuzzType02 < FizzBuzzType
^^^^^
lib/domain/type/fizz_buzz_type_01.rb:3:1: C: Style/Documentation: Missing top-level class documentation comment.
class FizzBuzzType01 < FizzBuzzType
^^^^^
lib/domain/type/fizz_buzz_type.rb:3:1: C: Style/Documentation: Missing top-level class documentation comment.
class FizzBuzzType
^^^^^
lib/domain/model/fizz_buzz_list.rb:3:1: C: Style/Documentation: Missing top-level class documentation comment.
class FizzBuzzList
^^^^^
lib/domain/model/fizz_buzz_value.rb:3:1: C: Style/Documentation: Missing top-level class documentation comment.
class FizzBuzzValue
^^^^^
 7/7 files |======================== 100 =========================>| Time: 00:00:00

7 files inspected, 7 offenses detected
...
```

テストは壊れていないようですが警告が出るようになりました。まだ仕掛ですが一旦コミットしておきます。

``` bash
$ git add .
$ git commit -m 'refactor(WIP): モジュール分割'
```

![diag-5f47ca3306a2dc4364311b77c4b5cea3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20035/3da7c995-b63a-7864-7091-a0f43d3c99b1.png)


#### アプリケーション

続いて **アプリケーション層** の分割を行います。

> データクラスと機能クラスを分ける手続き型の設計では、アプリケーション層のクラスに業務ロジックの詳細を記述します。
> 
> —  現場で役立つシステム設計の原則 

    /main.rb
      |--lib/
          |
          domain/
               |
               model/
                   |
                   -- fizz_buzz_value.rb
                   -- fizz_buzz_list.rb
               type/
                   |
                   -- fizz_buzz_type.rb
                   -- fizz_buzz_type_01.rb
                   -- fizz_buzz_type_02.rb
                   -- fizz_buzz_type_03.rb
           -- fizz_buzz.rb
      |--test/
          |
           -- fizz_buzz_test.rb

    /main.rb
      |--lib/
          |
         application/
               |
               -- fizz_buzz_command.rb
               -- fizz_buzz_value_command.rb
               -- fizz_buzz_list_command.rb
         domain/
               |
               model/
                   |
                   -- fizz_buzz_value.rb
                   -- fizz_buzz_list.rb
               type/
                   |
                   -- fizz_buzz_type.rb
                   -- fizz_buzz_type_01.rb
                   -- fizz_buzz_type_02.rb
                   -- fizz_buzz_type_03.rb
           -- fizz_buzz.rb
      |--test/
          |
           -- fizz_buzz_test.rb

ここでは **ドメインオブジェクト** を操作する **メソッドオブジェクト** を `application` フォルダ以下に配置します。

``` ruby
# frozen_string_literal: true

class FizzBuzzCommand
  def execute; end
end
```

``` ruby
# frozen_string_literal: true

class FizzBuzzValueCommand < FizzBuzzCommand
  def initialize(type)
    @type = type
  end

  def execute(number)
    @type.generate(number).value
  end
end
```

``` ruby
# frozen_string_literal: true

class FizzBuzzListCommand < FizzBuzzCommand
  def initialize(type)
    @type = type
  end

  def execute(number)
    FizzBuzzList.new((1..number).map { |i| @type.generate(i) }).value
  end
end
```

テストは壊れていないのでコミットしておきます。

``` bash
$ git add .
$ git commit -m 'refactor(WIP): モジュール分割'
```

![diag-84a49e2f281dfc169055d0bfc4b4aeb6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20035/b4adb851-0e0d-a5eb-f490-cddd272822cc.png)

#### テスト

アプリケーションのメイン部分は分割できました。続いてテストも分割しましょう。

    /main.rb
      |--lib/
          |
         application/
               |
               -- fizz_buzz_command.rb
               -- fizz_buzz_value_command.rb
               -- fizz_buzz_list_command.rb
         domain/
               |
               model/
                   |
                   -- fizz_buzz_value.rb
                   -- fizz_buzz_list.rb
               type/
                   |
                   -- fizz_buzz_type.rb
                   -- fizz_buzz_type_01.rb
                   -- fizz_buzz_type_02.rb
                   -- fizz_buzz_type_03.rb
           -- fizz_buzz.rb
      |--test/
          |
           -- fizz_buzz_test.rb

    /main.rb
      |--lib/
          |
         application/
               |
               -- fizz_buzz_command.rb
               -- fizz_buzz_value_command.rb
               -- fizz_buzz_list_command.rb
         domain/
               |
               model/
                   |
                   -- fizz_buzz_value.rb
                   -- fizz_buzz_list.rb
               type/
                   |
                   -- fizz_buzz_type.rb
                   -- fizz_buzz_type_01.rb
                   -- fizz_buzz_type_02.rb
                   -- fizz_buzz_type_03.rb
           -- fizz_buzz.rb
      |--test/
          |
          application/
               |
               -- fizz_buzz_value_command_test.rb
               -- fizz_buzz_list_command_test.rb
          domain/
               |
               model/
                     |
                     -- fizz_buzz_value_test.rb
                     -- fizz_buzz_list_test.rb
          |
           -- learning_test.rb

``` ruby
# frozen_string_literal: true

require 'simplecov'
SimpleCov.start
require 'minitest/reporters'
Minitest::Reporters.use!
require 'minitest/autorun'
require './lib/fizz_buzz'

class FizzBuzzValueCommandTest < Minitest::Test
  describe '数を文字列にして返す' do
    describe 'タイプ1の場合' do
      def setup
        @fizzbuzz = FizzBuzzValueCommand.new(FizzBuzzType01.new)
      end

      describe '三の倍数の場合' do
        def test_3を渡したら文字列Fizzを返す
          assert_equal 'Fizz', @fizzbuzz.execute(3)
        end
      end

      describe '五の倍数の場合' do
        def test_5を渡したら文字列Buzzを返す
          assert_equal 'Buzz', @fizzbuzz.execute(5)
        end
      end

      describe '三と五の倍数の場合' do
        def test_15を渡したら文字列FizzBuzzを返す
          assert_equal 'FizzBuzz', @fizzbuzz.execute(15)
        end
      end

      describe 'その他の場合' do
        def test_1を渡したら文字列1を返す
          assert_equal '1', @fizzbuzz.execute(1)
        end
      end
    end

    describe 'タイプ2の場合' do
      def setup
        @fizzbuzz = FizzBuzzValueCommand.new(FizzBuzzType02.new)
      end

      describe '三の倍数の場合' do
        def test_3を渡したら文字列3を返す
          assert_equal '3', @fizzbuzz.execute(3)
        end
      end

      describe '五の倍数の場合' do
        def test_5を渡したら文字列5を返す
          assert_equal '5', @fizzbuzz.execute(5)
        end
      end

      describe '三と五の倍数の場合' do
        def test_15を渡したら文字列15を返す
          assert_equal '15', @fizzbuzz.execute(15)
        end
      end

      describe 'その他の場合' do
        def test_1を渡したら文字列1を返す
          assert_equal '1', @fizzbuzz.execute(1)
        end
      end
    end

    describe 'タイプ3の場合' do
      def setup
        @fizzbuzz = FizzBuzzValueCommand.new(FizzBuzzType03.new)
      end

      describe '三の倍数の場合' do
        def test_3を渡したら文字列3を返す
          assert_equal '3', @fizzbuzz.execute(3)
        end
      end

      describe '五の倍数の場合' do
        def test_5を渡したら文字列5を返す
          assert_equal '5', @fizzbuzz.execute(5)
        end
      end

      describe '三と五の倍数の場合' do
        def test_15を渡したら文字列FizzBuzzを返す
          assert_equal 'FizzBuzz', @fizzbuzz.execute(15)
        end
      end

      describe 'その他の場合' do
        def test_1を渡したら文字列1を返す
          assert_equal '1', @fizzbuzz.execute(1)
        end
      end
    end

    describe 'それ以外のタイプの場合' do
      def test_未定義のタイプを返す
        fizzbuzz = FizzBuzzType.create(4)

        assert_equal '未定義', fizzbuzz.to_s
      end

      def test_空の文字列を返す
        type = FizzBuzzType.create(4)
        command = FizzBuzzValueCommand.new(type)

        assert_equal '', command.execute(3)
      end
    end
  end

  describe '例外ケース' do
    def test_値は正の値のみ許可する
      e = assert_raises RuntimeError do
        FizzBuzzValueCommand.new(
          FizzBuzzType.create(FizzBuzzType::TYPE_01)
        ).execute(-1)
      end

      assert_equal '正の値のみ有効です', e.message
    end
  end
end
```

``` ruby
# frozen_string_literal: true

require 'simplecov'
SimpleCov.start
require 'minitest/reporters'
Minitest::Reporters.use!
require 'minitest/autorun'
require './lib/fizz_buzz'

class FizzBuzzListCommandTest < Minitest::Test
  describe '数を文字列にして返す' do
    describe 'タイプ1の場合' do
      describe '1から100までのFizzBuzzの配列を返す' do
        def setup
          fizzbuzz = FizzBuzzListCommand.new(FizzBuzzType01.new)
          @result = fizzbuzz.execute(100)
        end

        def test_配列の初めは文字列の1を返す
          assert_equal '1', @result.first.value
        end

        def test_配列の最後は文字列のBuzzを返す
          assert_equal 'Buzz', @result.last.value
        end

        def test_配列の2番目は文字列のFizzを返す
          assert_equal 'Fizz', @result[2].value
        end

        def test_配列の4番目は文字列のBuzzを返す
          assert_equal 'Buzz', @result[4].value
        end

        def test_配列の14番目は文字列のFizzBuzzを返す
          assert_equal 'FizzBuzz', @result[14].value
        end
      end
    end
  end

  describe '例外ケース' do
    def test_100より多い数を許可しない
      e = assert_raises RuntimeError do
        FizzBuzzListCommand.new(
          FizzBuzzType.create(FizzBuzzType::TYPE_01)
        ).execute(101)
      end

      assert_equal '上限は100件までです', e.message
    end
  end
end
```

``` ruby
# frozen_string_literal: true

require 'simplecov'
SimpleCov.start
require 'minitest/reporters'
Minitest::Reporters.use!
require 'minitest/autorun'
require './lib/fizz_buzz'

class FizzBuzzValueTest < Minitest::Test
  def test_同じで値である
    value1 = FizzBuzzValue.new(1, '1')
    value2 = FizzBuzzValue.new(1, '1')

    assert value1.eql?(value2)
  end

  def test_to_stringメソッド
    value = FizzBuzzValue.new(3, 'Fizz')

    assert_equal '3:Fizz', value.to_s
  end
end
```

``` ruby
# frozen_string_literal: true

require 'simplecov'
SimpleCov.start
require 'minitest/reporters'
Minitest::Reporters.use!
require 'minitest/autorun'
require './lib/fizz_buzz'

class FizzBuzzListTest < Minitest::Test
  def test_新しいインスタンスが作られる
    command = FizzBuzzListCommand.new(FizzBuzzType.create(FizzBuzzType::TYPE_01))
    array = command.execute(50)
    list1 = FizzBuzzList.new(array)
    list2 = list1.add(array)

    assert_equal 50, list1.value.count
    assert_equal 100, list2.value.count
  end
end
```

``` ruby
# frozen_string_literal: true

require 'simplecov'
SimpleCov.start
require 'minitest/reporters'
Minitest::Reporters.use!
require 'minitest/autorun'
require './lib/fizz_buzz'

class LearningTest < Minitest::Test
  describe '配列や繰り返し処理を理解する' do
    def test_繰り返し処理
      $stdout = StringIO.new
      [1, 2, 3].each { |i| p i * i }
      output = $stdout.string

      assert_equal "1\n" + "4\n" + "9\n", output
    end

    def test_selectメソッドで特定の条件を満たす要素だけを配列に入れて返す
      result = [1.1, 2, 3.3, 4].select(&:integer?)
      assert_equal [2, 4], result
    end

    def test_find_allメソッドで特定の条件を満たす要素だけを配列に入れて返す
      result = [1.1, 2, 3.3, 4].find_all(&:integer?)
      assert_equal [2, 4], result
    end

    def test_特定の条件を満たさない要素だけを配列に入れて返す
      result = [1.1, 2, 3.3, 4].reject(&:integer?)
      assert_equal [1.1, 3.3], result
    end

    def test_mapメソッドで新しい要素の配列を返す
      result = %w[apple orange pineapple strawberry].map(&:size)
      assert_equal [5, 6, 9, 10], result
    end

    def test_collectメソッドで新しい要素の配列を返す
      result = %w[apple orange pineapple strawberry].collect(&:size)
      assert_equal [5, 6, 9, 10], result
    end

    def test_findメソッドで配列の中から条件に一致する要素を取得する
      result = %w[apple orange pineapple strawberry].find(&:size)
      assert_equal 'apple', result
    end

    def test_detectメソッドで配列の中から条件に一致する要素を取得する
      result = %w[apple orange pineapple strawberry].detect(&:size)
      assert_equal 'apple', result
    end

    def test_指定した評価式で並び変えた配列を返す
      result1 = %w[2 4 13 3 1 10].sort
      result2 = %w[2 4 13 3 1 10].sort { |a, b| a.to_i <=> b.to_i }
      result3 = %w[2 4 13 3 1 10].sort { |b, a| a.to_i <=> b.to_i }

      assert_equal %w[1 10 13 2 3 4], result1
      assert_equal %w[1 2 3 4 10 13], result2
      assert_equal %w[13 10 4 3 2 1], result3
    end

    def test_配列の中から条件に一致する要素を取得する
      result = %w[apple orange pineapple strawberry apricot].grep(/^a/)
      assert_equal %w[apple apricot], result
    end

    def test_ブロック内の条件式が真である間までの要素を返す
      result = [1, 2, 3, 4, 5, 6, 7, 8, 9].take_while { |item| item < 6 }
      assert_equal [1, 2, 3, 4, 5], result
    end

    def test_ブロック内の条件式が真である以降の要素を返す
      result = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].drop_while { |item| item < 6 }
      assert_equal [6, 7, 8, 9, 10], result
    end

    def test_injectメソッドで畳み込み演算を行う
      result = [1, 2, 3, 4, 5].inject(0) { |total, n| total + n }
      assert_equal 15, result
    end

    def test_reduceメソッドで畳み込み演算を行う
      result = [1, 2, 3, 4, 5].reduce { |total, n| total + n }
      assert_equal 15, result
    end
  end
end
```

ファイル分割でテストは壊れていないようですが警告がたくさん出てきました。

``` bash
...
test/learning_test.rb:70:14: C: Naming/AsciiIdentifiers: Use only ascii symbols in identifiers.
    def test_ブロック内の条件式が真である間までの要素を返す
             ^^^^^^^^^^^^^^^^^^^^^^^
test/learning_test.rb:75:9: C: Naming/MethodName: Use snake_case for method names.
    def test_ブロック内の条件式が真である以降の要素を返す
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^
test/learning_test.rb:75:14: C: Naming/AsciiIdentifiers: Use only ascii symbols in identifiers.
    def test_ブロック内の条件式が真である以降の要素を返す
             ^^^^^^^^^^^^^^^^^^^^^^
test/learning_test.rb:80:9: C: Naming/MethodName: Use snake_case for method names.
    def test_injectメソッドで畳み込み演算を行う
        ^^^^^^^^^^^^^^^^^^^^^^^^^
test/learning_test.rb:80:20: C: Naming/AsciiIdentifiers: Use only ascii symbols in identifiers.
    def test_injectメソッドで畳み込み演算を行う
                   ^^^^^^^^^^^^^^
test/learning_test.rb:85:9: C: Naming/MethodName: Use snake_case for method names.
    def test_reduceメソッドで畳み込み演算を行う
        ^^^^^^^^^^^^^^^^^^^^^^^^^
test/learning_test.rb:85:20: C: Naming/AsciiIdentifiers: Use only ascii symbols in identifiers.
    def test_reduceメソッドで畳み込み演算を行う
                   ^^^^^^^^^^^^^^
 15/15 files |======================= 100 ========================>| Time: 00:00:00

15 files inspected, 87 offenses detected
...
```

これらはテストコードに関する警告がほとんどなので `.rubocop.yml` を編集してチェック対象から外しておきましょう。

``` yml
inherit_from: .rubocop_todo.yml

Naming/AsciiIdentifiers:
  Exclude:
    - 'test/**/*'

Naming/MethodName:
  EnforcedStyle: snake_case
  Exclude:
    - 'test/**/*'

Metrics/BlockLength:
  Max: 62
  Exclude:
    - 'test/**/*'

Documentation:
  Enabled: false
```

``` bash
...
08:21:55 - INFO - Running: all tests
Coverage report generated for MiniTest, Unit Tests to /workspace/tdd_rb/coverage. 144 / 215 LOC (66.98%) covered.
Started with run options --guard --seed 55977

  70/70: [=====================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.01518s
70 tests, 79 assertions, 0 failures, 0 errors, 0 skips

08:21:56 - INFO - Inspecting Ruby code style of all files
/workspace/tdd_rb/.rubocop.yml: Warning: no department given for Documentation.
 22/22 files |======================= 100 ========================>| Time: 00:00:00

22 files inspected, no offenses detected
08:21:58 - INFO - Inspecting Ruby code style: coverage/assets/0.10.2/colorbox/controls.png coverage/assets/0.10.2/colorbox/border.png coverage/assets/0.10.2/colorbox/loading.gif coverage/assets/0.10.2/colorbox/loading_background.png
/workspace/tdd_rb/.rubocop.yml: Warning: no department given for Documentation.
 0/0 files |======================== 100 =========================>| Time: 00:00:00

0 files inspected, no offenses detected
...
```

警告は消えました、仕上げに `fizz_buzz_test.rb` ファイルを削除します。

``` bash
...
08:24:12 - INFO - Running: all tests
Coverage report generated for MiniTest, Unit Tests to /workspace/tdd_rb/coverage. 135 / 201 LOC (67.16%) covered.
Started with run options --guard --seed 40104

  32/32: [=====================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00601s
32 tests, 36 assertions, 0 failures, 0 errors, 0 skips

08:24:13 - INFO - Inspecting Ruby code style of all files
/workspace/tdd_rb/.rubocop.yml: Warning: no department given for Documentation.
 21/21 files |======================= 100 ========================>| Time: 00:00:00

21 files inspected, no offenses detected
08:24:14 - INFO - Inspecting Ruby code style: coverage/assets/0.10.2/colorbox/controls.png coverage/assets/0.10.2/colorbox/border.png coverage/assets/0.10.2/colorbox/loading.gif coverage/assets/0.10.2/colorbox/loading_background.png
/workspace/tdd_rb/.rubocop.yml: Warning: no department given for Documentation.
 0/0 files |======================== 100 =========================>| Time: 00:00:00

0 files inspected, no offenses detected
...
```

テストの分割も完了したのでコミットしておきます。

``` bash
$ git add .
$ git commit -m 'refactor(WIP): モジュール分割'
```

#### エントリーポイント

仕上げはクラスモジュールのエントリーポイント作成とテストヘルパーの追加です。

    /main.rb
      |--lib/
          |
         application/
               |
               -- fizz_buzz_command.rb
               -- fizz_buzz_value_command.rb
               -- fizz_buzz_list_command.rb
         domain/
               |
               model/
                   |
                   -- fizz_buzz_value.rb
                   -- fizz_buzz_list.rb
               type/
                   |
                   -- fizz_buzz_type.rb
                   -- fizz_buzz_type_01.rb
                   -- fizz_buzz_type_02.rb
                   -- fizz_buzz_type_03.rb
           -- fizz_buzz.rb
      |--test/
          |
          application/
               |
               -- fizz_buzz_value_command_test.rb
               -- fizz_buzz_list_command._test.rb
          domain/
               |
               model/
                     |
                     -- fizz_buzz_value_test.rb
                     -- fizz_buzz_list_test.rb
          |
           -- learning_test.rb

    /main.rb
      |--lib/
          |
         application/
               |
               -- fizz_buzz_command.rb
               -- fizz_buzz_value_command.rb
               -- fizz_buzz_list_command.rb
         domain/
               |
               model/
                   |
                   -- fizz_buzz_value.rb
                   -- fizz_buzz_list.rb
               type/
                   |
                   -- fizz_buzz_type.rb
                   -- fizz_buzz_type_01.rb
                   -- fizz_buzz_type_02.rb
                   -- fizz_buzz_type_03.rb
           -- fizz_buzz.rb
      |--test/
          |
          application/
               |
               -- fizz_buzz_value_command_test.rb
               -- fizz_buzz_list_command._test.rb
          domain/
               |
               model/
                     |
                     -- fizz_buzz_value_test.rb
                     -- fizz_buzz_list_test.rb
          |
           -- learning_test.rb
           -- test_helper.rb

`fizz_buzz.rb` ファイルの内容をクラスモジュール読み込みに変更します。

``` ruby
require './lib/application/fizz_buzz_command.rb'
require './lib/application/fizz_buzz_value_command.rb'
require './lib/application/fizz_buzz_list_command.rb'
require './lib/domain/model/fizz_buzz_value.rb'
require './lib/domain/model/fizz_buzz_list.rb'
require './lib/domain/type/fizz_buzz_type.rb'
require './lib/domain/type/fizz_buzz_type_01.rb'
require './lib/domain/type/fizz_buzz_type_02.rb'
require './lib/domain/type/fizz_buzz_type_03.rb'
require './lib/domain/type/fizz_buzz_type_not_defined.rb'
```

``` bash
...
08:34:32 - INFO - Running: all tests
Coverage report generated for MiniTest to /workspace/tdd_rb/coverage. 119 / 211 LOC (56.4%) covered.
Started with run options --guard --seed 18696

  32/32: [=====================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00561s
32 tests, 36 assertions, 0 failures, 0 errors, 0 skips
....
```

コードカバレッジがうまく機能していないようなので、`test_helper.rb` を追加して共通部分を各テストファイルから読み込むように変更します。

``` ruby
# frozen_string_literal: true

require 'simplecov'
SimpleCov.start
require 'minitest/reporters'
Minitest::Reporters.use!
```

``` ruby
require 'simplecov'
SimpleCov.start
require 'minitest/reporters'
Minitest::Reporters.use!
require 'minitest/autorun'
require './lib/fizz_buzz'

...
```

``` ruby
require './test/test_helper'
require 'minitest/autorun'
require './lib/fizz_buzz'

...
```

テストタスクを実行したところ動作しなくなりました。

``` bash
$ rake test
```

テスト対象をテストディレクトリ内のすべてのテストコードに変更します。

``` ruby
...
Rake::TestTask.new do |test|
  test.test_files = Dir['./test/fizz_buzz_test.rb']
  test.verbose = true
end
...
```

``` ruby
...
Rake::TestTask.new do |test|
  test.test_files = Dir['./test/**/*_test.rb']
  test.verbose = true
end
...
```

``` bash
$ rake test
Started with run options --seed 46929

  32/32: [=====================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00800s
32 tests, 36 assertions, 0 failures, 0 errors, 0 skips
```

テストも壊れていないし警告も出ていません。モジュール分割完了です。

``` bash
$ git add .
$ git commit -m 'refactor: モジュール分割'
```

![diag-c335939a7cce862d0b767629390a1852.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20035/7b7ead60-577c-fb59-6866-0b3c49b30112.png)

### ふりかえり

今回、 **オブジェクト指向プログラム** から **オブジェクト指向設計** そして **モジュール分割** を **テスト駆動開発**を通じて実践しました。各トピックを振り返ってみましょう。

#### オブジェクト指向プログラム

エピソード1で作成したプログラムの追加仕様を **テスト駆動開発** で実装しました。 次に **手続き型コード** との比較から **オブジェクト指向プログラム** を構成する **カプセル化** **ポリモフィズム** **継承** という概念をコードベースの **リファクタリング** を通じて解説しました。

具体的には **フィールドのカプセル** から **setterの削除** を適用することにより **カプセル化** を実現しました。続いて、 **ポリモーフィズムによる条件記述の置き換え** から **State/Strategyによるタイプコードの置き換え** を適用することにより **ポリモーフィズム** の効果を体験しました。そして、 **スーパークラスの抽出** から **メソッド名の変更** **メソッドの移動** の適用を通して **継承** の使い方を体験しました。さらに **値オブジェクト** と **ファーストクラス** というオブジェクト指向プログラミングに必要なツールの使い方も学習しました。

#### オブジェクト指向設計

次に設計の観点から **単一責任の原則** に違反している `FizzBuzz` クラスを **デザインパターン** の1つである **Commandパターン** を使ったリファクタリングである **メソッドオブジェクトによるメソッドの置き換え** を適用してクラスの責務を分割しました。オブジェクト指向設計のイデオムである **デザインパターン** として **Commandパターン** 以外に **Value Objectパターン** **Factory Methodパターン** **Strategyパターン** を **リファクタリング** を適用する過程ですでに実現していたことを説明しました。そして、**オープン・クローズドの原則** を満たすコードに **リファクタリング** されたことで既存のコードを変更することなく振る舞いを変更できるようになりました。

加えて、正常系の設計を改善した後 **アサーションの導入** **例外によるエラーコードの置き換え** といった例外系の **リファクタリング** を適用しました。最後に **ポリモーフィズム** の応用として **特殊ケースの導入** の適用による **Null Objectパターン** を使った **オープン・クローズドの原則** に従った安全なコードの追加方法を解説しました。

#### モジュールの分割

仕上げに、**モノリシック** なファイルから個別のクラスモジュールへの分割を **ドメインオブジェクト** の抽出を通して **ドメインモデル** へと整理することにより **モジュール分割** を実現しました。最終的にプログラムからアプリケーションへと体裁を整えることが出来ました。以下が最終的なモジュール構造とコードです。

![diag-c335939a7cce862d0b767629390a1852.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20035/7b7ead60-577c-fb59-6866-0b3c49b30112.png)

  - Application

**/main.rb.**

``` ruby
# frozen_string_literal: true

require './lib/fizz_buzz.rb'

command = FizzBuzzListCommand.new(FizzBuzzType.create(FizzBuzzType::TYPE_01))
command.execute(100).each { |i| puts i.value }
```

**/lib/application/fizz\_buzz\_command.rb.**

``` ruby
# frozen_string_literal: true

class FizzBuzzCommand
  def execute; end
end
```

**/lib/application/fizz\_buzz\_value\_command.rb.**

``` ruby
# frozen_string_literal: true

class FizzBuzzValueCommand < FizzBuzzCommand
  def initialize(type)
    @type = type
  end

  def execute(number)
    @type.generate(number).value
  end
end
```

**/lib/application/fizz\_buzz\_list\_command.rb.**

``` ruby
# frozen_string_literal: true

class FizzBuzzListCommand < FizzBuzzCommand
  def initialize(type)
    @type = type
  end

  def execute(number)
    FizzBuzzList.new((1..number).map { |i| @type.generate(i) }).value
  end
end
```

  - Domain

**/lib/domain/model/fizz\_buzz\_value.rb.**

``` ruby
# frozen_string_literal: true

class FizzBuzzValue
  attr_reader :number, :value

  def initialize(number, value)
    raise '正の値のみ有効です' if number.negative?

    @number = number
    @value = value
  end

  def to_s
    "#{@number}:#{@value}"
  end

  def ==(other)
    @number == other.number && @value == other.value
  end

  alias eql? ==
end
```

**/lib/domain/model/fizz\_buzz\_list.rb.**

``` ruby
# frozen_string_literal: true

class FizzBuzzList
  MAX_COUNT = 100
  attr_reader :value

  def initialize(list)
    raise "上限は#{MAX_COUNT}件までです" if list.count > MAX_COUNT

    @value = list
  end

  def to_s
    @value.to_s
  end

  def add(value)
    FizzBuzzList.new(@value + value)
  end
end
```

**/lib/domain/type/fizz\_buzz\_type.rb.**

``` ruby
# frozen_string_literal: true

class FizzBuzzType
  TYPE_01 = 1
  TYPE_02 = 2
  TYPE_03 = 3

  def self.create(type)
    case type
    when FizzBuzzType::TYPE_01
      FizzBuzzType01.new
    when FizzBuzzType::TYPE_02
      FizzBuzzType02.new
    when FizzBuzzType::TYPE_03
      FizzBuzzType03.new
    else
      FizzBuzzTypeNotDefined.new
    end
  end

  def fizz?(number)
    number.modulo(3).zero?
  end

  def buzz?(number)
    number.modulo(5).zero?
  end
end
```

**/lib/domain/type/fizz\_buzz\_type\_01.rb.**

``` ruby
# frozen_string_literal: true

class FizzBuzzType01 < FizzBuzzType
  def generate(number)
    return FizzBuzzValue.new(number, 'FizzBuzz') if fizz?(number) && buzz?(number)
    return FizzBuzzValue.new(number, 'Fizz') if fizz?(number)
    return FizzBuzzValue.new(number, 'Buzz') if buzz?(number)

    FizzBuzzValue.new(number, number.to_s)
  end
end
```

**/lib/domain/type/fizz\_buzz\_type\_02.rb.**

``` ruby
# frozen_string_literal: true

class FizzBuzzType02 < FizzBuzzType
  def generate(number)
    FizzBuzzValue.new(number, number.to_s)
  end
end
```

**/lib/domain/type/fizz\_buzz\_type\_03.rb.**

``` ruby
# frozen_string_literal: true

class FizzBuzzType03 < FizzBuzzType
  def generate(number)
    return FizzBuzzValue.new(number, 'FizzBuzz') if fizz?(number) && buzz?(number)

    FizzBuzzValue.new(number, number.to_s)
  end
end
```

**/lib/domain/type/fizz\_buzz\_type\_not\_defined.b.**

``` ruby
# frozen_string_literal: true

class FizzBuzzTypeNotDefined < FizzBuzzType
  def generate(number)
    FizzBuzzValue.new(number, '')
  end

  def to_s
    '未定義'
  end
end
```

  - Test

**/test/application/fizz\_buzz\_value\_command\_test.rb.**

``` ruby
# frozen_string_literal: true

require './test/test_helper'
require 'minitest/autorun'
require './lib/fizz_buzz'

class FizzBuzzValueCommandTest < Minitest::Test
  describe '数を文字列にして返す' do
    describe 'タイプ1の場合' do
      def setup
        @fizzbuzz = FizzBuzzValueCommand.new(FizzBuzzType01.new)
      end

      describe '三の倍数の場合' do
        def test_3を渡したら文字列Fizzを返す
          assert_equal 'Fizz', @fizzbuzz.execute(3)
        end
      end

      describe '五の倍数の場合' do
        def test_5を渡したら文字列Buzzを返す
          assert_equal 'Buzz', @fizzbuzz.execute(5)
        end
      end

      describe '三と五の倍数の場合' do
        def test_15を渡したら文字列FizzBuzzを返す
          assert_equal 'FizzBuzz', @fizzbuzz.execute(15)
        end
      end

      describe 'その他の場合' do
        def test_1を渡したら文字列1を返す
          assert_equal '1', @fizzbuzz.execute(1)
        end
      end
    end

    describe 'タイプ2の場合' do
      def setup
        @fizzbuzz = FizzBuzzValueCommand.new(FizzBuzzType02.new)
      end

      describe '三の倍数の場合' do
        def test_3を渡したら文字列3を返す
          assert_equal '3', @fizzbuzz.execute(3)
        end
      end

      describe '五の倍数の場合' do
        def test_5を渡したら文字列5を返す
          assert_equal '5', @fizzbuzz.execute(5)
        end
      end

      describe '三と五の倍数の場合' do
        def test_15を渡したら文字列15を返す
          assert_equal '15', @fizzbuzz.execute(15)
        end
      end

      describe 'その他の場合' do
        def test_1を渡したら文字列1を返す
          assert_equal '1', @fizzbuzz.execute(1)
        end
      end
    end

    describe 'タイプ3の場合' do
      def setup
        @fizzbuzz = FizzBuzzValueCommand.new(FizzBuzzType03.new)
      end

      describe '三の倍数の場合' do
        def test_3を渡したら文字列3を返す
          assert_equal '3', @fizzbuzz.execute(3)
        end
      end

      describe '五の倍数の場合' do
        def test_5を渡したら文字列5を返す
          assert_equal '5', @fizzbuzz.execute(5)
        end
      end

      describe '三と五の倍数の場合' do
        def test_15を渡したら文字列FizzBuzzを返す
          assert_equal 'FizzBuzz', @fizzbuzz.execute(15)
        end
      end

      describe 'その他の場合' do
        def test_1を渡したら文字列1を返す
          assert_equal '1', @fizzbuzz.execute(1)
        end
      end
    end

    describe 'それ以外のタイプの場合' do
      def test_未定義のタイプを返す
        fizzbuzz = FizzBuzzType.create(4)

        assert_equal '未定義', fizzbuzz.to_s
      end

      def test_空の文字列を返す
        type = FizzBuzzType.create(4)
        command = FizzBuzzValueCommand.new(type)

        assert_equal '', command.execute(3)
      end
    end
  end

  describe '例外ケース' do
    def test_値は正の値のみ許可する
      e = assert_raises RuntimeError do
        FizzBuzzValueCommand.new(
          FizzBuzzType.create(FizzBuzzType::TYPE_01)
        ).execute(-1)
      end

      assert_equal '正の値のみ有効です', e.message
    end
  end
end
```

**/test/application/fizz\_buzz\_list\_command\_test.rb.**

``` ruby
# frozen_string_literal: true

require './test/test_helper'
require 'minitest/autorun'
require './lib/fizz_buzz'

class FizzBuzzListCommandTest < Minitest::Test
  describe '数を文字列にして返す' do
    describe 'タイプ1の場合' do
      describe '1から100までのFizzBuzzの配列を返す' do
        def setup
          fizzbuzz = FizzBuzzListCommand.new(FizzBuzzType01.new)
          @result = fizzbuzz.execute(100)
        end

        def test_配列の初めは文字列の1を返す
          assert_equal '1', @result.first.value
        end

        def test_配列の最後は文字列のBuzzを返す
          assert_equal 'Buzz', @result.last.value
        end

        def test_配列の2番目は文字列のFizzを返す
          assert_equal 'Fizz', @result[2].value
        end

        def test_配列の4番目は文字列のBuzzを返す
          assert_equal 'Buzz', @result[4].value
        end

        def test_配列の14番目は文字列のFizzBuzzを返す
          assert_equal 'FizzBuzz', @result[14].value
        end
      end
    end
  end

  describe '例外ケース' do
    def test_100より多い数を許可しない
      e = assert_raises RuntimeError do
        FizzBuzzListCommand.new(
          FizzBuzzType.create(FizzBuzzType::TYPE_01)
        ).execute(101)
      end

      assert_equal '上限は100件までです', e.message
    end
  end
end
```

**/test/domain/model/fizz\_buzz\_value\_test.rb.**

``` ruby
# frozen_string_literal: true

require './test/test_helper'
require 'minitest/autorun'
require './lib/fizz_buzz'

class FizzBuzzValueTest < Minitest::Test
  def test_同じで値である
    value1 = FizzBuzzValue.new(1, '1')
    value2 = FizzBuzzValue.new(1, '1')

    assert value1.eql?(value2)
  end

  def test_to_stringメソッド
    value = FizzBuzzValue.new(3, 'Fizz')

    assert_equal '3:Fizz', value.to_s
  end
end
```

**/test/domain/model/fizz\_buzz\_list\_test.rb.**

``` ruby
# frozen_string_literal: true

require './test/test_helper'
require 'minitest/autorun'
require './lib/fizz_buzz'

class FizzBuzzListTest < Minitest::Test
  def test_新しいインスタンスが作られる
    command = FizzBuzzListCommand.new(FizzBuzzType.create(FizzBuzzType::TYPE_01))
    array = command.execute(50)
    list1 = FizzBuzzList.new(array)
    list2 = list1.add(array)

    assert_equal 50, list1.value.count
    assert_equal 100, list2.value.count
  end
end
```

**/test/learning\_test.rb.**

``` ruby
# frozen_string_literal: true

require './test/test_helper'
require 'minitest/autorun'
require './lib/fizz_buzz'

class LearningTest < Minitest::Test
  describe '配列や繰り返し処理を理解する' do
    def test_繰り返し処理
      $stdout = StringIO.new
      [1, 2, 3].each { |i| p i * i }
      output = $stdout.string

      assert_equal "1\n" + "4\n" + "9\n", output
    end

    def test_selectメソッドで特定の条件を満たす要素だけを配列に入れて返す
      result = [1.1, 2, 3.3, 4].select(&:integer?)
      assert_equal [2, 4], result
    end

    def test_find_allメソッドで特定の条件を満たす要素だけを配列に入れて返す
      result = [1.1, 2, 3.3, 4].find_all(&:integer?)
      assert_equal [2, 4], result
    end

    def test_特定の条件を満たさない要素だけを配列に入れて返す
      result = [1.1, 2, 3.3, 4].reject(&:integer?)
      assert_equal [1.1, 3.3], result
    end

    def test_mapメソッドで新しい要素の配列を返す
      result = %w[apple orange pineapple strawberry].map(&:size)
      assert_equal [5, 6, 9, 10], result
    end

    def test_collectメソッドで新しい要素の配列を返す
      result = %w[apple orange pineapple strawberry].collect(&:size)
      assert_equal [5, 6, 9, 10], result
    end

    def test_findメソッドで配列の中から条件に一致する要素を取得する
      result = %w[apple orange pineapple strawberry].find(&:size)
      assert_equal 'apple', result
    end

    def test_detectメソッドで配列の中から条件に一致する要素を取得する
      result = %w[apple orange pineapple strawberry].detect(&:size)
      assert_equal 'apple', result
    end

    def test_指定した評価式で並び変えた配列を返す
      result1 = %w[2 4 13 3 1 10].sort
      result2 = %w[2 4 13 3 1 10].sort { |a, b| a.to_i <=> b.to_i }
      result3 = %w[2 4 13 3 1 10].sort { |b, a| a.to_i <=> b.to_i }

      assert_equal %w[1 10 13 2 3 4], result1
      assert_equal %w[1 2 3 4 10 13], result2
      assert_equal %w[13 10 4 3 2 1], result3
    end

    def test_配列の中から条件に一致する要素を取得する
      result = %w[apple orange pineapple strawberry apricot].grep(/^a/)
      assert_equal %w[apple apricot], result
    end

    def test_ブロック内の条件式が真である間までの要素を返す
      result = [1, 2, 3, 4, 5, 6, 7, 8, 9].take_while { |item| item < 6 }
      assert_equal [1, 2, 3, 4, 5], result
    end

    def test_ブロック内の条件式が真である以降の要素を返す
      result = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].drop_while { |item| item < 6 }
      assert_equal [6, 7, 8, 9, 10], result
    end

    def test_injectメソッドで畳み込み演算を行う
      result = [1, 2, 3, 4, 5].inject(0) { |total, n| total + n }
      assert_equal 15, result
    end

    def test_reduceメソッドで畳み込み演算を行う
      result = [1, 2, 3, 4, 5].reduce { |total, n| total + n }
      assert_equal 15, result
    end
  end
end
```

#### 良い設計

エピソード1では **良いコード** について考えました。

> TDDは「より良いコードを書けば、よりうまくいく」という素朴で奇妙な仮設によって成り立っている
> 
> —  テスト駆動開発
> 

> 「動作するきれいなコード」。RonJeffriesのこの簡潔な言葉が、テスト駆動開発(TDD)のゴールだ。動作するきれいなコードはあらゆる意味で価値がある。
> 
> —  テスト駆動開発 

> 良いコードかどうかは、変更がどれだけ容易なのかで決まる。
> 
> —  リファクタリング(第2版) 

> コードは理解しやすくなければいけない。
> 
> —  リーダブルコード 

本エピソードでは **テスト駆動開発** による **オブジェクト指向プログラミング** の **リファクタリング** を経てコードベースを改善してきました。そして **オブジェクト指向設計** により **良いコード** のプログラムを **良い設計** のアプリケーションへと進化させることができました。

> どこに何が書いてあるかをわかりやすくし、変更の影響を狭い範囲に閉じ込め、安定して動作する部品を柔軟に組み合わせながらソフトウェアを構築する技法がオブジェクト指向設計です。
> 
> —  現場で役立つシステム設計の原則
> 

> 設計の良し悪しは、ソフトウェアを変更するときにはっきりします。
> 
> 構造が入り組んだわかりづらいプログラムは内容の理解に時間がかかります。重複したコードをあちこちで修正する作業が増え、変更の副作用に悩まされます。
> 
> 一方、うまく設計されたプログラムは変更が楽で安全です。変更すべき箇所がかんたんにわかり、変更するコード量が少なく、変更の影響を狭い範囲に限定できます。
> 
> プログラムの修正に３日かかるか、それとも半日で済むか。その違いを生むのが「設計」なのです。
> 
> —  現場で役立つシステム設計の原則 


では、いつ設計をしていたのでしょうか？ わかりますよね、このエピソードの始まりから終わりまで常に設計をしていたのです。

> TDDは分析技法であり、設計技法であり、実際には開発のすべてのアクティビティを構造化する技法なのだ。
>
> —  テスト駆動開発

## 参考サイト

  - [オブジェクト指向のいろは](https://qiita.com/nrslib/items/73bf176147192c402049)


## 参考図書

  - テスト駆動開発 Kent Beck (著), 和田 卓人 (翻訳): オーム社; 新訳版 (2017/10/14)

  - 新装版 リファクタリング―既存のコードを安全に改善する― (OBJECT TECHNOLOGY SERIES) Martin
    Fowler (著), 児玉 公信 (翻訳), 友野 晶夫 (翻訳), 平澤 章 (翻訳), その他: オーム社; 新装版
    (2014/7/26)

  - リファクタリング(第2版): 既存のコードを安全に改善する (OBJECT TECHNOLOGY SERIES) Martin
    Fowler (著), 児玉 公信 (翻訳), 友野 晶夫 (翻訳), 平澤 章 (翻訳), その他: オーム社; 第2版
    (2019/12/1)

  - リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)
    Dustin Boswell (著), Trevor Foucher (著), 須藤 功平 (解説), 角 征典 (翻訳):
    オライリージャパン; 初版八刷版 (2012/6/23)

  - Clean Code　アジャイルソフトウェア達人の技 (アスキードワンゴ) Ｒｏｂｅｒｔ Ｃ．Ｍａｒｔｉｎ (著), 花井
    志生 (著) ドワンゴ (2017/12/28)

  - 現場で役立つシステム設計の原則 〜変更を楽で安全にするオブジェクト指向の実践技法 増田 亨 (著) 技術評論社
    (2017/7/5)

  - かんたん Ruby (プログラミングの教科書) すがわらまさのり (著) 技術評論社 (2018/6/21)