Not that long ago, we published a post defending the SOLID principles of object-oriented design. In today’s post, we take it a step further: we’re going to present NDepend’s rules that will enable you to measure how SOLID your app is and show you the actions you can take to make it even better.
NDepend and the SRP
We will start by tackling the “S” in SOLID: the single responsibility principle. This is probably the most misunderstood of the principles, and, to be fair, there seems to be a reason for that. There is a degree of subjectivity around what constitutes a responsibility. So how can NDepend help with this conundrum?
Avoid Types Too Big
As its name makes it clear, this specific rule advises you to keep your types small. Keeping your classes small won’t necessarily cause them to have just one responsibility, but it certainly will steer things in that direction.
Nice, but how big is “too big”? According to the rule’s documentation, more than 200 logical lines of code is too big.
Avoid Types With Too Many Methods
Again, a rule with a very self-explanatory name. You might think that this rule is somewhat redundant, given the previous one, but it’s not. A type can be too big due to reasons other than a large number of methods; it could have a very large number of other members or maybe a single enormous method.
In the same spirit of the previous rule though, we have to ask: how many is “too many”? The documentation for the rule states that 20 or more methods per type is too many, adding that methods such as constructors or property and event accessors aren’t considered a part of the count.
Avoid Types With Too Many Fields
In continuing the trend of having a type with too many members, this rule covers fields. The documentation for the rule says it matches types with more than 15 fields. It also says that constant and read-only static fields aren’t considered, as well as enumeration types.
Avoid Methods That Are Too Big and Too Complex
Here we continue the trend of rules that encourage simple design, but we’re now at the method level, having left types behind. So, how is it going to be? Are we just going to recycle the same rules we’ve just seen but swap “type” for “method”?
Nope, not that easy. When dealing with methods, things get a little more complex, no pun intended. Let’s see what the documentation for the “avoid methods too big and too complex” rule has to say about its definition:
12345 //<b> This rule matches methods where *ILNestingDepth* > 2</b>//<b> and (*NbLinesOfCode* > 35</b>//<b> or *CyclomaticComplexity* > 20</b>//<b> or *ILCyclomaticComplexity* > 60)</b>//<b> Such method is typically hard to understand and maintain.</b>
In short, this rules worries about methods with a nesting depth that is larger than two and that also matches one of several other conditions, such as lines of code and cyclomatic complexity.
Avoid Methods With Too Many Parameters
After a detour to a more complex rule, we get back to very simple/to the point/self-explanatory terrain. This rule is pretty much what its name implies. What’s really left for us is to quantify. How many is too many?
Once again, we must resort to the documentation, which informs us that the rule matches methods with more than eight parameters. Curiously, the documentation for the rule justifies its existence by claiming that “such a method is painful to call and might degrade performance.”
Those are valid reasons, of course. But it’s also important to notice that a method with a lot of parameters is probably trying to do too much. A class with such methods is a serious candidate for an SRP violator.
Avoid Methods With Too Many Local Variables
Finally, the last of the many rules that can help you with the single responsibility principle. Just like most of the previous rules, this one has a name that says it all. Once again, the rule just has to define what it means by “too many.” And the answer, according to the documentation, is 15.
With that, we finally wrap up with the rules that can help you with the first of the SOLID principles. There were a lot of rules! That won’t happen again, though. Throughout the rest of the post, we’ll stick to one rule per principle. Let’s go to the open/closed principle.
Many people consider the open/closed principle as the hardest of all of the principles to understand and apply. As I’ve said in the previous post about SOLID, people way smarter than me have struggled with this particular principle, so there must be some merit to the perception that it’s the hardest of them.
The OCP states that our types should be open to extension but closed to modification. In other words, the addition of subclasses must not cause the parent class to change in any way. The “base class should not use derivatives” rule help us prevent that by dictating that a base class should never mention one of its subclasses.
In the post defending the SOLID principles, we’ve seen that Robert C. Martin defines the Liskov Substitution Principle like this:
Derived classes must be substitutable for their base classes.
When faced with such a definition, some people end up considering the Liskov Substitution Principle to be obvious or redundant. “Yeah, inheritance is a thing” they might say. “What’s the point?”
Well, the point is that people violate it all the time! One very common (maybe the most common) way of doing that is by having a class implement one or more interfaces and then throwing in methods that are not applicable in some way.
And NDepend just happens to have a rule to warn you against this scenario. It’s called, rather appropriately, “Do implement methods that throw NotImplementedException.”
You see, this exception is most useful when you’re using TDD. It’s a very common scenario like the following:
- When writing a test for a piece of code, you call some method that doesn’t exist.
- The compiler complains, which is a good thing.
- You accept the Visual Studio suggestion and generate a method stub since you don’t and shouldn’t want to implement the method right now.
- If you later forget to implement the method, the test will fail and you’ll be reminded.
That’s the acceptable usage of a NotImplementedException. If you use this—or other—exception to escape from implementing a method from some interface that your type implements, then you’re breaking the LSP.
The Interface Segregation Principle states that
Clients should not be forced to depend upon interfaces that they do not use.
In order to follow this principle, you should make your interfaces as small and granular as possible. Or, in other words, you should avoid interfaces too big, which is the exact name of the NDepend rule we’ll talk about now.
How big is “too big”? I’ll spare you from the suspense and get right to the point: 10. That is, this rule will match with interfaces that have more than 10 methods.
One nice thing about this rule is that it also helps you with the previous one. If you keep your interfaces as small as possible, you decrease the likelihood that all clients will provide meaningful implementations of all their methods. Two birds with just one stone!
The dependency inversion principle states:
Depend on abstractions, not on concretions.
Depend on high-level, more stable policies that are often presented as abstractions instead of relying on concretions that are volatile and more likely to change.
Suppose you have an application that is tightly coupled to the fact that it uses Microsoft SQL Server as a persistence solution. What ‘s going to happen if you need to change to another RDBMS? That’s bound to be a painful transition.
If, on the other hand, the business rules on your application aren’t even aware of the database—which is possible by employing proper architecture—they don’t need to suffer a single change in order for the transition to happen (which is not to say the transition will be extremely easy).
To help you with that, NDepend has a rule called “higher cohesion, lower coupling.” “Higher cohesion” and “lower coupling” are, similar to “separation of concerns,” phrases that people sometimes toss around as if they are self-evident, enlightening truths. In short, you want your software artifacts to have high cohesion (“to do one thing and do it well”) and at the same time have low coupling with each other.
This NDepend rule helps you do that by encouraging you to separate namespaces that contain abstracts from namespaces that contain concretions, i.e., the implementations of said abstractions.
Ready to Write More SOLID Code?
The rules we’ve presented above are just some of many NDepend has to offer. Are they enough to make your application compliant to the SOLID principles? Of course not. No set of tools or techniques is guaranteed to improve your code. Believing such a thing is step number one of falling prey to the dreaded cargo cult programming mentality. Don’t fall into that trap.
But while the rules alone won’t automatically make your code perfect, they offer you fantastic help by encouraging you to write types that are small, focused, and less complex.
Add to that constant reading and studying about the SOLID principles, proper architecture, and general best practices and patterns, and you’re on the right path to write great applications.