Pages

Friday, August 22, 2014

Why I Don't Teach SOLID

If you talk about software design with anyone who cares about code quality, you won't spend much time before you hear SOLID.  SOLID is a mnemonic device that helps developers remember five important principles of object oriented design:

  • Single Responsibility Principle
  • Open-Closed Principle
  • Liskov Substitution Principle
  • Interface Segregation Principle
  • Dependency Inversion Principle
SOLID is helpful.  It came from really important people in our field.  It helps people reason about design.  It helps people make systems resilient to change. 

SOLID used to be a cornerstone of my design toolbox.  I used to strive to make my code SOLID.  I used to teach others to do the same.

Today, SOLID remains important, but I don't strive to make my code SOLID.  I rarely mention it when talking about design.  And I definitely don't teach it to developers eager to learn good design techniques.  It doesn't come with me in my toolbox.  It stays in a dusty box up in the attic.  I keep it because it is important, but I access it rarely.

SOLID causes problems.  Problems big enough that warrant stowing it away in the attic.  When I used to champion SOLID, those who resisted would point to these problems.  I would dismiss these people as naysayers, resistant to change, or disinterested in code quality.  But now I've learned that they were right all along. 

If I could use one word to represent these problems, it would be unintelligible.  Developers (myself included) applying SOLID frequently produce codebases that are unintelligible.  These SOLID codebases have low coupling.  And they are testable.  But they are unintelligible.  And often not as adaptable as the developers had hoped.

The main culprit is SOLID's focus on dependencies.  Open/Closed, Interface Segregation, and Dependency Inversion each encourage heavy use of dependencies defined as abstractions (i.e. a C#/Java interface or Abstract Class).  Open/Closed encourages abstractions for easy extensibility.  Interface Segregation encourages more client-defined abstractions.  Dependency Inversion says to depend on abstractions instead of concretions. 

These all lead developers to create interfaces everywhere.  They litter the codebase with interfaces like IFooer, IDoer, IMooer, and IPooer.  Navigating it becomes a nightmare.  You rarely know by inspection what piece of code is actually going to run.  But it's ok.  Because it's SOLID.  It's great design!

To help manage the madness, we then introduce an IoC container.  And a mocking framework for our tests.  If it was intelligible before, now it's really not intelligible.  You now literally can't find a call to 'new' anywhere in the code.  Good luck reasoning about any piece of code now.  But it's ok.  Because it's SOLID.  It's great design!

Is it great design if it's unintelligible?  Is it great design if a developer can't easily reason about it?  I don't think so.  SOLID is helpful and important.  But I don't think developers handle it well.  Which is why I don't teach SOLID anymore. 

Stay tuned for how to achieve what you're trying to gain with SOLID without the legibility baggage.

UPDATE: You can find the next part here.

13 comments:

  1. Thanks for the sharing.
    I ever worked on a similar project: this project has two simple functions: wrapper for Icewarp API to create/update email accounts.
    3k line of codes, near 10 layers of interface. Indeed, it is "SOLID" :)

    ReplyDelete
  2. Sorry, but I don't agree.

    http://yourcodesucksexception.blogspot.com/2015/01/reusable-software-just-do-write-generic.html

    Writing software is about choosing lesser evil. Sure you get a little clutter if you introduce abstracted DI, but you also get so much more.

    ReplyDelete
    Replies
    1. Can you expand on what you gain with the interface-heavy SOLID approach? It sounds like from your article that it's easy to replace implementations?

      Delete
  3. Even better answer:

    "As you will one day discover, none of the most known principles in software development can be 100% followed.

    Programming is often about making compromises - abstract pureness vs. code size vs. speed vs.efficiency.

    You just need to learn to find the right balance: not let your application fall into abyss of chaos but not tie yourself hands with multitude of abstraction layers."

    http://stackoverflow.com/questions/2997965/are-solid-principles-really-solid

    Every programmer should know design patterns and SOLID, but knowing when to use them is as much important as knowing when to NOT use them.

    ReplyDelete
  4. I've been talking about complexity for years and how application architecture very often ignores it in favor of SOLID and some of the more complex design patterns used in application architecture. SOLID-C might be better. Take all of the benefits of SOLID and subtract Complexity.

    ReplyDelete
    Replies
    1. Yup, it really is about reducing complexity. I've found that using dependencies heavily (especially when coupled with a DI framework) increases complexity. And the LID in SOLID encourage heavy use of dependencies.

      So SOLID-C for me becomes So -- SOLID with no LID and a modified definition of O :).

      Delete
  5. When it comes to SOLID, DRY, DDD or other design principles/ways of thinking, it's best to start with simplicity and logic and use these principles as salt to make the code taste a bit better :)

    ReplyDelete
  6. The issue with your post is it's utterly meaningless and adds nothing to the discussion. Generally speaking, make points wherein a counter argument wouldn't require a thesis level effort. I've seen hardcore abstraction in very large codebases and been a part of those teams. People are doing this for a reason, not because they read it in a book.

    Instead, I suggest you choose a specific pattern/workflow that particularly bothers you, and most importantly provide an alternative. Nothing is more amateurish than knocking something as broad as a methodology and not even hinting at an alternative. If I was forced to have a rebuttal to your post, it would be simply that, on large teams defensive programmng saves the company much more money than it costs in extra development time. I also think from reading your post that you don't, 'get it'. In situations where there is a large team practicing this, it's perfectly normal for only 1-2 developers to know a certain part of the code. You can not expect to look at a large code base and just 'reason' what it all does having not used the code. On the surface this appears bad, but there are many business factors that go into having these knowledge silos; accountability, team communication, and better training processes with faster ramp up. A lot more to be said, but once again you need to look at it through the eyes of a large business.

    ReplyDelete
  7. The issue with your post is it's utterly meaningless and adds nothing to the discussion. Generally speaking, make points wherein a counter argument wouldn't require a thesis level effort. I've seen hardcore abstraction in very large codebases and been a part of those teams. People are doing this for a reason, not because they read it in a book.

    Instead, I suggest you choose a specific pattern/workflow that particularly bothers you, and most importantly provide an alternative. Nothing is more amateurish than knocking something as broad as a methodology and not even hinting at an alternative. If I was forced to have a rebuttal to your post, it would be simply that, on large teams defensive programmng saves the company much more money than it costs in extra development time. I also think from reading your post that you don't, 'get it'. In situations where there is a large team practicing this, it's perfectly normal for only 1-2 developers to know a certain part of the code. You can not expect to look at a large code base and just 'reason' what it all does having not used the code. On the surface this appears bad, but there are many business factors that go into having these knowledge silos; accountability, team communication, and better training processes with faster ramp up. A lot more to be said, but once again you need to look at it through the eyes of a large business.

    ReplyDelete
  8. Previous commenter loves SOLID but violates DRY....

    ReplyDelete
  9. For me SOLID actually already fails at the S, it says that a class should only have a single responsibility. But how would you define a responsibility?
    For an error logger what would be an responsibility?
    writing something.
    opening a file.
    closing a file.

    or would it be even smaller. Then they say you need to use ID of solid and make everything more abstract.
    resulting in code like this.
    open something with an abstract opening class.
    write something with an abstract writer class.
    close something with an abstract closing class.

    and use factories to create these somethings.

    I actually really hate it when an interface is only used by a single class. For it seems best to use the concrete type until you really need extended runtime flexibility.

    It would be much easier to just put these things in functions wrap it into an object that holds the file or stream and be done with it. An initialization function would allow you to set the filename/stream. The rest would be done with logger.log(String msg).

    ReplyDelete
    Replies
    1. I guess i am a fan of YAGNI and DRY.

      Delete
  10. "Navigating it becomes a nightmare". If you apply Pure DI and create a clean Composition Root, then navigation becomes easy. See this article for more details: http://www.dotnetcurry.com/patterns-practices/1285/clean-composition-roots-dependency-injection

    ReplyDelete