4. Don’t Talk To Strangers
In the previous chapter, we started to look at how even the accidental implementation of couriers will, in all likelihood, make portions of your application fragile and susceptible to bugs. The example we looked at passed a database connection through a controller in order to reach its final destination inside a model instance.
The issue wasn’t that the code doesn’t work, or that it breaks occasionally (although it might if you haven’t wrapped the establishment of a database connection with the appropriate safeguards and error-handling). No, the issue is the fact that when the target code, in this case the model class, is revisited at some point down the line, you also have a string of other classes to consider if the reception of the database connection changes in any significant way.
It’s very much a case of “I fixed this bit here, but now that bit over there is broken!”
This chapter focuses on a very similar problem and concerns itself with something known by many names, one of which is the Law of Demeter. Devised by Ian Holland way back in 1987, the Law of Demeter isn’t even a law, but a guideline. I suppose we can forgive the fine folks that came up with that particular moniker though, since “The Guideline of Demeter” is somehow less punchy.
Emerson Macedo boils down the Law of Demeter into three succinct statements, which are thus:
- Each unit should have only limited knowledge about other units: only units “closely” related to the current unit.
- Each unit should only talk to its friends; don’t talk to strangers.
- Only talk to your immediate friends.
In a way though, these three statements are a little too much on the succinct side. Without appropriate context or prior knowledge of this particular guideline, it isn’t particularly easy to intuit either their meaning or their intended application.
But hey, that’s what this chapter is for. Let’s get going.
The Principle of Least Knowledge
That sub-heading up there gives us another one of the names that this particular guideline is known by and at least this time it’s a little more indicative of what this topic is actually about. This new name for it ties in nicely with those three bullet points that we looked at on the first page of this chapter.
The idea behind the Principle of Least Knowledge is that wherever you happen to be coding inside your application, the code that you write should only express knowledge of its immediate surroundings. When you follow this particular guideline, you’re automatically promoting the notion of loose coupling within your codebase, which in turn leads to a much higher degree of maintainability.
What on earth does this all mean though?
Clearly, we need one of those examples around about now.
class OrderController
{
public function addOrderAction()
{
...
try {
$user->getAccount()
->getBalance()
->deductAmount($orderTotal);
} catch (InsufficientFundsException $e) {
// handle exception
}
...
}
Here we are again with that woefully used and abused addOrderAction method, looking for a simple way for our customer to pay their damned bill.
In this case, it looks like we are passing the value of the order through to a method called deductAmount(), catching the exception that might be thrown if the user doesn’t have enough funds in their account to pay for the order.
This is fine, surely? We’ve got error handling in there, and even if you can’t see it in such a small snippet of code, there’s lazy loading going on in the background with the UserAccount and AccountBalance objects.
Lizzie’s certainly familiar with this sort of approach; they did it all the time at her old place and the old place managed to turn out some reasonably useful applications. The latter doesn’t change the fact that Lizzie’s last place was rubbish though.
Don’t tell her I said that, we’re lucky to have her here with us.
Back to the code. Just what is the problem with it, given the fact that it works when all is well, and catches the exception when they’re not? The answer to that lies not with the fact that it’s perfectly operational today, but that it presents us with a potential problem in the future.
Nothing will impact so negatively upon our valuable pub time than code constructs that provide fertile ground for defects to geminate within.
With the preservation of pub time firmly set at the forefront of our minds, it’s time to look at yet another name that this particular principle is known by.
The one dot principle.
The first thing to note here is that this particular name was devised for those languages that separate objects from their fields with the dot (period) symbol, which in real terms, is pretty much all of them except PHP and Perl.
You know the sort of thing. If you’ve ever taken even the briefest look at Java, you will have encountered the following line:
System.out.println("Hello, World");
Of course, in PHP we use the arrow (->) to separate an object from its fields but the idea is just the same; if you’re utilising more than one dot to access a property or method, the chances are exceedingly high that you’re not following the Principle of Least Knowledge correctly.
The second thing to note is that this particular version of the name for the guideline/idiom/principle is the least accurate of the bunch so far. Determining whether we are conforming to or violating the Law of Demeter is a little bit more involved than a process of counting the dots.
Still, we haven’t even gotten as far as considering why it’s a bad thing, so let’s get straight onto that now by recalling the offending “line” of code.
$user->getAccount()
->getBalance()
->deductAmount($orderTotal);
In PHP terms, that’s three “dots” right there…
Could we fix that code by adhering to a “single dot” approach like this?
$account = $user->getAccount();
$balance = $account->getBalance();
$balance->deductAmount($orderTotal);
The answer is emphatically no.
All that we have achieved here is to move each of those three troublesome “dots” onto their own lines. The key problem remains, there’s just too much knowledge here.
Not only is it aware that invoking the getAccount() method will return an object that presents the getBalance() method, it’s also aware that the return value of the getBalance() method offers the deductAmount() method.
That’s just too much knowledge.
In the language of the Law of Demeter, this line of code is reaching through the intermediate objects in order to invoke the deductAmount() method at the end of the chain. The way that we’ve written that code, even after we decomposed the chain down to three individual statements, expresses intimate knowledge of how that chain is structured all the way through to the final method call.
What we have here is another instance of tight coupling, which is something that we should always be trying to avoid. Tight coupling reduces the quality of the application code that we write by making it harder to maintain. Changes made to one piece of tightly coupled code require us to review and potentially modify all the other members of the tightly coupled relationship.
To put this into perspective, imagine a situation where, six months down the line, the business team pops up with a new requirement: instead of requiring our users to pre-fund their accounts, we’ll be offering credit facilities to a select few of them.
Just to make things a little more complicated, let’s imagine that the pre-funders are going to get to enjoy a discount on their orders but the users that pay on credit are going to be subject to a small surcharge.
This is a significant change for the business, but the way things are currently, it’s massive change for the codebase, all thanks due to our tightly coupled design.
When this particular story lands in Lizzie’s lap, it’s apparent that she will be working primarily within the UserAccount class to handle the credit versus pre-funded account scenarios.
Unfortunately, thanks to our tightly coupled code, she’s also going to have to check through the entire codebase to determine whether other parts of the application are affected as well. For one thing, we know that the addOrderAction() method will have to be modified since it most certainly doesn’t accommodate the notion of juggling credit facilities currently.
What if the company offered a monthly subscription service? A notion that’s certainly not beyond the realms of possibility but if the answer is to be a yes, then poor Lizzie’s faced with the prospect of digging through the cron scripts as well. We’d better make sure there’s plenty of coffee in the kitchen in this case.
Had we been aware of the Law of Demeter from the outset, all of this extra work might have been avoided. Instead of having our action method reach through multiple objects in order to trigger an order payment, our controller code might have looked like this.
class OrderController
{
public function addOrderAction()
{
...
try {
$user->payForOrder($order);
} catch (OrderPaymentException $e) {
// handle error
}
...
}
}
The critical line of code is this one:
$user->payForOrder($order);
In one fell swoop we’ve eliminated all of that extra knowledge that the action method shouldn’t have had in the first place. Even though I’ve left you to imagine how this method acquires the $user and $order instances in the first place, the simple act of passing the $order instance to the user’s payForOrder() method would have served the pre-credit account era just as well as the post-credit account one.
In other words, knowledge of the inner workings of the order payment process has been removed from where it doesn’t belong. Our action method no longer has any kind of idea as to what happens to that Order instance on the other side of the payForOrder() invocation.
Now, the only knowledge it has of that process is that if an exception is thrown, it has to deal with it, which is exactly as it should be.
But does this mean that the logic that handles order payments now has to be moved into the User class?
Not at all. Given the code that we’ve already seen, and the nature of the story that Lizzie has to deal with, we know that it’s the UserAccount objects that are going to be dealing with order payments.
What we’re adding to the User class is a proxy method, like so:
class User
{
...
public function payForOrder(Chargeable $order)
{
try {
$this->getAccount()->payForOrder($order);
} catch (InsuffientFundsException $e) {
// throw new OrderPaymentException();
}
}
}
A proxy method is a pretty simple construct. In this instance it does just two things. Firstly, it makes sure that it’s receiving a parameter that conforms to our Chargeable interface, whatever that may be. Secondly, it passes that Chargeable item on to this user’s UserAccount instance.
This is the point where we start encountering the downsides to achieving loose coupling by following the Law of Demeter; a proliferation of proxy methods.
In this rather simple scenario, it isn’t really a problem. The addition of the payForOrder() method to the public interface of our User model is both short and to the point. Any other developer on our team ought to be able to look at that code as see that an order can be paid for just by sending Chargeable compliant instance to the payForOrder() method.
Where things do start to get a bit messy is the point where we are adding too many proxy methods. It doesn’t take too long before we end up polluting the public interface of our User model with methods that are only vaguely related to our concept of what a User actually is. With too much of this going on, our User model will end up looking like an implementation of the Facade Pattern.
We’re getting ahead of our selves though. If we didn’t want to start going down the route of adding proxy methods, how do we solve the issue in our controller’s action method.
One possibility would be to cut out the middleman and work with the UserAccount instance directly, given us code that looks something like this:
public function addOrderAction()
{
...
try {
$userAccount->payForOrder($order);
} catch ( OrderPaymentException $e) {
// handle exception
}
...
}
If you read that aside earlier on in this chapter you might be tempted to exclaim “Aha! But isn’t that just like handing the wallet over to the paperboy?” to which the answer is, thankfully, no. If our action method is behaving like the paperboy, it’s still successfully ignorant of the actual payment process, which is still nicely abstracted away behind the payForOrder() method.
What we’re actually looking at here is the action method working directly with an immediate collaborator. Admittedly, our controller might be better served by handing off the entire order/payment process to a service and letting that service co-ordinate the appropriate interactions with the model, but that’s an architectural concern that we’ll look at in Part Five of the book.
Connecting code with its immediate collaborators is exactly what the Law of Demeter wishes for us to do. Let’s take another look at those three bullet points that we started this chapter with.
- Each unit should have only limited knowledge about other units: only units “closely” related to the current unit.
- Each unit should only talk to its friends; don’t talk to strangers.
- Only talk to your immediate friends.
By revising our code to cut out the middle man (the User instance), we’ve brought our three “friends” closer together; the Order and UserAccount instances, and the OrderController itself. Even so, we’re still promoting loose coupling between the players within this system; the knowledge of how to handle a Chargeable item remains firmly where it should be and nowhere where it shouldn’t.
This in turn frees Lizzie up to play merry hell inside the UserAccount object itself. As long as she receives a Chargeable object and emits an exception when the value of that object can’t be charged to the user’s account, it’s not going to matter too much what the actual logic implementation looks like. No other piece of code has that knowledge, so no other piece of code will get broken due to changes in the way that that knowledge is implemented.
This is the situation that we want to find ourselves in, one that promotes loose coupling and one that helps us to properly encapsulate the knowledge of potentially complicated operations into the places where they should be.
The Law of Demeter helps us to achieve both of these desirable outcomes.
Summary
The Law of Demeter wants us to keep our friends close and our enemies as far away as possible, erecting barricades and boundary fences along the way. Tight coupling is one such enemy and, whilst it’s certainly unavoidable 100% of the time, erecting the proper boundaries allows us to avoid the miserable loss of pub time due to action at a distance effects.
In our erroneous early example, our controller code was reaching through both the $user instance and the $userAccount instance to get at the deductAmount() method of the $balance instance. Hopefully now it should be clear how this can lead to problems. The developer that is working on the UserAccount or the Balance class isn’t necessarily going to be aware that the controller code was accessing the $balance instance’s methods directly.
With the appropriate barrier in place, the payForOrder(Chargeable $order); method, the controller code becomes ignorant of any changes to the way that order payments are settled, which is precisely how we want it.
One thing that we still have to guard against though is the proliferation of proxy methods that can lead to a bloated, muddied interface.
But with loose coupling and encapsulation promoted through the application of The Law of Demeter, we’re golden.
Don’t talk to strangers. Unless they’re offering to pick up your bar tab.