Hack Then Refactor: Going from Comments to Classes*

Sep 30, 2013

Rough Drafts Should Look Rough

One of the common challenges holding up beginning game developers, much like it holds up beginning writers, is trying to get everything down right the first time, perfect immediately, as soon as it’s “on the page.” A rough draft ought to be rough, whether it’s written in code or in words. It’s the best time to be taking some chances, hashing out some incomplete thoughts to see where (if anywhere) they lead, getting it on screen as typed characters to then figure out in the following steps step how it might be better rearranged, chunked, and reorganized.

Schooling introduces everyone to some basic process steps to apply this process to writing. For practical game coding we’re mostly on our own.

Functionality First Then Revise and Edit

One process for doing that in code is something that I mentioned briefly in the refactoring entry.

Broken down here into a step-by-step expansion of the strategy:

  1. Give up pretense for elegance or doing things the proper way and just embrace the hackiness while figuring out what you want. Even if this is the first step of a larger projects, treat this step like game jam or timed programming challenge, just get it working and out of your head. Don’t worry yet about optimization, what you’ll keep, code readability, or organization, just get it working to be able to make a more informed choice about what’s worth keeping. During initial prototyping treat it less like an engineering task and more like brainstorming, sketching, or outlining. My early iterations when getting a game working can tend to look a little like this (though handling all 4 movement directions instead of just 1, and usually for a less trivial interaction):
    //// DISCLAIMER: this is just pseudocode, not valid C++, AS3, Java, etc.
    int playerX, playerY, enemyX, enemyY;
    int enemyAlive = 1;
    int playerHealth = 1;
    Bitmap backgroundFile; // loading jpg elsewhere in code

    function everyFrameTick() { // incomplete example for illustration! if(keyboard.holding(RIGHT)) { playerX++; } if(playerX > screenWidth) { playerX = screenWidth; } if(distance(playerX,playerY,enemyX,enemyY) < collisionRange) { playerHealth--; enemyAlive = 0; } drawImage(backgroundFile); if(enemyAlive != 0) { drawCircle(enemyX,enemyY,color.RED); } if(playerHealth > 0) { drawCircle(playerX,playerY,color.GREEN); } }
  2. That first step can be good for ideation, but typically results in large undifferentiated blocks of code serving mixed purposes. This definitely doesn’t scale well to a larger, distributed, or more elaborate program, but while just getting traction it has its advantages. Having all the code in one place like that can make it very quick and easy to make sweeping changes to what you’re doing and how, ways that data affect one another, and there’s no time spent hopping between files. The code at this point is small enough to keep mostly in mind all at the same time, and using CTRL+F in the text editor to jump to spots works fine but probably isn’t even necessary.

    Don’t beat yourself up over a rough draft looking like a rough draft. That is how it should be. Add short, one-line, functionally descriptive comments breaking up sections of that code. “Noun verb” form works best for this, such as “// application setup” or “particle effects update.” Rearrange line order to better group them if necessary without changing functionality. One section might be “// player movement” or “//// player bounds checking” – using multiple comment pairs as in that second example to mark subsections just as you would for an outline.

    int playerX, playerY, enemyX, enemyY;
    int enemyAlive = 1;
    int playerHealth = 1;
    Bitmap backgroundFile; // loading jpg elsewhere in code

    function everyFrameTick() { // incomplete example for illustration! // wipe screen by redrawing background drawImage(backgroundFile); // enemy movement and draw //// collision checking between player and enemy if(distance(playerX,playerY,enemyX,enemyY) < collisionRange) { playerHealth--; enemyAlive = 0; } //// draw enemy if(enemyAlive != 0) { drawCircle(enemyX,enemyY,color.RED); } // player movement and draw //// player keyboard input if(keyboard.holding(RIGHT)) { playerX++; } //// player bounds checking if(playerX > screenWidth) { playerX = screenWidth; } //// draw player if(playerHealth > 0) { drawCircle(playerX,playerY,color.GREEN); } }
  3. Carve that larger body of code into functions named after the comments you made to split up groups of the code. The code going from “// player movement” up to the next “//” top-level depth comment should be moved to a new function called “playerMovement()” which, in the example above which had a subsection marked as “//// player bounds checking” could be further split to call a function called “playerBoundsChecking()” and so on. If variables are needed among both…

(continued in ebook)

*This entry is now in the Videogame Developer’s Strategy Guide, free with Gamkedo Weekly Check-In.



Learn and practice team game development with Gamkedo Club.
Membership worldwide. Professional support. Proven process.




Subscribe by e-mail to receive weekly updates with Gamkedo.Community interviews and YouTube training videos for game developers!



2 Comments

  1. I don’t *think* that there’s much difference between game software and most other types (I’ve worked in finance, mapping, military radar) in terms of how much they can sometimes rapidly change early on based on feedback from seeing it it action, pulling together basic functionality just to get something basic working.

    Addressing this is precisely what the agile and lean movements are all about. Unless the team is deeply familiar with the problem area, and with the implementation strategy and technologies being used, requirements will always be massively incomplete and woefully incorrect, and initial designs will always be inappropriate and misunderstood, no matter how hard you try. It’s the single biggest problem in all types of software, not just games. You address it – just as Chris says – by getting started, trying something out, and iterating based on feedback. Faster feedback cycles are better, with no lower limit. Seconds is better than minutes, and on higher, fractal levels, days is better than weeks, etc.

    So the justification for rapid prototyping like this isn’t ‘being a game’ precisely. I think it’s more accurate to say that it’s valuable whenever you don’t understand the requirements, or how your design is going to work, or how you’ll interact with an important new library. Whenever you’re deeply unsure.

    This more precise delineation has an important flipside. Prototype-and-tidy is not always appropriate for game dev. It’s only appropriate for the parts of your game which you are massively unsure about.

    An important part of programming is to conquer complexity by partitioning large problems into several smaller ones. A large program will be split into smaller sections. Some of those areas will be more unfamiliar to you than others. Prototype-and-stabilise is appropriate for the parts you are least familiar with. For the other modules, the ones you are more sure about, it’s more appropriate to do it more formally, with a smidgeon of just-in-time planning and design. The ratio of ‘scary uncertain modules’ to ‘plannable code’ will vary depending on your experience in the problem domain and with the technologies used.

    How you choose how to break your program up into small bits is key. Some subdivisions will fail to produce ANY modules which entirely contain code you are fairly sure about – the uncertainty will be spread amongst all modules. I propose that there are other subdivisions which can limit the uncertainties to particular modules, leaving behind some modules which are full of nothing but easy code. Indeed, I think that choosing modules such that this is the case – such that the uncertainties are contained within particular modules – is the single most important factor in program design. It’s very difficult to see and impose this structure correctly after the fact.

    I can’t help but think the process Chris describes is inevitably biased towards producing chunks of code which divide the program temporally. First X happens, then Y happens, so we end up with two chunks of code X and Y. I absolutely agree with what Chris says about then looking for commonalities to group together proto-classes or modules, but I think that it’s difficult to do this correctly at this stage.

    A classic paper on the topic ‘On the criteria to be used in decomposing systems into modules’ by David Parnas (http://www.cs.umd.edu/class/spring2003/cmsc838p/Design/criteria.pdf) compares the same program broken up in two different ways, and concludes that temporal subdivisions are the least useful way to divide up code.

    Producing more maintainable code like this doesn’t just make life easier in some distant end-game, when you’re just fixing the odd bug after the program is released. It makes life easier through the whole lifecycle of the code. Eric Evans talks of programs that are designed will in this way speeding up as development continues, because the initial coding produces design abstractions that support the coding needed later on. This is in contrast to most projects, which slow down as development proceeds, because of the increasing complexity of interactions between poorly-chosen modules.

  2. Joel Shapiro says:

    Really well written with some good ideas. More ideas would succeed if they were “hacked and then refactored” like you suggest.

Leave a comment

Comments Form
  • Your email will not be used for any purpose other than to update you in replies to your comments. Your website address will be shown as a link from your name with your comment. Your profile photo is auto-magically provided by Gravatar.

All contents Copyright ©2017 Chris DeLeon.

Site production by Ryan Burrell.