One of the things that's interesting about this - and is good meat for further discussion - is how to react when you find yourself on a team with someone who's clearly fighting the last war. I think it's inevitable that people end up fighting the last war, but I'd love to know if there are ways to break the cycle for someone once you see it happening.
I'm kind of doing that right now--we've got that issue pretty badly, actually. It's surmountable, but annoying.
Here are some observed causes or environmental factors:
Using an older or low-level language. If you're doing code in asm/C/C++/Matlab/Fortran (arguably Perl, PHP), odds are you aren't in a position to easily pull in other people's solutions. That means that you are probably trying to reinvent the wheel, because you don't know of a pre-existing solution, because you don't want to pull in the massive baggage of other solutions, or because you enjoy making the wheel over again yourself. Newer, more abstract languages don't have this problem, mostly I think because they come from a time of easy collaboration via the internet. The whole gem/npm/hex ecosystems simply couldn't be possible without easy internet access and github. I'm going to call out C++ specifically here, because using that language with old code is terrible because odds are that, even if you are doing things correctly, the library author isn't (from a strict software engineering standpoint).
Not understanding your problem space. Oftentimes, being overly conservative about your design requirements (we must be webscale, fault tolerant, absolutely have to support XYZ vendor integration) means that you overdesign--and when you do that, you are more likely to be fighting old wars. If you really grok and have cleanly defined your problem space, you know where you can cut corners, instead of overbuilding based on previous experience.
Not following de facto standards. There's a bajillion ISO standards for everything you might want, and a lot of people will glibly explain "The great thing about standards is that there are so many to choose from". The problem is, those people will either go on to ignore standards in favor of their own (usually created to fight the last war), or pick one that isn't being used (usually something that is fighting a war that was already won/lost). The only thing worse than fighting the last war is fighting the last war from the losing side. JSON vs XML is a great example of this, as is RFC3339 vs ISO8601 vs <insert wrong in-house way of doing time here>. Another great example is REST vs SOAP, or Unix Way vs Not Unix Way, or ORM vs Not ORM.
Not being lazy. The best wars are never fought, right? There's nothing worse than a developer who is super industrious and never pauses to think "Wait, is the problem I'm solving just a rehash of something I've done before, or is there a deeper conflict here?". Fighting the last war happens when these people continually reiterate on the last battle ("This time I've got time right!", "This time I've got better performance!", "This time I've got the right object model!"), all the while missing the important thing ("My customer still can't do X").
Not sharing knowledge. Probably the biggest one of these other than the previous one is the problem of developers not letting other developers know about what's been tried before, and why it did or did not work. We already hate rehashing solutions--so if presented with a module that isn't explained, are you going to rewrite it, or just accept it? Almost all the developers I know would prefer to rewrite it (and only experience and wisdom holds them back from doing so). When you've got a large codebase, and you've got like three different exception handling systems built by different groups because they don't talk, you've fought the last war three times.
From working with an old PHP codebase, I'd like to offer: "Design for deletion."
This has important differences from "design for modularity" or "design for future change" or "design so someone can swap it out", because those phrases tend to make developers go off and engineer extra stuff, under the assumption that their current paradigm for how the software operates is going to continue to be accurate even if their implementation someday needs replacing.
... But what happens when it's not just "this code sucks", but also "we shouldn't even have any code that's doing this task"?
Thanks. When it comes to "easily refactor" I think there's a distinction to be drawn between "hard but understandable and verifiable" versus "maybe-easy but with unknown surprises."
The difficulty of replacing or refactoring code should come from the "inherent complexity" of creating its replacement, not from the "incidental complexity" of discovering its boundaries and influence.
Nah ... not using async, not running with tests, these aren't "let's build a Maginot line" mistakes. These are "let's not make sure our troops can run 10 miles and shoot accurately" mistakes.
Sure there are plenty of strategic mistakes that will doom any army or enterprise - but the biggest one, the granddaddy of them all, is only listening to half of the proverb "hire great people then get out of their way"
So many generals want to be sure everyone understands it is their clever tactics that won the war they forget the lessons of generals who really do - logistics and economics. Everything is subservient to getting a well fed and trained soldier with a gun in his hand at the point he needs to be.
For a good example look at Spolsky - hiring good people, fixing up nice offices, handing over lunch - probably there is some office manager who acts as a general problem solver for the devs and magically makes boilers and landlords and IRS problems vanish.
This is a problem of planning; I don't think more or more frequent planning is the answer.
Instead you need to make the minimum viable thing, even more minimally. Make sure the very first iteration is end-to-end usable to solve a real problem. Then you'll notice if you can't use async on the backend before putting async everywhere. You'll notice if you're testing too much. And you'll have a better understanding of your burn rate because you've done all the tasks, including the things like design and hosting and legal that can easily be forgotten.
Great article and topic. But just FYI, I don't think building a Maginot Line actually qualifies as "fighting the last war". As the author explains, the ML actually worked -- the Germans had to invade through Belgium! And had they built the ML all the way to the sea, the blitzkrieg would have failed. Perhaps a better historical military analogy would be that of Billy Mitchell and the rise of Naval Aviation (http://en.wikipedia.org/wiki/Naval_aviation#Interwar_period).
Anyway great lessons here -- thanks for posting.
I groaned when I saw the Maginot Line thing yet again. People, your grade school history teacher lied to you!
There was nothing wrong with the Maginot Line. As part of the overall plan they intended to stop a German invasion through Belgium in Belgium. They had an agreement with the Belgians which had Belgian troops using forts and pillboxes to slow down the German invasion until French troops arrived.
But the French lost the war before it started when the Belgians decided they didn't want to fight the next Great War in Belgium and pulled out of the agreement. And they did so far too late for the French to make other arrangements.
I like your reference to naval aviation, which is a much better fit.
My take away from "Do just enough" is the flip side - throw it away and start over. The second part is just as important as the first or you end up with crap prototypes in the heart of your solution. The first time doesn't have edge case handling and will be insecure, so toss it out when it starts to become a product, if not before.