Community코딩 & 개발github.com

ShortArrow/라이브러리-설계

게시된 패키지이거나 두 번째 소비자가 생긴 인앱 모듈인 라이브러리를 설계합니다. 구현은 영원히 편집 가능하지만, 공개 API, 의존성 집합, 성능 특성 및 모든 관찰 가능한 동작은 첫 번째 외부 사용 시점에 고정됩니다(하이럼의 법칙). 호출자를 위한 다섯 가지 의무에 중점을 둡니다: 제어권을 빼앗지 말 것, 숨은 비용을 추가하지 말 것(제로-코스트 추상화 기준), 숨은 상태를 유지하지 말 것, 실패를 숨기지 말 것, 탈출을 막지 말 것.

라이브러리-설계란 무엇인가요?

이 기술은 '라이브러리 계약' 개념을 중심으로, 라이브러리(게시된 패키지 또는 두 번째 소비자가 생긴 인앱 모듈) 설계에 대한 엄격한 원칙을 제시합니다. 핵심은 구현은 영원히 변경 가능하지만, 호출자가 관찰할 수 있는 모든 것(동작, 성능, 오류 등)은 첫 번째 의존이 발생하는 순간 고정된다는 점입니다. 이는 하이럼의 법칙에서 비롯된 현실을 인정하며, 공개 API, 파일 형식, 직렬화 스키마 등이 모두 이 '고정선'에 포함됩니다. 기술은 호출자에 대한 다섯 가지 의무를 강조합니다: (1) 제어권을 빼앗지 말 것(메커니즘과 정책 분리), (2) 숨은 비용을 추가하지 말 것(제로-코스트 추상화, 의존성은 API의 일부), (3) 숨은 상태를 유지하지 말 것(전역 상태 금지, 명시적 인스턴스 사용), (4) 실패를 숨기지 말 것(타입을 통한 오류 모델링, 원인 보존), (5) 탈출을 막지 말 것(저수준 프리미티브 위에 고수준 API 제공, 관찰 가능성 주입). 또한 이 원칙들은 클린 아키텍처의 의존성 규칙과 동일한 거부(런타임 성능에 대한 세금 vs 변경 가능성에 대한 세금)를 다른 통화로 표현한 것임을 설명합니다. 마지막으로, 소비자가 하나뿐인 모듈(0.y.z 상태)에는 과도한 설계를 적용하지 말고 두 번째 소비자가 등장할 때까지 기다리라고 조언합니다. 이 기술은 주로 Claude Code, Cursor, Codex 등 AI 기반 개발 도구에서 라이브러리 설계 및 검토 시 참조로 사용하기에 적합합니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/ShortArrow/skills/tree/HEAD/skills/library-design

Installed? Explore more 코딩 & 개발 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

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

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

문서

라이브러리 계약

애플리케이션은 사용자에 의해 평가됩니다. 라이브러리는 프로그래머에 의해 평가되며, 특정 순간에 평가됩니다 — 바로 작성자가 상상하지 못한 무언가를 원할 때입니다. 좋은 라이브러리는 의도된 대로 사용될 때 편리한 라이브러리가 아닙니다. 다른 방식으로 사용될 때 폐기되지 않아도 되는 라이브러리입니다.

아래의 모든 규칙은 하나의 비대칭성에서 비롯됩니다. 라이브러리 내부에서는 무엇이든 영원히 변경할 수 있습니다. 외부에서는 호출자가 관찰할 수 있는 모든 것이 누군가가 처음으로 의존하는 순간 고정됩니다 — 그리고 충분한 호출자가 있다면, 누군가는 그것의 모든 것에 의존하게 됩니다.

무엇이 고정되며, 언제 고정되는가

하이럼의 법칙은 작동 조건이지 호기심이 아닙니다: 충분한 사용자가 주어지면, 모든 관찰 가능한 동작은 하중을 견디게 됩니다 — 예외 문구, 열거 순서, 로그 출력, 타이밍, 심지어 문서화된 정의되지 않은 동작까지도 포함됩니다. "계약에 없었다"는 그것을 기준으로 출시한 호출자를 구제하지 못합니다.

따라서 고정선은 public 키워드가 아닌 관찰 가능성에 있습니다. 그리고 세 가지 결과가 따릅니다:

  • 표면은 작게 시작합니다. 한 번 공개된 심볼은 넓힐 수는 있지만 조용히 철회할 수 없는 약속입니다. "당분간 public"은 이득 없이 부담만 지는 빚입니다; 필요할 때 노출하십시오.
  • 포맷은 코드보다 오래갑니다. 파일 포맷, 네트워크 프로토콜, 영구 스키마는 라이브러리를 둘러싼 모든 재작성을 견딥니다. 내부 구조체를 있는 그대로 직렬화하는 것은 고정된 계층을 편집 가능한 계층에 용접하는 것입니다.
  • 인앱 모듈은 패키징 시점이 아니라 두 번째 소비자 시점에 이 선을 넘습니다. 두 호출자가 존재하는 순간, 그 API는 게시된 패키지와 동일한 고정 경제학을 따릅니다 — 단지 폭발 반경이 더 작을 뿐입니다.

의미론적 버전 관리(SemVer)는 동일한 선을 공개적으로 표기한 것이며, 하나는 없으면 작동을 거부하는 것으로 시작합니다: "의미론적 버전 관리를 사용하는 소프트웨어는 공개 API를 선언해야 합니다" (SemVer 2.0.0, 1항). 일단 선언되면, 증분은 느껴지는 것이 아니라 결정됩니다 — 해당 API에 대한 하위 호환되지 않는 변경은 MAJOR를 올려야 하고(8항), 새로운 하위 호환 기능은 MINOR를 올려야 하며(7항), 하위 호환 버그 수정은 PATCH를 올려야 합니다(6항). 하이럼의 법칙이 선언을 어렵게 만드는 이유입니다: 스펙은 선언한 것을 고정하고, 호출자는 관찰한 것을 고정합니다.

스펙은 아직 선을 넘지 않은 모듈에 대한 이름도 가지고 있습니다. "주 버전 0(0.y.z)은 초기 개발을 위한 것입니다. 무엇이든 언제든지 변경될 수 있습니다" (4항). 이것이 하나의 소비자를 가진 모듈에 대한 정직한 레이블이며, 두 번째 소비자가 생기는 날은 패키징되는 날이 아니라 1.0.0이 필요한 날입니다.

제어권을 빼앗지 말라

메커니즘과 정책을 분리하십시오. HTTP 요청을 수행하는 것은 메커니즘입니다. 지수 백오프로 세 번 재시도하는 것은 정책이며, 핵심에 정책이 내장되면 라이브러리는 그 정책을 공유하는 호출자로 범위가 좁혀집니다. 로깅, 스레딩, 재시도, 타임아웃, 캐싱, 직렬화에 대한 편리한 기본값을 제공하십시오 — 그리고 모든 것을 교체 가능하게 만드십시오.

애플리케이션의 역할은 단 하나이며, 그것은 여러분의 것이 아닙니다. 프로세스 종료, stdout에 쓰기, SIGINT 트랩, 최상위 예외 처리기 설치, 스레드 풀 크기 조정: 프로세스당 정확히 하나의 컴포넌트만 이 작업을 수행할 수 있으며, 이를 수행하는 라이브러리는 그 역할을 가진 컴포넌트와 싸우게 됩니다. 프로세스의 입력을 읽는 것도 동일한 역할에 속합니다: 환경 변수, 구성 파일 및 명령줄 인수는 애플리케이션 루트에서 읽히며, 이 루트는 각 값을 라이브러리에 매개변수나 옵션 객체로 전달합니다. 그러면 호출자는 값의 출처를 선택할 수 있으며, 테스트는 머신을 설정하는 대신 값을 전달합니다.

매직 — 자동 감지, 자동 변환, 자동 재연결, 자동 캐시 — 은 호출자가 왜 그런 일이 발생했는지 재구성할 수 있는 경우에만 허용됩니다. 사용자가 디버깅할 수 없는 편의는 작업을 절약하는 것이 아니라 제어권을 빼앗는 것입니다.

숨은 비용을 추가하지 말라

일이 일어나는 시점은 호출자의 몫입니다. 스레드를 시작하는 임포트, 연결을 여는 생성자, 환경 변수를 읽는 정적 초기화자 — 각각은 호출자가 결코 선택하지 않았고 되돌릴 수 없는 순간에 실제 작업을 이동시킵니다.

의존성은 여러분의 API의 일부입니다. 그것의 런타임, 업데이트 주기, ABI 안정성, 라이선스, 자체 초기화 부작용은 모두 모든 호출자에게 전달됩니다; 여러분은 그들을 대신하여 선택하고 있는 것입니다.

성능 특성은 계약입니다. O(1) 조회를 O(n)으로 바꾸거나 할당 및 복사를 두 배로 늘리는 것은 호환되는 서명을 가진 호환성을 깨는 변경이며, 호출자는 이를 프로덕션에서 발견합니다.

입력이 출력보다 빠르게 도착하는 곳 — 스트림, 이벤트, 로그, 큐 — 에서 무제한 내부 버퍼는 지연된 메모리 누수입니다. 역압(Backpressure)은 운영 세부 사항이 아니라 설계의 일부입니다.

추상화 자체의 기준은 제로-코스트이며, 스트로우스트룹의 두 조항 모두에서 그렇습니다. 호출자가 사용하지 않는 것에 대해 비용을 지불하지 않습니다: 편의 계층은 기본 계층에 할당, 디스패치 또는 설정 비용을 부과해서는 안 됩니다. 그리고 호출자가 사용하는 것은 손으로 작성한 것보다 합리적으로 더 잘 작성할 수 없어야 합니다: 손으로 작성한 코드에 의해 능가되는 추상화는 우회되며, 우회된 라이브러리는 버전 번호만 남은 폐기물입니다.

숨은 상태를 유지하지 말라

싱글톤, 정적 캐시, 전역 이벤트 루프, 프로세스 전역 구성: 각각은 여러분의 라이브러리가 프로세스에서 유일할 때는 작동하지만, 두 번째 라이브러리, 플러그인 호스트 또는 병렬 테스트 스위트가 공유하는 순간 충돌합니다. 상태는 호출자가 생성하고, 소유하며, 두 번 생성할 수 있는 인스턴스에 존재해야 합니다.

실패를 숨기지 말라

API 형태는 무엇이 발생할 수 있고 누가 책임을 지는지 선언합니다. 오류, 취소, 타임아웃, 부분 성공, 부재 — 타입으로 모델링하고, "이건 정상적으로는 절대 발생하지 않아"와 예외를 통해 우회하지 마십시오. 동일한 형태는 주석이 강제할 수 없는 소유권 답변을 전달합니다: 누가 스트림을 닫는지, 버퍼가 재사용될 수 있는지, 콜백이 도착하는 스레드는 무엇인지.

원인을 보존하십시오. 실패를 false나 null로 축소하는 것은 상위 계층이 결정을 내리는 데 필요한 정확한 정보를 제거합니다; 직접 로깅하고 계속 진행하는 것은 동일한 정보를 잃고 호출자의 로깅 정책을 빼앗습니다.

품질은 비정상 경로에서 판가름납니다. 정상 경로에서는 라이브러리가 서로 교체 가능합니다; 타임아웃, 재연결, 스트림 중간 연결 끊김, 부분 읽기, 손상된 데이터 및 재시도가 차이가 있는 부분이므로, 먼저 그것들의 구성을 위해 설계하십시오.

취소는 나중에 추가하기 어렵습니다 — 취소 토큰은 그것이 닿는 모든 서명을 관통합니다. I/O 및 장기 실행 작업은 첫 번째 버전부터 이를 받아들입니다.

탈출을 막지 말라

두 개의 층을 구축하십시오. 일반적인 경우를 한 줄로 처리하는 고수준 API를, 공개 프리미티브 위에 올리십시오 — ReadAsync() 위에 ReadAsync(Memory<byte>) — 그래서 최상위 층에 맞지 않는 요구 사항을 가진 호출자가 떠나는 대신 아래로 내려갈 수 있습니다. 최상위 층만 있는 라이브러리는 예상하지 못한 첫 번째 요구 사항에서 폐기됩니다.

관찰 가능성은 주입되는 것이지 소유되는 것이 아닙니다: 로거, 메트릭, 트레이스는 호출자의 스택에 연결됩니다. 여러분의 라이브러리만 읽을 수 있는 진단 형식은 인질로 잡힌 원격 측정입니다.

하나의 세금, 두 개의 통화

이 기술과 클린 아키텍처는 두 개의 다른 통화로 표현된 동일한 거부입니다. 제로-코스트 추상화는 런타임 성능에 대한 세금을 거부합니다; 의존성 규칙은 변경 가능성에 대한 세금을 거부합니다. 둘 다에서 추상화는 의존하는 쪽이 포기해야 하는 것으로 평가됩니다.

여기서의 의무아키텍처적 명칭
메커니즘과 정책 분리유스케이스를 프레임워크 및 드라이버와 분리
기본값 교체 가능의존성 역전 — 안정적인 쪽이 구현을 선택
편의 층 아래의 프리미티브외부 세부 사항 교체 가능
전역 상태 없음의존성이 명시적이므로 경계를 그릴 수 있음
의존성은 API의 일부의존성 규칙

호출자의 건강한 형태는 여러분의 라이브러리를 맨 아래에 둡니다: 그들의 정책과 도메인 아래에 있는 교체 가능한 세부 사항입니다. 반전된 형태 — 애플리케이션이 프레임워크 안에 살며, 그의 생명주기, 타입, DI, 오류 모델, 비동기 모델을 끌고 다니는 것 — 은 라이브러리가 애플리케이션 역할을 하며, 아키텍처 규모로 작성된 것입니다.

일반적인 형태: 좋은 아키텍처는 의존성의 수를 최소화하는 것이 아니라 각 의존성이 빼앗는 자유를 최소화합니다.

문장

라이브러리의 추상화는 호출자의 선택을 넓히기 위해 존재합니다. 성능, 제어권 또는 정보를 대가로 가져가는 순간, 그것은 추상화를 중단하고 결정을 내리기 시작한 것입니다.

적용하지 말아야 할 때

하나의 소비자, 하나의 저장소, 하나의 팀을 가진 모듈은 고정선을 넘지 않았습니다 — SemVer 용어로 0.y.z 상태이며, 번호를 가지고 있든 없든 — 그리고 교체 가능한 모든 정책 인터페이스를 구축하는 것은 유지 보수 비용이 드는 추측입니다. 첫날부터 비용이 들지 않는 규율을 유지하십시오 — 전역 상태 없음, 원인을 보존하는 실패, 호출자가 요청하기 전에는 작업 없음 — 그리고 두 번째 소비자가 도착할 때 인터페이스를 추가하십시오. 그 순간이 추측이 중단되는 때입니다.

출처

  • Semantic Versioning 2.0.0, semver.org: 1항 (공개 API는 반드시 선언되어야 함), 6–8항 (PATCH, MINOR, MAJOR 증분), 4항 (0.y.z), 및 지원 중단에 대한 FAQ 항목 (문서화한 다음, 지원 중단이 적용된 MINOR 릴리스를 발행). 문구는 페이지를 기준으로 확인함.
  • 하이럼의 법칙은 표준이 아닌 관찰이며, 그렇게 인용됨.

Individual skills in this repo

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

ShortArrow/interface-design

Deciding how a screen looks and behaves, triggered by the moments that hand the decision to the user unworked: about to ask which layout, which colour, which placement, how to draw a diagram, how wide a pane, how to split a view; about to call a visual choice a matter of taste; about to treat "it looks fine" as having checked it; about to ship a control sized by eye; about to read an automated accessibility pass, or a Lighthouse score of 100, as a clean bill of health. Most of these have an answer in a published standard, a measurable threshold, or one screenshot — WCAG 2.2 fixes contrast, target size, reflow and focus; a design system fixes spacing and components; axe-core finds about 57% of WCAG issues, and Lighthouse's accessibility score is that same engine weighted, so both leave the rest to a person. What survives is a genuine question, asked with the evidence attached. Use before asking any question about appearance, layout or interaction, and before calling

ShortArrow/test-design

Deriving test cases, triggered by the moments that produce weak ones: about to write a test list that is the happy path restated, about to derive tests by reading the implementation they check, about to test the bound the specification names and not the other one, about to enumerate the roles the specification lists and skip the caller it forgot, about to vary one condition at a time from the happy path, about to draw a state or decision table without tying each row to a test, about to write an expected result with "or" in it, about to skip the error paths as unlikely, about to leave a path unimplemented and untested, or about to grade a suite as adequate by reading it. A test derived from the implementation passes by construction; coverage counts classes, not tests. Use when writing a test plan or list, when a use case is the specification in hand, when adding tests to existing code, when reviewing a suite for gaps, and when a suite is all green and proves little.

관련 스킬