Вы находитесь на странице: 1из 18

Notes on Style

Testing the TicTacToe game

27-Apr-19
A first approach
 I want to create a large number of tic-tac-toe partial
games for testing purposes
 I could do it this way:
 char[][] array;
 array = new char[][] { { 'X', ' ', 'O' },
{ ' ', 'X', ' ' },
{ 'O', ' ', 'O' } };

 ...and I could reset the array for each tic-tac-toe board I


want to use as input
 This looks nice, and is easy to read, but it is a real
nuisance to type out a lot of these
So I did this instead
 setBoard(" o x o o");

 private void setBoard(String xxx) {


xxx = xxx.toUpperCase();
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
array[i][j] =
Character.toUpperCase(xxx.charAt(3 * i + j));
}
}
}

 Now it's a lot easier to create tic-tac-toe boards for testing


Morals
 Use methods to make your life easier
 If something is ugly, hide it in a method

 Also...
 While our main goal should be to write programs that
are easy to read, it isn’t our only goal
 The best thing to do with hard-to-read methods is to
rewrite them
 Second best is to explain them in comments
 I didn’t include the comments on the slide, but they are in
my code!
Refactoring
 Refactoring is reorganizing a program without changing
what it does
 Refactor in order to:
 Make a program easier to understand
 Make a program easier to modify
Before refactoring
 public final void testMakeCornerMove() {
setBoard(" oxoxxoxo");
computerPlayer.makeMove(board);
assertBoardIs("xoxoxxoxo");

setBoard("oo xxooxx");
computerPlayer.makeMove(board);
assertBoardIs("ooxxxooxx");

setBoard("oxoxxoxo ");
computerPlayer.makeMove(board);
assertBoardIs("oxoxxoxox");
}

 I seem to be doing the same thing over and over...


After refactoring
 private void beforeAndAfterMove(String before, String after) {
setBoard(before);
computerPlayer.makeMove(board);
assertBoardIs(after);
}
 public final void testMakeCornerMove() {
// Center and all other corners taken
beforeAndAfterMove(" o x o o", "x o x o o");
beforeAndAfterMove("o x o o", "o x x o o");
beforeAndAfterMove("o o x o ", "o o x o x");
beforeAndAfterMove("o o x o", "o o x x o");
// Corner move is all that's left
beforeAndAfterMove(" oxoxxoxo", "xoxoxxoxo");
beforeAndAfterMove("oo xxooxx", "ooxxxooxx");
beforeAndAfterMove("oxoxxoxo ", "oxoxxoxox");
beforeAndAfterMove("xxooxxxoo", "xxooxxxoo");
}
Moral
 The DRY principle: Don’t Repeat Yourself
 Every piece of data should have a single unique representation
 “A man with a watch knows what time it is. A man with two watches is
never sure.” -- Segal’s Law
 Example: If you have a measure of distance, don’t keep it in two
variables, distanceInFeet and distanceInMeters -- keep it in one
variable, and use a method to convert to the other units as needed
 Each nontrivial operation should be represented by a unique
piece of code
 Don’t “cut and paste” code--turn it into a method
 Variations in code can often be handled by a parameter list
 Corrections and updates are much simpler
Testing for a winning move
 Here’s one way to test for a winning move:
 if (board.get(1, 1) == 'X' && board.get(1, 2) == 'X'
&& board.get(1, 3) == ' ') {
board.set(1, 3, 'X');
return true;
}
 There are 24 combinations to test for
 This is why I made testing for a winning move optional!
 Using a method would help some
 if (winningMove(1, 1, 1, 2, 1, 3)) return true;
 But that’s still 24 error-prone lines, plus a method
A bright idea
 For each location on the tic-tac-toe board,
 Put an 'X' in that location
 Check for a win (with our computerHasWon() method)
 If it’s a win, that’s our move
 Otherwise, put a blank in that location, and keep trying

 We can do something very similar for testing if we need


to make a blocking move
The code
 private boolean makeWinningMove(TicTacToeBoard board) {
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
if (!board.isEmpty(i, j)) continue;
board.set(i, j, 'X');
if (board.computerHasWon()) return true;
board.set(i, j, ' ');
}
}
return false;
}
 This code works, but...
An unexpected consequence
 Row 1, column 1 is already taken.
Row 1, column 2 is already taken.
Row 2, column 1 is already taken.
Row 2, column 1 is already taken.
Row 2, column 3 is already taken.
Row 1, column 2 is already taken.
Row 2, column 1 is already taken.
Row 1, column 2 is already taken.
Row 2, column 1 is already taken.
Row 2, column 3 is already taken.
Row 3, column 2 is already taken.

 Why did this happen?


 I did check for a blank space before placing my 'X'
In the set method of TicTacToeBoard
 if (board[row - 1][column - 1] != ' ') {
error("Row " + row + ", column " + column +
" is already taken.");
}

 I can only “set” a location if it is initially blank


 I never thought about “erasing” an X or an O
 Proposed solution: Modify the set() method
 Problem: I asked you not to modify the provided methods
 Under these constraints, my “bright idea” cannot be
made to work :-(
Morals
 Insofar as possible, methods should do a single thing
 In particular, it’s usually a bad idea to mix computation
and input/output in the same method
 If you mix computation and input/output in the same method,
then you can’t do the computation without also doing the
input/output
 Example: In a previous assignment I specified methods
findDayOfWeek to only do computation, and
findAndPrintDayOfWeek to call the former and print the
results
 This allowed me to test your computations without getting a bunch of
output
Fix #1 for board.set(row, column, ch)
 I could have made set return a boolean--true if the location was
set, false if it wasn’t

 boolean set(int row, int column, char ch) {


if (board[row - 1][column - 1] == ' ' &&
(ch == 'X' || ch == 'O')) {
board[row – 1][column – 1] = ch;
return true;
}
else return false;
}

 Disadvantage: The user might not check the result


 Disadvantage: I test for two things that could go wrong (location
is taken, bad character) and this doesn’t distinguish between them
Fix#2 for board.set(row, column, ch)
 I could assert that the location is available, and assert
that the character is legal

 void set(int row, int column, char ch) {


assert board[row - 1][column - 1] == ' ';
assert ch == 'X' || 'O' ;
board[row – 1][column – 1] = ch;
}

 Disadvantage: Bad use of assert--it should be used for


things you believe to be true, not for error checking
Fix#3 and #4
 I could throw an Exception for each error condition
 This is the best solution
 We haven't covered Exceptions yet

 (I nearly forgot this one) I could just skip error checking


 Big disadvantage: No warning to the user if something is
wrong
The End

Вам также может понравиться