A coworker recently found an “interesting” problem: A database starts to have errors once a table reaches more than 500 columns. Well, duh. I mean, what kind of idiot designs a schema that way?
Let’s do some “Git blame”…oh.
It’s the guy sitting next to me. And I know he’s not an idiot. So, what happened? How did this get so awful?
It’s Not as Bad as It Seems
First, let me say this: In actuality, everything about this situation is good. This was an unqualified win. I can prove that with the Git logs. Logs indicate that this project hasn’t been touched for ages. It’s been out there in the wild serving customers for years without anyone having to muck with it. Would that all of my projects had that level of success!
This was exactly the right design for this situation. It was a cheap attempt at shipping the product and solving the user’s needs for a while–and it worked 100%. If it’s dumb and it works…it’s not dumb.
A perfect schema might’ve taken longer to implement, required more complicated admin pages, needed careful indexing and caching to be performant, complicated validations, or any of a hundred other things.
None of those things are bad! But none of them are free, either. In this situation, a better schema or a “perfect” answer would have had lower ROI than this solution did.
Things Get Bad a Little Bit at a Time
Question: How does a project get six months behind schedule? Answer: One day at a time.
No one woke up one morning and said to themselves, “Self, let’s go see if we can hit MySQL’s row size limit today!” No. What happened was that they had a dozen fields that needed to be included, and they were all 1:1 with the rows. The author added a dozen columns, got the work done under budget, and moved on.
And then a year later, more data needed to be added. And then some more, but as it was a small support request it didn’t have the budget for a real refactoring. And it worked…until it didn’t.
There’s No Such Thing as Bad Code
“This is bad code and you’re a bad person for having written it.” We all know it’s not nice to say such things. But it’s easy to say similar things and make similar implications. When I’ve been tired and grumpy, I’ve caught myself doing that.
There’s no such thing as bad code. There’s just code that’s a poor fit for the problem today—but it probably was a good fit for the problem, the knowledge, and the timeframe for which it was written. Keep that in mind when you talk about problems, and have some empathy for the myriad pressures that the original author was balancing.
The Trick is Knowing When to Bite the Bullet and Make It Better
We do things in suboptimal ways for all sorts of reasons:
- We’re uncertain what the real requirements are and need to try something before we invest heavily.
- We don’t have the time or a sufficient need right now.
- We’re stuck with some piece of legacy technology that doesn’t work the way we want, and it’d cost too much to upgrade right now.
- We just don’t have all the data we need to make a final good decision.
These are all good reasons! The enemy of good software isn’t “starting with something imperfect.” The enemy of good software is “never questioning the imperfect.”
Next time you see some absurd hack, ask yourself, “How far can I push this before it blows up in my face?” That’s a little tongue-in-cheek, but it’s a good question. Will that suboptimal query fail when you have five customer records, or will it work until you have 5000? And how fast are you acquiring new customers?
Maybe this doesn’t really matter today, or even this year. Maybe just make a note for later and review it when you plan development for next quarter.
You can’t make good micro-level decisions about code without the macro picture. I’ve written before about the need to understand the big picture when you’re feeding these decisions. It’s OK to push out a refactoring to the next release cycle.
You can’t always know the things that are going to come back and bite you later, but sometimes you do. When you know, drop something in the backlog. You can reevaluate it at regular backlog grooming sessions throughout the life of the project. This allows you to tackle things when they’re going to make a difference, and not before.
Don’t Plan for Perfection; Plan for Contingencies
Track problems and todos you know about, buffer your budgets for unknowns, and actively monitor your deployments for problems. Don’t plan on being perfect–just plan to be responsive and flexible.