An in depth guide to the Extreme Single Responsibility Principle

Note: This is a more in depth look at an article I previously wrote titled, Intro to the Extreme Single Responsibility Principle. The previous article is not a prerequisite.

I’m going to talk about a technique I picked up that I’ve never seen done anywhere except for where I currently work. Because I’ve been programming professionally for over a decade and this is the first time I’ve seen this, I figure it must be relatively unfamiliar to others. This is a technique I’ve been calling “Extreme SRP”. But before I explain what the Extreme version of SRP is, I should explain what SRP is:

SRP stands for the Single Responsibility Principle. It means that a class should only have one reason to change. If you want to learn more, this link is a good reference.

Extreme SRP, surprisingly enough, is taking SRP to the extreme. Before I talk about what it is, I want to preface the description with a suggestion: Keep an open mind. When I first saw this technique my immediate thought was, “That’s not idiomatic”. But, I went with it to see how it’d turn out. In the end I had few complaints but I thought I would have many. It’s easy to think, “That looks weird so it must be bad” but unless you try it, you don’t really know if that’s true.

Extreme SRP converts your code base to look like a tree of nodes and leafs. A leaf contains the “meat” of your feature. It’s where your code actually does something. A node, on the other hand, merely delegates to other nodes and leaves. It never has any “meat” in it. Visually, here’s how the nodes and leaves look:

Tree

Note that the tree in the image also has arrows. These are nodes calling other nodes/leaves. When these calls occur, “data” is passed from a node to a node/leaf and “data” is returned by the call. So far this description is probably a little vague, so here’s a code example to make things a little more concrete:

With this example in mind, I’m going to describe Extreme SRP with a set of rules. Here are the rules for nodes and leafs:

  1. Every class can only have one method.
  2. That method must be public.
  3. Every field of the class must be dependency injected.

Here are the rules for data:

  1. There must be no logic.
  2. It must be possible to get each field.
  3. It must be possible to set each field.

Here’s how that OutputData class might be defined:

Lets look at another example. Here’s some code that changes a user’s email address written in the Extreme SRP style:

This class does not know if it’s delegating to nodes or leafs, but it does not have any logic in it besides delegating. That’s how you can tell it’s a node. I would imagine the EmailAddressChanger is a leaf. It probably creates a new User that’s a copy of the input but with a different email address. That logic is “meat”, not delegation. So that class would be a leaf. And the User class itself is data. It only has fields and no logic.

Now lets look at the pros and cons of this style of programming:

“Extreme SRP” has all the advantages of SRP:

  • Code complexity is reduced by being more explicit and straightforward
  • Loose coupling
  • Improved readability

The methods are very small and easy to understand. But, if you’re looking for a specific leaf and you don’t know its name, you have to traverse the tree to get there. Using traditional object oriented techniques, this code would often be inlined into one place so it would be easier to find. This is one of the downsides you have to live with when you practice “Extreme SRP”. But this cost brings the benefits mentioned above.

This code is not very idiomatic, at least for most object oriented languages. It’s more procedural/functional in nature. But because of the rule above that says every field must be dependency injected, the entire code base is extremely easy to test. The non-idiomatic code threw me off originally, but the ease of testability makes it worth it, in my opinion.

Wouldn’t it be better to just use a functional language for this instead of an object oriented one? A lot of functional programming concepts are in vogue right now and this technique comes to terms with that. Yes, a functional programming language would be better, but that’s not always an option. If you have to maintain a large legacy code base, you usually don’t get the luxury of rewriting it in a new language: You have to gradually refactor it and add new features. In my experience, an Extreme SRP code base is easier to maintain than a traditional object oriented one. So if you have to use an object oriented programming language, you should consider writing your code in the Extreme SRP style.

But, why not just use functions? If your language makes it convenient to write pure functions, by all means use them instead. But if your language doesn’t make that easy, I prefer the Extreme SRP style because it has code seams (the classes and the calls between them) to make everything unit testable. When your leafs have side effects, it’s good to have a way to mock out the side effects when you’re testing other parts of your system. This isn’t always easy to do with impure functions and Extreme SRP can be thought of as an alternative way of achieving this important testability.

The way you go about creating a system like this from start to finish has a lot of advantages. You think about a node and what it directly delegates to instead of the whole tree at once. How you go about thinking this was is documented in another article I wrote. But to summarize the idea behind this, look at the ChangeUserEmailAddress class. When that class was created, you didn’t have to think about the implementation details of finding a user, changing its email address, and saving it back into the database. You just delegate to classes that have that as their sole responsibilities. Then you focus on the implementation details of one at a time. As long as the plumbing of the method signatures hook up, you can thinking about how to implement how to save a user before you’ve thought about how to find a user. This is a useful way to deal with complexity: Divide and conquer.

There are no cyclical dependencies where A depends on B and B depends on A. This simplifies a lot in your code base and makes it easier to modularize your code. The code base requires minimal refactoring. Usually I can throw away a node and rewrite it instead of refactoring it into what I want. Or I simply have to connect one node to a different one, etc. Whereas using traditional object oriented techniques, refactoring feels built in to the process and is the first and last step of adding a new feature.

This technique does create an explosion of classes and you have to come up with names for all of them. It’s not always easy but whenever I struggle with this I feel like there is a concept that needs to be named, I just can’t think of the name for it. It’s rarely a situation where the rules are forcing me to think in unnatural ways.

Because of this class explosion, you have to be really good with your namespaces to organize your code. Otherwise, you’ll get lost and it’ll slow you down to browse your code base.

Other than that, I’m really happy with “Extreme SRP”. I recommend that readers put their biases aside and give it a try on your next hobby project and see how it turns out for you. I’d love to hear your feedback on how it turned out!

Join the email list for bonus content.

Leave a Reply

Your email address will not be published. Required fields are marked *