SOLID Code for SOLID Reasons

We should write good code because good code is easy to maintain, not because it makes the code easier to unit test. However, it just so happens that well written code is easy to unit test; and testing our code, especially test-driving our code, helps us to write good code.  But ease of unit-testing is not the only reason for writing good code, in fact it is one of the very last reasons.

So how do we define good code? I think a great starting point, at least for OO code, are the SOLID principles of object oriented design defined by Bob Martin and the concepts of bounded context and anti-corruption layers defined by Eric Evans.

Wikipedia has a great overview of SOLID, or if you prefer, here it is from the horses mouth. SOLID is an acronym for the following 5 principles: Single Responsibility Principle, Open/Closed Principle, Liskov Substitution Principle, Interface Segregation Principle, and Dependency Inversion Principle.  If you are not familiar with SOLID, I’d recommend you read that now.

Eric Evans recommends in his book, Domain Driven Design, that we define the various Bounded Contexts that exist in our code.  Typically when we work on a project, we have a code base that we work on most often and then there are the external systems with which our code communicates.  That is an example of bounded context — with our traditional code base being one bounded context and each external system  being considered a separate bounded context.  Evans suggests that when we identify a context boundary we should use anti-corruption layers to prevent the contexts from leaking through to one another.  An anti-corruption layer often takes the form of an adapter that translates and isolates the external context from our code.  This has two very valuable advantages; first, it prevents our code from having to speak the language of the other context (except within the adapter itself); and second, it isolates our code from changes to the other context.  If the other context makes dramatic changes to it’s interface, we have no need to panic because that code is not sprinkled throughout our bounded context, it is isolated to the anti-corruption layer.

With that understanding, I would like to address some questions that come up frequently when doing TDD (test-driven development):

Why do we create small classes and break up complex tasks among multiple objects?  It is NOT because it makes it easier to write isolated unit tests and it is NOT because it solves the “how do I test private methods?” question. We do it because classes who do too much work cause spaghetti-like coupling, and maintaining the large class and all the classes that interact with it, becomes tiresome and error-prone.  (Single Responsibility, Interface Segregation and Open/Closed Principles)

Why do we inject dependencies and depend on abstractions (interfaces), rather than concretions (classes)?  It is NOT because it makes our code easier to unit test even though it is nearly impossible to unit test code that depends on concretions.  We depend on abstractions because it makes our code much more loosely coupled and therefore much easier to change without negative consequences. (Dependency Inversion Principle)

Why do we wrap third-party dependencies in adapters?  It is NOT because it’s hard to isolate the third-party dependencies from our tests.  We do it because third party dependencies, especially those over which we have little or no control, are subject to change and we want to isolate our code from that change.  We also do it because sometimes those third parties do not believe in the same principles, such as SOLID, that we do, and creating an adapter allows us to adhere to the principles we hold dear. (Anti-Corruption Layer)

When we see code that does not adhere to our principles, we need to stop arguing that the reason to change them is for the sake of unit testing — because, frankly, that’s a tough argument to defend.  We first need to understand the reasons why we believe what we do, and “because it makes it unit testable” shouldn’t be a primary reason.  Furthermore, when we believe in a core principle like those above, it should guide everything we do and arguments like “It’s too hard in this case” or “It doesn’t really apply here because I have a simpler way to do it even though it violates my principles” should rarely if ever be valid arguments.  When we find ourselves making arguments like those, and we really think about the reasons why we believe in these principles, we will almost always find that the principle still applies even in complex (or very simple) cases.

The good news is that TDD, done right, helps us to realize when we are violating our own principles.  In my experience when I’ve run into a hard-to-unit-test piece of code, it has always been because of a violation of one of the above principles.  The failure, then, is not a failure of our testing tools, it is our failure to adhere to these good coding principles.

And this is why I so dislike moles as test doubles; and it is my biggest beef with the VS11 testing framework which includes moles (they’re called shims in the VS11 framework).  When you use moles (or shims) in your unit-testing framework, it keeps you from recognizing that you have poorly-written code that does not adhere to principles like those above.

A classic example of how the VS11 testing framework is going to be widely misused can be found in Rich Czyzewski’s blog post, Noninvasive Unit Testing in ASP.NET MVC4 – A Microsoft Fakes Deep Dive.  I understand the temptation to want to solve problems like those Rich discusses using moles and I think his post was very clear and well thought out.  Using moles to test (or test-drive) poorly designed code can be easier (up front) than writing the code correctly, especially when working with legacy code.  But, I fundamentally disagree with his assertion that KISS (keep it simple, stupid) and YAGNI (you ain’t gonna need it) should be used to dismiss such fundamental principles as SOLID and context boundaries.  Even Peter Provost, a Visual Studio program manager lead at Microsoft, disagrees with using shims in this manner as he lays out in his post on shims.

Our industry is young and evolving.  We need to be sure, as we evolve, that we are basing our evolution on sound principles, not just on what makes our daily jobs less frustrating up-front, especially, when it causes long-term maintainability consequences.  What do you think?

This entry was posted in Miscellaneous and tagged , , by Jim Cooper. Bookmark the permalink.

About Jim Cooper

@jimthecoop I have been a software engineer for almost 20 years and have been doing agile software development (mostly XP) for about 5 years. I love TDD and pairing and many of the concepts of domain driven design. and I love simple code (http://codesimply.blogspot.com), which is surprisingly difficult to write. I am now a software engineer for Pluralsight and absolutely love my job! Pluralsight is an awesome place for a software developer.

14 thoughts on “SOLID Code for SOLID Reasons

    • It might also be worth mentioning that I took the Pluralsight SOLID course, and it was great! Thanks, Steve.

    • Dmitry, an adapter is a very simple “wrapper” class that you write to wrap around another class that is either created by a 3rd party or is in another bounded context (perhaps a separate library that you wrote). It will either just do a straight pass-through (i.e. you call your adapter and it just calls the other class) or, more typically, it will do some type of translation from your code’s domain objects to the language of the other class and then make the pass through call. This allows your code to speak the language you want to speak and keeps the language and objects of that 3rd party component from leaking into your domain.

  1. Pingback: http://blog.pluralsight.com/2012/05/22/solid-code-for-solid-reasons/ « Carlos' Blog

  2. I often find myself defending design decisions that closely follow the SOLID principles and design principles outlined by Eric Evans in DDD. You’re correct that justifying the architecture for the sake of testing is a huge cop out and should be the _last_ point made. I sometimes find it tough to justify the architecture on new projects because there’s not enough meat in terms of 3rd party integration points, bloated business logic, and complex persistence layers, but I know through experience that making use of these architectural strategies is key to creating a maintainable solution. It also leaves the door wide open when it comes time to scale, which can be treated as just another requirement change if your boundaries are defined appropriately.

    Moles also rubs me the wrong way for the same ways you’ve outlined in your article. Introducing testing to a legacy app seems like the best use of the technology.

    For those new to the SOLID principles I recently came across a good post that includes some code examples of violations and solutions to each one.

    http://blog.gauffin.org/2012/05/solid-principles-with-real-world-examples/

    Jim, thanks again for the reminder of why everyone should truly use these principles beyond the scope of just testing.

  3. “Our industry is young and evolving”? Programming in high level languages has been around for at least 50 years (Algol 60 anyone?). Can you imagine this world if hardware engineering had made as little progress as software over the past 50 years? In comparison we’ve got nowhere. I’m not sure why this is, but I think part of it may be generational loss of best principles and practices and failure to preserve them as a foundation to build on. That would make an interesting study.

    • Maybe if hardware hadn’t evolved software would have had to evolve a little quicker. I’m sure I take lots of shortcuts I wouldn’t be able to if hardware wasn’t as advanced as it is. Regarding our young industry, I guess I’m comparing our industry to others–like medicine and construction. People have been trying to heal people and build houses for centuries. I feel like Waterfall was our industry’s version of blood-letting. And I wonder what we’re doing wrong today that we’ll shake our heads about in 20 years.

  4. “Why do we wrap third-party dependencies in adapters?”

    Yes, what you said /and/ Dependency Inversion. I read a great article on OOD some time back (don’t remember where, sorry) and one of the points was that when designing an interface you should design it how YOU want it to be. That is, when wrapping a 3-rd party thing, give it the interface you want. Make it look like you want it to look. That’s both antii-corruption, as you said, but it is also, at least to me, a fundamental part of the DIP. While I had not articulated the idea so deeply up to that point, I had practiced it.

    for example, consider message oriented middleware (MSMQ, Tibco, etc.). If you are given an architectural constraint to use these tools, which is common in an enterprise environment, often your use is strictly synchronous. That is, it is for integration, not for guaranteed delivery, asynchronous processing, etc. In that case, your adapter should not expose an asynchronous API. It should look how you want to consume it.

    I just had a discussion a buddy of mine last night as we came up several names: me-centered design, Narcissistic API, but whatever, we should make things look like we want/need/suggested by our problem pace.

  5. Pingback: Our HTML 5 player just got more flexible | the pluralsight blog

  6. Pingback: Understanding JavaScript Objects | the pluralsight blog

  7. Pingback: Understanding Javascript Prototypes | the pluralsight blog

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s