
기술 문서 작성법: README, API 문서, 변경 로그
오픈소스에 기여하려고 봤는데 README가 없어서 포기한 적이 있다. 그때 좋은 문서의 가치를 뼈저리게 느꼈다.

오픈소스에 기여하려고 봤는데 README가 없어서 포기한 적이 있다. 그때 좋은 문서의 가치를 뼈저리게 느꼈다.
백엔드: 'API 다 만들었어요.' 프론트엔드: '어떻게 써요?' 이 지겨운 대화를 끝내주는 Swagger(OpenAPI)의 마법.

React에서 `count && <Component />`를 썼을 때 의도치 않게 숫자 0이 화면에 출력되는 현상, 다들 겪어보셨죠? JavaScript의 단락 평가(Short-Circuit Evaluation)와 Falsy 값의 특성 때문에 발생하는 이 버그의 원인을 심층 분석하고, 3가지 확실한 해결책(비교 연산자, 이중 부정, 삼항 연산자)과 React Native에서의 크래시 위험성까지 상세히 다룹니다.

복잡한 페이지 깊숙이 있는 컴포넌트를 수정할 때마다 로그인을 다시 하고 클릭을 5번 해야 하나요? Storybook으로 컴포넌트를 격리(Isolation)해서 개발하는 CDD 방법론.

빨간 줄을 없애려고 습관적으로 `as unknown as Type`을 쓰시나요? `as`가 사실 컴파일러의 눈을 가리는 행위인 이유와 Type Guard를 통한 올바른 해결법.

작년에 괜찮은 오픈소스 라이브러리를 발견했다. 딱 내가 필요한 기능이었고, 스타도 1.5k 정도 되었다. 근데 막상 사용하려고 보니 README가 달랑 3줄이었다. "This is awesome library. Clone and use it." 그게 전부였다.
설치는 어떻게 하는 건지, 기본 사용법은 뭔지, 어떤 옵션이 있는지 전혀 알 수 없었다. 코드를 직접 뜯어봐야 했다. 30분 정도 삽질하다가 결국 다른 라이브러리로 갈아탔다. 그때 깨달았다. 아무리 코드가 좋아도 문서가 없으면 아무도 안 쓴다는 걸.
반대로 최근에 Supabase 문서를 보면서 감탄했다. 코드 예제가 탭으로 나뉘어 있어서 JavaScript, Python, Go 중에 선택할 수 있었다. 모든 함수에 설명이 붙어 있고, 실제 유즈케이스가 있었다. 5분 만에 통합할 수 있었다.
결국 이거였다. 코드를 짜는 것만큼 문서를 쓰는 것도 중요하다는 걸. 오늘은 내가 배운 기술 문서 작성법을 정리한다.
첫 번째 프로젝트를 GitHub에 올렸을 때 일이다. 코드는 나름 깔끔하게 짰다고 생각했다. 그런데 한 달 뒤에 다시 보니 내가 뭘 만들었는지 기억이 안 났다. README에 "My awesome project"라고만 써놨더니 당연한 결과였다.
그때 와닿았다. 문서는 미래의 나를 위한 것이기도 하다는 걸. 다른 사람이 쓸 수도 있지만, 일단 나부터 헷갈린다. 6개월 뒤의 나는 완전히 다른 사람이나 마찬가지다.
README를 초대장이라고 생각하니 쓰기가 쉬워졌다. 파티에 초대할 때 "오세요"라고만 하지 않는다. 언제 어디서 뭐 하는지, 복장은 어떻게 하는지, 주차는 어디 하는지 알려준다.
README도 마찬가지다:
# Project Name
## What is this?
간단한 한 줄 설명. "A blazing fast markdown parser" 같은.
## Why does this exist?
왜 만들었는지. 어떤 문제를 해결하는지.
## Quick Start
```bash
npm install awesome-lib
import { parse } from 'awesome-lib';
const result = parse('# Hello');
상세한 설치 방법. 필요한 버전, 의존성 등.
주요 기능별 예제. 실제로 쓸 법한 코드.
함수, 클래스, 메서드 설명.
기여 방법. 이슈 리포트, PR 규칙 등.
MIT, Apache 2.0 등.
이 구조를 따르니 문서가 훨씬 체계적이 되었다. 방문자가 원하는 정보를 빠르게 찾을 수 있다.
### 뱃지는 신뢰의 신호
처음엔 뱃지(badge)를 장식용인 줄 알았다. 근데 이해했다. 뱃지는 신뢰를 주는 신호등 같은 거다.
```markdown




빌드가 통과했고, 테스트 커버리지가 80%고, 라이센스가 MIT라는 걸 한눈에 보여준다. 신뢰가 생긴다.
처음엔 API 문서를 손으로 하나씩 적었다. 함수 하나 수정할 때마다 문서도 고쳐야 했다. 당연히 동기화가 안 됐다. 코드는 바뀌었는데 문서는 옛날 버전이었다.
그러다 JSDoc을 알게 됐다:
/**
* 사용자 데이터를 가져온다
*
* @param userId - 사용자 고유 ID
* @param options - 추가 옵션
* @param options.includeProfile - 프로필 정보 포함 여부
* @returns 사용자 객체를 담은 Promise
* @throws {UserNotFoundError} 사용자를 찾을 수 없을 때
*
* @example
* ```typescript
* const user = await getUser('123', { includeProfile: true });
* console.log(user.name);
* ```
*/
async function getUser(
userId: string,
options?: { includeProfile?: boolean }
): Promise<User> {
// implementation
}
코드 바로 위에 주석으로 쓰니까 동기화 문제가 사라졌다. TypeDoc이나 API Extractor로 자동으로 HTML 문서도 만들 수 있다.
REST API는 OpenAPI(Swagger) 스펙을 썼다:
openapi: 3.0.0
info:
title: User API
version: 1.0.0
paths:
/users/{userId}:
get:
summary: Get user by ID
parameters:
- name: userId
in: path
required: true
schema:
type: string
responses:
'200':
description: User found
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'404':
description: User not found
이걸 Redoc이나 Scalar에 넣으면 멋진 인터랙티브 문서가 나온다. 직접 API 테스트도 할 수 있다.
처음엔 Changelog를 "Fixed bugs, added features"처럼 대충 썼다. 어떤 버그를 고쳤는지, 어떤 기능을 추가했는지 알 수 없었다.
Keep a Changelog 형식을 따르니 훨씬 명확해졌다:
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.2.0] - 2026-02-20
### Added
- 다크 모드 지원
- 사용자 프로필 이미지 업로드 기능
### Changed
- 로그인 화면 UI 개선
- API 응답 속도 30% 향상
### Deprecated
- `oldFunction()`은 다음 메이저 버전에서 제거 예정. 대신 `newFunction()` 사용
### Removed
- IE11 지원 중단
### Fixed
- 로그아웃 시 세션이 남아있던 버그 수정
- 모바일에서 이미지가 깨지던 문제 해결
### Security
- XSS 취약점 패치
Added, Changed, Fixed처럼 카테고리로 나누니 변경사항을 쉽게 파악할 수 있다. 사용자 입장에서도 업그레이드할 때 뭐가 달라지는지 알 수 있다.
Changelog를 손으로 쓰는 것도 귀찮아졌다. 그래서 Conventional Commits를 도입했다.
커밋 메시지를 규칙에 맞게 쓰면 자동으로 Changelog를 생성할 수 있다:
feat: 다크 모드 추가
fix: 로그아웃 버그 수정
docs: README에 설치 방법 추가
chore: 의존성 업데이트
standard-version이나 semantic-release 같은 도구로 자동으로 버전도 올리고 Changelog도 만든다:
npx standard-version
# CHANGELOG.md가 자동으로 업데이트되고
# package.json 버전이 올라가고
# git tag가 생성된다
이제 커밋만 잘 쓰면 문서가 저절로 생긴다.
큰 프로젝트를 하면서 문서가 많아졌다. README 하나로는 부족했다. 튜토리얼, 가이드, API 레퍼런스를 분리하고 싶었다.
Docusaurus를 써봤더니 딱이었다:
npx create-docusaurus@latest my-docs classic
Markdown 파일만 쓰면 자동으로 사이드바가 생기고, 검색이 되고, 다크 모드가 지원된다. 문서를 코드처럼 관리할 수 있다. Git으로 버전 관리하고, PR로 리뷰받고, CI/CD로 자동 배포한다.
Next.js 프로젝트라면 Nextra가 더 편하다. 파일 구조가 곧 사이드바가 된다:
docs/
getting-started.mdx
api-reference.mdx
guides/
authentication.mdx
deployment.mdx
6개월 뒤에 "왜 PostgreSQL 대신 MongoDB를 썼지?"라는 질문을 받았다. 기억이 안 났다. 분명 이유가 있었는데.
그때부터 Architecture Decision Records(ADR)를 쓰기 시작했다:
# ADR-001: MongoDB를 데이터베이스로 선택
## Status
Accepted
## Context
사용자 이벤트 로그를 저장해야 한다. 스키마가 자주 바뀔 것으로 예상된다.
## Decision
MongoDB를 사용한다.
## Consequences
### Positive
- 스키마리스라 유연하게 대응 가능
- 수평 확장이 쉬움
- JSON 형태라 JavaScript와 궁합이 좋음
### Negative
- 복잡한 JOIN 쿼리가 어려움
- 트랜잭션 지원이 제한적
결정의 맥락(Context), 결정 내용(Decision), 결과(Consequences)를 기록한다. 나중에 봐도 "아, 그래서 이렇게 했구나" 하고 이해할 수 있다.
복잡한 아키텍처를 글로만 설명하려니 한계가 있었다. 그림 하나가 천 마디 말보다 낫다.
처음엔 draw.io로 그렸는데, 코드가 바뀔 때마다 그림도 수정하기 힘들었다. Mermaid를 알고 나서 모든 게 바뀌었다:
```mermaid
graph TD
A[User] -->|HTTP Request| B[API Gateway]
B --> C[Auth Service]
B --> D[User Service]
C --> E[(Redis)]
D --> F[(PostgreSQL)]
Markdown 안에서 다이어그램을 코드로 작성한다. Git으로 버전 관리되고, 수정도 쉽다. GitHub도 이제 Mermaid를 지원한다.
시퀀스 다이어그램도 만들 수 있다:
```mermaid
sequenceDiagram
User->>+API: Login Request
API->>+Auth: Verify Credentials
Auth->>+DB: Check User
DB-->>-Auth: User Data
Auth-->>-API: JWT Token
API-->>-User: Login Success
개발자는 시간이 없다. 긴 설명 읽고 싶지 않다. 예제부터 본다.
그래서 문서를 이렇게 바꿨다:
Before:After:이 라이브러리는 매우 강력한 기능을 제공합니다. 먼저 설정 객체를 생성해야 하며, 이 객체는 여러 프로퍼티를 가질 수 있습니다...
// Quick Start
import { createClient } from 'awesome-lib';
const client = createClient({
apiKey: 'your-key',
timeout: 5000
});
const data = await client.fetch('/users');
예제를 먼저 보여준다. 복붙해서 바로 쓸 수 있게. 그다음에 상세한 설명을 붙인다.
스캔 가능하게 만든다:
최근엔 AI를 활용한다. 코드를 던져주고 "이 함수 설명해줘"라고 하면 초안을 만들어준다.
GitHub Copilot이 JSDoc 주석을 자동 완성해준다:
/**
* [Copilot이 자동으로 생성]
* Calculates the total price including tax
*
* @param price - Base price before tax
* @param taxRate - Tax rate as decimal (e.g., 0.1 for 10%)
* @returns Total price with tax applied
*/
function calculateTotal(price: number, taxRate: number): number {
return price * (1 + taxRate);
}
100% 정확하진 않지만 시작점으로는 충분하다. 내가 다듬기만 하면 된다.
문서를 방치하면 썩는다. 코드는 바뀌었는데 문서는 옛날 버전이다.
그래서 문서를 코드처럼 관리한다:
markdownlint로 문서 형식 검사# .github/workflows/docs.yml
name: Docs Check
on: [pull_request]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Markdown Lint
run: npx markdownlint '**/*.md'
- name: Check Links
run: npx markdown-link-check '**/*.md'
문서 변경을 PR 체크리스트에 넣는다:
## PR Checklist
- [ ] 코드 변경사항
- [ ] 테스트 추가/수정
- [ ] 문서 업데이트 (README, API docs, Changelog)
처음엔 문서 쓰는 게 시간 낭비 같았다. 코드 짜는 게 더 생산적이라고 생각했다. 하지만 이해했다. 문서는 비용이 아니라 투자라는 걸.
좋은 문서가 있으면:
문서 작성 체크리스트:
README문서는 코드의 얼굴이다. 첫인상을 좌우한다. 시간 들여서 잘 만들 가치가 있다.