Search code examples
ooptdd

Why does class Money extend Expression in Kent Beck's TDD by Example?


I'm studying TDD by Example and up so far I'm finding it a great book. But there's a point where he tells us to write:

// in class Money:
Expression plus(Money addend) {
    return new Money(amount + addend.amount, currency);
}

Which won't build unless we declare:

class Money implements Expression {...

This doesn't really make sense to me. Author created Expression as an interface for Sum and Money has nothing in common with Sum. Later on he adds method reduce() in common to both classes, but reduce in Money merely returns this.

Making Money implement Expression is only the path of least effort for removing the error from the plus() method, but it fills the code with unnecessary information (it will have to implement reduce() because of this decision) and increases entropy.

I haven't given it much of a thought, but wouldn't it be cleaner to do something such as this?

class Money {        
    Money plus(Money addend) {
        return new Money(amount + addend.amount, currency).reduce();
    }
}
// edited this, it previously returned Expression

Edit: In the following chapter, author implements a reduce() method in another class (named Bank), which converts Money in between currencies. I still find this to be a weird solution, Sum and Expression names imply we should have a conversion expression class for this task instead. What the author is likely intending to do is to use recursion to add money in different currencies. Either way, it seems to me that he did some far-ahead planning which seems incompatible with TDD as is presented in the book.


Solution

  • On planning ahead

    TDD doesn't prohibit planning ahead. The process is about getting rapid feedback on your plans instead of wasting days (or weeks) creating elaborate plans, only to see them 'not survive contact with reality' (to paraphrase Helmuth von Moltke). It's okay to think ahead.

    Still, Kent Beck reveals in chapter 17 that this isn't his first rodeo:

    "I have programmed money in production at least three times that I can think of. I have used it as an example in print another half-dozen times. I have programmed it live on stage [...] another fifteen times. I coded another three or four times preparing for writing [...] Then, while I was writing this, I thought of using expression as a metaphor and the design went in a completely different direction than before."

    So, if you think that he's cheating: yes, he is. He's open about it, though. I think that the motivation was to present a compelling example. He also writes that it was in part based on early reviews of the book.

    On the API

    That doesn't explain why the code looks like it does, but there's a reason for that. It's actually a nice API.

    Why can't you write something like return new Expression(amount + addend.amount, currency).reduce()?

    You can't because the reduce method isn't nullary. It takes arguments. You must supply both a bank (which holds the currency conversion rates) and a destination currency.

    Keep in mind the problem being solved by the code. People get this wrong all the time, and I think that Kent Beck (inadvertently) fuelled the confusion by naming the example Money.

    The problem isn't to model money, but to model investment portfolios in different currencies. If you have a portfolio of 25.000 USD and 10.000 CHF, reducing it to a single currency hides important details. With a portfolio in multiple currencies you diversify risk. Portfolio owners will want to see more than one view of their portfolios. Sometimes, they want to see the portfolio grouped by currency, and at other times they want to see 'the current total worth' of the portfolio presented in a single currency.

    The API in the book enables both views.

    The reason the underlying 'metaphor' is called an expression is that the API is just a specialised expression tree. It turns out fairly well, though, because it's lawful. Restricted to the sub-types presented in the book, it informally gives rise to a monoid.