"추측하지 말고 확인하라"를 지시에 넣었더니 내 전제가 틀린 게 드러났다
AI 에이전트에게 작업을 맡길 때, 나는 설계자 역할로 작업을 분해하고 브리프를 쓴다. 브리프에는 파일 경로, 프로젝트 관례, 알려진 함정, 완료 기준을 담는다. 구현자가 컨텍스트를 다시 탐색하지 않게 하기 위해서다. 잘 쓴 브리프는 구현자가 코드를 처음부터 뒤질 필요 없이 바로 작업에 들어가게 해 준다.
문제는 내가 브리프에 쓴 것이 틀릴 수 있다는 점이다. 압축해서 전달하는 사람의 기억과 가정이 브리프 안에 함께 섞여 들어가기 때문이다. 나는 코드를 마지막으로 본 시점의 기억으로 브리프를 쓰는데, 그 기억이 실제 코드와 어긋나 있어도 브리프의 문장은 똑같이 확신에 찬 어조로 나온다. 받는 쪽은 그 어조를 구분할 방법이 없다.
실제 사례
OAuth 콜백 라우트를 고치는 작업이었다. 로그인 후 돌아갈 경로를 쿼리로 받는데, 에러가 나면 그 경로를 무시하고 어드민 로그인 페이지로 하드코딩 이동하고 있었다. 랜챗에서 로그인하다 실패한 사용자가 뜬금없이 어드민 화면에 떨어지는 셈이었다. 왜 이런 코드가 있었는지는 짐작이 갔다 — 아마 어드민 로그인 흐름만 있던 시절에 짠 코드가, 다른 흐름이 붙은 뒤에도 그대로 남아 있었을 것이다.
브리프에 이렇게 썼다.
에러 시 next가 가리키는 곳으로 돌려보낸다. 다만 /admin/login은
에러를 화면에 보여주는 페이지이므로 그 경로로 돌아가는 경우는
기존 동작(?error=)을 유지해야 한다.
이 문장 자체는 그럴듯했다. "어드민 로그인 페이지는 원래 에러를 보여주도록 만들어졌을 테니, 그 경로로 돌아가는 경우만 예외로 남겨야 한다"는 추론이었다. 그런데 이건 코드를 열어서 확인한 사실이 아니라 이름과 맥락으로 추측한 것이었다. 그래서 한 줄을 덧붙였다.
먼저 어드민 로그인 페이지를 열어 ?error= 를 실제로 읽는지
확인하라. 안 읽는다면 그 전제가 틀린 것이니 보고하고 더 단순한
형태를 택하라. 추측하지 마라.
구현자가 확인했고, 그 페이지는 ?error= 쿼리를 아예 읽지 않았다. 로컬 상태만 쓰고 있었다. 죽은 파라미터였다. 그래서 특례 분기 없이 항상 원래 경로로 돌아가는 단순한 형태로 구현했고, 그 사실을 보고받았다.
내가 그 한 줄을 안 넣었으면 어떻게 됐을까. 구현자는 브리프대로 특례 분기를 만들었을 것이다. 코드는 동작하고 테스트도 통과한다. 아무도 그게 불필요한 분기라는 걸 모른 채 남는다. 그리고 다음 사람은 "어드민 경로는 왜 다르게 처리하지?"를 고민하며 그 근거 없는 전제를 코드 고고학처럼 다시 파헤쳐야 한다. 틀린 전제 하나가 확인 절차 없이 통과하면, 그 뒤로는 코드 자체가 그 전제의 증거가 되어버린다.
더 나쁜 경우도 있다. 특례 분기가 생기면 그 분기를 다루는 테스트도 같이 생긴다. "어드민 로그인 경로로 돌아갈 때는 ?error=를 유지한다"는 테스트가 통과하는 순간, 이 전제는 코드와 테스트 양쪽에서 사실로 굳는다. 나중에 누군가 이 코드를 정리하려 해도, 테스트가 지키고 있는 동작을 함부로 지울 수 없다. 확인 한 번을 건너뛴 대가가 테스트 스위트 안에 영구히 박제되는 셈이다.
왜 이게 필요한가
브리프는 컨텍스트를 압축해 전달하는 수단인데, 압축하는 사람의 기억과 가정이 함께 들어간다. 코드를 직접 열어보지 않고 "아마 이럴 것이다"로 쓴 문장이 브리프에 섞이면, 그건 지시가 아니라 검증되지 않은 주장이다. 그런데 받는 쪽은 그걸 지시로 읽는다. 구현자 입장에서는 브리프에 적힌 문장을 의심할 근거가 없다 — 설계자가 이미 코드를 봤을 거라고 전제하고 작업을 시작하기 때문이다.
그래서 브리프에 어떤 문장이 검증된 사실이고 어떤 문장이 내 가정인지 구분해 적는 게 도움이 됐다. 가정에는 확인 지시와 함께 "틀리면 보고하라"는 탈출구를 붙인다. 이 사례처럼 확인 한 번으로 특례 분기 자체가 사라지는 경우도 있다. 확인 비용은 몇 분이지만, 틀린 전제 위에 쌓인 분기를 나중에 걷어내는 비용은 훨씬 크다 — 그 분기에 의존하는 테스트가 생기고, 주석이 붙고, 다음 브리프가 그 분기를 다시 전제로 삼기 시작한다.
같은 계열의 다른 장치들
비슷한 목적으로 브리프에 넣어 효과를 본 것들이 있다. 공통점은 구현자가 "일단 되게 만들자"는 쪽으로 판단을 미루지 못하게 막아둔다는 것이다.
"테스트가 통과해버리면 거기서 멈추고 보고하라." 리뷰가 구조 분석만으로 결함을 지적했을 때, 수정 전에 먼저 재현 테스트를 쓰게 했다. 그 테스트가 그냥 통과하면 진단이 틀린 것이니 억지로 고치지 말라고 명시했다. 이런 지시가 없으면 구현자는 "리뷰가 문제라고 했으니 뭐라도 고쳐야 한다"는 압박을 받는다. 재현되지 않는 버그를 억지로 고치면 관련 없는 코드를 건드리게 되고, 진짜 문제는 그대로 남는다. 결과적으로는 재현됐지만, 그 문장이 없으면 구현자는 재현 여부와 상관없이 어떻게든 고치려 했을 것이다.
"추측하지 말고 확실하게 하라"를 구체적 방법과 함께. 어떤 테스트에서 두 개의 비동기 작업 중 어느 것이 먼저인지 알아야 했는데, "인덱스로 추측하지 말고 호출 인자로 식별하라"고 적었다. 순서에 의존하는 테스트는 조용히 틀린 것을 검증한다 — 어쩌다 통과하는 순서로 실행되다가, 나중에 실행 순서가 바뀌면 그제서야 실패한다. "추측하지 마라"는 말만으로는 부족해서, 대신 무엇을 근거로 판단해야 하는지까지 적어야 실제로 지켜졌다.
하지 말아야 할 것도 명시. "이 부분은 건드리지 마라"를 이유와 함께 적었다. 방금 고친 구조가 있는데 무심코 정리하다 되살아나기 쉬운 자리였다. 이유 없이 "건드리지 마라"만 적으면 구현자는 왜 안 되는지 몰라서 결국 다시 건드리게 된다. 이유를 같이 적어야 그 자리가 왜 위험한지 판단할 수 있다.
교훈
- 브리프에는 사실과 가정이 섞인다. 둘을 구분해 적고, 가정에는 "확인하라, 틀리면 보고하라"를 붙여라.
- 지시를 받는 쪽은 지시자의 문장을 검증된 사실로 읽는다. 그래서 틀린 전제는 코드에 그대로 굳는다.
- "안 되면 멈추고 보고하라"는 탈출구가 없으면, 받는 쪽은 어떻게든 되게 만든다. 그 결과가 잘못된 방향일 때가 문제다.
- "추측하지 마라"만으로는 부족할 때가 있다. 무엇을 근거로 판단해야 하는지까지 적어야 실제로 추측을 멈춘다.
- 컨텍스트를 압축해 전달하는 모든 상황에 같은 원리가 적용된다 — 인수인계 문서, 이슈 설명, 코드 주석. 확인하지 않고 쓴 문장이 가장 오래 살아남는다.
- 확인 비용은 몇 분이지만, 틀린 전제 위에 분기와 테스트가 함께 쌓이고 나면 되돌리는 비용은 그보다 훨씬 크다. 확인은 아끼지 않는 편이 결국 더 싸다.