12

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?

  • 10
    YAGNI conflicts with everything. SRP? You aren't gonna need it. MVVM? You aren't gonna need it. Unit Tests? You aren't gonna need it. Sensible software design? You aren't gonna need it (until you do). Improving performance has its own special brand of YAGNI called "Premature Optimization." – Robert Harvey Nov 30 '19 at 1:17
  • 7
    Does this mean YAGNI is useless? Far from it. YAGNI is a reminder not to build software features without existing requirements, in the hope that you might need them later. – Robert Harvey Nov 30 '19 at 1:20
  • Removing existing, working modularization is not "the simplest thing that could possibly work". Leaving things as they are is the simplest thing. What you're doing is not actually YAGNI. – ObscureOwl Nov 30 '19 at 16:17
32

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.

| improve this answer | |
  • 10
    This is a good answer, and it illustrates nicely why dogma in software development may be problematic. One doesn't need to stick to YAGNI or SRP or DI or whatever it be and run with it as a religion - they are tools, guidelines. True mastery of the craft comes when you stop applying them blindly and start using your experience to check when it is time to use one or another. – T. Sar Nov 29 '19 at 11:04
  • 4
    I like this answer. Both SRP and YAGNI are what Billy Vaughn Koen calls a heuristic: anything that provides a plausible aid or direction in the solution of a problem but is in the final analysis unjustified, incapable of justification, and fallible. They also meet all four criteria: Applying either or both don't guarantee that you'll reach a solution. They do reduce the time it takes to get to a solution by reducing the search space. They depend on the immediate context rather than absolute truth. And, most importantly, two heuristics may conflict and lead to different answers. – Thomas Owens Nov 29 '19 at 11:30
  • 1
    I would tolerate it if I had to make two or three small extensions - this is often called the rule of three, and it's a very practical approach to balancing YAGNI and SRP. – casablanca Nov 29 '19 at 18:53
  • 3
    As your answer implies, You need to understand why you’re doing what you’re doing!. The key problem with these high principles is their dissociation from the reasons we have them. You can't apply the SRP or YAGNI if you don't understand what you get out. And frankly, that often just comes down to experience and domain knowledge ... until you have the intuition to know the right path (or unless you have an "elder"), you learn how SRP/YAGNI/etc. apply to your domain by making a lot of mistakes. – svidgen Nov 29 '19 at 18:57
12

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.

| improve this answer | |
  • 5
    I don't think the right way of dealing with the mentioned principles is just sorting them in some more or less arbitrary way and then looking which of them "stands above the other". What helps is to gather some years of real-world software development experience in different contexts so one can judge when to prefer which one over the other (if they are really have opposing goals, which is often not the case). So I agree to your bottom line, but I would be careful with that pyramid. – Doc Brown Nov 29 '19 at 17:55
  • 1
    I agree with you. Sorting the principles is a north. Definitely to be taken with a grain of salt. It would be amazing if we had a fast way of getting real world experience. So, to compensate and to try to propagate knowledge, we write principles and practices to generalize situations. Specifically, that pyramid helped me prioritize working solutions before starting abstracting/cleaning code (aka overengineering), which IMO is a trend of people that get introduced to software engineering principles. Because of that, I felt it could be useful as a north for the author. – Albuquerque Nov 29 '19 at 18:16
  • Excellent talk from Sandi Metz. Thank you very much. – Eric Duminil Nov 30 '19 at 20:01
2

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 :)

| improve this answer | |
  • The single responsibility principle was propounded by Robert C. Martin. The principle is one of the least explained in the internet. I found a beautiful explanation here. The video could explain why add and subtract if needed will be by the same actor. There would be clarity if the we clearly define actor. – Nachiappan Kumarappan Nov 29 '19 at 12:48
  • @NachiappanKumarappan I obviously did not buy the linked video, but in the intro Mr. Martin says the "SRP is about users". Which makes even less sense to me than "actors". – Robert Bräutigam Nov 29 '19 at 13:43
  • Some background information: when thinking of different "actors" in the context of the SRP, Robert Martin was talking about different people (or different groups of people) in a larger organization with different, maybe opposing goals (like in this case "end users" and "product management", as described by the OP). – Doc Brown Nov 29 '19 at 13:45
  • Your definition of users and actors are different from his. His explanation is top notch. There used be this video available in safari online for free for 10 days. – Nachiappan Kumarappan Nov 29 '19 at 13:45
  • 2
    No, now you are running into a well-know fallacy called Denying the antecedent ;-) There are more valid reasons for putting things into different classes than just "different actors". And just because a certain part of a larger system has just one user, we don't start to build that whole system in one class. – Doc Brown Nov 29 '19 at 14:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.