Mastery and Perfection
Published on 20 August 2022
An oft-repeated mantra in agile software development is that “done is better than perfect.” Within the business context of software the surface reading of this aphorism is obvious and self-evident, as a feature that done but poorly functioning might still bringing business value, in contrast to a feature that is still in development and not in production. However, while that is its intended meaning, it does not give any useful advice to how development should be approached and as such tends to suggest that it is permissible to write ugly code that works and never learn anything about why it is ugly or how it can be improved.
Indeed I have seen this quote used to justify a lot of irresponsible behaviour when the spirit of it is not properly explained. The properties “done” and “perfect” concern the object. Perfection of objects is not possible in any reasonable capacity, and pursuing perfection of any single object is bound to hit diminishing returns at some point and is therefore unproductive.
What does perfect code mean? We might say that it first and foremost fulfills the business purpose, it follows the principles of SOLID, it is written clearly — that is, it is easy to intuit it’s purpose by anyone who is reading it in the future, it enables others to build new features on top of it in the future when they are needed, it is well-tested, typed and documented.
There are a lot of properties of perfect code and once presented with a piece that seems to fulfill them all, it would not be challenging to find more ways to “improve” it. This fact seems to underline the premise that it is pointless to chase after perfect code because perfection of the object is unattainable. At some point the piece of code we are working on will be good enough, and the increase in quality coming from adding a new unit test or another refactor will be tiny or, possibly, negative. Another layer of abstraction might make the code more elegant or extensible, but it will impact the readability, especially in the case when it might not need any more extension. Similarly, adding more tests makes the code less modifiable as every change in the production code should also require a change in the tests.
An issue with “done is better than perfect” is misunderstanding it as “done is enough.” A done piece of code is perhaps just one that fulfills the business purpose. But no piece of code is an island and there will come a time when it needs to change and influence other elements of the system, and as such it is reasonable to expect a certain level of quality from the code that is committed to the repository. So done might be better than perfect, but done well is better than just done.
Additionally, “done is better than perfect” does not excuse from considering how the code could indeed be more perfect, perhaps with the privilege of hindsight obtained after the last commit. It might not be necessary to act on it and add improvements to code that’s “done” to the codebase, but it is always useful to think how it might be improved. First because there might come a time in the future where it will have to be improved and second in order to develop experience that will allow for deducing ways to get it right on the first attempt in the future.
Instead of perfection of the code, one should strive for the mastery as the programmer. One of the hallmarks of such mastery is the intuition for correct solutions before they are even attempted. Knowledge of modelling, abstractions and design patterns is often invaluable here, as sometimes an hour of good design can save a week of coding. On top of that, when developing features or projects with tight deadlines, you will have to make decisions based on incomplete information, decisions that are unlikely to be correct without a certain amount of intuition and experience.
Careful and deliberate consideration of the solutions already developed and eagerness to look for improvements is another proclivity which distinguishes novices from masters. A novice might finish developing a feature, commit it to the repository, consider his work complete and find something else to do. A master understands that most likely some shortcuts were taken in order to deliver the feature on time and documents possible future improvements or directions for developing planned extensions. Aspiring to mastery means looking for this as well, while understanding that those improvements might not be necessary within the current business context.
There is a difference between mastery and seniority, too. Seniority is a much broader concept, referring more to team-building enterprises, where one is responsible for ensuring that the entire team’s skills are utilised as best as possible. In contrast, mastery is a purely individual pursuit that exists only between the programmer and his tools.