One thing that I always try to bring home to the developers at NML is this:
All code changes introduce risk
For some reason, this concept is understood at face value but does not really translate into how we developers approach coding.
I have a couple of mantras I repeat over and over to my teams. Here are the top three:
- In virtually all contexts, readability trumps other considerations as it improves maintainability.
- All code changes introduce risk. Code such that when changes are needed, the least amount of code is touched.
- Do the simplest thing first and build from there. You can always make things more complex, but it is much harder to make complex things simpler.
The biggest side effect of the fact that coding is hard, is that it is also very easy to break things. Seemingly innocuous changes can have unforeseen consequences.
SOLID = managing risk
Every one of the five principles in SOLID reduces risk.
The more responsibility a module, class or method has, the more likely it is to be touched when change is required. It is just logical. If one piece of code has three behaviors and one of those behaviors needs changes, then all three behaviors are at risk of breaking.
Allowing implementation to be open for extension and closed for modification hugely reduces risk. If you can implement what is needed by extending an existing class, the only risk is to the new implementation. If you modify a class, especially a base or abstract class, you introduce risk to the class you are modifying, all inheritors and all other code that use it.
This is a straight forward one: if you cannot reliably use a concrete class anywhere only the interface or abstract is known, you are introducing risk, as you have built-in the ability that your application will fail in specific conditions.
This aligns very closely with SRP and Liskov substitution, and following this principle reduces the risk for the same reasons as the aforementioned. Dependency Injection
As I pointed out in my article on unit testing, dependency injection allows you to reduce risk by covering almost all your implementation with unit tests.
Additionally, you can change the behavior of a component by registering a different implementation for a dependency. Your risk is less as no changes are needed in any code that uses the dependency. They reference the abstraction of the dependency, generally an interface.
Lastly, inversion of control removes the lifetime management of your dependencies outside of the scope of your implementation. This hugely reduces risk as you don’t need every developer to know the intricacies building a dependency hierarchy in code, and more often than not, getting it wrong.
Architectural seams are the border between distinct parts of your system. The data, logic, API, UI, etc are all areas within an application that serve a specific purpose. The purpose of an architectural seam is to provide separation by allowing cooperation without bleeding implementation across distinct areas.
They reduce your code risk by limiting the impact of changes to an area to extend only up to the seams.
Automapper on the seams
Automapper is incredibly useful for many reasons and reducing code risk is one of them. A well-architected system relies heavily upon models to transform and transfer data between layers at the architectural seams.
Using Automapper to perform this duty reduces your risk as changes to model structures are contained in mapping profiles. You might not even need to touch the mapping profile if the affected items do not need explicit mapping.
Source control is a fantastic way to mitigate risk. They provide a history of changes, strategies to control code merging, etc. I do not know of any software company that does not use source control, however, there are some basic measures one can apply to enable your source control system to mitigate risk even further.
The master branch is your source of truth. It, therefore, needs to be carefully looked after and protected. Policies that limit how code is merged into the master branch is vital to reduce your risk. Various Git vendors, for example, provide different kinds of policies you can apply to protect your branches, and you need to find the right balance between flexibility and freedom, and allowed risk.
There is nothing worse than trying to find a code change in history and having to troll through rubbish comments on commits that give no proper insight into the changes they contain. At NML, we follow the Karma commit message style, and since employing it as a standard, it has made a tremendous difference in our coding lives.
It may seem odd to some, but there are many companies still not using pull requests for controlling code merges. Pull requests are critical to reducing risk, as they allow other skilled eyes to peruse the suggested code changes and identify problems. Even just putting the obstacle in place that forces a developer to stop and hopefully think about what he or she is about to submit for scrutiny, already reduces risk.
There are many other areas within the development discipline that effects code risk, and an exhaustive list will make this article impossibly long. The items mentioned here were paramount in minimizing NML and our clients’ exposure to code risk, and every company should, at a minimum, apply these to their contexts.