😞 내 코드 구려...
코드를 작성하다 보면 필연적으로 예외(Exception) 처리를 마주하게 됩니다.
그런데 new 키워드로 직접 예외를 생성하고 메시지를 일일이 작성하다 보면, 코드가 점점 복잡해지고 일관성이 깨지는 경험을 해본 적 있을 겁니다.
우테코 프리코스 1주차에 코드를 짜면서 제가 직면했던 문제인데요...
new로 throw 해주고, try catch문으로 감싸고...
이런 방식을 예외마다 사용해주다보니, 로직 코드인데, 코드의 절반이 try catch문으로 변하더군요...
1주차가 끝나고 나서 이를 어떻게 하면 해결할 수 있을까 찾아보다가 예외 팩토리 패턴 이라는 것을 발견해서 공유해드립니다!
🤔 기본 개념: 예외 생성 맡기기
예외 팩토리 패턴은 예외 객체를 생성하는 로직을 별도의 팩토리 클래스나 정적(static) 메서드로 분리하여 캡슐화하는 디자인 기법입니다
즉, 예외가 발생할 때 직접 new를 호출하는 대신, 팩토리 메서드를 통해 예외를 생성합니다.
이렇게 하면 비즈니스 로직에서 예외 생성의 복잡성이 사라지고, 코드의 의도가 훨씬 명확해집니다.
예시로 보여드리겠습니다.
⏸️ 팩토리 사용 전
// UserService.java
if (user == null) {
throw new UserNotFoundException("사용자 ID: " + userId + "를 찾을 수 없습니다.");
}
쉬운 예시로, 위와 같이 유저가 없으면, 사용자 ID와 함께 해당 사용자를 찾을 수 없다는 메시지의 UserNotFoundException 예외를 날린다고 해봅시다.
문제가 되는 부분은 메시지를 직접 작성해야 한다는 점입니다.
이런 방식을 사용하면, 예외를 띄우는 곳마다 해당 코드를 전부 써줘야 하기 때문에 오타나 포맷 불일치로 인해 일관성을 유지하기 어려워집니다.
한 가지 예시로, 위와 같은 예외를 10곳에서 보내고 있는데, userId는 예외 메시지에서 뺴줘야 한다고 하면... 10곳의 예외 메시지를 전부 다르게 변경해야 하는 것입니다...!
으악!
▶️ 팩토리 사용 후
> UserExceptionFactory
public final class UserExceptionFactory {
private UserExceptionFactory() {
// 인스턴스 생성을 막습니다.
}
public static UserNotFoundException notFound(Long userId) {
String message = String.format("사용자 ID: %d를 찾을 수 없습니다. (CODE: U-404)", userId);
return new UserNotFoundException(message);
}
}
팩토리 사용을 위해 먼저 이런식으로 Factory 코드에서, UserNotFoundException를 반환하는 함수를 짜줍니다.
그리고 새로운 객체에 매시지를 담아서 반환해주는 겁니다!
> UserService
// UserService.java
if (user == null) {
throw UserExceptionFactory.notFound(userId);
}
그 다음 UserService.java에서 이를 받아서 throw만 해주면?
안에서 어떤 text가 들어가서 나오는지도 더럽게 노출되지 않습니다!
이렇게 하면, 예외 생성 로직이 한 곳에 집중되고
즉, 비즈니스 코드에서는 예외 발생 "상황"만 명확히 표현하면 되는겁니다.
⏩ 더 나아가서 Enum 기반 오류 코드 관리
더 나아가서 저는 개인적으로 오류 format의 Text메세지 같은 것이 바로 쓰이는 것을 싫어해서 Enum class로 따로 관리하는 것을 선호합니다!
> Exception Message
public enum ExceptionMessage {
USER_NOT_FOUND("사용자 ID %d를 찾을 수 없습니다."),
INVALID_INPUT("입력값 '%s'이(가) 올바르지 않습니다."),
DUPLICATE_USER("이미 존재하는 사용자입니다. (ID: %d)");
private final String messageTemplate;
ExceptionMessage(String messageTemplate) {
this.messageTemplate = messageTemplate;
}
public String format(Object... args) {
return String.format(messageTemplate, args);
}
}
이런 식으로 Enum을 선언하고, 값에 에러 메시지를 저장합니다.
> UserException
public class UserException extends RuntimeException {
public UserException(String message) {
super(message);
}
}
그 다음 메시지를 받는 구조로 가볍게 새로운 예외를 정의해주고 (예외 출력 format에 이런저런 값들을 넣으려면 여기서 추가로 생성하시면 됩니다!)
> UserExceptionFactory
public final class UserExceptionFactory {
private UserExceptionFactory() {
// 인스턴스 생성을 막기 위한 private 생성자
}
public static UserException of(ExceptionMessage message, Object... args) {
String formattedMessage = message.format(args);
return new UserException(formattedMessage);
}
public static UserException userNotFound(Long userId) {
return of(ExceptionMessage.USER_NOT_FOUND, userId);
}
public static UserException invalidInput(String fieldName) {
return of(ExceptionMessage.INVALID_INPUT, fieldName);
}
}
이런 식으로 Factory 내부에서 Enum을 받아서 메시지를 포맷해주고 이를 service로 넘기는 방식을 사용할 수 있습니다!
이 경우의 동작 역할을 정리하면 아래와 같죠!
| ExceptionMessage | 예외 메시지 템플릿을 Enum으로 일괄 관리 |
| UserException | 단순 메시지 기반의 런타임 예외 클래스 |
| UserExceptionFactory | 메시지 포맷팅 및 예외 생성 담당 |
| UserService | 팩토리 호출로 간결한 예외 처리 |
✨ 주의점
사실 Factory 패턴은 가독성이 높아지는 반면에 너무 단순한 프로젝트엔 과한 설계일 수 있습니다.
또한 초반 설계가 중요한데, 예외 처리를 지나치게 일반화하면, 실제 원인을 추적하기 어려워질 수 있습니다.
그래서 진짜 중요한것은
내 프로젝트의 사이즈와 다룰 Exception의 범위를 확실히 파악하고 사용여부를 결정하는 것
이라고 생각합니다.
예외 팩토리 패턴은 단순히 예외 생성을 “예쁘게” 만드는 게 아니라, 코드의 일관성과 유지보수성을 높이는 강력한 설계 기법입니다.
프로젝트의 예외 처리가 점점 복잡해지고 있다면, 한 번쯤 예외 팩토리 패턴을 도입해보는 것을 추천드립니다...!
'Develop note' 카테고리의 다른 글
| [트러블슈팅] 공백 입력 테스트코드, NoSuchElementException (1) | 2025.10.28 |
|---|---|
| [Java] Utility class란? (1) | 2025.10.25 |
| [Git] Git alias 설정은 어떻게 돌아가는가? (0) | 2025.10.20 |
| [Git] Hooks 적용으로 커밋 메세지 형식 강제하기 (0) | 2025.10.15 |
