During the last months I had a few controversial chats about the “Single Responsibility Principle” (SRP), which is a concept in object-oriented programming for better encapsulation. Interestingly, the same conversation flamed up again and again when discussing Reform’s validate method.
Since that “validate” method does a bunch of things I was accused of exposing “a method that breaks SRP”.
What Is Not SRP?
A Reform form object comes with a handful of public methods, only. Their names are
#save. There are a couple more but that’s not relevant now. As their names are pretty self-explaining let me briefly talk about
Here’s how you use this obscure method.
result = form.validate(params)
So, what validate does is it first populates the form’s internal attributes with the incoming
params. It then runs the defined validations in that form instance and returns the result.
Several people complained that this is not a good API as it breaks SRP – the validate method was “doing too much”.
I don’t really know if SRP only applies to classes but I can say one thing for sure: SRP can in no way be used with methods. If you say “this method breaks single responsiblity” you are talking about private concerns within a class.
You’re right, because it’s a good thing to break up logic into small methods. But you’re wrong, because you’re talking about the private method stack and not the public API of a class.
In my understanding, when talking about SRP you talk about classes.
What Is SRP?
I had this eye-opening moment in a brillant keynote by Uncle Bob at Lonestar RubyConf a few years back: An SRP’ed class is reflected by having exactly one public method.
Having this pretty simple rule, I admit that Reform is not SRP. To have a clean architecture, I should split Reform into one class per public method:
Reform::Validate, and so on.
form = Reform::Setup.new(model).call result = Reform::Validate(form).call(params)
Each class would only expose the
#call method, in an SRP setup there’s no need to name the only public method, the class name tells you what’s gonna happen.
Of course, this is super clumsy and no one wants to work with a single responsible “API”.
As a side note, Reform does exactly that behind it’s manly back – it provides you all the necessary methods via one instance, then orchestrates to separate objects. You don’t need to know how it works as a user.
About API Design.
I have no clue where it comes from, nevertheless, exposing as many methods as possible to your class’ user seems to be “OK” or even “cool”, coming from Rails where an unconfigured ActiveRecord model offers you 284 public methods right away.
During +10 years of designing open-source frameworks I realised that the more public methods I allow my users to call the more work it gets to change my framework’s API later. Deprecating public methods is a pain in the ass.
Coming back to Reform, people suggested to split
#validate into two public methods: One to populate (or “fill out”) the form instance, one to actually validate it afterwards.
The word “after” indicates only one of the problems you introduce by extending the API:
- Users will fuck it up. They will call
#fill_out, then ask why validate doesn’t validate and then someone else will reply that they forgot to call
- They will call
#fill_out– in the wrong order.
- Reform is a form object – there simply is no case where you wanna fill out a form but then leave it unvalidated.
I decided to leave the validate method as it is and I do not regret it. Acceptance for this rebelious method increased after improving its documentation.
Don’t use SRP when talking about methods. It’s a concept to be used with classes that expose a single public method.
The more methods you expose, the more things can go wrong due to wrong order, not calling a method or general confusion. Don’t make methods public because they “could be helpful”. A good API has a limited set of methods, only. If people ask for more, think about moving it to a separate class.
Applying SRP to workflows and generally to objects in (Rails) app, and orchestrating those, is one of the numerous interesting topics discussed in my upcoming book. Sign up for the mailing list!