One of the biggest struggles that developers have with adopting Unit Testing, whether it’s Test Driven Development (TDD), Behavior Driven Development (BDD) or even just Test Eventual Development (TED), is the difficulty some code is to test. This is typically when code doesn’t follow the SOLID design principles. If you aren’t familiar with Robert C. Martin’s SOLID Principles, here is a quick summary.
There are five SOLID principles put forth by Robert (Uncle Bob) C. Martin.
Single Responsibility Principle
A class should have one, and only one, reason to change
Did you ever have a Swiss Army knife? 64 features, but you could never get to the one you needed. And who uses the toothpick from a knife anyway? Yuck! Code that does too much is very much like that Swiss Army knife – trying to be all things to all scenarios results in overly complex and fragile code. Keeping your methods short and of singular purpose brings clarity and ease of maintainability to your software.
An extension of the Single Responsibility Principle is to make sure you don’t instantiate a class where you also use that class. Combining the creation and use of a class obfuscates the purpose of your code, and forces the calling code to change if the type of class you need changes. Instead, depend on abstractions and use factories or Dependency Injection to get the instances the calling code requires.
Open Closed Principle
A module should be open for extension but closed for modification
Just as you wouldn’t want to add a basement to a finished house, making modifications to fundamental parts of code significantly increases the chance of introducing bugs or side effects. It is better to use extensions (like adding a patio room) to add on to or alter functionality of a module through polymorphism than to continually edit the base code by adding switch statements.
Liskov Substitution Principle
Subclasses should be substitutable for their base classes
If you are at a restaurant, and order a salad, the way you eat it (and when it gets served) doesn’t vary just because this time you ordered a Caesar Salad and last time you ordered a house salad. In the same way, derived classed must be able to be used by the client code without any adverse effects on the function of the client code.
Interface Segregation Principle
Many client specific interfaces are better than one general purpose interface
Just like in Visual Studio where you can pick you development profile (C#, VB.NET), your classes should tailor their interfaces to the clients that consume them. Fine grained interfaces reduce the analysis paralysis that can occur when there are too many choices.
Dependency Inversion Principle
Depend upon Abstractions. Do not depend on concretions.
For those of you who drive, do you have to build your car every time you go to work? Can you drive more than one different car, or can you only drive one physical instance of a car? Have you ever borrowed a car (or rented one?)
Think of this in your software. When your code instantiates a concrete class and then uses that instance, it completely depends on that particular implementation, and will never be able to use another one until you change how the code is written. However, if you code to an Interface, you can borrow a car, rent a car, or buy a new one!
Let’s talk about how SOLID can help make the day in the life of a .NET developer better.
I was working on a large ecommerce project not too long ago, and a developer wrote the following code:
public class SecurityHandler
{
public bool LoginUser(string userName, string password)
{
LoginService service = new LoginService();
return service.ValidateUser(userName, password);
}
}
Listing 1
There are several things wrong with this (as the developer figured out). The ValidateUser method was never implemented, so the LoginUser call always failed.
public class LoginService
{
public bool ValidateUser(string userName, string password)
{
// TODO: Implement this method
throw new NotImplementedException();
}
}
Listing 2
Are we there yet?
If you are responsible for this section of code, and another team member (or third party organization) is responsible for delivering the service, you can’t compile your code until the service is done. This adds a lock to the development process, ensuring that the code moves forward on a single thread, crippling productivity.
It’s not my code that broke!
How many times have you heard that? The login screen doesn’t work. Where is the offending code? Is it the LoginService, or that class that holds the method LoginUser? Now we have to step through the code to see where it breaks, and we all know how much fun that is…
The code is doing too much aka the “All of my eggs are in one basket” principle
How is that? There’s only two lines! It’s not the line count that matters, it’s the content. In those two lines, the Unit of Work (in C#/VB.NET this is a method) is instantiating an object AND using it.
Because this method is responsible for creating and using the LoginService, it is tightly coupled to a single implementation. What if the organization wants to centralize the user validation service? If a new login service class is created (CentralizedLoginService), the code that consumes the service needs to be changed, looking like the following code block.
public bool LoginUser(string userName, string password)
{
CentralizedLoginService service = new CentralizedLoginService();
return service.ValidateUser(userName, password);
}
Listing 3
What changed? Just the type of service that was instantiated. Nothing else. Yet this means editing the code, a recompile and a redeploy, and that is usually not a trivial exercise. Lots of wasted cycles that could have been used to further the feature set of the application.
This breaks the Single Responsibility Principle – “A class should have one, and only one, reason to change.”
Let’s get abstract
We already saw the problems with creating and using an object in the same unit of work. The first step in fixing this is to abstract the implementation away from the invocation. We do that by making sure we are always coding to an interface.
The Dependency Inversion Principle states “Depend on abstractions, not concretions”.
We have to extract the interface from the classes, and update our code to declare the instance as an interface instead of a type. (Yes, I could use “var”, but that gets away from the points we are making).
Telerik’s JustCode makes this very easy.
Navigate to the LoginService class by either:
- Clicking on “LoginService” in the LoginUser method and hitting F12
- Pressing Ctl-Alt-T (Find Type) and entering “LoginService”
Next, extract the interface by hitting CTL-~, selecting the “R”, and selecting Extract Interface (or selecting the menu JustCode->Refactor->Extract Interface…
Image 1
Once the Extract Interface dialog is up, click on “Select All Public” (as in Image 2), and the ValidateUser method will be selected.
Image 2
The final result is the code is Listing 4 (combined into one file for easy reading – the Extract Interface refactoring in JustCode will automatically places the generated interface into a new file)
public interface ILoginService
{
bool ValidateUser(string userName, string password);
}
public class LoginService : ILoginService
{
public bool ValidateUser(string userName, string password)
{
// TODO: Implement this method
throw new NotImplementedException();
}
}
Listing 4
Now that we have an interface created for LoginService classes, we can update the LoginUser method like the following sample in Listing 5:
public bool LoginUser(string userName, string password)
{
ILoginService service = new CentralizedLoginService();
return service.ValidateUser(userName, password);
}
Listing 5
But there is still a major issues with this code. We haven’t isolated the creation of the LoginService class (regardless of the actual implementation), so we will have to duplicate the above code each time we need the class. This breaks DRY (Don’t Repeat Yourself), and can lead to hard to find bugs. One way to resolve this is to use a factory pattern for object creation. This leads us to class that has the single responsibility of creating the classes, and a great example is the simple factory. When we create the factory object that returns an instance of an interface, we get code that might look like this:
public bool LoginUser(string userName, string password)
{
ILoginService service =
LoginServiceFactory.CreateService(ServiceType.Centralized);
return service.ValidateUser(userName, password);
}
Listing 6
Better. The only thing that needs to change is the factory when the implementation needs to change. And since we are depending on the abstraction of the login service, it doesn’t matter to the line of business code which implementation is passed back from the factory.
This also gets us in line with another SOLID principle.
The Liskov Substitution Principle states “Derived classes must be substitutable for their base classes.”
We can make any number of subclasses (as long as they implement the ILoginService interface), and the line of business code will chug along happily ever after.
Injecting In What’s Needed
Another way to resolve this issue (and is often used in conjunction with factories) is to use a pattern known as Dependency Injection, or DI for short. As the name implies, all of the dependencies for a class or method are injected in, creating a better separation of concerns without having to create a lot more code. Keep in mind that the service class will still have to be instantiated somewhere, and that could certainly be done with the simple factory or some other mechanism (as long as it doesn’t violate DRY).
There are several types of DI, but what we are going to talk about here is Constructor Injection. Again, as the name implies, all of the dependencies are injected into the constructor of the class. The ensures that all of the necessary scaffolding for the class are there when the class is instantiated, eliminating the need to a lot of guard clauses. (There are scenarios where setter injection and the other types provide value, but that is for another post).
This is very easy with JustCode. Starting with the code in listing 4 and the caret on “service”, select “Introduce Field” (Ctl-R, Ctl-L or Alt-Ins->Select “Introduce Field” or Ctl-`->Select “Introduce Field”). The variable name “service” will be highlighted, allowing for renaming. I prefer to have my fields prefix with “_”, so I rename it to “_service”, and it will be renamed everywhere. Hit Enter to complete the refactoring.
The caret will end up on the field name “_service”. From here, Create Constructor (Ctr-R, Ctr-C) and a constructor will be created with the ILoginService as a parameter. This is a convenient way to create injected constructors with JustCode. All of the fields that are selected will be added as parameters. You will need to update any code that instantiates this class to pass in an instance of the ILoginService. For now, you can use the factory that we utilized in Listing 4 in the constructor call.
Lastly, delete the assignment statement for “_service” in the LoginUser method.
We again refactor the code to use constructor injection, and end up like this:
public class SecurityHandler
{
ILoginService _service;
public SecurityHandler(ILoginService service)
{
_service = service;
}
public bool LoginUser(string userName, string password)
{
return _service.ValidateUser(userName, password);
}
}
Listing 7
Summary
What have we accomplished? We changed our code so that it only does the one thing that is it supposed to, we removed the concrete implementation, and we injected our dependencies. The code is still clean and concise, and pretty much the same number of lines that we had before the refactoring.
We are no longer dependent on the LoginService being implemented before we can test our code, get it checked in, and move on to the next feature request. We can test this code to make sure that it behaves as we expect, knowing that the dependencies won’t occlude the results.
Happy Coding!
About the author
Philip Japikse
|
Philip Japikse an international speaker, a Microsoft MVP, INETA Community Champion, MCSD, CSM/ CSP, and a passionate member of the developer community, Phil Japikse has been working with .Net since the first betas, developing software for over 20 years, and heavily involved in the agile community since 2005. Phil works as a Developer Evangelist for Telerik's RadControls for Windows 8 as well as the Just family of products (JustCode, JustMock, JustTrace, and JustDecompile) and hosts the Zero To Agile podcast (www.telerik.com/zerotoagile). Phil is also the Lead Director for the Cincinnati .Net User’s Group (http://www.cinnug.org). You can follow Phil on twitter via www.twitter.com/skimedic and read his personal blog at www.skimedic.com/blog. |
Technorati Tags:
SOLID Principles,
Single Responsibility Principle,
Open Clodes Principle,
Dependency Injection,
Liskov Substitution Principle,
Interface Segregation Principle,
Dependency Inversion Principle,
Telerik,
JustCode,
Navigate,
Refactor,
Don't Repeat Yourself,
Philip Japikse,
skimedic