Explanation on how “Tell, Don’t Ask” is considered good OO

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

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

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

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

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.

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!

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”.

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.

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

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.

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật