Skip to content

Exception 관련 작성 요령

Lee kangmin edited this page Sep 24, 2024 · 1 revision

CustomException을 세세하게 쪼갤지? 말지?

ClubDuplicateException, LeagueDuplicateException

DuplicateException

위와 같이 합친다면,

DuplicateException은 그럼 어디에 있어야 하냐?

Club 패키지? League 패키지?

Common 패키지 .. ?? 😵

또 뭐가 중복되었는지를 명시해주려면, 예외를 던지는(throw)하는 부분에서 “동호회”인지, “회원”인지를 매번 보내주어야 한다. 만약 동호회 중복이 발생해 예외를 던지는 곳이 10곳이라면, 10곳에서 전부 “동호회”라는 것을 넘겨주어야 하는데.. !

이보다는 custom exception을 잘게 쪼개서 “동호회가 중복되었습니다.”를 한 곳에만 두는 것이 좋지 않을까?

커스텀 예외 클래스가 Runtime Exception을 상속받지 않고 Badminton Exception을 상속받는 이유

Runtime Exception을 상속받는 Badminton Exception이 있다.

중추적인 Exception을 둘 수도 있고,

개별 커스텀 Exception들이 Runtime Exception을 상속받게 그냥 두어도 되었을 것이다.

그럼 왜 중추적인 Exception을 만들었냐?

개발자가 예상한 예외가 있고, 예상하지 못한 예외가 있다.

Exception Handler에서 BadmintonException을 잡았다는 것은 개발자가 예상할 수 있었던 예외가 발생했다는 것이다.

그런데 만약 Badminton Exception과 같은 중추적인 예외 클래스를 두지 않고 Runtime Exception을 상속받았다면, 개발자가 예상한 예외와 예상하지 못한 예외가 모두 같은 곳으로 가게 된다.

이 둘을 구분하지 못한다!

중추적인 Exception을 두면,

개발자들이 예상한 예외는 BadmintonExceptionHandler로 가고, 예상하지 못한 예외는 GlobalExceptionHandler로 갈 것이다.

에러 로그는 Exception Handler에서만 남겨야 한다.

에러에 대한 로그는 핸들러에서만 찍어라. 에러를 던질 때 로그를 또 던지면 로그 중복이 된다.

로그를 많이 찍는게 마음이 편안한 건 이해한다 … 😇

다만, 예외를 try-catch 로 잡는다면 catch에서 로그 찍어야 한다. catch로 잡아서 또 throw 한다면 또 안잡아도 된다. ㅋㅋ

에러코드는 그럼 어떻게? NOT_FOUND or CLUB_NOT_FOUND ?

에러 코드를 왜 만들까?

Custom Exception을 많이 안 나누고 싶어서 에러 코드를 정의했을 수 있다. 에러 코드를 세분화되게 나눠서 Custom Exception은 공통된 것을 묶어서 처리를 한다. 예를 들어, 에러 코드로 CLUB_NOT_FOUND, MEMBER_NOT_FOUND, LEAGUE_NOT_FOUND를 정의했다고 하자. 커스텀 예외는 NotFoundException을 만들고, 에러 코드만 다르게 담는 것이다.

그런데 이렇게 하면 생기는 문제가 있다.

어떤 파라미터가 예외를 발생했는지 아는 것은 개발자에게 중요하기 때문에 이 파라미터의 타입과 값을 로그에 남기는 편이 좋다. 그런데 NotFoundException으로 정의하면 파라미터의 타입을 알기 어렵다.

또한 문제가 되는 clubId, memberId, leagueId를 알고 있는 시점은 예외를 던질 때이다. 그렇다면 이곳에서 로그를 남겨야 하는데, 우린 Exception Handler에서 로그를 남기고 있다. 그래서 로그 중복이 발생할 수 있다.

Exception도 하나의 객체이다. Exception을 객체로 보지 못하면 여기에 정보를 담을 수 없는 것이다. 또 id만 담을 수 있는 것이 아니다. 다양한 정보를 exception에 담으면 된다.

클라이언트에 보여줄 내용과 내가 볼 로그를 구분해야 한다.

에러 코드를 사용자에게 보여 줄것인가?

동적인 파라미터를 받아서 상황을 추적하는 것은 개발자가 보는 로그에 남기는 것이다. **log.error**가 중요한 것이다. 로그에 찍을 거는 내가 보기 편하게 남기면 된다. 내가 볼 것은 소중하게 🙈

사실 Client에게 넘길 정보는 유연하게 해도 괜찮다.

에러를 클라이언트에게 어떻게 알려줄까?

너무 자세하게 알려줘도 문제고 너무 안 알려줘도 문제이다. 그 중간 지점을 찾아야 한다. 이건 프론트 분들과 이야기해볼 문제이다.

송금 서비스를 예로 들어보자. 에러 코드에 따라서 송금의 첫 화면으로 보내는 경우가 있다. 또는 직전의 마지막 화면으로 가도록 하는 경우가 있다. 어떤 경우에는 Alert를 띄우고, 어떤 경우에는 페이지를 이동시킨다. 클라이언트에서 이런 조건들을 처리해야 한다. if 안에 들어가는 것이 보통 **error code**이다.

Exception과 상속

Exception은 상속 밖에 답이 없다 !!

Stack trace 찍으려면 cause를 찍어야 한다.

package org.badminton.api.common.exception;

import org.badminton.api.common.error.ErrorCode;

import lombok.Getter;

@Getter
public class BadmintonException extends RuntimeException {

	private final ErrorCode errorCode;
	private final String errorMessage;

	public BadmintonException(ErrorCode errorCode) {
		this(errorCode, null);
	}

	public BadmintonException(ErrorCode errorCode, Exception e) {
		super(errorCode.getDescription(), e);
		this.errorCode = errorCode;
		this.errorMessage = wrapErrorDescription(errorCode.getDescription());
	}

	private String wrapErrorDescription(String description) {
		return "[" + description + "]";
	}
}