Still Making

Knip으로 미사용 코드를 찾으며 알게 된 것들

makim

목차

미사용 코드를 정리하기로 한 이유

새로운 팀에 합류하고 프로젝트 저장소를 살펴보면서 눈에 띈 것이 있었다. 더 이상 사용하지 않는 코드가 여러 곳에 남아 있었다는 점이다. 이전 구현을 참고하거나 이력을 보존하려고 따로 모아둔 코드도 있었고, 급하게 개발한 뒤 정리하지 못한 코드나 스펙에서 빠진 기능이 코드에는 그대로 남아 있는 경우도 있었다.

각각 남겨둔 이유는 있었겠지만, 새로 합류한 입장에서는 지금 사용하는 코드가 어디까지인지 파악하기가 쉽지 않았다. 이전 구현은 Git 이력으로 확인할 수 있으니, 사용하지 않는 코드는 정리해두면 좋겠다는 생각이 들었다.

그런데 막상 정리하려니 어디서부터 시작해야 할지 고민이었다. 오래돼 보이는 파일이라고 바로 지울 수는 없고, 파일마다 참조를 하나씩 찾아보자니 시간이 꽤 들 것 같았다. 이 과정을 조금 더 효율적으로 진행할 방법을 찾다가 Knip을 알게 됐다. Knip은 JavaScript와 TypeScript 프로젝트에서 사용하지 않는 파일, 의존성, export를 찾아주는 정적 분석 도구다. 파일 사이의 참조와 프로젝트 설정을 분석해 미사용 후보를 알려준다. 이 도구로 후보를 먼저 모아두면 하나씩 확인해야 할 범위를 줄일 수 있을 것 같아 분석해보기로 했다.

Claude로 Knip 실행 결과 정리하기

Knip으로 분석한 뒤에는 실행 결과를 Claude에 전달하고 필요시 실제 코드베이스를 참고해서 보고서로 정리해달라고 요청했다. 파일 목록을 내가 검토하기 편하면서 공유하기 좋은 형태로 만들고 싶었다.

이렇게 만든 보고서에는 실험용 코드, 이전 컴포넌트, 테마, 훅, GraphQL 관련 파일 등이 분류되어 있었다. '삭제 강력 추천', '삭제 전 확인 필요'와 같은 구분과 주의사항도 들어 있었다. 어떤 항목부터 살펴볼지 정하는 데 도움이 됐다.

보고서를 보며 실제 코드 다시 확인하기

첫 보고서에는 미사용으로 추정되는 파일이 161개라고 적혀 있었다. 실험용 랜딩부터 이전 버전의 컴포넌트, 테마 파일, 훅, GraphQL 관련 파일까지 생각보다 종류가 다양했다.

목록이 생기니 어디서 시작할지는 보였다. 이후에는 보고서에 나온 항목을 보면서 실제 코드를 살펴보고, 사용하지 않는 코드가 맞는지 다시 검증했다. 보고서에 삭제를 추천하는 문구가 있더라도 그 판단을 그대로 적용하지는 않았다.

다음 분석에서는 단위 테스트와 mock 파일, 빌드 도구까지 미사용 후보에 들어 있었다. 앱 코드에서 직접 import하지 않아도 필요한 파일들이었다. Knip이 후보로 찾았다는 사실과 AI가 보고서에서 붙인 분류, 실제로 삭제해도 되는지는 각각 구분해서 봐야 했다.

여기서 먼저 알아야 할 것은 Knip이 무엇을 기준으로 사용 여부를 판단하는지였다. 실제로 사용하는 파일이라도 분석 과정에서 그 연결을 찾지 못했다면 후보에 나올 수 있기 때문이다.

Knip은 어디서부터 코드를 따라갈까

Knip의 진입 파일 설명을 보면, 분석은 진입 파일(entry file) 에서 시작한다. 그 파일과 연결된 코드를 따라가며 사용 관계를 파악하는 방식이다. 프레임워크와 도구의 플러그인, 패키지 설정, 실행 스크립트 등도 진입점을 찾는 데 쓰인다.

일반적인 컴포넌트라면 다른 파일의 import를 따라가면 된다. 하지만 프로젝트의 모든 파일이 이런 방식으로 연결되지는 않는다. 테스트 파일은 테스트 러너가 찾고, 페이지는 프레임워크의 규칙에 따라 연결된다. 빌드 설정에서만 사용하는 패키지도 있다.

결국 미사용 목록을 볼 때는 분석에 필요한 진입점이 제대로 잡혀 있는지도 함께 확인해야 한다. 결과에 나온 파일을 전부 진입점으로 추가하면 목록에서는 사라질 수 있겠지만, 그것만으로 실제 사용 여부가 확인되는 것은 아니다. 어떤 실행 경로에서 필요한 파일인지 설명할 수 있어야 한다.

테스트 파일과 빌드 도구가 나온 이유부터 확인하기

두 번째 보고서에는 단위 테스트와 mock 파일 외에 @svgr/webpack도 포함되어 있었다. 이런 항목은 결과만 보고 지우기 어렵다. 테스트 러너의 탐색 대상일 수 있고, webpack 설정에서 SVG를 처리할 때 사용하는 도구일 수도 있기 때문이다.

이 경우에는 소스 코드의 import뿐 아니라 테스트 설정, 실행 스크립트, 빌드 설정까지 살펴봐야 한다. Knip의 문제 해결 가이드에서도 플러그인 설정이나 인식하지 못한 참조 경로처럼 여러 원인을 나눠 설명하고 있다. 사용하는 파일이 후보에 나왔다면 분석 설정을 보완한 뒤 다시 실행해보는 편이 좋겠다.

개인적으로 이 부분이 중요하다고 느꼈다. 도구가 미사용이라고 표시했을 때, 실제로 필요 없는 것인지 도구가 사용 경로를 놓친 것인지 한 번 더 구분해야 한다. 분석 결과를 잘 활용하려면 프로젝트의 실행 방식도 어느 정도 알고 있어야 했다.

동적 import도 형태에 따라 다르다

동적 import가 있다는 이유만으로 해당 파일을 모두 예외 처리할 필요는 없다. import('./KnownComponent')처럼 경로가 명확한 코드와, 실행 중에 얻은 값으로 경로를 조합하는 코드는 다르게 봐야 한다.

현재 공식 문서의 진입 파일 탐색 경로에는 동적 import도 포함되어 있다. 다만 외부 라이브러리의 API를 통해 export를 사용하는 경우에는 참조를 끝까지 따라가지 못하는 상황이 있을 수 있다고 한다. 사용하는 Knip 버전과 실제 코드 패턴을 같이 확인할 필요가 있다.

확실히 사용 중인 파일이라면 어떤 경로로 호출되는지 먼저 좁혀보고 싶다. 분석 설정으로 해결할 수 있는지 확인한 뒤, 어려운 경우에만 예외를 두는 것이다. 예외를 남긴다면 왜 필요한지와 언제 다시 확인할지도 적어두면 좋겠다. 이유 없이 제외 목록만 늘어나면 나중에는 그 목록도 정리 대상이 될 것 같다.

파일의 종류에 따라 확인할 곳도 달랐다

보고서에 나온 후보를 정리해보면 대략 다음과 같이 나눌 수 있었다.

후보 종류확인할 내용
실험용 또는 이전 화면라우트와 링크, 실험 설정에 남아 있는지, 실제로 종료한 기능인지
테스트와 mock테스트 러너의 탐색 대상인지, 다른 테스트에서 사용하는지
테마와 스타일 파일디자인 시스템이나 스타일 설정에서 참조하는지
빌드 도구번들러와 로더 설정, CI에서 사용하는지
GraphQL 관련 파일쿼리 원본과 생성 코드가 어떻게 연결되는지

첫 보고서에는 실험용 폴더나 이전 컴포넌트를 강한 삭제 후보로 분류한 항목도 있었다. 조사 순서를 정하기에는 도움이 됐다. 하지만 experimental이나 abandoned라는 폴더 이름만으로 판단하기는 어렵다. 외부 링크로 들어오는 페이지이거나, 특정 기간에 다시 사용하는 기능일 수도 있으니 제품의 사용 여부까지 확인해야 한다.

관련 담당자에게도 확인 요청하기

코드를 직접 살펴보는 것과 함께, 관련 담당자들을 태그해서 확인을 부탁하기도 했다. 새로 합류한 입장에서는 코드만으로 기능의 사용 이력이나 운영 상황까지 모두 파악하기 어려웠기 때문이다.

저장소 안에서 참조를 찾는 일과 제품에서 더 이상 필요하지 않은지 확인하는 일은 확인할 정보가 다르다. 외부 링크로 접근하는 화면인지, 특정 기간에 사용하는 기능인지 같은 부분은 담당자의 맥락도 필요하다. 혼자 추측해서 결론을 내리기보다 해당 내용을 아는 사람에게 확인을 요청하는 과정이 중요하다고 느꼈다.

이때 보고서는 내가 검토할 목록이면서, 다른 사람과 확인할 대상을 공유하기 위한 자료이기도 했다. Knip 실행 결과를 AI로 정리하고 끝내는 것이 아니라, 직접 코드 검증과 담당자 확인 요청으로 이어가는 흐름으로 활용했다.

GraphQL은 생성 과정까지 함께 보기

GraphQL 관련 파일도 따로 볼 필요가 있었다. 첫 보고서에는 다른 파일과 함께 포함되어 있었는데, 두 번째 보고서를 만들 때는 GraphQL 관련 파일을 별도 항목으로 분리해서 정리해달라고 요청했다.

쿼리 원본을 바탕으로 타입이나 실행용 코드를 생성하는 구조라면 원본과 생성 파일을 함께 살펴봐야 한다. 한쪽의 참조가 보이지 않는다고 다른 쪽을 바로 지울 수는 없다. 삭제한 뒤 생성 명령을 실행했을 때 파일이 다시 만들어지는지도 확인할 부분이다. 원본이나 생성 설정이 남아 있다면 파일만 지워서는 정리가 끝나지 않을 수 있다.

이런 코드는 사용하지 않는 화면과 그 화면 전용 쿼리를 묶어서 검토하는 편이 좋겠다. 이후 생성기를 실행하고 산출물의 변경까지 확인하면, 왜 이 파일들을 함께 제거하는지 리뷰에서도 설명하기 쉬울 것 같다.

사용 경로를 기준으로 삭제 단위 나누기

정리할 때는 관련 없는 파일을 한꺼번에 삭제하기보다 사용 경로를 기준으로 작업을 나누는 편이 좋다. 폐기한 화면과 그 화면에서만 쓰던 파일, 더 이상 사용하지 않는 테마, 제거할 도구와 연결된 설정을 각각 묶는 식이다.

삭제 대상들이 공통으로 사용하던 코드도 함께 정리해야 한다. 다만 공통 코드를 먼저 지우면 아직 정리하지 않은 파일의 참조가 깨질 수 있으므로, 다른 사용처가 없는지 확인하고 마지막 사용처를 제거할 때 함께 삭제한다.

검증도 삭제한 대상에 맞춰야 한다. 컴포넌트를 지운 경우와 빌드 도구를 제거한 경우에 확인할 내용이 같지는 않다.

변경 대상확인할 검증
컴포넌트와 유틸리티타입 검사, 관련 테스트, 영향을 받는 화면 확인
테스트 설정과 mock테스트 탐색 목록과 관련 테스트 실행
로더와 빌드 의존성프로덕션 빌드와 해당 자산이 있는 화면 확인
GraphQL 원본과 생성 파일코드 생성 후 diff, 타입 검사, 관련 요청 흐름 확인
라우트와 실험 화면내부 이동과 직접 URL 진입 정책 확인

빌드가 성공해도 외부 링크로만 들어오던 페이지가 사라진 영향까지 알 수는 없다. 그렇다고 사용 경로가 분명히 끊긴 코드를 막연히 불안하다는 이유로 계속 남겨둘 수도 없었다. 그래서 어떤 내용을 확인했고, 왜 삭제해도 된다고 판단했는지를 작업 단위마다 남기는 것이 중요했다.

이런 기록은 나중에 인수인계할 때도 도움이 되겠지만, 무엇보다 내가 작업을 다시 이어갈 때 편했다. 미사용 코드 정리는 한 번에 끝내기보다 다른 업무 사이에 시간을 내서 조금씩 진행하는 경우가 많기 때문에 작업을 관련 코드끼리 묶고 확인한 내용을 남겨두면, 시간이 지난 뒤에도 어디까지 검토했는지 다시 파악하기 수월하다.

정리하며

미사용 코드 정리를 하면서 가장 좋았던 점은 내가 담당한 제품을 더 잘 이해하게 됐다는 것이다. 이 코드가 어디에서 쓰였는지, 지금은 왜 필요하지 않은지 살펴보고 담당자에게 물어보는 과정에서 기능의 흐름과 이전 작업의 맥락을 조금씩 알게 됐다. 새로 합류한 입장에서 제품과 코드에 익숙해지는 데도 도움이 되는 작업이었다.

나와 마찬가지로 새로 합류한 팀원도 미사용 파일 때문에 실제로 수정해야 할 코드를 찾는 데 혼란을 겪곤 했다. 정리 후에는 현재 사용하는 코드를 찾기가 쉬워졌고, 팀원들과 어느 부분을 수정할지 이야기하기도 한결 수월해졌다.

AI와 작업할 때 느낀 변화도 있었다. 체감상 AI가 코드를 잘못 해석하거나 엉뚱한 경로를 찾고, 잘못된 위치에 코드를 생성하는 일이 줄었다. 사용하지 않는 코드가 AI에게도 혼란을 줄 수 있겠다는 생각이 들었다.

팀원마다 서로 다른 AI 도구와 모델을 사용했는데, 같은 코드베이스에서도 필요한 코드를 찾아가는 모습은 달랐다. 요청한 작업에 필요한 코드를 잘 찾는 경우도 있었지만, 미사용 코드나 예전 경로를 따라가는 경우도 있었다. 이런 차이를 보면서 코드베이스를 정리할 필요성을 더 크게 느꼈다. 어떤 도구와 모델을 사용하든 작업을 시작할 때 헷갈릴 만한 코드를 줄여두는 것이 도움이 되겠다는 생각이 들었다.

이후 컨벤션을 잡을 때도 편했다. 지금 사용하는 코드가 어느 정도 정리되어 있으니, 현재 구조를 기준으로 앞으로 어떻게 맞춰갈지 고민하기가 수월했다. 미사용 코드 정리가 이후의 작업을 위한 준비가 된 셈이다. 무엇보다 쌓여 있던 코드를 정리하고 나니 속이 시원했다.

물론 한 번 정리했다고 끝나는 일은 아니었다. 이후에도 새로 발견되는 파일이 있었고, 스펙이 바뀌면서 사용하지 않게 된 코드도 생겼다. 정리된 코드베이스에서 작업하는 게 얼마나 편한지 느낀 만큼, 앞으로는 기능을 변경하거나 제거할 때 관련 코드까지 함께 정리하는 습관을 들여야겠다 싶다.