Say your last carrier integration took three weeks, and a comparable team shipped a similar one in four days. Same kind of API, same seniority on both sides, the same boring list of edge cases1. The difference wasn’t talent, and it wasn’t effort. It was the code each team had to reach through to add the carrier, and that code is the bill for every “just ship it” the team has ever said yes to.
“Just ship it” isn’t a mistake. It’s a trade. The problem is that most teams make the trade without ever seeing the price, because the price doesn’t land on the sprint we shipped. It lands months later, on the integration that should have been easy, the hire who should have been productive, the bug that should have been a one-line fix. This post is about putting a number on that price, in the language the person who approves the roadmap actually speaks.
Doing it right has a price too, of course. But that one is easy to see, because it lands on the sprint we’re in. The cost of not doing it is the invisible half of the trade, and the invisible half is the one this post prices.
One thing first: shipping fast is not the enemy, and “do it properly” is not a free action. Sometimes the right call is to take the shortcut, hit the date, and grab the opportunity while it’s there. I’ve made that call many times, and I’d make it again.
Design has a failure mode too. We can over-build, gold-plate an abstraction for a future that never arrives, and pay for flexibility nobody asked for. That’s real, and it’s a different post. The expensive thing this one is about is the opposite, and far more common: the shortcut nobody priced.
The trouble isn’t speed. It’s speed with no meter running. A shortcut we priced is a decision. A shortcut we didn’t is a surprise we’ve already agreed to pay for. The first one is engineering. The second one is hoping the bill never arrives.
So this isn’t an argument against shipping. It’s an argument against shipping blind. When a team says “just ship it” and means “we’ve looked at what this costs us later, and we’re fine with it”, that’s a tool. When it means “we don’t want to think about later”, that’s the expensive version. The rest of this post is what it costs.
Go back to the two teams. The four-day team didn’t have better engineers. It had a carrier integration that was behind one clear boundary: a single interface every carrier implemented, one place to plug the new one in, a set of tests that flagged the moment something was off. Adding a carrier meant writing one new implementation and nothing else. It was boring - the good way2.
The three-week team had carriers smeared across the codebase. Shipping logic in the checkout flow, a few if (carrier == "X") branches in the label printer, rate lookups copied into three services because that was faster than finding the right home for them once. Adding a carrier meant hunting down every one of those spots. The only way to find them was to break something and wait for a test, or a customer, to notice.
The two codebases look like this from the inside:
Same task, two very different prices. The difference is entirely the design each team shipped on the carriers before this one. Integration time is the interest payment on undesigned code. Every shortcut that smeared the logic a little wider made the next integration a little slower. Three weeks isn’t the cost of this integration. It’s the accumulated cost of every “just ship it” that came before it, billed all at once.
It’s a cost that hits payroll straight. A strong senior joins the team. On the designed codebase, they’re shipping real work in three weeks: the code has a shape, and that shape tells them where things go. On the undesigned one, the same hire takes two months, and spends most of it asking the two people who “just know how it works”.
The difference isn’t the hire. It’s that undesigned code carries its knowledge in people’s heads instead of its structure3. When the logic has no shape, there’s nothing to learn except trivia, and trivia only lives in whoever wrote it. Onboarding slows to the speed of interrupting the busiest engineers on the team4.
That’s two expensive things at once. The new hire isn’t producing, and our most valuable people spend their week as human documentation instead of building. Time to productive is a direct readout of how much of the system lives in code versus in people. The longer a good hire takes to contribute, the more of the design we’ve been storing in the worst possible place.
A customer reports a bug. On a clean design, the fix is contained. We find the one place the logic lives, change it, and the tests confirm nothing else moved. Report to safe deploy in an afternoon.
On undesigned code, the same bug is a week. Not because the fix is hard to write (it’s often one line), but because nobody can be sure that one line didn’t break two other things. The logic is duplicated, the dependencies are implicit, and the test suite doesn’t cover the boundaries because there are no boundaries. So the fix gets manual testing, a careful release, and a held breath. The cost isn’t the typing. It’s the time from report to a deploy the team trusts.
This one already has a name the whole industry tracks: time to resolve, and it tends to climb with complexity for exactly this reason5. Every shortcut that added a branch nobody simplified made every future fix a little less safe. Customers feel this cost directly, which is why it’s usually the one that finally gets a manager’s attention. The irony is that the same “just ship it” that closed the task fast is what makes the next task slow.
Notice what’s missing from those three costs. None of them is “the code is ugly”. None needs a senior engineer’s taste to see. Integration time, onboarding time, time to resolve: a manager already feels all three, already complains about all three, and already has them somewhere on a dashboard6. They just haven’t connected them to the shortcut that caused them.
That’s the move this post is really about. The cost of “just ship it” isn’t a code-quality problem, it’s a profit-and-loss problem (P&L) disguised as a code-quality issue. Slower integrations are revenue arriving later. Slow onboarding is salary spent without output. Long time-to-resolve is churn and support load. Every one of them converts cleanly into the currency the roadmap is decided in.
This is why “we should refactor because the code is messy” never wins the argument, and it shouldn’t. Messy is an aesthetic. Integration time is a number. When we make the case in quality language, we’re asking for a favor. When we make it in time and money, we’re reporting a cost the business is already paying and hasn’t itemized yet.
Opinions lose to data, so before having this conversation, get the data. The good news is that all three costs are already happening, which means we can measure them without changing anything yet. We just have to write the numbers down.
Three are enough to start:
Integration lead time. How many calendar days from “start a new carrier” to “live in production”? Track it across the last few. A flat or rising line as the team adds carriers is the smear getting worse.
Time to productive for new hires. How many weeks until a new engineer ships something real without hand-holding? Ask the last three hires. A number that keeps climbing means the knowledge is leaving the code and moving into heads.
Time to resolve, against complexity. How long from bug report to a trusted deploy, and how does that track with the complexity of the area the bug lives in? The correlation is the argument.
A skeptic will push back here, and rightly: these numbers rise for reasons that have nothing to do with design. The product grew. The team turned over. A vendor’s API got worse.
All true, which is exactly why the trend matters more than any single reading. Hold scope and seniority roughly steady, keep adding carriers, and watch what integration time does. If it keeps climbing, the thing getting worse is the code we reach through, not the problem we’re solving.
None of this needs a tool or a quarter-long initiative7. It needs someone to baseline the three numbers now and watch the trend. A single data point is an anecdote. A trend line is a business case8. Once we have it, the “just ship it” conversation stops being two people trading opinions about quality, and becomes a manager looking at the cost of a decision they can actually change.
“Just ship it” isn’t the problem. Shipping without pricing it is. The bill comes due later, on the integration that drags, the hire who stalls, the fix nobody trusts, long after the sprint that earned it. Measure the three costs, and the shortcut goes back to being a decision instead of a reflex.
If your team is stuck in the “just ship it” loop, this is the post to forward to the person who decides what the team works on next. Not as a complaint about code quality, but as a list of costs the business is already paying and hasn’t itemized.
Here’s the version for that person:
This article puts specific, measurable costs on shipping without design, in business terms rather than engineering ones. It breaks the cost of “just ship it” into three numbers any team can track: how long a new integration takes (and why that time keeps rising), how long a new hire needs to become productive, and how long it takes to resolve a bug safely. The argument isn’t “stop shipping fast”. It’s “measure what shipping without design actually costs us, and compare it to the price of doing it right”. If we’re feeling slow integrations, long onboarding, or risky bug fixes, this explains why, and tells us exactly what to measure to find out how much it’s costing.
The goal was never to ship slower. It’s to know the price before we agree to pay it. A shortcut with a number next to it is a decision the team made on purpose. A shortcut without one is a bill we’ve agreed to pay later, sight unseen.
Media attributions:
Cover image and infographic by the author (generated with Gemini and Claude)
Boring to read, thrilling to discover in production at 2 AM.
Boring code is a compliment. The next person can guess what it does without reading all of it first.
Reading code, not writing it, is most of the job. A field study of 78 developers (Measuring Program Comprehension, Xia et al., 2018) found they spend around 58% of their time understanding existing code, and juniors spend more of it than seniors. On undesigned code there is more to understand and less help understanding it.
The polite name for this is tribal knowledge: the design living in a few people's heads instead of the code, so the only way in is to ask them. The impolite version is that your architecture is currently stored in two people's heads who would quite like to take a vacation.
Not just a feeling. A 2022 study of 39 production codebases (Code Red: The Business Impact of Code Quality, Tornhill and Borg) found that resolving an issue in low-quality code takes 124% longer on average, with up to 9 times longer worst cases and 15 times more defects.
"Somewhere" is important in that sentence. The dashboard exists. Whether anyone has opened it since the demo is a separate question.
You can tell it is an initiative and not a fix because it has a steering committee.
Two of these map closely onto the DORA metrics a lot of organizations already track: lead time for changes and time to restore service. If your team measures them, that’s two of the three for free. If it doesn’t, this is a good reason to start.
No posts

Comments
Nothing yet. Say the first thing.
Sign in to join the conversation.