59

This blogpost was posted on Hacker News with several upvotes. Coming from C++, most of these examples seem to go against what I've been taught.

Such as example #2:

Bad:

def check_for_overheating(system_monitor)
  if system_monitor.temperature > 100
    system_monitor.sound_alarms
  end
end

versus good:

system_monitor.check_for_overheating

class SystemMonitor
  def check_for_overheating
    if temperature > 100
      sound_alarms
    end
  end
end

The advice in C++ is that you should prefer free functions instead of member functions as they increase encapsulation. Both of these are identical semantically, so why prefer the choice that has access to more state?

Example 4:

Bad:

def street_name(user)
  if user.address
    user.address.street_name
  else
    'No street name on file'
  end
end

versus good:

def street_name(user)
  user.address.street_name
end

class User
  def address
    @address || NullAddress.new
  end
end

class NullAddress
  def street_name
    'No street name on file'
  end
end

Why is it the responsibility of User to format an unrelated error string? What if I want to do something besides print 'No street name on file' if it has no street? What if the street is named the same thing?


Could someone enlighten me on the "Tell, Don't Ask" advantages and rationale? I am not looking for which is better, but instead trying to understand the author's viewpoint.

8
  • 2
    I always wonder if something like the first example isn't rather a violation of SRP?
    – stijn
    Commented Jul 20, 2012 at 7:33
  • 1
    You may read that: pragprog.com/articles/tell-dont-ask
    – Mik378
    Commented Nov 15, 2012 at 1:12
  • Ruby. @ is shorthand for instance and Python ends its blocks implicitly with whitespace. Commented Feb 27, 2013 at 6:01
  • 3
    "The advice in C++ is that you should prefer free functions instead of member functions as they increase encapsulation." I don't know who told you that, but it's not true. Free functions can be used to increase encapsulation, but they do not necessarily increase encapsulation.
    – Rob K
    Commented Jul 23, 2014 at 18:09
  • The second example looks weird to me because I don't see why sound_alarms should be a method on SystemMonitor. Commented Jan 13, 2016 at 18:33

9 Answers 9

97
+100

Asking the object about its state, and then calling methods on that object based on decisions made outside of the object, means that the object is now a leaky abstraction; some of its behavior is located outside of the object, and internal state is exposed (perhaps unnecessarily) to the outside world.

You should endeavor to tell objects what you want them to do; do not ask them questions about their state, make a decision, and then tell them what to do.

The problem is that, as the caller, you should not be making decisions based on the state of the called object that result in you then changing the state of the object. The logic you are implementing is probably the called object’s responsibility, not yours. For you to make decisions outside the object violates its encapsulation.

Sure, you may say, that’s obvious. I’d never write code like that. Still, it’s very easy to get lulled into examining some referenced object and then calling different methods based on the results. But that may not be the best way to go about doing it. Tell the object what you want. Let it figure out how to do it. Think declaratively instead of procedurally!

It is easier to stay out of this trap if you start by designing classes based on their responsibilities; you can then progress naturally to specifying commands that the class may execute, as opposed to queries that inform you as to the state of the object.

https://web.archive.org/web/20160204103129/https://pragprog.com/articles/tell-dont-ask

11
  • 4
    The example text disallows many things which are clearly good practice.
    – DeadMG
    Commented Jul 20, 2012 at 1:38
  • 15
    @DeadMG it does what you say only to those slavishly follow it, who blindly ignore "pragmatic" in the site name and key thought of site authors that has been clearly stated in their key book: "there is no such thing as a best solution..."
    – gnat
    Commented Jul 20, 2012 at 5:01
  • 3
    Never read the book. Nor would I wish to. I only read the example text, which is completely fair.
    – DeadMG
    Commented Jul 20, 2012 at 5:46
  • 3
    @DeadMG no worries. Now that you know key point that puts this example (and any other one from pragprog for that matter) in the intended context ("no such thing as a best solution..."), it's OK not to read the book
    – gnat
    Commented Jul 20, 2012 at 13:10
  • 3
    I'm still not sure what Tell, Don't Ask is supposed to spell out for you without context but this is really good OOP advice. Commented Feb 27, 2013 at 6:10
19

Neither of these options is better than the other, inherently. It is all a relative question depending on the exact domain, etc of the objects at hand. Decreasing coupling can be both a good and a bad thing, or you simply end up trading one kind of coupling for another.

Take the example of the system temperature. Is this coupled to a specific class or instance? No. You might think that the operations available on the temperature are relatively limited, like overclocking or underclocking the processor. But then we get into 3rd party apps that slow down or speed up fans, or just display your temperature on the screen. These would be impossible if the temperature was architected as the SystemMonitor above. The reality is that the temperature of the system is a global fact. Even the human being operating the machine can observe it. It's not an internal value. Trying to hide it only makes things worse.

Meanwhile, another interesting observation is that in the "not so good", the sound_alarms call does not have to be coupled to the SystemMonitor. In the "better" case the SystemMonitor itself needs to know how to sound an alarm. We have simply changed one kind of coupling for another.

That being said, there are other examples that behave exactly the opposite. One good example would be that when reading or writing from a file stream, you probably don't want to give the user the underlying OS handle. They might do God knows what with it and break your class. Or this might even expose you to contract/ABI problems. In this case it's quite true to say that the data and the relevant operations belong together.

In example 1 we see a particularly bad example. The author has in both cases coupled the user object to the rendering of a welcome message. It would be better to avoid this coupling entirely by having the caller decide the welcome message and the user only exposes admin.

Fundamentally, some coupling is unavoidable, but exactly what coupling is problematic and what coupling is no problem depends on the exact circumstances involved. The programmer has to make an informed decision as to whether any specific piece of data or state is internal or public. So far I'm generally finding that objects either have all public data and no operations, or very little to no public data and all operations. Mixing the two seems like it rarely produces a good result.

Also note that Null Object is generally an anti-pattern when applied to data, as data integrity and clarity concerns are much more important than the odd missing null handler. It's much harder to report, migrate, or change the semantics of missing data if you have supplied a default. Often what ends up happening is basically the same as null handling anyway but the default takes the place of null. When it comes to data you should generally be as exact and precise as possible and never throw it away or lose fidelity if it can be avoided.

13
  • 20
    “Null object” isn’t what I’d call an anti-pattern, so I wonder what’s your reason for doing so? Commented Jul 20, 2012 at 7:04
  • 5
    Pretty sure that nobody has any methods which are specified as "Does nothing". That makes calling them kinda pointless. That means that any object implementing Null Object breaks LSP, at the very least, and describes itself as implementing operations that it, in fact, does not. The user expects a value back. Their program's correctness depends on it. You simply bring more problems by pretending that it is a value when it is not. Have you ever tried to debug silently failing methods? It is impossible and nobody should ever have to suffer through that.
    – DeadMG
    Commented Jul 20, 2012 at 7:39
  • 6
    I’d argue that this entirely depends on the problem domain. Commented Jul 20, 2012 at 8:27
  • 7
    @DeadMG I agree that the above example is a bad usage of the Null Object pattern, but there is a merit to using it. A few times I've used a 'no-op' implementation of some interface or other to avoid null-checking or having true 'null' permeating the system.
    – Max
    Commented Jul 20, 2012 at 10:20
  • 6
    Not sure I see your point with "the client can check_for_underheating without having to change SystemMonitor". How is the client different from SystemMonitor at that point? Aren't you now dissipating your monitoring logic across multiple classes? I also don't see the problem with a monitor class that provides sensor information to other classes while reserving alarm functions to itself. The boost controller should be controlling boost w/o having to worry about raising an alarm if the temperature gets too high.
    – TMN
    Commented Jul 20, 2012 at 13:26
10

The real issue with your overheating example is that the rules for what qualifies as overheating are not easily varied for different systems. Suppose System A is as you have it (temp>100 is overheating) but System B is more delicate (temp>93 is overheating). Do you change your control function to check the type of system, and then apply the correct value?

if (system is a System_A and system_monitor.temp >100)
  system_monitor.sound_alarms
else if (system is a System_B and system_monitor.temp > 93)
  system_monitor.sound_alarms
end

Or do you have each type of system define its heating capacity?

EDIT:

system.check_for_overheating

class SystemA : System
  def check_for_overheating
    if temperature > 100
      sound_alarms
    end
  end
end

class SystemB : System
  def check_for_overheating
    if temperature > 93
      sound_alarms
    end
  end
end

The former way makes your controlling function get ugly as you start dealing with more systems. The latter lets the control function be stable as time goes on.

4
  • 1
    Why not have each system register with the monitor. During registration they can indicate when overheating occurs. Commented Jul 20, 2012 at 19:47
  • @LokiAstari - You could, but then you could run into a new system that's also sensitive to humidity or atmospheric pressure. The principle is to abstract out what varies--in this case it is the susceptibility to overheating Commented Jul 20, 2012 at 20:59
  • 1
    This is exactly why you should have a tell model. You tell the system the current conditions and it informs you if it is outside normal working conditions. Thus way you never need to modify the SystemMoniter. That's encapsulation for you. Commented Jul 20, 2012 at 23:40
  • @LokiAstari - I think we're talking at cross purposes here--I was really looking at creating different systems, rather than different monitors. The thing is, the system should know when it is in a state that raises an alarm, as opposed to some outside controller function. SystemA should have its criteria, SystemB should have its own. The controller should just be able to ask (at regular intervals) whether the system is OK or not. Commented Jul 21, 2012 at 2:54
6

First off, I feel I must take exception to your characterization of the examples as "bad" and "good". The article uses the terms "Not so good" and "Better", I think those terms were chosen for a reason: these are guidelines, and depending on circumstances the "Not so good" approach may be appropriate, or indeed the only solution.

When given a choice, you should give preference to including any functionality that relies solely upon the class in the class instead of outside it -- the reason is because of encapsulation, and the fact that it makes it easier to evolve the class over time. The class also does a better job of advertising it's capabilities than a bunch of free functions.

Sometimes you have to tell, because the decision relies upon something outside of the class or because it is simply something you don't want most users of the class to do. Sometimes you want to tell, because the behavior is counter intuitive for the class, and you don't want to confuse most users of the class.

For example, you complain about the street address returning an error message, it isn't, what it is doing is providing a default value. But sometimes a default value isn't appropriate. If this was State or City, you might want a default when assigning an a record to a salesman or a survey taker, so that all of the unknowns go to a specific person. On the other hand, if you were printing envelopes, you might prefer an exception or guard that keeps you from wasting paper on letters that can't be delivered.

So there can be cases where "Not so good" is the way to go, but generally, "Better" is, well, better.

5

This paradigm is sometimes referred to as ‘Tell, don't ask’, meaning tell the object what to do, don't ask about its state; and sometimes as ‘Ask, don't tell’, meaning ask the object to do something for you, don't tell it what its state should be. Either way around the best practice is the same — the way an object should perform an action is that object's concern, not the calling object's. Interfaces should avoid exposing their state (e.g. via accessors or public properties) and instead expose ‘doing’ methods whose implementation is opaque. Others have covered this with the links to pragmatic programmer.

This rule is related to the rule about avoiding "double-dot" or "double arrow" code, often referred to as ‘Only talk to immediate friends’, which states foo->getBar()->doSomething() is bad, instead use foo->doSomething(); which is a wrapper call around bar's functionality, and implemented as simply return bar->doSomething(); — if foo is responsible for managing bar, then let it do so!

4

Data/Object Anti-Symmetry

As others pointed out, Tell-Dont-Ask is specifically for cases where you change the object state after you asked (see e.g. the Pragprog text posted elsewhere on this page). This is not always the case, e.g. the 'user' object is not changed after it was asked for its user.address. It's therefore debateable if this is an appropriate case to apply Tell-Dont-Ask.

Tell-Dont-Ask is concerned with responsibility, with not pulling logic out of a class that should be justifiably within it. But not all logic that deals with objects is necessarily logic of those objects. This is hinting at a deeper level, even beyond Tell-Dont-Ask, and I want to add a short remark about that.

As a matter of architectural design, you might want to have objects that are really just containers for properties, maybe even immutable, and then run various functions over collections of such objects, evaluating, filtering or transforming them rather than sending them commands (which is more the domain of Tell-Dont-Ask).

The decision which is more appropriate for your problem depends on whether you expect to have stable data (the declarative objects) but with changing/adding on the function side. Or if you expect to have a stable and limited set of such functions but expect more flux at the objects level, e.g. by adding new types. In the first situation you would prefer free functions, in the second object methods.

Bob Martin, in his book "Clean Code", calls this the "Data/Object Anti-Symmetry" (p.95ff), other communities might refer to it as the "expression problem".

3

These answers are very good, but here's another example just to emphasize: note that it's usually a way to avoid duplication. For example, let's say you have SEVERAL places with a code like:

Product product = productMgr.get(productUuid)
if (product.userUuid != currentUser.uuid) {
    throw BlahException("This product doesn't belong to this user")
}

That means you'd better have something like this:

Product product = productMgr.get(productUuid, currentUser)

Because that duplication means most clients of your interface would use the new method, instead of repeating the same logic here and there. You give your delegate the work you want done, instead of asking for the info you need in order to do it yourself.

1

In addition to the other good answers about "tell, don't ask", some commentary on your specific examples that might help:

The advice in C++ is that you should prefer free functions instead of member functions as they increase encapsulation. Both of these are identical semantically, so why prefer the choice that has access to more state?

That choice does not have access to more state. They both use the same amount of state to do their jobs, but the 'bad' example requires the class state to be public in order to do its work. Further, the behavior of that class in the 'bad' example is spread out to the free function, making it harder to find and more difficult to refactor.

Why is it the responsibility of User to format an unrelated error string? What if I want to do something besides print 'No street name on file' if it has no street? What if the street is named the same thing?

Why is it the responsibility of 'street_name' to do both 'get street name' and 'provide error message'? At least in the 'good' version, each piece has one responsibility. Still, it's not a great example.

4
  • 3
    That's not true. You presume that checking for overheating is the only sane thing to do with the temperature. What if the class is intended to be one of a number of temperature monitors, and a system must take different action depending on many of their results, for example? When this behaviour can be limited to pre-defined behaviour of a single instance, then sure. Else, it obviously can't apply.
    – DeadMG
    Commented Jul 20, 2012 at 1:47
  • Sure, or if the thermostat and the alarms existed in different classes (as they likely should).
    – Telastyn
    Commented Jul 20, 2012 at 1:53
  • 1
    @DeadMG: The general advice is to make things private/protected until you need access to them. While this particular example is meh, that doesn't dispute the standard practice.
    – Guvante
    Commented Jul 20, 2012 at 17:50
  • An example in an article about the practice being 'meh' kinda disputes it. If this practice is standard because of it's great benefits, then why the trouble to find a suitable example? Commented Mar 15, 2016 at 23:09
1

I believe this is more true when writing high-level object, but less true when going down to the deeper level e.g. class library as it's impossible to write every single method to satisfy all class consumers.

For example #2, I think it is over-simplified. If we were actually going to implement this, the SystemMonitor would end up having the code for low level hardware access and logic for high level abstraction embedded in the same class. Unfortunately, if we are trying to separate that into two classes, we would violate the "Tell, Don't ask" itself.

The example #4 is more or less the same -- it's embedding UI logic into data tier. Now if we are going to fix what user wants to see in case of no address, we have to fix the object in data tier, and what if two projects using this same object but need to use different text for null address?

I agree that if we can implement "Tell, Don't ask" for everything, it would be very useful -- I myself would be happy if I can just tell rather than ask (and do it myself) in real life! However, as same as in the real life, the feasibility of the solution is very limited to high level classes.

Not the answer you're looking for? Browse other questions tagged or ask your own question.