Chapter 3. Portfolio

Penny wise and Dollar foolish 1

Tired Proverb

We can multiply and divide amounts in any one currency by numbers. Now we need to add amounts in multiple currencies.

5 USD x 2 = 10 USD

10 EUR x 2 = 20 EUR

4002 KRW / 4 = 1000.5 KRW

5 USD + 10 EUR = 17 USD

1 USD + 1100 KRW = 2200 KRW

Remove redundant Money multiplication tests

In this chapter, we’ll deal with the mixed-mode addition of currencies.

Designing our next test

To test drive the next feature — 5 USD + 10 EUR = 17 USD — it’s enlightening to first sketch out how our program will evolve. TDD plays nicely with software design, contrary to prevailing myths!

The feature, as described in our feature list, says that 5 Dollars and 10 Euros should add up to 17 Dollars, assuming we get 1.2 Dollars for exchanging one Euro.

However, it’s equally true that:

1 EUR + 1 EUR = 2.4 USD

Or, rather obviously:

1 EUR + 1 EUR = 2 EUR

Ah: an epiphany! When we add two (or more) Money s, the result can be expressed in any currency, as long as we know the exchange rate between all currencies involved (i.e. from currency of each Money into the currency in which we want to express the result). This is true even if all the currencies involved are the same, as in the last example — which is just one particular case out of many.

Tip

Test-driven development gives us an opportunity to pause after each RGR cycle and design our code intentionally.

We realize that “Adding Dollars to Dollars results in Dollars” is an oversimplification. The general principle is that adding Money in different currencies gives us a Portfolio; which we can then express in any one currency (given the necessary Exchange Rates between currencies).

Did we just introduce a new entity: Portfolio? You bet! It’s vital to let our code reflect the realities of our domain. We’re writing code to represent a collection of stock holdings; for which the correct term is a Portfolio. 2

When we add two or more Money s, we should get a Portfolio. We can extend this domain model by saying that we should be able to evaluate a Portfolio in any specific currency. These nouns and verbs give us an idea about the new abstractions in our code which we’ll drive out through tests.

Tip

Analysis of the problem domain is an effective way to discover new entities, relationships, functions, and methods.

Given this new realization, let’s add the simpler case of adding two Money s in the same currency first, deferring the case of multiple currencies until later:

5 USD x 2 = 10 USD

10 EUR x 2 = 20 EUR

4002 KRW / 4 = 1000.5 KRW

5 USD + 10 USD = 15 USD

5 USD + 10 EUR = 17 USD

1 USD + 1100 KRW = 2200 KRW

Remove redundant Money multiplication tests

Let’s build this feature: adding Money s together. We’ll start with a test to add two Money s in the same currency, using the Portfolio as a new entity.

Go

Here’s our new test, TestAddition, which we add after the existing tests in money_test.go:

func TestAddition(t *testing.T) {
    var portfolio Portfolio
    var portfolioInDollars Money

    fiveDollars := Money{amount: 5, currency: "USD"}
    tenDollars := Money{amount: 10, currency: "USD"}
    fifteenDollars := Money{amount: 15, currency: "USD"}

    portfolio = portfolio.Add(fiveDollars)
    portfolio = portfolio.Add(tenDollars)
    portfolioInDollars = portfolio.Evaluate("USD")

    assertEqual(t, fifteenDollars, portfolioInDollars)
}

Notice that we have declared the portfolio and portfolioInDollars variables explicitly, to emphasize their types. The verbosity makes things clear to us as we proceed.

What our test says is:

  1. We can start with an empty Portfolio.

  2. We can then Add multiple Money structs to a Portfolio.

  3. We can ask the Portfolio to Evaluate itself in a specific currency.

  4. Finally, the result of the evaluation should be a Money with the correct amount and currency.

Of course, in our current simple case, the currency is always the same, so exchange rates don’t (yet) become a concern. Let’s walk before we run!

By now, we’re very accustomed to errors like undefined: Portfolio. Let’s speed ahead and implement the barest possible type Portfolio to get beyond these errors. Here’s what it looks like, added to the end of money_test.go:

type Portfolio []Money

func (p Portfolio) Add(money Money) Portfolio {
    return p
}

func (p Portfolio) Evaluate(currency string) Money {
    return Money{amount: 15, currency: "USD"}
}

We declare a new type Portfolio as an alias for a slice of Money s. We then define the two missing methods: Add and Evaluate. The signatures of these methods are suggested by the failing test we wrote. The implementation is the least possible code to get the test to pass — including the “silly” hard-coded Money that Evaluate returns.

In an earlier round of RED-GREEN-REFACTOR, we recognized the subtle duplication in the test and production code and used it change the “silly” implementation to a more correct one. Where is the duplication in this case? Yep: it’s the “15” that’s in both the test and production code.

We should replace the hard-coded 15 in Evaluate method with code that actually sums up the amount in the Money s:

func (p Portfolio) Evaluate(currency string) Money {
    total := 0.0
    for _, m := range p {
        total = total + m.amount
    }
    return Money{amount: total, currency: currency}
}

Hmm… our TestAddition fails with an assertion failure:

... Expected  [{amount:15 currency:USD}] Got: [{amount:0 currency:USD}]

Ah! We are iterating over an empty slice. We made the correct change to Evaluate, however, our Add method still has a trivial (“silly”) implementation. Let’s fix that, too:

func (p Portfolio) Add(money Money) Portfolio {
    p = append(p, money)
    return p
}

The test is now green.

We know that the value of currency in the Money struct returned by Evaluate is whatever was passed in as the first (and only) parameter to that method. This is obviously not the right implementation: it only works because our test uses two Money s that both have the same currency, and then calls Evaluate also with the same currency.

Should we test-drive our way to removing this “silly” behavior of our code, or use our “refactoring budget” (now that we have green test) to do it?

There is no one-size-fits-all answer. TDD allows us to define for ourselves how fast we want to go. In our case, we have good reason to defer fixing the “silly” behavior of our code.

We know that when we Evaluate a Portfolio containing Money s with different currencies, we’ll have to use exchange rates — a concept we haven’t defined yet. We also know that we have an item on our to-do list — 5 USD + 10 EUR = 17 USD — that will compel us to test-drive this mixed-currency feature. Therefore, we can defer the change for a bit: the “silly” implementation survives to see another day. Or maybe ten more minutes.

JavaScript

Here’s our new test for adding two Money s, which we add to the very end of test_money.js:

fifteenDollars = new Money(15, "USD");
portfolio = new Portfolio();
portfolio.add(fiveDollars, tenDollars);
assert.deepStrictEqual(portfolio.evaluate("USD"), fifteenDollars);

What our test says is:

  1. We start with an empty Portfolio object.

  2. We then Add multiple Money objects to this Portfolio. We use our pre-existing fiveDollars and tenDollars objects.

  3. We ask the Portfolio to evaluate itself in a specific currency.

  4. Finally, the result of the evaluation should be a Money object with the correct amount and currency.

In this test case, the currency is the same throughout, so exchange rates don’t yet become a concern.

By now, we’re very accustomed to errors like ReferenceError: Portfolio is not defined. Let’s speed ahead and implement the barest possible class Portfolio to get beyond the errors and a quick passing test.

class Portfolio {
    add(money) {
    }

    evaluate(currency) {
        return new Money(15, "USD")
    }
}

We define a new Portfolio class below the pre-existing Money class in test_money.js. We give it the two methods our test demands: add and evaluate. The signatures of these methods are also evident from our test. In evaluate, we implement the quickfire solution that gets our test to pass: always return a Money object representing “15 USD”.

In an earlier round of RED-GREEN-REFACTOR, we recognized the subtle duplication in the test and production code and used it change the trivial (“silly”) implementation to a more correct one. Where is the duplication in this case? Yep: it’s the “15” that’s in both the test and production code.

Now that our tests pass, we should replace the hard-coded 15 in evaluate method with code that actually sums up the amount in the Money s:

    evaluate(currency) {
        let total = this.moneys.reduce( (sum, money) => {
            return sum + money.amount;
          }, 0);
        return new Money(total, currency);
    }

We use the reduce function for an array. We declare an anonymous function that adds up the amount of each Money object, thereby reducing the array this.moneys to a single scalar value. We then create a new Money object with this total and the given currency and return it.

Tip

ES6 Arrays are list-like objects whose prototype defines methods like map, reduce, and filter to facilitate a functional programming style.

The evaluate function, predictably, results in an error:

        let total = this.moneys.reduce( (sum, money) => {
                                ^
TypeError: Cannot read property 'reduce' of undefined

Let’s define the missing this.moneys array in a new constructor in the Portfolio class:

     constructor() {
         this.moneys = [];
    }

After adding the constructor, we get an interesting assertion error:

AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
+ actual - expected

  Money {
+   amount: 0,
-   amount: 15,
    currency: 'USD'
  }

Ah! We are iterating over an empty array. Our evaluate method and constructor are correct, however, our add method is still empty. Let’s rectify this shortcoming. We’ll use the implicit arguments object to allow multiple Money s to be added simultaneously:

    add() {
        this.moneys = this.moneys.concat(Array.prototype.slice.call(arguments));
    }

The test is now green.

Python

Here’s our new test for the addition of two Money objects, which we append to our growing list of tests in TestMoney class:

  def testAddition(self):
    fiveDollars = Money(5, "USD")
    tenDollars = Money(10, "USD")
    fifteenDollars = Money(15, "USD")
    portfolio = Portfolio()
    portfolio.add(fiveDollars, tenDollars)
    self.assertEqual(fifteenDollars, portfolio.evaluate("USD"))

What our test says is:

  1. We start with an empty Portfolio object.

  2. We then Add multiple Money objects to this Portfolio.

  3. We ask the Portfolio to evaluate itself in a specific currency.

  4. Finally, the result of the evaluation should be a Money object with the correct amount and currency.

In this test case, the currency is always the same, so exchange rates don’t yet become a concern.

We’re now quite accustomed to errors like NameError: name 'Portfolio' is not defined. Let’s speed ahead and implement the smallest possible class Portfolio to get beyond these errors and a passing test. We add the new class after the Money class definition in test_money.py.

class Portfolio:
    def add(self, *moneys):
        pass

    def evaluate(self, currency):
        return Money(15, "USD")

The Portfolio class has a no-op add method, and an evaluate method with a “silly” implementation that always returns a Money object that’s worth “15 USD”. Just enough code to get a passing test.

In an earlier round of RED-GREEN-REFACTOR, we recognized the subtle duplication in the test and production code and used it change the trivial (“silly”) implementation to a more correct one. Where is the duplication here? Yep: it’s the “15” that’s in both the test and production code.

We can replace the hard-coded 15 in evaluate method with code that actually sums up the amount in the Money s:

import functools 1
import operator 2
...
class Portfolio:
...
    def evaluate(self, currency):
        total = functools.reduce(
            operator.add, map(lambda m: m.amount, self.moneys))
        return Money(total, currency)
1

The functools package gives us the reduce function

2

The operator package gives us the add function

This code uses Python’s functional programming idioms. The best way to understand how total is derived is to unravel the expression from the inside out:

  1. We import the packages we need: functools and operator.

  2. Using a lambda expression, we map the self.moneys array to a map of only the amount s in each Money object.

  3. We then reduce this map to a single scalar value, using the operator.add operation.

  4. We assign this scalar value to the variable named total.

  5. We finally create a new Money object using this total and the currency passed in the first (and only) parameter to the evaluate method.

Phew: that one line of functional code sure packs a lot of punch!

Tip

Python has rich support for functional programming, including map, reduce, and filter in the functools package; and custom-written lambda functions.

We’re not done yet: when we run our test, the error message AttributeError: 'Portfolio' object has no attribute 'moneys' reminds us of that. Let’s add an __init__ method that initializes this missing attribute in Portfolio:

    def __init__(self):
        self.moneys = []

Ah: this gives us a new error! TypeError: reduce() of empty sequence with no initial value. We realize two things:

  1. The add method in Portfolio is still a no-op. That’s why our self.moneys is an empty array; and

  2. Notwithstanding the above problem, our code should still work with an empty array.

We fix these two shortcomings by the following code changes in Portfolio:

    def add(self, *moneys):
        self.moneys.extend(moneys)

    def evaluate(self, currency):
        total = functools.reduce(
            operator.add, map(lambda m: m.amount, self.moneys), 0) 1
        return Money(total, currency)
1

The last parameter to reduce (0 in our case) is the initial value of the accumulated result

We give the add method its correct implementation: it accumulates any given Money s in the self.moneys array. And we add an initial value of 0 to our call to functools.reduce. This ensures that the code works even when there is an empty array.

All tests are now green.

Committing our changes

We have the addition feature implemented for Money s in the same currency. This suggests the appropriate message for our next commit to our local Git repository:

git add .
git commit -m "feat: addition feature for Moneys in the same currency done"

We now have three commits in our Git repository.

Where We Are

We started to tackle the problem of adding different representations of Money. This new feature requires us to introduce a new entity to our code, which we named Portfolio. Addition of Money s also requires introduction of exchange rates. Since that is too much to take on all at once, we used a divide-and-conquer strategy to first add two Money s and evaluate the value of the Portfolio all in the same currency. This allows us to gently introduce the concepts of Portfolio and addition of Money s.

This divide-and-conquer strategy means our Portfolio is far from finished. It needs to be enhanced to evaluate correctly when the Money s in it have different currencies, as well as when the currency of evaluation is different.

Also, we can’t help noticing that our source code is growing as we accrete tests and features. No surprises there! However, it’s getting a bit too long to all be in one file. We need to restructure our code: separating the test code from the production code would be a good start.

For now, let’s take a deep breath and celebrate crossing one more item from our feature list, before we pick up the next item.

5 USD x 2 = 10 USD

10 EUR x 2 = 20 EUR

4002 KRW / 4 = 1000.5 KRW

5 USD + 10 USD = 15 USD

5 USD + 10 EUR = 17 USD

1 USD + 1100 KRW = 2200 KRW

Remove redundant Money multiplication tests

1 Or, to unfurl the brows of my many and dear British friends, “Penny wise and Pound foolish”!

2 Are there other entities that we should also have in addition to “Money”? Possibly. However, the “Money” abstraction meets our current needs. We’ll add one more entity later in Chapter 11, when its time comes.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset