Communitygithub.com

랜딩/깊은 모듈 설계

깊은 모듈(deep module)을 설계하기 위한 공유 어휘를 제공합니다. 사용자가 모듈의 인터페이스를 설계하거나 개선하고, 심화 기회를 찾고, 경계(seam) 위치를 결정하고, 코드를 테스트 가능하거나 AI가 탐색하기 쉽게 만들고자 할 때, 또는 다른 스킬이 깊은 모듈 어휘를 필요로 할 때 사용하세요.

깊은 모듈 설계란 무엇인가요?

이 스킬은 '깊은 모듈(deep module)' 설계 원칙에 기반한 공유 어휘와 실용적인 지침을 제공합니다. 핵심 용어(모듈, 인터페이스, 구현, 깊이, 경계, 어댑터, 레버리지, 지역성)를 정확히 정의하여 팀 내 일관된 커뮤니케이션을 가능하게 합니다. 또한 인터페이스 설계 시 깊이를 평가하는 '삭제 테스트(deletion test)'와 같은 원칙과, 테스트 용이성을 높이기 위한 구체적인 코드 예제(TypeScript)를 포함합니다. Claude Code, Cursor, Codex 등 다양한 AI 에이전트 플랫폼에서 활용할 수 있으며, SKILL.md를 통해 전체 용어집과 설계 원칙이 문서화되어 있어 개발자가 인터페이스와 구현을 명확히 분리하고 깊은 모듈을 설계하는 데 도움을 줍니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Castle-Solutions/landing/tree/HEAD/.agents/skills/codebase-design

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

코드베이스 설계

깊은 모듈(deep module) 을 설계하세요: 작은 인터페이스 뒤에 많은 동작을 숨기고, 깔끔한 경계(seam)에 배치하며, 해당 인터페이스를 통해 테스트 가능하게 만듭니다. 코드를 설계하거나 재구성할 때마다 이 언어와 원칙을 사용하세요. 목표는 호출자에게는 레버리지(leverage), 유지보수자에게는 지역성(locality), 모두에게는 테스트 용이성(testability)입니다.

용어집

다음 용어를 정확히 사용하세요: '컴포넌트', '서비스', 'API', '경계(boundary)' 같은 단어로 대체하지 마세요. 일관된 언어가 전부입니다.

모듈(Module): 인터페이스와 구현을 모두 가진 모든 것. 의도적으로 규모에 무관합니다: 함수, 클래스, 패키지, 계층을 가로지르는 조각 등. 피하세요: 유닛, 컴포넌트, 서비스.

인터페이스(Interface): 호출자가 모듈을 올바르게 사용하기 위해 알아야 할 모든 것: 타입 시그니처뿐만 아니라 불변 조건, 순서 제약, 오류 모드, 필요한 구성, 성능 특성까지 포함합니다. 피하세요: API, 시그니처(너무 좁아서 타입 수준의 표면만 가리킵니다).

구현(Implementation): 모듈 내부, 코드 본문. 어댑터(Adapter) 와 구별됩니다: 어떤 것은 큰 구현을 가진 작은 어댑터(Postgres 저장소)일 수도 있고, 작은 구현을 가진 큰 어댑터(인메모리 페이크)일 수도 있습니다. 주제가 경계(seam)일 때는 '어댑터'를, 그 외에는 '구현'을 사용하세요.

깊이(Depth): 인터페이스에서의 레버리지. 호출자(또는 테스트)가 학습해야 하는 인터페이스 단위당 발휘할 수 있는 동작의 양. 모듈이 깊다(deep) 는 것은 많은 양의 동작이 작은 인터페이스 뒤에 있을 때, 얕다(shallow) 는 것은 인터페이스가 구현만큼 복잡할 때입니다.

경계(Seam) (마이클 훼더스, Michael Feathers): 해당 위치에서 편집하지 않고도 동작을 변경할 수 있는 곳; 모듈의 인터페이스가 존재하는 위치. 경계를 어디에 둘지는 그 자체로 설계 결정이며, 그 뒤에 무엇이 올지와는 별개입니다. 피하세요: 경계(boundary, DDD의 bounded context와 중복됨).

어댑터(Adapter): 경계에서 인터페이스를 충족하는 구체적인 것. 역할(어떤 슬롯을 채우는지)을 설명하며, 실체(내부가 무엇인지)는 아닙니다.

레버리지(Leverage): 호출자가 깊이에서 얻는 것. 학습한 인터페이스 단위당 더 많은 능력. 하나의 구현이 N개의 호출 지점과 M개의 테스트에 걸쳐 보답합니다.

지역성(Locality): 유지보수자가 깊이에서 얻는 것. 변경, 버그, 지식, 검증이 호출자 전체에 퍼지지 않고 한 곳에 집중됩니다. 한 번 수정하면 모든 곳에서 수정됩니다.

깊은 모듈 vs 얕은 모듈

깊은 모듈 = 작은 인터페이스 + 많은 구현:

┌─────────────────────┐
│   작은 인터페이스    │  ← 적은 메서드, 단순한 매개변수
├─────────────────────┤
│                     │
│  깊은 구현          │  ← 숨겨진 복잡한 로직
│                     │
└─────────────────────┘

얕은 모듈 = 큰 인터페이스 + 적은 구현 (피하세요):

┌─────────────────────────────────┐
│       큰 인터페이스             │  ← 많은 메서드, 복잡한 매개변수
├─────────────────────────────────┤
│  얇은 구현                     │  ← 그냥 통과만 시킴
└─────────────────────────────────┘

인터페이스를 설계할 때 물어보세요:

  • 메서드 수를 줄일 수 있나요?
  • 매개변수를 단순화할 수 있나요?
  • 더 많은 복잡성을 내부에 숨길 수 있나요?

원칙

  • 깊이는 인터페이스의 속성, 구현의 속성이 아닙니다. 깊은 모듈은 내부적으로 작고, 모킹 가능하며, 교체 가능한 부품들로 구성될 수 있습니다; 그 부품들은 단지 인터페이스의 일부가 아닐 뿐입니다. 모듈은 내부 경계(internal seams) (구현에 비공개이며, 자체 테스트에서 사용됨)와 인터페이스의 외부 경계(external seam) 를 모두 가질 수 있습니다.
  • 삭제 테스트(deletion test). 모듈을 삭제한다고 상상해보세요. 복잡성이 사라지면 통과(패스스루)였던 것입니다. 복잡성이 N명의 호출자에게 다시 나타나면 제 역할을 하고 있었던 것입니다.
  • 인터페이스는 테스트 표면입니다. 호출자와 테스트는 같은 경계를 넘습니다. 인터페이스 너머를 테스트하고 싶다면, 모듈의 모양이 잘못된 것일 가능성이 높습니다.
  • 어댑터 하나는 가상의 경계를 의미합니다. 어댑터 두 개는 진짜 경계를 의미합니다. 실제로 어떤 것이 경계를 가로질러 변하지 않는다면 경계를 도입하지 마세요.

테스트 용이성을 위한 설계

좋은 인터페이스는 테스트를 자연스럽게 만듭니다:

  1. 의존성을 받아들이고, 생성하지 마세요.

    // 테스트 가능
    function processOrder(order, paymentGateway) {}
    
    // 테스트 어려움
    function processOrder(order) {
      const gateway = new StripeGateway();
    }
    
  2. 결과를 반환하고, 부작용을 만들지 마세요.

    // 테스트 가능
    function calculateDiscount(cart): Discount {}
    
    // 테스트 어려움
    function applyDiscount(cart): void {
      cart.total -= discount;
    }
    
  3. 작은 표면 면적. 메서드가 적을수록 필요한 테스트도 적습니다. 매개변수가 적을수록 테스트 설정이 더 간단해집니다.

관계

  • 모듈(Module) 은 정확히 하나의 인터페이스(Interface) (호출자와 테스트에 제공하는 표면)를 가집니다.
  • 깊이(Depth) 는 모듈(Module) 의 속성이며, 해당 인터페이스(Interface) 에 대해 측정됩니다.
  • 경계(Seam) 는 모듈(Module) 의 인터페이스(Interface) 가 존재하는 곳입니다.
  • 어댑터(Adapter) 는 경계(Seam) 에 위치하며 인터페이스(Interface) 를 충족합니다.
  • 깊이(Depth) 는 호출자에게 레버리지(Leverage) 를, 유지보수자에게 지역성(Locality) 을 제공합니다.

거부된 프레이밍

  • 구현 라인 수 대 인터페이스 라인 수의 비율로서의 깊이(Ousterhout): 구현을 부풀리도록 유도합니다. 우리는 레버리지로서의 깊이를 사용합니다.
  • 인터페이스를 TypeScript의 interface 키워드나 클래스의 public 메서드로 보는 것: 너무 좁습니다: 여기서 인터페이스는 호출자가 알아야 할 모든 사실을 포함합니다.
  • '경계(boundary)': DDD의 bounded context와 중복됩니다. 경계(seam) 또는 인터페이스라고 말하세요.

더 깊이 들어가기

  • 의존성이 주어진 클러스터 심화하기, DEEPENING.md 참조: 의존성 범주, 경계 규율, 그리고 레이어 대체 테스트.
  • 대안 인터페이스 탐색하기, DESIGN-IT-TWICE.md 참조: 병렬 하위 에이전트를 실행하여 인터페이스를 근본적으로 다른 여러 방식으로 설계한 후, 깊이, 지역성, 경계 배치 측면에서 비교하세요.

Individual skills in this repo

This repo contains 4 individual skills — each has its own dedicated page.

관련 스킬