This post is a slightly revised version of a document I wrote about a year ago
to share some gentle technical planning guidance with colleagues. Itâs not
intended to provide a template for writing technical design documents; every TD
is probably going to look a bit different, based on its scope and audience.
Instead, itâs a narrative around what you should think about in order to
write an effective TD, what you and others should be able to get out of it,
and some guidelines on how to get started with your tech spike and written
document.
First, some context and definitions:
A technical design document (TD or TDD, not to be confused with test-driven
development), describes a solution to a technical problem. You might also see it
called a technical specification, engineering design doc, or similar, but they
all serve the same purpose: communicate what the solution is, how weâre
going to build it, and why weâre going to do it that way.
A project will typically start with a product requirements document (PRD),
which describes what the problem is and what the solution needs to do. From
the requirements, we build out a TD to guide us through the implementation of
the solution. TDs Iâve worked with typically cover both the general system
architecture and the implementation approach for the project, but your team
might split this into two separate documents.
While developing your TD, youâll typically engage in one or more tech
spikes: this is where youâre building the simplest possible proofs of concept
to explore the technologies youâre considering, so you can answer your own
questions about what they can do and how they compare to each other.
And now to the bunnifesto:
The ultimate goal of a TD is to reduce technical risk. If you canât use it
to build a work plan, itâs not finished. The TD helps with planning by giving a
bit more certainty around scoping â itâs the document that lets you say âI
think we can deliver this much in 6 weeksâ and be reasonably certain about it.
Youâre not trying to rigorously define the entire project â at that point you
may as well have just written the code instead of the TD. Figure out the general
approach/technologies/interfaces needed, leave the implementation details to
whoever picks up the ticket.
Whatâs your end goal for this document? Youâre fearlessly (or fearfully) leading
your team into this project work. Your goal should be to anticipate their
questions and factor them into your proposal. You want your teammates to know
youâve thought through everything â the scope, the risks, reasonable
alternatives and their tradeoffs â so they can trust your
recommendations about how to proceed. You want your manager to be able to read
the proposal and just say yes.
Your work plan might have known unknowns â tasks that require some
exploration, you know what you need to do and you probably have some idea about
how to do it, but you might need to choose between a couple of approaches/scopes
at the time or read up on something unfamiliar. Youâre trying to avoid
unknown unknowns, these are
what can really blow out a project. Theyâre what happen when you say âsheâll be
rightâ and launch into the project and then six weeks in someone says âOK, but I
need to be able to read back that data tooâ and a look of horror slowly
appears on your face as you realise you designed and built an entire system
where you prevented data exfiltration by only ever storing cryptographic hashes
of user input and you forgot the next stage of the project involved displaying
that data. Oops.
On that note, requirements: you should be aware of the scope of your
design and how it fits into broader priorities. Read and re-read the PRD. Donât
spend a lot of time designing for the project youâre not building right now.
Requirements change. Do spend time architecting for future requirements
though â this feature might involve write-only data right now, but you know
down the line weâre going to want to be able to read it back/scale it up/etc.
You donât want to have to completely rebuild the system in the near future. (For
this reason, we generally donât ask less experienced engineers to write TDs on
their own â their main focus is still on learning to build solutions, as they
gain more experience theyâll be able to see the bigger picture and think about
planning for the future.)
And make sure your TD covers all your current requirements! Make a list. Tick
them off. If a requirement isnât clear enough or you donât understand it fully,
go and ask people. Thereâs little point in building a beautiful system that
canât do what its users needed in the first place.
Donât forget the implicit non-functional requirements either â the ones that
apply to all engineering work we do. Even if the PRD doesnât explicitly call out
security, performance, reliability, maintainability, usability, and other
âilitiesâ, you should always keep these technical requirements in mind.
A lot of the work in writing a TD is considering technology choices and making
tradeoffs. Think about our current patterns and what the team already knows how
to use â if weâre already heavily integrated with AWS services everywhere and
know how to use them, and AWS and GCP have basically feature-identical offerings
that fit the bill, use the AWS one. That JS package may do everything, but
do we really need it to do everything â is there a smaller package that does
just what we need and is simpler to configure as a result? Is that new
hipsterware actually prod-ready? Apply the principle of technological
minimalism, but be aware that it has its limits: as a team, we want to minimise
the number of technologies we need to maintain, but sometimes a well-informed
decision to introduce a new technology is the best choice.
Write down what your tradeoffs are. Donât just document what you do want to
use. Document what you considered and rejected, and why. This gives you and
your reviewer confidence that youâve deeply understood the requirements and
tradeoffs, and also means that when that lagomorph person who has an Opinion
on Everything but didnât actually read the TD pipes up with âwhy didnât you
choose BunnyRabbitWare?!â you can point to the TD without having to even argue.
Broaden your options before narrowing. Even if youâre pretty sure you know
how we could do this, try to think of another way, even if itâs silly and just
in your head. This makes you think about why the silly thing is bad and thus
some things that might be bad (that you want to avoid) and gives you some
baseline to compare against. If you canât come up with another option, you
probably donât understand the problem well enough.
Take the time to understand the current state of the system and prior
art (and write that down too). It might inform your options. Does this look
like something weâre already doing â did that work well, do we want to follow
the same example, or do we want to take a different approach after learning from
our mistakes there? If we have two different ways weâre currently doing
something, which one is the modern/preferred pattern? If your feature hooks into
legacy code, do you need to rewrite a bunch of existing modules for
compatibility with the Modern One True Way (you know, as opposed to last yearâs
One True Way), and how much extra time is that going to take? If you donât have
that much time, can you limit the scope of what you need to rewrite, and at
least avoid introducing new legacy code even if you canât rip it all out?
You want to break down the tasks in such a way that you can ship partial
value even if you donât complete the whole project. Priorities might change,
or you might run out of time. If you can ship what you have and leave the system
in a good state to build on it in the future, this is a good outcome!
Think about how you need to interact with existing systems too. Draw on your
understanding of the system architecture. Your new feature feels standaloneâŚ
but does it need to integrate with those constant background maintenance tasks?
Have you considered that strangely-implemented-but-important widget that no one
ever remembers? If you have questions about these things, ask now, donât wait
until the project has already started. If you donât have questions, ask anyway
â no matter how low your personal bus factor is, itâs almost impossible for you to know
about every interaction in the system, so pick your colleaguesâ brains for
anything they can think of!
Level of context: as the author of the TD, you have all the relevant context
in your head. Donât assume your reviewer has all the same context, even if
theyâre the CTO (probably especially if theyâre the CTO; thereâs a lot of other
stuff floating around in their head). They shouldnât have to go digging through
the whole PRD to understand what needs to be done, so explain why youâre
making your choices â donât rewrite the PRD, but donât be afraid to give some
background information.
Technical background information is definitely a good idea. Explain the
current state of the system and what youâre slotting into.
Whatâs your narrative? Think about providing a smooth reading experience. Itâs
fine to write prose. Sometimes itâs easier to grok than dot points. If youâre
asking if there should be a diagram, the answer is almost certainly yes.
Ask around broadly for people to review your TD, but chase people specifically
too! Find the people who care the most about the design or are most likely to
disagree with you. Get them on board now. Give your reviewer everything they
need to give you informed feedback. You want them to rip things apart at
this stage, before your team actually starts work.
On that âbefore your team actually starts workâ point, letâs talk about tech spikes: this is
an exploration phase and should be used to assess viability of an approach,
not to pre-implement the feature.
Donât retcon the TD after actually implementing the solution, either. But if you
can, document your divergence â the best-laid plans go oft awry, after all,
and codebases evolve over time.
In the words of a friend who has seen (and
perhaps even been responsible for) many, many programming sins:
Tell me why your plan failed!!!! If you donât youâre as bad as that one
stackoverflow user who asked a question and then replied with âI solved it!!!â
HOW????? HOW DID YOU SOLVE IT USER OF 9 YEARS AGO?????
Knowing why the initial attempt didnât go as planned can be core to
understanding why the eventual solution works. Future archaeologists will thank
you.
âQCCCC,â you whine, âthatâs too much text. Just tell me how to get started.â
Sorry, friend, Iâm not here to give you a template. I want you to read all
those words! But because Iâm feeling generous, a basic structure to start with
might be:
- Problem statement. Whatâs the problem weâre trying to solve? Why is it
a problem (that needs to be solved with technology)?
- Background information. What does the system currently look like? Which
parts of it support what weâre trying to do, what do we need to build on to
solve our problem?
- The broadening: solution space. How might we solve the problem? What are
our options? What are the tradeoffs between them?
- The narrowing: recommendation. Weâve talked about what we could do.
Now, what should we do?
âIs there a checklist for writing a TD?â
Yes, itâs this bunnifesto.
More seriously, though, I donât believe a generic checklist provides enough
utility; as I mentioned in the preamble, every TD is going to be different,
because so much of what goes into it is contextual. Instead, Iâd encourage you
to re-read this bunnifesto with the context of your project in mind, and
construct your own list of things to ask yourself (and other people).
Make sure your TD answers the right questions. If youâre still not sure what
those questions are, ask people who should know, until you find the answer to
the question of what questions you should answer out.
đ°đ°đ°