Evolutionary Design and TDD why are they related?.
Once I published my Evolutionary Design blog post, I received some feedback about why TDD was related to it. It’s true that there are different levels when we talk about Evolutionary Design (emergent design). Nowadays Agile companies change their software designs in each feature they introduce. This is normally done in a Design Up Front style, so thinking how to solve the whole feature before coding it.
In fact there are two scenarios when you have to implement a feature:
You know how to implement one feature because you did a lot of times in the past, so you don’t need to infer what is the correct design.
You don’t know how to solve the problem, and you don’t know how the design is going to be.
In my experience the first thing happens from time to time while the second one is the rule. Evolutionary Design can help you on the second case, basically Evolutionary Design is trying to find the design of your system while you are working to find how your system behaves.
So we have more levels of granularity than the feature:
User Stories
Scenarios in the user stories
Tasks
Is it possible to do Evolutionary Design through scenarios?.
To solve the whole feature we need to solve all the user stories and to solve them we need to solve all the their scenarios. If we are able to solve each scenario once after the other we are evolving the design more frequently. Small steps are always better because have less risks and we can obtain early feedback from them.
Anyway it’s clear that to change things all the time you need a fast way to know if you are introducing bugs. You will say, wait, but it’s enough having tests for doing this, I agree. So we need tests to evolve, to have confidence on our changes.
We still need to plan the next scenario, so to understand what to plan we first need to understand what to do. Usually the scenarios of the user stories come with some acceptance criteria, in fact these acceptance criteria are describing tests that we can use to know if our software achieve the solution of the problem. So writing tests are a way to know if we are understanding the problem (that is a step before planing how to solve the problem).
And the last thing is planning how to solve it. But wait, if the scenarios are smalls enough, and I select the correct order what do I have to plan?. I just need to accommodate my code for the next scenario.
What I’ve described is what TDD tries to solve:
think what is the next simpler problem to solve
create a test to understand the problem to solve
create the code to pass the test
improve your design
Start again
You don’t need TDD to emerge your design, but you need to solve these problems, or you will have a chaotic codebase. TDD solves the problems I described, that’s the reason I think it’s the easiest way to achieve Evolutionary Design.
The problem
We are going to use the Bowling Kata in this example, and we will do it in Java following TDD:
The game consists of 10 frames. In each frame the player has two rolls to knock down 10 pins. The score for the frame is the total number of pins knocked down, plus bonuses for strikes and spares.
A spare is when the player knocks down all 10 pins in two rolls. The bonus for that frame is the number of pins knocked down by the next roll.
A strike is when the player knocks down all 10 pins on his first roll. The frame is then completed with a single roll. The bonus for that frame is the value of the next two rolls.
In the tenth frame a player who rolls a spare or strike is allowed to roll the extra balls to complete the frame. However no more than three balls can be rolled in tenth frame.
Requirements
Write a class
Game
that has two methods
void roll(int)
is called each time the player rolls a ball. The argument is the number of pins knocked down.
int score()
returns the total score for that game.
Ok, understood. Now the first thing should be finding smaller problems that guide us to solve the whole thing. It has no value at all trying to write a test to solve the whole problem because in that way we need to find the correct solution the first attempt and that’s very difficult.
It’s clear that you don’t need to play ten frames to ask for the score. So I can ask for the score in each frame we have played. This is a way to increment the problem, our first test could be:
@Test
public void noFrame(){
Game bowling = new Game();
assertThat(bowling.score(), is(0));
}
public class Game {
private int totalScore = 0;
public int score() {
return totalScore;
}
public void roll(int pins) {
totalScore = pins;
}
}
To pass this test I will need to create the Game class and the score method. By now it is enough returning 0 to solve it.
The next thing to do should be playing one roll in one frame and see the result.
@Test
public void oneFrameOneRoll(){
Game bowling = new Game();
bowling.roll(1);
assertThat(bowling.score(), is(1));
}
Now the Game will need to change to return the pins knocked down.
public class Game {
private int totalScore = 0;
public int score() {
return totalScore;
}
public void roll(int pins) {
totalScore = pins;
}
}
But one frame has two rolls, let’s try with that scenario and see how to solve it.
@Test
public void oneFrameTwoRolls(){
Game bowling = new Game();
bowling.roll(1);
bowling.roll(1);
assertThat(bowling.score(), is(2));
}
Now we need to add the pins knocked down to the final score.
public class Game {
private int totalScore = 0;
public int score() {
return totalScore;
}
public void roll(int pins) {
totalScore += pins;
}
}
Ok, let’s force to create a way to manage spares, this will be our next small problem to solve. If we do a spare in the first frame we need to add a bonus to the next roll.
@Test
public void twoFramesFirsIsSpare(){
Game bowling = new Game();
bowling.roll(9);
bowling.roll(1);
bowling.roll(1);
bowling.roll(1);
assertThat(bowling.score(), is(13));
}
To manage spares, we need to create something, a frame comes to my mind (it is inside the description of the problem).
public class Game {
private Frame frame0 = new Frame();
private Frame frame1 = new Frame();
public int score() {
return frame0.score()+frame1.score();
}
public void roll(int pins) {
if(frame0.isFrameOpen()){
frame0.roll(pins, false);
}else{
frame1.roll(pins, frame0.isSpare());
}
}
}
public class Frame {
private int roll0 = 0;
private int roll1 = 0;
private int rollsPlayed = 0;
public void roll(int pins, boolean previousFrameIsSpare) {
if (rollsPlayed == 0) {
roll0 = previousFrameIsSpare ? 2*pins : pins;
} else {
roll1 = pins;
}
rollsPlayed++;
}
public int score() {
return roll0 + roll1;
}
public boolean isFrameOpen() {
return rollsPlayed < 2;
}
public boolean isSpare() {
return roll0 + roll1 == 10 && rollsPlayed==2;
}
}
I needed to create the Frame class to support playing two frames and having a bonus when the first frame is a spare. This is a big step because once we were able to create the Frame class we needed to manage the bonus logic for the spare. So It was easier to refactor first the Game class to easily create the Frame concep then introduce the new feature (refactor when we are in a green status).
It’s clear that we will need one variable for each frame. So let’s refactor our code to use an array instead of two variables to represent the frames.
public class Game {
private Frame frames[] = IntStream.range(0, 2).mapToObj(i -> new Frame()).toArray(Frame[]::new);
public int score() {
return Arrays.stream(frames).mapToInt(Frame::score).sum();
}
public void roll(int pins) {
Frame previousFrame = new Frame();
for (Frame frame : frames) {
if (frame.isFrameOpen()) {
frame.roll(pins, previousFrame.isSpare());
return;
}
previousFrame = frame;
}
}
}
Now our game support only two frames, let’s allow ten frames without bonus.
@Test
public void tenFrames() {
Game bowling = new Game();
for(int i=0; i<20; i++){
bowling.roll(1);
}
assertThat(bowling.score(), is(20));
}
public class Game {
private final Frame[] frames = IntStream.range(0, 10).mapToObj(i -> new Frame()).toArray(Frame[]::new);
public int score() {
int result = 0;
boolean isSpare = false;
for (Frame frame : frames) {
result += frame.score(isSpare);
isSpare = frame.isSpare();
}
return result;
}
public void roll(int pins) {
for (Frame frame : frames) {
if (frame.isOpen()) {
frame.roll(pins);
return;
}
}
}
}
So we changed the number of frames created, now we need to iterate over the array until we find an open frame to play a roll. The score will be the sum of the scores, and we will pass if the previous frame was a spare to add the bonus.
I prefer a list than an array, so let’s do an small refactor here.
public class Game {
private final List<Frame> frames = IntStream.range(0, 10).mapToObj(i -> new Frame()).collect(Collectors.toList());
public int score() {
return frames.stream().mapToInt(Frame::score).sum();
}
public void roll(int pins) {
Frame previousFrame = new Frame();
for (Frame frame : frames) {
if (frame.isFrameOpen()) {
frame.roll(pins, previousFrame.isSpare());
return;
}
previousFrame = frame;
}
}
}
We have spares but not squares, so let’s try to introduce two frames with an square to have a bonus.
@Test
public void twoFramesFirstOneIsSquare() {
Game bowling = new Game();
bowling.roll(10);
bowling.roll(1);
bowling.roll(1);
assertThat(bowling.score(), is(14));
}
So we can do the same we did for the spare but for the square.
public class Game {
private final List<Frame> frames = IntStream.range(0, 10).mapToObj(i -> new Frame()).collect(Collectors.toList());
public int score() {
return frames.stream().mapToInt(Frame::score).sum();
}
public void roll(int pins) {
Frame previousFrame = new Frame();
for (Frame frame : frames) {
if (frame.isFrameOpen()) {
frame.roll(pins, previousFrame.isSpare(), previousFrame.isSquare());
return;
}
previousFrame = frame;
}
}
}
We use the previous frame to know if we need to apply a bonus in the current frame.
public class Frame {
private int roll0 = 0;
private int roll1 = 0;
private int rollsPlayed = 0;
public void roll(int pins, boolean previousFrameIsSpare, boolean previousFrameIsSquare) {
if (rollsPlayed == 0) {
roll0 = (previousFrameIsSpare || previousFrameIsSquare) ? 2*pins : pins;
} else {
roll1 = previousFrameIsSquare ? 2*pins : pins;
}
rollsPlayed++;
}
public int score() {
return roll0 + roll1;
}
public boolean isFrameOpen() {
return rollsPlayed < 2 && (roll0<10);
}
public boolean isSpare() {
return roll0 + roll1 == 10 && rollsPlayed==2;
}
public boolean isSquare() {
return roll0 == 10 && rollsPlayed==1;
}
}
Taking a look to the code it is clear that this is not going to work for several consecutive squares because we need to remember the last two, even is not going to work if we have two bonus to apply. But we need to improve the current design to allow simplify the next step, perhaps it’s time to introduce the Bonus concept.
We can start to introduce the bonus in the game so we can use it to calculate when it is needed to apply a bonus.
public class Game {
private final List<Frame> frames = IntStream.range(0, 10).mapToObj(i -> i < 9 ? new NormalFrame() : new TenthFrame()).collect(Collectors.toList());
private Bonus bonus = new Bonus(0);
public int score() {
return frames.stream().mapToInt(Frame::score).sum();
}
public void roll(int pins) {
for (Frame frame : frames) {
if (frame.isFrameOpen()) {
frame.roll(pins, bonus);
bonus = Bonus.newBonus(bonus, frame);
return;
}
}
}
}
public class Frame implements Frame {
private int roll0 = 0;
private int roll1 = 0;
private int rollsPlayed = 0;
@Override
public void roll(int pins, Bonus bonus) {
if (rollsPlayed == 0) {
roll0 = calculateScore(pins, bonus);
} else {
roll1 = calculateScore(pins, bonus);
}
rollsPlayed++;
}
private int calculateScore(int pins, Bonus bonus) {
return bonus.applies() ? 2 * pins : pins;
}
@Override
public int score() {
return roll0 + roll1;
}
@Override
public boolean isFrameOpen() {
return rollsPlayed < 2 && (roll0 < 10);
}
@Override
public boolean isSpare() {
return roll0 + roll1 == 10 && rollsPlayed == 2;
}
@Override
public boolean isSquare() {
return roll0 == 10 && rollsPlayed == 1;
}
}
We are starting with a bonus of zero that is like not having bonus and moving all the logic to know if we need to apply the bonus to the Bonus class. Here I noticed that roll0 and roll1 was not going to be enough to calculate if the frame is spare or square and also the score of that roll, this is outside our current step but we can take into account for next features.
public class Bonus {
private int applyForThisNumberOfRolls;
private int numberOfRollsConsumed;
public Bonus(int applyForThisNumberOfRolls) {
this.applyForThisNumberOfRolls = applyForThisNumberOfRolls;
this.numberOfRollsConsumed = 0;
}
public boolean applies() {
return numberOfRollsConsumed < applyForThisNumberOfRolls;
}
public void consumeRoll() {
this.numberOfRollsConsumed++;
}
public static Bonus newBonus(Bonus currentBonus, Frame frame){
Bonus bonus = currentBonus;
bonus.consumeRoll();
if (frame.isSpare()) {
bonus = new Bonus(1);
} else if (frame.isSquare()) {
bonus = new Bonus(2);
}
return bonus;
}
}
The bonus depends on how many rolls apply and the number of rolls played with the bonus activated, so we can use it instead of the isSpare isSquare parameters.
Now, let’s try with three consecutive squares.
@Test
public void threeFramewWithSquaresInAllOfThem() {
Game bowling = new Game();
for (int i = 0; i < 3; i++) {
bowling.roll(10);
}
assertThat(bowling.score(), is(60));
}
Our fears appear now, we need to have a way to remember the rolls knocked down (to implement the isSpare, isSquare methods) and the scores of those rolls with bonus to be able to calculate the total score.
public class Frame {
private final int[] scores;
protected final int[] rolls;
protected int rollsPlayed = 0;
public Frame() {
scores = new int[2];
rolls = new int[2];
for (int i = 0; i < 2; i++) {
scores[i] = 0;
rolls[i] = 0;
}
this.rollsPlayed = 0;
}
public void roll(int pins, Bonus bonus) {
rolls[rollsPlayed] = pins;
scores[rollsPlayed] = bonus.calculateScore(pins);
rollsPlayed++;
}
public int score() {
return stream(scores).sum();
}
public boolean isFrameOpen() {
return rollsPlayed < 2 && (rolls[0] < 10);
}
public boolean isSpare() {
return rolls[0] + rolls[1] == 10 && rollsPlayed == 2;
}
public boolean isSquare() {
return rolls[0] == 10 && rollsPlayed == 1;
}
public int getRollsPlayed() {
return rollsPlayed;
}
}
In the refactor phase of this step we also changed roll0 and roll1 by an array, we took the same approach for the scores array. Previously the scores and the rolls occupy the same concept, now after this step they have been splited in two.
But the test doesn’t pass because we need to apply two bonus on the third step. This forces us to create another concept the Multiple Bonus.
public class MultipleBonus implements Bonus {
private final Bonus firstBonus;
private final Bonus secondBonus;
public MultipleBonus(Bonus firstBonus, Bonus secondBonus) {
this.firstBonus = firstBonus;
this.secondBonus = secondBonus;
}
@Override
public boolean applies() {
return firstBonus.applies() || secondBonus.applies();
}
@Override
public void consumeRoll(int rollsPlayed) {
if (firstBonus.applies()) {
firstBonus.consumeRoll(rollsPlayed);
}
if (secondBonus.applies()) {
secondBonus.consumeRoll(rollsPlayed);
}
}
public int calculateScore(int pins) {
int result = 0;
if (firstBonus.applies()) {
result = firstBonus.calculateScore(pins);
}
if (secondBonus.applies()) {
result += secondBonus.calculateScore(pins) - pins;
}
return result;
}
}
But to introduce this new concept in our program we need to extract from Bonus an interface and change the Bonus class to SingleBonus.
public interface Bonus {
static Bonus newBonus(Bonus currentBonus, Frame frame) {
Bonus bonus = currentBonus;
if (frame.isSpare()) {
bonus = newBonus(currentBonus, spareBonus());
} else if (frame.isSquare()) {
bonus = newBonus(currentBonus, squareBonus());
}
return bonus;
}
static Bonus newBonus(Bonus currentBonus, SingleBonus singleBonus) {
return currentBonus.applies() ? new MultipleBonus(currentBonus, singleBonus) : singleBonus;
}
static SingleBonus squareBonus() {
return new SingleBonus(2);
}
static SingleBonus spareBonus() {
return new SingleBonus(1);
}
boolean applies();
void consumeRoll(int rollsPlayed);
int calculateScore(int pins);
}
public class SingleBonus implements Bonus {
private int applyForThisNumberOfRolls;
private int numberOfRollsConsumed;
public SingleBonus(int applyForThisNumberOfRolls) {
this.applyForThisNumberOfRolls = applyForThisNumberOfRolls;
this.numberOfRollsConsumed = 0;
}
@Override
public boolean applies() {
return numberOfRollsConsumed < applyForThisNumberOfRolls;
}
@Override
public void consumeRoll(int rollsPlayed) {
this.numberOfRollsConsumed += rollsPlayed;
}
@Override
public int calculateScore(int pins) {
return this.applies() ? 2 * pins : pins;
}
}
public class Game {
private final List<Frame> frames = IntStream.range(0, 10).mapToObj(i -> i < 9 ? new Frame(2) : new TenthFrame()).collect(Collectors.toList());
public int score() {
return frames.stream().mapToInt(Frame::score).sum();
}
public void roll(int pins) {
Bonus bonus = new SingleBonus(0);
for (Frame frame : frames) {
bonus.consumeRoll(frame.getRollsPlayed());
if (frame.isFrameOpen()) {
frame.roll(pins, bonus);
return;
}
bonus = Bonus.newBonus(bonus, frame);
}
}
}
I realized that having bonus like a global attribute was not a good idea and we also need a way to consume the rolls applied for the frame we are working. So calculating the bonus that applies for the open frame is an iterative process consuming all rolls of the previous closed frames plus the rolls played in the open frame. That’s why the bonus is moved inside the roll method and calculated in each iteration, depending on the frame it is created a new bonus. If there is no square or spare in the last frame played then we continue with the bonus we have before.
I applied the factory pattern inside Bonus to put here the logic to decide if we need to create a multiple or a single bonus instance.
I was thinking that doing four squares worked fine but not.
@Test
public void fourFramesWithSquaresInAllOfThem() {
Game bowling = new Game();
for (int i = 0; i < 4; i++) {
bowling.roll(10);
}
assertThat(bowling.score(), is(90));
}
My multiple bonus were not taking into account that if we don’t apply the first bonus the second bonus must be applied and I cannot substract the pins calculated in the score of the first bonus because it is not calculated.
public class MultipleBonus implements Bonus {
private final Bonus firstBonus;
private final Bonus secondBonus;
public MultipleBonus(Bonus firstBonus, Bonus secondBonus) {
this.firstBonus = firstBonus;
this.secondBonus = secondBonus;
}
@Override
public boolean applies() {
return firstBonus.applies() || secondBonus.applies();
}
@Override
public void consumeRoll(int rollsPlayed) {
if (firstBonus.applies()) {
firstBonus.consumeRoll(rollsPlayed);
}
if (secondBonus.applies()) {
secondBonus.consumeRoll(rollsPlayed);
}
}
public int calculateScore(int pins) {
int result = 0;
if (firstBonus.applies()) {
result = firstBonus.calculateScore(pins);
}
if (secondBonus.applies()) {
result += secondBonus.calculateScore(pins) - (firstBonus.applies() ? pins: 0);
}
return result;
}
}
The tenth frame is special, let’s focus on it and see the rules. But before we need a way to extend the Frame to have different behaviours, without adding any behaviour we can refactor the Frame to follow the template pattern to have two implementations
public abstract class Frame {
private final int[] scores;
protected final int[] rolls;
protected int rollsPlayed = 0;
public Frame(int numberOfRollsAllowed) {
scores = new int[numberOfRollsAllowed];
rolls = new int[numberOfRollsAllowed];
for (int i = 0; i < numberOfRollsAllowed; i++) {
scores[i] = 0;
rolls[i] = 0;
}
this.rollsPlayed = 0;
}
public void roll(int pins, Bonus bonus) {
rolls[rollsPlayed] = pins;
scores[rollsPlayed] = bonus.calculateScore(pins);
rollsPlayed++;
}
public int score() {
return stream(scores).sum();
}
public int getRollsPlayed() {
return rollsPlayed;
}
public abstract boolean isFrameOpen();
public abstract boolean isSpare();
public abstract boolean isSquare();
}
public class NormalFrame extends Frame{
public NormalFrame() {
super(2);
}
@Override
public boolean isFrameOpen() {
return rollsPlayed < 2 && (rolls[0] < 10);
}
@Override
public boolean isSpare() {
return rolls[0] + rolls[1] == 10 && rollsPlayed == 2;
}
@Override
public boolean isSquare() {
return rolls[0] == 10 && rollsPlayed == 1;
}
}
Now it will be easy to fix this new problem
@Test
public void tenFramesOneSpareTenthFrame() {
Game bowling = new Game();
for(int i=0; i<18; i++){
bowling.roll(1);
}
bowling.roll(9);
bowling.roll(1);
bowling.roll(1);
assertThat(bowling.score(), is(29));
}
Tenth frame is like the Frame but with 3 allowed rolls depending on some cases.
public class TenthFrame extends Frame {
public TenthFrame() {
super(3);
}
@Override
public boolean isFrameOpen() {
return (rollsPlayed == 2 && (rolls[0] + rolls[1] >= 10)) || rollsPlayed < 2;
}
@Override
public boolean isSpare() {
return false;
}
@Override
public boolean isSquare() {
return false;
}
}
With this extension we just need to change the Game to create the tenth frame with our new implementation.
public class Game {
private final List<Frame> frames = IntStream.range(0, 10).mapToObj(i -> i < 9 ? new NormalFrame() : new TenthFrame()).collect(Collectors.toList());
public int score() {
return frames.stream().mapToInt(Frame::score).sum();
}
public void roll(int pins) {
Bonus bonus = new SingleBonus(0);
for (Frame frame : frames) {
bonus.consumeRoll(frame.getRollsPlayed());
if (frame.isFrameOpen()) {
frame.roll(pins, bonus);
return;
}
bonus = Bonus.newBonus(bonus, frame);
}
}
}
We can refactor this using the same approach as above. We can move the logic to decide what frame to create to a factory method inside the abstract Frame class.
public abstract class Frame {
private final int[] scores;
protected final int[] rolls;
protected int rollsPlayed = 0;
public Frame(int numberOfRollsAllowed) {
scores = new int[numberOfRollsAllowed];
rolls = new int[numberOfRollsAllowed];
for (int i = 0; i < numberOfRollsAllowed; i++) {
scores[i] = 0;
rolls[i] = 0;
}
this.rollsPlayed = 0;
}
public void roll(int pins, Bonus bonus) {
rolls[rollsPlayed] = pins;
scores[rollsPlayed] = bonus.calculateScore(pins);
rollsPlayed++;
}
public int score() {
return stream(scores).sum();
}
public int getRollsPlayed() {
return rollsPlayed;
}
public abstract boolean isFrameOpen();
public abstract boolean isSpare();
public abstract boolean isSquare();
public static Frame newFrame(int framePosition) {
return framePosition < 9 ? new NormalFrame() : new TenthFrame();
}
}
public class Game {
private final List<Frame> frames = IntStream.range(0, 10).mapToObj(Frame::newFrame).collect(Collectors.toList());
public int score() {
return frames.stream().mapToInt(Frame::score).sum();
}
public void roll(int pins) {
Bonus bonus = new SingleBonus(0);
for (Frame frame : frames) {
bonus.consumeRoll(frame.getRollsPlayed());
if (frame.isFrameOpen()) {
frame.roll(pins, bonus);
return;
}
bonus = Bonus.newBonus(bonus, frame);
}
}
}
The last thing is to verify that we are able to calculate a perfect game.
@Test
public void perfectGame() {
Game bowling = new Game();
for (int i = 0; i < 12; i++) {
bowling.roll(10);
}
assertThat(bowling.score(), is(300));
}
The final picture of our design: