A fundamental design move
Pardon me if the content of this post looks obvious to you. It took me years to understand this!
When doing incremental design, I found that there is one fundamental move that makes design emerge. It is
- I recognize that one thing has more than one responsibility;
- then I separate the responsibilities by splitting the thing into two or more smaller things;
- and I connect the smaller things with the proper degree of coupling.
For instance, in the description of the domain of the game Monopoly we have this sentence:
A player takes a turn by rolling two dice and moving around the board accordingly.
A straightforward translation of this into code is
class Player { // ... public void takeTurn() { int result = Math.random(6) + Math.random(6); this.position = (this.position + result) % 40; } }
Can you see that Player#takeTurn() does two things? You can see this from the beginning by the wording: “by rolling two dice *and* moving around the board”.
You can also see it when you try to write the test:
@Test public void playerTakesTurn() { Player player = new Player(); assertEquals(0, player.position()); // initial position player.takeTurn(); assertEquals(???, player.position()); // how should I know??? }
We can’t write the last assertion because we have no control over the dice.
The standard way to solve this is to move the responsibility of extracting a random result to a separate class.
@Test public void playerTakesTurn() { Dice dice = new Dice(); Player player = new Player(dice); //... } class Player { public void takeTurn() { int result = this.dice.roll(); this.position = (this.position + result) % 40; } }
This still does not solve our problem, since Dice#roll() will still produce a random result that we have no control over. But now we have the option of making the coupling between Player and Dice weaker, by making Dice an interface and passing a fake Dice implementation that will return the result that we want:
@Test public void playerTakesTurn() { Dice dice = new FakeDiceReturning(7); Player player = new Player(dice); assertEquals(0, player.position()); // initial position player.takeTurn(); assertEquals(7, player.position()); // now it's easy }
This design move, that is almost forced by the need to write a clear test, has a number of advantages:
- Now we have the option to pass different dice implementation. If the rules ever called for using different kinds of dice (for instance, 8-sided dice) we would not need to change the Player class at all.
- Now we can test the Dice implementation separately. We might thus discover the bug in the above random number generation code. (did you see it?) :-)
So, to summarize, the fundamental design move is in three steps:
- You realize a portion of code has two responsibilities, perhaps because the test is hard to write.
- You separate the portion of code in two parts; this is usually some form of “extract method“.
- You’re not finished yet, because you still have to decide what kind of coupling you want between the two parts. Usually, a simple extract method will produce a coupling that is too tight. More work is required to ease the coupling to the degree that you want.
It is simply a consequence of the Single Responsibility Principle; but it can also be seen as the or as the application of the “clarity of intent” rule in Kent Beck’s 4 Rules of Simple Design.
Now the interesting thing is that this design move applies not just to methods, but to classes, modules, services and applications! Whenever you see two things inside one, separate them. If you applied the right degree of coupling, you will end up with a system that is simpler.