중복처럼 보이는 코드를 함부로 합치면 안 되는 이유
오래 운영한 코드베이스를 점검하다 보면 비슷하게 생긴 코드 뭉치를 발견하고 "이거 중복이네, 합치자"라는 충동이 듭니다. 중복 제거는 리팩토링의 기본이라고 배우기도 했고, 지우는 작업은 손도 빠릅니다. 저는 얼마 전 그 충동대로 갔다가 큰일 날 뻔했습니다.
운영 중인 서비스의 API 폴더를 점검하는데 /api/admin/docs와 /api/admin/documents라는 두 트리가 있었습니다. 하위 구조까지 거의 판박이였습니다. 둘 다 [id]가 있고, snapshots가 있고, restore가 있습니다. 파일을 열어 보면 핸들러 구성도 비슷합니다. 이 정도면 한쪽이 레거시라고 거의 확신했습니다. 예전에 경로를 정리하면서 옛날 트리를 지우다 만 흔적이겠거니 했습니다.
지우기 전에 한 가지만 확인하기로 했습니다. 프론트엔드에서 실제로 누가 어느 경로를 부르는가. 문서 관리 화면 코드를 열어 보니 스페이스별로 endpoint를 매핑하는 부분이 있었고, 거기서 확신이 무너졌습니다. 전역 문서 공간은 /api/admin/docs를 부르고, 프로젝트별 문서 공간은 /api/admin/documents를 부르고 있었습니다. 둘 다 현역이었습니다.
더 파 보니 겉모양만 닮은 남남이었습니다. 한쪽은 정수 autoincrement id를 쓰는 전역 문서 모델이고, 다른 쪽은 cuid 문자열 id를 쓰는 프로젝트 소속 문서 모델입니다. 필드 구성이 다르고, 한쪽만 zod로 입력을 검증하고 있었습니다. 구조가 닮은 건 복붙의 흔적이 아니라 같은 패턴으로 두 도메인을 각각 만든 결과였습니다. CRUD에 스냅샷과 휴지통 복구가 붙는 문서 관리라면 어느 도메인이든 이 모양이 나옵니다.
만약 호출자 확인을 건너뛰고 한쪽 트리를 지웠다면 프로젝트 문서 기능이 통째로 죽었을 겁니다. 무리해서 하나로 합치는 쪽을 택했어도 id 타입부터 충돌합니다. 정수와 문자열 id를 한 라우트에서 받으려면 분기가 생기고, 그 분기는 두 트리를 따로 두는 것보다 읽기 어려웠을 겁니다. 결국 그대로 두는 것이 정답인 중복이었습니다. 정확히는 중복이 아니었던 거지만.
이날 이후로 저는 중복 판단 기준을 코드 모양에서 두 가지 질문으로 바꿨습니다. 실제 호출자가 같은가, 그리고 다루는 데이터 모델이 같은가. 둘 다 예일 때만 중복입니다. 모양이 닮았다는 것은 판단 근거가 못 됩니다. 잘 만든 코드일수록 같은 패턴을 반복하기 때문에, 오히려 닮은 코드가 많이 나옵니다. 중복 제거가 목표가 되는 순간 이 구분이 흐려지니, 지우는 손보다 확인하는 눈이 먼저 가야 한다고 이제는 생각합니다.
같은 점검에서 겪은 다른 이야기:
함께 읽기
- 반복 코드 131줄을 정규식으로 한 번에 바꿔도 괜찮았던 이유혼자 운영하는 서비스는 점검을 자꾸 미루게 됩니다. 기능이 돌아가고 있으면 코드를 다시 들여다볼 이유가 생기지 않고, 그 사이에 같은 패턴이 계속 복사됩니다. 저도 얼마 전 하루를 잡고 운영 중인 서비스 코드를 전체 점검했는데, 관리자 API 라우트 파일 70개에 완전히 똑같은 인증 가드 한 줄이 131번 반복되고 있었…
- 스모크 테스트가 다시 중요해진 이유: 타입체크를 통과하는 결함들며칠 전 공개 문서 세 편을 발행했습니다. 타입 체크가 통과했고, 테스트도 통과했고, 배포도 성공했습니다. 그리고 화면에서는 문장 하나가 가로줄로 그어진 채 나갔습니다.
- AI 코딩의 함정: 실패하지 않는 실수랜딩 페이지의 앱 소개 화면에 QR 코드를 붙이는 작업을 클로드에게 맡겼습니다. 결과물은 잘 돌아갔습니다. 타입 체크를 통과했고 프리뷰 배포도 깨끗했습니다.
- git push 후 자동 검사가 시작되는 이유: Continuous Integration(CI) 입문코드를 수정하고 로컬에서 잘 동작하는 것까지 확인했습니다. 이제 아래처럼 GitHub에 올립니다.
- Next.js 앱에서 클릭이 한 박자 느리게 느껴질 때 살펴본 것들서버가 멀면 응답이 느린 건 어쩔 수 없습니다. 제 경우 오리진 응답 지연에 CDN 우회 경로까지 겹쳐 페이지 응답에 1초 가까이 걸리는 상황이었고, 네트워크 쪽은 당장 손댈 수 없었습니다. 그래서 응답 속도 대신 "눌렀을 때 반응하는 속도"를 올리는 쪽으로 방향을 잡았습니다. 느린 것과 느리게 느껴지는 것은 생각보다 …