Dev Article - Refactoring
Author: HS_Dave
- Posted on: 22/02/2015
Post Type: Blog Entry
TL;DR - Write better code and it'll save you time later. Pretty pictures in future blog posts.

This one is for the dev's. I want to shower you all in screenshots and visual media but I'm "not allowed" until it's "ready" says the artist. So instead I'm going to chat on about some of the work I've been doing codewise - what it entails and potentially how to avoid having to do it yourself if you are teaching yourself development right now.

The majority of the work at the moment falls roughly under the category of "Refactoring". This is where I take the code base and try to fix everything I did wrong 7 years ago. The ultimate goal is to end up with code that is a lot more concise, readable and maintainable.

So, why?

When I decided I wanted Dodeka to be more than just a pc port and that I wanted to add new features I immediately started hitting problems that were caused simply by how the old code was written. Even relatively simple things like face lifting the GUI was turning out to be a nightmare with everything hard-coded.

I decided that if I started "cleaning up" then I could save a lot of the game logic (which is actually pretty good, albeit untidy) and at the same time I could start altering code to prepare it for my new additions.

What about just rewriting it?

So that's the million dollar question. A friend of mine who works for a game studio in Canada actually recommended I just rewrote the game and I was umming and ahhing until I decided I actually wanted to save as much of it as possible. In hindsight I probably should of started from scratch.

One thing that has become clear during this process is that my programming is radically different from 7 years ago (as I should hope!). Not only do I tend to write tidier and quicker but I actually have a completely different style that becomes obvious when you compare blocks of old and new code in Dodeka.

On the plus side I have gained a good insight into my development as a game dev by doing it this way. If you are in a similar situation then ultimately you will have to answer this question yourself.

Some practicals then.

The State Machine of Doom

A lot of tutorials used to have a state machine style setup for controlling program flow. For example you would have a DrawGame() and Update() method and inside each you'd see a small switch() statement that would check engine state. For example your DrawGame method may of looked like:

case States.Menu:
    case States.InGame:

Or something like that. When you are following a small tutorial that's fine. But when you start working on a large game and you just keep adding little tidbits to the state machine it quickly becomes disgustingly convoluted! This happened inside Duodecad with some case statements having additional switches inside, REALLY BAD idea.

State machines are a good thing. Filling case blocks with lots of game code is not. Get your case statements to call external methods which handle things - it will save you a lot of headache later on. If you use visual studio then you'll notice switches are not collapsible. There is also no "go to" quick bookmarking for them. When you get lost in a giant state machine of doom you really ARE on your own, so do not create them.

Extracting Code

Finding code that can be moved ("extracted") to an external method is a key part of refactoring and it does not just apply to state machines. It is very easy as you work on a project and add new features that you just "add a line of code" to an existing method. 7 months of development later and you've built a 650 line mega method one line at a time unless you've been doing frequent refactoring sweeps. Find groupings of code that can be moved to their own method and replace the original block with a single call to that method. Naming the method something in plain English (or your own native language of course) will doubly increase the readability of your parent method.

Taking an example from my own code again, I had ended up with a mega method called UpdateCards(); It's job was simple: Tell all the cards where they are positioned on the screen.

Originally there could be 2 places a card would appear: On the side of the screen or on the actual game board. The first thing that was added was Player 2 - their cards were in a different place. Then I added an effect that showed a card sliding off screen then back down to the board when it was placed. Then I changed scaling of a card if it was selected and then they had to be rotated if cards were hidden from a player and.. you get the point. It went on for ages.

Younger me had never heard of refactoring but would of benefited a lot from it. Most of that code could be extracted into smaller, readable methods. PositionBoardCards(); PositionCardsInHand(); UpdateCardMovement(); etc.

Because of this the main UpdateCards() function is now very easy to read, you can see exactly what it is doing where and if a bug turns up or a new feature is required its very easy to just go straight to the piece of code and get the job done.

Naming and Readability

Readable code is easy to work with code. You know where you are, what is happening and thus what WILL happen if you add or change something at any particular point. To that end the simplest refactor and one of the most useful is the rename. Do you have a variable called xt? That is not useful - what is it doing? It makes perfect sense to you right now but you'd be amazed how it wont if you come back to it in 5 years. Let alone if someone else comes to look at the code.

With the advent of super powerful IDE's (big Visual Studio fan here), incredible Intellisense and auto completion there is no need to use tiny short hand for everything. It saves you nothing. Perhaps you're a javascript developer and you're trying to save on filesize? A worthy aim but write good readable code and then minify your js using a tool later.

There are of course conventions that are completely fine. Everyone knows that N in a loop is a counter and X/Y/Z in iterative loops are self explanatory. But things become a bit vague when you write xs instead of XSpacing and cw instead of CardWidth.

I am also a big fan of keeping things in separate files. Each class, each enum etc I move to an external file with the same filename as the class or enum inside it. Visual Studio has good integration for this type of system too where if you rename a file in the Solution Explorer it will automatically rename the class inside and update references to it. It just means everything is always easy to find.

Refactoring is a giant topic and I could be writing on forever about it. There are several books you can buy and a plethora of videos on youtube that can help explain it all in more detail, I really recommend having a look. But if I had just been covering the 3 headings above when working on my original game the changes I'm making today might well be done already.

Look after your code and it will look after you :)