In 2011, a variation of this game was presented as a "nifty assignment" at the SIGCSE conference on computer science education. In this variation, called "Evil Hangman", the computer program changes the target word according to what letters the human player has suggested, making it very hard to correctly guess the word. Sneaky, huh?
The "evil" program works by maintaining a list of words and then, each time the player suggests a letter, removing all words containing that letter from its list (assuming that some words would still remain), essentially dodging the human player's guesses as much as possible. Although it is possible for the human player to win, it is certainly tricky to do so.
In this assignment, you are given an implementation of Evil Hangman as a Java Swing app. The code works correctly (i.e., it correctly plays the game and passes its unit tests) but admittedly is not very "good" in terms of internal quality: design, understandability, readability, etc. Your job is to refactor the code to make it better.
This assignment is more complicated than it sounds. Even if you are a good programmer, do not wait until the last minute, as it can take quite a bit of time to figure out what the code is doing before you can start changing it. You may work with one other student on this assignment; it does not have to be the same student with whom you worked on previous assignments.
GUI Classes
The app that you will refactor uses the Java Swing API for its user interface; if you haven't encountered Swing before, it's pretty similar to Android in that you have objects to represent the window itself (in Swing, these are called JFrames), you have the various UI widgets (e.g. JLabel, JComboBox, JButton, etc.), and you have methods that are called whenever the user interacts with a widget (this method is usually called "actionPerformed")
There are four classes that represent the different windows that appear in the app:
Game Classes
There are three other classes that represent the data and logic for playing the game:
Tests
JUnit tests have been provided for the NormalHangMan and EvilHangMan classes. Note that there is overlap in some parts, but not in all parts; you will address this overlap in your refactoring.
Although these tests may not be "complete", you can assume that they are "sound": if any test fails, then that indicates a bug in the program.
If you have a question about the specification or the intended behavior of the code, please ask the instructor immediately; relevant answers will be distributed to the entire class. You may find (unintentional) bugs in the code that are not related to the test cases we've provided; it is not necessary for you to fix them, but ask the instructor if you are unsure.
NOTE: it is recommended that you complete the refactoring steps in the order in which they are provided here. Although you will ultimately end up with the same final product anyway, the steps that are described here will help remove any redundant refactoring activities.
1. Set up Eclipse project
Run all the unit tests from the "tests" directory to make sure that they all pass. If any tests fail, please let us know immediately.
Because you are going to be making lots of changes to the code, and some of those changes will unintentionally break it, you may want to put this code into source control so that you can "roll back" to previous revisions if necessary. This may seem excessive, but better safe than sorry, right?
2. Refactor the NormalHangMan and EvilHangMan classes
There are a few ways to do this: use the one that makes the most sense to you, and explain your decision in your write-up. If you are unsure how to do this in Java, please ask a member of the instruction staff for help (please don't post approaches/solutions on Piazza; it is important that all students figure this out for themselves!). Hint: you may want to look into Eclipse refactoring capabilities; it may help things move a bit faster.
Because you are not changing the exposed (public) methods of NormalHangMan and EvilHangMan, nor are you changing the functionality, you should not have to change the corresponding JUnit tests. But make sure that all tests still pass, of course! If you need to change any of the tests, then document that in your write-up, too.
Before moving on to the next step, also be sure that the entire app still works correctly, by playing the game from the user interface.
3. Refactor the LetterGuessHistory
Note: Whatever data structure you choose, it should be one that maintains the order in which the player guessed the letters. That will make the test cases easier to write.
After making that change, modify all of the affected methods in the application. Unlike in Step 2, in this case, you will have to modify the unit tests! Make sure that all tests compile and run successfully before moving on, and also play the game to make sure it still works.
4. Refactor the Wordlist
Refactor the EvilHangMan class so that Wordlist is a more appropriate data structure (as in Step 3, you'll need to explain your choice in your write-up) and then modify the EvilHangMan.makeGuess method accordingly. This part is fairly tricky, so we recommend making a copy of the method and commenting out the original before you start your refactoring, rather than blowing the original away entirely.
In this case, you should not have to modify the test cases in EvilHangmanTest, but if it turns out that you need to, explain why in your write-up. As always, make sure the game still works before you continue.
5. Refactor the GUI_PlayGame.controller method
As discussed in class, moving a method can be a bit tricky, because you'll no longer have direct access to all the fields. Before you start, think about all the "inputs" and "outputs" and how you'll deal with them; you'll need to discuss your approach in your write-up, and describe the tradeoffs that you had to make.
Okay, here's the really tricky part: now that you've refactored it, create JUnit tests for the "controller" method. To do this, create a new JUnit test class (i.e., don't add these test cases to the classes we provided you) and add test methods that cover the six branches in the "controller" method:
Your test methods should put the object into the appropriate state before calling the "controller" method directly, and then checking for the correct behavior (output and side effects). This will almost certainly entail changing the visibility of some fields and/or adding setter and getter methods; make sure you describe those changes in your write-up.
Keep in mind that you are not testing the GUI_PlayGame class, but your tests will, of course, need some of the same objects that it uses for input and output. You may create an instance of the GUI_PlayGame class if that helps you, but your test should be calling the "controller" method directly, not going through the GUI_PlayGame.actionPerformed method. Ask a member of the instruction staff if you need help.
After you've finished refactoring and writing your tests, be sure to re-run all unit tests (even the ones you didn't modify!) and run the app from the UI to make sure that everything still looks okay!
6. "Refactor mercilessly"
When looking for things to refactor, use the suggestions from class and from Fowler's Refactoring: Improving the Design of Existing Code, and anything else you can think of to improve the code. You can certainly use the Eclipse refactoring capabilities as needed. For instance, you may want to consider:
As discussed in class, you should refactor iteratively, instead of making lots of changes all at once. Each time you make a change, be sure to modify and run the corresponding JUnit test(s) to ensure that the functionality is still correct. If you remove a method (e.g., combining two methods into one), then you should make sure that any JUnit tests for the removed method are modified so that the functionality captured by that method is still tested. If you create new methods (e.g. splitting one method into two, or adding a helper method), you should create unit tests for those as well. In all cases, you must make a good-faith effort to ensure that the code works as expected, and not that it simply passes the provided unit tests.
Remember, the goal is not to improve the functionality or to find bugs, but rather to improve the design of the code.
Note that there are no unit tests for the GUI classes, but you should still consider them for refactoring. Make your best effort to ensure that their functionality still remains the same by running the app from the UI.
Keep track of the changes you make, and the justification for doing so (i.e., why you made the change). You will need to justify all of your changes in your report.
We suggest that you start by making small "local" changes to the code (within a single method) before making more "global" changes that affect numerous methods. But the order in which you make changes is up to you.
NOTE!! It bears repeating: every time you make a change, even if it seems trivial, run the corresponding unit tests to make sure you didn't break anything. If you make a lot of changes and then run the tests, only to find that they fail, you'll have to go back and debug the program!
7. Document your work
Additionally, create a PDF in which you answer the following questions:
1. Modified code
Submit the entire Eclipse project containing your refactored code in its own zip/tar file. This should include all JUnit tests, including any new tests that you created; all unit tests should pass.
2. Spreadsheet
Submit a spreadsheet (described above) that describes the changes you made to the code in Step 6.
3. Report
Submit a single PDF file that answers the questions stated above (regarding Steps 2-5).
To turn in your assignment, zip and/or tar up the the modified code (you don't need to submit the original!) and submit it along with the spreadsheet and PDF, following the submission instructions.
If you worked with someone else, only one of you needs to submit the assignment. Please indicate the names of both students in the comments field of your submission.
There are, of course, other issues with the code, e.g. problems with naming conventions, performance, etc., and you should describe those, too, but focus on the code smells from class.
If you run into problems, please ask a member of the teaching staff for help before trying to find solutions online!
Updated: Mon, Mar 25 2013, 5:12pm