返回 Skill 列表
extension
分类: 开发与工程无需 API Key

tell-dont-ask

在审查、编写或重构面向对象代码时应用讲述不要询问(TDA)原则。当用户询问OOP设计、提到getter/setter、想要检查类的封装问题、询问如何将逻辑更接近数据、或者询问为什么尽管使用了类但代码感觉像是“过程式”的时候,都应该使用这项技能。当用户要求重构在外部做出决策前查询对象状态的代码时也应触发此技能。对于涉及数据访问模式、封装或对象应该如何协作的任何代码审查或设计问题,此技能都应发挥作用。

person作者: jakexiaohubgithub

Tell Don't Ask

Core Principle

Tell Don't Ask (TDA) means: instead of asking an object for its data and then acting on it externally, tell the object what to do and let it use its own data internally.

This flows directly from OOP's core idea — bundle data with the behavior that operates on it. Tightly coupled data and behavior belong in the same component.


Identifying "Ask" Style (The Problem)

Watch for this pattern: external code queries an object's state and then makes decisions based on that state.

if (order.status === "pending" && order.total > 1000) {
    order.approvalQueue.push(order.id);
    order.status = "awaiting_approval";
}

Red flags:

  • Chains of getters used to make a decision
  • if (obj.getX() > obj.getLimit()) patterns outside the class
  • External code setting state after reading state (get then set)
  • Logic that "belongs" to an object scattered across callers

Applying the Refactor

Move the decision-making logic into the object that owns the data.

order.submitForApproval();
class Order {
  submitForApproval(): void {
    if (this.status === "pending" && this.total > 1000) {
      this.approvalQueue.push(this.id);
      this.status = "awaiting_approval";
    }
  }
}

The caller tells the object what to do; the object decides how.


Step-by-Step Refactoring Guide

  1. Find the ask chain — locate external code that reads object data to make a decision.
  2. Identify which object owns the data — the class whose fields are being read.
  3. Name the intent — what is the caller trying to accomplish? Name a method for that intent (e.g., submitForApproval, checkAlarm, applyDiscount).
  4. Move the logic in — cut the conditional/action block and paste it into the new method on the owning class.
  5. Replace the call site — swap the ask chain with a single tell call.
  6. Remove now-unnecessary getters if nothing else uses them (be cautious — see nuance below).

Nuance: When Getters Are Fine

Martin Fowler himself notes he doesn't strictly follow TDA. Query methods are legitimate when:

  • An object transforms data for its caller (e.g., formatting, aggregation)
  • The object is a value object or DTO whose purpose is to carry data
  • You're crossing architectural layers (e.g., persistence, serialization)
  • Removing a getter would create convolutions worse than the query

The goal is co-locating behavior with data, not eliminating all accessors. Don't become a "getter eradicator" — use judgment.


Quick Reference Examples

| Ask (avoid) | Tell (prefer) | |---|---| | if (a.value > a.limit) a.alarm.warn(...) | a.setValue(newVal) — alarm logic inside setValue | | if (cart.items.length === 0) showEmpty() | if (cart.isEmpty()) showEmpty() — at minimum, encapsulate the check | | order.status = order.computeNextStatus() | order.advance() |


When Reviewing Code

For each class under review:

  1. List all getters — are any used only to make external decisions about the object?
  2. Find conditional blocks that read from a single object's fields — can they move in?
  3. Look for setter chains (setA, setB, setC in sequence) — could that be one configure(...) or initialize(...) call?

Flag violations clearly, suggest the encapsulated alternative, but note if the refactor would be overkill (e.g., trivial DTOs, framework-required accessors).