At youDevise we’ve started to run regular code katas, an idea that started in our dojos. Two of us practise a particular themed refactoring many times for a week, then perform it for everyone else along with a description of the theme. It’s early days, but seems to be working OK.
I started a wiki page on the first theme, Tell Don’t Ask, and encouraged developers to add thoughts and code snippets to it. I was pleased to see how many good ideas we had; for privacy reasons I can’t share the code, but here are some of the highlights from the page on this very useful principle.
Tell Don’t Ask
Using the principle Tell, Don’t Ask means sending messages from one object to another, rather than exposing the state of one object to others who manipulate that state. If you follow this principle:
- You will reduce coupling among your classes.
- You should be able to give your classes and methods more business-meaningful names, not just
getThat(). If you’re not sure what name to give a class, look at its methods and ask yourself what domain entity does those things; if the methods don’t suggest an entity, you may not have a coherent class! Business entities rarely, if ever,
get(); they may
show() but this implies much more than a naked
- Mocking will be easier and will make more sense. Mocks that are told what to do can easily fake the behaviour, or just record their incoming messages. Mocks that have to pretend that they have an internal state manipulated through invasive methods are harder to write and document behaviour poorly.
- You will avoid confusing, heavily-coupled “train wreck” code:
dog.getHead().getMouth().insertFood(steak) instead of
TDA vs. DI
Here are two sequence diagrams that illustrate the main differences and similarities between the Tell Don’t Ask principle and the Dependency Injection principle:
Ask without Telling
The parts to notice are that when using the tell, don’t ask principle there are going to be more objects interacting with each other. Each one of those classes will, however, be much smaller since each one is concerned with just its own little bit of the overall picture. The classes turn into an orchestration of the algorithm, rather than a procedural listing of the algorithm.
Another side effect of tell, don’t ask is that mocking becomes much more useful. The mocks can now make real assertions about what was supposed to be done and check that the correct interaction occurred. This is because the ideas of the algorithm have been elevated from plain, opaque data into meaningful and separated messages.
The other half of the diagrams is the dependency injection. DI has similar properties to Tell, Don’t Ask in that the user of an object is “telling” it what other objects to use, but differs in where its main focus lies. DI is much more about the lifecycle (creation and destruction) of objects and much less about how they are interacted with.
It all boils down to this: without DI testing is nearly impossible, without Tell, Don’t Ask testing is monolithic. With both DI and Tell, Don’t Ask testing is simplified and well separated.
“Tell don’t ask” is about reducing a particular kind of coupling complexity: operations by other objects on the exposed state of a target object. “Tell don’t ask” solves this by pushing the operation into the target object and hiding the associated state in there as well.
The more objects that can see and act on the states of other objects the more complex your system becomes: the exposed states of an object are multipliers of complexity. Control complexity by limiting the exposed states of objects.
Pure “tell don’t ask”, a void function with no exceptions, gives the caller no possibility of behaving differently in response to the action they’ve triggered, there’s no visible state, no return message, nothing for the caller to act on, and that makes it much easier to reason about the correctness of the caller.
The next step up allows is boolean return value with the caller able to follow two branches. A number return allows many branches, exceptions are also returns that cause branching, and so forth. It’s easier to think about just two branches rather than many branches. It’s much easier to think about no possibility of branching. If changes in the target object’s state are visible then anything with access to that object can change it’s behaviour in reponse to the initial operation, multiplying complexity uncontrollably.
When writing a caller class you want to be able to reason about your class, to assure yourself that it is correct, and to know that others looking at it later will feel sure it’s right. Being exposed to less state in other objects helps keep your object simpler, so you should be trying to expose yourself to the least number of objects and you should want them to have simple interfaces that don’t allow needlessly complex possiblities as a response to their operation. If nothing else hide the state of bad objects in facades to show how it could be done.
A programmer writing a target class that will be called by others should be trying to be a good citizen, should be trying to make it easy for the callers to be simple. Offering wide-open interfaces with access to complex state forces the callers to at least think about the possibility of responding to that complexity, and that makes their lives harder: general is not simple. Try to limit the names of public functions to clear business operations – being so specific can help keep the system as simple as possible.