The Implementation of Clean Code
Many software projects suffer from an exponential increase in costs. Over the years it becomes more and more expensive to implement new features in the existing code base. Due to its size, rewriting is not an alternative. The software thus makes more and more difficulties in the areas of correctness and changeability. Since there are no or far too few automated tests available, errors remain undetected when changes are made, and the correctness of the software deteriorates. Since the structures are very complicated, methods and classes are too long and responsibilities are mixed up and many other things are still in trouble, the changeability is also not given. Code that nobody understands is difficult to change. At the same time, developers are not able to test such code afterwards.
As developers, we should learn something from this observation: let’s do it right from the start! However, this insight alone is not the solution. The question remains: what does it mean to do it right? How do we avoid an exponential increase in effort?
Principles of professional software development
- production efficiency and
- continuous improvement
All this is important and right, but it does not help developers out of their misery.
The daily horror
To explain to a developer that a functional unit may have only one responsibility is one thing. Enabling him to really adhere to this principle at all times is another matter. And the lesson from over 10 years of the Clean Code Developer Initiative is: the problem is not solved at the source code level alone. Unfortunately, the SOLID principles do not lead to much better conditions. Of course, either coding or refactoring can improve a lot. But the real breakthrough comes when teams or individual developers design the solution before coding. You have to think again!
Whether it is due to the recommendation of the agility movement not to do a big design upfront, or to the fact that UML is not suitable for the design, cannot be conclusively clarified. Fact is: Developers do not design their solution before coding. As soon as the requirements have been discussed, the console lid opens, the IDE is started and encoded. And believe me, I see horror in my day-to-day work as a trainer and consultant!
The dear requirements
Since the industry has been using keywords like Clean Code, Clean Code Developer, Craftsmanship, SOLID, TDD etc. for more than 10 years now and at the same time has hardly improved at all, something essential is obviously missing. The principles alone are not enough. So we turn to the question, which steps in a development process are necessary to get from the requirements to the code.
- decompose requirements
- design in width
- select a requirement
- design in depth
- work organisation
- implementation based on the division of labour (incl. tests)
integration (incl. tests)
Before anything more concrete is done with the requirements, they must be systematically disassembled. Systematic here means that there must be a decomposition hierarchy into which requirements are broken down. The most common system mentioned in the literature consists of user stories and epics. But is it really enough to divide the requirements of a complex software system into just two hierarchical levels? My observation is that the sobering answer is no. In addition, the question of how to best formulate and refine user stories arises again, since the procedure is purely text-based. We therefore propose a hierarchy for the decomposition of the requirements, as can be seen in the following figure.
The upper levels Bounded Context and Application are left out here, as they are only relevant in really large systems. Within an application, each requirement can be assigned to a dialog. This is because users have to interact with the application. Only when a user selects a menu item, presses a button, etc. does it become necessary to program domain logic. And every such interaction takes place in a dialog.
Imagine a dialog as a window or form. In such a dialog there are often several possibilities of interaction. Therefore, the requirements are very clearly divided into smaller and smaller units. We can consider all requirements of an application, only the requirements of a dialog, or even only the requirements of a single interaction.
Often the requirements of an interaction are still too extensive to be realised in the time frame of 1-2 days. In such cases, it will be possible to identify features that are initially postponed. For example, one looks at the functionality of an interaction to be realised, but initially does without the validation feature. By omitting features, the scope becomes smaller, so that feedback from the product owner can occur earlier. The following figure uses a questionnaire application as an example to show how the interactions can be displayed graphically in a dialog.
The next step is to select a single requirement and then design and implement it in detail. The refinement of the design in depth is now about finding a concrete solution to the problem. The result should be so detailed that implementation is easy. This means that the individual methods to be implemented must be found. This is the only way to ensure that aspects are actually separated. And this is the only way to decide which methods will be combined due to high cohesion or which methods will be stored in different files or classes. So we don’t let ourselves be surprised by the code and then refactor, but think about the solution before we implement it. The next figure shows a refinement of the interaction “Start”.
It should be obvious that such a structure is easy to implement. In trainings I regularly hear that the implementation after the design would be almost boring, because it’s as clear as daylight what needs to be implemented. And that’s a good thing, because there’s very little that can be said against the code that was created in this way in the code review. The Single Responsibility Principle (SRP) violations that would otherwise be encountered are no longer there because attention was paid to them in the draft. In addition, the means of refining the drafts results in code that complies with the Integration Operation Segregation Principle (IOSP). This makes the code easy to understand and to test.
After more than 10 years of Clean Code Developer Initiative, I can clearly see that the principles and practices of professional software development alone are not enough to implement clean code. It requires the systematic decomposition of requirements and their design. I use an easy-to-use development process: flow design. It is proven, quick to learn, and easy to understand. And: You have already experienced it in my illustrations in use. A German cheatsheet for download can be found at https://flow-design.info.
Further information, contributions, book tips in German can be found on the website of Stefan Lieser.
Stefan Lieser is a passionate computer scientist and works as a trainer/consultant/author. He enjoys learning new things and is constantly looking for improvements and ways to improve the inner quality of software and the development process. He is co-initiator of the Clean Code Developer Initiative and offers training and consulting for Clean Code Development and Design with Flow Design. At http://refactoring-legacy-code.net he writes about refactoring legacy code.