Every now and then, I’m reading code and stumble upon some fragment of code that seems strange: while I can quickly see what it is doing, I can’t understand or remember why it is needed. Asking a colleague is probably the most effective way to get an answer or at least a pointer to the right direction. However, it’s unrealistic in the long-term to think that all knowledge will survive and persist through time. Undocumented reasons and decisions above a certain degree of criticality have an impact over the code, its maintainers, and possibly over its end users. Even good documentation might get lost due to poor placement or lack of references, leaving software archaeology as the only way to understand why some code is in place.
Here are some ways to produce documentation and avoid leaving code orphan of reason, depending on the granularity of the decision behind it.
The first and simplest way is to add a brief comment to the code explaining the why. I only embed comments in the code if I can’t make the code itself more self-explanatory by means of refactor, descriptive names and encapsulation. There is a cost in maintaining comments in the code, as they can easily fall out of sync with the code. For that same reason, think critically before trusting comments – they can be less reliable than the code itself.
Usage example: commenting a counterintuitive line of code that someone could mistake as redundant or unnecessary.
Sometimes the scope of a reason is too broad and cannot be attached to a particular symbol in the code. It’s usually the case of changes spanning multiple files, as the outcome of a technical task, user story implementation or a bug fix. In that case, it’s a good idea to document the reason in the issue tracker (like Jira, Asana or Trello) and reference it somewhere across the whole changeset, like in the commit message or, if applicable, in the pull request. Sometimes there are written conversations that enrich and elaborate on the requirements or the reasons for the code – don’t forget to reference them if you have the chance.
Usage example: implementing an A/B test experiment that requires changes in both frontend and backend.
Overarching decisions trascend units of work and often have a strategic vision about the technical domain or the product. If the reason derives from a technical decision (for example, using an architecture pattern) it’s a good idea to capture it in an Architectural Decision Record. Otherwise, if the reason derives from a product decision, it’s best to capture it somewhere closer to the product documentation, or in a knowledge-sharing tool like Confluence. Make sure to gather input and open a space for discussion when generating such documentation, as everyone affected should be aligned and onboard with the decision.
Usage example: choosing asynchronous communication between services in order to improve resilience and scalability.
While there are surely more ways to document decision-making and its outcomes, what I consider of greater importance is tracing and cross-referencing those knowledge sources. There should be a clear path from the code to a documentation artifact declaring its motivation or reason. How to do it? Ensure you have knowledge sources in the first place, and contribute writing and keeping them updated. Then, reference them. Mention them. Share them. Does anyone else have more context? Tag them. It will quickly create alignment and fortify the understanding of anyone looking for answers. Leave a trace of the reason that is driving you to write code.