跳到正文
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(原文)

安装前可以查看交给 AI 代理的指令内容。

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 반환 후에도 외부에서 수정 가능한 상태

评价

还没有评价。欢迎分享你的使用感受。

评论

无需登录即可发表。欢迎分享使用感受、提问,或说明在哪些环境下无法运行。

还没有评论。来写第一条吧。

提交后会立即公开。请勿填写邮箱、电话等个人信息。URL 最多 2 个。

0 / 1000 字

此表单受 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日 更新

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月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 的全部技能

报告此技能的问题