AI가 당신의 명세(Specs)를 망치는 숨겨진 이유 (그리고 효과적인 계층적 해결책)
요약
AI를 활용하여 명세 주도 개발(SDD)을 진행할 때, 단순히 '화면을 만들어줘'와 같은 요청만으로는 비즈니스 규칙 무시나 불일치 문제가 발생하기 쉽습니다. 이는 SDD가 작업을 조직화하지만 결정 사항들을 분리하지 못했기 때문입니다. 해결책은 프레젠테이션 계층, 도메인 모델, 백엔드 등 세 가지 핵심 결정을 독립적인 '계층'으로 나누어 순차적으로 설계하는 '계층적 SDD(layered SDD)'를 적용하는 것입니다.
핵심 포인트
- AI 활용 시 명세 주도 개발(SDD)의 한계를 인식하고, 결정 사항들을 분리하는 것이 중요합니다.
- 시스템을 프레젠테이션 계층, 도메인 모델, 백엔드 등 독립적인 '계층'으로 나누어 접근해야 합니다.
- UI 흐름 설계는 샘플 데이터를 사용해 먼저 진행하여 UX의 한계치를 포착하고, 이후 데이터 모델링을 통해 UI와 분리된 엔티티 및 비즈니스 규칙을 정의합니다.
- 두 계층(UI/데이터) 간의 차이점(gap)을 비교하고 명시적으로 문서화하는 단계를 거쳐 재작업을 계획적인 설계 결정으로 만듭니다.
- 마지막 단계에서 실제 API 계약과 동작을 모방한 '모크 계층'을 구축하여 백엔드 의존성 없이 전체 흐름 검증 및 반복 개발이 가능합니다.
나는 AI가 실수를 멈추기를 기대하며 명세 주도 개발 (Spec-Driven Development, SDD)을 채택했습니다. 하지만 그렇게 간단하지 않았습니다. 다듬어진 명세, 구성된 기술, 매끄러운 프로세스. 그럼에도 불구하고, 내가 AI에게 "화면을 만들어줘"라고 요청할 때마다 결과는 뒤섞여서 돌아왔습니다. 비즈니스 규칙 (business rules)은 무시되었고, 곳곳에 불일치가 존재했습니다. 나를 괴롭혔던 점은 도구나 명세를 탓할 수 없었다는 것입니다. 방법론은 옳았습니다. 단지 잘못된 입도 (granularity)로 작동하고 있었을 뿐입니다. 왜냐하면 "화면을 만들어줘"는 단일 작업이 아니기 때문입니다. 계획과 실행을 포함하여 전체 SDD 사이클을 돌리며 여러 작업으로 나누었을 때조차, 거기에는 여전히 세 가지 결합된 결정 사항이 포함되어 있었습니다: 인터페이스가 어떻게 보여야 하는지, 클라이언트의 도메인 (domain)이 어떤 규칙을 허용하는지, 그리고 API가 데이터를 어떻게 노출해야 하는지입니다. 세 가지의 별개 결정 사항이 작업 전반에 흩어져 있었지만, 결코 진정으로 분리되지는 않았습니다. 그리고 그것이 내가 깨닫는 데 시간이 걸렸던 부분입니다: SDD는 내 작업을 조직화했지만, 내 결정들을 분리(decouple)해주지는 않았습니다. 모델은 항상 그랬듯, 세 가지를 한꺼번에 매우 확신에 차서 해결하며 동작했습니다. 첫 번째 리뷰에서 불일치, 무시된 비즈니스 규칙, 그리고 재작업이 나타나기 전까지는 올바르게 보였습니다.
전환점
그 세 가지 결정은 하나가 아닙니다. 그것들은 독립적인 계층 (layers)이며, 서로 분리되어야 합니다. 프레젠테이션 계층 (presentation layer)이 하나이고, 도메인 모델 (domain model)이 또 다른 것이며, 백엔드 (backend)가 또 다른 것입니다. 동일한 단계에서 이 세 가지를 모두 해결할 수는 없습니다. 나는 이것을 가구 조립에 비유합니다. 아무도 샌딩(sanding), 조립, 페인팅을 동시에 하지 않습니다. 각 단계는 단일한 목표를 가지며, 그것이 실행 가능하게 만드는 핵심입니다.
여기서 나에게 놀라울 정도로 잘 작동하는 작업 방식이 나왔습니다: 계층적 SDD (layered SDD). 각 계층은 자신의 범위를 벗어나는 모든 것을 의도적으로 무시하며, 단일한 목표를 가진 자체적인 명세를 가집니다. 워크플로우를 설명하기 전에 한 가지 중요한 명확한 점을 짚고 넘어가겠습니다: 도메인을 모델링하는 것은 백엔드를 구축하는 것이 아닙니다. 도메인 모델링 (Domain modeling)은 클라이언트의 엔티티 (entities), 비즈니스 규칙, 그리고 관계를 기술하는 것을 의미합니다.
이는 API, 영속성 (persistence), 또는 인프라 (infrastructure)와는 독립적으로 이해를 도모하는 과정입니다. 백엔드 (backend)는 훨씬 나중에 등장합니다. 제가 오늘날 사용하는 워크플로우는 인터페이스를 계층별로 설계하는 방식입니다. 이것은 가장 직관에 어긋나는 단계이면서, 동시에 가장 강력한 단계이기도 합니다. 저는 샘플 데이터(UI 컴포넌트 자체에 존재하는 하드코딩된 플레이스홀더 값)를 사용하여 UI 흐름을 구축합니다. 이는 인터페이스가 렌더링되고 UX 흐름을 확인할 수 있을 정도면 충분합니다. 계약 (contract)도, 데이터 계층 (data layer)도, 그 뒤에 구조화된 것도 아무것도 없습니다. 이는 의도적인 것입니다. 이 단계에서는 아직 데이터 모델 (data model)이 존재하지 않기 때문입니다. 저는 마치 현재 시스템이 존재하지 않는 것처럼 가능한 최선의 인터페이스를 설계합니다. 그리고 네, 나중에 리팩터링 (refactor)할 것을 알면서도 이렇게 합니다. 괜찮습니다. 여기서 저는 즉각적인 실현 가능성이 아니라 UX의 한계치 (ceiling)를 포착하고자 합니다. 또한 첫 번째 프롬프트 (prompt)에서 완전한 프론트엔드 (frontend)를 만들 필요는 없습니다. 초기 UI만으로도 시작하기에 충분합니다. 그다음 저는 데이터를 모델링합니다. 저는 반대로 행동합니다. 인터페이스를 따로 떼어놓습니다. UI가 소비하는 데이터를 가져와서, UI를 전혀 건드리지 않고 프레젠테이션 (presentation) 계층으로부터 완전히 분리된 엔티티 (entities), 비즈니스 규칙, 그리고 관계를 독자적으로 모델링합니다. 어떤 필드가 어디에 나타날지는 생각하지 않고, 오직 데이터 모델 자체의 일관성에 대해서만 생각합니다. 저는 비교 계획을 세웁니다. 두 계층이 모두 준비되면, 하나를 다른 하나와 대조하여 양방향의 간극을 매핑합니다. 즉, UI가 요구하지만 데이터 모델이 지원하지 않는 부분, 그리고 모델은 노출하지만 UI가 소비하지 않는 부분을 찾아냅니다. 구현 중에 기습적으로 닥쳐오는 대신, 실제 트레이드오프 (trade-offs)가 명시적이고, 명명되며, 문서화되는 지점이 바로 여기입니다. 저는 실제 데이터 모델에 맞춰 UI를 리팩터링합니다. 데이터 모델이 실제로 지원하는 내용을 바탕으로 인터페이스를 다시 작성합니다. 전통적인 워크플로우에서 이러한 조정은 프로젝트 말기의 "정리 (cleanup)" 작업이 됩니다. 하지만 여기서는 재작업이 아니라 계획된 단계이자 설계 결정입니다. 마지막으로 저는 모크 계층 (mock layer)을 구축합니다. 이제 모크 (mocks)를 구현하는데, 여기서 용어의 정의는 정확합니다. 실제 API의 계약과 동작을 재현하는 모크를 만드는 것입니다.
이것은 오직 4단계 이후에만 가능하다는 점에 유의하십시오. 왜냐하면 모크 (mock)는 모방할 계약 (contract)이 존재함을 전제로 하며, 그 계약은 모델링 (modeling)과 비교 (comparison)를 거친 후에야 비로소 생성되기 때문입니다. 이 계층 (layer)을 통해 저는 백엔드 (backend)에 의존하지 않고도 전체 흐름 (flow)을 검증하고, 짧은 사이클 내에서 반복 (iteration)하며, 여기에는 AI를 활용한 반복도 포함됩니다. 저는 실제 백엔드를 구축합니다. 이제서야 이미 검증된 데이터 모델 (data model)과 이미 안정화된 인터페이스 (interface)를 바탕으로 확정적인 구현 (implementation)이 이루어집니다. 중간에 요구사항을 발견하는 일은 없습니다. 저는 E2E 통합 (E2E integration)을 수행합니다. 계층들을 서로 연결하고 흐름을 엔드 투 엔드 (end to end)로 검증합니다. 전체 프로세스는 반복적입니다: 초기 UI, 초기 데이터 모델, UI v2, 데이터 모델 v2, 이런 식으로 계속됩니다. 계층을 통과할 때마다 이전보다 더 정교해집니다.
이 방식이 AI와 왜 이렇게 잘 작동하는가: 제 경험상 이유는 간단합니다. AI 모델은 작고 잘 정의된 문제에서 훨씬 더 높은 성능을 발휘합니다. UX, 비즈니스 규칙 (business rules), 그리고 아키텍처 (architecture)를 동일한 범위 (scope)로 묶었을 때, 너무 많은 모호함 (ambiguity)이 남게 되었고, 모델은 그 모호함을 추측 (assumptions)으로 채워버렸습니다. 계층으로 분해함으로써 이러한 노이즈 (noise)를 획기적으로 줄일 수 있었습니다. 각 명세 (spec)가 모델이 정확히 이해할 수 있을 만큼 충분히 좁아졌기 때문입니다. 이것이 직관에 어긋나는 것처럼 들릴 수도 있습니다. 일반적인 통념은 "맥락 (context)이 구체적일수록 좋다"이므로, 왜 모델에게 모든 것을 한꺼번에 제공하지 않는 걸까요? 왜냐하면 그러한 수준의 구체성은 손에 잡히는 구체적인 결과물이 생긴 후에야 가능하기 때문입니다. 기능을 구축할 때는 병목 현상 (bottlenecks)을 확인해야 하는데, 병목 현상은 초기에 드러나지 않습니다. 다양한 각도에서 기능에 접근하고, 서로 다른 흐름을 구축하며, 그것들을 서로 대조할 때 비로소 표면 위로 드러납니다. 계층은 바로 그 대조 과정입니다. 각 계층은 실질적인 무언가를 만들어내며, 그것들을 나란히 놓는 과정이 바로 나중에야 발견하게 될 제약 사항 (constraints)들을 드러내 줍니다. 더 느려 보일 수 있지만, 그렇지 않습니다.
이 방법은 새로운 단계를 추가하는 것이 아니라, 단지 결정(decisions)을 더 앞 단계로 옮기는 것뿐입니다. 이전에는 사이클의 마지막 단계에서 재작업(rework)으로 돌아왔던 결정들을 말이죠. "정리(cleanup)" 과정이 사라지는 것이 아니라, 계획의 일부가 되는 것입니다. 저는 S&P 500 기업의 팀들과 작업할 때도 이 접근 방식을 사용해 왔으며, 그 결과는 일관적이었습니다. 즉, 전통적인 명세 기반 개발 (Spec-Driven Development) 방식보다 더 높은 예측 가능성, 더 적은 혼란스러운 리팩터링 (refactoring), 그리고 더 적은 마찰을 얻을 수 있었습니다. 이것이 만능 해결책 (silver bullet)이라고 생각하지는 않습니다. 하지만 저에게 있어, 이 방식은 소프트웨어 개발에서 AI와 협업하는 방식을 영구적으로 바꾸어 놓았습니다. 여러분은 어떠신가요? 여러분은 오늘날 AI 보조 개발 (AI-assisted development)을 어떻게 구조화하고 계신가요?
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기