Refactoring mount doom

Mount Doom

 public void roll(int roll) {
        System.out.println(players.get(currentPlayer) + " is the current player");
        System.out.println("They have rolled a " + roll);

        if (inPenaltyBox[currentPlayer]) {
            if (roll % 2 != 0) {
                isGettingOutOfPenaltyBox = true;

                System.out.println(players.get(currentPlayer) + " is getting out of the penalty box");
                places[currentPlayer] = places[currentPlayer] + roll;
                if (places[currentPlayer] > 11) places[currentPlayer] = places[currentPlayer] - 12;

                System.out.println(players.get(currentPlayer)
                        + "'s new location is "
                        + places[currentPlayer]);
                System.out.println("The category is " + currentCategory());
                askQuestion();
            } else {
                System.out.println(players.get(currentPlayer) + " is not getting out of the penalty box");
                isGettingOutOfPenaltyBox = false;
            }

        } else {

            places[currentPlayer] = places[currentPlayer] + roll;
            if (places[currentPlayer] > 11) places[currentPlayer] = places[currentPlayer] - 12;

            System.out.println(players.get(currentPlayer)
                    + "'s new location is "
                    + places[currentPlayer]);
            System.out.println("The category is " + currentCategory());
            askQuestion();
        }

    }

Reveal intent

int[] places = new int[6];
int[] purses = new int[6];
boolean[] inPenaltyBox = new boolean[6];
 int[] places = new int[MAXIMUM_AMOUNT_OF_PLAYERS];
 int[] numberOfGoldCoins = new int[MAXIMUM_AMOUNT_OF_PLAYERS];
 boolean[] inPenaltyBox = new boolean[MAXIMUM_AMOUNT_OF_PLAYERS];

Manual exploratory testing

Golden master

@Test
public void run_golden_master() throws IOException {
   writeOutputTo(TESTRUN_FILE_NAME);

   runGameTimes(500);

   assertThat(readFile(TESTRUN_FILE_NAME), 
              is(readFile(MASTER_FILE_NAME)));
}

Golden master

private void writeOutputTo(String filename)
                               throws FileNotFoundException {
        System.setOut(new PrintStream(
                            new FileOutputStream(filename)));
}

private String readFile(String filename) 
                               throws IOException {
        return new String(readAllBytes(
                             Paths.get(filename)));
}

Golden master

private void runGameTimes(int numberOfGamesRun) {
   for (int seed = 0; seed < numberOfGamesRun; seed++) {
         Random rand = new Random(seed);
         runOneGame(rand);
   }
}

private void runOneGame(Random rand) {
   Game aGame = createGame();

   boolean notAWinner;
   do {
      aGame.executeMove(rand.nextInt(5) + 1);
      notAWinner = checkIfWinner(answerWasWrong(rand), aGame);

   } while (notAWinner);
}

Golden master

Encapsulate external dependencies

public class ConsolePrinter implements Printer {
    @Override
    public void print(Object objectToPrint) {
        System.out.println(objectToPrint);
    }
}

Encapsulate external dependencies

public Game(Printer printer) {
    this.printer = printer;
    ...
}

public void executeMove(int roll) {
    ...
    print("They have rolled a " + roll);
    ...
}

private void print(Object output) {
    printer.print(output);
}

System tests

private Game game;
private List<String> printedLines;
private Printer printer = new Printer() {
    @Override
    public void print(Object objectToPrint) {
        printedLines.add(objectToPrint.toString());
    }
};

@Before
public void initialise() {
    printedLines = new ArrayList<>();
    game = new Game(printer);
}

System tests

@Test
@Parameters({"0,Pop", "1,Science", "2,Sports", "3,Rock"})
public void
prints_game_moves_and_asks_categorised_questions(
                          int rolled, String category) {
    addPlayers(2);

    game.executeMove(rolled);

    assertThat(printedLines, contains(
                "player1 was added",
                "They are player number 1",
                "player2 was added",
                "They are player number 2",
                "player1 is the current player",
                "They have rolled a " + rolled,
                "player1's new location is " + rolled,
                "The category is " + category,
                category + " Question 0"
   ));
}

System tests

Remember?

 public void roll(int roll) {
        System.out.println(players.get(currentPlayer) + " is the current player");
        System.out.println("They have rolled a " + roll);

        if (inPenaltyBox[currentPlayer]) {
            if (roll % 2 != 0) {
                isGettingOutOfPenaltyBox = true;

                System.out.println(players.get(currentPlayer) + " is getting out of the penalty box");
                places[currentPlayer] = places[currentPlayer] + roll;
                if (places[currentPlayer] > 11) places[currentPlayer] = places[currentPlayer] - 12;

                System.out.println(players.get(currentPlayer)
                        + "'s new location is "
                        + places[currentPlayer]);
                System.out.println("The category is " + currentCategory());
                askQuestion();
            } else {
                System.out.println(players.get(currentPlayer) + " is not getting out of the penalty box");
                isGettingOutOfPenaltyBox = false;
            }

        } else {

            places[currentPlayer] = places[currentPlayer] + roll;
            if (places[currentPlayer] > 11) places[currentPlayer] = places[currentPlayer] - 12;

            System.out.println(players.get(currentPlayer)
                    + "'s new location is "
                    + places[currentPlayer]);
            System.out.println("The category is " + currentCategory());
            askQuestion();
        }

    }

More readable?

public void executeMove(int roll) {
   print(players.get(currentPlayer) + " is the current player");
   print("They have rolled a " + roll);

   if (inPenaltyBox[currentPlayer]) {
       boolean rollIsOdd = roll % 2 != 0;

       if (rollIsOdd) {
          playerGetsOutOfPenaltyBox();

          moveTheCurrentPlayer(roll);

          askTheNextQuestion();
       } else {
          playerDoesNotGetOutOfPenaltyBox();
       }
   } else {
       moveTheCurrentPlayer(roll);

       askTheNextQuestion();
   }
}

Refactored conditionals

  • Extract the variable rollsOdd to explain the conditional expression
  • Extract player gets out of penalty box behaviour into a method
  • Extract player does not get out of penalty box behaviour into method
  • Extract the end of the board conditional expression into a variable

Refactored conditionals

  • Extract the behaviour of really executing a move into a method
  • Extract move the current player behaviour into a method
  • Extracted the ask the next question behaviour into a method
  • Inlined the move the player and ask the next question

What's next?

Roll

Penalty Box

Player

Category

Move

Board

Questions

Position

Gold Coins

Thank you

github.com/franziskas/trivia/tree/conditionals

slides.com/franziskasauerwein/refactoring

Refactoring

By Franziska Sauerwein

Refactoring

  • 2,346