
AI에게 기능 구현을 맡길 때 생기는 결함은 코드를 잘못 짜서만 생기지 않습니다. 명세에 적지 않은 빈칸을 AI가 그럴듯한 값으로 채우면서도 생깁니다. 빈칸 하나가 어떤 사고로 이어지는지는 환불 목록에 있던 주문 하나가 보여줍니다.
현금 결제액은 0원이었고, 결제 수단은 가입 이벤트 포인트 5,000점이었습니다. 그런데 이 주문의 환불 이력에는 "PG사 결제 취소 요청 성공"이라는 기록이 남아 있었습니다. 사용자가 실제로 지불한 현금은 없었지만, 시스템은 이 주문을 외부 PG사의 결제 취소 흐름으로 처리한 것입니다. 무상 포인트가 현금 반환 경로로 들어갈 틈이 열려 있었다는 뜻입니다.
주문 하나만 보면 5,000원짜리 실수입니다. 하지만 이벤트 시간대에 같은 유형의 주문이 몰렸다면 한 시간 만에 수천만 원의 손실로 이어질 수 있는 구조였습니다. 서버 로그에는 경고가 없었고, 테스트도 통과했을 수 있습니다. 이 주문에서 빠져 있던 것은 코드가 아니라 명세의 금지 조건이었습니다.
엔지니어링이 지향하는 목표는 결정론(Determinism)에 기반한 자판기입니다. 콜라 버튼을 누르면 콜라가 나와야 하고, 때에 따라 사이다가 나오는 자판기는 고장 난 기계입니다. 하지만 자연어로 AI에게 지시하는 방식은 슬롯머신을 돌리는 것과 같습니다. 자연어는 사람이 맥락을 공유하며 쓰는 언어라서, 기계가 따라야 할 조건을 빠짐없이 담는 명세로 쓰기에는 형식이 느슨하기 때문입니다.
사람 개발자는 기획서에서 모호한 부분을 발견하면 기획자에게 되묻습니다. AI는 모호한 지시를 받아도 항상 멈춰 되묻지는 않습니다. 대신 학습 데이터의 확률 분포에 따라 가장 그럴듯한 답을 생성합니다.
"비밀번호를 5번 틀리면 로그인을 막아줘"라는 요청을 예로 들어 보겠습니다. 사람이 읽기에는 명확해 보이지만, 실제 동작을 구현하기에는 빠진 조건이 많습니다. AI는 명시되지 않은 조건을 다음과 같이 일반적인 값으로 추정해 채웁니다.
그 결과 생성된 코드는 실제 의도와 일치하지 않을 수 있습니다. 프롬프트를 길고 자세하게 쓴다고 해결되지도 않습니다. 긴 프롬프트는 필요한 조건을 모두 명세했다는 착각을 주고, 필수 조건의 누락을 발견하기는 더 어렵게 만듭니다. 이렇게 만들어진 잘못된 로직은 평소에는 정상적으로 동작하다가 특정 경계 조건(Edge Case)에서만 드러나곤 합니다.
AI의 생성 결과는 본질적으로 확률적입니다. 같은 입력에도 매번 다른 출력이 나올 수 있으며, 이는 모델의 특성입니다. 그러나 이 불확실성은 생성 과정에만 머물러야 합니다. 따라서 AI를 활용한 제품 설계에서는 생성의 다양성과 동작의 일관성을 분리해야 합니다. AI의 생성은 확률적이어도, 제품의 동작까지 확률적이어서는 안 됩니다.
AI가 빈칸을 잘못 채우면 흔히 그 결과를 환각(Hallucination)이라고 부르며 모델의 문제로 돌립니다. 그러나 문제의 출발점은 AI의 변덕이 아니라 전달하지 않은 맥락에 있습니다. 동료는 회사의 비즈니스 맥락과 말 속에 생략된 전제를 공유하지만, AI는 이러한 업무 맥락을 저절로 알지 못합니다.
동작을 임의로 지어내지 않게 하려면 기능의 동작 의도를 세 가지 요소로 나누어 구조화해야 합니다. 하나의 기능은 다음 요소의 결합으로 정의됩니다.
이를 공식으로 표현하면 Function = Context + Trigger → Reaction입니다. 자연어로 요구사항을 전달할 때 가장 자주 빠지는 요소는 Context입니다. "비밀번호를 5번 틀리면 로그인을 막아줘"라는 문장에는 "한 번 더 틀린다"는 행위와 "로그인을 막는다"는 결과는 보이지만, 그 직전 계정이 어떤 상태였는지는 빠져 있습니다.

세 칸을 채우면 AI가 추정하던 빈칸이 명시적인 제품 정책으로 바뀝니다. 비밀번호 차단 정책에서 누락된 조건은 불편이나 사소한 보안 문제로 그칠 수 있습니다. 하지만 포인트 환불처럼 돈을 직접 다루는 기능에서는 조건 하나의 누락이 금전적 손실로 이어질 수 있습니다. "포인트 환불 기능을 구현해 줘"라는 지시에는 환불 기준이라는 Context가 빠져 있습니다. 환불 대상이 유상으로 충전한 포인트인지, 이벤트로 지급된 무상 포인트인지 알 수 없습니다. 환불 기준을 명세하지 않은 채 구현을 맡기면, AI는 해당 서비스의 실제 정책 대신 학습 데이터에서 접한 일반적인 환불 로직으로 구현합니다.
조건·행위·결과를 빠짐없이 쓰게 만드는 명세 언어가 Gherkin입니다. Given-When-Then의 3단 구조는 각각 조건(Context), 행위(Trigger), 결과(Reaction)에 대응합니다. Gherkin은 BDD(행동 주도 개발)에서 쓰이던 언어로, 과거에는 시나리오를 일일이 문서화하는 비용 때문에 실무에 널리 자리 잡지 못했습니다. AI 시대에 다시 주목받는 이유는 AI에게 초안 작성을 맡길 수 있어 작성 비용이 낮아졌고, AI가 모호한 자연어보다 구조화된 명세를 따를 때 동작 의도를 더 정확하게 구현하기 때문입니다.
앞의 0원 주문 사례를 Gherkin으로 명세하면 다음과 같습니다.
| Feature: 포인트 환불 정책 Scenario: 이벤트 무상 포인트 환불 시 현금화 방지 Given 이벤트 무상 포인트 5,000점으로 결제한 주문이 있고 And 해당 주문의 유상 결제 금액은 0원이다 When 사용자가 해당 주문에 대해 환불을 요청하면 Then 시스템은 PG사 결제 취소(현금 반환) API를 호출하지 않아야 한다 And 사용자의 포인트 잔액에 5,000점을 다시 적립해야 한다 And 복구된 포인트의 지급 유형은 이벤트 무상 포인트로 유지해야 한다 And "무상 포인트는 포인트로만 복구됩니다"라는 알림을 띄워야 한다 |
이 명세의 핵심 제어 장치는 Then 절의 금지 조건입니다. 막연히 "환불 안 됨"이라고 적는 대신, 시스템이 실행해서는 안 될 경로를 명확히 제한합니다. 이 조건이 없으면 AI는 무상 포인트 환불 건을 현금 환불처럼 처리해 외부 PG 취소 API를 호출하는 코드를 작성할 가능성이 있습니다.
실무에서는 같은 요구사항을 "포인트 환불 기능 짜줘. 무상 포인트는 현금으로 환불해 주면 안 되는 거 알지?"처럼 전달하곤 합니다. 사람끼리는 이 말만으로도 의도가 통합니다. 하지만 이 문장에는 막아야 할 API, 유지해야 할 포인트 속성, 실결제 금액이 0원일 때 적용해야 할 분기가 한 줄도 적혀 있지 않습니다.
빈칸이 남으면 AI는 이 회사의 포인트 정책이 아니라 일반적인 쇼핑몰의 환불 흐름을 따라가기 쉽습니다. 코드만 놓고 보면 이상할 게 없고 테스트도 통과할 수 있습니다. 엔지니어가 환불 예외 정책을 정확히 알지 못한 채 리뷰를 통과시키면 그대로 배포될 수 있고, 문제는 정산 데이터가 마감된 뒤에야 드러납니다.
Gherkin 명세는 이 애매한 지시를 실행 가능한 금지선으로 바꿉니다. 금지 조건이 명세에 있으면 환불 함수는 PG API 호출 직전까지 달려가서 멈추는 것이 아니라, 처음부터 포인트 잔액 복구 흐름으로 꺾입니다.
금지 조건 하나를 명세에 적었다고 AI가 만든 결과를 믿을 수 있게 되는 것은 아닙니다. 성공 경로만 담은 명세는 실제 운영 환경의 실패 앞에서 다시 빈칸을 드러내고, 명세가 코드로 제대로 옮겨졌는지는 테스트와 실행 증거로 확인해야 합니다. AI가 바꾼 코드를 배포하기 전 "이 변경을 내가 책임질 수 있는가?"라는 질문에 답하려면, 명세 다음 단계까지 설계되어 있어야 합니다.

위 컨텐츠는 신상윤 저자의
『AI 루프를 설계하라』를 재구성하여 작성되었습니다.
댓글