The process of building a Technical Spec
I wanted to share what has been my development process for the last 6 years, and how it has helped me solve problems and communicate very efficiently!
Overview
Back at 2016, while I was working at VoxFeed, my mentor back there would not stop adding comments to my Pull Requests about how my code was unreadable and didn't work for the relevant test cases (woooops!), he would even just bounce my PRs and ask me to re-write my code, as it would be much easier than modifying my actual one. At this point, I felt my 4 years of studying Computer Science were just pointless, as I got to dimension how different school was from work.
Besides, something I'm really grateful about struggling that much at the beginning of my career is learning the in-depths of building a Technical Specification Document. The process back then consisted on building a Google Doc for every JIRA ticket we needed to work on, with a very specific format that would help us communicate (mostly for ourselves as writers) what was the real problem we needed to solve, including relevant information around the problems, and a hypothesis of how to fix it.
The most important part about this Spec is that we were able to land our thoughts around what the real problem behind our tickets was, and proposing a solution that would help the problem, and as a nice plus, we were able to create a shareable piece of information that anyone in the team would understand and immediately provide any relevant feedback. This was really a game changer within the team, as our team process would consist on review this Specs before jumping into code.
- Do I want you to start building technical specs for every ticket you need to do? Not really
- Do I encourage you to identify a problem and solve it before coding? Absolutely
I'll explain what this document consisted on.
Creating a Technical Spec
The Technical Spec is a document aimed to land an accurate description of a problem, understand what is the environment in which the problem lives, and identify what should be the best course of action to solve that problem. The format we would follow at that time is:
- Problem: Define what is the problem are solving
- Context: Land what are the most important variables to take into consideration that affect this problem, or explain why the problem exists.
- Hypothesis: Land a paragraph with the format if we do X, we will solve the problem
- Solution: Start landing a detailed proposal of how to fix a problem. This would normally include code snippets, UML diagrams, and other pieces of information to explain the proposal.
NOTE: The format can change depending on the problem, and the intention is not to make things more verbose than they should be.
We would be doing a single piece of this documents to solve each ticket at the company. After finishing the specs, we would need to share it with 1-2 people to review them, and then we would just start to code, which normally was the easiest part of the process as we already knew what to do.
This would be very time consuming for anyone, and maybe just to think about making a document for each small/big ticket is nuts, and simply not worth it. I do think there's a good balance between which problems deserve this kind of documentation, and also, this type of processes encourage Jr. developers to think before coding, when I was a junior I would just think about code.
After starting working with this format, I started digging a bit more into what good software looks like, finding concepts and methodologies such as Clean Architecture, Domain driven design, and others. I do think they are really good things to know, specially the concepts around abstractions and boundaries, but sometimes sticking too hard into those concepts is more harmful, one should find the balance of when to fully use them. I do believe one can start simple and start abstracting early on, but not too much for development to slow down. It's hard to find the balance, it's not something easy to master, specially in startups.
Example of a Tech Spec for a small problem
Problem
We want to create a verification process for our users, so that they need to enter a 4-digit code we send them through SMS before they can start using our product
Context
The system does not have any mechanism to send SMS messages to phone numbers, so we might need to take a look into multiple providers. Some of the most relevant seem to be X
and Y
provider. Also, we know from our previous implementations that this type of features do take a bit more than expected as we need to test with physical devices.
Proposed Solution
The purpose of this pseudocode is to give a general idea of what needs to be done in the solution, this is not code copied from an IDE, it's just to land ideas.
- Implement an SMS Gateway object that communicates with a provider
class SMSGateway { constructor(providerClient) { this.providerClient = providerClient; } send({ phoneNumber, message }) { // send sms to provider // handle relevant provider errors } }
Create a Use Case object
SendVerificationCode
that will receive a SMS Gateway via dependency injectionclass SendVerificationCode { constructor({ smsGateway, userId, database }) { this.smsGateway = smsGateway; this.database = database; this.userId = userId; } async execute() { const user = await this.database.getUser(this.userId) const verificationCode = this._generateVerificationCode(); this.smsGateway.send({ phoneNumber: user.phoneNumber, message: "Welcome, this is your verification code: " + verificationCode }); } _generateVerificationCode() { // Implement an algorithm that retrieves 4 random digits } }
- Connect use case to a
/user/verification
POST Endpoint (handle user does not exists, invalid phone number, and others)
How/When to use a Technical Spec
This type of documentation can be built for any kind of problems, whether you are implementing a simple verification process for your users, or maybe you want to create a completely new authentication mechanism from the ground by using biometrics. The most important part of using this type of tools is to understand problems in a deep level, and being able to communicate them by sharing a single URL to a document.
Specially when you are mentoring a junior, this type of processes can help them to mature their thinking process and grow as engineers. At the end, we expect them to excel at code reviewing, automated testing, design, and other very important factors!
Conclusions
One of the biggest problems I've seen in this industry for some years is that we as developers don't put effort into the ability to abstract low-level concepts, we think more about implementation details. We should acknowledge they are not just programming tickets and making nice CSS or REST Endpoints or JS functions, or a Kubernetes Cluster, or a gRPC infrastructure, those are merely a toolset to solve problems. Actually, tickets that do imply creating a JS function
or refactor function getUser
or stuff like that just increase the level of implementation detail thinking, and it's really easy to lose the overview.
We should rather think on abstractions, so that we can defer the implementation details until they are important, and this will also help us to create a better architecture to grow the system and make things easier for developers and stakeholders to understand the system and make modifications in business terms, not low-level terms. A good abstraction I've kept doing when it makes sense is the concept of a Use Case from Clean Architecture, where you define a class that receives the name of a Use Case (verb + subject). For example, CreateUser
, ConnectIntegration
, etc.
But, what does an abstraction
actually mean? Is it a fancy word senior developers use nowadays to pretend being cool? I'll talk more about that in my next post!