나쁜 코드는 어디까지 나빠질 수 있을까

Simon Willison의 짧은 코멘트에서 출발해 유지보수 비용과 테스트, 리뷰, 보안 부채, 코딩 어시스턴트 시대의 실무 감각을 정리합니다.

원문 논문 보기
'나쁜 코드는 어디까지 나빠질 수 있을까' 기사 커버 이미지

노시욱 (Patrick Rho) · AI 연구 해설

이 글은 Simon Willison이 쓴 짧은 코멘트 "There's No Limit to How Bad Code Can Get"을 다룹니다. Simon Willison은 오랜 시간 웹 개발과 실무 도구를 다뤄온 개발자이자 기록자로 알려져 있습니다. 원문 링크는 제목 아래에 걸려 있습니다. 이 코멘트의 주장은 유지보수와 디버깅이 소프트웨어 생애주기 비용을 지배하므로 읽기 어렵고 테스트 없는 코드는 작성 순간의 절약을 훨씬 웃도는 비용을 만든다는 것입니다. 이번 글에서는 그 진단을 따라가면서 중복과 비대해진 객체, 삼켜버린 예외, 테스트 부재, 보안 부채, 조직의 인센티브, 그리고 코딩 어시스턴트 시대에 달라진 실수 방식을 차례로 풀어봅니다.

왜 나쁜 코드는 고쳐지지 않고 쌓이는가

제가 이 코멘트에서 가장 흥미롭게 본 부분은 나쁜 코드의 목록이 아니라 방향에 대한 태도입니다. 코드는 어느 수준에서 멈추지 않습니다. 누가 붙잡지 않으면 계속 더 나빠지는 쪽으로 흐릅니다. 마감은 다가오고, 요구사항은 바뀌고, 급한 수정이 쌓이면서 구조는 조금씩 기울어집니다. 기울어진 구조 위에서는 다음 수정이 더 어렵고, 어려운 수정은 다시 구조를 더 기울게 만듭니다.

현장에서는 이런 흐름이 눈에 잘 띄지 않습니다. 하루하루는 멀쩡해 보입니다. 기능은 나가고, 데모는 돌아가고, 고객의 불만도 바로 터지지 않습니다. 문제는 몇 달 뒤에 옵니다. 사소한 문구 하나를 바꾸려는데 관련된 분기가 다섯 군데에서 튀어나옵니다. 로그를 뒤져보면 예외가 조용히 사라진 자리가 있습니다. 담당자는 이미 팀을 옮겼고, 남은 사람은 코드를 읽기보다 추측하면서 고칩니다.

저는 이 지점에서 "빨리 가기"라는 말을 다시 묻게 됩니다. 빨리 간다는 말은 보통 작성 속도를 가리킵니다. 키보드가 빠르게 움직이고, 파일이 빠르게 늘어납니다. 그런데 소프트웨어에서 진짜 속도는 읽기 속도에서 갈립니다. 처음 읽는 사람이 얼마나 빨리 이해하는지, 수정할 때 얼마나 좁은 범위를 건드리는지, 고친 뒤에 얼마나 확신을 가지는지가 속도를 정합니다. 쓰기 속도는 하루짜리 지표이고, 읽기 속도는 몇 년짜리 지표입니다.

저희가 새로운 AI 아키텍처를 볼 때도 benchmark 숫자만 보지는 않습니다. 실제로 무엇이 바뀌었고, 그 변화가 시스템에서 어떤 비용 구조를 만드는지를 함께 봅니다. 코드도 같습니다. 줄 수나 커밋 수가 아니라, 변경이 전파되는 범위와 확인에 드는 노력을 봅니다. 그 관점에서 보면 나쁜 코드는 단순히 지저분한 문체가 아닙니다. 변경 비용을 키우는 구조입니다.

유지보수 비용이 처음 작성 시간을 삼키는 구조

소프트웨어 생애주기에서 작성은 짧고 유지보수는 깁니다. 처음 만드는 데 몇 주가 걸린 기능이 몇 년 동안 매주 조금씩 손질됩니다. 요구사항이 바뀌고, 라이브러리가 바뀌고, 플랫폼이 바뀌고, 보안 권고가 떨어집니다. 그때마다 누군가 코드를 다시 읽습니다. 읽기 어려운 코드는 그때마다 세금을 매깁니다.

세금은 여러 형태로 나타납니다. 이해하는 데 시간이 더 들고, 수정 범위가 넓어지고, 사이드 이펙트를 확인하느라 테스트 범위가 넓어집니다. 확신이 없으니 배포가 늦어지고, 롤백 계획이 두꺼워집니다. 작은 변경인데도 회의가 길어집니다. "이거 건드려도 될까요"라는 질문이 반복됩니다. 질문이 반복된다는 사실 자체가 코드가 설명을 못 하고 있다는 증거입니다.

숫자로 정확히 재기는 어렵지만, 방향은 분명합니다. 읽기 어렵고 테스트 없는 코드는 초기 절약을 금방 갉아먹습니다. 처음에 이틀을 아꼈다가 나중에 매달 이틀씩 더 쓰는 구조가 됩니다. 이자는 복리로 붙습니다. 중복이 늘고, 분기가 깊어지고, 의존성이 얽히면 다음 변경의 단가가 올라갑니다. 단가가 올라가면 리팩터링은 더 미뤄지고, 미뤄진 리팩터링은 단가를 더 올립니다.

실무에서 이 고리를 끊는 방법은 거창하지 않습니다. 변경 범위를 작게 유지하고, 읽는 사람을 배려하고, 동작을 증명하는 장치를 남기는 것입니다. 함수 하나를 고쳤을 때 그 결과를 말로 설명하지 않아도 되게 만드는 것이 목표입니다. 테스트가 그 역할을 하고, 타입이 그 역할을 하고, 짧은 리뷰가 그 역할을 합니다. 작성 순간의 편의를 조금 양보하면 유지보수 구간에서 크게 돌려받습니다.

이미지 1: 왜 나쁜 코드는 고쳐지지 않고 쌓이는가
깔끔한 모듈과 얽힌 코드베이스를 나란히 보여주는 그림

복사가 복사를 부르는 코드에서 생기는 일

중복은 가장 흔한 출발점입니다. 급할 때 복사해서 붙이는 것은 합리적으로 보입니다. 기존 코드를 건드리지 않고 새 분기만 만들면 오늘의 장애를 피할 수 있습니다. 문제는 내일입니다. 복사된 코드는 따로 진화합니다. 한쪽에서 버그를 고치면 다른 쪽은 그대로 남습니다. 같은 로직이 세 군데에서 조금씩 다르게 동작하기 시작합니다.

중복이 무서운 이유는 버그 자체보다 확인 비용 때문입니다. "이 로직이 어디에 또 있지"라는 질문에 답하려면 검색에 의존해야 합니다. 검색은 사람을 피곤하게 하고, 피곤한 사람은 한 군데를 놓칩니다. 놓친 한 군데가 몇 달 뒤 장애 보고서로 돌아옵니다. 보고서를 받은 사람은 다시 전체를 검색합니다. 같은 작업이 반복됩니다.

중복을 줄이는 처방은 단순합니다. 달라지는 부분만 밖으로 빼내고, 같은 부분은 한곳에 둡니다. 함수로 묶고, 이름을 붙이고, 경계를 분명히 합니다. 말은 쉽지만 판단이 필요합니다. 너무 일찍 묶으면 엉뚱한 것까지 함께 묶이고, 너무 늦게 묶으면 이미 세 군데로 퍼져 있습니다. 저는 보통 세 번째 복사가 보일 때 묶는 편을 택합니다. 두 번까지는 우연일 수 있지만, 세 번이면 구조가 말을 하고 있다고 봅니다.

여기서 조심할 대목이 있습니다. 중복 제거가 만능은 아닙니다. 겉모습이 비슷해도 바뀌는 이유가 다르면 억지로 묶지 않는 편이 낫습니다. 함께 바뀌는 것끼리 묶어야 합니다. 따로 바뀌는 것을 묶으면 오히려 수정 범위가 넓어집니다. 중복을 볼 때는 모양이 아니라 변경 리듬을 봅니다. 같이 고쳐지는 코드는 같이 두고, 따로 고쳐지는 코드는 떨어뜨립니다.

크기를 감당하지 못하는 객체와 깊어지는 분기

god object라고 불리는 거대한 객체는 작은 중복보다 다루기 어렵습니다. 모든 것을 아는 객체는 모든 변경에 등장합니다. 주문도 알고, 결제도 알고, 알림도 알고, 정산도 압니다. 처음에는 편합니다. 어디서든 꺼내 쓰면 되니까요. 시간이 지나면 그 객체를 건드리지 않고는 아무것도 바꿀 수 없게 됩니다.

거대한 객체의 진짜 비용은 이해 범위에 있습니다. 한 메서드를 고치려고 파일을 열었는데, 알아야 할 맥락이 수십 개 필드와 수십 개 메서드로 퍼져 있습니다. 머릿속에 올려야 할 상태가 너무 많아서 확신을 갖기 어렵습니다. 확신이 없으니 조건을 하나 더 붙입니다. "혹시 모르니"라는 분기가 쌓입니다. 분기가 쌓이면 다음 사람은 더 이해하기 어려워집니다.

깊게 중첩된 조건문도 같은 뿌리를 가집니다. if 안에 if가 있고, 그 안에 다시 예외 처리가 있고, 그 안에 플래그 분기가 있습니다. 이런 코드를 읽을 때는 눈이 아니라 기억력이 일합니다. 세 번째 깊이에 들어가면 첫 번째 조건이 가물가물해집니다. 그래서 실수가 생깁니다. 잘못된 분기에 수정을 넣거나, 도달하지 않는 코드를 남기거나, 순서 의존성을 놓칩니다.

저는 이런 코드를 만나면 분기를 납작하게 펴는 것부터 시도합니다. 이른 반환으로 예외 상황을 먼저 털어내고, 조건을 함수로 뽑아 이름을 붙이고, 상태 조합을 줄이는 방향으로 데이터 모양을 바꿉니다. 분기가 깊다는 것은 코드가 한 번에 너무 많은 질문에 답하려 한다는 신호입니다. 질문을 나누면 깊이가 줄어듭니다. 깊이가 줄면 테스트를 붙이기도 쉬워집니다.

이미지 2: 유지보수 비용이 처음 작성 시간을 삼키는 구조
중복된 코드와 하나로 모은 함수를 비교하는 도식

조용히 사라지는 예외와 박혀버린 비밀값

삼켜버린 예외는 조용한 부채입니다. catch 블록이 비어 있거나 로그 한 줄만 남기고 넘어가는 코드는 당장 터지지 않습니다. 그래서 오래 남습니다. 나중에 장애가 나면 증상과 원인이 멀리 떨어져 있습니다. 결제가 실패했는데 로그에는 아무것도 없고, 사용자는 "그냥 안 됐어요"라고 말합니다. 디버깅은 그때부터 보물찾기가 됩니다.

예외 처리의 원칙은 단순합니다. 복구할 수 있으면 복구하고, 모르면 위로 알리고, 기록은 맥락과 함께 남깁니다. 빈 catch는 "모르겠으니 묻어두자"는 선언과 같습니다. 일시적으로 필요할 때가 없는 것은 아닙니다. 외부 시스템이 요란하게 실패하는데 흐름을 멈출 수 없을 때가 있습니다. 그렇다면 범위를 좁히고, 이유를 적고, 다시 볼 날짜를 정해야 합니다. 예외를 삼킬수록 나중에 갚을 이자가 커집니다.

하드코딩된 비밀값은 다른 종류의 위험입니다. 코드에 박힌 API 키나 비밀번호는 편리하지만, 저장소 기록에 남는 순간 통제권을 잃습니다. 접근 범위를 좁히기 어렵고, 교체 주기를 지키기 어렵고, 누가 보았는지 알기 어렵습니다. 키를 바꾸려면 코드를 다시 배포해야 하고, 배포 과정에서 또 노출될 여지가 생깁니다. 비밀값은 코드가 아니라 환경과 권한 시스템이 들고 있어야 합니다.

의존성 관리도 같은 선상에 있습니다. 직접 쓴 코드만 코드가 아닙니다. 가져다 쓴 라이브러리, 전이 의존성, 빌드 플러그인까지 모두 실행되는 코드입니다. 오래된 의존성을 방치하면 알려진 취약점이 그대로 남습니다. 업데이트가 두려워서 미루면 두려움은 더 커집니다. 작은 버전부터 자주 올리고, 변경 로그를 읽고, 테스트로 받치는 리듬이 필요합니다. 의존성 목록을 줄이는 것도 실력입니다. 쓰지 않는 의존성을 덜어내면 공격 표면도 줄고, 이해 범위도 줄어듭니다.

테스트가 없을 때 속도는 왜 착각이 되는가

테스트 없는 팀의 속도는 앞부분만 빠릅니다. 처음 몇 주 동안은 테스트를 짜지 않으니 손이 빠릅니다. 기능이 계속 나갑니다. 그러나 어느 순간부터 속도가 꺾입니다. 수정 하나에 확인 작업이 따라붙기 때문입니다. 손으로 클릭하고, 눈으로 확인하고, 동료에게 "한번 봐주세요"라고 부탁합니다. 사람이 매번 확인하는 속도는 늘 일정한데, 확인할 거리는 계속 늘어납니다.

자동화된 테스트는 확인을 기계에 맡기는 장치입니다. 기계는 지치지 않고, 같은 걸 매번 같은 방식으로 확인합니다. 리팩터링할 때 용기가 생기는 이유가 여기 있습니다. 구조를 바꿔도 테스트가 깨지지 않으면 동작이 유지된다는 신호를 받습니다. 신호가 있으니 과감해지고, 과감해지니 구조가 좋아지고, 구조가 좋아지니 다음 변경이 빨라집니다. 선순환의 출발점은 작은 테스트 한 묶음입니다.

여기서는 조금 조심해서 읽을 필요가 있습니다. 테스트가 많다고 좋은 것은 아닙니다. 깨지기 쉬운 테스트, 구현 상세를 그대로 묶어둔 테스트, 실행에 몇십 분씩 걸리는 테스트는 오히려 발목을 잡습니다. 저는 겉모습이 아니라 동작을 고정하는 테스트를 선호합니다. 입력과 출력, 상태 변화, 외부와의 약속을 적습니다. 내부 구현이 바뀌어도 깨지지 않는 테스트가 오래 남습니다.

특성화 테스트라는 발상도 유용합니다. 낡은 코드를 고치기 전에 현재 동작을 그대로 기록하는 테스트를 먼저 둡니다. 옳고 그름을 따지기 전에 "지금 이렇게 움직인다"를 고정합니다. 그 다음에 구조를 바꿉니다. 낡은 코드 앞에서 막막할 때, 이 한 걸음이 심리적 안전망이 됩니다. 전체를 이해하지 못해도 변경을 시작할 수 있습니다. 이해는 바꾸면서 따라옵니다.

이미지 3: 복사가 복사를 부르는 코드에서 생기는 일
비대해진 객체와 깊어진 분기를 보여주는 개념도

코딩 어시스턴트가 만들어내는 새로운 실수 방식

코딩 어시스턴트는 코드 양을 늘리는 데 뛰어납니다. 설명을 적으면 뼈대를 금방 만들어주고, 반복 작업을 대신해줍니다. 겉으로 보면 생산성이 뛴 것 같습니다. 그런데 늘어난 코드만큼 확인할 거리도 늘어납니다. 읽지 않은 코드가 저장소에 들어오는 속도가 빨라지면, 나쁜 코드가 쌓이는 속도도 빨라집니다.

새로운 실수 방식은 그럴듯하다는 데 있습니다. 존재하지 않는 API를 자연스럽게 호출하고, 기본값으로 안전하지 않은 설정을 고르고, 논리적으로 매끄러워 보이지만 틀린 구현을 내놓습니다. 컴파일이 되고, 데모가 돌아가니 넘어가기 쉽습니다. 리뷰어도 속도에 밀리면 "돌아가니 괜찮겠지"라고 생각합니다. 그 순간 검증되지 않은 추측이 코드로 굳어집니다.

저는 어시스턴트가 만든 코드를 볼 때 출처가 아니라 동작을 묻습니다. 이 함수가 어떤 입력을 받고, 어떤 보장을 하고, 실패하면 어떻게 알리는지를 봅니다. 모호하면 테스트를 먼저 붙입니다. 의존하는 API가 실제로 존재하는지 문서가 아니라 실행으로 확인합니다. 권한, 입력 검증, 에러 경로 같은 보안 관련 부분은 더 깐깐하게 봅니다. 편의를 위해 경계를 느슨하게 만든 코드는 나중에 공격 경로가 됩니다.

좋은 활용법도 분명합니다. 보일러플레이트를 걷어내고, 테스트 뼈대를 만들고, 이름과 구조에 대한 초안을 받는 데는 뛰어납니다. 차이는 사람이 어디에 시간을 쓰느냐에서 갈립니다. 작성 시간을 아낀 만큼 읽기 시간과 검증 시간을 늘려야 합니다. 어시스턴트는 타자를 대신해주지만 판단을 대신해주지 않습니다. 판단의 자리에 사람을 남겨두는 팀이 오래 갑니다.

나빠질 수 있는 범위를 미리 묶어두는 장치들

나쁜 코드에 한계가 없다는 말은 뒤집으면 이렇게 읽힙니다. 한계를 만드는 것은 사람의 의지와 도구의 조합입니다. 의지만으로는 버티기 어렵습니다. 피곤하면 타협하고, 급하면 건너뜁니다. 그래서 범위를 미리 묶어두는 장치가 필요합니다. 정적 분석, 타입 시스템, linter, formatter, 리뷰, CI 품질 게이트가 그 장치입니다.

정적 분석과 타입은 실행 전에 실수를 잡습니다. null이 될 수 있는 값이 어디까지 흐르는지, 닫히지 않은 자원이 있는지, 위험한 패턴이 숨어 있는지를 코드가 돌아가기 전에 보여줍니다. linter와 formatter는 논쟁을 줄입니다. 들여쓰기나 import 순서 같은 사소한 선택을 기계에 맡기면 리뷰는 로직에 집중할 수 있습니다. 사소한 것을 기계가 맡아야 사람이 중요한 것을 봅니다.

리뷰는 속도를 늦추는 장치가 아니라 속도를 지키는 장치입니다. 작은 변경은 빨리 읽히고, 빨리 읽히면 빨리 합쳐집니다. 큰 변경은 읽기 어렵고, 읽기 어려우면 오래 묵습니다. 오래 묵은 변경은 충돌이 나고, 충돌을 풀다 실수가 납니다. 그래서 변경을 작게 나누는 규칙이 리뷰보다 먼저입니다. 작게 나누면 리뷰가 쉬워지고, 리뷰가 쉬워지면 품질 게이트가 버겁지 않습니다.

품질 게이트는 통과 기준을 문서가 아니라 실행으로 만듭니다. 테스트 커버리지의 절대 숫자에 집착하기보다, 새로 들어오는 코드에 대한 기준을 분명히 하는 편이 낫습니다. 새 코드는 테스트를 동반하고, 린트를 통과하고, 타입 검사를 통과합니다. 낡은 코드는 점진적으로 끌어올립니다. 기준이 자동이면 예외를 논의하는 시간이 줄고, 예외가 줄면 기준이 지켜집니다.

이미지 4: 크기를 감당하지 못하는 객체와 깊어지는 분기
CI 파이프라인 속 자동 검사 단계를 보여주는 도식

전부 엎지 않고 조금씩 갈아엎는 방법

낡은 코드를 마주하면 크게 한 번에 엎고 싶은 유혹이 생깁니다. 처음부터 다시 쓰면 깨끗해질 것 같습니다. 경험적으로 그 길은 험합니다. 다시 쓰는 동안 기존 시스템은 계속 바뀌고, 요구사항도 계속 바뀝니다. 새 시스템이 완성될 때쯤이면 세상이 달라져 있습니다. 마이그레이션은 길어지고, 팀은 지치고, 두 개의 진실을 떠안게 됩니다.

그래서 strangler fig 방식이 자주 언급됩니다. 낡은 코드의 목을 조르듯이 바깥부터 조금씩 감아가는 방법입니다. 새 요청은 새 모듈로 보내고, 낡은 부분은 건드리지 않거나 최소한만 손댑니다. 경계에 테스트를 두고, 의존성을 하나씩 끊어내고, 확신이 생긴 부분부터 갈아끼웁니다. 크게 이기는 대신 자주 이깁니다.

의존성 정리는 이 과정에서 터를 다지는 일입니다. 쓰지 않는 라이브러리를 덜어내고, 중복된 유틸을 모으고, 버전 정책을 단순하게 유지합니다. 의존성이 줄면 빌드가 빨라지고, 보안 검토 범위가 좁아지고, 새 사람이 읽어야 할 분량이 줄어듭니다. 화려한 작업은 아니지만, 유지보수 단가를 낮추는 데 확실합니다.

작고 리뷰 가능한 변경이라는 원칙도 같은 방향을 가리킵니다. 하루에 다섯 개의 작은 변경이 한 개의 거대한 변경보다 낫습니다. 작은 변경은 설명이 짧고, 롤백이 쉽고, 원인을 찾기 쉽습니다. 큰 변경은 그 반대입니다. 저희 팀 관점에서 보면 리뷰 속도는 친절의 문제가 아니라 설계의 문제입니다. 나누기 쉬운 구조가 빠른 팀을 만듭니다. 나누기 어려운 구조에서는 사람이 아무리 애써도 느려집니다.

이미지 5: 조용히 사라지는 예외와 박혀버린 비밀값
낡은 코드를 조금씩 감아 바꾸는 과정을 보여주는 그림

조직의 습관이 코드의 모양을 정한다

같은 언어로 써도 팀마다 코드 모양이 다릅니다. 차이는 재능보다 습관과 인센티브에서 옵니다. 출시 압박만 있고 리팩터링 시간이 없으면 코드는 당연히 썩습니다. 소유자가 없으면 아무도 경계를 지키지 않습니다. "돌아가면 된다"는 말이 칭찬이 되는 조직에서는 테스트가 사치로 취급됩니다.

소유권이라는 말을 오해하기 쉽습니다. 한 사람이 독점한다는 뜻이 아닙니다. 누가 품질에 책임지는지가 분명하다는 뜻입니다. 모듈마다 돌보는 사람이 있고, 낡은 부분을 끌어올릴 계획이 있고, 새 코드의 기준을 어길 때 멈춰 세울 권한이 있어야 합니다. 소유권이 분명하면 중복이 보일 때 바로 손댑니다. 소유권이 흐릿하면 모두가 보고도 지나칩니다.

일정도 마찬가지입니다. 리팩터링을 일정 밖에 두면 영원히 못 합니다. 일정 안에 두어야 합니다. 기능 포인트 옆에 부채 상환 항목을 나란히 놓고, 매주 조금씩 갚습니다. 급한 불만 끄고, 구조 작업은 다음 스프린트로 미루는 패턴이 반복되면 구조는 계속 기울어집니다. 리더가 "이번 주는 읽기 쉽게 만드는 주"라고 말할 수 있어야 합니다. 그 한마디가 몇 달 뒤 속도를 바꿉니다.

측정 방식도 코드를 바꿉니다. 줄 수와 커밋 수로 사람을 평가하면 줄 수와 커밋 수가 늘어납니다. 변경 실패율, 복구 시간, 리뷰 turnaround, 테스트 실행 시간 같은 지표를 보면 행동이 달라집니다. 무엇을 재느냐가 무엇을 만든다는 말은 코드 조직에서 특히 들어맞습니다. 좋은 지표는 사람을 닦달하지 않고 구조를 고치게 만듭니다.

다음에 코드를 볼 때 제가 먼저 확인할 것

제가 앞으로 보고 싶은 것은 거창한 선언이 아니라 작은 확인 목록이 현장에서 도는지입니다. 다음에 낯선 저장소를 열면 저는 이런 순서로 봅니다. 먼저 테스트를 실행합니다. 얼마나 빨리 돌고, 얼마나 자주 깨지는지를 봅니다. 그 다음에 가장 자주 바뀌는 파일의 크기와 의존 방향을 봅니다. 그 다음에 예외가 어디로 흐르는지, 비밀값이 어디에 있는지를 봅니다. 이 네 가지만 봐도 저장소의 건강 상태가 대략 잡힙니다.

코딩 어시스턴트가 일상화된 팀에서는 확인할 목록에 두 줄을 더합니다. 외부 API 호출은 실행으로 확인했는지, 보안 관련 기본값은 안전한 쪽으로 잠가두었는지입니다. 그럴듯한 코드를 의심하는 것은 사람을 의심하는 일이 아닙니다. 도구의 성질을 이해하고 쓰는 일입니다. 도구는 빠르게 제안하고, 사람은 느리게 검증합니다. 그 역할 분담이 분명할 때 속도와 안전이 함께 갑니다.

이 코멘트가 남기는 문장은 짧지만 여운이 깁니다. 나쁜 코드에 바닥이 없다는 말은 절망이 아니라 초대로 읽힙니다. 바닥이 없으니 천장도 우리가 정해야 합니다. 어디까지는 허용하고, 어디부터는 막을 것인지를 정하는 일이 엔지니어링입니다. linter 규칙 하나, 타입 하나, 작은 리뷰 하나가 천장을 만드는 벽돌입니다.

닫으면서 한 가지만 남기겠습니다. 오늘 작성하는 코드가 내일의 읽기 목록에 오른다는 사실을 기억하는 것입니다. 내일의 독자는 동료일 수도 있고, 몇 달 뒤의 자신일 수도 있습니다. 그 독자에게 "왜 이렇게 짰지"라는 질문을 덜어주는 코드가 좋은 코드입니다. 한계 없는 나빠짐 앞에서 우리가 할 수 있는 일은 한계 있는 좋아짐을 반복하는 것입니다. 작게 나누고, 자주 확인하고, 조금씩 갚는 일입니다. 그 반복이 쌓이면 코드베이스는 더 이상 무서운 곳이 아닙니다.

이미지 6: 테스트가 없을 때 속도는 왜 착각이 되는가
짧은 체크리스트와 함께 차분히 리뷰하는 장면

참고 자료

  1. Comment: There's No Limit to How Bad Code Can Get