Featured Post

AI 코딩, 결국 스펙부터더라: Kiro에서 토큰 아끼며 배운 현실적인 이야기

Kiro - 스펙주도개발 관련 이미지

AI랑 코딩하다가 어느 날 딱 걸린 생각

요즘 저도 회사에서 팀원들이랑 이런저런 이야기를 하다 보면 바이브 코딩(Vibe Coding) 얘기를 꽤 자주 하게 돼요. 예전 같으면 개발자가 혼자 IDE 붙잡고 끙끙대던 시간이 많았는데, 이제는 CursorGitHub Copilot 같은 도구를 옆에 켜두고 같이 일하는 느낌이 들잖아요. 편하긴 정말 편합니다. 그런데 신기하게도, 편한 만큼 또 다른 피곤함이 생기더라고요. 토큰은 생각보다 빨리 닳고, 프롬프트를 조금만 대충 던지면 AI가 아주 자신감 있게 엉뚱한 코드를 만들어오고요. 저도 개발을 오래 해왔지만, 요즘 들어 더 자주 느낍니다. 결국 중요한 건 “AI를 얼마나 잘 쓰느냐”보다 “내가 뭘 만들고 싶은지 얼마나 또렷하게 알고 있느냐”더라고요.

모니터 앞에서 AI 코딩 도구를 켜두고 작업하는 개발자 모습

사실 이 글을 쓰게 된 계기는 지난주에 제대로 한 번 삽질을 했기 때문이에요. 팀에서 새 마이크로서비스를 하나 만들어야 했는데, 저도 살짝 욕심이 났죠. “이번엔 AI한테 많이 맡겨보자. 속도 좀 내보자.” 이런 마음이었어요. 그래서 요구사항을 대충 정리해서 던졌습니다. 그랬더니 AI가 엄청 긴 코드를 뱉어내더라고요. 화면은 뭔가 그럴듯했어요. 문제는 토큰을 10만 개 넘게 써버렸고, 결과물은 우리가 원한 스펙이랑 꽤 많이 달랐다는 겁니다. 결국 밤에 커피 한 잔 놓고 다시 뜯어고쳤어요. 그때 좀 허탈하더라고요. 뭐랄까, AI한테 일을 맡긴 게 아니라 AI가 만든 일을 제가 다시 수습한 느낌이었거든요.

그날 이후로 제 생각이 조금 바뀌었습니다. AI 코딩을 잘하려면 프롬프트를 예쁘게 쓰는 것보다 스펙을 먼저 정리해야 한다는 쪽으로요. 이게 제가 Kiro 프로젝트를 하면서 가장 크게 배운 부분입니다. 스펙주도개발(Specification-Driven Development)이라는 말이 거창하게 들릴 수 있는데, 실제로 해보면 그렇게 어려운 이야기는 아니에요. “무엇을 만들지”를 먼저 선명하게 적어두고, AI가 그 안에서 움직이게 만드는 방식입니다. 사람한테도 그렇잖아요. 목적지를 알려주지 않고 “운전 잘해봐”라고 하면 어디로 갈지 모르니까요.

AI 바이브 코딩을 하다 보니 스펙주도개발이 더 중요해졌다

Kiro는 데이터 파이프라인 쪽 로직이 꽤 많은 프로젝트였어요. 입력 데이터도 다양하고, 변환 규칙도 있고, 검증 조건도 있었죠. 처음에는 저도 별생각 없이 AI에게 이렇게 말했습니다. “여기 데이터 변환 로직 좀 짜줘.” 그랬더니 매번 결과가 달랐어요. 어떤 날은 예외 처리를 과하게 넣고, 어떤 날은 타입 변환을 제멋대로 하고, 또 어떤 날은 없는 요구사항까지 친절하게 추가해주더라고요. 친절한데 부담스러운 친구 같은 느낌이랄까요.

그래서 방식을 바꿨습니다. AI에게 바로 코드를 부탁하지 않고, 먼저 스펙(Specification)을 적었어요. 입력은 무엇인지, 출력은 무엇인지, 예외 상황에서는 어떻게 해야 하는지, 절대 추가하면 안 되는 동작은 무엇인지. 이걸 정리한 뒤에 AI에게 작업을 맡겼습니다. 그러니까 확실히 달라지더라고요. AI가 추측하는 영역이 줄어드니까 코드가 훨씬 단정해졌고, 토큰도 덜 먹었습니다.

프롬프트를 길게 쓰는 게 아니라, 쓸데없는 추측을 줄이는 게 핵심이었다

처음에는 저도 프롬프트를 길게 쓰면 더 좋은 결과가 나올 거라고 생각했어요. 그런데 막상 해보니 꼭 그렇지는 않더라고요. 길어도 모호하면 AI가 계속 상상합니다. 반대로 짧아도 조건이 정확하면 꽤 괜찮은 결과가 나와요. Kiro에서 이메일 검증 유틸리티를 만들 때도 그 차이가 확실히 보였습니다.

예전에 하던 방식 스펙을 정리한 뒤의 방식
"이메일 검증 함수 하나 만들어줘"라고 요청. 토큰은 5000개 가까이 쓰고, 정작 null 처리나 반환 기준은 애매했음 "스펙: 입력은 문자열, 반환은 boolean. 유효 이메일 형식은 RFC 5322 기준. null 입력 시 false 반환"이라고 요청. 토큰은 약 1200개 수준으로 줄고 결과도 정확했음
"데이터 변환 파이프라인 만들어줘"라고 요청. 토큰은 2만 개 넘게 쓰고, 필요 없는 로직이 섞였음 "스펙: 입력 JSON에서 field_a를 추출하고, 필드명은 field_b로 변경. 타입은 string 고정. 출력은 배열"이라고 요청. 약 3000개 토큰으로 원하는 코드에 가까웠음

이 차이가 생각보다 큽니다. 토큰을 아끼는 것도 좋지만, 더 중요한 건 제가 AI 결과물을 다시 읽고 고치는 시간이 줄었다는 거예요. 솔직히 말하면 스펙 쓰는 일, 처음엔 좀 귀찮습니다. 개발자 입장에서 “그냥 코드부터 치면 안 되나?” 싶은 마음이 올라오죠. 저도 그랬고요. 그런데 한두 번 제대로 효과를 보고 나면 생각이 바뀝니다. 스펙을 쓰는 시간이 낭비가 아니라, 나중에 삽질할 시간을 미리 줄이는 보험처럼 느껴져요.

Kiro에서 몇 번 측정해보니, 비슷한 작업 기준으로 토큰 사용량이 평균 60% 정도 줄어드는 경우도 있었습니다. 물론 프로젝트 상황마다 다르겠지만, 제 체감은 꽤 분명했어요. AI가 알아서 잘해주길 기대하는 방식에서, AI가 벗어나지 못할 울타리를 쳐주는 방식으로 바뀐 거죠.

Cursor 설정을 조금만 손봐도 AI가 훨씬 덜 헤맨다

스펙을 잘 쓰는 것도 중요하지만, 매번 프롬프트에 같은 말을 반복하는 건 또 피곤하잖아요. 그래서 저는 프로젝트 설정 쪽도 같이 손봤습니다. 저는 주로 Cursor를 쓰는데, Kiro 프로젝트 루트에 .cursorrules 파일을 만들어두고 기본 규칙을 정해놨어요. 이거 별거 아닌 것처럼 보여도 효과가 꽤 좋습니다. 팀원들한테도 몇 번 이야기했는데, 다들 써보고 나서는 “이건 계속 두는 게 낫겠다” 쪽으로 의견이 모였어요.

.cursorrules에 AI가 지켜야 할 선을 미리 그어두기

Kiro에서 실제로 썼던 설정은 이런 느낌이었습니다.

You are an expert in Python and data pipeline.
  • Always strictly follow the specification (SPEC.md) in the project root.
  • When generating code, first read the relevant spec section from SPEC.md.
  • Do not add extra features unless explicitly requested.
  • Use type hints and docstrings as defined in the spec.
  • Keep output minimal: no comments that are not in the spec.

핵심은 단순해요. SPEC.md를 먼저 보고, 스펙에 없는 기능은 추가하지 말고, 불필요한 설명도 줄이라는 겁니다. AI가 가끔 “이것도 있으면 좋겠죠?” 하면서 친절하게 덧붙이는 기능들이 있잖아요. 작은 프로젝트에서는 그게 귀여워 보일 수도 있는데, 실무에서는 그런 코드가 나중에 부채가 됩니다. 리뷰할 때도 “이거 누가 넣었어요?” 하고 추적해야 하고요.

.cursorrules를 넣은 뒤에는 생성되는 코드의 톤이 꽤 안정됐습니다. 타입 힌트나 docstring 스타일도 맞아갔고, 팀원들이 AI로 만든 코드라고 해서 매번 스타일을 고칠 일이 줄었어요. 무엇보다 좋았던 건, 제가 프롬프트마다 “스펙 지켜줘, 이상한 기능 넣지 마, 간단히 해줘” 같은 말을 반복하지 않아도 된다는 점이었어요. 사소한데 은근히 편합니다. 여행 갈 때 캐리어에 항상 들어가는 멀티어댑터 같은 느낌이에요. 한 번 챙겨두면 계속 든든한 그런 거요.

SPEC.md는 길게 쓰되, AI에게는 필요한 부분만 보게 하기

또 하나 효과가 좋았던 방식은 스펙 문서를 SPEC.md로 따로 관리하는 거였습니다. 전체 스펙을 매번 프롬프트에 복사해서 넣으면 그 자체가 토큰 낭비예요. Kiro의 SPEC.md는 한때 500줄 정도까지 갔는데, 그걸 매번 통째로 넣었다면 아마 토큰 비용도 비용이고, AI도 집중을 못 했을 겁니다.

그래서 프롬프트를 이렇게 바꿨어요.

    • 프롬프트 예시: "Kiro SPEC.md의 'Data Validation' 섹션을 참고해서 이메일 검증 유틸리티를 만들어줘. 단, spec에 없는 에러 처리 로직은 추가하지 마."
    • 프롬프트 예시: "SPEC.md의 'Transform Rule - field mapping' 부분만 기준으로 구현해줘. 기존 함수 시그니처는 유지하고, 출력 포맷은 변경하지 마."
    • 프롬프트 예시: "SPEC.md의 'Failure Policy'를 확인하고 테스트 케이스만 작성해줘. 구현 코드는 건드리지 마."

이렇게 하면 AI가 필요한 부분만 보게 됩니다. 실제로 토큰 사용량이 체감상 1/5 수준까지 줄어든 작업도 있었어요. 물론 모든 도구와 환경에서 똑같이 동작한다고 말할 수는 없지만, 적어도 Cursor에서 프로젝트 문맥을 잘 잡아주는 상황이라면 꽤 쓸 만했습니다.

여기서 중요한 건 “SPEC.md를 만들었다”가 아니라 “SPEC.md를 AI가 참조하기 좋은 구조로 썼다”는 점이에요. 사람이 읽기 좋은 문서와 AI가 작업하기 좋은 문서는 살짝 다릅니다. 저는 섹션 제목을 꽤 명확하게 나눴어요.

섹션 이름 담은 내용 AI에게 요청할 때 좋은 점
Data Validation 입력값 검증 규칙, null 처리, 반환 기준 검증 유틸리티와 테스트 케이스를 만들 때 범위를 좁히기 좋음
Transform Rule 필드 매핑, 타입 변환, 출력 포맷 데이터 변환 로직에서 AI의 추측을 줄일 수 있음
Failure Policy 실패 시 재시도 여부, 로그 수준, 예외 처리 방식 AI가 과한 예외 처리를 덧붙이는 일을 막기 좋음

섹션을 이렇게 나눠두니까 팀원들도 편해졌어요. 누가 프롬프트를 작성하든 “SPEC.md의 어느 부분을 보라”고 말할 수 있으니까요. AI 코딩에서 팀 생산성을 높이는 건 결국 이런 작은 합의들이더라고요.

Kiro에서 제일 크게 배운 실패: 스펙도 너무 자세하면 독이 된다

물론 저도 처음부터 잘한 건 아닙니다. 오히려 초반에는 반대로 너무 많이 적었어요. 스펙을 명확하게 해야 한다는 생각이 강해서 거의 의사코드(Pseudo-code)처럼 써버렸거든요. “이 함수는 변수 a를 받고, 내부에서 for문을 돌고, 조건문으로 b를 확인하고…” 이런 식이었습니다. 그때는 나름 친절하다고 생각했는데, 지금 보면 AI의 손발을 묶어놓은 셈이었어요.

더 난감했던 건, 제가 쓴 의사코드에 논리 오류가 있었는데 AI가 그걸 그대로 따라 했다는 겁니다. AI가 똑똑하게 고쳐줄 줄 알았는데, 스펙을 너무 강하게 줘버리니까 그냥 복종하더라고요. 그 코드를 디버깅하면서 좀 웃겼습니다. “이거 AI가 바보라서가 아니라 내가 너무 자세하게 틀린 길을 알려줬구나.” 이런 생각이 들었어요.

그 뒤로 스펙을 쓰는 기준을 바꿨습니다. 스펙은 무엇을 해야 하는지(What)를 분명히 쓰고, 어떻게 구현할지(How)는 가능한 한 열어둔다는 쪽으로요. 물론 성능 제약이나 보안상 꼭 지켜야 하는 방식은 써야 합니다. 다만 구현 디테일까지 매번 고정하면 AI가 가진 장점을 못 쓰게 되더라고요.

제가 처음에 썼던 아쉬운 스펙 나중에 고친 스펙
"리스트를 for문으로 순회하면서 각 요소에 1을 더하고 새로운 리스트를 반환해." 구현 방식까지 고정되어 있고, 더 나은 표현을 선택하기 어려움 "입력: 정수 리스트. 출력: 각 요소에 1을 더한 새로운 리스트. 성능: O(n) 이하. 빈 리스트 입력 시 빈 리스트 반환." 목표와 제약 조건만 명확함

이렇게 바꾸니까 AI가 훨씬 자연스럽게 코드를 만들었습니다. Python에서는 리스트 컴프리헨션을 쓰기도 하고, 타입 힌트도 깔끔하게 붙이고요. 스펙은 짧아졌는데 결과는 좋아졌습니다. 참 묘하죠. 많이 설명한다고 좋은 게 아니더라고요. 사람 일도 비슷한 것 같아요. 방향은 분명히 알려주되, 숨 쉴 공간은 남겨줘야 결과가 좋아집니다.

프롬프트를 쓸 때 제가 실제로 보는 체크리스트

요즘 저는 AI에게 코드를 부탁하기 전에 아주 짧게라도 체크리스트를 봅니다. 거창한 건 아니고요. 그냥 실무에서 삽질을 줄이기 위한 제 나름의 습관입니다.

    • 입력과 출력이 문장으로 분명한가? “데이터 처리해줘”가 아니라 “입력 JSON 배열을 받아 특정 필드를 변환한 배열을 반환”처럼 적었는지 봅니다.
    • 예외 상황을 어디까지 처리할지 정했는가? null, 빈 배열, 잘못된 타입, 외부 API 실패 같은 조건을 미리 적어두면 AI가 덜 헤맵니다.
    • 스펙에 없는 기능을 넣지 말라고 했는가? 이 문장 하나가 생각보다 중요합니다. AI는 은근히 친절해서 자꾸 뭘 더 넣습니다.
    • 수정 범위를 제한했는가? “이 파일만 수정해줘”, “기존 public interface는 유지해줘” 같은 말을 꼭 붙이는 편입니다.
    • 테스트 기준을 함께 줬는가? 코드만 달라고 하면 애매할 때가 많아서, 가능하면 기대하는 테스트 케이스도 같이 적습니다.

이 체크리스트를 지키면 프롬프트가 엄청 화려해지지는 않습니다. 대신 안정적이에요. 저는 요즘 AI 코딩에서 화려함보다 안정성을 더 중요하게 봅니다. 특히 회사 프로젝트에서는 더 그렇고요. 혼자 사이드 프로젝트 할 때야 실패해도 재미로 넘길 수 있지만, 팀 코드베이스에서는 작은 오해가 PR 리뷰 시간과 장애 대응 시간으로 돌아오니까요.

AI 코딩은 더 빨리 치는 기술이 아니라, 더 덜 헤매게 만드는 기술에 가깝다

Kiro를 하면서 제일 많이 바뀐 생각이 이겁니다. 처음에는 AI 코딩을 “코드를 빠르게 뽑는 도구”로 봤어요. 물론 그것도 맞습니다. 그런데 실무에서는 단순히 빨리 뽑는 것만으로는 부족하더라고요. 잘못된 코드를 빨리 많이 만들면, 그건 생산성이 아니라 빚입니다. 나중에 이자까지 붙어서 돌아와요.

그래서 저는 이제 AI 코딩을 할 때 스펙주도개발을 거의 기본값처럼 생각합니다. 스펙이 있으면 AI도 덜 헤매고, 사람도 덜 싸웁니다. “나는 이렇게 이해했는데?” 같은 이야기가 줄어들거든요. 특히 팀 프로젝트에서는 이 효과가 꽤 큽니다. AI가 만든 코드도 결국 사람이 리뷰하고 운영해야 하니까요.

이 글은 이런 분들이 읽으면 특히 도움이 될 것 같아요.

    • Cursor나 GitHub Copilot을 쓰는데 토큰이 너무 빨리 소진된다고 느끼는 분
    • AI가 만든 코드가 매번 달라져서 리뷰하기 피곤한 분
    • 팀 프로젝트에 AI 코딩을 도입했는데 코드 스타일이나 판단 기준이 흔들리는 분
    • SPEC.md, .cursorrules 같은 설정을 어떻게 써야 할지 감이 잘 안 오는 분
    • LLM 기반 개발에서 속도와 품질을 같이 잡고 싶은 분

사실 스펙주도개발은 새로운 유행이라기보다 오래된 기본기에 가깝습니다. 다만 AI 시대가 되면서 그 가치가 다시 선명해진 것 같아요. 20년 가까이 개발 일을 하다 보면 기술은 계속 바뀌는데, 묘하게 안 바뀌는 것들이 있습니다. 요구사항을 정확히 이해하는 것, 경계를 분명히 하는 것, 나중에 읽을 사람을 생각하며 만드는 것. 이런 것들이요.

다음에 AI에게 코드를 부탁할 일이 있다면 바로 “이거 만들어줘”라고 던지기 전에 잠깐만 멈춰보세요. 입력은 뭔지, 출력은 뭔지, 하지 말아야 할 일은 뭔지. 딱 그 정도만 적어도 결과가 꽤 달라집니다. 저도 아직 매번 완벽하게 하지는 못합니다. 그래도 이제는 압니다. AI 코딩에서 진짜 속도는 손이 빨라지는 데서 오는 게 아니라, 덜 되돌아가는 데서 온다는 걸요.

댓글