SOLID vs. YAGNI [closed]
Asked Answered
I

10

68

One of the most frequent arguments I hear for not adhering to the SOLID principles in object-oriented design is YAGNI (although the arguer often doesn't call it that):

"It is OK that I put both feature X and feature Y into the same class. It is so simple why bother adding a new class (i.e. complexity)."

"Yes, I can put all my business logic directly into the GUI code it is much easier and quicker. This will always be the only GUI and it is highly unlikely that significant new requirements will ever come in."

"If in the unlikely case of new requirements my code gets too cluttered I still can refactor for the new requirement. So your 'What if you later need to…' argument doesn't count."

What would be your most convincing arguments against such practice? How can I really show that this is an expensive practice, especially to somebody that doesn't have too much experience in software development.

Interface answered 13/10, 2010 at 8:27 Comment(7)
"the unlikely case of new requirements": nice one. May I quote that? ;-)Bleach
I'm not really sure how to show someone how wrong-headed those propositions are, but it's worth saying that none of them are consistent with the spirit of YAGNI, which is about features, not design principles.Markup
@Jeff: If you drop down a level of abstraction, classes and libraries have features, which are affected by your process in designing them.Zaporozhye
@Roger - certainly, but only if you're delivering a library (as opposed to an application).Markup
@Jeff: If you're working within a team, you're "delivering" individual components to the rest of the team.Zaporozhye
@Jeff: I realize that's what you're saying, but I'm disputing it: the other developers are customers of your code. YAGNI is also important when considering whether to add features to an API even when it's completely internal. For example: "Don’t write the setter because 'we're going to need it.'" It's just terminology; we can agree to disagree. :)Zaporozhye
@Roger - fair enough. I'm going to back up to your first response, and say 'sure.'Markup
Z
53

Design is the management and balance of trade-offs. YAGNI and SOLID aren't conflicting: the former says when to add features, the latter says how, but they both guide the design process. My responses, below, to each of your specific quotes use principles from both YAGNI and SOLID.

  1. It is three times as difficult to build reusable components as single use components.
  2. A reusable component should be tried out in three different applications before it will be sufficiently general to accept into a reuse library.

  — Robert Glass' Rules of Three, Facts and Fallacies of Software Engineering

Refactoring into reusable components has the key element of first finding the same purpose in multiple places, and then moving it. In this context, YAGNI applies by inlining that purpose where needed, without worrying about possible duplication, instead of adding generic or reusable features (classes and functions).

The best way, in the initial design, to show when YAGNI doesn't apply is to identify concrete requirements. In other words, do some refactoring before writing code to show that duplication is not merely possible, but already exists: this justifies the extra effort.


Yes, I can put all my business logic directly into the GUI code it is much easier and quicker. This will always be the only GUI and it is highly unlikely that signifcant new requirements will ever come in.

Is it really the only user interface? Is there a background batch mode planned? Will there ever be a web interface?

What is your testing plan, and will you be testing back-end functionality without a GUI? What will make the GUI easy for you to test, since you usually don't want to be testing outside code (such as platform-generic GUI controls) and instead concentrate on your project.

It is OK that I put both feature X and feature Y into the same class. It is so simple why bother adding a new class (i.e. complexity).

Can you point out a common mistake that needs to be avoided? Some things are simple enough, such as squaring a number (x * x vs squared(x)) for an overly-simple example, but if you can point out a concrete mistake someone made—especially in your project or by those on your team—you can show how a common class or function will avoid that in the future.

If, in the unlikely case of new requirements, my code gets too cluttered I still can refactor for the new requirement. So your "What if you later need to..." argument doesn't count.

The problem here is the assumption of "unlikely". Do you agree it's unlikely? If so, you're in agreement with this person. If not, your idea of the design doesn't agree with this person's—resolving that discrepancy will solve the problem, or at least show you where to go next. :)

Zaporozhye answered 13/10, 2010 at 16:46 Comment(0)
M
11

I like to think about YAGNI in terms of "half, not half-assed", to borrow the phrase from 37signals (https://gettingreal.37signals.com/ch05_Half_Not_Half_Assed.php). It's about limiting your scope so you can focus on doing the most important things well. It's not an excuse to get sloppy.

Business logic in the GUI feels half-assed to me. Unless your system is trivial, I'd be surprised if your business logic and GUI haven't already changed independently, several times over. So you should follow the SRP ("S" in SOLID) and refactor - YAGNI doesn't apply, because you already need it.

The argument about YAGNI and unnecessary complexity absolutely applies if you're doing extra work today to accommodate hypothetical future requirements. When those "what if later we need to..." scenarios fail to materialize, you're stuck with higher maintenance costs from the abstractions that now get in the way of the changes you actually have. In this case, we're talking about simplifying the design by limiting scope -- doing half, rather than being half-assed.

Matter answered 15/9, 2014 at 1:58 Comment(0)
D
8

It sounds like you're arguing with a brick wall. I'm a big fan of YAGNI, but at the same time, I also expect that my code will always be used in at least two places: the application, and the tests. That's why things like business logic in UI code don't work; you can't test business logic separate of UI code in that circumstance.

However, from the responses you're describing, it sounds like the person is simply uninterested in doing better work. At that point, no principle is going to help them; they only want to do the minimum possible. I'd go so far as to say that it's not YAGNI driving their actions, but rather laziness, and you alone aren't going to beat laziness (almost nothing can, except a threatening manager or the loss of a job).

Disaffiliate answered 13/10, 2010 at 12:45 Comment(2)
I have the feeling that those persons want to get the features/requirements implemented fast test the main use cases and then forget about it. Then fix the issues when the code is deployed. The customer is a bit the acceptance tester.Interface
@bitbonk: Yes, a very common scenario. It can be a legitimate way to work, provided you inform the customers that they will be the beta testers (and they agree). However, not everyone does that :-).Bleach
E
3

There is no answer, or rather, there is an answer neither you nor your interlocutor might like: both YAGNI and SOLID can be wrong approaches.

Attempting to go for SOLID with an inexperienced team, or a team with tight delivery objectives pretty much guarantees you will end up with an expensive, over-engineered bunch of code... that will NOT be SOLID, just over-engineered (aka welcome to the real-world).

Attempting to go YAGNI for a long term project and hope you can refactor later only works to an extent (aka welcome to the real-world). YAGNI excels at proof-of-concepts and demonstrators, getting the market/contract and then be able to invest into something more SOLID.

You need both, at different points in time.

Enrika answered 30/3, 2017 at 14:19 Comment(0)
H
2

The correct application of these principles is often not very obvious and depends very much on experience. Which is hard to obtain if you didn't do it yourself. Every programmer should have had experiences of the consequences of doing it wrong, but of course it always should be "not my" project.

Explain to them what the problem is, if they don't listen and you're not in a position to make them listen, let them do the mistakes. If you're too often the one having to fix the problem, you should polish your resume.

Hovis answered 13/10, 2010 at 8:51 Comment(2)
"polish your resume"? You mean, get a better job?Interface
yes, when the hassle is too much it is best to move on. The are after all companies who value this type of thinkingHovis
B
2

In my experience, it's always a judgment call. Yes, you should not worry about every little detail of your implementation, and sometimes sticking a method into an existing class is an acceptable, though ugly solution.

It's true that you can refactor later. The important point is to actually do the refactoring. So I believe the real problem is not the occasional design compromise, but putting off refactoring once it becomes clear there's a problem. Actually going through with it is the hard part (just like with many things in life... ).

As to your individual points:

It is OK that I put both feature X and feature Y into the same class. It is so simple why bother adding a new class (i.e. complexity).

I would point out that having everything in one class is more complex (because the relationship between the methods is more intimate, and harder to understand). Having many small classes is not complex. If you feel the list is getting to long, just organize them into packages, and you'll be fine :-). Personally, I have found that just splitting a class into two or three classes can help a lot with readability, without any further change.

Don't be afraid of small classes, they don't bite ;-).

Yes, I can put all my business logic directly into the GUI code it is much easier and quicker. This will always be the only GUI and it is highly unlikely that signifcant new requirements will ever come in.

If someone can say "it is highly unlikely that signifcant new requirements will ever come in." with a straight face, I believe that person really, really needs a reality check. Be blunt, but gentle...

If in the unlikely case of new requirements my code gets too cluttered I still can refactor for the new requirement. So your 'What if you later need to ...' argument doesn't count

That has some merit, but only if they actually do refactor later. So accept it, and hold them to their promise :-).

Bleach answered 13/10, 2010 at 10:22 Comment(3)
Have the right number of classes of the right size. Make them too small and you end up putting the complexity into the interconnect between the classes, and that doesn't help.Iridium
@Donal Fellows: Yes, that is true. My rule of thumb is to put at least one bit of non-trivial functionality into each class - that works reasonably well. But to be honest, I have never seen a real-world system with too many small classes, but many with large classes, so most people seem to gravitate to the "big class" side anyway.Bleach
I've seen both (some well known frameworks are really bad that way). I just try to remember that a problem has a certain natural complexity, and I try to make sure that the solution has that level of complexity made manifest and as little extra as practical.Iridium
T
2

SOLID principles allow software to adapt to change - in both requirements and techical changes (new components, etc), two of your arguments are for unchanging requirements:

  • "it is highly unlikely that signifcant new requirements will ever come in."
  • "If in the unlikely case of new requirements"

Could this really be true?

There is no substitute for experience when it comes to the various expenses of development. For many practitioners I think doing things in the lousy, difficult to maintain way has never resulted in problems for them (hey! job security). Over the long term of a product I think these expenses become clear, but doing something about them ahead of time is someone else's job.

There are some other great answers here.

Tiruchirapalli answered 13/10, 2010 at 19:8 Comment(1)
Very true about the job security. I've seen this so many times and it is sickening.Stroy
Z
2

Understandable, flexible and capable of fixes and improvements are always things that you are going to need. Indeed, YAGNI assumes that you can come back and add new features when they prove necessary with relative ease, because nobody is going to do something crazy like bunging irrelevant functionality in a class (YAGNI in that class!) or pushing business logic to UI logic.

There can be times when what seems crazy now was reasonable in the past - sometimes the boundary lines of UI vs business or between different sets of responsibilities that should be in a different class aren't that clear, or even move. There can be times when 3hours of work is absolutely necessary in 2hours time. There are times when people just don't make the right call. For those reasons occasional breaks in this regard will happen, but they are going to get in the way of using the YAGNI principle, not be a cause of it.

Zenithal answered 14/10, 2010 at 9:21 Comment(0)
O
2

Quality unit tests, and I mean unit tests not integration tests, need code that adheres to SOLID. Not necessarily 100%, in fact rarely so, but in your example stuffing two features into one class will make unit testing harder, breaks the single responsibility principle, and makes code maintenance by team newbies much harder (as it is much harder to comprehend).

With the unit tests (assuming good code coverage) you'll be able to refactor feature 1 safe and secure you won't break feature 2, but without unit tests and with the features in same class (simply to be lazy in your example) refactoring is risky at best, disastrous at best.

Bottom line: follow the KIS principle (keep it simple), or for the intellectual the KISS principle (kis stupid). Take each case on merit, there's no global answer but always consider if other coders need to read / maintain the code in the future and the benefit of unit tests in each scenario.

Olympias answered 29/7, 2016 at 22:59 Comment(1)
Actually stuffing multiple features into one class does not necessarily make things simpler when the features are inter-related, keeping inter-related features separate to satisfy SOLID can actually be quite complex not just technically but intellectually as well (iow keeping things simple can be hard)Enrika
C
1

tldr;

SOLID assumes, you understand (somewhat atleast), the future changes to the code, wrt SRP. I will say that is being optimistic about capability to predict. YAGNI on the other hand, assumes most of the times you don't know future direction of change, which is pessimistic about capability to predict.

Hence it follows that SOLID/SRP asks you to form classes for the code such that it will have single reason for change. E.g. a small GUI change or ServiceCall change.

YAGNI says (if you want to force apply it in this scenario), since you don't know WHAT is going to change, and if a GUI change will cause a GUI+ServiceCall change (similarly A backend change causing GUI+SeviceCall change), just put all that code in single class.

Long answer :

Read the book 'Agile Software Development, Principles, Patterns, and Practices'

I am putting short excerpt from it about SOLID/SRP : "If,[...]the application is not changing in ways that cause the two responsibilities to change at different times, there is no need to separate them. Indeed, separating them would smell of needless complexity.

There is a corrolary here. An axis of change is an axis of change only if the changes occur. It is not wise to apply SRP—or any other principle, for that matter—if there is no symptom."

Conchaconchie answered 4/9, 2014 at 6:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.