You've got documentation wrong
And your team's got software development wrong too
This one is going to be fun. Keep an open mind.
You finally made it! You can put the last ticket as “done” and you finally can enjoy your free time, only to see the last ticket “write documentation about that”. Or maybe that was embedded as acceptance criteria in the ticket. This happens then:
You are also thinking some of the cliches:
No one reads documentation
The code is the documentation!
Any my code, obviously good code, is self-documenting!
What a waste of time!
And well, my friend, you are right. (About some of those points).
But in order to explain myself properly first - and for you to completely agree with me, which is the whole point for writing this - I need to add some nuance to this “documentation”.
Documentation is a broad word: if you were new in a company and were asked to write documentation, without any specifics, you’d be clueless. It could be openAPI docs, specifications, requirements, comments in the code, wikipedia pages, diagrams, a myriad of documents with fancy names. So lets get specific.
The bad documentation
The bad documentation is that one that is written after the code is written. That is the one that it is indeed a complete waste of time. The developer doesn’t need to write again what they just wrote in code, but now in words. (nor the developer nor anybody)
It also doubles the maintenance effort of that project. If the code changes, the documentation needs to change as well. And how often that is not the case? How many times we have some outdated documentation explaining what the code does not do anymore, just adding more confusion to the reader? The reader will start thinking “Which is the real intention, the one in the code or the one in the documentation? Should I fix the code then?”
Yes, I hear you, there are systems and tools that can help you get some documentation in sync, like those that generate openAPI docs out of the code. Just that -some- got the order wrong - you will see later why. And on many you still need to actively write some comments in the code to make them work.
The forgotten principle
If the bad documentation is the one that happens after the code is written, then, the good documentation is that one that happens before the code is written.
And now I need to introduce you to the actual core of this article. A principle that somehow is being ignored by so many developers:
Code is an implementation of a solution.
Of a solution, not twenty solutions. Code is also not the solution of the problem. Code is the implementation of one of such solutions.
The work needed to find, create and design a solution could be called “strategy”. There are many ways for doing such work, and it can be called in so many different ways too. Could be specification writing. Or refinement, modelling, “the responsibility of the product owner”, architecture, etc. How is it done will also vary greatly depending on the kind of problem you are working on and on the size of the team/organization. But all of that is about doing one and only one thing: the designing of a solution.
Such work is the aim of DDD - Domain Driven Design. That aims to understand the domain well in order to design a solution, and then implement it. That’s what the “software engineering” or “software architecture” subjects that you did in college were all about!
And this action, if it’s not consciously done, then it’s bad by default. I am trying to desperately to find a quote like “there is no such thing as ‘no design’ but instead what you have is bad design”. I believe this is said by Vaughn Vernon, but I cannot find it in the Implementing DDD, so instead, I am stealing a quote from this post:
Vaughn also makes the point that […] that developers just start coding; if no design is applied and if not refactored into a design then you end up with Bad Design by default.
Good code
Without this strategy, this designed solution, good code is impossible, the resulting output will always be bad code. All code has inherently a design, if there was no conscious exercise, then it’s very difficult that you can have good code.
Examples can be seen in so many projects, where you can tell that many design decisions were taken on the go. The solution was designed as the developer was programming.
Now go ahead and document that! Add more fuel to your frustration! Indeed that is the the only case where this kind of documentations could be useful. But know that you are working on a failure. If documentation written after the code has actual value, it’s a sign of deeper trouble. It’s time to do some retrospective and analyze why, what was missing in the process. It’s when the team probably got the software development process wrong too.
Because before you code, you need to know what you are coding.
And here’s my definition of good code: good code is the code that leaves no room for doubt about which designed solution it is implementing. Good code can only be used to execute that solution, and not another.
Therefore good code is not ambiguous, neither flexible. Flexible would be the designed solution, not the code. It should be possible to look at the code with a glance and roughly understand what it’s doing. To understand which was the designed solution.
Code and the designed solution are two sides of the same coin. And note that if in one side you have the code, the other side will have the solution the code is implementing - not the one you designed. Good code is the one that guarantees that the solution you can find on the other side of the coin is the designed one.
Now, you need some technical knowledge in order to come with both a solution and the code that can implement it. There are countless good practices in the industry that all of them aim to help designing and implementing good solutions. It’s a simplification, but you could say that’s why they exist in the first place: design patterns, DDD tactical patterns, software architecture patterns, etc.
I also believe that a good solution can only be possible when working within a clean architecture context (call it ports and adapters, hexagonal, onion, they all have the same principles, read the blog post for more or even better read the book). Only in this context a complete separation of concerns is possible. You will not have in your head technical problems that have nothing to do with the solution you’re designing, like persisting data, logging, dealing with HTTP requests or messages, etc.
Technical concerns have their own place, and even their strategy, but here is the place where you don’t need to reinvent the wheel, where you can use libraries and tools and the infrastructure that suits your needs the most. Many developers love spending time on this part, many thinking that a change of a DB or framework will be the end of their problems. I find this part way less important and mechanic, if you have a mess, putting the latest DB will just buy you time, it will not solve the problem. There is little discussion a part from choosing the specific infrastructure, the rest just follows from the domain layer until the infra. Because, if you are doing good code, it will be clear that this code is only in charge of the technical concerns like persisting data, dealing with HTTP requests or giving access to execute domain logic, to execute the designed solution.
You can argue that my definition of good code leaves little room to abstractions and re usability. And I still stand by it. If you design something that should be reusable and abstract, go for it, the code should implement exactly that, and then it will be reusable and abstract. But you cannot code something that can be used for several solutions, because you are just changing the strategy.
Documenting the design
As you can see, the design and the code go hand in hand. Code cannot exist without a design. So in order to do good code it is important to do some conscious work on its design. And the documentation on that is the one that matters!
Now, since this conscious work on the design can take many different shapes, so it will its documentation. On big corporations this process can start way before it reaches any coding stage, with many high level documents like strategy decks, use case maps, vision documents business process mappings and other documents that I am not well acquainted with that I took them all from "Technology Strategy Patterns" (note that some patterns are to help defining strategies and making them more concrete, but there’s a lot that are about just defining better the problem that a strategy should solve), and from here “Making things happen”
Eventually it will reach some stage where developers are involved. That would be the typical agile iterative process. The more you spend on planning activities the better the output code will be, for the principles I have stated above. I recommend reading “Essential Scrum” for a general introduction on how to tackle these plannings.
The meetings on this level are crucial. During refinements is where the code is being “architected" the solution is designed, the problem is solved, and the documentation created. The documentation will take the shape of user stories, tickets, issues, their acceptance criteria, even the product backlog. But you should not only stick to scrum like items, you should use sequence diagrams, UML, diagrams, prototypes and anything that conveys properly the message. The designed solution.
This process is iterative, it’s very hard to design the solution you want on the first attempt: there will always be changes, new requirements, and new findings when trying to code it. The level of details will never be deep at first. That’s why SCRUM rightly separates some stages of planning, from the ones that are more broad and less details, refining them until there’s a lot of detail, already with developer and even final user feedback. The deeper the level of the details, the deeper the design, the better will be the code, and also faster.
As a side note I recommend involving the whole team in all those stages of planning, refining and creating documentation, instead of giving this responsibility to just “the architect” (that commonly denotes an elitist status). Architecture is a competence, not a job title.
Other competences that shouldn’t be job titles are backends, frontends, devops, etc. You want your teams to be “T-shaped”. From “Essential Scrum”:
t-shaped skills. A metaphor used to describe a person with deep vertical skills in a specialized area as well as a broad but not necessarily very deep skills in other relevant areas.
The book also explains that you need to be pragmatic.
Philosophically speaking, among all of these job titles that should be instead competences, “architecture” is the one that stands out. By now, you know that there is no code without design, therefore anybody who writes code is an architect.
Only when the documentation is detailed enough - the ticket well explained, the acceptance criteria well thought, the diagrams well drawn - the code can begin. The level of detail of that will very greatly also depending on the technical level of the team, we can be pragmatic. If the team is well experienced with design patterns, DDD tactical patterns, and architecture, and other technical competences, many details could be out, since the implementation will be the same, whether stated in the design or not. Documentation will not focus on the meaning of what is a “value object” or an “aggregate root” but on which are going to be the aggregate roots and how are they designed instead (for instance).
That is the documentation needed. That is the good documentation. It has been created by the same people in charge of coding it. And it goes first. When doing an an API, the design of the API endpoints comes first, since the implementation is going to be that one.
That’s why those tools that generate openAPI docs got the order wrong. Docs should go first, the implementation follow. Maybe the whole endpoint implementation cannot be done automatically (I hope that is NOT the case or else you have a useless domain / useless design), just the infra layer if any, and the domain will be done based on other docs.
Finally, the best kind of documentation for me, is the one that executes. Those are the tests. Even if you do not have access to the previous documentation, the designed solution should be clear by looking at the tests. And is with the rest of the documentation, tests should go first. That’s TDD (or BDD depending on what level). TDD helps design better the code - which we’ve seen is an iterative process-.
And in the last line of defense, you have comments in the code. If you have absolutely no other kind of docs - maybe because you are working solo - and you do not want to show your design through tests, you may do it with comments. You might need to use them depending on the kind of project. Go libraries and Rust libraries generate the documentation web pages out of the annotations in the code. Just remember the principle of documentation first.
In “A philosophy of software design” the author argues in favor of comments, with a chapter like “Comments should describe things that aren’t obvious from the code”. He also argues in favor of a documentation first approach - but narrowing it down to just comments, ignoring the majority of cases of software development that aren’t done in an academia context, where there are actually many other ways to document. If you need to resolve to comments in this context, if you need to add a comment that is describing something that isn’t obvious from the code, know that you have failed in the design, and the view from uncle Bob applies:
..comments are, at best, a necessary evil.[..]
The proper use of comments is to compensate for our failure to express ourselves in code.
Because good code is indeed self-documenting.
Conclusion
That’s the summary of what I am trying to convey in this post, in single sentences:
On software development - as in many other fields- the design/planning/strategy work is very important.
Code has inherently a design, if that work is not done, the resulting code will probably be terrible.
Code can still be bad if it doesn’t really implement the design.
The way to properly implement the design is by using good practices of the industry (DDD and clean architecture). By using good practices the way to properly code the design narrows down greatly.
An iterative agile process helps giving more levels of details to the design to ultimately output a single code (with no room to wiggle, just technicalities if using functional paradigm or similar things)
The documentation should all be about the design, not the resulting code, as the resulting code is almost an automatic outcome out of the design.
As the design should go first, so should the documentation. Including executable documentation like tests.
And now I just explained what is commonly known as “documentation first” approach - please don’t kill me, I know you had it already right from the beginning, I just need you to read it! - I hope that you have enough reasons from this post to convince and be convinced.
Cheers!