Tambourine作業メモ

主にスキル習得のためにやった作業のメモ。他人には基本的に無用のものです。

ケント・ベックの「テスト駆動開発」の写経をRustでやってみる(9)

第8章に入る。

この辺りから、本のコードと自分のコードの差が大きくなって、 本の戦略がそのまま使えない感じになってきた。ちょっと試行錯誤しながら進めたい。

まず、本の方ではtimesメソッドがDollarとFrancの両方にあるので、それをまとめたいと言っている。 ところが、それはもうやっちゃった(笑)。 この先の行く末としてはサブクラスをなくして、Moneyに集約していきたいのかなと思っている。

本の方では、MoneyにDollarやFrancの生成の機能を持たせようとしていて、 テストコードで

Dollar five = new Dollar(5);

としているところを、

Money five = Money.dollar(5);

とFactory Methodで生成するようにして、テストコードにあらわにDollarクラスが出てこないようにしようとしている。

意図はわかる。こうしていって、テストコードにDollarが出てこないようにしておいて、しれっとDollarクラスを消しても グリーンが維持されれば理想的だ。Factory Methodはこんな感じ。

class Money {
    static Dollar dollar(int amount) {
        return new Dollar(amount);
    }
}

同じ事をRustの方でもできるのか。Moneyはトレイトだが実装は持てる。 Moneyトレイトのメソッドが、Dollarを返すことをは許されるのか。

trait Money {
    fn new(i: i32) -> Self;

    // Factory Method
    fn dollar(i: i32) -> Dollar {
        Dollar::new(i)
    }
    fn franc(i: i32) -> Franc {
        Franc::new(i)
    }

これは別にエラーにならない。へー。

ところが、テストコード側はエラーになる。

例えば、このようにMoney::francメソッドを呼び出すと、

#[test]
fn test_franc_multiplication() {
    let five = Money::franc(5);
    assert_eq!(Franc::new(10), five.times(2));
    assert_eq!(Franc::new(15), five.times(3));
}

このように、型アノーテーションが必要だと怒られる。

error[E0283]: type annotations needed
  --> src/main.rs:71:20
   |
11 |     fn franc(i: i32) -> Franc {
   |     ------------------------- required by `Money::franc`
...
71 |         let five = Money::franc(5);
   |                    ^^^^^^^^^^^^ cannot infer type
   |
   = note: cannot resolve `_: Money`

こうするのは問題にならない。が、そういうことじゃない(笑)。

#[test]
fn test_franc_multiplication() {
    let five = Franc::franc(5);
    assert_eq!(Franc::new(10), five.times(2));
    assert_eq!(Franc::new(15), five.times(3));
}

これはつまり、このタイミングでMoneyをトレイトから構造体に格上げしてやる必要があると言うことなんだろう。