ousterhout-quality-program
무엇을 하나요
코드를 작성하거나 검토할 때 경계가 생성되거나 변경되는 경우에 사용하세요 — 새로운 모듈, 클래스, 컴포넌트, 헬퍼, 훅, 서비스 또는 래퍼; 공유 코드를 추출하거나 중앙 집중화하는 모든 경우; "재사용 가능하게 만들자"는 순간; 그리고 명시적으로 모듈을 검토, 리팩토링 또는 설계할 때 사용합니다. 추상화가 그 가치를 증명하는지 판단합니다: 모듈 깊이, 설계 결정을 숨길지 여부, 중복 코드가 공유 불변성을 보호하는지 아니면 단순한 유사성인지, 인터페이스가 안정적인지 여부를 평가합니다. 많은 얕은 클래스를 생성하는 기계적인 SOLID/Clean Code를 방지합니다. 또한 리더 비용 테스트(사람과 에이전트가 읽고 변경하기 쉬운 코드)와 기존 코드베이스를 이 표준에 맞게 리팩토링하는 절차를 정의합니다.
설치를 누르면 이 항목이 AgentsRoom 데스크톱 앱에서 열립니다. 앱이 아직 설치되어 있지 않으면 다운로드 페이지로 이동합니다.
SKILL.md
---
name: ousterhout-quality-program
description: 코드를 작성하거나 검토할 때 경계가 생성되거나 변경되는 경우에 사용하세요 — 새로운 모듈, 클래스, 컴포넌트, 헬퍼, 훅, 서비스 또는 래퍼; 공유 코드를 추출하거나 중앙 집중화하는 모든 경우; "재사용 가능하게 만들자"는 순간; 그리고 명시적으로 모듈을 검토, 리팩토링 또는 설계할 때 사용합니다. 추상화가 그 가치를 증명하는지 판단합니다: 모듈 깊이, 설계 결정을 숨길지 여부, 중복 코드가 공유 불변성을 보호하는지 아니면 단순한 유사성인지, 인터페이스가 안정적인지 여부를 평가합니다. 많은 얕은 클래스를 생성하는 기계적인 SOLID/Clean Code를 방지합니다. 또한 리더 비용 테스트(사람과 에이전트가 읽고 변경하기 쉬운 코드)와 기존 코드베이스를 이 표준에 맞게 리팩토링하는 절차를 정의합니다.
---
# Ousterhout 품질 프로그램
## 개요
모듈의 역할은 복잡성을 작은 인터페이스 뒤에 숨기는 것입니다. 핵심 척도는 **깊이(depth)** 입니다: 깊은 모듈은 상당한 기능 위에 단순한 인터페이스를 제공하고, 얕은 모듈의 인터페이스는 구현만큼 복잡하여 아무런 이득이 없습니다. 복잡성은 변경이 예상치 못한 코드를 이해하거나 건드리게 만들 때 느끼는 것입니다 — Ousterhout는 두 가지 원인을 지적합니다: **의존성**(A를 변경하려면 B도 변경해야 함)과 **불명확성**(중요한 정보가 명확하지 않음).
Ousterhout는 좋은 모듈이 *느껴지는* 방식을 알려줍니다. 경계가 어디에 있어야 하는지, 그리고 안전하게 그쪽으로 나아가는 방법을 알려주는 몇 가지 다른 관점과 결합할 때 가장 강력합니다. 이 스킬은 그 결합된 관점입니다.
## 리뷰가 실제로 잘못되는 지점
이 스킬이 교정하려는 두 가지 실패는 — 에이전트가 작성한 코드에서 반복적으로 관찰된 — **해결책**에 있으며, 분할/비분할 판단 자체에는 없습니다:
1. **얕은 수정.** 여섯 개의 `as unknown as` 캐스트가 있을 때, 도움 없는 리뷰어는 이를 하나의 일반적인 `castRows<T>()` 헬퍼로 중앙집중화합니다 — 깔끔해지지만 불명확성은 그대로입니다. 깊은 수정은 테스트가 먼저 고정된 타입화된 행→도메인 매퍼입니다 (Parnas 적용: 캐스트는 경계가 없다는 냄새; Beck: 이동하기 전에 매핑을 증명). 냄새를 정리하는 것은 제거하는 것이 아닙니다.
2. **반사적 추출.** 세 개의 형제 컴포넌트에 동일한 업데이트 로직이 반복될 때, 모든 도움 없는 리뷰어는 "공유 헬퍼를 추출하라"고 했습니다 — DRY 반사 작용입니다. Metz를 확장한 이 프로그램의 규칙은: 세 번째 닮은꼴이 아니라 불변 조건을 기다리라는 것입니다 — 코드를 중앙집중화하는 것은 공유 규칙을 보호할 때이지, 단순히 운율이 맞을 때가 아닙니다.
수정안을 추천할 때, 두 가지를 모두 적용해 보세요: 불명확성을 제거하는가 아니면 단지 옮기는가, 그리고 추출이 불변 조건을 보호하는가 아니면 단지 모양을 중복 제거하는가?
## 비례성 게이트
변경이 새로 내보내거나 가져올 수 있는 이름을 추가하지 않고, 새 모듈/클래스/컴포넌트/헬퍼/훅/서비스/래퍼를 만들지 않으며, 중앙집중화하지 않는 경우 이 렌즈를 건너뜁니다. 순수 이름 변경, 기계적 코드 변환, 설정/데이터 편집, 한 줄 수정은 제외됩니다. 의심스러울 때는 두 가지 핵심 테스트(깊이, 불변 조건)만 실행하고 거기서 멈추세요.
## 전체 규칙
생성되거나 리뷰된 모든 코드는 작업이 완료되었다고 간주되기 전에 Ousterhout 렌즈를 통과해야 합니다 — 명시적 설계 리뷰뿐 아니라 — 비례성 게이트 아래 변경(새 경계 없음, 중앙집중화 없음: 이름 변경, 코드 변환, 설정 편집)은 제외합니다. 두 가지 테스트: (1) **깊이** — 새 인터페이스는 노출하는 것보다 훨씬 더 많은 것을 숨겨야 하며, 감싸는 것만큼 복잡한 인터페이스는 아무런 이득이 없습니다. (2) **불변 조건** — 공유 코드는 세 군데가 닮았다고 해서 추출하지 말고, 공유 규칙을 보호할 때만 추출하며, 수정은 불명확성을 제거해야 하며 단지 옮기면 안 됩니다(여섯 개 캐스트를 하나의 헬퍼로 중앙집중화해도 여전히 여섯 개 캐스트입니다). 변경이 경계를 생성하거나 재구성할 때는, 먼저 확립된 제품이 이 모양과 규모의 문제를 어떻게 해결하는지 찾아보고, 명시적 이유가 없으면 그 관례를 채택하세요(훈련에서 기억한 패턴은 주장이지 출처가 아닙니다). 그런 다음 아래 검사를 실행하세요.
## 사용 시기
- 새 클래스/함수/훅이 그 인터페이스만큼 가치가 있는지, 아니면 단순한 얕은 통과인지 결정할 때.
- 파일이 크기 임계값을 넘었고, 단순히 분할해야 한다는 것뿐 아니라 *어떻게* 분할할지 결정할 때.
- 반복되는 코드가 공유 헬퍼 추출을 유혹할 때.
- 비즈니스 규칙 경계(권한 범위 검사, 금액/반올림 규칙, 상태 머신 전이 가드, 데이터 보존 규칙)를 설계하거나 리뷰할 때.
- 인터페이스가 매개변수나 특수 사례를 추가하려 할 때.
- 기존 코드베이스를 이 표준에 맞추어 개선할 때 — 아래 "기존 코드베이스를 이 표준으로 리팩터링하기" 참조.
**적합하지 않음:** 사소한 기계적 편집이나 프로젝트 관례가 이미 구조를 규정하는 경우 — 위 비례성 게이트 참조. 수술적 변경 규율을 위한 `karpathy-guidelines`와 리팩터 안전망을 위한 테스트 주도 개발 스킬이 있을 때는 그것을 따르세요.
## 렌즈들
각 렌즈는 정확히 한 가지 질문을 추가합니다. Ousterhout가 중심이며, 다른 렌즈들은 그 맹점을 보완합니다.
| 렌즈 | 추가하는 질문 | 언제 무시하는가 |
|---|---|---|
| **Ousterhout** — 깊은 모듈 | 이 인터페이스가 노출하는 것보다 더 많이 숨기고 있는가? | 기본 스파인. |
| **Parnas** — 정보 은닉 | 이 모듈이 숨기는 (변경될 가능성이 있는) 설계 결정은 무엇인가? | 모듈이 깊어야 하는 *이유*. 변경되는 것을 숨기지 않는다면 깊이는 겉치레다. |
| **Brooks** — 본질적 vs 우발적 | 이것이 우발적 복잡성을 제거하는가, 아니면 본질적 도메인 복잡성을 단지 옮기는가? | 문제를 줄이지 않고 옮기는 "리팩터"를 제거한다. |
| **Evans** — 도메인 주도 설계 | 이 경계가 일반적인 유틸리티 언어가 아닌 도메인 언어로 명명되었는가? | `utils`/`helpers`를 이름 변경 — 이 저장소가 실제로 가진 불변 조건에 따라 경계 이름을 짓는다. |
| **Fowler** — 리팩터링 / 냄새 | 더 깊은 설계로 가는 가장 작은 안전한 이동은 무엇인가? | "더 깊어져야 한다"는 것을 테스트를 통과하는 구체적인 단계로 바꾼다. |
| **Beck** — 단순 설계, 테스트 우선 | 깊이를 더하기 전에 현재 동작을 증명했는가? | 조기 아키텍처 진행을 제동한다. 먼저 작동하고 테스트된 상태를 만들고, 그 다음 올바른 경계를 깊게 한다. |
| **Hickey** — 단순 vs 쉬움 | 관련 없는 개념을 섞었는가, 아니면 진정한 하나의 개념인가? | 얕은 헬퍼는 보통 *쉬움*(가까이 있고 빠름)이지 *단순함*(섞인 개념이 적음)이 아니다. 단순함을 선호한다. |
| **Metz** — 잘못된 추상화보다 중복 | 이 반복된 코드는 공유 불변 조건을 보호하는가, 아니면 단지 비슷해 보이는가 (이 프로그램의 규칙, Metz 확장)? | Metz: 중복은 잘못된 추상화보다 싸다 — 잘못된 추상화를 다시 인라인하라, 굽히지 말라. 이 프로그램은 확장한다: 반복된다고 중앙집중화하지 말라; 실제 불변 조건을 보호할 때만 중앙집중화하라. 불변 조건이 드러날 때까지 중복을 허용하라. |
| **Hyrum's Law** — 관찰 가능한 동작 | 호출자가 이 인터페이스 계약을 넘어선 동작에 의존할 것인가? | 작고 안정적인 인터페이스를 주장한다: 모든 관찰 가능한 동작은 결국 필수적이 된다. |
## 결합 레시피
다음 순서로 적용 — 이전 렌즈가 통과해야 다음 렌즈가 의미 있다:
1. **Metz — 입장 게이트.** 이 경계/추상화가 존재할 자격이 있는가? 이 프로그램의 규칙, Metz 확장: 코드는 공유 규칙을 보호할 때만 추출한다 — 세 번 비슷한 것은 드러난 불변 조건이 아니다. 아니면 여기서 멈춘다.
2. **Parnas / Ousterhout** — 변하기 쉬운 결정(권한 범위, 반올림 규칙, 전환 가드, 보존 규칙)을 깊은 모듈 뒤에 숨긴다.
3. **Evans** — 그 모듈을 도메인 언어로 이름 짓고, `utils`가 아니다.
4. **Beck / Fowler** — 기존 코드에 대해 현재 동작을 테스트로 고정하고, 작은 안전한 단계로 리팩터링한다. 새로 생성된 코드에는 고정할 현재 동작이 없으니, 의도한 동작을 정의하는 테스트를 작성한다.
5. **Hickey** — 워크플로우가 비슷해 보여도 관련 없는 개념을 섞은 인터페이스는 거부한다.
## 구조적 안티패턴
**기계적인 SOLID / 클린 코드는 얕은 모듈을 만든다.** 교조적인 해석 — 책임당 한 클래스, 모든 함수를 추출, 모든 것을 작게 유지 — 은 인터페이스가 몸체만큼 복잡한 클래스 무리를 낳는다. "분할하라"는 규칙이 있으면 어떤 *결정*을 숨기는지(Parnas)와 노출하는 것보다 더 많이 숨기는지(Ousterhout)를 묻는다. 변경되는 것을 숨기지 않으면 분할하지 말라. 이 방어는 리팩터 압박("이것을 정리하라", "이 파일이 너무 크다")에서 가장 중요하다 — 차분한 분석에서는 리뷰어가 이미 저항하지만, 리팩터 중간에 가시적 변화를 내라는 명령이 있을 때 얕은 파일 무리가 작성된다.
## 흔한 실수
- **크기만으로 분할.** 하나의 일관된 결정을 숨기는 400줄 쿼리 모듈이 같은 조인을 누출하는 100줄짜리 4개보다 더 깊을 수 있다.
- **분할을 `helpers`/`utils`로 이름 짓기.** 도메인 언어로 이름 지을 수 없으면(Evans) 경계가 아마도 잘못된 것이다.
- **두 번째 발생에서 추출.** 이 프로그램의 규칙, Metz 확장: 세 번째 비슷한 것이 나올 때까지 기다려라.
- **동작 고정 전에 깊이 더하기.** Beck: 현재 동작을 증명하는 테스트 없이는 "깊이 더하기" 리팩터는 재작성이다.
- **통과용 래퍼를 모듈로 착각.** 인자를 전달하는 래퍼는 인터페이스를 추가하지만 아무것도 숨기지 않는다 — 정의상 얕다.
- **운율을 불변 조건으로 착각.** 공유 불변 조건의 가장 좋은 증거는 함께 변경된 이력이다(두 곳에서 같은 버그가 수정됨). 독립적으로 변경되는 비슷한 것은 운율이다; 중복을 유지하라.
- **냄새를 제거하지 않고 정리만 함.** 여섯 개 캐스트를 하나의 일반 캐스트 헬퍼로 중앙집중화하는 것은 같은 모호함의 깔끔한 버전이다. 깊은 수정은 캐스트가 가리고 있던 경계에 이름을 붙인다.
## 독자 비용: 세 번째 테스트
깊이와 불변 조건이 경계 존재 여부를 결정한다. 독자 비용은 그 주변 코드가 변경하기 쉬운지 결정한다. 다음 독자, 인간이든 에이전트든, 안전하게 변경하려면 읽어야 할 모든 줄에 비용을 지불한다. 에이전트는 토큰으로 비용을 지불하고 텍스트 검색, 부분 읽기, 타입체크/테스트 루프로 탐색하므로 같은 결함이 더 큰 비용을 낸다. 묻자:
- **찾기 쉬운가?** 개념당 하나의 이름, 어디서나 같은 철자, 일반 텍스트 검색으로 접근 가능. 결함: 문자열을 조합한 이름, import 부작용에 의한 연결, 정의를 숨기는 재내보내기 체인, 하나의 개념에 두 개의 이름.
- **독자가 일찍 멈출 수 있는가?** 계약은 파일 상단이나 export 위에 위치: 약속하는 것, 숨기는 것, 절대 하지 않는 것. 결함: 계약은 본문을 읽어야만 유추 가능.
- **기계 검증 가능한가?** 모든 경계의 입출력에 정확한 타입, 타입체크가 호출자 읽기를 대체. 결함: `any`, 빈 딕셔너리, 의미가 본문에 있는 불리언 플래그.
- **결합이 보이는가?** 함께 변경해야 하는 부분이 강제됨(공유 타입, 테스트, 단일 소스) 또는 그렇지 않으면 양쪽에 표시됨. 숨겨진 결합의 증거는 코드 어디에도 언급되지 않은 채 이력에서 함께 변경되는 것.
- **잡음이 없는가?** 코드를 반복하는 주석 없음, 주석 처리된 코드 없음, 죽은 분기 없음, 변경 이력 주석 없음, 대체 경로 옆에 구식 경로 보관 없음.
- **예측 가능한가?** 레이아웃이 저장소 기존 패턴을 따름; 테스트는 독자가 찾을 위치에 있고 독립적으로 실행됨.
파일 크기는 의도적으로 제외됨. 매우 큰 파일은 두 번째 숨겨진 결정을 찾는 이유이지 자르기 이유가 아님: 독자는 범위를 검색하고 읽을 수 있으며, 아무것도 숨기지 않는 분할은 부하를 줄이지 않고 인터페이스를 추가함.
코드 내 마커와 저장소 코드맵에는 가능한 경우 `context-audit` 사용: 그 `AIDEV-NOTE:` 앵커(복구 불가능한 사실 하나와 출처 참조, 최대 두 줄, 위치에 있음)는 강제할 수 없는 결합의 관례임.
## 기존 코드베이스를 이 표준에 맞게 리팩터링하기
레트로핏은 새 코드와 동일하게 평가됨; 차이는 순서와 절제임. 대부분의 코드베이스는 그대로 둬야 함.
1. **조사, 읽기 전용.** 경계(모듈, 서비스, 공유 헬퍼)를 나열. 각 기록에: 숨겨진 결정 또는 "없음"; 인터페이스 크기 대비 본문; 이력에서 함께 변경된 파트너; 독자 비용 결함. 아직 변경하지 않음.
2. **변경 빈도 순위, 못생김 순 아님.** 우선순위는 코드 변경 빈도 × 읽기 비용. 작동하는 차가운 코드는 얕더라도 그대로 둠. 필수 도메인 복잡성은 그대로 둠(Brooks).
3. **발견마다 하나의 해결책 할당:**
- 아무것도 숨기지 않는 통과 계층 또는 래퍼: 삭제, 호출자는 감싼 것을 직접 사용;
- 플래그와 특수 사례로 왜곡된 잘못된 추상화: 다시 인라인(Metz), 그 후 진짜 불변 조건 찾기;
- 하나의 결정을 공유하는 얕은 형제들: 하나의 인터페이스 뒤로 병합;
- 누출된 결정(호출자가 형식, 규칙, 스키마를 앎): 소유 모듈로 끌어내림;
- 일반 이름(`utils`, `helpers`, `manager`): 숨긴 결정에 맞게 이름 변경하거나 호출자에 녹임;
- 타입 없는 경계: 타입 지정, 캐스트를 매퍼로 교체;
- 숨겨진 결합: 강제하거나 양쪽에 표시;
- 잡음: 삭제.
독립적으로 변경되는 라임은 해결책 없음.
4. **행동을 먼저 고정.** 테스트가 현재 코드 행동을 증명하기 전까지 해결책 시작 금지(Beck). 리팩터는 행동 보존; 행동 변경은 별도 커밋.
5. **작업을 한 에이전트가 혼자 끝낼 단위로 나눔.** 단위당 하나의 경계. 각 단위는 소유 파일, 유지할 계약, 독립 증명 명령을 명명. 두 단위가 같은 파일을 동시에 쓰지 않음; 공유 파일(배럴, 레지스트리, 라우트 테이블)은 단일 소유자 지정 또는 통합 대기. 여러 단위가 의존하는 인터페이스 변경은 먼저 단위로 처리.
6. **결과 측정.** 시작 전 대표 변경 선택, 독자가 읽어야 할 파일과 줄 수 계산; 완료 후 다시 계산. 내보낸 이름과 총 줄 수는 줄거나 유지되어야 함. 인터페이스를 추가하는 리팩터는 이유 명시 필요.
7. **중지** 남은 것이 차갑거나 필수이거나 라임일 때.
관련 스킬(가능한 경우): `repo-review`(디자인 타입)는 조사를 조언용 산출물로 생성; `design-cleanup`은 우발적 복잡성에 대한 수정 및 재조사 루프 실행; `context-audit`은 앵커와 코드맵 추가; `ousterhout-build-deep`은 단위를 수행하는 에이전트용 작성자 시간 체크리스트.
## 이 스킬의 위치
이 스킬은 리뷰 및 판단 계층임: 추상이 깊고 올바른 결정을 위해 명명되었으며 추출할 가치가 있는지 결정하는 데 사용. `find-shared-code`는 최근 이력에서 공유할 가치가 있는 코드를 찾을 때 입장 시험으로 사용. 아래 부록은 각 저자의 추론을 제공.
---
## 부록: 렌즈 심층 분석
각 저자가 잡아내는 실패 모드와 각자가 제공하는 한 가지 조치. 위 표는 빠른 참조; 여기는 그 이면의 추론.
### Ousterhout — 깊은 모듈(중추)
*소프트웨어 설계 철학.*
- **깊이** = 이익(숨겨진 기능) ÷ 비용(인터페이스 복잡성). 깊은 모듈은 적은 것으로 많은 것을 숨김. 얕은 모듈 인터페이스는 본문만큼 복잡해 아무 이득 없음.
- **복잡성**은 시스템을 이해하거나 수정하기 어렵게 만드는 모든 것. 두 가지 원천:
- **의존성** — 한 부분을 변경하면 다른 부분도 건드려야 함.
- **불명료함** — 중요한 정보가 코드에서 명확하지 않음.
- **증상:** 변경 증폭(한 결정에 많은 편집), 인지 부하(머리에 담아야 할 양), 알 수 없는 미지(변경이 영향을 줄 코드를 알 수 없음).
- **핵심 조치:** 복잡성을 *아래로* 끌어내림 — 모듈이 어려운 경우를 흡수해 호출자가 신경 쓰지 않도록 함. 구성 매개변수와 통과 계층은 복잡성을 호출자에게 *올림*; 이것이 얕음.
잡아내는 것: 구현을 누설하는 인터페이스; 도움이 되지 않는 헬퍼.
### Parnas — 정보 은닉(깊이가 중요한 이유)
*시스템을 모듈로 분해할 때 사용할 기준에 관하여 (1972).*
- 변경될 가능성이 높은 **설계 결정**을 중심으로 분해하라, 계산 단계가 아니라. 각 모듈은 그런 결정을 하나 숨긴다.
- 이것은 deep module의 직접적인 조상이다. 모듈이 deep한 이유는 호출자들에게 파급될 수 있는 결정을 숨기기 때문이다.
주의할 점: 변동성이 전혀 없는 "모듈"은 깊이가 겉치레에 불과하다. 이 인터페이스 뒤에서 호출자가 절대 보지 못하는 변화가 무엇인지 물어라. 답이 "없다"면 경계는 장식에 불과하다.
### Brooks — 본질적 복잡성과 부수적 복잡성
*은총알은 없다.*
- **본질적** 복잡성은 도메인에 내재된 것이다 (감정평가가 정말 이렇게 복잡하다). **부수적** 복잡성은 도구와 구조가 부과하는 것이다.
- 제거할 수 있는 것은 오직 부수적 복잡성뿐이다. 본질적 도메인 복잡성을 한 파일에서 다른 파일로 옮기는 리팩터링은 아무것도 하지 않은 것이다.
주의할 점: 단순화로 위장한 재배치. 전체 복잡성이 줄었는지, 아니면 단지 옮겨졌는지 물어라.
### Evans — 도메인 주도 설계
*도메인 주도 설계.*
- 경계는 도메인의 **보편적 언어**로 이름 지어져야 하며, 일반적인 유틸리티 용어로는 안 된다. `helpers`라는 모듈은 아무것도 이름 짓지 못하지만, `AccessScope`나 `PricingPolicy`라는 모듈은 불변식을 이름 짓는다.
- 경계가 명확한 컨텍스트는 비즈니스 불변식이 경계를 넘어 새어나가는 것을 막는다.
주의할 점: 올바른 분해지만 의미 없는 이름. 도메인 언어로 모듈 이름을 지을 수 없다면 경계를 잘못 자른 것이다.
### Fowler — 리팩터링과 코드 냄새
*리팩터링.*
- 현재 설계에서 더 깊은 설계로 가기 위한 구체적이고 안전하며 이름 있는 동작(함수 추출, 필드 이동, 조건문을 다형성으로 대체 등)을 제공한다.
- 모든 동작은 동작 보존적이고 작아서 되돌릴 수 있다.
주의할 점: "이것은 더 깊어져야 한다"와 다음 커밋을 아는 것 사이의 간극. Ousterhout가 목표를 설정하고 Fowler가 길을 제시한다.
### Beck — 단순 설계, 테스트 우선
*테스트 주도 개발; XP.*
- Beck이 발표한 단순 설계의 네 가지 규칙: 테스트 통과, 중복 없음, 의도 드러냄, 최소 요소. 이 프로그램은 후대 Fowler/Haines의 재배열(중복보다 의도 우선)을 따른다. 이는 이 프로그램의 Metz 확장 불변식 규칙(아래 Metz 참고)을 따르기 때문이다: 중복을 보호하는 의도를 이름 지을 수 있을 때까지 중복에 행동하지 말라.
- 테스트 우선은 조기 아키텍처를 막는 제동장치다. 먼저 작동하게 하고 동작을 증명한 다음, 이제 테스트가 보호하는 경계를 깊게 하라.
주의할 점: 동작이 고정되기 전에 구축된 아키텍처. 현재 동작을 증명하는 테스트 없이는 "깊게 하는" 리팩터링은 검증되지 않은 재작성이다.
### Hickey — 단순함과 쉬움
*Simple Made Easy.*
- **단순함(Simple)** = 꼬이지 않음: 하나의 개념, 다른 것과 섞이지 않음(객관적).
- **쉬움(Easy)** = 가까이 있음, 익숙함, 빠르게 접근 가능(당신에게 상대적).
- 둘은 독립적이다. 얕은 헬퍼는 보통 *쉬움* — 작성이 빠르고 가까움 — 이지만, 관련 없는 관심사를 섞으면 *단순*하지 않다.
주의할 점: 편의가 설계인 척 하는 경우. 타이핑이 빠른 꼬인 것보다 개념을 꼬이지 않게 유지하는 구성을 선호하라.
### Metz — 잘못된 추상화보다 중복을 선호하라
*"The Wrong Abstraction" (2016).*
- 중복은 잘못된 추상화보다 훨씬 저렴하다. 너무 일찍 추상화를 추출하면 모든 미래 호출자가 결코 모두에게 맞지 않는 가정을 맞추느라 고생한다.
- 추상화가 잘못된 것으로 판명되면 Metz의 해결책은 다시 인라인하고 중복을 허용하는 것이다. 결코 맞지 않는 경우에 맞추려 하지 말라.
- **이 프로그램의 규칙, Metz 확장: 코드가 반복된다고 중앙집중화하지 말라. 진짜 공유 불변식을 보호할 때 중앙집중화하라.** 불변식이 드러나기 전까지는 중복을 용인하라.
주의할 점: 과도한 중앙집중화 — 모두가 이제 피해야 하는 얕은 공유 헬퍼. 이것은 기계적인 "무조건 DRY"에 대한 균형추다.
### Hyrum의 법칙 — 관찰 가능한 동작은 계약이 된다
*"충분한 수의 사용자가 있으면, 시스템의 모든 관찰 가능한 동작은 누군가가 의존하게 된다."*
- 인터페이스가 *우연히* 하는 것 — 순서, 타이밍, 오류 메시지 등 — 은 결국 누군가가 의존한다. 그래서 노출하는 표면은 문서화한 표면보다 크다.
- 이것은 Ousterhout가 선호하는 **작고 안정적인 인터페이스**를 뒷받침한다: 노출이 적을수록 우연히 부하를 지는 부분도 적어진다.
주의할 점: 굳어질 넓은 인터페이스. 모든 추가 관찰 가능성은 미래의 제약이 된다.
### 이들이 어떻게 맞물리는가
- **Parnas → Ousterhout:** 변동성 있는 결정을 숨겨라 → 모듈이 깊다.
- **Brooks:** 깊이가 복잡성을 제거했는지, 단지 옮겼는지 확인하라.
- **Evans:** 경계를 도메인 언어로 이름 짓는다.
- **Beck → Fowler:** 동작을 고정하고, 작고 안전한 동작으로 리팩터링하라.
- **Metz:** 불변식이 진짜일 때까지 중앙집중화를 저항하라.
- **Hickey:** 인터페이스는 하나의 개념으로 유지하라.
- **Hyrum:** 그 인터페이스를 작게 유지해 안정성을 유지하라.
위험은 Ousterhout를 SOLID나 Clean Code의 기계적 해석과 섞는 것이다: 이는 얕은 인터페이스를 가진 아주 작은 클래스와 함수들을 많이 만들어 deep module과 정반대가 된다. Ousterhout는 Metz가 균형추 역할을 하며 해독제다.
태그
더 알아보기
Claude Ads: 광고 계정을 감사해 주는 Claude Code 스킬
Claude Ads는 Claude Code용 오픈소스 스킬입니다. Google, Meta, LinkedIn, TikTok, Amazon Ads 등에서 250개가 넘는 항목을 점검하고, 100점 만점 점수와 우선순위가 매겨진 실행 계획을 단 10여 분 만에 내놓습니다. 설치법, 명령어, 한계, 그리고 AgentsRoom에서 이를 오케스트레이션하는 방법까지 정리했습니다.
AGENTS.md: 모든 코딩 에이전트를 위한 단 하나의 컨텍스트 파일 (Codex, Antigravity, Claude)
AGENTS.md는 AI 코딩 에이전트가 코드를 건드리기 전에 읽는 이식 가능한 지침 파일입니다. 무엇을 담아야 하는지, CLAUDE.md와 무엇이 다른지, 그리고 Codex, Antigravity, Claude 사이에서 하나의 컨텍스트를 유지하는 방법을 알아봅니다.
AgentsRoom 다운로드
모든 AI 에이전트를, 모든 프로젝트에서, 하나의 창으로 실행하세요.
컴패니언 앱: 이동 중에도 에이전트를 모니터링
Claude, Codex, Antigravity CLI 또는 다른 AI 공급자를 사용하세요.
버그와 요청을 공개 백로그로 바로 보내세요.