How to Think Like a Software Architect
Two engineers, one automotive feature. Three months of code that failed certification, versus two weeks (one of design, one of code) that shipped clean. The four-step desk-level sequence that made the difference.
A few years ago I watched an engineer spend three months on a feature for an automotive client. He delivered on schedule. The code passed unit tests. The team moved on. Then certification started, and one by one the test cycles began failing — corner cases the implementation hadn't anticipated, behaviour that contradicted the spec, edge cases that crashed the ECU on the bench. Three months of work that couldn't ship.
I rewrote the same feature in two weeks. One week of design, one week of code. Same person typing — me. Same compiler. Same target. The only difference was that I had a design before I had a file.
This post is about what made the difference. Not "design first" as a slogan — the actual sequence I follow at the desk before any code gets written. It's grounded in embedded automotive C, where every change has to survive certification and lives in production for a decade. The principles travel beyond it.
The scar that taught me
Years earlier, I was working on a fleet-vehicle tracking system. Legacy C, no structure, every change introduced a regression. We were spending six months at a time fixing bugs from minor features.
The moment that landed was small. We had the same logic copy-pasted across three files. A bug came in. I found one of the copies, fixed it, shipped. Next sprint the bug was back, reported by a different user, hitting through one of the other two copies. I fixed that one. The bug came back a third time.
The bug wasn't really the bug. The bug was that the codebase had no decided home for that particular responsibility. Three files thought they owned it. None did.
I deduplicated, the bug stayed dead, and I took away something larger than "deduplicate code." Every responsibility in a system needs exactly one home. If you don't decide where it lives up front, the system decides for you, badly, distributed across files that drift independently for the next ten years.
That's where I stopped trusting code that had been written without a design.
How do I design software before writing any code?
Engineers avoid weeks of certification rework by spending one week designing a feature before writing code. The sequence below — write the requirements in plain language, enumerate the failure modes the feature must survive, draw module boundaries with one responsibility per box, and define every interface signature — is what turned the three-month implementation that failed certification into a two-week implementation that shipped clean. Same compiler, same target, same person typing. The difference was that the design existed before the file did. Most articles skip this part because they show you what to type, not what to think before you type. The week of design is not time spent producing nothing — it produces the thing that makes the implementation week fast and the certification cycle predictable. It's not waterfall. It's the floor below which I will not start typing.
| Stage | Code-first (3 months → fail) | Design-first (1 week + 1 week → ship) |
|---|---|---|
| Requirements | Implicit, in head while typing | Written in plain language before file 1 |
| Failure modes | Discovered at certification | Enumerated up front, in the design |
| Module boundaries | Emerge from cut-and-paste | One responsibility per file, declared up front |
| Interfaces | Negotiated mid-implementation | Signatures defined before any function body |
| Where time goes | Most spent fixing what design would have caught | Most spent on implementation that just works |
1. Write down what the feature has to do — in plain language. Not pseudocode, not types, not interfaces. A bulleted list of behaviours, in the same words I'd use to explain it to a non-technical PM. "When the vehicle starts, send a status ping. If the network is down, queue the ping for later. If the queue exceeds N, drop the oldest." Keep going until you can't think of another thing the feature must do.
2. Then write down what it must NOT do. Corner cases, failure modes, the things people usually call edge cases. "What if power cuts mid-ping. What if the queue file is corrupted. What if the system clock jumps backwards. What if two pings race." If you can't list the failure modes, you don't understand the feature yet — and these are the ones that fail in certification.
3. Draw a box for each file the feature will need, and assign one job to each box. I work in C, so I think in files, not classes — but the unit of design is universal: a thing with a name and one responsibility. "This file owns the queue. This file owns the network transport. This file owns the lifecycle." If you can't write the job on the box in one short sentence, the box is wrong.
4. Define the interfaces between the boxes, and out to the world. Function signatures each file exposes, plus the interfaces to the OS, the hardware, and external APIs. I write these as headers — names, parameters, return types — before any logic is implemented. If I can't write the signature, I don't understand the contract. If I don't understand the contract, I'm not ready to implement.
When the four are done, the design is done. Implementation is then mostly typing. The interfaces are in place, the corner cases are mapped, the responsibilities are pinned down. The week of design wasn't time spent producing nothing — it was time spent producing the thing that makes coding fast and certification clean.
Why teams keep skipping this
If this works, why doesn't everyone do it? Three reasons I see over and over:
Junior engineers can't. Not because they're not smart — they were trained to think with code in front of them. The autocomplete is part of their thought process. Asked to design without an editor open, they freeze. The fix isn't "tell them to design more." It's pair them through the design phase, the same way we pair them through the coding phase.
PMO pressure for visible output. A week of headers and box diagrams is invisible to the metrics that managers report up. The team that thinks for a week and ships in another week looks slower than the team that ships nothing usable in three months but commits every day.
Single-resource projects. When one engineer is on a feature alone, there's nobody to push back on a missing design. Nobody to ask "what happens when the queue overflows." The pressure to start coding becomes overwhelming, because typing feels like progress.
None of these are fixed by writing better engineers. They're fixed by the people running the project deciding that design output is real output.
Two automotive sacred cows that don't help
Two parts of automotive software process I think waste time without producing safety:
MISRA C compliance treated as a checkbox. Some MISRA rules genuinely catch real bugs. But "we are MISRA-compliant" has become a green light at the gate of half the projects I see, with the actual code unreviewed for design. A function can be perfectly MISRA-clean and still be a 600-line god function with five reasons to change. Compliance isn't correctness.
V-model documents that exist for sign-off. Specs that drift away from the code the moment the next sprint starts. Test plans written for the auditor, not for the engineer. The process is sound on paper. In practice the artifacts become decorative — they exist so the deliverable list is complete, not because anyone reads them. If a doc isn't read after sign-off, it isn't a doc; it's overhead.
A two-page design document the team rereads before each sprint is worth more than a 200-page V-model bundle nobody opens after release one.
What to do about it
If you lead an engineering team: Make the design phase a deliverable. Review the design before the code is written, the same way you review code before it ships. If your stand-ups can't represent "I drew the file map yesterday and we agreed on responsibilities" as legitimate progress, fix the stand-up.
If you're an engineer: Resist the pull to type. If you can't write down what the feature must do, what it must not do, what files own what, and what interfaces they expose, you are not ready to implement. The cost of finding out later — usually during certification, in front of a customer — is everything you've typed.
Coming back
The one-week rewrite wasn't a heroic feat. It was the natural pace of work that has been planned. The four steps above aren't unique to me — they're what's left when you take "think like an architect" out of the abstract and put it on a desk, in front of a project that can't fail.
If you ever find yourself patching the third unexpected case in a function and reaching for a fourth — stop. The patches aren't your problem. The design you skipped is.
About the author
Founder & Technical Director
Richin is the founder of NeuraByte, a small consultancy building software for clients who want it done right the first time. He has spent over a decade in embedded and automotive engineering — across microcontrollers, Linux platforms, and ISO 26262 safety-critical systems — and writes here about how that experience shapes the way he builds today.