I write code.
Scratch that; I write lots of code. More code than most programmers I’d wager.
Not because I’m more productive, but because I’m rarely smart enough to dream up a good implementation the first time around, even for small things. I’m much better at critiquing an existing solution. And so I write, aggressively rewrite and refactor until satisfied. No surprises then that I believe strongly in unit testing and iterative development. Recently I was surprised to hear a colleague say that she rarely revisits or rewrites her code, because it’s such a core part of my development approach. But it wasn’t always.
One thing I used to do when creating software was stress about every design choice (classes, methods, interactions, etc.), particularly in cases where I wasn’t intimately familiar with the problem domain. I’d worry that at any moment during development – particularly late in the game – I’d hurtle full-speed into an unforseen shortcoming in the guise of a metaphorical brick wall that would annihilate the racing car that was my design. As such I’d try to plan everything up front, think through every possibility I could dream up, and aim for perfection because I was sure great programmers got things right the first time.
In many ways I was probably setting myself up to fail often, since I had it in my head that it was possible to build the ideal solution from the get go without necessarily being a domain expert. Having to later change the design meant I had failed, because a truly great programmer would foresee the minutiae of consequences emanating from early design decisions; I just had to practice and try to become a great programmer so I could do the same.
Over the years my approach changed considerably. I learned to accept that I couldn’t know what I didn’t know about the domain, and as I wasn’t omniscient I couldn’t foresee all consequences current choices might lead to down the track. On top of that I found that the best way to become intimately familiar with the problem domain (specifically as it applies to the software being developed) was to, paradoxically, build the software. And the utopian dream of a first-time perfect solution that was just beyond my current grasp faded somewhat.
These days I try to stick to larger architectural decisions up front, and then create as simple a solution as possible with the expectation that it will be viciously reworked and will probably end up looking very different by the time it’s complete (for the current version of course, since software’s never really finished). If there are elements of the problem domain I’m not sure about, I feel confident that I can rework the approach to incorporate them.
Even if it turns out that I overlooked some key element, necessitating re-jigging much of code (thankfully this hasn’t happened yet), in creating the initial solution – and perhaps more importantly in reworking it – I learn many of the nuances of the problem domain that I couldn’t foresee up front, leading to better understanding of the problem and an overall improved solution.
Now it would be remiss of me to say all this without acknowledging a few things. Firstly, most of my experiences are with small teams writing bespoke software for a small user base. I imagine that larger teams make aggressive design reworking harder (particularly beyond well-defined interface boundaries), while a large remote user base can work against tight iterative development.
Secondly, this change in perspective didn’t come about in a vacuüm. As I began to learn and apply SOLID principles more (and lord knows there’s still plenty of room for improvement), I found that potentially fatal ‘brick wall’ problems were often reduced to niggly speed humps. Designs became more malleable, lowering the barriers to refactoring (as did the availability of powerful refactoring tools), and the need to get everything just right up-front was lessened.
None of this is really news of course; agile practitioners have said this stuff for years. What does surprise me a little is that while there’s plenty of iterative development and refactoring on the ‘macro’ scale, it’s applied less often at the ‘micro’ scale of a developer reworking their own code in the way that a sketch artist might start a rough sketch and then gradually refine it to a portrait all in one sitting.
I also no longer think that great programmers design perfect solutions up-front and foresee all consequences. Instead they are adept at creating designs that flex and grow without breaking as requirements change. And I still hope one day to become one.
- Characteristics of Great Programmers (dotnet.dzone.com)
- Top 10 Traits of a Rockstar Software Engineer (jelastic.com)