관리자 페이지 백엔드 로직 안정성
목차
관리자 페이지는 묘하게 방치되기 쉬운 영역이다. 사용자 수가 적고, 운영팀만 쓰고, "어차피 내부용"이라는 인식 때문에 코드 품질 기준이 슬쩍 낮아지는 경우가 많음. 근데 실상은 반대다. 관리자 기능은 데이터를 직접 건드리고, 권한 체계가 복잡하며, 잘못 동작하면 서비스 전체에 영향이 튄다. 이번에 백엔드 로직 파일 2개를 손본 건 그 맥락에서였음.
왜 백엔드 로직만 건드렸나
변경 범위는 의도적으로 좁혔다. JSP, 쿼리 XML, CSS는 손대지 않았음. 이번 목표가 "화면이 바뀌었다"가 아니라 "로직이 안전해졌다"였기 때문에, UI 레이어까지 범위를 넓히면 테스트 포인트가 불필요하게 늘어난다. 운영 중인 서비스에서 변경 범위를 최소화하는 건 선택이 아니라 원칙임.
파일은 2개였지만, 그 안에서 건드린 포인트는 크게 세 가지였다.
중복 로직 제거. 서로 다른 메서드에서 거의 같은 처리를 하고 있었는데, 각자 조금씩 다르게 구현돼 있었음. 이런 코드는 버그가 하나 생기면 두 군데를 따로 수정해야 하고, 둘 중 하나를 빠뜨리면 동작이 달라진다. 공통 메서드로 뺀 다음 두 군데서 위임하도록 교체했다.
엣지 케이스 처리. "정상 케이스" 위주로 짜여 있던 코드에 방어 로직을 추가했다. 관리자 기능 특성상 입력값이 예상 범위를 벗어나는 경우가 의외로 자주 생김. null 체크, 빈 컬렉션 처리, 예외 발생 시 fallback 같은 것들.
로그·에러 메시지 개선. 기존 로그가 너무 두루뭉술했다. "처리 실패"라고만 찍혀 있으면 새벽 2시에 알림 받고 들어왔을 때 뭘 봐야 할지 모른다. 어느 메서드에서, 어떤 입력으로, 어떤 이유로 실패했는지를 로그에 담도록 고쳤음.
// 변경 전
log.error("처리 실패");
// 변경 후
log.error("AdminService.processApproval 실패 | adminId={}, targetId={}, reason={}",
adminId, targetId, e.getMessage());
이 한 줄 차이가 장애 대응 시간을 30분 단축할 수도 있다는 게 과장이 아님. 로그는 미래의 디버거가 읽는 문서다.
운영 코드 리팩터링의 트레이드오프
코드를 좋게 만들고 싶은 욕심과, 안정성을 유지해야 하는 현실 사이에는 항상 긴장이 있다. 리팩터링은 기본적으로 "지금 잘 돌아가는 코드를 건드린다"는 얘기라서, 잘못하면 잘 돌아가던 게 안 돌아가게 된다.
이번에 스스로 적용한 기준은 이랬다.
| 허용 | 비허용 |
|---|---|
| 내부 구현 교체 (인터페이스 동일) | 메서드 시그니처 변경 |
| null 체크·방어 로직 추가 | 기존 분기 로직 재설계 |
| 로그 문구 교체 | 로그 레벨 일괄 변경 |
| 중복 제거 후 위임 패턴 적용 | 공통 모듈 신규 추출 (별도 배포 필요) |
"허용" 항목들은 외부 동작이 바뀌지 않는 변경이다. "비허용"은 실수 하나가 사이드 이펙트를 일으킬 수 있거나, 영향 범위 파악이 이번 작업 목표를 넘어서는 것들.
이 기준을 미리 잡아두지 않으면 작업하다 보면 범위가 자꾸 늘어난다. "이것도 고치면 좋을 텐데"가 연속으로 쌓이면 어느새 10개 파일을 건드리고 있고, QA 범위가 감당이 안 되는 상황이 된다. 작은 커밋을 유지하는 이유가 여기 있음. 변경 단위가 작아야 리뷰가 쉽고, 뭔가 잘못됐을 때 롤백도 명확해진다.
코드가 나중의 나를 도와주는가
코드를 쓰면서 계속 물어보는 게 있다. "6개월 뒤에 이 코드를 다시 봤을 때 뭘 하는 코드인지 10초 안에 파악할 수 있냐"는 것. 변수명, 메서드명, 주석의 위치 같은 것들이 다 거기에 영향을 준다.
관리자 기능은 특히 이게 중요하다. 사용 빈도가 낮아서 코드를 자주 안 보게 되고, 그래서 다시 봤을 때 컨텍스트가 이미 날아가 있음. "이게 왜 이렇게 돼 있지?"를 스스로 물어보는 상황이 생기면 그건 코드가 설명을 안 하고 있다는 신호다.
에러 처리도 비슷한 맥락이다. 예외를 그냥 catch하고 넘기거나, 상위로 throw만 하는 코드는 나중에 원인 추적이 어렵다. 예외를 잡았으면 왜 잡았는지, 이 상황에서 어떻게 처리하는지가 코드에서 보여야 함.
// 잡기만 하고 끝 - 나중에 아무 단서도 없음
try {
process(data);
} catch (Exception e) {
// nothing
}
// 의도가 보이는 처리
try {
process(data);
} catch (IllegalArgumentException e) {
log.warn("입력값 검증 실패, 기본값으로 진행 | data={}", data, e);
processWithDefault();
} catch (Exception e) {
log.error("처리 중 예상치 못한 오류 | data={}", data, e);
throw new AdminProcessException("처리 실패, 운영팀 확인 필요", e);
}
실행 결과가 같을 수 있어도, 6개월 뒤에 로그 뒤지면서 원인 파악할 때 경험이 완전히 달라진다. 좋은 에러 처리는 다음 사람에게 보내는 메모다.
이번 작업이 당장 뭔가 새로 생기거나 눈에 보이는 변화를 만든 건 아님. 근데 이런 작업들이 켜켜이 쌓여야 나중에 기능을 빠르게 추가할 수 있고, 장애가 나도 빠르게 복구할 수 있다. 개발 속도는 코드 품질에서 온다는 말이 처음엔 그냥 원칙처럼 들렸는데, 직접 쌓아보면서 그 말이 구체적으로 어떤 의미인지 점점 실감하고 있음.
댓글 0
첫 댓글 달아줘.