Pages

Showing posts with label dependency elimination principle. Show all posts
Showing posts with label dependency elimination principle. Show all posts

Tuesday, April 14, 2015

Dependency Elimination Example: Primitive Support

Last post, I showed a canonical example for the Dependency Elimination Principle. I want to show another Dependency Elimination example that solves another common pattern I see. I've given this pattern a name: Primitive Support.

Introducing: Primitive Support

Primitive Support is the natural outworking of the Code Smell called Primitive Obsession. Our code suffers from Primitive Support when it has these two parts:

  1. A primitive type (int, string, etc) representing a domain concept (megabytes, phone number, etc).
  2. A helper class that does something meaningful with that primitive (e.g. MegabyteConverter or PhoneNumberParser or FooHelper).

If we have classes named similar to MegabyteConverter or PhoneNumberParser or FooHelper, we might be suffering from Primitive Support. Now let's look at an example.

A Simple Example: E-mail Address Validation

Let's say we're working on a user signup experience. A valid e-mail address is required for new users. We need our API to enforce that the e-mail address is valid. One way we might do this is like the below1:

This is actually pretty good as is. It's relatively easy to test and easy to read. There is, however, a lack of cohesion. First, UserSignup is worrying about validating e-mails when it really should just sign up the user. Second, email validation is off on its own in a general EmailHelper class. We will improve this soon, but first we'll take a detour in the wrong direction.

A Slight Rant

Unfortunately, some of the SOLID and TDD literature will lead you away from this design towards a more complicated design. In the name of separating concerns (good), standard practice will tell you to extract e-mail validation to its own concern and pass it as an interface to UserSignup (less good). Similar to this:

Now we have an IEmailValidator and an EmailValidator to separate the validation concern. To test UserSignup, we'll mock IEmailValidator and make sure it's called with the given emailAddress. Standard unit testing practice.

Testable, But Complicated

Let's evaluate the code. It's testable. We've separated our concerns. But it's more complicated. And we still have a cohesion problem: Email address validation is still a concern of UserSignup, and the validation implementation lives in its own EmailValidator world. We can do better than this.

Address the Root Cause: Kill the Primitive

The fundamental problem is that our system cares about e-mail addresses, but it doesn't represent e-mail addresses as first class citizens. We've split the e-mail address concern across UserSignup and EmailValidator. To fix this cohesion problem, we need to stop using string for e-mail addresses and start using EmailAddress for e-mail addresses. Watch how the complexity melts away:

First, note two important pieces:

  1. EmailAddress is a new type that is now a Whole Value for e-mails.
  2. The EmailAddress constructor throws when it is invalid. This makes it impossible to create an invalid e-mail address.

Now, note the supreme wins we get from this approach:

  1. We've completely separated the concerns. UserSignup has no validation calls or implementations. It just uses an EmailAddress like it should.
  2. We've improved cohesion. E-maily things live on EmailAddress. UserSignupy things live on UserSignup. No overlap.
  3. EmailAddress validation is now a compile-time concern. You can't make a bad one, so type-checking will guarantee that the thing you pass in is valid2.

What We Learned

Code with Primitive Obsession will usually lead to Primitive Support: introducing dependencies to do meaningful things on primitives. We can eliminate these dependencies by replacing the primitive with a Whole Value for the concept it represents. The supporting dependencies go away, the code is more clear, and the compiler catches bugs for us. Wins all around!




Footnotes

1Note: you might do this more simply via your library/framework validation system, but I'm leaving those out of this conversation for educational purposes.

2I know, I know we can still technically pass null. But if null makes it this far into our system we have bigger problems.

Monday, February 9, 2015

The Dependency Elimination Principle: A Canonical Example

There's been quite a bit of discussion so far on the Dependency Elimination Principle. One thing obviously missing from my posts is a code example. This example illustrates a common class of unnecessary dependencies.

I'll use application configuration as the example. I'll uncreatively title the class of unnecessary dependencies: "The thing that gets the thing you need". And I'll uncreatively title the remedy to this problem: "Pass the thing you need, not the thing that gets the thing you need".

The config example


Most applications we write have a configuration defined via some XML or JSON file sitting near the executing code.  We want to keep the system modular and testable so we make sure the application code doesn't depend directly on this configuration file.  A common way we implement this looks similar to this:

Then when one of our application classes needs a value from the application's configuration, we give it a dependency on IConfigurationReader and let it read a config value in one of its methods. For this example, let's make a class that generates invoices. We have multiple environments and don't want to mistake test invoices for real ones. So we decide to put a watermark on invoices generated outside of production. One way to accomplish this would be to create a "invoiceWatermarkText" config setting that is set in test/staging and not set in production. Here's what this might look like:

This is SOLID, decoupled code. InvoiceGenerator rightly doesn't know about the details of how configuration works. And it's testable because you can stub out the configuration reader. However, there are still problems.

Loose coupling is still coupling


Although InvoiceGenerator doesn't know about how configuration works, it still has to know there is such a thing as configuration. This makes things more complicated. When you test, you have to provide a stub. And when you create an InvoiceGenerator in the production code, you have to provide a real configuration. InvoiceGenerator is still coupled to configuration, thus making it impossible to use it without supplying some sort of configuration reader.

Eliminate the coupling


We can improve this with a simple fix: Pass the thing we need (the watermark text), not the thing that gets the thing we need (the IConfigurationReader). It will look something like this:

Now we can painlessly use an InvoiceGenerator literally anywhere in any application. And there's less to think about when reasoning about it.

But...we still need the config part


You're probably wondering, "Ok, so I still need to read that text from config to give it to the InvoiceGenerator. Where does that code go?". You have a couple options here:
  • Just read it from config on the line that is calling GenerateInvoice
  • Create a one-line overload to GenerateInvoice that reads from the real configuration reader.  No need to test that line.

Wrapping it all up


We often introduce a dependency so we can get the thing we need.  However, we usually only need the thing we need.  So we pass the thing we need, not the thing that gets the thing we need.

Tuesday, September 23, 2014

Beyond SOLID: The Dependency Elimination Principle

Last post I explained why I don't teach the SOLID design principles.  Read the post for more detail, but the primary reason is that SOLID encourages heavy use of dependencies.  Applying SOLID to a codebase for even a short time will yield dependencies on abstractions everywhere -- quickly producing a codebase that is unintelligible.

What should we do about these dependencies?  We start applying the Dependency Elimination Principle.

Treat Dependencies As Code Smells


With the Dependency Elimination Principle, we change our default to treat dependencies as code smells.  This doesn't mean we won't have dependencies.  It just means our default is to treat them as code smells.  This new default forces us to examine why we need each dependency and how to eliminate each.

Are we getting something from the dependency?  If so, pass the thing we're getting, not the thing that gives it to us.

Are we sending something to the dependency?  If so, consider a model that uses events + event binding instead of passing in an interface.  Or use a DTO that represents the state of the dependency and respond to changes to it (e.g. MVVM).

Whole Value


Most codebases have way too many dependencies because they don't have their concepts defined as Whole Values.  Here are some simple pointers to create a clean, intelligible codebase that won't use a lot of dependencies on abstractions (lifted from J.B. Rainsberger's take on the 4 Rules of Simple Design):

Kill Primitive Obsession
Name things as Nouns (and not using verb nouns that have an ‘er’ ending)
Remove duplication

Applying these simple pointers will shock you in how quickly your code becomes simple, readable, testable, and extendable but without all of the baggage that comes with littering interfaces everywhere.

Using Testability as the Lens


Let's look at the Dependency Elimination Principle from another lens: testability.  Code with dependencies (even if those dependencies are mockable) is more difficult to test than code without them.  Here are the levels as I think of them from easiest to hardest (I think I got this from another blog post but can't for the life of me find it):

  • Level 0: A pure static function with no side effects
  • Level 1: A class that has immutable state.  Think a Whole Value that replaces a primitive, like EmailAddress or PhoneNumber.
  • Level 2: A class that has mutable state and may operate against behaviorless dependencies like Level 1 Whole Values.
  • Level 3: A class that operates against a dependency with its own behaviors


Level 0 is trivial to test.  Just pass in the different inputs and expect the right outputs.

Level 1 is not much different than level 0, except you have a few more methods to test and a few more configurations to test as well.  Level 1 is nice because you get to encapsulate a concept into a Whole Value.

Level 2 is more difficult than level 1 because you have to manage the internal state and test different cases when the state changes.  But sometimes you’ll want level 2 code because of the benefits it brings in encapsulating a concept into a Whole Value.

Level 3 is the most difficult to test.  You either use mocks or you test more than one thing at once.

I want to make testing as easy as possible, so I strive to choose the lowest level that meets my needs.  This means lots of Level 0 and Level 1 code.  Some Level 2 and rarely any Level 3.  My code becomes mostly functional but takes advantage of OO to create nice Whole Values that keep all related behavior in the same place.

Circling Back to SOLID


Assume we've applied the DEP to a codebase.  Let's analyze how SOLID that codebase is:

Single Responsibility Principle: Heck yes.  Whole Values everywhere.  Extremely high cohesion.
Open-Closed Principle:  Yes, but in a different way.  The openness is in the way we compose all the little Whole Values, not in plugging in new dependencies everywhere.
Liskov Substitution Principle:  Irrelevant.  We're not using inheritance much anymore.
Interface Segregation Principle: Again, irrelevant.  We aren’t using many interfaces
Dependency Inversion Principle: Mostly irrelevant because most dependencies are eliminated.  The dependencies that remain are just Whole Values which you treat as part of your type system and perhaps a tiny handful of interfaces to talk to the outside world.

It's all about the Single Reponsibility Principle


Applying the Dependency Elimination Principle leaves you laser-focusing on the Single Responsibility Principle.  And you get the flexibility of Open-Closed that leads to business agility.  And you lose all the legibility baggage that comes with Liskov, ISP, and DIP.  Wins all around.

For more reading on Dependency Elimination, check out Arlo Belshee's posts about No Mocks.  He's the one who introduced all of these ideas to me.

(UPDATE: It's possible the unknown source of the levels described above is from John Sonmez's blog here and in related posts)

(UPDATE 2: Check out an example that shows a common class of unnecessary dependencies here)

(UPDATE 3: Another example that shows how Primitive Obsession leads to unnecessary dependencies