Hidden dependencies can give you major headaches when coding. Generally they make your code
- tough to test
- brittle
- fragile
Let’s take a look at an innocuous example:
This method has a hidden dependency on the UserNotifier class. Anyone looking at the rdoc for place_order would have no idea that it uses UserNotifier under the hood.
How would we write a test for that method? UserNotifier sends an email, so perhaps we could verify that the email was received. Well, that sucks. It would make our test run slowly, and it might fail even though our logic is correct.
What we’d like to do is use a fake implementation to sense the call. Fortunately, Ruby’s power makes this trivial:
We can dig into the method and write expectations on classes that it uses. In languages like Java, you can’t do that without some funky bytecode manipulation.
Just because we can do that doesn’t mean we should though. I used “dig into the method” for a reason – it should feel somewhat dirty. That simple test trick hides an opportunity to improve our design.
What if we’d like to change this method to send a message to the site admin as well? We could do something like this:
Our code is clearly brittle. In order to change its behavior, we have to change the implementation.
Remember though, an object’s behavior is a combination of its own logic and its interaction with other objects. The easiest way to change an object’s behavior is to have it interact with different objects.
What if we were to pass a notifier into the method?
Now we’ve made the dependency explicit, which gives us several benefits. It certainly makes it easy to test:
The test is less brittle because it doesn’t rely on place_order making calls to UserNotifier anymore. The method itself is a lot more flexible because in order to change its behavior, all you have to do is pass in an object that responds to order_completed. It would be trivial to build up a notifier that delivers emails to the consumer and the admin, writes to the log file, and contacts the shipping department telling them to reserve some space on the next truck out.
There’s a downside to this approach though. The dependency still exists, we’ve just moved it to the client code. Instead of
we now have to do
(assume that I renamed deliver_receipt to order_completed).
What if most of the time we want to use UserNotifier, and only occasionally want some other behavior? Making the dependency explicit made place_order more flexible, but its clients now shoulder the burden of selecting a notifier to use.
Ruby has very powerful default parameters that allow you to achieve exactly this behavior. We can rewrite place_order as
Clients are back to simply
if they want, which will use the UserNotifier by default. We’ve added a great deal of flexibility, while maintaining simplicity for basic use cases.
Note that place_order doesn’t depend on UserNotifier anymore. The UserNotifier object doesn’t even have to exist to be able to use this method. Here’s an example that you can copy and paste into irb:
It’s reasonable to think that running it might result in NameError: uninitialized constant Bar, but it turns out that Ruby never references Bar if you pass foo an argument.
That example also illustrates that default parameter values are nothing but simple objects. These are all valid default parameter lists:
Pitfalls
Default parameters are a very nice feature, but I think there are some pitfalls when using them. It makes it possible to do stuff like
Sometimes it can be tough to disambiguate between good and bad design, but I’m confident that recognizing evil is something that comes from within our hearts. Stuff like that is evil. Don’t do it.
Making dependencies explicit is almost always a good thing. Making them translucent, though, might muddle our intent a bit. When you look at a method definition and see
Are you supposed to pass in a notifier of your own? Are you supposed to use the default? With explicit dependencies you know you have to pass one in, and with hidden dependencies you know there’s nothing you can do. But what are you supposed to do in this case?
That is a valid concern. I think that the power and flexibility we gain by using this technique outweighs those times that the intent isn’t 100% clear. Personally, I look at it as writing well-designed code with some added conveniece. If we had method overloading, I would probably have written
Design Principles at Work
There are a couple of well-known design principles at work here. I decided to defer discussion until the end so that it didn’t look too formal. However it’s important to be aware of them when designing objects.
Single Responsibility Principle
The SRP states that an object should only have one reason to change. Examining the original place_order method, we find that it has two responsibilities – the logic of placing an order, and the details of how to send a notification.
That distinction can sometimes be subtle. For example, changing the method to handle credit card authorization would be okay:
This is because we’re changing the basic logic of what it means to process an order. The change we made at the beginning – adding the AdminNotifier – was a change in how notification took place.
Open-Closed Principle
The OCP states that modules should be open for extension, but closed for modification.
In our case, it is desirable to change the method’s behavior without changing its implementation. The original design was not only brittle, but fragile as well. If we wanted to change it, there’s no telling how it might affect client code. By passing in the dependency, it is possible to extend the method’s behavior without modifying it directly.
Dependency Inversion Principle
The DIP states that components should not depend on other concrete components, but on abstractions.
The concept of “abstractions” is sometimes lost in Ruby because it’s not explicit. In Java, a rough way to follow this principle is to extract interfaces from classes and code to those interfaces rather than concrete implementations. Duck-typing is a powerful way to code to abstractions, but sometimes we forget the underlying principle.
The DIP is more or less a consequence of following the other principles. Instead of objects managing their own dependencies, you end up passing dependencies in. In that sense, the dependency structure gets inverted. This leads to more flexible code, as we’ve discussed.