본문 바로가기
Homo Coding

enum을 사용한 에러메시지 코드화

by javauser 2009. 9. 22.


자바에서의 에러는 에러 메시지와 에러의 종류/내용에 대한 것이 전부이다. 그렇다 보니, 자바의 에러(exception) 이외의 코드를 처리하는 별도의 모델을 만들어서 이를 사용하는 경우가 대부분이다. 이 경우, 기본적으로 자바의 예외처리 메커니즘의 코딩 표준을 해치게 되면 이로 인해, 에러가 비즈니스 로직 내로 표현되는 경우가 많다. 분명 에러와 비즈니스 로직은 별도로 처리되어야 하며, 비즈니스 로직 내에서는 분명한 목적을 가진 예외는 throw 처리를 해줘야 한다.

여기서는 enum을 사용하여 에러 메시지를 코드화시켜 관리하는 방법에 대해서 소개하고자 한다.

우선, 예외는 크게 checked 와 unchecked로 나뉘게 되며, 여기서는 checked 예외를 적용하여 설명한다.

비즈니스 로직에 대한 예외처리를 위해서 예외를 별도로 선언한다. 이는 모든 정의된 비즈니스 예외에서 상속을 받아서 처리하게끔 추상 클래스로 선언한다.

public abstract class AbstractBizException extends Exception {
   private String code;
   private String message;

   private AbstractBizException(String message) {
      super(message);
      this.message = message;
   }
 
   protected AbstractBizException(String code, String message) {
      this(message);
      this.code = code;
   }
 
   private AbstractBizException(String message, Throwable err) {
      super(message, err);
      this.message = message;
   }
 
   protected AbstractBizException(String code, String message, Throwable err) {
      this(message, err);
      this.code = code;
   }

   protected AbstractBizException(ErrCodable errCodable, String...args) {
      this(errCodable.getErrCode(), errCodable.getMessage(args));
   }
 
   public String getCode() {
      return code;
   }

   public String getMessage() {
     return message;
   }
}


위의 클래스를 보면 여러가지 생성자가 표현되어 있는 것을 볼 수 있을 것이다. 우선 java.lang.Exception이 기본적으로 사용하고 있는 String 파라미터 하나의 생성자와, Throwable을 같이 가지고 있는 생성자는 private으로 선언하여 이 기능을 직접적으로 사용하지 못하게 선언했다. 또한, 내부적으로 에러코드와 에러메시지를 위한 변수를 선언하고, 이 두가지를 통해 제어하도록 생성자를 선언했고, 모두 protected로 선언되어 있다. 그리고, 좀 특수한 생성자가 있는데, ErrCodable 과 String... 의 두가지 변수를 취하는 생성자가 선언되어 있다. ErrCodable은 enum 이 상속받는 인터페이스이며, String... 인자는 message 내의 대체 인자값이 들어간다. 예를 들어, 'OOO값은 필수입니다.' 와 같이 메시지의 특정 부분을 공통으로 사용하기 위해 변수화 처리를 하면, 이때 'OOO'에 들어가는 진짜 값이 String...args 를 통해 넘어오게 된다.

그럼, ErrCodable 인터페이스를 살펴보자.

public interface ErrCodable {
   String getErrCode();
   String getMessage(String... args);
}


ErrCodable 인터페이스는 두개의 오퍼레이션을 가지고 있으며, 에러코드를 조회하는 오퍼레이션과 해당 메시지에 인자값이 처리된 내용을 조회하는 오퍼레이션이 있다. 인터페이스를 선언하는 이유 중에 한가지는 enum이 상위 enum으로부터의 확장이 불가능하기 때문에 이 인터페이스를 구현하는 에러코드 정의 enum을 확장하기 위함이다.

그럼, 이 인터페이스를 구현하는 enum을 살펴보자.

public enum BizCommonErrCode implements ErrCodable {
   /** %1은(는) 필수 입력 항목입니다. */
   ERR_0001("ERR_0001", "%1은(는) 필수 입력 항목입니다."),
   /** %1은(는) 존재하지 않습니다. */
   ERR_0002("ERR_0002", "%1이(가) 존재하지 않습니다."),
   /** %1이(가) 불일치합니다. */
   ERR_0003("ERR_0003", "%1이(가) 불일치합니다.")
   ;

   private String errCode;
   private String msg;
 
  @Override public String getErrCode() {
     return this.errCode;
   }

   @Override public String getMessage(String... args) {
      return ErrCodeUtil.parseMessage(this.msg, args);
   }

   BizCommonErrCode(String errCode, String msg) {
      this.errCode = errCode;
      this.msg = msg;
   }
}

위의 enum은 3개의 에러코드가 정의되어 있으며, 두개의 파라미터인 에러코드와 메시지를 모두 받는 생성자를 통해 선언된다. 여기서 에러코드는 enum에서 선언된 내부 속성과 동일하게 선언되었음을 유의하라. 그 이유는 실제 코딩시에도 코드에서 나타나는 에러코드와 내부적으로 사용되는 에러코드를 동일하게 보여주기 위함이다. 이를 달리했다고 해서 문제가 되지는 않지만, 코드의 유지보수를 편하게 하기 위해서 이와 같이 사용한다. 또 한가지는 에러메시지 표기법이 중간에 변수를 처리하는 경우 '%' 를 사용하여 표기하고 있다는 것이다. 이는 일반적인 자바 메시지 처리와 동일한 표기법을 사용했으며, 이를 처리는 부분이 ErrCodeUtil 클래스에 구현되어 있다.

public class ErrCodeUtil {
   public static String parseMessage(String message, String...args) {
      if (message == null || message.trim().length() <= 0)
         return message;
  
      if (args == null || args.length <= 0) return message;
  
      String[] splitMsgs = message.split("%");
      if (splitMsgs == null || splitMsgs.length <= 1)
         return message;
  
      for (int i = 0; i < args.length; i++) {
         String replaceChar = "%" + (i + 1);
         message = message.replaceFirst(replaceChar, args[i]);
      }
      return message;
   }
}


예외에 대한 선언은 다음과 같이 단순하게 정의될 수 있다.

public class BizCheckedException extends AbstractBizException {
   public BizCheckedException(String code, String message) {
      super(code, message);
   }
   public BizCheckedException(String code, String message, Throwable err) {
      super(code, message, err);
   }
   public BizCheckedException(ErrCodable errCodable, String... args) {
      super(errCodable, args);
   }
}

이제 이와 같이 선언된 예외를 사용해서 코딩하면 다음과 같이 에러코드를 직관적으로 볼 수 있게 된다.
public boolean login(String loginId, String passwd) throws BizCheckedException {
   if (loginId == null || loginId.trim().isEmpty())
      throw new BizCheckedException(BizCommonErrCode.ERR_0001, "로그인 ID");
  
   UserAccount userAccount = userProvider.findUserAccountByLoginId(loginId);
  
   if (userAccount == null)
      throw new BizCheckedException(BizCommonErrCode.ERR_0002, "로그인 ID");
  
   if (userAccount.getLoginPassword() == null && passwd == null) {
   } else if ((userAccount.getLoginPassword() == null && passwd != null) || 
                    !userAccount.getLoginPassword().equals(passwd)) {
      throw new BizCheckedException(BizCommonErrCode.ERR_0003, "비밀번호");
   }
   return true;
}
반응형