Notes on StyleA first approachSo I did this insteadMoralsRefactoringBefore refactoringAfter refactoringMoralTesting for a winning moveA bright ideaThe codeAn unexpected consequenceIn the set method of TicTacToeBoardSlide 14Fix #1 for board.set(row, column, ch)Fix#2 for board.set(row, column, ch)Fix#3 and #4The EndJan 14, 2019Notes on StyleTesting the TicTacToe gameA first approachI want to create a large number of tic-tac-toe partial games for testing purposesI 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 inputThis looks nice, and is easy to read, but it is a real nuisance to type out a lot of theseSo I did this insteadsetBoard(" 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 testingMoralsUse methods to make your life easierIf something is ugly, hide it in a methodAlso...While our main goal should be to write programs that are easy to read, it isn’t our only goalThe best thing to do with hard-to-read methods is to rewrite themSecond best is to explain them in commentsI didn’t include the comments on the slide, but they are in my code!RefactoringRefactoring is reorganizing a program without changing what it doesRefactor in order to:Make a program easier to understandMake a program easier to modifyBefore refactoringpublic 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");}MoralThe DRY principle: Don’t Repeat YourselfEvery 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 LawExample: 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 neededEach nontrivial operation should be represented by a unique piece of codeDon’t “cut and paste” code--turn it into a methodVariations in code can often be handled by a parameter listCorrections and updates are much simplerTesting for a winning moveHere’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 forThis is why I made testing for a winning move optional!Using a method would help someif (winningMove(1, 1, 1, 2, 1, 3)) return true;But that’s still 24 error-prone lines, plus a methodA bright ideaFor each location on the tic-tac-toe board,Put an 'X' in that locationCheck for a win (with our computerHasWon() method)If it’s a win, that’s our moveOtherwise, put a blank in that location, and keep tryingWe can do something very similar for testing if we need to make a blocking moveThe codeprivate 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 consequenceRow 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 TicTacToeBoardif (board[row - 1][column - 1] != ' ') { error("Row " + row + ", column " + column + " is already taken.");}I can only “set” a location if it is initially blankI never thought about “erasing” an X or an OProposed solution: Modify the set() methodProblem: I asked you not to modify the provided methodsUnder these constraints, my “bright idea” cannot be made to work :-(MoralsInsofar as possible, methods should do a single thingIn particular, it’s usually a bad idea to mix computation and input/output in the same methodIf you mix computation and input/output in the same method, then you can’t do the computation without also doing the input/outputExample: In a previous assignment I specified methods findDayOfWeek to only do computation, and findAndPrintDayOfWeek to call the former and print the resultsThis allowed me to test your computations without getting a bunch of outputFix #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’tboolean 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 resultDisadvantage: I test for two things that could go wrong (location is taken, bad character) and this doesn’t distinguish between themFix#2
View Full Document