신뢰는 Code Review 안에 있지 않다
게시일 2026년 6월 30일
당신이 되어버린 게이트
Agent는 사람이 읽을 수 있는 속도보다 빠르게 코드를 쓴다. 이것은 예측이 아니다. 지금의 기준선이다. 하나의 Agent는 오후 한 번에, 팀이 일주일 동안 꼼꼼히 읽을 수 있는 양보다 더 많은 코드를 내보낸다. Code Review는 쓰기와 읽기가 같은 속도로 일어난다고 조용히 가정해 왔다. 처음으로, 둘은 더 이상 같은 속도가 아니다.
그래서 나쁜 선택지가 두 개 남는다. 선을 지키며 전부 읽으면, 팀 전체가 뒤에 줄 서는 스로틀이 된다. 그대로 통과시키면 리뷰는 도장이 된다. 대부분의 팀은 조용히 도장을 선택했다. 최악은 지나쳐버린 버그가 아니다. 놓친 버그는 되돌릴 수 있다. 가짜 확신은 되돌릴 수 없고, 그것은 일정에 맞춰 만들어진다. 도장은 여전히 신뢰의 느낌을 만들어내고, 당신은 그 느낌 위에서 배포한다. 당신은 신뢰를 만들지 못하고 있는 것이 아니다. 가짜 신뢰를 만들고 있다.
하지만 한 걸음 물러서 보자. 게이트는 애초에 목적이 아니었다. 필요한 것은 읽는 행위가 아니다. 프로젝트에 대한 통제력과 그 안에 근거 있는 신뢰를 갖는 것, 즉 무엇을 내보내고 있는지 안다는 지속되는 확신이다. diff를 읽는 것은 언제나 대리물에 불과했고, 그것 자체와 혼동되었을 뿐이다. 외주 줄 수 없는 것은 통제와 신뢰다. 그것은 당신의 것으로 남는다. 읽는 일은 그것을 하기에 가장 알맞은 사람, 혹은 무엇에게든 갈 수 있다.
쓸 수 없다면, 읽을 수도 없다
먼저 당신이 읽는 일이 왜 가치 있었는지부터 보자. 당신이 조심스러워서가 아니다. 리뷰는 그것을 하는 개발자만큼만 좋다. 그리고 개발자를 좋게 만드는 것은 하나다. 손이 충분히 자주 코드 안에 있어, 세부를 살아 있는 상태로 붙잡고 있다는 것. 그 살아 있는 이해가 리뷰의 엔진이고, 읽기는 그것이 드러나는 장소일 뿐이다. 그것을 빼앗으면 읽기는 빈 동작이 된다. 눈이 줄 위를 움직일 뿐이다.
Agent 시대는 한 가지 이유로 당신을 그 개발자의 자리에서 끌어낸다. 그 이유는 두 가지 방식으로 드러난다. 당신과 Agent는 코드에 같은 방식으로 비용을 치르지 않는다. 비용 모델이 다르고, 모든 것은 거기서 이어진다.
실시간에서는 그 차이가 속도로 보인다. Agent는 거의 제로에 가까운 한계비용으로 반복한다. 그 사이에서 사람이 손으로 코드를 쓰면, 일이 기다리는 느린 차선이 된다. 솔직한 선택은 멈추고 Agent가 달리게 하는 것이다. 그리고 그렇게 하는 순간, 살아 있는 이해는 흐려진다. 더 이상 당신이 그것을 만들고 있지 않기 때문이다.
코드베이스 전체에서는 같은 차이가 일관성으로 보인다. 이는 따라잡을 만큼 빠른 사람에게도 해당된다. 코드베이스는 하나의 비용 모델이 내린 수천 개의 작은 판단이 결정화된 것이다. 어떤 중복은 리팩터링할 가치가 있는지, 어느 정도의 로깅이 노이즈를 감수할 만한지, 경계가 실제로 어디에 놓이는지. Agent는 피로나 지루함 때문에 로깅을 줄이거나 리팩터링을 건너뛰지 않고 그 엄격함을 고르게 유지한다. 사람이 손으로 끼어드는 것은 더 나쁜 코드를 쓰는 것이 아니다. 다만 다른 비용 모델에 최적화된 코드를 쓰고, 다른 모델에 맞춰진 시스템에 그것을 떨어뜨리기 때문에, 시스템을 읽을 수 있게 만들던 일관성을 쪼갠다. 높은 수준은 당신의 것으로 남는다. 시스템이 무엇을 위한 것인지, 어디로 갈 수 있는지, 어떤 위험을 미리 볼 가치가 있는지, 어떤 트레이드오프를 선택할 가치가 있는지. 그 아래 구현의 결은 코드를 위해 비용을 치르는 쪽으로 가라앉는다.
분명히 말하자면, 이는 이미 모든 곳을 지배하는 체제가 아니라 우리가 향해 만들고 있는 특정한 체제 안에서의 이야기다. 프로젝트가 Agent-native가 되었고, Agent가 그 도메인에서 최소한 당신만큼 강하며, 코드베이스도 이미 Agent의 결로 되어 있다면, 사람은 속도로나 결로나 더 이상 진짜로 코드를 쓸 수 없다. 그리고 코드를 쓸 수 없는 사람은 리뷰할 만큼 그것을 살아 있게 붙잡을 수 없다. 자연법칙은 아니다. 그러나 그 체제 안에서는 결론이 단단하다. 리뷰를 계속한다면, 당신은 더 이상 판단할 자격을 갖지 못한 줄을 눈으로 훑고 있는 것이다. Agent가 프로젝트를 빠르게 만들지 못했을 때 병목이 거의 언제나 아직 손으로 쓰고 손으로 읽는 한 사람인 이유도 이것이다. 신뢰도, 방향감도 아니다. 그것들은 남는다. 병목은 아직 키보드 위에 놓인 손이다.
이 중 어느 것도 새롭지 않다. Agent 시대가 그것을 무시할 수 없게 만들었을 뿐이다. 신뢰는 Code Review 안에 있지 않았다. 지금도 아니고 전에도 아니었다. 큰 프로젝트를 이끄는 사람은 코드를 읽지 않는다. 당신이 아는 가장 시니어한 엔지니어가 자신이 책임지는 시스템을 마지막으로 한 줄씩 읽은 것이 언제였나? 그들은 아래 사람들에 대한 신뢰와 대략적인 아키텍처 감각으로 조종하고, 아무도 그것을 무모하다고 부르지 않는다. 한 줄씩 읽는 일은 낮은 레벨이 감당할 수 있었던 것이고, 감당할 수 있었다는 사실이 신뢰한다는 것과 혼동되었다. Agent 시대는 모든 레벨을, 최상위가 언제나 있던 위치로 밀어 올렸을 뿐이다.
그렇다면 신뢰는 어디서 왔을까? 오래 함께 일해서 예측할 수 있는 작성자, 부하 아래에서도 버티는 테스트 스위트, 시스템이 수백 번 돌아가는 것을 지켜본 파트너, 깨끗하게 돌아온 beta, 세 번의 릴리스 동안 조용했던 대시보드. Code Review는 그 묶음 안에서도 언제나 약한 채널 중 하나였다. 그것이 계속 가리켰지만 끝내 제대로 전달하지 못한 것은 하나의 질문에 대한 답이었다. 시스템 전체가 아직 붙들려 있는가? 볼륨이 올라가도 그 질문은 사라지지 않는다. 더 날카로워지고, diff가 아니었던 계기가 필요해진다.
더 빠른 코드, 더 빠른 부패
시스템이 변화 속에서 나이를 먹을수록, 당신의 것처럼 보일 수 있는 프로그램의 수는 계속 늘어난다. 젊고 단단한 시스템에서는 살아남는 것들이 거의 같은 프로그램이다. 방치한 채 나이를 먹이면 가족이 벌어진다. 같은 spec, 같은 green check, 그러나 서로 일관되지 않은 구현들의 거대한 공간이 모두 통과한다. 그 벌어짐은 일종의 엔트로피이고, 이 단어가 맞는 이유는 방향이 있기 때문이다. 그대로 두면 그것은 커지기만 한다. 날카로운 경계와 정직한 테스트는 그것을 줄이고, 지름길과 중복된 source of truth는 다시 자라게 한다.
이 역시 새롭지 않다. Lehman은 1970년대에 이미 적었다. 사용 중인 프로그램은 누군가 그것을 붙잡기 위해 실제 일을 쓰지 않는 한, 릴리스를 거듭하며 복잡도를 계속 얻는다. software rot, code decay. 이 분야에는 소프트웨어를 배포해 온 만큼 오래된 말들이 있었다. 새로워진 것은 현상이 아니다. 온도다. 작동하는 제품은 한 시간 만에 세워질 수 있고, 하루 뒤에는 유지보수 불가능하게 남겨질 수 있다. 어느 쪽이든 같은 속도다. Agent가 엔트로피를 발명한 것이 아니다. 예전에는 속도를 낮추던 것, 사람이 코드를 쓰고 읽을 수 있는 속도를 제거해 온도를 올렸을 뿐이다.
엔트로피 상승은 하나의 상실로 느껴진다. 더 이상 자신의 시스템을 예측할 수 없다. 여기서 무언가를 바꾸기 전에, 저기서 무엇이 일어날지 말할 수 없다. 시스템에 대한 신뢰는 언제나 이것이었다. 그것을 예측할 수 있는 능력. 느낌도 아니고 green check도 아니다. 그 물건이 어떻게 행동하는지 안다는 획득된 확신이다. diff는 그것을 돌려줄 수 없다. diff는 하나의 변경을 보여주고, 예측은 전체의 성질이기 때문이다. 그래서 질문은 어떤 diff가 맞는지가 아니다. 전체의 엔트로피가 오르고 있는지, 붙들려 있는지다. 그것은 읽을 수 있고, 그것을 읽는 데 diff는 필요 없다.
diff 읽기를 멈춰라
그렇다면 모든 diff를 열지 않고 엔트로피를 어떻게 읽을까. diff는 변경을 보여준다. 시스템을 보여줄 수는 없다. diff 읽기를 멈추고 시스템을 읽기 시작한다. 테스트 스위트와 대시보드는 신뢰가 오던 곳이었다. 아래의 신호들은 시스템이 돌아가는 동안 그것을 지금 읽는 방식이다.
리뷰는 산출물을 읽는다. 눈앞의 변경을 한 줄씩 읽는다. 하지만 알고 싶은 것은 어느 하나의 변경의 성질이 아니다. 시스템 전체의 성질이다. 그리고 시스템 전체는 누가 파일을 열지 않아도, 언제나 자기 자신에 대해 말하고 있다. 신호를 낸다. 당신은 그것을 읽는다. 그래서 이것은 리뷰가 닿지 못했던 곳에서 작동한다. 리뷰는 산출물을 향해 있었고, 당신이 알아야 했던 것은 처음부터 산출물 안에 있지 않았다.
버그 목록은 가장 직접적이다. 예측이 아니라 시스템이 이미 잘못된 곳의 기록이기 때문이다. 버그는 모이고, 그 모임이 지도가 된다. 같은 모듈이 주마다 다시 떠오르고, 같은 contract가 매달 새 edge case를 키운다. 거기가 시스템이 새는 곳이기 때문이다. Agent의 실패는 특히 읽기 쉽다. Agent의 실수는 보통 깨끗하지 않았던 입력으로 되돌아가기 때문이다. 모호한 contract, 어떤 case를 말하지 않은 spec. 사람은 습관으로 보정하고 틈을 숨긴다. Agent는 그 안으로 걸어 들어가 자리를 표시한다.
다른 신호들은 아직 어디서 깨질 수 있는지를 보고한다. mutation testing은 작동 중인 코드의 조건을 뒤집거나 branch를 지우고, 어떤 테스트라도 빨갛게 되는지 묻는다. 살아남은 mutant는 당신의 스위트가 아직 축복하는 잘못된 버전의 프로그램이고, "테스트가 통과한다"와 "행동이 고정되어 있다" 사이의 정확한 간격이다. 정적 구조는 깨짐이 시작되는 곳을 보여준다. branch로 두꺼워진 함수는 spec이 필요로 하는 것보다 더 많은 상태를 가진다. 두 곳에 저장된 사실은 언젠가 누군가 하나만 갱신하고 다른 하나를 잊는 날을 초대한다. 그리고 Agent들이 함께 일하는 곳에서는 하나의 신호가 실시간이다. 그들의 변경이 충돌하는지 여부다. 중요한 충돌은 두 Agent가 인접한 코드를 만지는 것이 아니다. 그것은 평범한 동시 작업이다. 중요한 것은 같은 seam이 반복해서 다투어지는 것이다. 그곳의 경계가 틀렸다는 뜻이다.
지도는 어디를 겨냥해야 하는지도 알려준다. 버그 목록이 빽빽하고 branch가 가장 깊은 곳에는 알려지지 않은 버그도 가장 빽빽하다. 그래서 그곳으로 테스트 프레임워크를 겨냥한다. coverage 숫자를 채우기 위해서가 아니라, 얽힌 영역에 숨어 있는 실패를 끌어낼 case를 만들기 위해서다. 발견된 버그는 그 위에서 테스트가 빨간색에서 초록색으로 바뀌어야 한 번으로 친다. 그래야 수정이 고정되고 조용히 돌아올 수 없다.
하지만 green test는 공짜 품질이 아니다. 대부분의 스위트가 당신에게 거짓말하는 곳이 여기다. 테스트의 초록색이 가치를 얻으려면 그 check가 단단해야 한다. 실제 행동에 대해, 실제 payload 위에서 assert해야 하며, 진짜 regression이면 빨갛게 변해야 한다. 그리고 그 행동이 흐르는 path 전반에 걸쳐 완전해야 한다. 한 path에서는 통과하지만 다른 path가 실행되지 않는다면, 그것은 통과한 테스트의 옷을 입은 미테스트 case다. 코드의 모양을 확인하고 효과를 확인하지 않는 테스트는 아무 데도 연결되지 않은 green light다. 같은 의심을 모든 신호에 적용한다. 조용한 버그 목록은 모듈이 건강하다는 뜻일 수도 있고, 아무도 그것을 쓰지 않는다는 뜻일 수도 있으며, instrumentation이 꺼졌다는 뜻일 수도 있다. 신호가 없다는 것은, 그것이 울렸어야 했다는 점을 먼저 세워야만 의미가 있다. 그래서 당신은 diff에 대해서는 내려놓으려 했던 기준을 각 계기에도 적용한다. 실패할 수 있음이 증명되어야만 신뢰한다. 품질은 check가 얼마나 단단한지에 있다. 누군가 줄을 읽었다는 사실에는 없다.
여기에 기대기 전에 두 가지 정직함이 필요하다. 첫째, 이 읽기들의 대부분은 뒤따라온다. 버그 목록은 이미 깨진 곳이고, 살아남은 mutant는 이미 가진 테스트의 틈이다. 부패가 착지한 뒤 한 박자 늦게 잡는다. 아무것도 잡지 않는 도장보다는 낫지만, 예방은 아니다. 예방은 상류, 입력으로 이동한다. 그리고 어떤 하나의 읽기도 전체 그림이 아니므로, 여러 개를 읽고 서로 삼각측량하게 해야 한다. 둘째, 높은 엔트로피가 모두 decay는 아니다. 하나의 interface를 만족하는 수백 개의 extension을 가진 plugin system은 숫자로 보면 엄청나게 높은 엔트로피지만, 그것은 설계가 의도대로 작동한다는 뜻이다. 신호는 엔트로피가 어디 있는지 보여준다. 어떤 종류인지는 말해주지 않는다. 그 판단은 공짜로 오지 않는다.
리뷰는 이벤트이고, 신뢰는 상태다
이 모든 것이 diff에서 떠나야 했던 이유는 속도보다 깊다. 리뷰는 당신이 붙잡으려는 것에 맞는 모양이 아니다. 리뷰는 이벤트다. 신뢰는 상태다. 이벤트는 한 번 일어나 판정을 남기고 사라진다. 당신이 신뢰하려는 시스템 안에는 아무것도 축적되지 않는다. 상태가 실제로 원하는 것이다. 자신의 시스템을 예측할 수 있다는 지속되는 확신. 그리고 아무것도 남기지 않는 이벤트들로 상태를 만들 수는 없다. 이것이 제목이 가리키던 문장이다. 이벤트는 상태를 담을 수 없기 때문에, 신뢰는 Code Review 안에 있지 않다. 이벤트를 천 번 돌려도 그 물건은 아직 없다. 신호는 연속적이므로 신뢰는 거기서 축적된다. 도장은 애초에 그럴 수 없었다.
그래서 신뢰의 계기로서의 리뷰는 떠난다. 코드를 읽는 일이 사라지는 것은 아니다. 이해가 사는 곳으로 이동한다. Agent 시대에는 그것이 코드를 쓰는 Agent이지 당신이 아니다. 반복마다 세부에 손을 넣고 있는 것은 그들이다. 떠나는 것은 당신이 게이트로서 그것을 하는 일이다. 그리고 사람들이 먼저 떠올리는 고위험 case, security boundary, 되돌릴 수 없는 release야말로 피곤한 사람의 스캔이 언제나 가장 못하던 곳이다. 이제 그것들은 더 강한 것들 위에 탄다. 타입 시스템이 위반을 불가능하게 만드는 property, assertion이 단단한 check, 읽기가 놓쳤을 regression을 monitoring이 잡는 staged rollout.
여기서 신호를 읽는 것은 지켜보는 것 이상이 된다. 각각은 원인을 되짚는다. 살아남은 mutant는 아무도 고정하지 않은 행동을 가리킨다. drift한 경계는 더 이상 하나의 뜻을 갖지 않게 된 contract를 가리킨다. 버그 목록의 cluster는 더 깊은 곳을 가리킨다. 이 코드는 유능한 model이 썼다. 그래서 어떤 모듈이 계속 버그를 만든다면, 가장 그럴듯한 원인은 model이 그 부분에 약하다는 것이 아니다. 그 모듈의 interface가 그것을 상대하는 모든 것을 오도한다는 것이다. 버그는 경계가 틀렸다고 시스템이 말하는 방식이다. 그래서 방금 만든 테스트 프레임워크 아래에서 그것을 리팩터링한다. 그 프레임워크가 행동을 고정하므로, 모든 변경은 행동을 보존한다고 증명되거나 진짜 버그로 드러난다.
거의 언제나 다음 행동은 "다음 diff를 더 세게 리뷰하라"가 아니다. 실패를 있는 그대로 다루는 것이다. 루프를 위한 진단 입력으로 다루는 것이다. 실패는 model에 대한 판결이 아니다. 당신이 깨끗하게 두지 않은 입력의 지도다. 그것은 당신이 준 어떤 조건이 아직 깨끗하지 않았는지 정확히 알려준다. 그렇게 보면 Agent의 실수는 그것이 만들어내는 가장 가치 있는 것이 된다. 하나하나가 다른 방식으로는 볼 수 없었던 틈을 가리키는 라벨 붙은 포인터다.
역할로 보면 가장 쉽게 느껴진다. 아키텍처를 설계하는 Agent가 실패한다. 원인은 드물게 그 추론이다. 대개 mission, 당신이 향하는 방향이 입력으로 충분히 명확하지 않았기 때문이다. 구현을 쓰는 Agent가 실패한다. 원인은 드물게 지능이다. environment, test surface, validation harness가 마련되지 않았거나, 당신이 그은 경계가 그것을 오도했기 때문이다. 당신의 의도에 대해 테스트하는 Agent가 중요한 것을 놓친다. 의도가 테스트할 수 있을 만큼 명확하게 표현되지 않았기 때문이다. 실패는 Agent에서 끝나지 않는다. 사슬을 타고 올라가 입력에 도착한다. 당신이 흐릿하게 말한 것, 만들지 않은 surface, 부드럽게 남긴 boundary에 닿는다.
그것이 루프가 닫히는 지점이다. 엔트로피가 올라가는 곳을 읽고, 그것이 올라가도록 허용한 입력까지 실패를 추적하고, 그 입력을 조인다. 때로는 더 날카로운 spec을 뜻한다. 때로는 model을 오도한 contract를 리팩터링하는 일이다. 그리고 가장 강한 경우에는 애초에 실패가 일어날 수 없도록 설계를 바꾸는 것이다. 나쁜 상태를 표현할 수 없게 만드는 타입, 사후에 check하는 것이 아니라 구성으로 enforced되는 invariant다. 마지막 수단은 illegal state로 부호화할 수 있는 실패에만 닿는다. 외부와 timing에 속한 것들, 응답하지 않는 credential, 두 service 사이의 race는 줄일 수 없고, 앞서 말한 단단한 check와 staged rollout으로 돌아간다. 시스템은 누군가 더 많은 코드를 리뷰했기 때문에 더 예측 가능해지는 것이 아니다. 각 실패를 써서 조건을 더 참되게 만들었기 때문에 더 예측 가능해진다.
실천을 한 문장으로 말하면 이렇다. 게이트를 지키는 일을 멈추고, 시스템을 읽고, 그 시스템에 들어가는 입력을 계속 조여라. Agent가 누구보다 빠르게 움직이고 아무도 다 읽을 수 없게 된 뒤에도 품질과 속도를 같은 손 안에 두기 위해, 우리가 찾은 유일한 방법이다.
그리고 하나의 질문이 남는다. 이 글 전체가 향해 온 질문이다. 당신이 더 이상 게이트가 아니고, 코드를 읽는 사람조차 아니며, 하루가 시스템을 읽고 그것에 들어가는 것을 조이는 일이라면, 인간의 일은 정확히 무엇인가? 그것이 다음 글이다.



