-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Labels
Description
🚫 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;
}
}🎯 결론 및 가이드라인
✅ 좋은 패턴
- 생성 시: Builder 패턴 사용
- 수정 시: 의미 있는 비즈니스 메서드 사용
- DTO: Lombok @Setter 허용
- 검증: 메서드 내부에서 비즈니스 규칙 검증
❌ 피해야 할 패턴
- 무분별한 public setter: 모든 필드에 setter
- 비즈니스 로직 없는 setter: 단순 할당만 하는 setter
- 불변 객체의 setter: 값 객체의 변경 허용
- 도메인 모델의 setter: 엔티티의 일관성 파괴
📝 실용적인 규칙
// ✅ 이렇게 사용하세요
Schedule schedule = Schedule.builder() // 생성 시
.title("새 일정")
.startDate(startDate)
.build();
schedule.updateTitle("수정된 제목"); // 수정 시
schedule.postpone(Duration.ofHours(1)); // 비즈니스 행위
schedule.makeAllDay(); // 의미 있는 변경핵심: Setter를 무조건 금지하는 것이 아니라, 의미 있고 안전한 방식으로 객체를 변경하는 것이 목표! 🎯