"복잡한 구조는 단순한 원칙을 지켰을 때 저절로 생겨난다." — 창발성(Emergence)
들어가며
Clean Code의 핵심 원칙 중 하나인 "창발적 설계(Emergent Design)"는 단순한 규칙들을 철저히 지킴으로써 전체 시스템이 자연스럽게 잘 설계된 구조로 발전할 수 있음을 말합니다. 이번 글에서는 제가 실제로 타임딜 할인 시스템을 설계하고 리팩토링하면서 이 원칙을 어떻게 녹여냈는지 공유해 보려 합니다.
창발성이란?
창발성(Emergence)은 각 구성 요소가 단순하지만, 그들이 서로 상호작용하면서 전체적으로 예기치 않은 복잡하고 유기적인 결과를 만들어내는 현상을 말합니다.
예를 들어, 단일 뉴런은 생각을 못하지만 수십억 개의 뉴런이 상호작용하면 의식이 생기듯, 작은 설계 규칙을 지키다 보면 좋은 전체 시스템 구조가 자연스럽게 생겨납니다.
💸 내가 설계한 할인 정책 구조는?
- DiscountPolicy 엔티티는 할인 타입, 금액/퍼센트, 적용 기간, 최소 주문 금액 등을 통합 관리합니다.
- DiscountStrategy 인터페이스는 다양한 할인 전략(Coupon, Membership 등)을 추상화합니다.
- 각 전략은 컴포넌트로 등록되어 DI 환경에서 쉽게 주입되고 확장 가능합니다.
- 모든 할인 정책은 적용 조건(ex_지금 쿠폰 유효기간 안에 있는지)을 먼저 검증한 뒤, 적절한 방식으로 할인 금액을 계산합니다.
- 할인 방식이 늘어나도 OCP(Open/Closed Principle)를 지키며 유연하게 확장할 수 있습니다. (전략패턴 덕분에!)
적용한 창발적 설계 4원칙
1. 모든 테스트를 실행하라
할인 정책 로직은 단순하지만 비즈니스 예외 조건이 많습니다. 저는 다음 테스트를 먼저 만들었습니다:
- 정률 할인 정책이 정상 동작하는가?
- 최소 주문 금액 미만일 경우 할인 금액이 0인가?
- 비활성 정책이 적용되지 않는가?
테스트 코드를 먼저 작성함으로써 SRP를 준수한 작은 유틸 메서드로 분리하게 되었고, 그 결과 테스트 가능한 구조가 만들어졌습니다.
2. 중복을 제거하라
처음에는 applyDiscount 메서드 안에 유효성 검사와 할인 계산이 섞여 있었습니다. 다음과 같이 역할을 분리하면서 중복도 제거했습니다:
private boolean isValidPolicy(...)
private int calculateDiscountAmount(...)
유사 로직을 다른 할인 전략 클래스에서도 재사용할 수 있게 하면서 응집도는 높이고 중복은 줄였습니다.
3. 의도를 표현하라
- DiscountType, DiscountStrategy 같은 명칭은 각 역할을 분명히 보여줍니다.
- applyDiscount, deactivate, activate 같은 메서드 이름은 동작을 명확히 전달합니다.
- 할인 정책 생성 시 팩토리 메서드를 사용해 의도를 더욱 명확히 했습니다:
// DiscountPolicy.java
public static DiscountPolicy createPercentageCoupon(...)
4. 클래스와 메서드 수를 최소화하라
처음에는 할인 방식마다 클래스를 무작정 나누다가 오히려 관리가 어려워졌습니다. 이후 다음과 같이 조정했습니다:
- DiscountPolicy는 정책 데이터에만 집중
- 할인 계산은 DiscountStrategy 인터페이스와 구현체에서 처리
그 결과 관심사가 분리되었고 클래스 수는 적지만 기능은 명확히 분리된 구조가 완성되었습니다.
적용 예시: CouponDiscountStrategy 리팩토링 전후
리팩토링 전:
if (!policy.isActive() || originalPrice < policy.getMinPrice()) return 0;
if (policy.getPercentage() != null) { ... } else if (policy.getAmount() != null) { ... }
리팩토링 후:
if (!isValidPolicy(policy, originalPrice)) return 0;
int discountAmount = calculateDiscountAmount(...);
return Math.min(discountAmount, originalPrice);
- 역할을 나눔으로써 가독성, 재사용성, 테스트 용이성이 모두 향상
마무리하며
읽고 깨달은것은, 창발성은 거창하거나 추상적인 개념이 아닙니다. 테스트를 먼저 짜고, 중복을 제거하고, 이름을 명확히 짓고, 적절한 크기로 클래스를 관리하는 것—이런 단순한 행동들이 쌓일 때 시스템은 자연스럽게 견고하고 깔끔해집니다.
이번 작업을 통해, '좋은 설계를 하겠다'는 막연한 다짐 대신, '작은 원칙을 반복적으로 실천하는 개발자'가 되는 것이 더 중요한 목표라는 걸 다시금 깨달았습니다.
'개발프로젝트' 카테고리의 다른 글
[Clean Code] 복잡한 조건문은 어떻게 사라졌을까?— 클린 코드의 점진적 개선 원칙으로 할인 정책 리팩터링하기 (2) | 2025.06.16 |
---|---|
[CleanCode] 실제 프로젝트에서 Clean Code 원칙을 적용한 "좋은 경계" (0) | 2025.06.02 |
[CleanCode] 실제 프로젝트에서 Clean Code 원칙을 적용한 예외 처리 리팩토링 경험기 (1) | 2025.05.26 |
[개발프로젝트] 스프링 API 예외 처리, 이제 try-catch는 그만! (1) | 2025.05.26 |
[CleanCode] 형식 맞추기 – 형식 규칙에 대한 깊은 고찰,클린코드를 읽고... (1) | 2025.05.22 |