Code Critique and Refactoring
This exercise is intended to help you to learn to critically read code, understand its purpose, and identify ways to improve its design. Along the way, you should start to build a sense of the reader's expectations about code and what makes one piece of code better than another given a specific set of design goals. In pairs, examine the code from the perspective of how readable it is (i.e., is it easy to find or understand important parts of the code, does it do what you expect, does it require more or fewer comments). While studying this code, also note any good things about the code, that you might keep in the final version as well as smells in the code.
CompSci 101 currently, and CompSci 201 previously, give an assignment based on the game of Hangman. It is typically given in multiple parts based on what concept students are learning in the course, but the instructions tend to be the same: copy your previous version and update it to implement new kinds of players (either a guesser or executioner). To simulate this, there are three versions of the game that each play slightly differently. The goal of this exercise is to use the differences between the three versions of Hangman games to identify what code is specific to the players and extract those differences into guesser and executioner classes with methods that can be called from the game code instead of being part of the game class. In the end, instead of three classes that represent one concept within the project and have significant duplication, you should have three classes (game, guesser, and executioner) with no duplication that represent distinct concepts in the project. Removing the duplication and decomposing one conceptual class into three will also improve the overall design by separating the game logic from the player logic so that they can be changed independently.
To work on this lab exercise, fork the original project lab_hangman into your own repository so you can edit, commit, and push your group's own changes.
Discussion
In pairs, read over the code to see if you have any questions about how it works.
To begin discussing the code's design, spend some time evaluating the code using the following questions as a guide:
- What pieces of code help versus obscure your understanding?
- What comments might be helpful within the code?
- Are there places where the code could be more concise and also more clear?
- What Code Smells can you find?
- What additional methods or classes might be helpful?
- What assumptions about how something works or dependencies are there between different parts of the code (e.g., common variables, order of method calls, or assumed unexpected behavior)?
- How easy is it to identify those dependencies as part of the game or player logic?
- Can you clarify or remove those dependencies by creating different classes and encapsulating some logic?
- What suggestions does this new code make about how someone would extend it in the future to add different playing algorithms?
Then spend some time separating the game logic from the player logic by identifying candidate code within each game that could be turned into methods to represent a player's behavior within the game. For each potential method, think of a general name and parameters that would work across all three games. As part of your discussion, consider the following questions:
- What parts of the code do the game classes have in common and what parts are different?
- The method
play()is composed of several steps, which ones are essential to the game and which to the players? - The method
makeGuess()is composed of several steps, which ones are essential to the game and which to the players? - Which class should be responsible for each of these instance variables:
mySecretWord,myNumGuessesLeft, andmyDisplayWord?
Use Gitlab's markdown format to record the group's answers in the file called DISCUSSION.md that is included with the example code. IntelliJ includes a markdown editor that provides a preview or here is a web-based editor that provides similar functionality and can be shared like Google Docs.
Pair Programming
Another goal is for you to try Pair Programming: working closely with another student and sharing a single computer (a common practice in industry). To ensure both people try driving, switch the person who types on the primary computer every 10-15 minutes. You may use a second computer to look up documentation or search the Internet for solutions related to the exercises, but not to do your own work or socialize.
Refactoring
Refactoring is the practice of updating a program to improve its design and maintainability without changing its current functionality significantly. An example of refactoring is creating a single method or class that replaces two or more sections of similar code because it reduces the amount of redundant code within the program and makes the code easier to debug and test. You do not have to worry about updating the algorithmic code itself, instead your goal is primarily about reorganizing the existing code to make it more readable and improve the overall design.
Examine the given program and refactor it based on your group's discussion. As a first step, choose one game to refactor and try to extract the methods you identified in your discussion that represent potential player code within that game (this will make the next steps of extracting a class for each kind of player easier). Note, within your general player class methods, you may need to use if statements to choose between different player algorithms (e.g., between interactive or algorithmic or random) — this is not an ideal solution and we will address that separately in the next few weeks.
Your group may create (and comment why you choose to create them) any new methods or classes you want to help improve the program. Try to justify each change you make by explaining specifically how it improves the code. Justifications should refer specifically to principles discussed in class or the reading rather than using terms like "clearly/obviously", "good/sucks", or "like/hate".
After you have done as much as you think is reasonable, consider the following questions.
- In what ways is the refactored code simpler?
- In what ways is the refactored code more complex?
- What trade-offs did you make when refactoring the original code?
- Which code do you prefer and why?
Version Control Workflow
As a group, periodically discuss when you think it is appropriate to create a commit to represents a set of changes (i.e., not just at the end of the lab). After you think you have completed a reasonable goal, make a GIT commit with an appropriate comment (there should be at least four commits in your project history). After every two commits, push your changes up to Gitlab so your online repository reflects the work you have done in lab today.
Here is a review of the steps you will use to work with GIT during labs (as distinct from assigned team projects):
- On Gitlab, fork the original project
lab_hangmaninto your own repository so you can edit, commit, and push your group's own changes - In Terminal, use
git cloneto copy the code from your forked SSH location - In IntelliJ, create a Java project in the same folder you cloned the project to
- As a group, discuss the design issues above
- In IntelliJ, refactor the code to improve its design (i.e., edit the code)
- In Terminal, use GIT to
git addchanged filesgit commit -mto describe your changesgit pushchanges back to Gitlab- Repeat as many times as needed, letting each person try the changes/GIT steps in related chunks
- On Gitlab, when you are done, use Gitlab's Merge Request from your forked repository to the original. Do not worry about potential merge conflicts, your request will not be approved.
Submission
At the end of class, use Gitlab's Merge Request to submit your group's answers to the discussion questions above and refactored code to the original organization repository. Make sure the NetIDs of everyone in the group are in the title of your Merge Request and in the markdown file.