Live data from Hacker News

I don't love the single responsibility principle

sklivvz.com

1–10 of 125 posts

Re: I don't love the single responsibility principle

#2
It seems like the best way to design a codebase is to redesign (rewrite) it several times and then go with the most succinct design. For any given problem, it's unlikely you'll design the codebase properly on the first try. Also:

when one writes code, there are only real, present requirements. The future is pretty irrelevant

I'd disagree with this. The future of your codebase matters unless you're writing a throwaway prototype. And when you rewrite a codebase N times, you'll discover that there are ways of structuring it so that future tasks will become far easier. E.g. for Lisp you'd refactor common patterns into utility functions, and for C you'd carefully craft the interfaces between modules so that they're unlikely to be used improperly / unlikely to be surprising.

Re: I don't love the single responsibility principle

#3
This article speaks about one of the many, many reasons I don't like Robert Martin's approach to programming. If some principle results in a handful of single-method classes that don't really do anything on their own, the principle is not a good basis for design.

I find the author's alternative much more useful in practice.

Re: I don't love the single responsibility principle

#4
Typically I write something 3 times. The first is a proof of concept, I don't care much about how it works as long as it works. The second time I put a lot more care into the 'how' but I usually still get a few things wrong enough that they are out of place and feel awkward to maintain or extend. The third time I have a really good handle on the problem, where the tricky parts are and how to tackle them. That third version will then live for many years, sometimes decades. Some of stuff I wrote like that in the 80's is still alive and well today (or some descendant thereof).

I try very hard not to get attached to code too much, refactor agressively and will throw it out when I feel it needs redoing. For larger codebases I tend to rip out a chunk between a pair of interfaces and tackle that section indpendently. I'll change either the code or the interfaces but never both in the same sitting.

Re: I don't love the single responsibility principle

#5
I've found that if you approach OOP from a more pragmatic point of view, metrics like the sizes of classes mean very little: the point of classes is to encapsulate code+data so they can be reused to avoid duplication, so it feels obvious to me that if you see (non-trivial) functionality in a class that is likely to be reused in the future, then extract it into another one, but if you don't then there's no point in doing so, because it will only increase the overall complexity. Same for OOP in general - it's a tool, use it when it makes sense and simplifies the design. Sometimes you don't need a class, and sometimes a function doesn't belong in one because it does things across several, so all the "which class should this function go in" questions have a straightforward answer to me: if it's not immediately obvious which class it goes in, it probably doesn't belong in one. I mainly use Asm/C/C++ so I have the luxury of doing this, but I can see how some of the "more-constraining" languages make this more difficult.

All the examples I see are one-way towards simply creating a million single method classes.

That's a phenomenon that I've definitely seen a lot; often with the accompanying obfuscation that the method bodies have only one or two statements in them, that just calls into some other methods. It may look like it's made the code simpler locally, but all it's done is spread the complexity out over a wider area and increased it. This isn't "well-designed" or "straightforward", it's almost intentional obfuscation. I've seen this effect most with programmers who were taught OO design principles very very early, possibly before they even had a good grasp of concepts like procedures, loops, and conditionals. "When all you have are classes, everything turns into an object."

Re: I don't love the single responsibility principle

#6
Most of those principles can be bent to mean anything out of context. This is why citing principles alone can never be an argument for or against a certain design decision for a specific project situation when it comes down to it.

When you can't stop citing Uncle Bob slogans or Martin Fowler anti-pattern definitions, you're exercising Mantra-Driven Design. You've given up thinking, and you're making project decisions for religious reasons, citing what various deities said, and interpreting it like the Devil interprets the Bible.

The solution to OP-s problems:

1. Define a clear, minimal interface between "Stan's" classes and your own classes.

2. Communicate via that interface (when I say "interface", it doesn't have to be one interface type, BTW), and only via that interface.

3. Let everyone code their classes as they wish, and let the results speak:

- If either developer produces significantly more defects and issues over time than the other, have the decency and courage to learn from your team-mate. Because the interface is defined (see points 1, 2) it means you can refactor all code that isn't part of the interface. You can add classes, remove classes and you can assign from zero to infinite responsibilities to your classes, do whatever you want.

- If not, then this is one more instance of a valuable lesson: there's more than one wrong way to do things, but there's also more than one right way to do things. Maybe both developers have a good approach, they're arguing over B.S.

Re: I don't love the single responsibility principle

#7
In the absence of finding the time to really tear it apart, it seems that there are a few false choices in the article. I don't think a class that fulfills SRP necessarily means a tiny single-method class. It could mean a collection of methods that each do one conceptual thing, at one abstract level of understanding, but where the class and collection of methods is still cohesive.

The example of the client needing to instantiate B to pass it to A's constructor seems like a poor example of tight coupling. The only reason to instantiate B is because A needs it. The client doesn't literally need to know about B, it simply needs to know how to construct/build A. This could be done through a factory or service locator, or it could be done by A having B autowired into it, which would free the client from having to instantiate B directly.

I do, however, agree that SRP is poorly defined in most of the resources you find, so if anyone has links of case studies of applying it properly, they'd be interesting to review.

Re: I don't love the single responsibility principle

#8
>> The future is pretty irrelevant, so asking to design based on future requirements is uncanny.

The future is irrelevant? That's exactly what design is for -- to protect you from the future. If you didn't care about the future there would be no need to design at all.

A good way to evaluate various candidate designs is to imagine future use cases and ask which design holds up better. A good design will respond really well (ie., require fewer modifications) to novel use cases. Many people think this means design starting with future use cases. But you really just need to design with good principles. Future use cases are useful however at objectively evaluating designs and resolving arguments, such as Which design is better for futures that we care about?

>> Stating that "binding business rules to persistence is asking for trouble" is flatly wrong. Au contraire, It's the simplest thing to do, and in most cases any other solution is just adding complexity without justification.

Everything is a tradeoff. It doesn't make sense for many cases (hacking, etc) to avoid the simplest thing. But if you are building a project that's longed-lived, I can give you countless examples of how coupling business rules to model objects resulted in pain and copious paper cuts.

For me, I'll always decouple them. Why? Because it costs me nothing extra to do it and I have internalized the value in this principle from experience.

>>> Not all applications are big enterprise-y behemoths that benefit from Perfect 100% Decoupling™

You're right. It depends on how much you care about your future. Do you want your codebase to respond with agility to the future. Or are you okay with increasing your technical debt?

>>> Therefore, classes should be: >> small enough to lower coupling, but >> large enough to maximize cohesion.

Not sure I understand. Author seems to suggest these two are in contention with each other.

>>> Coding is hard and principles should not take the place of thinking.

Absolutely. There are many cases where the rules are to be broken. Systems with too much flexibility are just as bad as monolithic ones. It's an art what we do.

Re: I don't love the single responsibility principle

#9
I think you are focusing on the wrong phrase in the definition of SRP. You are focusing on reason for change without the context of responsibility. The way I read that definition is: if a class has more than 1 responsibility then making a change affecting any one of those responsibilities will require the class to be recompiled where as if those responsibilities were broken up in different classes making a change affecting one responsibility would not affect the other classes.

Hence a bug fix does not constitute a "change" since it does not change the responsibility of the class, neither does a refactor for code clarity or performance improvements. The purpose/responsibility of "what" the class does, does not change when you do any of those things.

"sometimes a class with more reasons to change is the simplest thing" Could you give an example of this? I can't think of one instance.

"Stating that "binding business rules to persistence is asking for trouble" is flatly wrong. Au contraire, It's the simplest thing to do, and in most cases any other solution is just adding complexity without justification" Now this statement here is flatly wrong precisely for the reason specified in the Uncle Bob's example. Let's do a thought experiment. Consider that you have an Employee class that is a black box to you. Someone else wrote that code and didn't have any exception handling logic in their code. The Employee class did business logic and persistence control. When you tried to use the class there was an error. Now which part of the class was faulty? The business logic part or the persistence control part? Now consider that you needed to use the same Employee class in another project but it needed a different set of business rules but the same persistence control, you would not be able to use this Employee class you would have to create a new one and duplicate your persistence control code.

Design principles have been developed to make sure that code written is maintainable, extensible, clear, comprehensible. Sure me as a single developer who is working on a simple project, who knows what each of my class does and has a small enough code base such that I don't need to separate the application into layers (e.g. call my database directly from my view because I just need this one value and don't want to write a couple of classes to that) can indeed write classes that have more than 1 responsibility. There's nothing to stop you but it does violate the SRP which will make it tougher for someone else to come in and maintain your code or extend it.

Post reply on HN