본문으로 건너뛰기
cccskills
무료GitHub에 공개

discover-value-object

Primitive Obsession 제거 — 검증·연산이 따라다니는 primitive 필드(금액+통화, 이메일 문자열 등)를 도메인 개념을 담은 Value Object로 치환. "값 객체 도입", "primitive obsession", "Money 클래스로", "/discover-value-object" 요청 시 사용. 단, 함께 전달되는 파라미터 묶음을 객체로 바꾸는 것은 /introduce-parameter-object, 컬렉션을 감싸는 것은 /first-class-collection이 적합. /discover-value-object [commit-ref]로 호출.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

설치 방법 보기

포함된 파일(1)

  • SKILL.md8.9 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

Discover Value Object Skill

Primitive Obsession을 제거하여 도메인 개념을 명시적으로 표현하는 Value Object 발견.

GOAL

  • 성공 = Primitive 타입이 Value Object로 치환되어 커밋 완료됨
  • Primitive 타입(int, String 등)에 도메인 로직이 산재함
  • 반복되는 검증/변환/포맷팅 로직이 식별됨
  • 사용자 확인 후 Value Object 추출
  • 모든 테스트 통과

CONSTRAINTS

  • 계열: System-wide — 후보 제시 후 승인 받고 적용 (../../references/refactoring-procedure.md §0·§3-B)

Hard Rules

  • 동작 변경 금지 — 구조 개선만 수행
  • 테스트 수정 금지 — 구조 변경이 테스트를 실패시키면 되돌리기
  • 사용자 확인 없이 리팩토링 금지 — 모든 후보는 사용자 승인 후 실행
  • 커밋 단위 — 1파일 x 1기법 = 1커밋 (논리적으로 연결된 파일은 함께)
  • git add -A 금지 — 변경된 파일만 명시적으로 추가

적용 패턴

Discover Value Object 리팩토링 단계:

  1. Primitive Obsession 패턴 식별

    • Entity 필드가 primitive 타입
    • 해당 primitive에 대한 검증/변환/포맷팅 로직 산재
    • 동일한 검증/변환 로직 반복
  2. Value Object 클래스 생성

    • 도메인 개념을 명시하는 이름 부여
    • Immutable record 또는 class 사용
    • 생성자에서 검증 수행
  3. 관련 로직 이동

    • 검증 로직을 생성자로 이동
    • 변환/포맷팅 로직을 메서드로 이동
    • 비즈니스 규칙을 메서드로 캡슐화
  4. 사용처 변경

    • Entity 필드 타입을 Value Object로 변경
    • 산재된 로직을 Value Object 메서드 호출로 치환

Before/After 예시

// Before: Primitive Obsession
public class Order {
    private int amount;        // primitive 타입
    private String currency;   // primitive 타입
    
    public Order(int amount, String currency) {
        // 검증 로직 산재
        if (amount < 0) {
            throw new IllegalArgumentException("Amount cannot be negative");
        }
        if (currency == null || currency.isEmpty()) {
            throw new IllegalArgumentException("Currency is required");
        }
        this.amount = amount;
        this.currency = currency;
    }
    
    // 비즈니스 로직 산재
    public boolean isExpensive() {
        return amount > 1000;
    }
    
    public String formattedPrice() {
        return currency + " " + amount;
    }
    
    public double convertToUSD(double exchangeRate) {
        if ("USD".equals(currency)) {
            return amount;
        }
        return amount * exchangeRate;
    }
}

// After: Value Object 추출
record Money(int amount, String currency) {
    // 생성자에서 검증
    public Money {
        if (amount < 0) {
            throw new IllegalArgumentException("Amount cannot be negative");
        }
        if (currency == null || currency.isEmpty()) {
            throw new IllegalArgumentException("Currency is required");
        }
    }
    
    // 비즈니스 로직을 Value Object로 이동
    public boolean isOver(int threshold) {
        return amount > threshold;
    }
    
    public String formatted() {
        return currency + " " + amount;
    }
    
    public Money convertTo(String targetCurrency, double exchangeRate) {
        if (currency.equals(targetCurrency)) {
            return this;
        }
        return new Money((int) (amount * exchangeRate), targetCurrency);
    }
    
    public Money add(Money other) {
        if (!currency.equals(other.currency)) {
            throw new IllegalArgumentException("Cannot add different currencies");
        }
        return new Money(amount + other.amount, currency);
    }
}

public class Order {
    private Money price;  // Value Object
    
    public Order(Money price) {
        this.price = price;
    }
    
    // 비즈니스 로직이 간결해짐
    public boolean isExpensive() {
        return price.isOver(1000);
    }
    
    public String formattedPrice() {
        return price.formatted();
    }
}

적용 기준

Discover Value Object를 적용해야 하는 경우:

  1. Entity 필드: primitive 타입 필드가 도메인 개념을 나타냄
  2. 로직 산재: 해당 primitive에 대한 검증/변환/포맷팅 로직이 여러 곳에 분산
  3. 반복 검증: 동일한 검증 로직이 여러 곳에서 반복
  4. 비즈니스 규칙: primitive 값에 대한 비즈니스 규칙이 존재
  5. 함께 사용: 여러 primitive가 항상 함께 사용됨 (예: amount + currency)
  6. 외부 라이브러리/API 타입 직접 노출: 3rd-party 타입(BigDecimal, LocalDate, UUID, JDBC ResultSet, 외부 API 응답 DTO 등)이 도메인 개념을 그대로 대변
    • 해당 타입에 대한 검증/변환/계산 로직이 호출부에 산재
    • 수정/상속이 불가능해 Wrapper(composition) 형태의 Local Extension으로 감쌈 (Fowler: Introduce Local Extension)
    • 일회성 편의 메서드만 필요하면 Value Object 대신 Introduce Foreign Method를 검토

발견 후보:

  • Entity의 필드 (예: amount, price, email, phoneNumber)
  • 반복되는 파라미터 (Introduce Parameter Object와 연계)
  • 기능이 많은 primitive (검증/변환/포맷팅 로직 다수)
  • 외부 라이브러리 타입 (예: BigDecimal → Money, LocalDate → BusinessDate, String UUID → OrderId)
  • 외부 API/프로토콜 타입 (예: PaymentApiResponse → PaymentResult, HttpResponse<String> → AuthToken)

OUTPUT FORMAT

실행 절차

공통 골격(대상 파일 수집 → 후보 제시(계열별 승인 규칙) → 적용 → 테스트 → 커밋/되돌리기, 브랜치·PR이 필요한 조건)은 이 스킬 디렉터리 기준 ../../references/refactoring-procedure.md가 정본이다. 아래는 이 기법에 고유한 부분만 규정한다.

Value Object 후보 식별 (공통 절차 2단계)

대상 파일에서 다음 패턴을 찾는다:

  • primitive 타입 필드가 도메인 개념을 나타냄
  • 해당 필드에 대한 검증 로직이 2곳 이상
  • 해당 필드에 대한 변환/포맷팅 메서드가 존재
  • 여러 primitive가 항상 함께 사용됨
  • 외부 라이브러리 타입(BigDecimal, LocalDate, UUID 등)이 필드/파라미터로 직접 노출되고, 관련 계산/검증 로직이 호출부에 산재
  • 외부 API 응답/DTO 타입이 도메인 경계를 넘어 내부 로직에서 그대로 사용됨

후보 제시 예시 (공통 절차 3단계)

후보를 하나씩 제시하고 사용자 확인:

## 리팩토링 후보 1: Discover Value Object

**파일**: Order.java
**대상**: int amount, String currency

**현재 사용**:
- 필드: private int amount; private String currency;
- 검증: 생성자, setAmount() 등 3곳에서 검증 반복
- 로직: isExpensive(), formattedPrice(), convertToUSD() 등

**현재 코드**:
[관련 코드 블록]

**제안 변경**:
1. Money record 생성 (amount, currency)
2. 검증 로직을 Money 생성자로 이동
3. 비즈니스 로직을 Money 메서드로 이동
   - isOver(threshold)
   - formatted()
   - convertTo(currency, rate)
   - add(Money)
4. Order 필드를 Money 타입으로 변경

**적용할까요?** (yes / no / 수정 요청)
  • 사용자가 yes → 실행 목록에 추가
  • 사용자가 no → 스킵
  • 사용자가 수정 요청 → 요청 반영 후 재제시

모든 후보 확인 후 최종 실행 목록을 보여주고 진행 여부 확인.

Value Object 추출 실행 (공통 절차 4단계)

확정된 리팩토링을 하나씩 수행:

  1. Value Object record 생성
  2. 검증 로직을 생성자로 이동
  3. 비즈니스 로직을 메서드로 이동
  4. Entity 필드 타입 변경
  5. 사용처 업데이트

커밋 메시지 형식:

refactor: discover value object [객체명] in [클래스명]

결과 보고

사용자에게 보고:

  • 적용된 Value Object 목록

FAILURE CONDITIONS

공통 실패 조건(계열별 승인 규칙 위반, 테스트 실패 방치, 테스트 수정, 커밋 단위, git add -A, heredoc 한글 메시지)은 ../../references/refactoring-procedure.md에 있다. 아래는 이 기법에 고유한 것만.

  • 관련 로직 없는 모든 primitive를 객체로 감쌈 (불필요한 복잡도)
  • 단순 getter/setter만 있는 Value Object 생성 (의미 없음)
  • mutable Value Object 생성 (불변성 필수)

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

동일한 결과를 내는 여러 조건문(OR 나열·중첩 AND)을 하나로 통합하고 의미 있는 boolean 메서드로 추출. "조건문 합쳐", "같은 결과 반환하는 if 정리", "중첩 if 평탄화", "/consolidate-conditional" 요청 시 사용. 단, 여러 메서드에 흩어진 동일 조건을 호출자 쪽으로 올리는 것은 /lift-up-conditional, 복잡한 조건식·분기를 메서드로 쪼개는 것은 /decompose-conditional이 적합. /consolidate-conditional [commit-ref]로 호출.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

msbaek/msbaek-claude-plugins82026년 10월 16일 업데이트

기능의 external behavior를 Cucumber 인수 테스트(주 검증층)로 구축 — .feature 실행으로 문서↔코드 드리프트를 구조적으로 차단, Four Layer(Steps→Protocol Driver→SUT), 태그 기반 가역 제외, 기존 JUnit 인수 테스트 이관. "인수 테스트 도입", "Gherkin을 실행 가능하게", "cucumber 셋업" 요청 시 사용. /cucumber-acceptance로 호출.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

msbaek/msbaek-claude-plugins82026년 10월 16일 업데이트

복잡한 if/then/else의 조건식과 각 분기를 의미 있는 메서드로 추출하여 가독성 향상. "조건문 분해", "if 가독성", "복잡한 조건식에 이름 붙여", "/decompose-conditional" 요청 시 사용. 단, 같은 결과를 내는 조건문들을 하나로 합치는 것은 /consolidate-conditional, 타입별 분기를 클래스로 바꾸는 것은 /replace-conditional-with-poly가 적합. /decompose-conditional [commit-ref]로 호출.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

msbaek/msbaek-claude-plugins82026년 10월 16일 업데이트

컬렉션 getter가 내부 List/Set을 직접 노출하는 것을 방지 — unmodifiable 반환 + add/remove 메서드 제공. "컬렉션 캡슐화", "getter가 List 그대로 노출", "unmodifiable로", "/encapsulate-collection" 요청 시 사용. 단, 컬렉션과 관련 로직을 전용 클래스로 뽑는 것은 /first-class-collection이 적합. /encapsulate-collection [commit-ref]로 호출.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

msbaek/msbaek-claude-plugins82026년 10월 16일 업데이트

암묵적 의존성(전역 변수·클래스 필드·싱글턴 접근)을 명시적 파라미터로 전환하여 메서드 투명성 향상. "숨은 의존성 드러내", "필드 대신 파라미터로", "전역 참조 제거", "/explicit-parameters" 요청 시 사용. 단, 파라미터가 많아져 묶어야 하면 /introduce-parameter-object, I/O와 계산 분리는 /segregate-functional-core가 적합. /explicit-parameters [commit-ref]로 호출.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

msbaek/msbaek-claude-plugins82026년 10월 16일 업데이트

지역 변수가 서로 얽혀 Extract Method가 불가능한 거대 메서드를 별도 클래스(Method Object)로 추출. "긴 메서드를 클래스로", "지역 변수 때문에 추출 불가", "method object", "/extract-method-object" 요청 시 사용. 단, 지역 변수 얽힘 없이 잘라낼 수 있는 긴 메서드는 /intent-revealing-names(grouping→extract)가 적합. /extract-method-object [commit-ref]로 호출.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

msbaek/msbaek-claude-plugins82026년 10월 16일 업데이트

msbaek의 스킬 모두 보기

이 스킬의 문제 신고