Question

I my job, I am tasked with the responsibility of improving the code quality. To meet this responsibility I often pair program with developers and conduct sessions on design principles and design patterns. I was surprised by a developer who said either he has to violate Single Responsibility Principle or YAGNI.

Single Responsibility Principle states that every class or module should have one reason to change. To restate it for clarity. A module should be responsible to one, and only one, actor.

In my understanding the reason we follow single responsibility principle is because: if a class is responsible for two actors, And if one actor drives a change in that class, there is a possibility of unintentionally changing the requirements of the other actor.

YAGNI acronym for You ain't gonna need it. The extreme programming principle states that do the simplest thing that could possibly work. I often leaned upon YAGNI to make my code simpler by removing needless modularization.

Conflict between SRP and YAGNI: We were having a work flow implemented in the system. And there was a need to collect data about the usage of Work Flow. We needed to find out about the percentage distribution of parameters used in the work flow. I explained that the data collection has to in an other class (and we could possibly use observer or decorator pattern) and not in class where the work flow is implemented as both of the requirements are driven by different actors in the system. The work flow is serving the end user and the data collection is serving the product management.

My colleague wanted to directly log the parameter from class where the workflow is implemented. He told me this is the simplest thing to be done to get it working.

I am nearly certain in this scenario I have to follow SRP here. And I am following SRP (implementing in 2 classes) because if there is change in the work flow the probability of accidentally modifying data collection is low. And when when there is a change in data collection, the probability of accidentally modifying the work flow is also low. But when I explain about the possible change in workflow or data collection, he tells me "You ain't gonna need it".

Any suggestion on how this could be explained?

Was it helpful?

Solution

YAGNI means to avoid investing effort into code changes for hypothetical requirements which may arrive later, and instead focus on the requirements one has now. But this is not restricted to functional requirements - as long as one does not create "use-once-and-then-throw away" software, there is always the non-functional requirement of keeping code readable, understandable and evolvable. And separating responsibilities is one of the major tools to achieve that goal.

Hence I interpret the YAGNI principle in such a situation as a recommendation for not separating responsibilities before the benefits of the SRP become visible, which is actually as soon as the code becomes convoluted. And that usually happens very quickly when one tries to implement different business requirements in one class.

I would tolerate it if I had to make two or three small extensions to the "class where the workflow is implemented" to add this logging requirement. But then my pain level would probably be reached, and I would start thinking "heck, can we refactor this logging mechanism, or at least the data collection out of the workflow class to a better place".

So instead of telling your devs:

  • "create a separate data collection class right from the start just in case" (which makes your request prone to the YAGNI counter-argument),

tell them:

  • "refactor to a separate data collection class as soon as different responsibilities become apparent and it helps to make the code clearer".

That should be the justification one needs to apply the SRP not for some unknown requirement in the future, but for the requirement of keeping the code understandable now.

Of course, the threshold where code is perceived as being convoluted, and when the SRP could be applied to fix this may vary from one dev to another, but that is balance your team has to find, and where only code reviews can help.

OTHER TIPS

Principles should not be followed strictly.

They are there to illustrate a knowledge obtained through experience. Their goal is to give an insight on different aspects of software development. It is not surprising that they may sometimes work against each other.

I really like this Medium article that presents the following pyramid:

enter image description here

The idea behind the pyramid is that you shouldn’t undermine the lower layers at the expense of higher layers.

First of all, do not think this image is absolute. It should not be followed blindly. But it basically states that it is reasonable for some principles to be more important than others - and perhaps, getting your solution working should be the highest priority.

In the lenses of your SRP vs YAGNI question, I would ask myself:

  • Is your solution currently working? Or are you trying to improve it before getting the right functionality right?
  • Will SRP make your code better? Aren't you overengineering/generalizing something too early?

There is a really good talk from Sandi Metz, author of "Practical Object-Oriented Design in Ruby" that touches a very good point:

Duplication is far cheaper than the wrong abstraction

Perhaps developing an abstraction right now may cause you to abstract it in the wrong way.


Bottom Line

In the end, principles must be applied given the context of your application. It could indeed be the case where SRP would be the right guide for your situation. Or... SRP may cause you to over-engineer it for now and using the YAGNI would be better suited.

The main take out perhaps should be that yes, it is common for principles to contradict each other if followed religiously and this is not a problem. Everything is fine.

If anything, it is advisable to first implement a working solution (that is, choose one of the solutions and just go with it) and adapt to the implications that arise from it, building the software organically.

Single Responsibility Principle states that every class or module should have one reason to change. To restate it for clarity. A module should be responsible to one, and only one, actor.

I disagree with this statement, i.e. I don't think it's a good rule to follow. And I know Uncle Bob says something very similar or even the same, still.

Let's just think about this a little. Say I have a simple Amount class that has add() and subtract(). What if one "actor" only needs add() and another subtract(). Should I split this class? Would that make the code more maintainable? I don't think you would argue that it would.

Before you say it all depends and you should always exercise judgement, I don't think a rule is a good rule when it doesn't even apply in simple cases.

To your question: You're sort-of right, I would also try to separate the data collection from the actual workflow implementation if that is possible. For example if the Workflow is an interface and all data for the data collection is available through the parameters, then I would create an implementation that collects data and delegates the calls.

If however there are internal data to the workflow that needs collecting, that is not available through the public parameters, then I would have no problems with putting the data collection into the implementation itself, because in this case I am actually interested in the actual implementation's behavior.

So, you did not yet manage to convince me either :)

Licensed under: CC-BY-SA with attribution
scroll top