Sustainable software development: What is meant by it and how to develop sustainably
How to develop software that can be flexibly and adapted to new market and customer requirements in the long term
DRY and KISS are two software development principles that aim to produce better, more correct and more understandable (clean) code. Both start on a small scale, but can also be applied on a larger scale, e.g. in a project. However, there are situations where they conflict and exclude each other. This article aims to address this dilemma. It won't be able to provide a general solution to the problem, but I hope to at least draw attention to it. I will first briefly discuss the two Clean Code principles that the article deals with, and then outline the problem and possible approaches.
DRY stands for "Don't repeat yourself": Don't repeat yourself. This clean code principle is fairly self-explanatory. Repetitions in code should be avoided. That is the essence. It can also be applied outside the code, for example to automate processes or steps in the development process that would otherwise always be done by hand, but this article is not intended to cover that.
The simplest example: "magic numbers" or "magic strings". Hard-coded numbers or strings. These are often used in several places and always have the same meaning. To comply with DRY, such a value should be written to a constant and this constant should be placed in its code instead of in the code. If this value changes, only one digit needs to be adjusted.Another example is extraction methods.
A block of code is repeated several times, often due to copy and paste. "Oh, that's exactly what I did above. I can just copy and paste it. It would be better to outsource the functionality to your own method and call it when needed. Not only does this reduce the amount of work required to change exactly that logic, but it also makes the code more readable if descriptive names are used for such functions.
The acronym stands for "Keep it simple, stupid". Just keep it simple. Simplicity is always in the eye of the beholder, but in principle this clean code principle should encourage the developer to look for the simplest, least complex solution. The trickier way, which shows off all your technological prowess, isn't always the best. Simple should primarily mean that the code is easy for everyone involved to understand. Other developers (including your "future self") shouldn't have to think twice to understand the purpose of a function. This basically rules out deeply nested branches and loops, as well as methods that span a large number of lines. A violation of this principle is most likely to be caught by early and regular code reviews. That's when another developer sees the code for the first time and has to understand it.
The problem with this principle is that "simple" cannot be defined so strictly. Everyone may have a different idea of what is difficult and what is easy.
Following these two fairly simple rules can have a big impact on a project. Just remembering to avoid repetition by extracting methods will usually make the code clearer and easier to understand. Instead of the repetitive block of code, there is now the name of a method that describes what happens at that point. This makes it easier for the reader to grasp the function, and it can be used in any number of places.
But if KISS and DRY are so complementary, what is the point of pitting them against each other? Well, when you leave the world of simple examples, it sometimes happens that you have to choose one or the other, to put it dramatically. I found myself in such a situation, which is what made this "field of forces" clear to me in the first place. There are a few things to keep in mind. On the one hand, the project as a whole was far from being "clean code". In an environment designed from the ground up according to modern standards, this situation might not have arisen in the first place.
On the other hand, it is very difficult to present the facts in full without a complete outline of the project and the circumstances, which is obviously not possible within the scope of this post. But I'll try to make the underlying issue understandable: a set of Winforms controls that were part of our project were to be made available to another software project. These views represent a model that is a central part of the program. The user gets all the information about the model in different views and can also change a lot of it. Each view has a view model and a set of events or messages through which it communicates only with its parent. All the logic for building the view models and changing the underlying model is outside the views. They don't know anything about it.
So far, so good. As a result, it was relatively easy to extract the pure controls from the project and reuse them without any dependency on our software. Of course, along with the controls, their view models were also extracted. Again, no problem. That part was easy. The controls were completely decoupled from us and could be used in the other project, but what about the logic?
The "base model" that our software uses is different from what Project B has. In the end, it contains the same data, otherwise you could not logically reuse the views. In addition, our software uses a command infrastructure that is simply not available to others. As I said, the model is a central part of the programme. In addition, our application provides some additional functionality related to these views that is not available to the external application. So our problem is the interface between the viewModels and the data model. Although they are very similar, the two data models must be mapped differently to the ViewModels. Any changes initiated by the user in the views must be applied differently to the data model, even though they are very similar.
The solution we ultimately chose for this constellation was to put DRY at the back of the queue to stop KISS. To avoid making the structures too complex and introducing too many additional layers of abstraction, and to keep the code readable and understandable, all the logic in the source was left as it was, including local view models and message classes. These have now been traversed twice. Locally and in the extracted project. DRY was trampled on. The upside of this was that the component could continue to be used in our project unchanged. The disadvantage: The local view models have to be mapped to the external ones. And vice versa for the messages coming from the controls. But this mapping layer has no intelligence. It's just mapping.
So from a KISS point of view, okay, the external project, which is why we started extracting the controls, could now use the controls itself and build the logic for its own needs.
Was our implementation perfect? I don't think so. We made a compromise. We have accepted to repeat ourselves in order to keep the complexity of the structures low and not to introduce too many additional layers. In a "healthy world", there would be a common data model and the existing infrastructure would serve the same interfaces. Both projects would have been able to use exactly the same library without any further action. In this case, however, a solution had to be found that everyone could work with.
It is undeniable that the cost of future customisation of this component has increased. If another property of the data model is to be part of the view in the future, adjustments have to be made in at least two places. Perhaps we could have found a way to avoid this extra effort. But our gut told us that such a solution would be much more complicated. This in turn increases the amount of change required, and a developer not fully trained in such a solution would be more likely to make mistakes.
In the end, the decision is based solely on the opinions and feelings of the team members. Maybe another team would have chosen DRY over KISS. After all, some increase in complexity is normal as the complexity of requirements increases, and that is fine as long as everyone involved can cope with the increase and no one is left behind. Breaking DRY, on the other hand, poses a risk to any team, regardless of the skills of individual members, regardless of the difficulty of the architecture and structure being used. I'm sure every developer has been guilty of copy & pasting at some point in their programming career. And almost everyone will have experienced how easy it is to forget something and make mistakes when copying code. Maybe the problem is discovered immediately and fixed within seconds. But it is also possible that such a mistake could have major consequences that no one notices until later.
There may be situations where there is a tension between different Clean Code principles, KISS and DRY being just two examples. If there is no good solution that satisfies both rules, then you need to think very carefully about which one to prioritise. The pros and cons of both options must be thoroughly explored, and ultimately the team must agree and commit to one of the options. Only time will tell if this was a good decision.
How to develop software that can be flexibly and adapted to new market and customer requirements in the long term
Clean code development is divided into different grades, which you as a developer climb one by one and repeat in an eternal cycle
The value system of Clean Code Development comprises the values of evolvability, correctness, production efficiency and continuous improvement.
To become a Clean Code Developer, you need to internalize a number of virtues: Appreciate variation, only do the bare minimum, isolate aspects etc.