(I originally posted this on my MSDN blog.)
A question came up on an internal email list recently – someone asked if general utility or helper classes violated the Single Responsibility Principle. For example, you might have a class named StringHelper where you’d keep all kinds of handy little code snippets that you want to reuse; anything from SerializeString to CheckForSpecialCharacters. You could say that class has the responsibility of holding reusable code snippets for strings. Does it conform to SRP?
Open to Interpretation
The Single Responsibility Principle can be a little tricky to pin down if you try to treat it as an inviolate rule. There’s a reason it’s called a “principle”, after all; it’s a guideline that needs to be applied with interpretation for your specific context. Remember that the SRP is only useful to help drive us to a certain goal – to create loosely coupled, highly cohesive, object-oriented code which leads to flexible, maintainable software.
The idea of a “responsibility” is a fuzzy one. Responsibilities are usually multi-layered and a very broad responsibilities can be broken down into multiple more specific responsibilities, and those in turn can often be broken down further. What’s the right level at which to look for single responsibilities?
The SRP is often accompanied by a clarifying statement: “A class should have only one reason to change.” That helps a bit but it’s still open to a lot of interpretation. Most reasons to change are composed of multiple smaller reasons. Where do you stop decomposing reasons and call it a day?
Ultimately, it’s a judgment call. One reason why people are talking a lot about Software Craftsmanship these days rather than Computer Science is because for better or worse, our discipline still requires a strong aesthetic sense. There’s often not one provably correct solutions; there’s only solutions that “feel” right and that tend to perform better in the long run.
I think that one good rule of thumb is to make sure that your responsibilities and reasons map to concepts in your problem domain. You should be able to say, “If this specific operation in my problem domain changes, then this specific class will change.” That helps you to avoid working at levels of responsibility that are too broad or too narrow and will help you focus on building flexible software.
In the question above, saying that a string helper class has the single responsibility of holding reusable bits of code pertaining to strings is way too broad. That’s not a responsibility in the problem domain; that’s a responsibility in the code-writing domain. It doesn’t map to anything real and isn’t going to help you create flexible code.
So what’s wrong with helper and utility classes? I very much dislike them in an OOP design because inevitably they becomes a dumping ground for all kinds of things that don’t relate to each other at all in the problem domain. It’s an attractive nuisance, to use a legal term. There’s just no way those kinds of classes won’t turn into garbage dumps. You end up with little random bits of problem domain concepts that have been ripped out of their rightful places and stripped of proper naming that would help developers reason about the code and its intended use. One of the basic tenants of object-oriented programming is that your code should be grouped into objects with names that represent some actual concept in your problem domain. A general Utility class is the exact opposite of that. It’s not anywhere close to following OOP or SRP.
For some reason, most developers (including myself) have a strong aversion to creating new files and new classes. We’ll go to just about any length to find some way to shoehorn the code we need into an existing class rather than creating a new one. But a real adherence to OOP means that you’ll have lots and lots of small, single-purpose classes. The level of complexity goes up in one sense just due to the sheer number of classes, but the complexity of each individual class goes way down because it’s small and easy to understand at a glance. The net result is usually quite positive.
Back to the original question again, a method like StringHelper.CheckForSpecialCharacters(string) would probably be better expressed as something like CustomerNameValidator.IsValid(string) or PathValidator.ContainsIllegalCharacters(string) or whatever makes sense based on the intended use of that method. Sure, CustomerNameValidator has only one method on it, which feels like of silly to create a whole new file for, but it gives you some serious advantages. One, the name tells you more about what it does in the problem domain. Two, any code that need to validate names takes a dependency only on that one bit of code and not on serialization or anything else it doesn’t need. Three, it makes it easier to reason about what dependencies your code is actually taking. Four, the CustomerNameValidator class is much easier to understand at a glance than a giant StringHelper class would be.
Extension methods are another good way to add new behaviors to existing objects that you don’t own. Sometimes you have a method that’s targeted at such a low level that it doesn’t have anything to do with the problem domain, like perhaps a routine that returns the last N characters in a string. In the past you probably would have had to create a StringHelper class for that, but with C# 3.0 you can add it to the String class as an extension method. You should be careful not to abuse the mechanism but when used properly, extension methods are very effective.
Software Development Is Communication
If I were a brand new dev just joining a project, and I read a line calling StringHelper.CheckForSpecialCharacters, that name doesn’t tell me much about what it actually does. What characters are considered special? Why? Can I call that method for file paths? For URLs? For street addresses? I’d have to actually go read the implementation of CheckForSpecialCharacters before I could reason intelligently about its behavior. On the other hand, if I read something like CustomerNameValidator.IsValid, I still may not know the precise details of what’s considered a valid name, but often I don’t need to. I do know its intent, and that’s usually enough to let me reason intelligently about the code I’m reading. I also know when it’s appropriate to use that method in new code.
OOP design principles, including the SRP, aren’t terribly important when you’re writing the code for the first time. We’re all talented enough to write code any way we want to and make it work. Those principles become important during maintenance (i.e. any time after you first wrote it) where the most important information flow isn’t from the developer to the code, but rather from the code to the developer.