Fighting the Last War
One of the more interesting errors in planning I’ve run across is called “fighting the last war”. This means believing in and using a strategy that has been successful in the past but is no longer a relevant tactic in the present. A famous historical example of “Fighting the Last War” is when France’s military leadership built the Maginot Line.
France built a series of concrete fortifications along their border with Germany: the Maginot Line. They didn’t fortify Belgium. This was the winning move for WWI - where armies moved slowly and concrete fortifications slowed those armies down. But, it didn’t help in WWII, when Germany flanked the Maginot line and invaded from the North, from Belgium.
I’ve made this error a bunch of times in my career as a developer and entrepreneur. I used successful tactics from previous successes when they no longer applied (or the converse, over-corrected failed tactics.)
Async is Always a Good Idea, Right?
I once built a service for a mobile application that needed to make network requests to some third-party services. After some initial prototyping, I realized I needed a more scalable solution besides just tying up a bunch of threads. Asynchronous IO was clearly called for. The back-end services I was integrating with were very slow (over a second) and with even a minor amount of traffic, this was hurting overall performance. This new project was based on Ruby, so I used EventMachine (think Node.js for Ruby) and was able to build a highly scalable service.
Of course, on my next project (also Ruby, but using Rails this time, though no third parties were needed to integrate with) I decided I needed to have this same capability in Rails “just in case”. I spent days of development time understanding Rack and Unicorn and Rails internals all in order to build the componentry that would service any request in Rails using this same performance optimization. And, of course, I didn’t need it. This project didn’t integrate with any back-end or third-party services, and it didn’t have a huge load.
Most of my requests just needed to hit the database (which I could have also turned into an asynchronous request with even more work, but how many libraries did I want to fork?) In the end, I spent days building something I didn’t need, and could never hope to use.
Burn, Baby, Burn
After I left Microsoft, I co-founded a startup with some friends. We raised a large seed round, and then burned through it very, very quickly. We were trying to build a new OS with a web-based user experience (similar to what Google later did with ChromeOS) and custom hardware to go with it. This was a huge project, and required a massive amount of capital. It was 2008, and we were unable to get more funding after building some initial prototypes.
At Microsoft, this same pattern made me successful. I could form a small team, build an initial prototype, and then get additional funding. Politics mattered almost as much as engineering. But at a startup, this can be deadly behavior. You need to watch the burn rate carefully, and have a clear plan for raising more money (either by selling to customers or raising from investors.)
Tests Will Save Me
I once inherited a Web application used to track customer information (a highly specialized CRM for a very specific industry vertical). This application had many issues. For instance, the initial developers had used a proprietary database ORM library that generated a lot of code: literally thousands of lines of code for each table. The generator no longer worked after years of underlying technology upgrades. Unfortunately, the vendor was out of business. It was no longer possible to generate code based for database updates. Instead, we needed to write thousands of lines of code for tasks as simple as adding a column.
But the worst issue of all was the complete lack of tests. And to my eternal shame, I didn’t refactor and make tests. Instead, I kept things as they were, and manually tested the application. This, of course, meant that progress (already slow from issues like the database library) moved even more slowly. And, of course, I shipped bugs into production that should have been caught.
On my next project after that, can you guess what I did? I thought I learned from my mistake. I wrote tests: lots and lots of tests. I ran code coverage reports and targeted over 100% code coverage (meaning testing some paths more than once with different input.) I was fighting the last war, though. And I was killing my productivity. Refactoring became a pain. Simple changes became herculean tasks, Tests were written in such a way that such changes broke them.. One line of application code changes required ten lines of mocking, database fixtures, and test code changes.
But, I didn’t need such massive code coverage at this early stage. Even though I did need tests, I suddenly found myself swimming in too many tests. Most software needs to be flexible to accommodate new features and changes. I needed to take feedback from the reviewers and make changes quickly. But my beautiful, wonderful tests were hampering me. I was testing too much, too soon.
How to Fight The Next War
Like most people, when I start a project, I bring all my previous experience with me: the victories and losses and so-called learnings. But sometimes I’m so obsessed with what worked or didn’t work last time, that I make mistakes. I’ve found three techniques to help me here.
Do Just Enough
The minimal viable product works for more than just startups. It can work for any software engineering project (startup or not.) Martin Fowler talks about a similar concept called the Sacrificial Architecture: the design the gets you up and running, but you plan to replace. This can be useful in understanding the heart of the problem.
There is an important concept here: do just enough. Make a list of requirements from your user’s perspective, rank those. And then select a set of highly important ones. Do those quickly (over the course of a day or a week.) Obsessively focus on the user’s requirements and do only the amount of work to meet those.
Never Stop Learning
Your experience - the wars you’ve won and lost - is critical. But so is the experience of other developers. Instead of relying solely on your own knowledge, learn what has worked for others. Read books on software engineering technique, not just technology.
Plan Early, Plan Often, Get Your Plans Critiqued without Mercy
A plan can go a long way towards showing you the flaw in your approach. Make a detailed plan of how you will build your product before you build it. You will in no way be able to get it right, but such a plan will illuminate what you don’t know. Show it to others. Make sure they give you genuine, quality feedback. Accommodate criticism and don’t take it personally. Thank your reviewers.
If I had done this with my asynchronous Rails project, I may have realized that I would be unable to achieve what I wanted to in a reasonable time frame, and I’m sure the other developers I respect would have told me this up front.
Special thanks to Amy Bond, Bryan Costanich, and Ian Geoghegan for reviewing earlier drafts.