本文へ移動
cccskills
無料GitHub で公開

encapsulate-collection

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

インストール方法を見る

含まれるファイル(1)

  • SKILL.md8.5 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

Encapsulate Collection

GOAL

컬렉션을 반환하는 getter가 내부 상태를 직접 노출하는 것을 방지하여:

  • 불변성(Immutability) 보장
  • 캡슐화 원칙 준수
  • 의도하지 않은 외부 수정 방지
  • First Class Collection의 첫 단계

CONSTRAINTS

  • 계열: System-wide — 후보 제시 후 승인 받고 적용 (../../references/refactoring-procedure.md §0·§3-B)
  • 동작 변경 금지: 구조 개선만 수행 (기능 변경 없음)
  • 테스트 수정 금지: 구조 변경이 테스트를 실패시키면 되돌리기
  • 사용자 확인 필수: 자동 적용 금지
  • 명시적 git add: git add -A 금지, 변경된 파일만 명시

적용 패턴

Before: 내부 컬렉션 직접 노출

class Course {
    private List<Student> students = new ArrayList<>();
    
    public List<Student> getStudents() {
        return students;  // 위험: 외부에서 수정 가능
    }
}

// 호출부: 캡슐화 위반
course.getStudents().add(student);  // 직접 수정
course.getStudents().clear();       // 위험한 조작

After: Unmodifiable 반환 + 명시적 메서드

class Course {
    private List<Student> students = new ArrayList<>();
    
    public List<Student> getStudents() {
        return Collections.unmodifiableList(students);
    }
    
    public void addStudent(Student student) {
        students.add(student);
    }
    
    public void removeStudent(Student student) {
        students.remove(student);
    }
}

// 호출부: 명시적 메서드 사용
course.addStudent(student);
course.removeStudent(student);

추가 예시: Set 컬렉션

class Team {
    private Set<Member> members = new HashSet<>();
    
    public Set<Member> getMembers() {
        return Collections.unmodifiableSet(members);
    }
    
    public void addMember(Member member) {
        members.add(member);
    }
    
    public void removeMember(Member member) {
        members.remove(member);
    }
    
    public boolean hasMember(Member member) {
        return members.contains(member);
    }
}

추가 예시: 복사본 반환 (방어적 복사)

class Order {
    private List<OrderItem> items = new ArrayList<>();
    
    // Option 1: Unmodifiable (읽기 전용)
    public List<OrderItem> getItems() {
        return Collections.unmodifiableList(items);
    }
    
    // Option 2: 방어적 복사 (완전 격리)
    public List<OrderItem> getItemsCopy() {
        return new ArrayList<>(items);
    }
}

생성자로 받은 컬렉션도 복사한다

getter는 컬렉션이 반환되는 지점이고 생성자는 입력되는 지점이다. 반환 지점만 보호하면 캡슐화가 불완전하다 — 호출자가 넘긴 리스트를 참조로 보관하면 호출자가 나중에 그 리스트를 변경할 수 있고, 리스트의 성질(불변·가변)이 그대로 유입된다.

class Cart {
    private List<CartLine> lines;

    public Cart(List<CartLine> lines) {
        this.lines = lines;                    // 참조를 그대로 보관
    }
}

class Cart {
    private final List<CartLine> lines;

    public Cart(List<CartLine> lines) {
        this.lines = new ArrayList<>(lines);   // 들어올 때 복사
    }
}

JPA 사례: List.of(...)로 만든 불변 리스트를 그대로 보관한 엔티티를 재저장하면 Hibernate가 병합 과정에서 clear()를 호출해 UnsupportedOperationException이 발생한다. 생성자 복사로 해결한다.

private final은 재할당을 차단하지만 컬렉션 내용 변경은 방지하지 못한다. 생성자 복사(입력 지점) + getter 방어적 복사(반환 지점, 위 예시) 두 지점을 모두 보호해야 캡슐화가 완성된다.

First Class Collection으로 발전

이 기법 적용 후 /first-class-collection 스킬로 전용 클래스 추출 가능:

// Encapsulate Collection 적용 후
class Course {
    private Students students = new Students();  // First Class Collection
    
    public Students getStudents() { return students; }
}

class Students {
    private List<Student> students = new ArrayList<>();
    
    public void add(Student student) { students.add(student); }
    public void remove(Student student) { students.remove(student); }
    public int size() { return students.size(); }
    public boolean contains(Student student) { return students.contains(student); }
}

적용 기준

적용 대상

  • public List<T> getXxx() 패턴
  • 내부 컬렉션을 그대로 반환하는 getter
  • 외부에서 getXxx().add() 호출하는 코드
  • 도메인 객체의 컬렉션 필드
  • 생성자·팩토리가 받은 컬렉션을 그대로 필드에 보관하는 코드 (위 "반대 방향" 참조)

적용 제외

  • DTO/VO: 단순 데이터 전송 객체 (불변성 불필요)
  • Builder 패턴: 빌더 내부 컬렉션 (완성 전까지 가변)
  • 읽기 전용 래퍼: 이미 Unmodifiable 반환 중
  • 불변 컬렉션: List.of(), Set.of() 사용 중

주의사항

  • 호출부에서 getXxx().add() 패턴 모두 변경 필요
  • 테스트 코드도 영향받음 (명시적 메서드 사용으로 전환)
  • Unmodifiable vs 방어적 복사 선택:
    • 읽기 전용: Collections.unmodifiableList() (성능 우수)
    • 완전 격리: new ArrayList<>(...) (안전성 우수)

OUTPUT FORMAT

실행 절차

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

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

  • 컬렉션 타입 getter 탐지 (List<T>, Set<T>, Map<K,V>)
  • 내부 필드를 직접 반환하는지 확인
  • 호출부에서 getXxx().add() 패턴 검색
  • 각 후보에 대해:
    • 파일명 및 라인 번호
    • Before/After 코드 미리보기
    • 영향받는 호출부 수

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

발견된 후보 2개:

1. Course.java:15
   getter: public List<Student> getStudents()
   → Collections.unmodifiableList() 반환
   → addStudent(), removeStudent() 메서드 추가
   영향받는 호출부: 5곳 (course.getStudents().add() 패턴)

2. Team.java:20
   getter: public Set<Member> getMembers()
   → Collections.unmodifiableSet() 반환
   → addMember(), removeMember() 메서드 추가
   영향받는 호출부: 3곳

적용하시겠습니까? (yes / no / 수정)
Unmodifiable vs 방어적 복사 선택: (unmodifiable / copy)

리팩토링 적용 (공통 절차 4단계)

  • getter를 Unmodifiable 또는 방어적 복사로 변경
  • add(), remove() 메서드 추가
  • 모든 호출부 업데이트:
    • getXxx().add(item) → addXxx(item)
    • getXxx().remove(item) → removeXxx(item)
  • (선택) 추가 메서드 (contains(), size(), isEmpty())

커밋 메시지: refactor: encapsulate collection in <클래스명> (공통 절차 6단계)

출력 예시

완료: Encapsulate Collection

변경 내용:
- Course.java:15
  getStudents() → Collections.unmodifiableList() 반환
  + addStudent(Student)
  + removeStudent(Student)
  
- Team.java:20
  getMembers() → Collections.unmodifiableSet() 반환
  + addMember(Member)
  + removeMember(Member)

영향받는 호출부: 8곳 자동 업데이트

테스트: 모든 테스트 통과 (23 tests)
커밋: refactor: encapsulate collection in Course, Team

제안: Course.students는 First Class Collection으로 추출 가능합니다.
   /first-class-collection 스킬을 고려해보세요.

FAILURE CONDITIONS

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

  • DTO/VO 클래스에 불필요하게 적용함
  • 호출부 업데이트 누락 (getXxx().add() 패턴이 남음)
  • Unmodifiable 반환 후에도 외부에서 수정 가능한 상태

レビュー

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

同じリポジトリのスキル

概要と使いどころ

동일한 결과를 내는 여러 조건문(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日 更新

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

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