SOLIDifying C# Code - xerocrypt/Misc GitHub Wiki

Class, Method, Variable and Property Naming

As anyone who's tried to analyse the output of a decompiler would attest, the descriptive naming of objects within the code makes a huge difference to its readability. This principle should also be applied to classes, methods, variables and other objects when creating a program, so the names are descriptive of the objects' purpose and function. This will be important as we refactor code.

Single or Limited Responsibility Methods

A method or function should do one thing only, and have a single responsibility. Structuring code this way should make it easier to test each unit in isolation (when loose-coupling is applied), and it should be easier to extend or modify the code without inadvertently affecting the overall behaviour of the program. In an existing project, I'm looking at methods for units of operation that could be extracted out. There is a 'Quick Actions and Refactorings...' feature in Visual Studio that can do this for us.

Open/Closed Principle - Maker of Things Visible and Invisible

This SOLID principle states that a unit should be closed to modification but open for extension. The idea behind this is that developers should extend existing units of code, instead of modifying them, when implementing new features. A good reason for this is the assumption that a working iteration of the program is dependent on code that already exists, and therefore modifications come with a high risk of introducing defects. Secondly, the practice of extending existing methods helps us to avoid the duplication of code.

What does this mean in practice? Any class that could be re-used should be instantiated as a base class for whatever extends it. Visual Studio already does this by default with commonly-implemented features, such as MVC controllers. Let's look at a WebAPI controller that I've recently refactored:

As we can see, GetLookupDataController is a class that's derived from ApiController, and everything visible here is really an extension of that base class. Whenever we want to add a new WebAPI controller to the program, we declare the same ApiController instead of duplicating it under a different name.

I can provide an even simpler illustration of this principle at work: In my project I have a data structure called 'Item', which has three properties:

What if I anticipated a feature request that involved a larger version of this data structure? In that case, I'd rename the 'Item' class as 'BaseItem':

I could then add another class containing the additional properties that extend BaseItem:

When executed, the software would construct this as a single data structure containing all the properties within the base class and derived class.

Liskov substitution principle

This principle essentially seems an extension of the previous one. A derived class should implement all the functionality of its base class, without modifying whatever it's inherited. If the latter isn't the case, it's an indication that the base class violates the minimal-responsibility principle. In other words, a program's behaviour should remain unchanged if the reference to a base class was substituted with a duplicate of its code.

Interface Segregation

A client shouldn't be dependent on things it doesn't use. This form of dependency could be inadvertently created if we're setting up an interface with multiple methods. Imagine an interface called 'IFileOperations' that contains three implementations: Read(), Write() and Save(), that respectively implements three operations, read file, write to file and save file. Any client calling that interface would need to do something with all three implementations, even if only one is needed, or throw a 'not implemented' exception.

One way to solve this would be to put each implementation within a separate interface, IRead, IWrite and ISave. Or we could logically map interface names to implementations. e.g.

class FileOperations : IRead, IWrite, ISave

{ // Implementations here //}

Dependency Injection

Here I'm using the constructor injection method to resolve the tight coupling between a WebAPI helper and a GetDbReader class. In this project, GetReader() is the method that executes SqlCommand() using the parameters passed from the helper method. I started out with the following code in Helper.cs:

As we can see, GetLookupDataHelper() is dependent on an instance of GetDbReader(), which contains a method that implements the database reader function.

It is possible to use a form of dependency injection here, so the helper class and GetDbReader() aren't so tightly coupled. I added an interface called 'IGetDbReader' and declared the GetDbReader class as a member of it. After the interface is created, the helper instantiates it:

Getting the helper method to use the interface was easy, a simple matter of changing a line so that the interface was instantiated as 'getDbReader'.