When starting a new project, nobody intends to write poor code; yet without vigilant watch and a practice of refactoring, every code base eventually descends into chaos. Often the damage is done before anyone realizes it. One day you are looking through the code to make a change and you think to yourself, “This code is terrible! Who wrote this anyway?” You pull up the history on the file and realize… oh, it was me! Not a good feeling.

Nevertheless, there are still things you can do to restore order to the mess and reinstitute the SOLID design principles. This guide will take you through the five SOLID design principles in two parts and point out what problems to look out for and how to fix them. In Part 1, we’ll look at the first two principles and in Part 2 we’ll continue with the next three.

1. Single Responsibility Principle (SRP)

What’s the problem?

The Single Responsibility Principle (SRP) dictates that each class should have only one reason to change. More than one responsibility couples the behaviours and may lead to changes that have unintended consequences. Imagine a BankAccount class with three methods:

Single Responsibility Principle-1

This class is used in two different contexts; the UI calls DrawToCanvas() when drawing the BankAccount and an InterestCalculator calls GetBalance() when calculating the monthly interest. This class violates SRP because there are two reasons it may have to change: 1) a change in how the transactions are calculated, or 2) a change in how the account is displayed to the user. If someone updates the UI to store the balance in cents instead of dollars, the InterestCalculator may start reporting an interest 100 times greater than the correct value!

Code Metric Results in Visual Studio Code Metric Results in Visual Studio

Where do I find it?

One of the best places to start looking for SRP violations is in the classes with the most number of methods and dependencies. If running static code analysis is an option for you, look for classes with a high Class Coupling metric. Classes with a high level of coupling are most likely to have a low level of cohesion. Low cohesion means many of the methods on your class are working with different data, a code smell that often indicates an SRP violation. In the BankAccount example, DrawToCanvas() was the only method with a dependency on Canvas – an indication of low cohesion pointing to an SRP violation.

What can I do about it?

Depending on your context, the solution to the SRP violation will vary, but the Façade and Proxy design patterns will likely come in handy here. In our BankAccount example, we could use the Façade pattern to pull out the Draw functionality:

Single Responsibility Principle-2

Before you start hacking, however, remember that the class you are refactoring may be fragile due to the very problem you are trying to fix! Unit tests will be somewhat helpful here, but once you have modified your tests to compile with your changes you may lose much of their benefit. Better yet are integration or UI level tests that run from a higher level and won’t require as many changes. Cover your new code well with unit tests to ensure correct behaviour now and in the future.

2. Open-Closed Principle (OCP)

What’s the problem?

The Open-Closed Principle (OCP) defines modules as open for extension, but closed for modification. Does this mean that once you write a class you should never change it again? Probably not. Robert Martin clarifies that, “it should be easy to change the behavior of a module without changing the source code of that module.” Creating a design that allows for this is hard because it requires a lot of foresight into how your application will change. If you make your classes extremely extensible for features that are never added you have over architected and complicated your application. If you don’t allow for extension in the places that do change you may require large scale architecture changes to add a simple feature.

Where do I find it?

To find the most obvious OCP violations look for repeated switch statements and if-else blocks throughout the code base.

Open-Closed Principle-1

This model breaks OCP because as soon as you add another type of Bank Account, you will need to modify the BankAccountCanvas class, demonstrating that it was not “closed for modification.” You can also look to your past experience with the project to bring up possible OCP violations. What features have been added recently? Did you have to make changes in a number of different places?

The priority when enforcing OCP is identifying anticipated places of future change. Fixing violations does not give your project an immediate benefit; it is only helpful when new code is added. You want to anticipate those changes and ensure you have a good design that will allow you to make them easily bug-free!

What can I do about it?

Your goal here is to encapsulate the portions of code that vary, allowing new additions to be as self-contained and non-invasive as possible. In our BankAccountCanvas example, that means pulling the account title into the BankAccount class, simplifying the Draw() method substantially:

Open-Closed Principle-2

Once you have sufficient test coverage to ensure you are not breaking functionality, work on separating volatile code from static code, creating a design that will allow new changes to be localized and self-contained.

Though they are always the goal, it is easy to move away from the SOLID design principles as a project matures. Regularly refactoring the code base to maintain the SOLID principles will keep your project agile and extendable. In Part 2 of this post, I will look at the next three design principles: The Liskov Substitution Principle, the Interface Segregation Principle, and the Dependency Inversion Principle.


Leave a Reply