Are you writing a high quality code? Most of the developers would probably say yes. The really honest one would say that it might not be perfect but it’s OK. Both answers are fair. Most of us are very serious about the job we do and we always try to give our best (at least most of the time). The big question here is that if we’re all genuinely convinced about a reasonable quality of our code why there is so much technical debt wherever we look?
Should we worry about software quality at all? Surely we should be focused on solving business problems, not spending hundreds of man-hours on making a pretty code, right? We’ve heard it so many times in so many different forms that it has to be true! I don’t know who was the first person to make this statement but with eyes of my imagination I can see an overcaffeinated sales person.
If you think about how development time is distributed you might come to a conclusion that only 20% of it goes into new development and 80% is maintenance. In purely commercial terms cutting corners in the new development phase is saving $1 today and than losing $4 every following day. It sounds bad but, it’s actually even worse. Tolerating bad code has a snowball effect. Something which might look harmless at the beginning will quickly turn into a big problem. Roy Osherove in his book The Art of Unit Testing calls it the broken window (the Broken window theory). In a nutshell the theory says that one broken window will quickly turn into all broken windows, graffitis and burned cars in front of the building. Bad code is an invitation for other developers to produce even more bad code.
Following the best coding practice should support you in working fast and effectively. It’s counter intuitive for many people who wrongly associate it with slowness and unjustified perfectionism. Of course, even with the best code base there will be a time when you will have to break rules. When that happens make sure it won’t become part of your routine (if it’s a repeating patterns something doesn’t work), chose corners to cut very wisely and plan to fix all wrongdoings in the next few days.
So what is the definition of a good code? How can one tell the difference between good and bad. In simple terms I will risk to say that good code is a de-coupled code and bad code is a highly coupled code. For those who haven’t heard this phrase before coupling by Wikipedia definition is the manner and degree of interdependencies between software modules.
Why coupling is bad? Because it makes your code unpredictable, difficult to read, maintain and sometimes even execute. You might experience the Butterfly effect with it when adding a new menu item to a dropdown will for example result in truncating users table. This is of course an extreme case but how many applications are coupled with a particular environment, configuration or even dataset. How many developers can’t do their work properly because they can’t reproduce certain behaviours on their dev environment? How many bugs were closed only to open different bugs in a different part of the system. There is a countless list of examples which cost employers across the world billions of dollars or even a good name.
I assume we can agree that coupling is bad but how to avoid it? There is a very good term which I’ve found in the “Head First Design Patterns” book: “Program to an interface, not an implementation”. It’s simple but very powerful approach. It means that instead of writing a solution to a specific problem one should create an API for dealing with this class of problems and than use the API to solve the problem.
If you prefer more granular guidelines there is “SOLID programming” which in fact is 5 different principles. SOLID is an acronym and stands for: single responsibilities, open/close. Leskov substitution and interface segregation.
Single responsibilities principle – a class should have a single responsibility. What it means is that a single class should be focused on solving very narrow problem. For example, if you write an encryption library (which for the record would be a crazy thing to do) you don’t want all algorithms in one class. You will do better with a separate class for every algorithm and few additional classes to glue it all together. This principle can be also applied to methods and functions.
Open/close principle – software entities should be open for extension but close for modification. In other words new code should be extending the existing one instead of modifying it. The logic behind it is that modifying the existing code might break already working features so it’s safer to avoid it.
Liskov substitution principle – objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program. This could be understood as whenever you decide to override a parent method from a child class keep the behaviour consistent.
Interface segregation principle – many client-specific interfaces are better than one general-purpose interface. This could be seen as an extension of the Single responsibilities principle. In simple terms it encourages to continue narrowing down responsibilities of a single class. If a class grows too large It can be broken into at least two different classes. Analogically this can be also applied to methods and functions.
Dependency inversion principle – one should depend upon abstractions. Do not depend upon concretions. In essence this principle says that classes should depend on abstraction not a concrete implementation.
Although software quality is a vast topic this short list of guidelines should be enough to (if followed) significantly improve code quality. The domain specific terminology will also help to discuss code in an objective non-personal and respectful way.
Achieving and (what’s more difficult) maintaining a high quality source code requires team’s commitment and is a never ending process. As with the best coding guidelines it can be very smart and complex but I will try to simplify it.
There are two key components which I believe are sufficient to lay a foundation for a well maintain and stable software. Those are also prerequisites to more mature methodologies which you can build around them with a time. Those components are: unit testing and code reviews:
I deliberately said unit testing instead of a Test Driver Development because TDD can be a big move for an inexperienced team. You have to start somewhere and it’s better to take it easy. A good start would be writing Unit Tests for all core classes and for reproducing bugs before fixing them. The less critical code should be written with Unit Testing in mind. What I mean by that is it should be possible to write a Unit Test for such a class without a major refactoring (ideally no refactoring should be needed).
Unit tests will not only improve the stability of your software but also it’s quality! The reason for that is you can’t create a correct Unit Tests for a coupled code. If you’re not experience with Unit Testing I do recommend you a great book I’ve mentioned before – The Art of Unit Testing by Roy Osherove. If you already have some experience with Unit Testing than perhaps you might be interested in refreshing you knowledge about Unit Testing best practices.
All of the clever ideas I’ve already mentioned won’t help you much without consistency. Code can get easy out of control if the team doesn’t keep an eye on quality and how the new commits fit into the big picture. Regular code reviews will help you embracing the quality-centric culture and collaboration.
The easiest approach would be to setup weekly code reviews where everybody go though the latest commits of their peers. They can list out all issues and then discuss them on a meeting following the review. Ideally all of the issues should be fix on the same day after the meeting (it might not always be possible).
Regular reviews are good but the team might find them impractical at certain periods. If you’re using GIT for version control then you can build code review around pull requests. There are few good products to support it but if you’re not happy to pay you can install something like GitLab. It’s an open source version of GitHub and provides you similar functionality.
Writing a good code is not difficult and doesn’t require lots of additional time. I would say it’s actually the opposite. Bad code is very difficult to work with and cost lots of money in the long run. I hope you will find the proposed approach useful and give it a try. If you’re familiar with this subject but have a different opinion or perhaps some suggestions I would love to hear it.
### Single responsibilities principle
It is fine in principle. However, in practice there are two problems. First, one do not have a narrow problem to begin with. The natural process is to have a general problem, and as we work, we factor into smaller and smaller problems. So this principle is rather the end results of re-factoring. Without this context, people often try to factor their problems over jealously and over maturely, resulting in wrong factors and obscuring the bigger problems. Refactoring is good process from top down, not bottom up. To re-organize the existing factors often is a difficult and costly process, especially after these factors being fully integrated into a web.
Second, it is not always better to have the code factored. Each factor does not come free, it comes with its wraps and connectors, scopes and interfaces. So the refactoring process is really a constant balance of cost/benefits, the criterion being the manageability on the comprehension and maintenance. I emphasize balance here, treating this rule as a golden rule is responsible for some of the nightmares.
LikeLike
This is a very interesting point, thank you for bringing this up. I can only agree, one should be careful to not overdo this principle and apply it rather during re-factoring than the initial coding.
LikeLike