중복 코드와 매직 스트링 제거로 가독성 개선하기
1. 왜 가독성 리팩토링을 했는가
기능 구현이 마무리된 시점에 전체 코드를 다시 훑어봤다. 동작에는 문제가 없었지만 군데군데 읽기 불편한 지점들이 눈에 띄었다.
- 컨트롤러마다
SecurityUtils.getCurrentUserId()를 반복 호출하고 있었다. - 인증 목적을 나타내는 문자열이 상수가 아닌 리터럴로 여기저기 흩어져 있었다.
- 하나의 메서드가 너무 많은 일을 하고 있어서 흐름을 파악하려면 한참 읽어야 했다.
기능 개발 중에는 “일단 돌아가게” 하는 것이 우선이다 보니 이런 부분들이 자연스럽게 쌓인다. 하지만 이런 코드를 그대로 두면 나중에 수정할 때마다 먼저 코드를 해석하는 데 시간을 쓰게 된다.
2. 공통적으로 발견한 문제 패턴
도메인을 하나씩 열어보니 비슷한 문제가 반복되고 있었다.
중복 코드
인증 사용자 ID를 꺼내는 코드가 컨트롤러 메서드마다 그대로 복사되어 있었다. 로직 자체는 한 줄이지만, 같은 코드가 여러 곳에 있으면 나중에 변경할 때 모든 위치를 찾아야 한다.
매직 스트링
의미를 알 수 있는 문자열이더라도 리터럴이 코드 여러 곳에 흩어져 있으면 관리가 어려워진다. 오타가 발생해도 컴파일 타임에 잡히지 않고, 키 형식을 바꾸려면 모든 참조 지점을 찾아 수정해야 한다.
메서드 비대
하나의 메서드가 생성, 검증, 매핑을 한꺼번에 처리하는 경우가 있었다. 메서드 이름만 봐서는 정확히 무엇을 하는지 알기 어렵고, 내부 구현을 끝까지 읽어야 흐름을 파악할 수 있었다.
모호한 네이밍
반환하는 값이 무엇인지 이름만으로 추론하기 어려운 경우가 있었다. 예를 들어 getUsers처럼 단순한 이름은 단건 조회인지, 목록 조회인지, 페이지 조회인지 구분하기 어렵다.
3. 구체적인 개선 사례
3-1. getCurrentUserId() 헬퍼 추출
컨트롤러마다 아래와 같이 현재 로그인한 사용자 ID를 꺼내는 코드가 반복됐다.
Long userId = SecurityUtils.getCurrentUserId();
컨트롤러 메서드마다 SecurityUtils.getCurrentUserId()를 직접 호출하던 코드다.
memo, post, progress, reminder 컨트롤러 모두 같은 구조였다. 동작상 문제는 없었지만 같은 코드가 여러 곳에 있으면 변경에 취약하다.
예를 들어 SecurityUtils의 메서드 시그니처가 바뀌거나, 예외 처리 방식을 통일하고 싶을 때 모든 컨트롤러를 뒤져야 한다. 그래서 각 컨트롤러에 getCurrentUserId() private 헬퍼 메서드를 추출해 호출 지점을 하나로 모았다.
private Long getCurrentUserId() {
return SecurityUtils.getCurrentUserId();
}
SecurityUtils.getCurrentUserId() 호출을 private 헬퍼 메서드로 감싼 코드다.
단순한 위임처럼 보이지만, 이렇게 하면 이후에 로깅이나 예외 처리를 추가할 때 헬퍼 하나만 수정하면 된다. 그리고 메서드 본문에서 반복되는 노이즈가 사라져 핵심 로직이 더 잘 보인다.
3-2. 매직 스트링을 상수로 분리
AuthService에서 이메일 인증 목적을 구분하는 문자열이 리터럴로 사용되고 있었다.
// 변경 전
redisService.save("signup:" + email, code);
redisService.save("password-reset:" + email, code);
signup, password-reset 문자열을 Redis key 생성에 직접 사용하던 코드다.
signup, password-reset 같은 문자열은 의미를 어느 정도 알 수 있다. 하지만 코드 여러 곳에 흩어져 있으면 오타가 생겨도 컴파일 타임에 잡히지 않는다.
또한 나중에 키 형식을 바꾸려면 모든 참조 지점을 찾아 수정해야 한다. 그래서 인증 목적을 나타내는 문자열을 상수로 분리했다.
private static final String SIGNUP_PURPOSE = "signup";
private static final String PASSWORD_RESET_PURPOSE = "password-reset";
인증 목적 문자열을 SIGNUP_PURPOSE, PASSWORD_RESET_PURPOSE 상수로 분리한 코드다.
이제 목적 값을 바꾸고 싶으면 상수 선언 한 줄만 수정하면 된다. 또한 이름이 의도를 담고 있기 때문에 코드를 읽을 때 문자열의 의미를 다시 추론하지 않아도 된다.
4. 돌아보며
가독성 리팩토링은 기능을 새로 추가하는 작업보다 훨씬 조용한 작업이었다. 화면에 보이는 변화도 없고, 테스트가 통과하는 것 외에는 겉으로 드러나는 차이가 크지 않았다.
하지만 전체 코드를 훑고 나니 코드를 읽는 흐름은 확실히 달라졌다. 메서드 하나를 열었을 때 무엇을 하는지 더 빠르게 파악할 수 있었고, 같은 패턴이 어디 있는지 찾으러 다니는 시간도 줄었다.
동작은 그대로지만 읽히는 속도가 빨라진 느낌이었다. 결국 가독성 리팩토링은 지금 당장의 나보다 나중에 다시 코드를 읽을 나를 위한 작업이라고 느꼈다.