Skip to content

TIL -2025-06-19 [Setter를 피해야 하는 경우, Setter 대신 사용해야 하는 패턴들, Setter 사용이 허용되는 경우] #83

Description

@soheeGit

🚫 Setter를 피해야 하는 경우

1. 무분별한 Public Setter (가장 큰 문제)

// ❌ 나쁜 예시 - 무분별한 Setter
@Entity
public class Schedule {
    private String title;
    private LocalDateTime startDate;
    private LocalDateTime endDate;
    
    // ❌ 모든 필드에 대한 무차별 setter
    public void setTitle(String title) { this.title = title; }
    public void setStartDate(LocalDateTime startDate) { this.startDate = startDate; }
    public void setEndDate(LocalDateTime endDate) { this.endDate = endDate; }
}

// 🚨 문제점: 비즈니스 규칙 무시
schedule.setStartDate(startDate);
schedule.setEndDate(endDate);  // 시작일보다 이른 종료일 설정 가능!

2. 객체 불변성 파괴

// ❌ 불변 객체가 되어야 하는 경우
public class Money {
    private BigDecimal amount;
    
    // ❌ Money는 불변이어야 하는데 setter가 있으면 문제
    public void setAmount(BigDecimal amount) {
        this.amount = amount;  // 돈 객체가 변경됨!
    }
}

3. 데이터 일관성 파괴

// ❌ 연관된 필드들을 따로 설정
schedule.setStartDate(newStartDate);
// 다른 코드가 실행되다가...
schedule.setEndDate(newEndDate);  // 중간에 불일치 상태 발생 가능

✅ Setter 대신 사용해야 하는 패턴들

1. 생성자 + Builder (객체 생성 시)

// ✅ 좋은 예시 - Builder 패턴
@Entity
public class Schedule {
    private String title;
    private LocalDateTime startDate;
    private LocalDateTime endDate;
    
    @Builder
    public Schedule(String title, LocalDateTime startDate, LocalDateTime endDate) {
        validateDateRange(startDate, endDate);  // 생성 시 검증
        this.title = title;
        this.startDate = startDate;
        this.endDate = endDate;
    }
    
    private void validateDateRange(LocalDateTime start, LocalDateTime end) {
        if (start.isAfter(end)) {
            throw new IllegalArgumentException("시작일이 종료일보다 늦을 수 없습니다.");
        }
    }
}

2. 의미 있는 메서드 (비즈니스 로직 포함)

// ✅ 좋은 예시 - 비즈니스 의미가 있는 메서드
@Entity
public class Schedule {
    // ❌ public void setTitle(String title)
    // ✅ 의미 있는 메서드명
    public void updateTitle(String newTitle) {
        validateTitle(newTitle);
        this.title = newTitle;
    }
    
    // ❌ public void setStartDate(), setEndDate() 따로
    // ✅ 연관된 데이터를 함께 처리
    public void updateDateTime(LocalDateTime newStartDate, LocalDateTime newEndDate) {
        validateDateRange(newStartDate, newEndDate);
        this.startDate = newStartDate;
        this.endDate = newEndDate;
    }
    
    // ✅ 비즈니스 행위를 나타내는 메서드
    public void postpone(Duration duration) {
        this.startDate = this.startDate.plus(duration);
        this.endDate = this.endDate.plus(duration);
    }
    
    private void validateTitle(String title) {
        if (title == null || title.trim().isEmpty()) {
            throw new IllegalArgumentException("제목은 필수입니다.");
        }
    }
}

🎯 Setter 사용이 허용되는 경우

1. JPA 엔티티의 Package-Private Setter

// ✅ JPA/Hibernate를 위한 제한적 setter
@Entity
public class Schedule {
    private String title;
    
    // JPA 프록시 생성을 위한 기본 생성자
    protected Schedule() {}
    
    // ✅ JPA를 위한 package-private setter (외부 접근 불가)
    void setTitle(String title) {  // package-private
        this.title = title;
    }
    
    // ✅ 비즈니스 로직이 있는 public 메서드
    public void updateTitle(String newTitle) {
        validateTitle(newTitle);
        setTitle(newTitle);  // 내부에서만 호출
    }
}

2. DTO/Request 객체의 Setter

// ✅ 데이터 전송용 객체는 setter 허용
@Getter @Setter  // Lombok으로 생성
public class UpdateScheduleRequest {
    private String title;
    private LocalDateTime startDate;
    private LocalDateTime endDate;
    
    // DTO는 단순 데이터 컨테이너이므로 setter 허용
    // 비즈니스 로직은 Service나 Entity에서 처리
}

3. 테스트에서의 임시 Setter

// ✅ 테스트에서만 사용하는 setter
@Entity
public class Schedule {
    // ... 비즈니스 메서드들
    
    // ✅ 테스트 전용 (패키지 접근 제한)
    @VisibleForTesting
    void setCreatedAtForTest(LocalDateTime createdAt) {
        this.createdAt = createdAt;
    }
}

🎯 결론 및 가이드라인

✅ 좋은 패턴

  1. 생성 시: Builder 패턴 사용
  2. 수정 시: 의미 있는 비즈니스 메서드 사용
  3. DTO: Lombok @Setter 허용
  4. 검증: 메서드 내부에서 비즈니스 규칙 검증

❌ 피해야 할 패턴

  1. 무분별한 public setter: 모든 필드에 setter
  2. 비즈니스 로직 없는 setter: 단순 할당만 하는 setter
  3. 불변 객체의 setter: 값 객체의 변경 허용
  4. 도메인 모델의 setter: 엔티티의 일관성 파괴

📝 실용적인 규칙

// ✅ 이렇게 사용하세요
Schedule schedule = Schedule.builder()     // 생성 시
        .title("새 일정")
        .startDate(startDate)
        .build();

schedule.updateTitle("수정된 제목");        // 수정 시
schedule.postpone(Duration.ofHours(1));   // 비즈니스 행위
schedule.makeAllDay();                    // 의미 있는 변경

핵심: Setter를 무조건 금지하는 것이 아니라, 의미 있고 안전한 방식으로 객체를 변경하는 것이 목표! 🎯

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions