TDA에 대해서

Tell, Don't Ask. 객체에게 데이터를 요구(Ask) 하지 말고, 객체에게 일을 시켜라(Tell)

1. 객체의 책임을 명확히 해야 한다

TDA 원칙의 핵심은 객체가 스스로 자신의 책임을 다하도록 만드는 것입니다. 즉, 객체는 자신의 상태를 외부에서 조작하거나 판단하게 만들기보다는, 자신의 상태를 기반으로 행동할 책임을 가져야 합니다. 이렇게 하면 객체 내부의 상태 변경에 따라 외부 코드가 불필요하게 변경되는 상황을 피할 수 있습니다.

2. 캡슐화의 중요성

캡슐화는 객체의 데이터와 메서드를 보호하고 숨기는 것을 의미합니다. 객체 외부에서 객체의 속성(데이터)을 직접 접근하는 것이 아니라, 객체 스스로가 데이터를 처리하는 메서드를 제공함으로써 캡슐화가 이루어집니다. 이렇게 함으로써, 내부 구현이 변경되어도 외부 코드는 변경되지 않는 안정적인 설계를 할 수 있습니다.

3. TDA 원칙이 중요한 이유

  • 유지보수성 향상: 객체의 내부 구현이 변경되어도 그 객체를 사용하는 코드에는 변화가 없으므로 유지보수성이 크게 향상됩니다.
  • 변경에 강한 설계: 새로운 요구사항이 추가되더라도, 하나의 객체에서 그 요구사항을 반영하는 코드만 수정하면 되므로 시스템 전반의 코드를 수정할 필요가 없습니다.
  • 코드 중복 방지: 객체의 상태를 판단하는 로직이 여러 곳에서 중복되게 작성되지 않고, 객체 내부에 집중됨으로써 중복 코드를 제거할 수 있습니다.

4. TDA의 구체적인 적용 방법

예제에서처럼 User 객체의 데이터를 캡슐화하고, 사용자의 유효성을 판단하는 로직을 isValid 메서드로 처리함으로써 TDA 원칙을 적용했습니다. 여기서 중요한 것은, 외부 코드가 User 객체의 세부 사항에 의존하지 않고, 객체가 제공하는 메서드를 통해 필요한 정보를 얻는다는 점입니다.

5. 확장 가능성

TDA 원칙을 적용하면 객체의 내부 로직을 수정하거나 확장하는 데 용이합니다. 예를 들어, 만약 사용자 유효성 검사 규칙에 또 다른 조건이 추가된다면, 이를 isValid 메서드에만 반영하면 됩니다. 외부에서 유효성 검사를 호출하는 부분은 전혀 수정할 필요가 없습니다. 이런 방식은 확장에 매우 유연한 코드를 만들어줍니다.

class User {
  private email: string;
  private age: number;
  private createdAt: Date;

  constructor(email: string, age: number) {
    this.email = email;
    this.age = age;
    this.createdAt = new Date();
  }

  // 유효성 검사
  isValid(): boolean {
    return this.isAdult() && this.isGmailUser();
  }

  // 성인 여부 확인
  private isAdult(): boolean {
    return this.age >= 20;
  }

  // 이메일 검사
  private isGmailUser(): boolean {
    return this.email.endsWith('gmail.com');
  }
}

6. 메서드 추가와 리팩터링 가능성

위 예시에서 isAdult()isGmailUser()와 같은 메서드를 추가하여 로직을 좀 더 작은 단위로 분리할 수 있습니다. 이렇게 하면 각 메서드는 명확한 책임을 가지게 되고, 변경이나 추가 조건이 생길 때 더 쉽게 대응할 수 있습니다. 만약 나이 기준이 변경된다면, isAdult() 메서드만 수정하면 됩니다.

7. 비즈니스 로직을 객체로 이동

비즈니스 로직을 서비스 레이어에 두는 것이 아니라, 가능한 한 관련된 객체로 이동시킴으로써 객체가 자신과 관련된 모든 행위를 책임지도록 설계하는 것이 TDA의 핵심입니다. 이는 SRP(단일 책임 원칙)와도 일맥상통합니다.

8. 예제 코드의 수정

TDA 원칙을 따르는 방식으로 예제 코드를 재구성하면 다음과 같이 사용될 수 있습니다.

doSomething(user: User) {
  if (!user.isValid()) {
    throw new Error('Invalid user!');
  }
  // 유효한 사용자에 대해 무언가를 수행한다
}

이제 사용자 검증 로직이 User 클래스 내부에 캡슐화되었으므로, 다른 서비스에서도 동일한 로직을 중복해서 작성할 필요가 없습니다. user.isValid() 메서드를 호출하는 것만으로 모든 비즈니스 로직이 일관되게 적용됩니다.

이렇게 하면 리팩토링 할 때도 클래스의 메서드만 수정해주면 되기 때문에 효율성도 올라가겠죠?
이상 TDA에 대한 설명이었습니다.

개발자 성현