Good code is bad, OK?
, 2,277 words
Brilliant code blocks brilliant products at an early stage
Reading the ever-wry James Old’s blog I stumbled on a link to an article on how bad code is the first step to good code, and it got me thinking about the misdirection in much of what is written about the journey to engineering greatness and technical success. Good code often doesn’t matter; it is not a critical ingredient for great technology, and can be harmful when building a new product. That might read like a bit of a generalisation, and a strange angle for someone who has worked to rescue many companies from poor technology. I shall explain.
At Reincubate we're focused on delighting our customers with the best products possible. That doesn't mean we're sending anything shoddy their way, but rather that we use an approach which lets us maximise time spent on product quality and functionality over anything else. Thus, we can satisfy customers, which is what we’re here to do. Bad code doesn’t necessarily lead to bad products, just as good code is not a sure-fire path to, or even a necessary ingredient for good products. Of course some elements, such as secure code, aren’t negotiable, but much of the rest is.
Method before value is lazy and weakens products and careers
In my own development as an engineer I became focussed on methodology, or the processes that one must follow to go about creating something which is technologically great. I could bore anyone on Java enterprise software development. I got into the Capability Maturity Model (CMM) and could talk at length about RUP, DSCM, Scrum, and XP. I was fortunate to get my first commercial experience in 2001, eight years or so before Agile seemed to achieve ubiquity. I was a techno-zealot, but as I developed in my career the experiences I gathered pushed me more and more towards pragmatism and away from technology for its own sake.
This progression is common in passionate high potential coders. However, many reach the “code zealot” / “engineer as an artist” / craftsman stage and get derailed there, failing to progress. It’s an important stage of the journey, but it's close to the beginning: somewhere an engineer might reach a third of the way to the 10,000 hours or so they need to achieve enduring, pragmatic mastery.
Rigorous practitioners of Agile miss the point
A lot of start-ups recruit for passionate engineers who want to do things the best way, often in a perversely rigorous Agile manner, because they see that as a weapon against larger companies in the war for talent. Moreover, it is cooler and more convenient to market for engineers around method rather than success. This approach works quite well to bring in naïve talent, who often end up being misdirected in their own career development by CTOs who themselves have not progressed or not had the benefit of building and shipping commercially successful products from the start.
The technical prima donna-ism that comes at this stage is toxic for organisations as they come out of head-down build mode and go into operating and exploiting a product. Companies can find they are reliant on a core of people who only want to do “sexy” projects, write green-field code and plug in new technology: a focus on the means and not the end. This culture makes it hard to extract value, and even to retain, hire and motivate team members. Engineering culture should emphasise enabling the business and solving problems, with a level of quality which suits the customer’s need rather than the engineer’s ego.
Process can block value creation
For an example of a business caught up in this one might look to The Trainline, a company famous for running some of the biggest Agile projects, and also notable for “starting again” with agile every five or six years. When there is evidence that a technology company is struggling to deliver value it is not surprising to see analysis of their engineering process and culture without reference to value creation.
Making my own progression from development manager to CTO, and subsequently to an entrepreneur I found that that quicker and the dirtier the code I wrote, the more success I had. Much of what I learnt came from getting it wrong – a lot! After more than a decade trying, it was only when I was prepared to create value and experiment much more quickly and pragmatically that I was able to create products which could reach millions in sales or users. My focus on writing quality code which was whole, tested and really fast did not matter; what mattered was delivering the majority of the functionality quickly in a way that worked well.
I’ve written before about the problems with development consultancies and this is another factor that makes them a poor choice for startup companies. Good consultants will tend to produce the sort of work one shouldn’t be able to afford in terms of time and process, and the bad ones should be avoided anyway!
Lean is good, but there is no canonical technique for creating successful technology
Many of those who write at length about process-driven technical success are detached from commercial technical reality. They might lead Open Source projects, for instance, but they are rarely proven commercially or in a sustained team environment. There are all sorts of arguments for Agile or iterative or test-first being tried and tested ways to build successful products, but one should be wary of taking them at face value.
Time and again, the most successful products I see are created by groups at level 1 on the CMM: chaotic, unrepeatable process and heroics. That’s not to say there isn’t value in being mindful of and experienced with methodology and process, as they will be important once the product has traction and scale. Fundamentally, though, the writing on methodologies for starting successful technology stem from a desire to be able to say there is a correct, organised and step-by-step way of doing things, when in reality it is as simple, unstructured and confusing as building simple things that people want very quickly. JFDI.
Test-first is great for products with traction, but a poor choice for new products
One technique I see held up as sacred is test-first: a laudable policy – and one that I used to be quite taken with – that engineers write tests ahead of writing any code. In many cases, pre-traction, it isn’t very helpful at all, and having the capability to run tests on users is much more valuable. Building a new product is not like writing software for a bank or an established business where the code might last years, change maintainers many times, and risk established revenue if it is fragile.
The reality is that there are two outcomes for the code written at a pre-traction start-up. One is overwhelmingly likely: that it won’t work, as the model doesn't work, it won't get traction and create value and the customers don't want it. In which case no-one cares, and the code gets thrown away. The other outcome is that code works, people like it, and that it gets used, but it probably needs adaptation and in 6 or 12 months there will hopefully be enough scale that the way it needs to get written changes. So the outcome there is also that the code is going get thrown away, it's just going to last a few months longer. So in neither case does it really matter. Of fragility, scalability, extensibility, none are pressing concerns ahead of getting commercial momentum. Instead, one need think about making the absolute minimum investment in creating and iterating on something which can deliver value.
Platform choice, architecture and security are still important
Despite my dismissal of some of these engineering best practises in a start-up environment, there are some which really do help. Platform choice matters because whilst one might throw away many components of the stack and rebuild them, it changes the skills required of the team, either in one scary big bang or as part of a hard to manage rolling process. Switching from Windows to Linux, or from MySQL to SQL Server isn’t easy on an established product, and if time has unwisely been spent optimising an early-stage product for a specified set of technologies it will be all the harder. Thus the value of designing for low scale, and resisting building for more than 10x or so the current load.
Another element that's quite important is separation or componentisation, but only at a high, near-physical level. Boundaries which are fewer and broader are likely to withstand more than granular ones whilst a product is being built, and subsequently can make for easier incremental substitution and change. Low-level componentisation within the software itself (such as dependency injection) doesn't really matter because again it’s likely to get thrown away and rebuilt.
Hopefully it goes without saying why it is important to bake in security from the start.
The latest technology is not the best technology
If a start-up team can claim without hyperbole they are using the latest technology or the best practise tools, they likely are stepping back to look and review too often, and have too much fat in their processes. It is a sign that they are not heads-down focused on delivery of functionality and value, but instead focused on the metabollocks that goes around it.
In ecommerce for example – a field which is 20 or more years old – there are few and infrequent pattern-shifting technology releases. The emergence of high productivity Web 2.0 frameworks like Django and Rails was important and changed the game, but both came out 9 years ago, and Microsoft’s own ASP.NET MVC framework took more than 7 years to release.
Adopting the latest and greatest comes with risks and drawbacks compared to the simplicity of tried and tested solutions. 2009’s Node.js framework is a lovely piece of technology and a good case in point. Some over-eager teams flocked to it on (or even before) its release for its speed in handling many simultaneous requests, but within a year or so there were much simpler event-based frameworks available for just about every pre-existing platform. The Node.js users were stuck with logic trapped in what was a beautiful but otherwise unconventional framework with little adoption and few skilled practitioners in the market.
Narrow the stack, track and incur technical debt with pride
Narrowing the scope of best of breed tools in one’s tech stack is a fine and pragmatic approach. There are only so many tools one can stay on top of. To name a handful in web development alone, specialist tools exist for search, testing, builds, parallel processing, data storage, caching, counters, monitoring, database scaling, cross server transactions, and session management. Spending time evaluating, learning and adopting robust and comprehensive 3rd party systems takes time from focusing on the product and on delighting customers. Having a team rush around to address compatibility breaking changes in new releases of these tools is like the tail wagging the dog. In many cases one can get by without highly flexible session management, or an advanced database engine. After all, in a low-scale environment, why would one really need an excellent session management tool, or extra couple of odd bits of database functionality for? Using a no-frills database with a few helper scripts for functionality which seems important and differentiated at the time is a big time-saver. Of course, wisdom and experience are useful for making the right decision on where to take these shortcuts and where not.
Taking shortcuts like these leads to technical debt. Deliberately incurring debt in a code base may not feel intuitive but it is important nonetheless. A team that that is not actively running it up in cycles is not lean enough for a typical start-up to afford, and absence of this debt suggests a team has bloated processes. Perhaps they have a beautifully crisp development cycle and a build environment with no niggles? That might be a nice way to get acqui-hired, but it is not a good route to success. A team’s technical debt wall should be a mark of pride, and a sign that they are delivering functionality to users and stakeholders at a good pace.
Less code, late process – and growth!
This article isn’t arguing against the need for good coders in growth companies. Technical mastery is vital to get the platform, architecture and security choices right, and understand where to take shortcuts. A mix of masters and juniors can be the right fit, whereas engineers arriving in the middle of their journey can detract from an organisation.
In almost all environments code is a means to an end, and at growth companies it is particularly transient. Metrics around code production and quantification of its quality are interesting, but ultimately tangential to the business. For engineers to progress meaningfully and capture more value for themselves they need orient their approach around delivering great products.
As young technology teams mature, process is rarely brought in at the right time: it is usually either early or late. Process-early is stymying whereas process-late leads to occasional breakage. Mark Zuckerberg’s “move fast and break things” approach is process-late in a nutshell, and at Reincubate that lesser evil helps us to deliver extra value to our customers and clients. To paraphrase Reid Hoffman, if a team isn’t embarrassed by their code they’ve launched it too late.