개발 slecs

백엔드 로직 중복 제거와 엣지 케이스 처리로 시스템 안정성 강화

목차

백엔드 파일 6개를 건드렸는데 커밋 로그만 보면 아무것도 안 한 것 같다. JSP 변경이 없고, XML 쿼리 손댄 것도 없고, CSS는 당연히 0. 근데 이게 맞다. 이번 작업의 목적 자체가 사용자한테 보이는 부분이 아니라 내부 로직의 품질이었으니까.

변경 영역을 정리하면 이렇다.

레이어 파일 수 주요 변경
백엔드 로직 6개 핵심 처리 로직 개선
화면 (JSP) 0개 -
쿼리 (XML) 0개 -
스타일 0개 -

중복 코드 제거, 엣지 케이스 보강, 로그/에러 메시지 개선, 불필요한 코드 정리. 이 네 가지가 전부다.

중복 코드를 왜 그냥 두면 안 되나

레거시 코드베이스에서 중복이 생기는 경로는 거의 정해져 있다. 처음엔 "비슷하지만 다른 케이스"라고 생각해서 복붙으로 시작하고, 나중에 로직이 바뀌면 한쪽만 수정하는 실수가 생긴다. 몇 달 지나면 두 함수가 미묘하게 다른 동작을 하는데 왜 다른지 아무도 모르는 상태가 된다. 이번에도 정확히 그런 케이스를 몇 개 발견했다. 처리 흐름이 같은데 파라미터 조합만 다른 로직이 여러 곳에 흩어져 있었다. 동작은 당장 문제없어 보이지만, 변경 요청이 왔을 때 한 곳만 고치고 나머지를 빠뜨리는 버그의 온상이다.

공통화 방향은 단순하게 잡았다. 핵심 처리를 한 곳으로 뽑고, 분기 조건은 파라미터로 주입하는 구조. 완벽한 추상화보다는 "이 로직이 바뀌면 한 곳만 고치면 된다"는 확신을 만드는 게 목표였다.

// Before: 같은 처리 흐름이 여러 메서드에 흩어진 상태
// 어느 시점부터 조금씩 달라졌는데 이유가 주석에 없음
private void processTypeA(Request req) {
    validate(req);
    // 핵심 처리 로직
}
private void processTypeB(Request req) {
    validate(req);
    // 미묘하게 다른 처리 - 왜 다른지 불명
}

// After: 파라미터로 통일, 분기 정책을 명시적으로 분리
private void process(Request req, ProcessType type) {
    validate(req);
    // 공통 처리
    applyTypePolicy(req, type);
}

이렇게 하면 validate 조건이 바뀌어도 한 곳만 손대면 된다. 실수할 여지 자체를 줄이는 것이 핵심이다.

엣지 케이스 - 정상 입력만 생각하면 안 되는 이유

로직 개선보다 시간이 더 걸린 건 사실 엣지 케이스 보강이었다. 정상 입력이 들어올 때야 다 잘 돌아간다. 문제는 null이 올 때, 빈 컬렉션이 올 때, 예상 범위를 벗어난 값이 들어올 때다.

"그런 케이스가 실제로 오겠어?"라는 생각이 가장 위험하다. 오지 않던 케이스가 오는 건 항상 배포 직후이거나 새벽이다.

이번에 보강한 케이스들:

  • null 체크 없이 바로 필드에 접근하는 코드 - NPE 잠재 위험
  • 컬렉션 사이즈 검증 없이 인덱스를 직접 접근하는 부분
  • 외부 입력값 범위 검증이 빠진 처리 구간
  • 실패했을 때 그냥 무시하고 넘어가던 빈 catch 블록

마지막이 제일 나쁜 패턴이다. 예외를 삼키고 아무 일 없다는 듯 계속 진행하면, 나중에 전혀 다른 곳에서 이상한 결과로 터진다. 로그조차 안 남기는 빈 catch는 이번에 전부 정리했다. 에러가 났을 때 명확하게 실패하고 명확한 메시지를 남기는 쪽이 훨씬 낫다. 장애 상황에서 원인 파악이 10분이냐 2시간이냐를 가르는 건 대부분 로그 품질이다.

운영 서비스 변경 시 챙겨야 할 것들

운영 중인 서비스에서 리팩터링할 때 제일 무서운 건 "기존 동작이 그대로인가"다. 눈에 보이는 기능은 안 바꿨는데 내부 로직을 건드렸으니까. 이번 작업에서 잡은 원칙 세 가지.

변경 단위를 작게. 파일 6개를 한 커밋에 몰아넣지 않았다. 관련 변경끼리 묶어 커밋을 나눴다. 문제가 생겼을 때 어디서 생겼는지 바로 좁혀지도록.

변경 전 동작을 먼저 파악. 리팩터링 시작 전에 현재 입출력을 손으로 확인해두는 것. 자동화된 테스트가 없는 레거시일수록 이게 더 중요하다. "이게 원래 이랬나?" 하고 헷갈리는 상황을 막는 가장 원초적인 방법이다.

코드가 바뀌면 주석도 같이. 코드는 바뀌었는데 옆에 붙은 주석이 예전 동작을 설명하고 있으면 그게 더 혼란스럽다. 이번에 주석 불일치가 몇 개 있었고, 같이 정리했다.

원칙 실제로 언제 빛을 발하나
단일 책임 스펙이 바뀌었을 때 수정 범위가 명확해짐
명시적 코드 6개월 후 내가 다시 봤을 때 파악 속도
실패 우선 처리 새벽 장애 대응에서 원인 찾는 시간
작은 커밋 롤백이 필요할 때 되돌릴 지점이 명확함

티 안 나는 작업이다. 기능 추가가 아니고, 화면이 바뀐 것도 아니다. 그래서 스프린트 우선순위에서 늘 뒤로 밀리고 기술 부채라는 이름으로 쌓인다. 근데 이게 쌓이면 어떻게 되는지는 경험으로 안다. 새 기능 추가할 때 왜 이렇게 복잡한지 설명하기 어렵고, 버그 하나 고치면 옆에서 다른 게 터지고, 온보딩 비용이 몇 배가 된다.

코드를 쓸 때 계속 머릿속에 두는 기준이 있다. 6개월 후 내가 다시 봤을 때 이해할 수 있는가. 나 말고 다른 사람이 봐도 의도가 읽히는가. 새벽 장애 상황에서 이 코드가 원인 파악을 빠르게 해주는가.

좋은 코드의 기준 나쁜 코드의 신호
읽으면 의도가 바로 보임 주석 없으면 이해 불가
변경이 한 곳에만 영향 한 곳 바꾸면 여러 곳 수정 필요
테스트 작성이 자연스러움 테스트하려면 구조부터 바꿔야 함
실패 시 원인이 로그에 남음 빈 catch로 예외 삼킴

이런 작업들이 쌓이면서 시스템이 조금씩 견고해진다는 걸 느낀다. 당장 배포 노트에 쓸 내용이 없어도 괜찮다. 나중에 디버깅 시간이 줄어드는 걸로 돌아온다.

댓글 0

첫 댓글 달아줘.