| CPSC 329 | Software Development | Fall 2026 |
This lab introduces Eclipse's automated refactoring tools and the use of GitHub issues to track work. You'll be given a program along with a code review which has already identified a number of code smells — your job is to turn the review into tracked work (issues, a milestone, and a backlog) and then fix the problems, one issue at a time, each on its own branch and merged with a pull request.
Successful completion of this lab means that you:
Labs are for learning and practice — you may help and be helped by others when it comes to figuring out what to do, but in the end you need to understand and be able to do the things for yourself.
This lab is individual — everyone works with their own repository and does their own refactoring.
You may not use AI to figure out what to do or to do the task for you.
due Fri 10/2 at the start of class
To hand in your work:
Create a tag to mark your final version of main as your handin:
On github.com, go to the repository's main page and click "Create a new release" (under the Releases heading on the righthand sidebar).
Choose "Create new tag" (on the "Tag: Select tag" dropdown) and enter handin for the tag name.
Verify that the target is main (on the "Target" dropdown), enter a human-readable release title such as "lab 5 handin", and click "Publish release" to create the tag.
Back on the main repository page, you should be able to see the new tag listed under "Releases" in the righthand sidebar. If you change something after creating the handin tag and need to update your handin, first delete the tag — click on its listing under "Releases" and then, on the tag's page, click on the little red trashcan icon — and then create a new one.
Add sbridgeman (me) to your lab5 repository as a collaborator so that I can see your work.
You'll be working with a console version of Klondike solitaire. Game runs the game; Card, Deck, Foundation, and TCol (a tableau column) are the pieces; Klondike contains main.
There is also a JUnit test suite. This test suite is your safety net — a refactoring changes the structure of the code but not its behavior, so tests which pass before a refactoring should still pass after it. Run the tests after every refactoring step to help ensure that nothing broke during refactoring.
Each finding in the provided code review identifies a smell, where it is, why it is a problem, and what the fix should be. The review refers to the code as it was reviewed — names like TCol in later findings refer to the class you'll have renamed by then.
Each finding in the review will become a GitHub issue. The required issues go into a milestone — a set of issues you're committing to finish by a date. The other issues stay in the backlog — open issues with no milestone, recorded but not scheduled. Each issue is fixed on its own short-lived branch in small commits (with the tests passing at each one) and is merged into main with a pull request which closes the issue.
For this lab, findings 1-6 are required (and will go into the milestone) while findings 7-12 are optional (address them if you have time) and will go into the backlog.
Start the same way you'd start any Eclipse project with Git, by creating the Eclipse project and sharing it to a local Git repository:
Create a new Eclipse project named lab5. Remember that the project directory should end up under ~/cs329/dev, not in Eclipse's workspace.
Import all of the Java files from /classes/cs329/lab5 into your project. Make sure they end up under the src folder in the project.
Resolve the errors by updating the build path to include JUnit 5. (There will be one warning remaining; ignore that until later.)
Run the tests to verify that they all pass. Since there are multiple test classes, a JUnit test suite is included to run all of the tests in one go — right-click on KlondikeTestSuite in the Package Explorer and run it like a single test class (Run As → JUnit Test).
Create a new (local) Git repository, create/set up .gitignore, and do the initial commit. Include a meaningful commit message.
Then create a GitHub repository and connect your local repository:
On github.com, create a new repository named lab5. Remember to make it private and don't initialize with a README or other files.
Back in Eclipse, push your main branch. (Check the GitHub repository page afterwards to make sure everything is as you expect.)
Labels categorize issues, and a milestone groups the issues that need to be done by a particular date. Set up the labels and milestones for this lab:
On the main repository page on github.com:
On the Issues tab, click "Labels". There's already a bug label; create a new label called refactoring.
On the Issues tab, click "Milestones" → "New milestone". Title it lab 5 checkoff and set the due date to Friday 10/2.
Each of the findings in the code review is something to fix, so create an issue for each in order to track progress:
On the main repository page on github.com:
Create one issue for each of the twelve findings in the review report, in order. (If you create them in order, the issue numbers will match the finding numbers, which makes the rest of the lab easier to follow.) To do this:
On the Issues tab, click "New Issue".
Use the finding's heading as the title and copy the text of the issue (everything but the heading) from the review as the description.
In the right side panel, assign it to yourself and add the refactoring label.
For the findings (1-6) only, set the milestone to lab 5 checkoff. Leave findings 7-12 without a milestone — they are the backlog (optional; work on them if you have time).
Click Create. (If you check the "Create more" box before clicking Create, you'll get a new issue template with the assignees, labels, and milestone pre-filled.)
Go to the Issues tab — you should see all 12 issues. (Change the sort order to "Oldest" to see them in numerical order.)
Handling each issue follows the same remote repository workflow process as in lab 4:
Start from an up-to-date main: switch to main (Team → Switch To → main), then Team → Pull or Pull.... (Since you are the only one working on main, there won't be any changes to pull in — but forgetting to update and working with out-of-date code is a common mistake so it is good to get in the habit of always updating before you start on something new.)
Create a branch for the issue: Team → Switch To → New Branch, with main as the source and "check out new branch" checked. Name the branch after the issue — include the issue number and a brief descriptive name e.g. 1-naming for issue 1.
Refactor in small steps. For example, issue 1 involves renaming four sets of things — treat each set of things as a step. After each step, run the tests. When they pass, commit with a short message saying what the step did (e.g. "renamed TCol to TableauColumn").
Push the branch and open a pull request into main on GitHub. (Use the "Compare & pull request" banner, or select the branch from the dropdown menu and click Contribute → Open pull request.) In the description, include Closes #n (replace n with the issue number you are addressing) so GitHub will automatically close the issue when the merge is complete. Click "Create pull request".
Merge and, assuming the merge was successful, delete the branch (you are now done with it).
Go to the Issues tab and confirm that the issue is now closed. For one of the milestone issues, the milestone page should also reflect the progress.
Eclipse's refactorings are in the Refactor menu (also on the right-click menu); Alt+Shift+T pops up the refactorings that apply to the current selection. A few things to keep in mind:
Most refactoring dialogs have a Preview button — use it. It shows exactly what will change, in every file, before anything happens.
If a refactoring doesn't do what you wanted, Edit → Undo (Ctrl+Z) undoes the whole thing, in every file it touched.
Eclipse updates code, but doesn't always update comments — check the Javadoc after each refactoring.
Fix the issues in numerical order (i.e. as listed below). Look at the code review as you start on each issue so you know what the refactoring steps are fixing. Refer to the workflow outlined above for the process steps; only specifics for the refactoring fix for each issue are given below.
This refactoring makes use of Refactor → Rename to rename things (classes, methods, parameters, variables).
Start from an up-to-date main and create a new branch as described in the workflow above.
Rename the class TCol: in the Package Explorer, right-click on TCol.java → Refactor → Rename. Enter the new name (TableauColumn) and leave "Update references" checked. Click Next to preview every file which will be changed (the class itself, Game, and the test class), review the changes (both to verify they are correct and to see what might be left for you to fix manually), then click Finish.
Run the test suite, verify that all the tests still pass, and then commit with an appropriate commit comment (e.g. "Rename TCol to TableauColumn"). (Until it is staged, a renamed file shows up as a deleted file plus a new one.)
Rename TColTest to TableauColumnTest in the same way. (Note that the reference to the class in the test suite is also updated.) Run the tests and commit.
Rename the method proc in Game: right-click on its name in the method declaration → Refactor → Rename. For methods and variables, Eclipse renames right in the editor — type moveColumnToColumn and press Enter. Every call is updated.
Rename its parameters (a → fromCol, b → toCol) and local variables (x → from, y → to) in the same way. Note that the @param tags were updated in the method's Javadoc comment but the text of the description still says "column a" and "column b". Fix it. Run the tests and commit.
Rename the variable t in play() to command. Run the tests and commit.
Rename getTop in TableauColumn to removeTop. Run the tests and commit.
Push the branch and merge into main as described in the workflow above. Be sure to include "Closes #1" in the pull request description so that GitHub automatically closes issue #1 with the merge. Check that the issue has been closed as described in the workflow above.
This refactoring makes use of Refactor → Extract Method to turns the selected statements or expression into a new method. Eclipse automatically determines what the new method needs as parameters and what it should return.
Start from an up-to-date main and create a new branch as described in the workflow above.
Extract printBoard(): select the statements under // print the board (but not the comment itself), starting with System.out.println(); all the way down to the blank line before the next comment. Right-click → Refactor → Extract Method... Name the method (printBoard) and leave it private. Verify the method signature shown, click Preview to see what changes are going to be made, and click OK. Run the tests and commit.
Extract readCommand(): select the two statements under // get the player's command — note that the second one declares command, which is used later. Extract the method, then note how command was handled — the new method returns it. Run the tests and commit.
Extract drawCard(): select the whole if/else statement inside the d branch and extract the method. Run the tests and commit.
Extract isWin(): select just the condition inside the if (...) under // check for a win, from foundations_[0] through the last == 13 and extract the method. Note what happens — extracting an expression results in a method which returns its value. Run the tests and commit.
Delete the four section comments — the method names now say the same thing — and then add a Javadoc comment to each new method — put the cursor in the body of the desired method and right-click → Source → Generate Element Comment to generate the skeleton. Normally you should then fill in the rest of the comment but for this lab you can skip that and leave just the skeleton. Run the tests and commit.
Push the branch, open a pull request (be sure to include Closes #2 in the description), and merge.
Extract Method can also find other copies of the selected code and replace them with calls to the new method.
Start from an up-to-date main and create a new branch as described in the workflow above.
In Game.play(), find the column input loop in the c branch (not the w branch). Select from int col = 0; through the closing } of the while loop and Right-click → Refactor → Extract Method... Name the method readColumn, then near the bottom of the dialog is a checkbox for replacing additional occurrences of the statements with the method — Eclipse searches the class for other copies of the selected code and if this box is enabled, it found some. If the box is enabled, check it, Preview, and Finish. How many copies were found?
Run the tests.
The review said there were four copies. Ideally Eclipse found three (the one you selected plus two others) — in that case, look at the one that remains and carefully compare it to readColumn. If Eclipse did not find any additional copies, locate the other three yourself and carefully compare each to readColumn. What you should find is that the comparison in the w branch has an off-by-one error (col < 7 rather than col <= 7). Replace the copies in the other branches with calls to readColumn if Eclipse didn't do that for you but leave the w case alone.
You've found a bug. Don't fix it here — a refactoring doesn't change behavior while a bug fix does, so they belong in separate commits and pull requests, each reviewed for what it is. Instead, file a bug report so it isn't lost:
Create a new issue (e.g. "Waste card can't be moved to column 7") with the bug label and no milestone. Include how to reproduce the problem (the seed and the commands), what should happen, and what happens instead.
Push the branch, open a pull request (be sure to include Closes #3 in the description), and merge.
Start from an up-to-date main and create a new branch as described in the workflow above.
In Game.isLegalTableauMove, select one occurrence of col.cards_.get(col.cards_.size() - 1) and right-click → Refactor → Extract Local Variable. Name it top and leave "Replace all occurrences" checked — all three really are the same thing, the column's top card. (You'll see a case where replacing all occurrences is wrong in issue 9.) Run the tests and commit.
Click on the method name isLegalTableauMove in its declaration and right-click → Refactor → Move. Eclipse offers to move the method to the type of one of its parameters — choose col (TableauColumn). Leave "Keep original method as delegate" unchecked (the method should be moved entirely). Preview, then click OK.
Look at the result: the method is now in TableauColumn, col.cards_ has become cards_, and Game's calls are now tableau_[col - 1].isLegalTableauMove(card) and to.isLegalTableauMove(card).
Eclipse gave the method package access — make it public. Also update its Javadoc, which still describes a column parameter. Run the tests and commit.
Push the branch, open a pull request (Closes #4), and merge.
Start from an up-to-date main and create a new branch as described in the workflow above.
Rename methods so that both classes use the same names for the same operations. Rename at the declaration so that every call is updated.
In TableauColumn: isLegalTableauMove → canAccept, put → add, getLast → top
In Foundation: addCard → add, topCard → top
Run the tests and commit. (Some test method names, such as putAddsCardOnTop, are now out of date — renaming them too is a good idea but you don't need to do that now.)
In Foundation, right-click → Refactor → Extract Interface. Name the interface Pile, select canAccept, add, top, and isEmpty, and check "Generate '@Override' annotations". Leave "Use the extracted interface type where possible" unchecked — it changes variable and return types throughout the program, which is more than this issue calls for. Preview, then click OK.
Eclipse only changes the class you started from. In TableauColumn, add implements Pile to the class header and @Override to the four methods yourself. (If a name doesn't match, the compiler will tell you.)
Run the tests and commit.
Push the branch, open a pull request (Closes #5), and merge.
Start from an up-to-date main and create a new branch as described in the workflow above.
In Game, click on the field in_ and right-click → Refactor → Extract Class. Name the class ConsoleUI and make it a top level class, make sure only in_ is selected ("Select fields for extracted class"), set the field name (this is the name Game will use for its ConsoleUI instance variable) to ui_, and check "Create getters and setters". Preview, then click OK. Note that the input calls in Game now read ui_.getIn().nextLine(). Run the tests and commit.
Extract Class only moves fields. Move the methods readCommand, readColumn, and printHelp one-by-one — in the body of each, right-click → Refactor → Move..., choosing ui_ as the new receiver each time. Inside the moved methods, ui_.getIn() becomes just getIn(). Make the moved methods public. Run the tests and commit.
Tidy up by hand: give ConsoleUI a constructor which takes the Scanner and have Game's constructor use it, delete setIn (which is no longer needed), and write Javadoc for the class and its methods. One place in Game still calls ui_.getIn() — the input loop in the w branch. That's the bug you filed in issue 3, so leave it alone for now. Run the tests and commit.
The review said to leave moving the output (printBoard and the messages printed by the move methods) for later. File a follow-up issue for it with the refactoring label and no milestone. (Recording work you've noticed but aren't doing now, instead of expanding the current change, keeps pull requests small and focused.)
Push the branch, open a pull request (Closes #6 in the description), and merge.
All six of the required issues in the milestone should now be closed. But don't close the milestone itself yet! Work on the additional issues below if you have time in lab, and then go to the Wrapping Up section at the bottom before handing in your lab.
This section is optional, but is encouraged for additional practice — continue with it until the end of lab.
Before starting each additional issue, pull it into the milestone (set its milestone to lab 5 checkoff) — it's now being worked on and no longer in the backlog. This means that the milestone will accurately reflect what actually got done.
For each issue, follow the same workflow. Only the refactoring/editing steps are given below — remember to still start with an up-to-date main and create a new branch before starting on the issue and push, open a pull request, and merge when you are done.
Add a test to GameTest which demonstrates the bug. Use the play helper at the top of the class: seed 519 with the commands d, w, 7, q should leave column 7 as ## ## ## ## ## ## 10D 9S and the waste empty.
Run the test — it should fail. (You'll get a NoSuchElementException rather than an assertion failure, because the game rejects the 7, keeps asking for a column, and runs out of input.) Commit the failing test on its own, with a commit comment which says so.
Fix the bug by replacing the w branch's loop with a call to readColumn(). The test now passes. If ConsoleUI.getIn() is no longer used, delete it. Commit.
On Game.reportMove, Refactor → Change Method Signature to remove the unused verbose parameter. The preview shows every call being updated. Run all the tests and commit.
Use Refactor → Introduce Parameter Object to replace the other five parameters with a top level class Move, with getters. Run all the tests and commit.
On TableauColumn.cards_, Refactor → Encapsulate Fields... Choose "keep field reference" for field access in the declaring type. Be sure to preview here before clicking OK! Eclipse's refactoring has a bug where it may not always respect the "keep field reference" setting — look at the changes in TableauColumn to see if internal uses of cards_ are being changed to getCards() or setCards(). ("Keep field reference" is meant to tell Eclipse not to do that.) If cards_ remains, go ahead with the refactoring — every use of cards_ outside the class becomes getCards() and cards_ becomes private. If cards_ is getting turned into getCards() even inside TableauColumn, skip to the "manual refactoring" steps below. Run all the tests and commit.
A getter which returns the list still lets Game do anything it likes to it, so the getter needs to go too. Use References → Project on getCards to find each use, and replace each one:
Where an existing TableauColumn method does the job, call it instead (dealing can use add and top()).
For the two loops in moveColumnToColumn, use Extract Method on each loop in Game, then Move the new method to TableauColumn. (This is issue 4 again: code which envies the column.)
When References → Project finds no more uses, delete getCards and setCards. Run all the tests and commit.
Manual refactoring — if Eclipse doesn't properly keep the reference to the cards_ with the automatic refactoring, make the changes yourself:
Make cards_ private in TableauColumn. This immediately breaks any code trying to access cards_ directly.
Look for the error flag icons in the Package Explorer and the red markers in the right margin of the editor when you open the file to locate the things that need to be fixed. Then:
Where an existing TableauColumn method does the job, call it instead (dealing can use add and top()).
For the two loops in moveColumnToColumn, use Extract Method on each loop in Game, then Move the new method to TableauColumn. (This is issue 4 again: code which envies the column.)
Once all the error flags have gone away, run all the tests and commit.
Select a literal and Refactor → Extract Constant. In Game, every 7 means "number of columns", so "Replace all occurrences" is exactly right — Preview to confirm. Do the same for 4 and 13. Extract Constant works within a single file, so change the 7 in ConsoleUI to use Game's constant by hand.
In Foundation.canAccept, select the 1 in card.getRank() == 1 and extract a constant ACE with "Replace all occurrences" checked — but stop at the preview. What else would change? Would the tests catch it? Go back, uncheck the box, and finish.
ACE (and KING, from TableauColumn) are about cards. Use Refactor → Move on each constant (find the declaration of the constant) to move it to Card.
Use References → Project on score_ and on revealTop to find everywhere that scoring happens, then collect the rules in a Score class as the review describes. Extract Class on score_, followed by Extract Method and Move, will get you partway. Change Method Signature can change revealTop's return type to boolean, and then the compiler will point out everything that needs fixing, including three tests.
Do these together on one branch — a pull request can close more than one issue.
Use Refactor → Inline on the declaration of foundationCanAccept, with "All invocations" and "Delete method declaration". Do the same for addToFoundation. Run all the tests and commit.
Eclipse already flags printDebugState — that's the warning in the Problems view ("never used locally"). Delete it. There's no warning for countFaceUp because it's public and could be called from anywhere, so use References → Project to confirm that nothing calls it, then delete it. Run all the tests and commit.
In the pull request, write Closes #11, closes #12 — each issue needs its own keyword.
At the end of lab, if any of the original six issues are still open, finish those. If you partway through an additional issue, finish it. Then close the milestone:
On github.com, go to the lab 5 checkoff milestone's page (on the Issues tab, click "Milestones", then click on the "lab 5 checkoff" milestone) — it should show that all of the associated issues are closed. Click "Close Milestone".