I used to work at an AutoZone when I was in college. I really liked working on my car (at the time, a Ford Taurus SHO, which I loved), and AutoZone gave me the ability to earn a little extra cash while learning a little more about cars, and by extension, a little more about tools and parts. Pretty much every part or tool we sold had several “grades”, which correlated with price and warranty. For example: the Gold battery wasn’t really any different than the Value battery, but it was more expensive, because it had a 7 year warranty vs a 3 year one. I’m sure some extra work went into quality assurance for those gold batteries, but they came off the same assembly line. They were functionally the same.
One of the biggest things I learned at AutoZone was a philosophy on tool purchases. Most people would come in and buy the most expensive tool or part they could afford, because they wanted to make sure that they had the highest quality available. These people wasted a lot of money: they would probably only ever use that torque wrench a handful of times. They’d be trading in their car 4 years before that battery warranty ran out.
That’s not everyone: some people are home mechanics and use these tools until they break. Some people keep their cars for 15 years, and that warranty makes sense. For those people, it made sense to pay the higher prices. But it’s also hard to know which group you fall in! Every tool has a different use case: even if you work on your car for 20 years you might never need a ball joint remover again. So how do you figure out what to buy?
The smartest mechanics figured it out: Buy the cheapest tool you can, and then when it wears out, buy the most expensive one you can afford. This allows you to test your usage before sinking a bunch of money into something you don’t need. But once that tool wears out, now you know you’re going to be using this thing quite a bit, and you should opt for quality.
We as software engineers (and especially software engineers who read blogs about software engineering, thanks for your patronage) tend to harp about wanting quality. That means a lot of different things to different people, but it generally boils down to spending a lot of extra time on design, reviews, build processes, full unit test coverage, requirements gathering, and other quality assurance pieces that ensure that we’re writing the absolute best piece of software that we can muster. But all that stuff costs us time, which costs a lot of money. As software engineers, we need to remember that we’re not really writing novels here; software is, at the end of the day, a tool. And since we make tools, I think we can take my auto parts philosophy to heart here.
Think About What You Need, Not What You Want To Write
Say we’re API developers, and we’re given a requirement to write a new set of crud endpoints that manipulate a table of widgets in a SQL database. And lets say our codebase is in Laravel, and this is an internal-only client: the only consumer is a team from the other side of the hall. They use this API to make sure we have enough widget supply for our co-workers to decorate their desks.
You want to make sure you write Quality Code, and so you set out designing your new endpoint with that in mind. You create your controller stub, and then write unit tests that fail for each method. And then you know that you don’t want your business logic in the controller, so you create a WidgetService for the Widget logic to stay in. You of course then need to write unit tests for this class: we need full coverage. We need to make sure we’re doing strict input validation: you never know what the client will send you. We also need to make sure that if the Widget Logic ever fails, we set up logging and error handling so that we can solve our issues quickly. We definitely need to aggregate these logs as well, so we can measure overall usage of our Widget controller. And if there are any fatal errors, we need to make sure someone gets notified: lets add in a Slack notification. And at the end of the day, we definitely need our QA team to test all of this thoroughly, we don’t want anything falling through the cracks. Lets make sure we diagram out the new functionality, too, so that later programmers can understand how our Widgets are working. Oh, and we’d better set up an automated deployment plan for this service: we don’t want any downtime.
And, yes, all of those things are signs of quality code being written. But none of that is what was asked for. We’ve added several requirements to our original project, all of which take time, effort, and money to complete. And at the end of the day, what exactly does it get us?
Do we really need to know how many times people asked for widgets on their desk? We know pretty much exactly what the guys across the hall will send us, don’t we? Wouldn’t the client team across the hall just send us a message if they find an error? Do we really need a full regression test suite on a simple controller? Is another developer really going to have a tough time figuring out what this code does?
This is obviously a contrived example, but I think the point holds. We don’t know that we need all that quality assurance, and because we thought it was the best way to write it, we added possibly weeks of work to something that could have been written and stood up in about an hour.
No, You Shouldn’t Write Crappy Code Instead
Please, don’t take me the wrong way. I’m not arguing that you shouldn’t care about how your code looks, acts, and is tested. You absolutely should care, and it’s really important for maintainability. My suggestion is that in most situations, we should focus on writing exactly what is asked for. Write the simple thing! And once that is no longer good enough: (something breaks, it starts getting used differently, etc.), then it’s time to start aiming for those Quality Assurance features, because now you know it’s necessary.
Nobody Buys The Cheapest Jack Stands
I want to be careful to not go too far in one direction: there are lots of times where spending a lot of time on quality up front makes all the sense in the world. If the software is holding the proverbial car up over your head (that’s what a jack stand does, for our automotive illiterates), you want to make sure that thing isn’t going to fail. For example, your payment systems should be rock solid. Anything with a customer’s personal information should be tested to ensure it’s protected. Anything keeping another person alive is probably not the best thing to be saying “good enough” on.
In general, though, we aren’t writing life endangering pieces of code. Most of the software engineers are automating some mundane task, or maintaining some non-critical system. And in these cases, we should be careful not to over-engineer. I know it’s boring, but sometimes that’s the job.