practice makes perfect – a ramble with bad capitalisation
so I've been reading this altdevblogaday thing and couldn't resist having a go! but, oh the agony, what to talk about? So, I was sitting there, on a sunny beach as it happens, knitting (I kid you not), very badly, and I realised: like playing a musical instrument, like any learnt skill, in knitting, muscle memory makes a huge difference. practice makes perfect. or at least, practice makes less crap.
and this is true of coding, and game development, at all levels - from code to architecture to design to vision. one of the things I've noticed about great coders (of which I am not yet a member) is that their code smells really, really nice. they just produce code that is readable, less buggy, robust to changes around it, easy to refactor - so many things. why is this? it's extremely hard to quantify or enshrine in a set of rules - many people have tried, with varying degrees of success.
It seems to me that as you practice, as you get good, you develop a spidey sense that tells you when you're going down the wrong route. the interwebs is LITTERED with advice about patterns and antipatterns - reduce state! don't duplicate code! dont use goto! - but for each of those rules, there are counters: duplicate state for efficiency! duplicate code to reduce plumbing! do use goto to avoid horrid indentation or more clearly show the execution path! - etc ad nauseam. The real key is not the DO or the DONT - the point is that great coders develop this weird, uncanny, freaky sixth sense about when to make a rule, and then when to break it. In that sense, this article/rant is a sort of unauthorized spiritual brother of Dylan's excellent earlier post on the importance Aesthetics in game coding (http://altdev.co/aesthetics-and-the-art-of-being-anal-for-the )
The coder I think of most in this respect is mediamolecule's David Smith. He's responsible for his fair share of smelly bugs, but in general his code has this - I dunno - gorgeous shape to it. A delicate fragrance of beauty. It's often so succinct, so short and readable, and not in the perl-golf-line-noise kind of way, that sometimes I'm left browsing around it looking for where the CODE is. I expect to find some horror lurking at the deepest recesses of a physics engine or hand written parser, and instead find a series of neat interlocking parts with no fat and no mess. By the way, he's quite a good knitter & crochet-er too.
I first met dave at Lionhead studios, where he was a young gun coding away on Fable and I was a slightly more jaded but equally young gun experimenting with R&D projects. It was back in the era when physics engines were all new and shiny, supposedly impossible to write (oh the classic FUD of middleware. but I digress), and as a result there was a slew of pretenders to the throne that now seems to be safely sat in by Havok (which is, by the way, by all reports, a very fine piece of middleware - though I have never used it myself). Having woefully and n00bishly bought into the FUD, I wanted to pick me a middleware to solve all my physics woes. But which one? I knew dave's resume to lionhead had included absolutely no industry experience but also an exceptionally interesting physics based driving game - so I sent him an email asking him if he'd help me evaluate various physics middleware that I was contemplating using for a toy game project called 'the room' (consisting at the time of only me and an artist - Mark Healey, also a founder of MediaMolecule). Dave replied about 48 hours later with a rather scathing deconstruction of my front runner's middleware demo program (which was, if I recall, lots of little chairs falling into a messy pile at what seemed like an impressive frame rate). Along with his critique came some source code and an executable - that recreated the demo, faster, with more stable stacking, and, well, almost no code. Or at least, none that smelt bad. I was flummoxed. Reading his attached code, there was just so little there! I could understand it, or so I thought. It was actually easier to read and understand, than the code to exercise the rather crufty API of the (now-defunct) middleware. of course it captured a bunch of subtle and not obviously visible experience encoded in the order he chose to integrate, or the particular magic values, or whatever. but I think it was more the aesthetic (there's that word) of the code, and the approach to it, that really, really appealed more than the fact it worked so well, that 'got' me.
I've learnt a lot from dave since (and violently disagree with him often, so in case he's reading - you're not perfect dave! you're wrong! etc) - and for the longest time I tried to emulate his style: state is the root of all evil, get rid of it, or encapsulate the places that change it; OOP is the devils work - especially in a world where most of the meat of the code involves two or more objects interacting, rather than one object rendering itself as a webpage; simple, functional-where-possible and direct-procedural code is beautiful; but it was only when I started internalising these ideas to the extent that they formed an inner ear buzzing noise whenever violated, that I started to improve as a coder.
You see, the thing is, it's one thing to say 'practice makes perfect', but its another to know WHAT to practice. If you've ever learnt a tricky piece on the piano, you'll know the temptation when practicing to play the bit you can already do, start feeling really awesome because you're definitely the next Glenn Gould, and then hit that passage that you can't play yet. You either go on, or go right back to the start so you can play the easy bit again. ('ah, it feels so good') That's crap practice. Good practice is really hammering on the bit you CAN'T do, until you understand what you need to improve, and then, actually improve. Blood sweat and tears is part 1, knowing to apply it on the painful bits is part 2. Part 3 is presumably profit...
I'm rambling again. Sorry. Here are the things I try to think about when I'm practicing coding, to guide me towards the hard bits - which practicing by the way, includes professional coding just as much as hobby coding: (this list by the way serves as an internet-remembered list of things that I will come back to and curse that I dont even follow my own advice. hypocrisy!)
* firstly, and MOST IMPORTANTLY, remember: the cost of re-doing something is TINY. code is CHEAP. really, really cheap. Especially if you wrote it, even better if recently. Lost your harddrive? devstudio crashed? GREAT! You'll do it better second time round. Corollary: there is NO SHAME in revisiting old code and rewriting it as you go. In fact, do that lots! And as above, go back over the MOST PAINFUL bits, not the least painful ones. this isn't fancy refactoring, this is just iteratively noticing a spray of x+1s and realising that if you just stored x+1 instead of x, they'd all go away. doh! This is realising that that horrific mess of 5,000 lines of C code has a user-observable result of rand()%3, and therefore it should just be replaced with... rand()%3.
This is realising that source control has elephant memory, and it's time to delete that function you never use that's there in a comment 'for reference'. DELETED.
* the above point works at all scales too: single functions, single lines, and entire systems. NEVER, EVER, EVER be afraid to rewrite a whole system if it is causing even moderate amounts of scary undefined pain and crash bugs. I can't stress this enough, and you should to yourself, and your peers, and your boss, and your mum, and your cat. the cost of just soldiering on with poor or ugly or legacy or messy code 'because it's really complex to rewrite' or 'because nobody really understands it' is HUGE AND ONGOING, and the cost of rewriting it is probably MEDIUM AND SHORT LIVED. even the new bugs you introduce, will be fresh and relevant and you'll be around to fix them. infinitely better. Unfortunately, this is the most terrifying maxim to actually follow when in the thick of development, and I have violated it enough times to know the pain. But every time I have gone ahead and rewritten, the short term pain has ALWAYS been justified. 100%. one day, I will learn.
* another point about rewriting: be prepared to know & love your data structures. some of the most successful refactors (or, most successful codez first time around) revolve around having just the right data structure representing their state. a lot of people think of refactoring as changing variable names or moving where API boundaries are, but an equally valid type - especially in constrained environments like consoles - is to reconsider a system in terms of its data structures, and be bold enough to change them. perhaps you realise that a particular kind of hashmap would really beat the redblack tree you are using, or that those backpointers should not be needed, if only you could get rid of that api call that is the only one that needs them, or whatever. the resulting rewrite is typically extensive but extremely satisfying. the spidey sense (and reduction in lines of code) will tell you when you've got it right.
* be laser, laser specific. do not, ever, ever, code something more general 'because I might need it later'. NO NO NO a thousand times no. premature generalisation is even more evil than premature optimisation. see the hilarious post 'how not to write factorial in java' (http://chaosinmotion.com/blog/?p=622 ) for an eloquent example that is terrifyingly realistic. If you have a variable that is a bool now, but might be an enum later, dont make an enum now. make an enum later! leave it a bool! for the love of god! this is a corollary of the 'rewriting is cheap' point above - you can always go and change it later! when you do, you'll probably re-read the code and fix 3 bugs at the same time. next time you're coding new stuff, every 30 seconds ask yourself 'do I really need this NOW' and, if you're anything like me, be terrified by the number of times the answer is 'no'. side comment: when the underlying state changes to have more than 2 values and you need to turn it into an enum, a scary number of times people fall into the trap of just adding another flag / bool. now, instead of 3 states, the code has 4, one of which is probably invalid. It's probably harder to turn a bool into an enum than it is to just add another one, but when I say rewriting is cheap, I dont mean that you have carte blanche to rewrite badly or sloppily...
* a sort of synergy of all the points above is: almost all coding challenges reward clear thinking. you might think, in reading the above or any of a number of 'K.I.S.S.' (keep it simple stupid) rants that all code MUST be reducible to simple stuff. well, sometimes the problem at hand ISNT simple, it's hard. BUT... BUT... the common fallacy of the idiot programmer is 'in that case I'll muddle through with muddy code'. NO ALEX! STOP! there is something cosmic, and I am being totally serious here, but you will KNOW when you've got the right data structure for an algorithm; you'll know when you have the right mental model, the right factoring, and the right choice of state, when everything starts feeling simple, neat, gorgeous and the code just flows. conversely, if you are writing a function and find yourself 7 tabs indented, or choosing function names like EnableStopStartBeginEnd(), you have not got a good clear view on the problem. until that point, keep going back, clear your mind, write out notes, rethink the problem, turn it around, go looking for a different layout of data or a different data structure to store the bastard. the reward in code quality and maintainability is epic.
The bad smells that signal muddy thinking are numerous and obvious: magic numbers, unnecessary duplication (of code or state), confusing names, shifting expectations, code that doesn't work how you expected it to because you forgot what the plan was by the time you'd written it, lots of if statements (seriously!) or ugly control flow, lots of odd looking un-idiomatic bits of code, or odd off by one errors and a suspiciously high bug rate.... you get the picture. stop, try to clear out the head, look for the cosmic beauty, maybe sleep on it. I find myself endlessly ignoring this advice, and writing smelly code. Even when, at the back of my mind, I know it stinks. Normally, making it more specific, and simplyifying the state, or just giving up for a day, is a good remedy that I forget to take.
lastly, when coding, stretch yourself, don't just showboat. pick projects and areas that you've kinda done before, but not quite. you'll learn more!
Well, future Alex and current readers, I hope you found that list resonated to some degree. It's all advice as old as the hills, but I still seem to miss out on applying it in my day to day work. Now, to get back to that knitting...