데이터 요청을 그만! “Tell, Don't Ask” 원칙 마스터하기

발행: (2025년 12월 19일 오전 09:57 GMT+9)
4 min read
원문: Dev.to

Source: Dev.to

소개

객체‑지향 프로그래밍(OOP)에서 가장 흔한 실수 중 하나는 객체를 단순한 데이터 구조처럼 다루는 것입니다. 우리는 종종 객체에서 데이터를 꺼내 로직을 수행한 뒤 결과를 다시 넣습니다. 이 글에서는 Tell, Don’t Ask (TDA) 원칙을 설명하고, 간단한 ShopAccount 예제를 통해 코드가 어떻게 개선되는지 보여줍니다.

Tell, Don’t Ask 원칙

  • 묻지 말라(Don’t Ask): 로직을 직접 수행하기 위해 객체에게 데이터를 요청하지 말라.
  • 말하라(Tell): 객체에게 무엇을 해야 할지 알려주고, 객체가 자신의 상태를 스스로 처리하도록 하라.

TDA를 따르면 **캡슐화(encapsulation)**와 **응집도(cohesion)**가 향상됩니다.

안티패턴 예시

Shop 클래스가 Account에 속해야 할 로직을 관리함으로써 캡슐화를 위반하고 있습니다.

package com.mateandgit.programming.chapter1_2.problem;

public class Shop {

    public void sell(Account account, Product product) {
        // ❌ BAD: Asking for data (getMoney)
        long price = product.getPrice();
        long mileage = account.getMoney();

        // ❌ BAD: The Shop decides if the account has enough money
        if (mileage >= price) {
            // ❌ BAD: Modifying the account's state externally
            account.setMoney(mileage - price);
            System.out.println(product.getName() + " purchased.");
        } else {
            System.out.println("Insufficient balance.");
        }
    }
}

문제점

  • 캡슐화 위반ShopAccount의 내부 상세(예: money 필드와 잔액 계산 방식)를 알고 있습니다.
  • 로직 중복 – 돈을 차감해야 하는 다른 클래스는 모두 if (balance >= price) 검사를 중복해야 합니다.
  • 유지보수 어려움 – 검증 규칙(예: “최소 잔액은 100이어야 함”)을 변경하면 Account에 접근하는 모든 클래스에서 업데이트가 필요합니다.

리팩터링된 해결책

로직을 Account 클래스로 옮깁니다. 이제 ShopAccount에 명령만 내립니다.

package com.mateandgit.programming.chapter1_2.solution;

public class Shop {

    public void sell(Account account, Product product) {
        // ✅ GOOD: Ask a question (canAfford)
        if (account.canAfford(product.getPrice())) {

            // ✅ GOOD: Tell the object to do something (withdraw)
            account.withdraw(product.getPrice());
            System.out.println(product.getName() + " purchased.");
        } else {
            System.out.println("Insufficient balance.");
        }
    }
}

Account 클래스 안에는 canAfford()withdraw()와 같이 로직을 캡슐화한 메서드가 존재합니다.

리팩터링의 장점

  • 향상된 캡슐화Shop은 인출이 어떻게 이루어지는지 알 필요가 없으며, 단지 메시지를 보냅니다.
  • 재사용 가능한 로직 – 검증 규칙(canAfford)이 Account에 집중되어 있습니다.
  • 가독성 – 코드가 “계정이 충분히 있으면 인출한다”는 문장처럼 읽힙니다.
  • 높은 응집도와 낮은 결합도 – 객체가 자신의 상태를 관리하므로 코드가 더 깔끔하고 유지보수가 쉬워집니다.

Tell, Don’t Ask 적용하기

  • 하지 말아야 할 것: getSomething() → calculate → setSomething().
  • 해야 할 것: 객체 안에 메서드를 만들고 그것에게 동작을 말해 주기.

결과: 높은 응집도, 낮은 결합도, 그리고 더 나은 캡슐화.

Back to Blog

관련 글

더 보기 »

Java에서 메서드 오버라이딩

메서드 오버라이딩이란 무엇인가? 서브클래스가 이미 부모 클래스에 정의된 메서드에 대해 구체적인 구현을 제공할 때 이를 메서드 오버라이딩이라고 한다.