AI가 짠 코드를 이해하지 못한 채 배포해도 될까

AI 가 짠 코드를 한 줄씩 다 이해하지 못한 채 배포하는 일이 이제 드물지 않다. 이런 장면을 떠올려 보자. 에이전트가 만든 변경이 테스트를 전부 통과했다. 화면에는 11 files changed, +5,012 −3,147 이 찍혀 있다. 스크롤을 몇 번 내려 훑어본 뒤 git push 를 친다. 그런데 터미널이 멈추더니 질문을 하나 내민다.
웹훅 핸들러가 이제 결제 전에 이벤트 ID 를 저장한다. 결제 대행사가 시간 초과로 같은 이벤트를 다시 보내면 어떻게 될까?
이 장면은 푸시 전에 퀴즈를 내는 Git 훅 Sphinx의 소개 페이지에 실린 예시 대화를 옮긴 것이다. 예시 속 개발자는 첫 답을 틀린다. 그러자 두 번째 질문이 온다. 그 ID 가 결제가 성공하기 전에 저장된다면 결제가 실패했을 때 다음 재시도는 어떻게 되느냐는 질문이다. 개발자는 여기서 맞힌다(재시도는 이미 저장된 ID 를 보고 결제를 건너뛴다). 신화 속 스핑크스는 수수께끼를 못 풀면 사람을 잡아먹었다는데 이 스핑크스는 푸시만 막는다.
이해하지 못한 코드를 배포해도 되는지는 그 코드가 고장 났을 때 누가 고칠 수 있느냐로 갈린다. 주말에 만들고 버릴 코드라면 괜찮다. 남이 쓰는 코드라서 장애가 나면 내가 고쳐야 한다면 적어도 그 변경이 무엇을 하는지와 어디서 깨질 수 있는지는 설명할 수 있어야 한다. 커니핸 말대로 디버깅이 처음 짜는 것보다 두 배 어렵다면 직접 짜지도 않은 코드는 그 출발점조차 없다.
이번 포스팅에서는 이 판단을 오래된 법칙 두 개에 기대어 정리해보려고 한다.
푸시 앞의 스핑크스
Sphinx 는 2026년 10월 18일 Show HN에 올라온 Git 훅이다. README에 적힌 동작은 단순하다. main 이나 master 같은 보호 브랜치로 푸시하면 Git 이 기다린다. 그동안 대화를 보지 않은 별도 리뷰어 모델이 읽기 전용 Git 도구로 커밋된 변경을 읽어 질문을 낸다. 파일이나 함수 이름을 묻는 질문은 없다. 묻는 건 동작과 실패 모드, 불변식이다. 오픈북이라 코드를 다시 열어 봐도 된다. 틀리면 설명을 들은 뒤 새 상황으로 다시 질문을 받는다. 맞히면 멈췄던 푸시가 그대로 진행된다.
소개 페이지 맨 위에는 이 문장이 걸려 있다.
You can outsource your thinking, but not your understanding.
우리말로 옮기면 생각은 외주를 줄 수 있어도 이해는 외주를 줄 수 없다. 페이지는 이 문장을 손자(Sun Tzu)가 한 말로 적어 두었다. 물론 농담이다(손자병법에 git 이야기는 없다). 실제 출처는 kache(@yacineMTB)가 2026년 2월에 올린 트윗이다. 원문은 you cannot outsource your understanding 이라서 Sphinx 의 문구와 조금 다르다. kache 는 이어진 트윗에서 이해는 생각의 산물이라서 외주를 줄 수 있는 건 이미 해 본 생각뿐이라고 덧붙였다. 이 트윗을 인용한 사람 중에는 안드레이 카파시도 있다. 그는 2026년 4월 Sequoia Ascent 대담에서 머리를 띵하게 만든 트윗이라며 이 문장을 꺼냈다(본인 정리본).
코드가 있다는 것도 잊는 코딩
바이브 코딩이라는 이름을 붙인 사람도 카파시다. 2025년 2월 트윗에서 그는 분위기에 몸을 맡긴 채 코드가 있다는 사실조차 잊는 방식을 소개했다. 늘 Accept All 을 누르고 diff 는 더 이상 읽지 않는다고 썼다. 코드가 자기 평소 이해 범위를 넘어 자란다는 말도 있다. 그리고 트윗 끝에 이 방식이 버릴 주말 프로젝트라면 나쁘지 않다고 적었다. 이름을 붙인 사람부터 버릴 코드라고 선을 그어 둔 셈이다.
1년 뒤 Sequoia 대담에서는 이런 말도 했다.
You are still responsible for your software, just as before.
AI 가 짠 코드라도 책임은 예전처럼 그 코드를 푸시한 사람에게 있다. 사이먼 윌리슨은 여기에 더 단순한 규칙을 붙인다. 무엇을 하는지 남에게 정확히 설명할 수 없는 코드라면 자기 저장소에 커밋하지 않는다. 러버덕 디버깅은 왜 통할까에서 다룬 설명할 수 있어야 안다는 이야기와 같은 편에 선 규칙이다.
이 규칙을 지키지 않을 때 쌓이는 빚에도 이름이 있다. 제이슨 고먼은 2025년 9월 개발자가 이해하는 속도보다 빨리 코드를 만들 때 생기는 빚을 이해 부채(comprehension debt)라고 불렀다. 애디 오스마니는 2026년 3월 글에서 이 빚을 시스템에 있는 코드의 양과 사람이 실제로 이해하는 양 사이에 벌어지는 간격으로 정리했다. 기술 부채와 달리 이 빚은 코드 품질이 좋아도 쌓인다.
두 배 어려운 디버깅
Everyone knows that debugging is twice as hard as writing a program in the first place. So if you're as clever as you can be when you write it, how will you ever debug it?
디버깅이 처음 프로그램을 짜는 것보다 두 배 어렵다는 건 누구나 안다. 그러니 짤 때 머리를 있는 대로 다 썼다면 디버깅은 대체 어떻게 할 텐가?
Hello World 를 처음 쓴 그 커니핸이 플라우거와 함께 쓴 『The Elements of Programming Style』 2판(1978)에 나오는 문장이다. 흔히 커니핸의 법칙이라고 부른다. 원래는 자기가 짠 영리한 코드를 두고 한 경고다. 두 배라는 숫자도 잰 값은 아니고 말에 무게를 싣는 수사다.
그래도 이 계산을 에이전트가 짠 코드에 대입하면 셈이 조금 이상해진다. 에이전트의 코드는 카파시 말처럼 내 평소 이해를 넘어 자라기도 한다. 짤 때 쓴 머리가 0이니 디버깅할 때는 두 배 어려운 일을 맨바닥에서 시작한다.
예를 들어 에이전트가 이런 워커를 짰다고 해보자. 여기서 큐는 최소 한 번 전달(at-least-once) 방식이라서 완료 응답(ack)을 받기 전까지 같은 작업을 다시 보낸다.
// 큐는 완료 응답(ack)을 받기 전까지 같은 작업을 다시 보낸다.
async function handle(job, queue, db) {
await db.charge(job.orderId, job.amount); // 1. 결제
await queue.ack(job.id); // 2. 완료 응답
}
// 1과 2 사이에서 프로세스가 죽으면 같은 작업이 다시 온다. 결제가 한 번 더 일어난다.코드는 멀쩡해 보이고 평소 테스트도 통과한다. 이 코드는 결제와 완료 응답 사이에서 프로세스가 죽으면 깨진다. 완료 응답을 못 받은 큐는 같은 작업을 다시 보내고 같은 주문이 한 번 더 결제된다. 메모리 가짜 객체로 이 상황을 흉내 내 보면 결제 기록이 두 건 남는다. Sphinx README 의 예시 질문 중에도 커밋은 했는데 ack 전에 죽으면 재전달로 중복이 생기는 상황이 있다. 줄 단위로 다 읽었어도 이 질문을 떠올리지 못했다면 이 코드를 이해했다고 말하기 어렵다.
고치는 방법 하나는 이미 결제한 주문을 기억해 두는 것이다.
async function charge(orderId, amount) {
if (await db.hasCharge(orderId)) return; // 이미 결제한 주문이면 건너뛴다
await db.saveCharge(orderId, amount);
}같은 재전달 상황을 다시 돌리면 이번에는 결제 기록이 한 건만 남는다. 그런데 고친 코드에도 다음 질문이 따라온다. 확인과 저장이 한 동작으로 묶여 있지 않아서 같은 주문 작업 두 개가 동시에 들어오면 둘 다 기록이 없다고 보고 둘 다 결제할 수 있다. 실제로는 DB 유니크 제약을 걸거나 결제 대행사에 멱등성 키를 같이 보내서 이 틈을 막는다. 결제 호출과 기록 저장이 따로 떨어지면 순서도 고민거리가 된다. 결제 전에 ID 부터 저장하면 도입의 두 번째 질문처럼 결제에 실패한 주문을 재시도가 건너뛴다. 질문 하나를 맞혔다고 이해가 끝나지는 않는다. 다음 질문을 스스로 떠올려야 한다.
아무도 이유를 모르는 울타리
이유를 모르는 코드를 지우기 전에에서 체스터턴의 울타리를 다뤘다. 쓸모를 모르는 울타리는 이유를 알기 전에는 치우지 말자는 이야기였다. 그 글에서 우리는 울타리 앞에 선 개혁가였다. git log 와 PR 설명을 뒤지면 대개 이유가 나왔다.
이해하지 못한 코드를 푸시하면 이 그림이 뒤집힌다. 이번에는 우리가 울타리를 세우는 쪽인데 그 이유가 누구의 머릿속에도 없다. 커밋 작성자 칸에는 필자 이름이 있다. 커밋 메시지는 에이전트가 썼다. 반년 뒤 그 울타리 앞에서 git log 를 뒤질 개혁가는 동료일 수도 있고 반년 뒤의 필자일 수도 있다. 그 사람은 무엇이 바뀌었는지는 커밋 메시지에서 읽을 수 있다. 왜 그렇게 했는지 물어볼 사람은 찾지 못한다.
Show HN 스레드의 한 댓글은 좋은 PR 설명을 쓰다 보면 이해할 수밖에 없다고 했다. 울타리 옆에 이유를 적어 두는 일과 같은 방향이다. 에이전트가 써 준 PR 설명을 그대로 붙이는 대신 내 말로 다시 써 보면 어디를 모르는지가 먼저 드러난다.
퀴즈를 대신 풀어 주는 에이전트
예상대로 Show HN 스레드에는 질문을 그대로 에이전트에게 복사해 붙인다는 사람이 나왔다. 동료가 에이전트에게 훅을 지워 달라고 했다는 이야기도 있었다. GeekNews에도 diff 와 질문이 한 화면에 있으니 모델 호출 한 번이면 통과한다는 지적이 달렸다. 퀴즈를 푸는 hook 을 만들면 된다는 농담도 함께였다. 이해했는지 확인하는 장치까지 외주 목록에 오른 모습이다.
README 도 이 한계를 숨기지 않는다.
This is practice, not a security gate. Passing does not prove who wrote the code or that you understood it unaided. The examiner is an LLM and can be wrong.
연습용 장치라서 통과해도 누가 썼는지나 혼자 힘으로 이해했는지는 증명되지 않는다. 채점자도 LLM 이라 틀릴 수 있다. sphinx yolo 로 퀴즈를 건너뛸 수도 있다. 대신 건너뛴 기록이 남는다. 채점자가 틀릴 수 있다는 대목은 AI는 왜 모른다고 말하지 않을까에서 다룬 이야기와 겹친다.
반대 방향의 지적도 있었다. 한 댓글 작성자는 모든 줄을 설명할 수 있었는데도 non-blocking 파이프에서 깨지는 stdin 읽기 코드를 배포했다고 털어놓았다. 줄을 다 읽는 것과 이해하는 것 사이에는 거리가 있다. 이해했는지 가늠하는 기준으로는 무엇이 실패하면 어떻게 되는지 말할 수 있느냐가 더 쓸 만하다. 러버덕 글에서는 말하는 오리도 말 못 하는 오리처럼 쓰자고 했다. Sphinx 는 반대로 오리가 먼저 질문하는 장치다.
배포해도 되는지는 여전히 고장 났을 때 누가 고칠 수 있느냐로 정해진다. 스핑크스의 질문을 에이전트에게 대신 풀게 하는 사람은 이미 있다. 그 코드가 새벽 세 시에 장애를 내면 알림은 푸시한 사람에게 간다. 그 알림 앞에서도 에이전트에게 물어보면 충분할까? 필자는 아직 이 질문에 자신 있게 답하지 못한다.