Don't ask an object, tell it.
Core Principles
Do not make decisions based on an object's internal state and then update that object with the result. (The Pragmatic Programmer, 2nd Edition, p. 167)
| Approach | Characteristics | Problems |
|---|---|---|
| Ask | Get state -> judge externally -> operate | Logic is scattered, encapsulation is broken |
| Tell | Command the object directly | Responsibility is consolidated, resilient to change |
Decision Flow
オブジェクトのメソッド呼び出し
↓
getterで状態を取得しているか?
├─ YES → その後ifで判定している?
│ ├─ YES → Askパターン(問題あり)
│ └─ NO → 表示/出力目的なら許容
└─ NO → Tellパターン(推奨)
Anti-Pattern Detection
If you find the following patterns, consider converting them:
❌ if (obj.getX() > threshold) { obj.setY(...) }
❌ if (obj.getStatus() == ACTIVE) { doSomething(obj) }
❌ obj.getA().getB().doSomething() // デメテルの法則違反
❌ for (item : list) { total += item.getPrice() }
❌ if (user.getRole() == ADMIN) { ... }
Conversion Patterns
1. Internalize state judgment
// ❌ Ask: 状態を取得して外部で判断
if (user.getAge() >= 18) {
allowAccess(user);
}
// ✅ Tell: 判定ロジックをオブジェクトに持たせる
if (user.isAdult()) {
allowAccess(user);
}
// ✅✅ さらに良い: 処理自体を委譲
user.ifAdult(() -> allowAccess());
2. Turn conditionals into polymorphism
// ❌ Ask: 型で分岐
if (user.getType() == UserType.ADMIN) {
sendAdminNotification(user);
} else {
sendUserNotification(user);
}
// ✅ Tell: 各クラスに責任を持たせる
user.sendNotification(); // Admin/RegularUserで実装が異なる
3. Delegate collection operations
// ❌ Ask: 外部で集計
int total = 0;
for (Item item : order.getItems()) {
total += item.getPrice();
}
// ✅ Tell: オブジェクトに集計を任せる
int total = order.calculateTotal();
4. Null Object Pattern
// ❌ Ask: null判定の分岐
Address addr = user.getAddress();
if (addr != null) {
return addr.format();
} else {
return "住所未登録";
}
// ✅ Tell: NullObjectでデフォルト動作を定義
return user.getAddress().format(); // NullAddressは"住所未登録"を返す
Related Principles / Skills
| Principle / Skill | Relationship |
|---|---|
| law-of-demeter | Avoid chained calls (a.getB().getC() → a.doC()) |
| Feature Envy | Obsession with another class's data -> move responsibility |
| Single Responsibility Principle | Keep data and processing in the same place |
| Encapsulation | Hide internal state and expose behavior |
| breach-encapsulation-naming | Use getter naming to make encapsulation violations explicit |
Application Guidelines
Recommended
- Code that uses an if statement to judge after a getter
- The same judgment logic is scattered across multiple places
- Pattern of getting then updating an object's state
- Conditional branching based on type or status
Avoid Overapplication
- Data retrieval for display/reporting purposes
- Simple value retrieval from a DTO/Value Object
- When there are framework/library constraints
- If the class becomes bloated, consider splitting responsibilities
Review Perspectives
Checkpoints during code review:
- getter + if: Is there conditional branching after getting state?
- Chained calls:
a.getB().getC()Is there no such chain? - External aggregation: Is data being collected in a loop?
- Type/status branching: Can it be replaced with polymorphism?
Detailed Guidelines
For language-specific implementation patterns and detailed refactoring procedures, see references/patterns.md .
Related Skills (Recommended Reading)
When using this skill, also refer to the following skills:
law-of-demeter: Complementary structural principle (talk only to your immediate friends)first-class-collection: Patterns for applying Tell, Don't Ask to collectionsbreach-encapsulation-naming: Naming convention for when encapsulation needs to be broken