일기 slecs

하위 정산 관리 JSP 삭제로 코드베이스 기술 부채 해소

목차

기능 개발 일정에 치이다 보면 "나중에 지워야지" 하고 남겨둔 파일들이 어느새 코드베이스 곳곳에 박혀 있다. 하위 정산 관리 JSP도 그중 하나였다. 언젠가 기획이 바뀌면서 더 이상 호출되지 않는 화면이 됐는데, 당장 빌드가 터지는 것도 아니니 우선순위가 계속 밀렸다. 오늘은 그 파일들을 포함해 관련 코드를 한 번에 정리했다.

데드코드를 왜 지금 건드렸나

총괄 팀장 포지션에서 일하다 보면 기술 부채 정리는 늘 '바쁜 스프린트가 끝나면'으로 미뤄진다. 문제는 그 '끝나면'이 오지 않는다는 거다. 그래서 이번 주는 의도적으로 정리 시간을 잡았다.

JSP 파일 하나가 남아 있어도 팀에 생기는 비용은 생각보다 크다. 신규 입사자가 코드를 파악하려고 이 파일을 열면, 이게 현재 운영 중인 화면인지 아닌지 알 수가 없다. 호출 경로를 추적하고, 컨트롤러를 찾고, 서비스 레이어까지 훑어보고 나서야 "아, 안 쓰는 거구나"를 알게 된다. 그 탐색 비용이 사람마다, 코드 파악 때마다 반복된다.

데드코드가 쌓이면 또 다른 문제가 생긴다. 살아 있는 코드와 죽은 코드의 경계가 흐려지면서, 리팩토링할 때 '이거 건드려도 되나?'를 판단하는 데 시간이 더 들게 된다. 특히 정산처럼 돈이 오가는 도메인은 변경 영향 범위를 잘못 파악하면 실제 장애로 이어질 수 있기 때문에 더 조심스러워진다.

정리 절차와 기준

무작정 삭제하지 않고 다음 순서를 밟았다.

  • 해당 JSP로 forward/redirect하는 컨트롤러 코드 전수 검색 - 없으면 1차 통과
  • 메뉴 테이블, 권한 테이블 등 DB에서 해당 URL/경로를 참조하는 레코드 확인
  • 빌드 후 서버 기동, 관련 도메인 기능 전체 스모크 테스트
  • 제거 대상 파일과 연결된 공통 유틸/CSS/JS가 다른 화면에서도 사용되는지 확인

세 번째 항목이 생각보다 중요하다. JSP를 삭제한다고 해서 바로 문제가 생기진 않지만, JSP에서만 참조하던 공통 모듈을 함께 지우면 다른 화면에서 오류가 날 수 있다. 이번에도 CSS 파일 하나를 삭제 목록에 넣었다가, 다른 화면에서 같은 파일을 import하고 있는 걸 확인하고 제외했다.

정리 커밋은 기능 변경 커밋과 반드시 분리했다. 같은 PR에 feat:chore:가 섞이면 리뷰어 입장에서 무엇이 실질적인 로직 변경이고 무엇이 단순 삭제인지 구별하는 데 집중력이 분산된다. 롤백 단위도 불명확해지고. 조금 번거로워도 커밋을 분리해두면 나중에 git log --grep="chore" 로 정리 이력만 모아 볼 수도 있어서 편하다.

# 정리 커밋은 따로
git commit -m "chore: 하위 정산 관리 JSP 및 연관 컨트롤러 제거"

# 기능 커밋과 섞지 않음
git commit -m "feat: 정산 집계 API 신규 추가"

DB 쿼리 점검 포인트

이번 정리 작업에서 관련 DB 쿼리도 함께 들여다봤다. 세 가지를 체크했다.

인덱스 설계. WHERE 조건에 자주 들어오는 컬럼에 인덱스가 없으면 풀 테이블 스캔이 발생한다. 정산 도메인은 statuscreated_at으로 필터링하는 패턴이 많아서 복합 인덱스를 확인했다.

-- 상태 필터 후 최신순 정렬이 잦은 경우
CREATE INDEX idx_status_created ON settlement (status, created_at DESC);

-- 실행 계획으로 인덱스 활용 여부 직접 확인
EXPLAIN SELECT * FROM settlement
WHERE status = 'PENDING'
ORDER BY created_at DESC
LIMIT 20;

인덱스 컬럼 순서가 중요하다. (status, created_at)(created_at, status)는 쿼리 패턴에 따라 활용 여부가 달라진다. status 카디널리티가 낮으면 앞에 두는 게 일반적으로 유리하지만, 이론보다는 실행 계획을 직접 확인하는 게 맞다.

소프트 삭제 유지. 정산 이력처럼 감사 추적이 필요한 데이터는 DELETE를 쓰지 않고 deleted_at 컬럼으로 논리 삭제 처리한다. 이 패턴을 그대로 유지했다. 다만 소프트 삭제를 쓸 때 주의할 점이 있는데, 조회 쿼리 전체에 WHERE deleted_at IS NULL 조건이 빠짐없이 들어가야 한다. 이 조건이 하나라도 빠지면 삭제된 것처럼 보이는 데이터가 화면에 노출된다. 공통 레포지터리 메서드에서 기본 조건으로 처리하거나 어노테이션 필터를 활용하는 방식으로 실수를 줄이는 게 좋다.

페이징 방식. 현재 데이터 규모에서는 LIMIT/OFFSET 기반 페이징이 문제없었다. 다만 OFFSET이 커질수록 DB는 건너뛴 행들도 다 읽어야 하기 때문에, 데이터가 수십만 건을 넘어가면 커서 기반 페이징으로 전환을 고려해야 한다.

페이징 방식 장점 단점
LIMIT/OFFSET 구현 단순, 특정 페이지 바로 이동 가능 OFFSET 클수록 성능 저하
커서 기반 대용량에서 일관된 성능 중간 페이지 이동 불가, 구현 복잡

커서 기반은 보통 WHERE id < :lastId ORDER BY id DESC LIMIT 20 패턴으로 쓴다. 무한 스크롤 UI에는 잘 맞는데, 관리자 화면처럼 "3페이지로 바로 이동"이 필요한 경우엔 제약이 있다. 현재는 LIMIT/OFFSET으로 충분하지만, 데이터가 쌓이면 다시 봐야 할 지점이다.

오늘 작업으로 코드베이스가 조금 가벼워졌다. 정리 작업은 배포해도 눈에 띄는 게 없어서 티가 안 나지만, 다음에 이 코드를 파악해야 할 사람이 헤매는 시간이 줄어든다는 점에서 기능 개발 못지않게 가치 있는 일이다.

댓글 0

첫 댓글 달아줘.