TL;DR Never too late to refactor. Do it in small chunks. Protected by tests. Using IDE to refactor.
My RestMud game grew organically, I do have a fair bit of Unit testing, but I also perform more integration testing than unit testing.
The good thing about this approach is that the integration testing represents requirements i.e. in this game I should be able to do X, and my integration tests at a game engine level create test games and check conditions in there.
These tests rarely have to change when I amend my code.
The side-effect of this type of testing is that the classes don’t have to be particularly good, so I have a lot of large classes and not particularly good organisation.
I’m now refactoring the classes and organising the code to have 4 main sections:
- Game Engine
- Games
- API
- GUI
At the moment I’m concentrating on the Game Engine.
I have a large main class called MudGame and I’m splitting that into smaller classes now.
Refactoring from Map to POJO
As an example my MudGame used to have a Map for locations, and collectables and messages.
This meant that I had 4 or 5 methods for each of these collections in my MudGame, I have now only a few high level methods in Game and most of the code has moved to the Locations, or Collections object.
As I was doing this I had to make a decision, do I make a public final
field, or do I create a private
field, with an accessor method.
I initially chose public final
and amended the code, and then changed my mind to have an accessor method.
I don’t worry too much about this because it is easy to use IntelliJ refactoring to rename and wrap fields in accessor methods.
private Map<String, MudLocation> locations = new HashMap<>();
To an object that manages locations, which contains all the code methods that were on MudGame
public final Locations gameLocations = new Locations();
I chose to make the field public initially, then I refactored using “Encapsulate as Method”:
private final Locations gameLocations = new Locations();
and
public Locations getGameLocations() {
return gameLocations;
}
Refactoring Methods to Inline Code
Sometimes when I have a method that is small and doesn’t really add any value because I delegate all the functionality off to another Object, I might choose to inline it:
public MudLocationObject getLocationObject(String nounPhrase) {
return getLocationObjects().get(nounPhrase);
}
When I inline this then anywhere in the code that had:
MudLocationObject locationObject = game.getLocationObject(thingId);
Becomes:
MudLocationObject locationObject = game.getLocationObjects().get(thingId);
Some Tips for Refactoring
- requirement level tests should not have to change during refactoring
- make sure you have tests before you refactor
- don’t worry too much about naming or field/method choices during initial coding because it is easy to refactor later
- use IDE refactoring where possible
- when code gets ugly, get refactoring
- refactor in small chunks, keep chipping away,
- refactor low hanging fruit first as it makes it easy to see what comes next
- group code together to loosely organise prior to refactoring into new classes
- Refactor Classes to represent semantics as well as helping organising code
It’s never too late to refactor your code.
Bonus Youtube Video
See also the accompanying YouTube Video:
In the video you’ll see:
An introduction to Refactoring Java in IntelliJ with a live demo using RestMud Game. I talk you through what refactoring is, and show examples of in built refactoring functionality in IntelliJ.
- An introduction to refactoring
- Basic Refactoring techniques and approaches explained
- Refactoring from fields to methods with “Encapsulate Field”
- Run tests after each refactoring
- Check in code to version control frequently to allow reverting if things go wrong
- Demonstration of refactoring
- Explanation of intermittent Unit Test Execution
- Sometimes as we refactor we discover we are creating duplicate code. When that happens, stop and decide if the existing code is good enough. -Try to avoid creating code that you aren’t using yet. You have to maintain it, and there are no tests, a nd you probably won’t use it in the future anyway!
- Refactoring to Inline methods to remove methods completely. Remove the method and replace invocations with the code in the method
- Reflect on your refactoring. Time to stop? Good enough to checkin? More to do?