검증이 완료되지 않아도, 우리는 멈춘다

배포가 끝난 직후, 팀 채널에는 에러율 그래프가 캡처되어 올라온다. 몇 시간 전까지 일정하게 튀던 에러 스파이크가 눈에 띄게 줄어들어 있고, 담당자는 그 그래프를 근거로 “일단 안정화된 것 같다”고 말한다. 회의에 참여한 사람들은 각자 대시보드를 다시 확인하고, 동일한 구간을 확대해서 보며 변화가 맞다는 것을 확인한다. 그 순간, 누군가 “이 정도면 괜찮지 않냐”는 말을 던지고, 아무도 그 말을 반박하지 않는다. 더 이상 어떤 케이스가 사라졌는지, 사라진 에러가 실제로 해결된 것인지, 아니면 다른 경로로 빠진 것인지는 확인되지 않는다. 확인하지 않은 상태가 아니라, 확인하지 않아도 되는 상태로 판단이 내려진다. 그 판단은 빠르게 다음 작업으로 이어지고, 수정 사항은 “효과 있음”으로 기록된다. 그 사이에서 누락된 입력 조건이나 재현되지 않는 케이스는 더 이상 다뤄지지 않으며, 남아 있는 문제는 존재하지 않는 것처럼 취급된다.
이 장면은 익숙하다. 개선된 지표는 바로 공유되고, 공유된 지표는 판단을 대신하며, 판단은 검증을 종료시키는 신호로 작동한다. 전체를 확인하는 대신 일부를 확인하는 선택은 반복되고, 그 선택은 매번 합리적으로 보인다. 모든 케이스를 확인하는 데 필요한 시간은 항상 부족하고, 결과를 보여줘야 하는 압박은 항상 앞에 있기 때문에, 검증은 끝나는 것이 아니라 끊긴다. 끊긴 검증은 다시 이어지지 않고, 이후의 변경은 그 상태 위에서 쌓인다. 이 시점부터 시스템에는 “확정된 이전 상태”가 존재하지 않으며, 무엇이 바뀌었는지를 비교할 수 있는 기준도 함께 사라진다. 검증이 멈춘 것이 아니라, 멈춘 상태가 기준으로 승인된다.
우리는 검증을 끝내는 게 아니라, 멈추는 타이밍을 승인한다
비교 기준이 없기 때문에, 변화는 해석되지 않는다

며칠 뒤, 비슷한 문제가 다시 보고된다. 이번에는 특정 요청에서 응답이 비정상적으로 길어졌다는 내용이다. 담당자는 바로 코드를 수정하고, 동일한 요청을 몇 번 반복 호출해본다. 이전에는 실패하던 케이스가 정상 응답을 반환하고, 그는 그 결과를 캡처해서 공유한다. “이제 된다”는 말과 함께 변경 사항이 배포되고, 문제는 해결된 것으로 처리된다. 하지만 그 테스트는 이전 상태와 동일한 조건에서 수행된 것이 아니다. 요청 파라미터는 일부 다르고, 실행 환경은 이미 다른 수정이 반영된 상태이며, 동일한 입력을 반복 실행한 기록도 남아 있지 않다. 이전에는 어떤 조건에서 실패했는지, 지금은 어떤 조건에서 성공하는지, 그 차이는 확인되지 않는다. 확인할 수 있는 기준이 없기 때문에, 확인 자체가 생략된다. 결과만 남고, 그 결과를 해석할 수 있는 기준은 존재하지 않는다.
이 패턴도 반복된다. “안 되던 게 된다”는 단일 사례는 빠르게 공유되고, 그 사례는 변화의 증거로 받아들여진다. 동일 조건에서의 비교는 점점 어려워지고, 비교를 유지하기 위한 비용은 자연스럽게 제거된다. 환경을 고정하지 않고, 입력을 기록하지 않으며, 반복 실행을 하지 않는 상태가 지속되면, 어떤 변화도 이전 상태와 연결되지 않는다. 변화는 존재하지만, 그 변화가 어떤 방향으로 작용했는지는 판단할 수 없다. 판단할 수 없는 상태에서는 해석이 불가능하고, 해석이 불가능하면 선택은 다른 방식으로 이루어진다. 더 이상 결과를 비교하지 않고, 결과를 만들어내는 방식 자체를 바꾸게 된다.
비교 기준이 없는 상태에서의 변화는, 해석이 아니라 추측이다
해석할 수 없기 때문에, 우리는 여러 개를 동시에 바꾼다

같은 문제가 다시 나타났을 때, 이번에는 수정 범위가 넓어진다. 담당자는 원인을 특정하지 못한 상태에서 여러 지점을 동시에 건드린다. 로직 일부를 수정하고, 설정 값을 조정하며, 관련된 파라미터를 함께 변경한다. 각각을 따로 적용해보지 않고, 한 번에 묶어서 배포한다. 몇 번의 호출 끝에 정상 동작이 확인되고, 그 조합은 “효과 있는 방식”으로 공유된다. 하지만 어떤 변경이 실제로 영향을 미쳤는지는 확인되지 않는다. 그 조합을 구성하는 요소 중 일부는 불필요했을 수도 있고, 오히려 다른 문제를 만들었을 수도 있지만, 그 차이는 구분되지 않는다. 하나씩 나눠서 확인할 수 있는 기준이 없기 때문에, 나누는 시도 자체가 사라진다. 결과는 만들어졌지만, 그 결과를 만든 경로는 남지 않는다.
이 선택은 빠르게 굳어진다. 단일 변경을 반복하는 대신, 한 번에 여러 개를 바꾸는 방식이 더 효율적으로 보이기 시작한다. 비교가 불가능한 상태에서는 어떤 방식이 더 나은지 판단할 수 없기 때문에, 더 빠르게 결과를 만드는 방식이 선택된다. 실험은 설계되지 않고, 조합은 점점 커진다. 이 시점에서 중요한 변화가 하나 발생한다. 여러 개를 동시에 바꾸는 순간, 각 변경이 어떤 영향을 미쳤는지 분리할 수 없게 된다. 결과는 존재하지만, 그 결과를 만든 원인은 나눠서 설명할 수 없는 상태가 된다. 설명되지 않는 결과는 기록으로 남지 않고, 덩어리로만 기억된다. 이후 남는 것은 “무엇이 작동했는지”가 아니라, “무엇을 함께 넣었는지”에 대한 인상이다.
해석할 수 없는 상태에서의 동시 변경은, 원인을 지우고 결과를 기억으로 바꾼다
기준은 하나가 아니라 여러 개가 된다

처음에는 단순한 예외였다. 특정 케이스에서만 다른 기준이 적용됐고, 그 기준은 기존 로직을 보완하기 위한 것이었다. 하지만 그 예외가 유지된 채로 시간이 지나면, 그것은 더 이상 예외가 아니라 또 하나의 기준이 된다. 기존 기준은 사라지지 않고 그대로 남아 있고, 새로 추가된 기준도 제거되지 않는다. 그렇게 기준은 교체되지 않고 누적된다.
문제는 이 시점부터 발생한다. 동일한 입력에 대해 서로 다른 기준이 동시에 적용 가능한 상태가 만들어진다. 어떤 기준을 적용하느냐에 따라 결과가 달라질 수 있지만, 그 선택 기준은 명확하게 정의되어 있지 않다. 코드 상에서는 분기문으로 표현되어 있지만, 그 분기를 선택하는 근거는 이미 코드 밖으로 밀려나 있다. 결국 판단은 사람이 하게 되고, 사람마다 다른 기준을 적용하기 시작한다.
이 상태에서는 더 이상 “정확한 기준”이라는 개념이 유지되지 않는다. 기준은 존재하지만, 어떤 상황에서 어떤 기준을 적용해야 하는지에 대한 기준은 존재하지 않는다. 기준 위에 기준이 없는 상태가 된다. 그리고 이 순간부터 시스템은 일관성을 잃는다. 같은 입력이 들어와도 같은 결과를 보장할 수 없게 되고, 결과의 차이를 설명할 수 있는 언어도 사라지기 시작한다.
기준이 사라진 순간, 되돌릴 수 없게 된다

여러 기준이 동시에 유지되는 상태 자체가 문제의 핵심이 아니다. 진짜 문제는 그 상태가 만들어지는 순간, 어떤 기준이 올바른지 판단할 수 있는 기준 자체가 사라진다는 데 있다. 서로 다른 기준들이 공존하는 구조에서는, 어떤 기준을 제거해야 하는지조차 결정할 수 없다. 제거 판단 역시 또 하나의 기준을 필요로 하기 때문이다.
이 시점에서 되돌리는 시도는 항상 실패한다. 특정 기준을 제거하면 일부 케이스는 정상으로 돌아오지만, 다른 케이스는 깨진다. 반대로 다른 기준을 제거하면 또 다른 문제가 발생한다. 어느 쪽이 더 “맞는 상태”인지 판단할 수 없기 때문에, 제거는 항상 리스크로 인식되고 결국 실행되지 않는다. 그렇게 모든 기준은 살아남는다.
결과적으로 시스템은 더 이상 “수정 가능한 상태”가 아니라 “유지될 수밖에 없는 상태”로 전이된다. 어떤 선택도 전체를 더 나은 방향으로 만들지 못하고, 항상 일부를 더 나쁘게 만든다. 이 구조에서는 복구라는 개념이 성립하지 않는다. 복구를 시도하는 순간 또 다른 기준이 추가될 뿐이다. 이때부터 변화는 개선이 아니라 누적이 되고, 시스템은 비가역 상태로 고정된다.
판단할 수 없기 때문에, 우리는 복사해서 붙인다

문제가 다시 발생했을 때, 담당자는 더 이상 원인을 찾으려고 하지 않는다. 어디를 수정해야 하는지 판단할 수 없기 때문이다. 어떤 조건이 문제를 만들었는지, 어떤 변경이 영향을 줬는지 확인할 수 있는 기준이 없기 때문에, 새로운 접근을 설계하는 것은 불가능에 가깝다. 대신 그는 이전에 “됐던 것”을 떠올린다. 특정 상황에서 정상 동작을 만들었던 코드 조합, 문제를 막았던 분기, 일시적으로라도 결과를 안정시켰던 설정 값이 기억 속에 남아 있다. 그는 그 조합을 현재 로직으로 가져온다. 코드를 이해하려고 하지 않고, 왜 동작했는지도 확인하지 않은 채, 그대로 붙여 넣는다.
이 선택은 점점 자연스러워진다. 새로운 코드를 작성하는 것보다, 이미 동작했던 것을 가져오는 것이 더 빠르고 안전해 보이기 때문이다. 검증할 수 없는 상태에서는 새로운 시도가 실패할 확률이 높고, 실패했을 때 되돌릴 기준도 존재하지 않는다. 반대로, 이전에 한 번이라도 성공했던 조합은 그 자체로 근거가 된다. 그 결과, 코드베이스에는 동일한 문제를 막기 위한 서로 다른 조합들이 계속 추가된다. 각 조합은 특정 상황에서는 효과를 내지만, 전체 흐름 안에서 어떤 역할을 하는지는 설명되지 않는다. 서로 다른 시점에서 만들어진 코드들이 하나의 로직 안에서 섞이고, 그 관계는 점점 복잡해진다.
이 시점부터 개발은 설계가 아니라 선택의 반복이 된다. 무엇이 맞는지를 판단하지 않고, 무엇이 한 번이라도 됐는지를 기준으로 가져온다. 그 선택은 누적되고, 누적된 선택은 구조를 만든다. 하지만 그 구조는 의도된 것이 아니라, 복사된 결과들의 집합이다. 이 구조는 한 번 만들어지면 되돌릴 수 없다. 어떤 조각이 어떤 의미를 가지는지 설명할 수 없기 때문에, 제거도 재구성도 시도되지 않는다.
판단할 수 없는 상태에서는, 설계 대신 복사가 선택된다
이해할 수 없기 때문에, 우리는 삭제하지 못한다

코드를 열어보면, 현재 요구사항과 맞지 않는 조건들이 명확하게 보인다. 더 이상 사용되지 않는 파라미터, 특정 값에서만 동작하는 분기, 의미를 설명할 수 없는 fallback 로직이 그대로 남아 있다. 논리적으로 보면 제거하는 것이 맞다. 하지만 실제로는 그 지점에서 멈춘다. 왜 추가되었는지 알 수 없기 때문이다.
이전 단계에서 이미 코드의 의미는 해석되지 않는다. 어떤 조건이 어떤 문제를 막기 위해 들어왔는지 설명할 수 없고, 동일한 상황을 재현할 수도 없다. 이 상태에서 삭제는 곧 실험이 된다. 하지만 그 실험은 결과를 비교할 기준이 없기 때문에 검증될 수 없다. 삭제했을 때 문제가 사라지는지, 아니면 다른 경로에서 다시 나타나는지 판단할 수 없다. 결국 삭제는 “확인되지 않는 리스크”가 된다.
그래서 선택은 고정된다. 지우지 않는다.
이 결정이 반복되면, 코드 안의 모든 조건은 의미를 잃는다. 더 이상 “필요해서 존재하는 것”이 아니라, “존재하기 때문에 유지되는 것”으로 바뀐다. 한 번 들어온 로직은 설명 여부와 관계없이 유지되고, 제거되지 않는다. 그 결과 구조는 점점 정리되지 않고, 누적된다.
이 상태에서는 더 이상 구조를 재구성할 수 없다. 기존 흐름을 이해할 수 없기 때문에, 그 흐름을 정리하는 것도 불가능하다. 새로운 요구사항이 들어오면, 기존 코드를 수정하지 않는다. 그 위에 조건을 추가한다. 삭제가 불가능한 구조에서는, 변경은 항상 추가로만 이루어진다.
이 시점에서 구조는 이미 설계의 결과가 아니다. 설명되지 않은 선택들이 제거되지 못한 채 굳어져 남은 형태에 가깝다. 무엇이 왜 존재하는지 알 수 없고, 그럼에도 그대로 유지되는 상태가 구조를 대신한다.
이 시점부터 시스템은 “유지되는 구조”가 아니라, “삭제할 수 없어서 고정된 구조”가 된다.
이해할 수 없는 시스템에서는, 삭제는 선택지가 아니다
고칠 수 없기 때문에, 수정은 문제를 이동시킨다

장애가 발생하면, 담당자는 기존 코드를 건드리지 않는다. 문제를 해결하기보다, 문제가 드러난 조건 주변에 새로운 분기를 추가하고 특정 케이스를 우회하도록 만든다. 기존 흐름은 그대로 유지되고, 새로운 조건만 그 위에 얹힌다. 수정은 빠르게 배포되고, 해당 케이스에서는 문제가 사라진 것처럼 보인다. 하지만 그 변화는 원인을 제거한 결과가 아니라, 특정 경로를 잠시 비켜간 결과에 가깝다. 같은 입력이 다른 경로로 들어오면, 문제는 다시 나타난다. 담당자는 다시 코드를 열고, 이번에는 다른 조건을 추가해 그 경로를 막는다. 이 과정에서 기존 조건은 제거되지 않고 그대로 유지된다. 제거했을 때 어떤 영향이 생길지 알 수 없기 때문이다.
이 선택이 반복되면서, 코드 안에는 서로 다른 시점에서 추가된 조건들이 겹쳐 쌓인다. 각 조건은 특정 상황에서는 문제를 막지만, 동시에 다른 조건과 충돌하거나 예상하지 못한 흐름을 만든다. 어떤 분기가 먼저 실행되는지, 어떤 조건이 우선되는지, 전체 흐름을 한 번에 설명할 수 있는 사람은 점점 사라진다. 이 시점부터 수정은 더 이상 구조를 바꾸는 행위가 아니다. 단지 기존 흐름을 유지한 채, 새로운 경로를 하나 더 추가하는 작업으로 고정된다.
문제는 여기서 끝나지 않는다. 어느 순간부터는 어떤 수정도 “문제를 줄이는 방향”으로 작동하지 않는다. 하나의 조건을 추가하면 특정 케이스는 사라지지만, 그로 인해 다른 경로에서 새로운 문제가 발생한다. 수정은 문제를 제거하지 않는다. 단지 문제의 위치를 바꾼다. 이전에는 A에서 발생하던 문제가 B로 이동하고, B를 막으면 다시 C에서 나타난다. 그때마다 새로운 조건이 추가되고, 기존 조건은 제거되지 않는다.
이 시점에서 “수정”이라는 행위는 본래의 의미를 잃는다. 수정은 더 이상 복구 수단이 아니다. 원인을 제거하고 상태를 정상으로 되돌리는 과정이 아니라, 기존 구조 위에서 문제를 다른 위치로 밀어내는 방식으로만 작동한다. 어떤 변경도 시스템을 안정화시키지 못하고, 단지 특정 상황을 잠시 가릴 뿐이다.
그리고 이 상태가 반복되면, 문제를 이동시키는 방식 자체가 시스템의 정상 동작으로 승인된다.
고칠 수 없는 시스템에서는, 수정은 복구가 아니라 이동이다