Start The World's Best Introduction to TDD... free!

Comments

The 30-second version

  • Adding behavior confidently involves having fewer parts to change (low duplication), knowing which ones to change (high cohesion), ease of changing just the part you want to change (low coupling), and understanding how you’ve changed it (strong tests).
  • Adding behavior requires breaking an existing assumption.
  • In a well-factored design, we can easily find the one place we have made that assumption. (Otherwise, why bother refactoring?)
  • First, make room for the new code, then add it.
  • To make room for the new code, extract the existing code into a method whose name describes the generalisation we want to make, or the idea we want to introduce.
  • By making room for the new code, we make that code easier to reuse by reducing its dependence on its surrounding context.

Want to learn more?

The Details

We all want to add behavior to a system confidently, and I have observed that my confidence in adding behavior depends on two factors:

  • I know where to add code.
  • I understand the behavior of the code I am adding.

I use test-driven development as the main technique for handling the second of these two factors, but what about the first? I have uncovered a technique that I both use and teach, and I’d like to share that with you. I call this “making room for the new code”, naming it for a phrase I vaguely remember reading in one of the Grand Old XP books. (Did Kent Beck or Ron Jeffries write it? I can’t remember.) This technique helps me quickly find a reasonable first-draft place in the code base to put new code. After I have put the new code in place, and I feel confident that it does what I expect, I then use the Four Elements of Simple Design to guide me in refactoring to improve the design.

A premise

I start with the premise that adding behavior means breaking an assumption. By this I mean that whenever we add code to a system in order to extend its behavior, we have to falsify at least one assumption we’ve previously made. For example:

  • In a payroll system, in order to support a second cheque printer, we likely have to break the assumption that there is only one cheque format.
  • In a point of sale system, in order to support separate cash and card payment reports, we likely have to break the assumption that all “we made a sale” events look the same.
  • In a mobile phone monitoring system, in order to support billing by the second, we likely have to break the assumption that we only have to count the number of minutes a call lasted.

Some of these seem obvious, and others less so, and it bears emphasising that the specific assumption or assumptions we break depends heavily on what we’ve built so far and the way we articulate the soon-to-be-added behavior. Even so, I conjecture that for every behavior we want to add to a system, we can identify a non-empty list of assumptions that we need to break.

The technique

  1. Identify an assumption that the new behavior needs to break.
  2. Find the code that implements that assumption.
  3. Extract that code into a method whose name represents the generalisation you’re about to make.
  4. Enhance the extracted method to include the generalisation.

The less duplication you have in the system, the better this works, because duplicate code makes it difficult to find all the code that implements the assumption in question. Similarly, the more appropriate the names in your system, the better this works, because unsuitable names make it difficult to know which code implements the assumption in question, as opposed to something unrelated. You’ll notice that these points relate both to the Four Elements of Simple Design and to the core concepts of Coupling and Cohesion.

Yes, yes…

An example

We are building a point of sale system, and we’ve just decided to implement sales tax. I live in PEI, Canada, where not only do we exclude sales tax from the price, we have two sales taxes, and we charge the second one on top of the first one. For example:

A $125 item that attracts both GST (the “Goods and Services” tax) and PST (provincial sales tax) costs a total of $144.38. GST at 5% costs $6.25, then PST at 10% of $131.25 (= $125 + $6.25) costs $13.13. The total is $125 + $6.25 + $13.13 = $144.38.

Notice that this example implies that GST or PST might or might not apply to a given product, so even before we identify the old assumption to break, we need to note the new assumption we’ll make: we assume that all products attract both GST and PST. Our customers won’t like it, but the tax authorities will love it, and only they have the power to treat us guilty until proven innocent.

Our code has a class like this in it.