본문으로 건너뛰기

© 2026 Molayo

Zenn헤드라인2026. 06. 15. 08:10

1시간 뒤의 나는 타인이다. 그래서 사양서를 쓰게 되었다.

요약

개인 개발 시 AI와의 세션 단절과 개발자 자신의 망각 문제를 해결하기 위해 사양서(Specification)를 작성하는 SDD(사양 주도 개발) 방법론을 제안합니다. CLI형 AI를 활용하며 겪은 경험을 바탕으로, 설계 의도를 문서화하여 AI를 의사 팀 멤버로 활용하는 과정을 다룹니다.

핵심 포인트

  • AI와 개발자 모두 세션이 끊기면 설계 맥락을 잊는다는 문제 인식
  • CLI형 AI는 코드베이스 전체를 파악하여 예상치 못한 설계 결함을 지적함
  • 사양서 작성을 통해 SDD(Specification-Driven Development) 구현 가능
  • 문서화를 통해 AI를 단순 도구가 아닌 '의사 팀 멤버'로 격상

요약

이 글은 개인 개발에서 AI와 대화를 이어가던 중, **사양서(specification)**를 쓰게 된 이야기이다.

동기는 정비가 아니라, 잊어버리기 때문이다. AI는 세션을 넘기면 설계 배경을 잊는다. 나는 1시간 뒤쯤에는 판단 근거를 잊는다. 이 문제를 해결하려고 쓰기 시작한 사양서가, 예상치 못하게 AI를 '의사 팀 멤버(pseudo team member)'로서 기능하게 만들었다.

결과적으로 SDD(사양 주도 개발, Specification-Driven Development)에 도달했다. 막연하게 모두가 혜택을 보고 있는 SDD의 이점을 스스로 방법론을 찾아냄으로써 언어화할 수 있었다. 그것이 이번 봄의 가장 큰 수확이었다.

개인 개발에서 SDD를 하는 의미, 그 구체적인 플로우, 실제 운용까지 정리한다.

AI를 도입하며 변한 것

경시(Competitive Programming)와 개인 개발, 그리고 AI

나는 평소에 알고리즘 문제 풀이(Competitive Programming)를 한다.

경시에서는 혼자서 단번에 코드를 만든다. 문제가 주어지고, 제약 조건 아래에서 코드를 작성해 AC(Accepted)가 나오면 끝이다. 도중에 설계 의도를 누군가에게 설명할 필요가 없다.

개인 개발에 CLI 기반의 AI를 도입하면서 그것이 변했다.

변한 것은 코드를 작성하는 속도만이 아니었다. 더 큰 변화는, 내가 예상하지 못했던 설계 문제나 트레이드오프(trade-off)를 지적받게 되었다는 점이었다. 혼자서는 깨닫지 못한 채 진행하던 것들이 대화(wall-hitting) 속에서 보이기 시작했다.

하지만 곧 번거로운 점을 깨달았다. 동일한 설계 판단의 배경을 몇 번이고 반복해서 설명해야 한다는 것이다.

왜 어떤 선택지를 기각했는지. 약간 불합리한 도메인 지식(domain knowledge) 유래의 판단 근거. 세션을 끊을 때마다 문맥(context)이 사라진다.

채팅형 AI와 CLI형 AI의 차이

지금까지의 AI 사용법은 채팅형 AI에 코드를 붙여넣고 "고쳐줘"라고 부탁하는 것이었다. 스스로 컨텍스트를 선택해서 붙여넣어야 하고, 내가 깨닫지 못한 문제는 지적받지 못한다.

CLI형 AI는 그것과 근본적으로 다르다. 코드베이스(codebase)를 직접 읽고, 여러 파일을 넘나들며 구조를 파악한다. 그래서 나 자신은 보이지 않았던 문제가 보였다.

인증이 작동하지 않고 있었다. Next.js는 프로젝트 루트의 middleware.ts라는 이름으로만 인증 미들웨어가 작동한다. 나는 처음에 그것을 proxy.ts라는 이름으로 만들고 있었다. 당연히 작동하지 않는다. 그동안 모든 API는 인증 없이 누구나 접근 가능한 상태였다.

이것은 스니펫(snippet)을 붙여넣고 "리뷰해줘"라고 부탁하는 것만으로는 깨닫지 못했을 것이다. 코드베이스 전체를 읽고 있었기에 보인 문제였다.

다만, 코드베이스를 읽어주는 대가로(?) 세션을 넘기면 전부 잊어버린다.

1시간 뒤의 나는 타인

처음에는 세션이 끊길 때마다 다시 설명해야 하는 문제가 AI의 문제라고 생각했다. 하지만 아니었다.

1시간 동안 코드를 쓰지 않으면, 나 자신도 방금 전의 판단을 잊고 있다. "왜 여기를 이렇게 썼더라", "왜 이 라이브러리를 골랐더라"

몇 주 동안 프로젝트를 방치하면, 그것은 이미 기억나지 않는다. 개인 개발은 방치 기간이 생기기 쉬우며, 오랜만에 열었을 때는 다음에 무엇을 해야 할지 파악하는 데만 시간이 걸린다.

잊어버리는 것은 AI뿐만이 아니다. AI도 나도, 세션을 넘기면 잊어버린다.

그래서 사양서를 쓰게 되었다

평소 나는 Notion으로 많은 정보를 관리하고 있다. ↓이런 것을 이용하기도 한다 (광고)

그런 감각으로 사양을 메모해 나가면 되지 않을까 하는 생각에 도달했다. 동기는 문서를 정비하자는 것이 아니었다. 잊어버리기 때문에 쓴다. AI도, 나도.

처음부터 체계적으로 생각했던 것은 아니다. 문제에 부딪힐 때마다 임시방편적인 해답을 찾아 나갔다.

  • 세션을 넘길 때마다 다시 설명하는 것이 번거롭다 → 일단 기록해 두었다
  • AI가 기각한 선택지를 다시 제안해 온다 → 기각 이유도 쓰도록 했다
  • AI의 리뷰가 빗나가서 도움이 되지 않는다 → spec을 전달한 뒤 리뷰하게 했다
  • 정신 차려보니 근거 없이 사양을 바꾸고 있었다 → spec이 있으니 AI가 지적해 주었다
  • 오랜만에 돌아오면 아무것도 모르겠다 → 진척도와 상태도 MD(Markdown)로 남기도록 했다

하나하나가 "번거로우니까"라는 이유로 시작한 습관이었다. 그것이 쌓여서, 어느샌가 SDD라고 불리는 방법론에 도달해 있었다.

SDD(사양 주도 개발, Specification-Driven Development)란:

사양서를 먼저 작성하고, 그것을 기점으로 개발을 진행하는 방법론이다. 기존에는 팀 간의 인식을 맞추기 위해 쓰는 것으로 여겨져 왔다. 현대의 SDD에서는 거기에 AI가 더해진다.

MD를 선택한 이유

Word나 Excel은 사람은 읽기 쉽지만 AI는 읽고 쓰기 어렵고 에디터에 의존한다. csv는 AI에게는 읽고 쓰기 쉽지만 사람이 읽고 쓰기 어렵다. Markdown(이하 MD)을 선택한 이유는, AI도 읽기 쉽기 때문이다. 구조화된 텍스트로서 다룰 수 있다. 게다가 Notion 등이 아닌 파일로서의 MD를 사용하는 이유는 다음과 같다.

  • Git으로 관리할 수 있다

  • "왜 이 사양으로 정했는가"의 경위까지 남는다

  • 사양은 어느 시점에 고정되는 것이 아니라 개발 중에 변화해 가는 것이라 믿기에, 버전 관리(Version Control)를 할 수 있다는 점이 중요했다

  • AI가 직접 다룰 수 있다

  • AI가 직접 읽을 수도, 쓸 수도 있다

  • 읽는 것은 위와 같고, 쓰는 것은 후술한다

실제 사양서로는 이러한 기각 이유나 논의 과정도 남기도록 하고 있다.

What/Why는 MD에, How는 코드에

사양서에 구현의 상세 내용은 쓰지 않는다. 코드를 읽으면 알 수 있는 것을 MD에 써봤자, 코드가 바뀌었을 때 spec이 부식될 뿐이다. 쓰는 것은 "무엇을 하는가(What)"와 "왜 그렇게 하는가(Why)"뿐이다.

## 로그인 엔드포인트의 레이트 리밋 (Rate Limit)
### What
동일 IP에서 5회 연속 실패 시 15분간 차단(Lockout). 차단 중에는 429를 반환한다.
...

How를 쓰지 않음으로써 spec의 유지보수 비용을 낮춘다. 구현 방법이 바뀌어도 What과 Why는 바뀌지 않는 경우가 많으므로, spec을 업데이트할 필요가 없다.

AI가 spec을 쓴다

사양서를 쓰는 비용이 높으면 지속할 수 없다. 벽치기(Wall-hitting, 대화형 논의)를 통해 논의한 내용을 그대로 AI에게 spec 형식으로 쓰게 한다. "지금의 논의를 MD로 정리해줘"라고 부탁하는 것만으로 문서가 만들어진다. 그것을 빠르게 확인하면 바로 구현으로 넘어갈 수 있다. 문서화에 걸리는 시간이 거의 제로가 된 만큼, 구현에 사용할 수 있는 시간이 최대화된다.

spec 작성법

기각 이유도 명기한다

"왜 이것을 선택했는가"뿐만 아니라, "왜 이것을 선택하지 않았는가"도 남긴다.

## DB 선정
### 채택: Neon
### 기각: Supabase
...

기각 이유를 남김으로써 같은 논의를 두 번 하지 않아도 된다. AI가 같은 선택지를 다시 제안하는 일도 없어진다. 다음 세션에서 "왜 Supabase가 아닌가"라고 물어도, spec을 읽으면 대답할 수 있다.

논의할 때 정신이 없어서 타당하지 않은 이유로 기각해 버렸더라도, 근거가 남아 있다면 나중에 정상적인 상태일 때 객관적으로 바라볼 수 있다. 기각했다는 사실만 기억하고 있는 상태에서는 그것이 불가능하다.

바꿔서는 안 되는 항목

사양은 키워 나가는 것이지만, 무엇이든 바꿔도 되는 것은 아니다. 프로젝트 도중에 바꿔서는 안 되는 것이 있다. 바로 달성해야 할 사항이다.

이것을 바꾸면 그 이후의 모든 판단이 무너진다. 구현 방법(How)은 바꿔도 된다. 하지만 달성해야 할 사항은 spec에 고정하여 변경을 금지한다. 이는 issue나 PR 단위에서도 마찬가지다. 이것은 결코 일어나서는 안 될 본말전도이다.

spec이 실패하는 패턴

What/Why만 쓰기로 결정했어도, 정신을 차려보면 How까지 쓰기 시작할 때가 있다. 구현의 세세한 절차나 코드 샘플을 spec에 쓰기 시작하면, spec이 코드와 이중 관리 상태가 된다. 코드가 바뀔 때마다 spec도 업데이트해야 하므로 금방 부식된다.

또 다른 실패는 수단과 목적이 뒤바뀌는 패턴이다. 자명한 내용에 spec을 쓰게 되거나, spec이 잘 작성되었는지 등을 지나치게 시간을 들여 확인하는 등 spec을 만드는 것 자체에 주안점을 두는 것도 의미가 없다. spec을 만드는 것이 목적이 되어버린 것이다. 이것의 가장 중대한 예는 달성해야 할 사항을 바꿔버리는 것이다. 무엇을 위해 개발하고 있는가라는 문제다.

규칙은 하나: 설계 판단을 포함하는 변경에는 spec을 쓴다. 자명한 변경에는 쓰지 않는다.

spec이 성장해 나간다

(대개의 경우) spec은 처음부터 완벽하게 쓸 수 없다. 기능을 추가하기 전에 개요만 작성하고, 벽치기를 통해 상세 내용을 조정해 나간다. 구현 중에 사양의 빈틈을 발견하면 그 자리에서 spec을 업데이트한다.

spec의 Git 히스토리는 "왜 이 사양으로 결정되었는가"의 기록이 된다. "처음에는 이렇게 생각했지만, 벽치기를 통해 이런 문제가 발견되어 이렇게 바꿨다"라는 경위가 남는다. 이는 코드의 git blame으로는 알 수 없는 정보다.

spec이 AI를 유사 팀 멤버로 만들었다

사양서를 쓰기 시작하며 깨달은 것은, spec이 있음으로써 AI의 행동이 변한다는 것이었다. 단순하게 코드를 쓰는 어시스턴트가, **설계 의도를 알고 있는 리뷰어(Reviewer)**가 되었다.

팀에 있으면 보통 있는 기능이, spec+AI로 재현 가능하다:

팀에 있으면 보통 있는 기능spec+AI로 재현할 수 있는 것
코드 리뷰 (Code Review)spec의 의도에 따른 AI 리뷰
...

코드 리뷰 (Code Review)의 대체

혼자라면 코드를 봐줄 타인이 없다. 자신이 작성한 코드를 스스로 봐도, 전제를 너무 많이 공유하고 있어서 허점이 허점으로 보이지 않는다.

spec이 없는 상태에서 AI에게 코드 리뷰 (Code Review)를 부탁하면 일반적인 지적밖에 돌아오지 않는다. spec이 있으면 "이 API는 spec에 적힌 필터 사양을 충족하는가", "이 구현은 Why에 적은 이유와 정합성이 있는가"라는 관점에서 리뷰할 수 있다. 팀의 리뷰어 (Reviewer)가 사양을 읽고 나서 코드를 보는 것과 같은 일을 혼자+AI+spec으로 재현할 수 있다.

자의적인 사양 변경에 대한 억제

혼자 개발하고 있으면 이유 없이 사양을 바꿔버릴 수 있다. "역시 이쪽이 더 나을지도", "이쪽이 구현하기 편하네"라고 생각하며 근거 없이 설계를 바꾼다. 그것이 쌓이면 왜 지금의 구현이 되었는지 알 수 없게 된다.

spec이 있으면 AI가 "spec에는 이렇게 적혀 있는데, 변경하는 이유는 무엇인가"라고 파고들어 준다. 이것이 무의식적인 사양 변경에 대한 제동 장치가 된다. 혼자이기 때문에 오히려 이러한 외부로부터의 파고들기가 필요했다.

구현 전에 사양의 버그를 발견할 수 있음

혼자라면 자신이 생각한 사양은 자신 안에서는 논리가 맞는 것처럼 보인다. 모순을 외부에서 지적해 주는 설계 리뷰 (Design Review)가 없기 때문이다.

하지만 spec을 작성하고 AI와 벽치기 (Wall-hitting, 대화하며 아이디어 다듬기)를 하면, 구현 전에 "그 사양, 모순되어 있어"라고 지적해 준다. 코드를 작성한 뒤에 깨닫는 것보다 spec 단계에서 깨닫는 편이 수정 비용 (Cost)이 압도적으로 낮다.

spec이 테스트 설계의 기점이 됨

What에 적은 사양은 그대로 테스트 케이스 (Test Case)의 사양이 된다. "동일 IP에서 5회 연속 실패 시 15분간 차단"은 테스트 케이스로 다시 쓸 수 있다. spec을 작성해 두면 테스트를 작성할 때 "무엇을 테스트해야 하는가"로 고민하지 않는다. 덤으로 나중에 미뤄둘 구현도 메모해 둘 수 있다. 흐름이 끊기지 않을 때 Issue를 만들려고 몇 번이나 잊어버렸던가.

유지보수 비용 (Maintenance Cost)이 낮음

What/Why만 적고 있기 때문에 구현이 바뀌어도 spec은 쉽게 부패하지 않는다. 속도 제한 (Rate Limiting) 구현을 메모리에서 Redis로 바꿔도 "왜 속도 제한이 필요한가 (Why)", "무엇을 하는가 (What)"는 변하지 않는다. 또한 오랜만에 코드를 읽을 때, 구현보다 먼저 spec을 읽으면 "어떤 의도로 이것이 구현되어 있는가"의 개요를 알 수 있다.

진척 관리용 MD

무엇이 어디까지 끝났는지를 MD로 관리한다. spec의 태스크 리스트 (Task List)를 그대로 진척 관리에 사용한다.

## 태스크 (Task)
- [x] DB 마이그레이션 (Migration)
- [x] 타입 정의 (Type Definition)
...

Issue의 체크리스트로 적어 두면, 오랜만에 돌아왔을 때 "다음에 할 일은 API 구현"이라고 즉시 알 수 있다. 서브 태스크 (Sub-task)를 파악할 수 있을 뿐만 아니라, 태스크 내에서의 진행 정도도 관리하기 쉬워진다.

탄생한 플로우 (Flow)

사양서를 쓰게 된 이후로 개발 플로우 (Flow) 전체가 바뀌었다.

spec → Issue → Branch → PR

기능을 추가할 때 가장 먼저 하는 일은 사양서를 쓰는 것이다. CLI의 AI와 벽치기를 하며 설계를 굳히고, 그것을 spec으로 기록한다. spec에서 Issue가 태어나고, Issue에서 브랜치 (Branch)가 태어난다.

혼자인데도 Issue를 만드는 의미는 무엇을 할지에 대한 메모로서 기능하기 때문이다. Issue에는 "spec에 기반하여 〇〇를 구현한다"라고 적어 두기만 하면 된다. 1시간 뒤의 내가 리포지토리 (Repository)를 열었을 때, Issue를 읽으면 컨텍스트 (Context)를 되찾을 수 있다. PR은 spec의 What/Why가 그대로 설명 (Description)이 된다.

기능 단위로 spec 파일을 분리

specs/
rule.md ← spec 작성법·명명 규칙 등의 메타 spec
rate-limiting.md ← 속도 제한 (Rate Limiting) spec
...

기능이나 테마 단위로 파일을 분리함으로써 "이 기능에 관한 판단은 여기를 읽으면 알 수 있다"라는 구조가 된다. 횡단적인 규칙 (명명 규칙, 구현 방침 등)은 별도 파일로 분리한다.

MD가 너무 커지지 않는다는 장점도 있다. 실제 개발에서 코드를 작성할 때와 가까운 의식이 된다는 느낌이 든다. 파일이 늘어나면 실제 코드와 맞춘 폴더 계층을 만들면 읽기 쉽고 관리하기 편하다.

메타 spec: spec 작성법 자체를 spec으로 만들기

spec(사양서)이 늘어나면, "어떻게 쓸 것인가" 자체를 미리 정해둘 필요가 생긴다. 어떤 입도(granularity)로 spec을 작성할지, 파일 이름의 명명 규칙(naming convention), What/Why 포맷, 기각 이유나 트레이드오프(trade-off)의 명기, 어떤 변경 사항에 spec이 필요한지 등이다.

이를 rule.md와 같은 형태로 관리해 두면, CLI 기반의 AI가 새로운 세션에서 이를 읽어 들였을 때 spec 작성법까지 파악한 상태로 시작할 수 있다. 세션마다 작성 내용을 누락하거나 작성 방식이 달라지는 것을 방지할 수 있으며, 통일된 포맷의 spec을 갖출 수 있다.

spec이 Issue의 입도를 결정한다

spec을 작성하면 자연스럽게 Issue의 입도가 결정된다. 실제 어떤 md 파일에는 "DB 마이그레이션(DB migration)", "타입 정의와 유효성 검사(Validation)", "API 정비", "UI 정비"라는 단위로 태스크가 적혀 있었다. 그것이 그대로 Issue가 되었다. spec이 있으면 "Issue를 어떻게 나눌 것인가"로 고민할 필요가 없다.

구현 순서

기능을 추가할 때는 다음 순서로 진행한다:

사양서(MD) → DB 마이그레이션(DB migration) → 타입 정의 → 유효성 검사(Validation) → API → UI

이 순서에는 이유가 있다. 타입과 유효성 검사가 먼저 결정되어 있기 때문에 API를 작성할 때 망설임이 없다. API 사양이 결정되어 있기 때문에 UI를 작성할 때 망설임이 없다. 재작업(rework)이 거의 사라졌다.

오버엔지니어링(over-engineering) 방지

기각 이유에 "〇〇는 현시점에서 불필요, over-engineering"이라고 적어 두면, AI가 같은 제안을 반복하지 않게 된다. "Redis를 사용하여 레이트 리밋(rate limit)을 분산 대응하는 것은 어떨까요?"라고 제안받더라도, "spec에 메모리 구현으로 충분하다고 적혀 있다"라고 답할 수 있다. spec이 over-engineering에 대한 방벽이 된다. 거기서 새로운 의문이 생기면 다시 논의하면 된다.

벽치기(Wall-hitting/Brainstorming)의 실제 흐름

실제 벽치기는 다음과 같은 흐름으로 진행되는 경우가 많다:

  • "〇〇라는 기능을 추가하고 싶다"라고 말한다.
  • AI가 "어떤 요구사항인가", "어떤 사용자가 사용하는가"라고 되묻는다.
  • 대답해 나가는 과정에서 스스로 모호했던 부분이 명확해진다.
  • 몇 가지 선택지를 제시받고, 트레이드오프(trade-off)를 묻는다.
  • 결정된 내용을 What/Why 형식으로 MD에 정리해 달라고 한다.
  • 그것을 spec으로 커밋(commit)한다.

포인트는 AI에게 "구현해 줘"가 아니라 "함께 생각해 줘"라고 부탁하는 것이다. AI가 "이런 방법도 있다"라고 제시하는 것들 속에 스스로는 깨닫지 못했던 관점이 포함되어 있는 경우가 많다.

요약

이번 봄에 변한 것은 사양서에 대한 인식이었다.

"팀이 늘어나면 쓰는 것"이라고 생각했던 사양서가, "혼자서 AI와 개발하기 때문에 필요한 것"이 되었다. 동기는 정비가 아니라, 잊어버리기 때문이다. AI도, 나 자신도.

그리고 사양서를 작성함으로써, 예상치 못하게 AI가 가상 팀 멤버로서 기능하게 되었다. 코드 리뷰, 사양 변경에 대한 지적, 설계에 대한 벽치기. 팀에 있으면 당연하게 존재하는 기능들이 spec이 있음으로써 1인+AI로 재현되었다.

내가 하고 있었던 일은 SDD라고 불리는 기법과 거의 동일했다.

"왜 그 관행이 필요한가"를 경험을 통해 설명할 수 있는 것은 스스로 도달한 사람뿐이라고 생각한다. 기각 이유를 쓰는 이유는 무엇인가. spec을 AI에게 전달하는 이유는 무엇인가. 사양 변경을 AI가 지적하게 만드는 이유는 무엇인가. 그저 팀의 방침으로서 사용하고 있을 뿐인 사람은 "베스트 프랙티스(best practice)니까"라고밖에 말할 수 없다. 하지만 나에게는 각각을 필요로 했던 순간이 있다.

막연하게 모두가 혜택을 보고 있는 SDD의 이점을 스스로 기법을 찾아냄으로써 언어화할 수 있었다. 그것이 이번 봄의 가장 큰 수확이었다.

1시간 뒤의 나는 다시 타인이 된다. 그래서 오늘도 spec을 키워나가고 있다.

Discussion

AI 자동 생성 콘텐츠

본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0