개요
개발의 패러다임이 바뀌고 있다
개발의 패러다임이 바뀌었다. 불과 몇 년 전만 해도 개발자가 한 줄 한 줄 직접 코드를 작성해야 했지만, 이제는 AI가 개발의 대부분을 수행해준다. 전체적인 아키텍처 설계 면에서는 아직 인간의 통찰력이 필요하지만, 단순 기능 구현을 지시하면 AI는 불과 몇 초 만에 결과물을 내놓는다.
물론 AI에게 모든 것을 맹목적으로 맡기면 심각한 기술 부채로 이어진다. 하지만 인간이 뼈대를 세우고, AI가 살을 붙이면 이야기가 달라진다. '초안 구현'은 AI에게 맡기고, 인간은 그 결과물을 검수하고 리팩토링하는 워크플로우를 구축했을 때 생산성은 폭발적으로 증대된다. 이제 개발의 대세는 AI에게 대부분의 작업을 시키고, 개발자는 그 내용을 검증하는 방식이다.
이러한 변화는 채용 시장에도 직접적인 영향을 미치고 있다. AI가 생성한 코드의 품질을 판단하고 수정하는 데에는 깊이 있는 지식이 필요하기 때문이다. 이에 따라 단순 코딩을 수행할 신입 개발자를 뽑기보다는, AI와 협업하여 전체를 조율할 수 있는 팀장급 개발자들이 직접 개발하고 검수하는 소수 정예 방식이 선호되는 추세다.
문서화가 중요한 시대
그렇다면 AI가 구현의 전반을 담당하는 이 시대에, 개발자가 집중해야 할 가장 중요한 업무는 무엇일까? 필자는 그것이 바로 '만들어진 코드의 문서화(Documentation)'라고 생각한다.
혹자는 '코드 설명이나 주석도 AI가 다 써주지 않는가?'라고 반문할 수 있다. 하지만 AI가 작성한 코드는 본질적으로 내가 짜지 않은 코드, 즉 남의 코드와 같다. 내가 직접 타이핑하지 않았기에 로직의 디테일과 흐름을 인간이 100% 장악하지 못하는 경우가 빈번하다.
따라서 개발자가 수행하는 문서화는 단순한 코드 설명이 아니다. AI가 쏟아낸 방대한 코드를 역으로 분석하여, '이 코드가 의도대로 작동하는지 검증하고, 전체 시스템 맥락 안에서 어떤 역할을 하는지 정의하는 과정'이다. AI가 써준 1차원적인 주석을 넘어, 비즈니스 로직의 인과관계와 히스토리를 사람이 검수하고 기록으로 남겨야만 비로소 그 코드는 블랙박스가 아닌 우리의 자산이 된다. 결국 AI 시대의 문서화란, AI가 만든 코드에 대한 인간의 최종 승인 도장이자 통제권을 유지하는 핵심 수단이다.
좋은 문서
좋은 문서란 무엇인가?
6개월 전의 내가 작성한 코드를 보며 '도대체 무슨 생각으로 이렇게 짰지?'라며 혼란스러워 해 본 경험이 있을 것이다. 코드는 기계가 실행할 명령이기에 기능이 어떻게(How) 돌아가는지는 완벽하게 보여주지만, 정작 개발자가 궁금해하는 왜(Why) 그렇게 짰는지는 말해주지 않는다.
좋은 문서는 바로 이 Why의 빈틈을 채우는 것이다. 단순히 코드를 한글로 번역하는 것이 아니라, 코드가 작성된 배경, 의도, 제약 사항을 기록하여 미래의 동료와 나 자신이 불필요한 추론과 삽질을 하지 않게 돕는 것. 이것이 진짜 문서화이며, 커뮤니케이션 비용 절감과 DX(Developer Experience) 극대화의 지름길이다.
거창한 위키(Wiki)가 아니더라도, 코드 바로 옆에서 가장 강력하게 Why를 남길 수 있는 도구, JSDoc과 Storybook을 활용한 실전 전략을 소개한다.
JSDoc: 단순한 주석이 아니라 IDE와의 대화
JSDoc을 단순히 설명서라고 생각하면 오산이다. JSDoc은 VS Code와 소통하여 자동 완성을 제공하는 강력한 DX 도구다.
실무 필수 태그 3대장
복잡한 건 몰라도 이 3가지는 반드시 써야 한다.
- @param: 무엇을 넣어야 하는가? (변수명, 타입, 의미)
- @returns: 무엇이 나오는가? (Promise 여부 중요)
- @example: (가장 중요) 백 마디 설명보다 한 줄의 예시 코드가 낫다. 개발자는 예제 코드를 복사해서 쓰는 것을 가장 좋아한다.
객체 파라미터 구조 분해 설명하기
함수 파라미터로 객체를 받을 때, 단순히 object라고 적으면 IDE 자동완성이 안 된다. 다음과 같이 내부 속성을 명시해야 한다. ▼
/**
* API 요청을 보낸다.
*
* @param {Object} config - 설정 객체
* @param {string} config.url - API 엔드포인트
* @param {string} [config.method='GET'] - (선택) HTTP 메서드. 기본값은 GET.
*/
function request({ url, method = 'GET' }) { ... }
이렇게 작성하면 `config.`을 입력하는 순간 url, method가 자동 완성 목록에 뜬다. 이것이 바로 친절한 코드다.
실전 사례 : Modal 컴포넌트 문서 작성
실제 Modal 컴포넌트의 문서를 리팩토링한 사례를 통해, 잘 쓰인 JSDoc이 팀의 생산성과 코드 품질을 어떻게 변화시키는지 확인해 보자.
단순한 스펙을 넘어 가이드라인을 심다
보통의 주석은 size: string 옆에 "크기"라고 적는 수준에 그친다. 하지만 좋은 문서는 '언제 써야 하고, 언제 쓰지 말아야 하는지(When & Do/Don't)'를 코드 안에 심어둔다.
다음은 리팩토링된 `Modal`컴포넌트의 실제 JSDoc이다. 이 주석 하나가 사내 위키 페이지를 대체한다. ▼
/**
* 사용자에게 중요한 정보를 표시하거나 입력을 요구하는 오버레이 대화상자입니다.
*
* ## When (언제 사용해야 하는가)
* **사용해야 하는 경우:**
* - **중요한 의사 결정**: 삭제, 저장, 제출 등 되돌릴 수 없는 작업 확인
* - **필수 정보 입력**: 다음 단계로 진행하기 전 반드시 입력해야 하는 정보 수집
*
* **사용하지 말아야 하는 경우:**
* - **긴급하지 않은 알림**: {@link Toast} 컴포넌트를 사용하세요
* - **복잡한 다단계 폼**: 별도 페이지로 이동하세요
*
* ## Usage guidelines
* ### ✅ Do (권장 사항)
* - **명확한 제목 사용**: "확인" 대신 "항목 삭제"처럼 목적이 드러나게 작성하세요.
* - **행동 지향적 버튼**: "확인" 대신 "삭제하기"처럼 동작을 명확히 표현하세요.
*
* ### 🚫 Don't (주의 사항)
* - **모달 위에 모달을 띄우지 마세요**: UX가 복잡해지고 혼란스러워집니다.
* - **긴 스크롤 피하기**: 내용이 너무 길다면 별도 페이지가 낫습니다.
*
* ## Accessibility (접근성)
* 이 컴포넌트는 WAI-ARIA Dialog 패턴을 따릅니다.
* - `Esc` 키로 닫기 지원 및 `Tab` 포커스 트랩(Focus Trap) 자동 적용
* - 스크린 리더를 위한 `aria-labelledby`, `aria-describedby` 자동 연결
*
* @example
* // 기본적인 확인 모달 사용 예시
* <Modal
* open={open}
* onOpenChange={setOpen}
* title="항목 삭제"
* footer={<Button variant="destructive">삭제하기</Button>}
* >
* 정말로 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.
* </Modal>
*/
export const Modal = React.forwardRef<HTMLDivElement, ModalProps>( ... );
보완 포인트 및 팁
- Do/Don't 섹션 강조: 제공해주신 내용 중 가장 인상적인 부분이 Usage guidelines였습니다. 이를 활용해 문서가 단순 스펙 전달을 넘어 UX 교육까지 담당할 수 있음을 강조했다.
- 구체적인 예시로 설득력 강화: 모달 위에 모달 같은 구체적인 사례를 들어 실무자들이 공감할 수 있는 포인트(잔소리 대체 효과)를 짚었다.
- Example 태그 표준화: 원본의 {@tool snippet}은 Dart/Flutter 생태계에서 주로 쓰이는 방식이므로, 범용적인 JavaScript/TypeScript 환경(VS Code)에 맞춰 Markdown 코드 블록 문법으로 다듬어 반영했다.
Best Practice Code
위의 원칙들을 모두 적용한 최종 Modal 컴포넌트 코드다. 문서화, 웹 접근성, 구현의 안전성을 모두 갖춘 형태다. ▼
/**
* 사용자에게 중요한 정보를 표시하거나 입력을 요구하는 오버레이 대화상자입니다.
*
* {@link Modal}은 사용자의 주의를 집중시켜야 하는 상황에서 사용됩니다.
* 모달이 열리면 배경이 어두워지며, 사용자는 모달과 상호작용하거나 닫기 전까지
* 페이지의 다른 부분과 상호작용할 수 없습니다.
*
* Radix UI의 Dialog 컴포넌트를 기반으로 구현되어 접근성과 키보드 내비게이션이
* 자동으로 처리됩니다.
*
* ## When (언제 사용해야 하는가)
*
* **사용해야 하는 경우:**
* - **중요한 의사 결정**: 삭제, 저장, 제출 등 되돌릴 수 없는 작업 확인
* - **필수 정보 입력**: 다음 단계로 진행하기 전 반드시 입력해야 하는 정보 수집
* - **중요 알림**: 사용자가 반드시 확인해야 하는 에러, 경고, 성공 메시지
* - **간단한 폼**: 페이지 전환 없이 빠르게 정보를 입력받아야 할 때
*
* **사용하지 말아야 하는 경우:**
* - **긴급하지 않은 알림**: Toast 컴포넌트를 사용하세요
* - **복잡한 다단계 폼**: 별도 페이지로 이동하세요
* - **많은 정보 표시**: 페이지 내 섹션으로 구현하세요
* - **비필수 부가 정보**: Popover나 Tooltip을 사용하세요
*
* ## Layout behavior
*
* 모달은 항상 화면 중앙에 고정되며, 배경 오버레이는 전체 화면을 덮습니다.
* 크기는 `size` prop에 따라 결정됩니다:
* - `sm`: 최대 너비 24rem (384px) - 간단한 확인 대화상자
* - `md`: 최대 너비 28rem (448px) - 기본값, 일반적인 폼이나 메시지
* - `lg`: 최대 너비 32rem (512px) - 많은 내용이나 복잡한 폼
*
* 모달의 높이는 콘텐츠에 따라 자동으로 조절되며, 화면을 넘어가면 스크롤이 발생합니다.
*
* ## Usage guidelines
*
* ### ✅ Do (권장 사항)
*
* - **명확한 제목 사용**: 모달의 목적을 한눈에 알 수 있는 제목을 제공하세요
* ```tsx
* <Modal title="항목 삭제">...</Modal> // Good
* <Modal title="확인">...</Modal> // Bad - 너무 모호함
* ```
*
* - **행동 지향적 버튼**: 버튼 레이블은 수행할 동작을 명확히 표현하세요
* ```tsx
* <Button>삭제하기</Button> // Good
* <Button>확인</Button> // Bad - 무엇을 확인하는지 불명확
* ```
*
* - **적절한 크기 선택**: 콘텐츠 양에 맞는 size를 선택하세요
* - **명확한 닫기 방법**: X 버튼, 취소 버튼, 배경 클릭 중 최소 하나는 제공하세요
* - **중요도에 따른 변형**: ConfirmModal, SuccessModal, ErrorModal 등 사용
*
* ### 🚫 Don't (주의/금지 사항)
*
* - **모달 위에 모달을 띄우지 마세요** - UX가 복잡해지고 혼란스러워집니다
* - **긴 스크롤이 필요한 콘텐츠는 피하세요** - 별도 페이지로 이동하는 것이 좋습니다
* - **자동으로 모달을 열지 마세요** - 사용자의 명시적 동작(클릭 등) 후에만 열어야 합니다
* - **모달 없이도 작업 가능한 경우** - 불필요하게 모달을 사용하지 마세요
* - **모바일에서 큰 모달(lg) 사용 지양** - 화면을 거의 다 차지하므로 페이지 이동 권장
*
* ## Accessibility
*
* 이 컴포넌트는 WAI-ARIA Dialog 패턴을 따릅니다:
*
* - **키보드 내비게이션**:
* - `Esc`: 모달 닫기
* - `Tab`: 모달 내부 포커스 순환 (모달 외부로 나가지 않음)
* - 모달이 열리면 첫 번째 포커스 가능한 요소로 자동 포커스
*
* - **스크린 리더**:
* - `role="dialog"`: 대화상자임을 인식
* - `aria-labelledby`: title이 모달의 레이블로 연결됨
* - `aria-describedby`: children 콘텐츠가 설명으로 연결됨
*
* - **포커스 트랩**: 모달이 열려있는 동안 포커스가 모달 내부에서만 순환합니다
* - **배경 스크롤 방지**: 모달이 열리면 body 스크롤이 자동으로 비활성화됩니다
*
* ## Example
*
* {@tool snippet}
* 기본적인 확인 모달 예시:
*
* ```tsx
* function DeleteConfirmDialog() {
* const [open, setOpen] = useState(false);
*
* return (
* <>
* <Button onClick={() => setOpen(true)}>삭제</Button>
* <Modal
* open={open}
* onOpenChange={setOpen}
* title="항목 삭제"
* footer={
* <div className="flex gap-2">
* <Button onClick={() => setOpen(false)} variant="outline">
* 취소
* </Button>
* <Button onClick={handleDelete} variant="destructive">
* 삭제하기
* </Button>
* </div>
* }
* >
* 이 항목을 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.
* </Modal>
* </>
* );
* }
* ```
* {@end-tool}
*
* {@tool snippet}
* 성공 아이콘이 포함된 모달 예시:
*
* ```tsx
* <Modal
* open={open}
* onOpenChange={setOpen}
* icon={<CheckCircle className="w-12 h-12 text-green-500" />}
* title="저장 완료"
* footer={
* <Button onClick={() => setOpen(false)} className="w-full">
* 확인
* </Button>
* }
* >
* 데이터가 성공적으로 저장되었습니다.
* </Modal>
* ```
* {@end-tool}
*
* {@tool snippet}
* 큰 크기의 폼 모달 예시:
*
* ```tsx
* <Modal
* open={open}
* onOpenChange={setOpen}
* title="새 사용자 추가"
* size="lg"
* footer={
* <div className="flex justify-end gap-2">
* <Button variant="outline" onClick={() => setOpen(false)}>
* 취소
* </Button>
* <Button onClick={handleSubmit}>저장</Button>
* </div>
* }
* >
* <form className="space-y-4">
* <TextInput label="이름" required />
* <TextInput label="이메일" type="email" required />
* <TextInput label="전화번호" />
* </form>
* </Modal>
* ```
* {@end-tool}
*
* See also:
*
* - {@link ConfirmModal}, 간단한 확인 모달을 위한 사전 구성된 변형
* - {@link SuccessModal}, 성공 메시지를 표시하는 모달
* - {@link ErrorModal}, 에러 메시지를 표시하는 모달
* - {@link WarningModal}, 경고 메시지를 표시하는 모달
* - {@link DeleteModal}, 삭제 확인을 위한 모달
* - {@link Toast}, 긴급하지 않은 알림을 위한 컴포넌트
* - {@link Popover}, 비필수 부가 정보를 표시하는 컴포넌트
*/
Storybook과의 시너지
JSDoc이 코드를 짜는 순간(IDE)의 생산성을 책임진다면, Storybook은 전반적인 파악과 협업(Browser)을 책임진다.
코드를 직접 열어보지 않는 디자이너, 기획자, 혹은 우리 팀의 디자인 시스템을 처음 접하는 신규 입사자에게 VSCode 주석을 보라고 할 수는 없지 않은가? 그래서 나는 배포된 Storybook 문서에도 JSDoc 못지않은 정성을 쏟았다.
Autodocs와 ArgTypes의 활용
단순히 컴포넌트만 렌더링 하는 게 아니라, argTypes를 통해 각 속성이 어떤 맥락에서 쓰여야 하는지 구체적으로 명시했다. ▼
const meta: Meta<typeof Button> = {
title: "Components/Button",
component: Button,
parameters: {
layout: "centered",
docs: {
description: {
component: "Design System의 기본 버튼 컴포넌트입니다.",
},
},
},
// 이 태그 하나면 별도 문서 작업 없이도 JSDoc과 연동된 문서 페이지가 생성된다.
tags: ["autodocs"],
argTypes: {
variant: {
control: "select",
options: ["default", "secondary", "outline", "ghost", "link"],
// 단순한 스타일 설명이 아니라, '언제' 써야 하는지 UX 가이드를 포함했다.
description:
"버튼의 스타일 변형입니다. 페이지 내 버튼의 중요도와 맥락에 따라 선택하세요. default는 주요 액션, secondary는 보조 액션에 사용합니다.",
table: {
type: { summary: "default | secondary | outline | ghost | link" },
defaultValue: { summary: "default" },
},
},
size: {
control: "select",
options: ["default", "sm", "lg", "icon"],
description:
"버튼의 크기입니다. 'icon'은 아이콘 전용 정사각형 버튼(10x10)에 사용합니다. 일반 버튼에는 default, sm, lg를 사용하세요.",
table: {
type: { summary: "default | sm | lg | icon" },
defaultValue: { summary: "default" },
},
},
...
},
};
...
시각적 테스트를 위한 스토리 구성
개별 스토리뿐만 아니라, AllVariants 같은 스토리를 만들어 한눈에 모든 스타일을 비교할 수 있게 구성했다. 이는 개발자가 작업하며 UI 리그레션(회귀)을 체크하기에도 매우 유용하다. ▼
export const Default: Story = {
args: {
children: "Default",
},
};
export const Secondary: Story = {
args: {
variant: "secondary",
children: "Secondary",
},
};
// ...중략
export const Disabled: Story = {
args: {
disabled: true,
children: "Disabled",
},
};
// 모든 변형을 한눈에 볼 수 있는 스토리
export const AllVariants: Story = {
render: () => (
<div className="flex gap-4 flex-wrap">
<Button>Default</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="link">Link</Button>
</div>
),
parameters: {
docs: {
description: {
story: "모든 버튼 변형을 한 번에 보여줍니다.",
},
},
},
};
마치며
좋은 코드는 기계가 이해하기 쉬운 코드지만, 위대한 코드는 사람이 이해하기 쉬운 코드다.
- JSDoc으로 IDE 내에서 개발 흐름이 끊기지 않게 돕고,
- Storybook으로 전체 숲을 조망하고 협업할 수 있게 돕는다.
'Develop > Web' 카테고리의 다른 글
| [Web] 시맨틱 태그 적용 작업 (3) | 2025.12.12 |
|---|---|
| [Web][Issue] MUI의 INP 성능 저하 문제 (0) | 2025.12.12 |
| [JS] Named export와 Default export, 둘 중에 뭘 써야할까? (0) | 2025.12.09 |
| [React][Issue] 간단한 알고리즘과 함께 말줄임 디테일 챙기기 (0) | 2025.12.01 |
| [React] Spread Attributes를 조심해야 하는 이유 (0) | 2025.11.26 |