위임 브리프의 규칙과 예시가 어긋나자 산출물이 갈라진 사례

타입체크 0건, 테스트 전부 초록불. 화면으로 봐도 두 출력물 다 멀쩡했다. 그런데 다른 벤더의 검증 에이전트 하나가 카테고리 목록을 나란히 놓고 보더니 순서가 다르다고 지적했다. 내가 직접 쓴 작업 지시서(brief, 에이전트에게 무엇을 어떻게 하라고 넘기는 문서) 때문에 생긴 결함이었다.
블로그에 출력물 두 개를 추가하는 작업을 코딩 에이전트에게 맡겼다. 하나는 사람이 보는 HTML 목록 페이지, 다른 하나는 AI 답변엔진이 읽어가는 텍스트 요약 파일이었다. 둘 다 같은 글 목록을 카테고리별로 묶어서 보여준다는 목적은 같았다. 겉보기엔 단순한 위임이었는데, 결함은 코드가 아니라 지시서 안에 이미 심어져 있었다.
초록불인데 순서가 어긋나 있었다
브리프에는 규칙을 분명히 적어 뒀다. "두 출력물의 카테고리 그룹핑은 동일해야 한다. 중복 로직이 생기면 공유 헬퍼(helper, 여러 곳에서 재사용하는 공통 함수)로 뽑아 둘 다 쓰게 하라." 여기까지는 흠잡을 데가 없었다.
문제는 같은 문서 안에 예시도 함께 넣었다는 데 있었다. 텍스트 요약 파일이 어떤 모양으로 나와야 하는지 보여주려고 샘플 출력을 붙였는데, 그 샘플 안에서 카테고리 순서를 별생각 없이 임의로 다르게 나열해 버렸다. 규칙은 "동일해야 한다"고 말하고 예시는 "이렇게 다르게 나온다"고 보여준 셈이다. 써 놓고도 그 순간엔 두 문장이 서로 부딪힌다는 걸 알아채지 못했다.
에이전트는 성실하게 일했다. 공유 헬퍼를 만들어 규칙을 지켰다. 그리고 텍스트 파일 쪽에서만 그 헬퍼에 순서를 지정하는 인자를 추가로 넘겨 예시가 보여준 순서를 그대로 재현했다. 규칙도 만족시켰고 예시도 만족시켰다. 다만 그 둘을 동시에 만족시키는 방법이 하나였다. 두 출력물의 순서를 갈라놓는 것.
기계적 검증은 아무것도 못 잡았다. 타입은 애초에 순서와 무관한 값이니 체크할 대상이 아니었고, 테스트는 각 출력물이 자기 안에서 유효한 형식인지만 확인했다. 두 출력물을 나란히 놓고 순서가 같은지 비교하는 테스트는 존재하지 않았다. 없는 걸 검사할 방법은 없다. 초록불은 "이상 없음"이 아니라 "물어본 것에는 답했음"이었을 뿐이다.
내 눈으로 봐도 잘 안 걸리는 종류의 결함이었다. 두 출력물을 각각 열어 보면 둘 다 자연스럽다. 카테고리별로 묶여 있고, 각 카테고리 안의 글 목록도 맞다. 다른 게 딱 하나, 카테고리가 나열되는 순서뿐이다. 나도 검수할 때 보통 한 파일을 먼저 확인하고 다음 파일로 넘어가지, 두 파일을 나란히 펼쳐 순서만 대조하지는 않는다. 검증 에이전트가 이걸 잡은 것도 특별히 예리해서가 아니라, 애초에 "두 산출물이 일치하는가"라는 질문을 기계적으로 던졌을 뿐이었다.
그 지적을 받고 나서 브리프를 다시 펴 봤다. 규칙 문장과 예시 문단은 같은 페이지, 몇 줄 간격으로 나란히 있었다. 눈으로 훑을 때는 둘 다 자연스럽게 넘어갔는데, 나란히 실제로 비교하려고 마음먹고 보니 그제야 부딪히는 지점이 보였다. 결함은 숨어 있지 않았다. 처음부터 문서 안에 그대로 적혀 있었는데, 겹쳐 읽지 않는 한 보이지 않는 방식으로 적혀 있었을 뿐이다.
규칙을 적었으니 지켜지리라는 착각
브리프에 규칙을 적으면 그걸로 안전하다고 믿기 쉽다. 나도 그랬다. "동일해야 한다"는 문장을 넣었으니 할 일을 다 했다고 생각했다. 그런데 같은 문서 안에는 구체적인 예시도 있었다. 결과를 보면 에이전트는 규칙과 예시를 함께 만족시키는 구현을 택했고, 그 과정에서 두 출력물의 순서가 갈렸다.
이걸 처음 겪었을 때는 에이전트가 지시를 잘못 이해했다고 생각했다. 다시 브리프를 읽어 보니 아니었다. 지시는 정확히 이해됐다. 문제는 지시 자체가 두 갈래였고, 에이전트는 둘 중 하나를 버리는 대신 둘 다 살리는 제3의 경로를 찾아낸 것이다. 이 경로는 브리프를 쓴 사람 눈에는 보이지 않았다. 왜냐하면 그 경로를 만든 모순 자체가 애초에 내가 심어 둔 것이었으니까.
내가 스스로 이 결함을 못 잡았던 이유가 여기 있다. 결함이 문법 오류나 오타였다면 다시 읽으면서 걸렸을 것이다. 하지만 규칙과 예시의 충돌은 문법적으로는 완벽했다. 두 문장 다 말이 됐고, 각각 따로 읽으면 아무 문제가 없었다. 겹쳐 놓고 봐야만 부딪힌다는 게 드러나는데, 나는 이미 그 문서를 "내가 원하는 것"이라는 하나의 그림으로 머릿속에 담고 있어서 겹쳐 읽지 못했다. 검증할 때도 같은 그림을 다시 꺼내 보니 모순이 안 보였다.
이 결함은 내가 겪은 다른 종류의 실수와 결이 달랐다. 요구사항을 빠뜨렸다면 "이것도 필요했는데"라고 나중에라도 알아챘을 여지가 있다. 하지만 두 지시가 서로 부딪히는 경우는 빠진 게 없었다. 오히려 지나치게 친절했던 게 문제였다. 규칙만 적었으면 에이전트가 스스로 순서를 정했을 테고, 그러면 최소한 하나의 출력물 안에서는 일관됐을 것이다. 예시까지 얹어 "더 명확하게" 설명하려던 시도가 오히려 갈림길을 만들었다. 이번 경우엔 친절함이 곧 안전함은 아니었다.
규칙과 예시를 함께 만족시키려다 왜 순서가 갈렸는가
이번 작업의 결과를 보면, 에이전트는 공유 헬퍼로 동일 그룹핑 규칙을 지키면서 텍스트 출력에만 순서 인자를 더해 예시까지 재현했다. 문제는 규칙이 요구한 두 가지가 서로 다른 층위였다는 데 있다. "공유 헬퍼로 뽑아 둘 다 쓰게 하라"는 문자 그대로의 지시였고, "그룹핑은 동일해야 한다"는 그 지시로 지키려던 실제 결과였다. 헬퍼 하나를 같이 쓰는 것과 그 헬퍼가 항상 같은 결과를 내는 것은 서로 다른 말인데, 이번 브리프는 그 차이를 명시하지 않았다.
예시는 정확히 그 틈을 파고들 여지를 만들었다. 헬퍼 함수에 순서를 지정하는 인자 하나만 더하면, "헬퍼를 공유한다"는 문자는 그대로 지키면서 예시가 보여준 순서도 낼 수 있었다. 결과만 보면 에이전트는 그 틈을 이용해 규칙의 문자와 예시를 동시에 만족시키는 구현을 택했고, 규칙이 정말로 지키려던 "결과가 같아야 한다"는 조건은 그 사이에서 빠졌다.
비슷한 상황을 다른 곳에서도 본 적이 있다. 상사가 "문서 양식은 팀 템플릿 하나로 통일하라"고 하면서, 그 템플릿에 담긴 예시 문구는 팀마다 다르게 써서 보여준 경우다. 새로 맡은 사람이라면 "템플릿 파일은 하나로 통일하되, 각 팀에 보여준 예시 문구는 그대로 반영하자"는 절충안을 찾을 가능성이 있다. 템플릿을 하나 쓴다는 규칙은 지켰지만, 정작 그 규칙이 막으려던 "팀마다 문서가 달라지는 문제"는 그대로 남는다. 이번 에이전트가 만든 결과도 같은 모양이었다. 공유 헬퍼라는 형식은 지켰고, 그 형식이 보장하려던 동일한 출력이라는 실질은 놓쳤다.
더 무거운 문제는 따로 있었다. 검증 에이전트가 지적한 건 순서 불일치가 아니라 그 아래 숨은 진짜 버그였다. 순서를 재현하려고 넣은 인자가 카테고리 세 개를 수동으로 나열한 배열이었다. 카테고리가 넷으로 늘면 어떻게 될까. 새 카테고리에 속한 글은 그 배열 어디에도 없으니 전부 "기타" 묶음으로 떨어진다. 순서가 틀린 정도가 아니라 분류 자체가 조용히 깨지는 잠재 결함이었다. 예시 하나를 맞추려다 미래의 확장성까지 깎아 먹은 셈이다.
모순을 브리프 밖으로 치운다
고친 방법은 인자를 없애는 것이었다. 두 출력물이 같은 기본 순서를 쓰게 만드니 애초에 "어떤 순서를 따를지" 선택할 자리가 사라졌다. 선택지가 없으면 예시를 흉내 낼 방법도 없다. 예시를 지우거나 규칙을 느슨하게 바꾸는 대신, 둘 다 만족시킬 수 있는 여지 자체를 코드에서 제거한 것이다.
인자를 없애면 모순이 설 자리가 사라진다
실제 코드가 아니라 뼈대만 옮기면 이렇다. 수정 전에는 헬퍼가 순서를 바꿀 수 있는 문을 열어 뒀고, 그 문으로 예시가 요구한 순서가 슬쩍 들어왔다.
# 수정 전 (의사코드) — 순서를 바꿀 수 있는 인자가 모순의 통로였다
function groupByCategory(posts, order = 기본_순서):
return sortByOrder(group(posts, "category"), order)
# 텍스트 요약 파일 쪽에서만 예시와 맞추려고 다른 순서를 넘김
textOutput = groupByCategory(posts, order = 예시에서_뽑은_순서)
htmlOutput = groupByCategory(posts, order = 기본_순서)
# 수정 후 (의사코드) — 순서를 바꿀 자리 자체를 없앰
function groupByCategory(posts):
return sortByOrder(group(posts, "category"), 기본_순서)
textOutput = groupByCategory(posts)
htmlOutput = groupByCategory(posts)
차이는 한 줄이 아니라 인자 하나가 통째로 사라졌다는 데 있다. 인자가 있으면 언젠가 또 다른 예시나 요청이 그 인자에 다른 값을 채워 넣을 여지가 남는다. 인자가 없으면 그럴 수가 없다. 문제를 "올바른 값을 넣도록 주의하자"로 풀지 않고 "값을 넣을 자리를 없애자"로 푼 셈이다.
그리고 두 출력물의 카테고리 순서가 실제로 같은지 비교하는 회귀 테스트(regression test, 이미 고친 문제가 나중에 다시 생기지 않았는지 확인하는 테스트)를 추가했다. 이 테스트가 있었다면 애초에 순서 인자가 들어가는 순간 바로 실패했을 것이다. 테스트가 부족했던 게 아니라, 검증해야 할 불변조건 하나를 아예 상상하지 못했던 것이다.
이 사건을 겪고 나서 브리프를 쓸 때부터 예시를 규칙의 일부로 취급하기 시작했다. 예시는 참고 자료가 아니라 또 하나의 지시다. 그래서 브리프를 보내기 전에 다음 항목을 훑어보는 절차를 넣었다.
| 점검 항목 | 확인 방법 |
|---|---|
| 규칙과 예시가 같은 것을 말하는가 | 예시를 규칙에 그대로 대입해 봐서 어긋나는 지점이 있는지 직접 확인한다 |
| 예시가 규칙의 암묵적 예외를 만들지는 않는가 | "이 경우만 예외"라는 말이 브리프 어디에도 없는데 예시가 예외처럼 읽히는지 본다 |
| 여러 출력물이 있다면 그들 사이의 불변조건이 규칙에 명시됐는가 | "동일해야 한다" 같은 문장이 실제 비교 테스트로 이어지는지 확인한다 |
| 검증자에게 브리프 전체를 줄 것인가, 요구사항만 줄 것인가 | 브리프 전체를 주면 작성자의 논리를 그대로 물려받는다는 위험을 감수하는 것이다 |
| 확장 시나리오(항목이 하나 더 늘면)를 가정해 봤는가 | 수동으로 나열한 배열이나 고정 목록이 있다면 그 자리를 특히 의심한다 |
이 점검을 한 번 도는 데 몇 분이면 됐다. 그런데도 브리프를 쓸 때는 매번 건너뛰고 싶어진다. 이미 규칙을 적었으니 다 됐다고 믿는 그 순간이 정확히 이 사건이 시작된 지점이었다.
남은 숙제
검증 에이전트가 브리프를 보지 않은 점은 두 산출물의 차이에 먼저 주목하는 데 도움이 됐다. 규칙과 예시 중 어느 쪽이 맞는지 판단할 필요 없이, 산출물 두 개를 나란히 놓고 다르다는 사실만 확인하면 됐다. 브리프를 본 사람이었다면 "아, 예시대로 한 거구나"라고 넘어갔을 자리다. 이번엔 브리프를 안 본 검증자였기에 "왜 다르지"라는 질문이 자연스럽게 떠올랐을 것이다. 이 원칙은 작성자와 다른 벤더가 산출물을 교차 검증하는 절차와 이어지는데, 그 글은 검증자를 벤더 단위로 어떻게 나눌지를 다뤘고 이 글은 그 검증자에게 브리프를 얼마나 보여줘야 하는지를 다룬다는 점이 다르다.
정확히 이 지점이 아직 안 풀렸다. 검증자에게 요구사항만 주고 브리프 전체를 감추면 작성자의 논리에 덜 물들지만, 애초에 왜 그렇게 만들었는지 맥락을 몰라서 오탐이 늘어날 수 있다. 반대로 브리프 전체를 주면 맥락은 풍부해지지만 그 안에 있던 모순까지 함께 물려받을 위험이 다시 생긴다. 지금은 요구사항과 산출물만 넘기고 브리프 원문은 별도로 요청이 있을 때만 참고하는 식으로 절충하고 있는데, 이 경계선을 어디에 그어야 오탐도 줄이고 모순도 안 물려받는지는 다음 작업에서 더 확인해야 한다.
브리프에 예시를 아예 안 넣는 것도 답은 아니다. 예시가 없으면 에이전트가 상상으로 빈칸을 채우고, 그 상상은 브리프를 쓴 사람의 기대와 다른 방향으로 벗어날 수 있다. 예시는 여전히 필요하다. 다만 그 예시는 규칙과 같은 무게로 다뤄야 한다. 그리고 여러 산출물이 얽힌 작업이라면 "각각 옳은가"뿐 아니라 "서로 일치하는가"까지 규칙에 못 박아야 한다. 다음 브리프를 쓸 때 이 두 가지부터 다시 확인하는 일이 남았다.
자주 묻는 질문
브리프에 예시를 넣으면 안 되나?
예시 자체가 문제는 아니다. 문제는 예시가 규칙과 다른 결과를 요구할 때다. 예시를 넣을 때는 그 예시를 규칙에 그대로 대입해 봐서 어긋나는 지점이 없는지 확인해야 한다. 브리프를 보내기 전 예시가 규칙의 암묵적 예외를 만들지는 않는지 점검하는 습관이 필요하다.
테스트가 전부 초록불인데 왜 결함이 남았나?
기존 테스트는 각 파일의 형식만 검사했으며 두 파일의 순서는 비교하지 않았다. 검증해야 할 불변조건 자체를 처음부터 상상하지 못했던 것이다.
왜 작성자 자신은 이 모순을 못 잡았나?
이번 경우엔 규칙과 예시가 각각 따로 읽으면 문법적으로 완벽해서 오류처럼 보이지 않았다. 두 문장을 겹쳐 놓고 봐야만 충돌이 드러나는데, 작성자인 나는 이미 브리프를 하나의 완성된 그림으로 머릿속에 담고 있어서 겹쳐 읽는 과정을 건너뛰었다. 검증할 때도 같은 그림을 다시 꺼내 보니 모순이 눈에 안 들어왔다.
왜 브리프를 안 본 검증자가 이 결함을 더 잘 잡았나?
브리프를 본 사람은 예시대로 나온 결과를 보고 의도된 동작이라고 넘어갈 수 있다. 브리프를 안 본 검증자는 산출물 두 개를 나란히 놓고 다르다는 사실 자체만 확인하면 된다. 판단할 배경 논리가 없으니 결과의 차이가 그대로 의문으로 남는다.
아직 댓글이 없습니다.