本文へ移動
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 생성 (불변성 필수)

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

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

日本語の概要は準備中です。原文の説明を表示しています。

msbaek/msbaek-claude-plugins82026年10月1日 更新

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

日本語の概要は準備中です。原文の説明を表示しています。

msbaek/msbaek-claude-plugins82026年10月1日 更新

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

日本語の概要は準備中です。原文の説明を表示しています。

msbaek/msbaek-claude-plugins82026年10月1日 更新

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

日本語の概要は準備中です。原文の説明を表示しています。

msbaek/msbaek-claude-plugins82026年10月1日 更新

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

日本語の概要は準備中です。原文の説明を表示しています。

msbaek/msbaek-claude-plugins82026年10月1日 更新

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

日本語の概要は準備中です。原文の説明を表示しています。

msbaek/msbaek-claude-plugins82026年10月1日 更新

msbaek のスキルをすべて見る

このスキルの問題を報告する