200 OK
{
"success": false, # 성공과 실패 여부만 알려주는 불친절한 flag. httpCode로 표현가능
"detail": null, # null을 응답하는 것은 프론트에서 null 처리를 하게 만드는 안티패턴 (과거 프로그래밍 언어의 유산)
"errorCode": -9999, # int 상수로 error에 대한 표준을 정하는 방식, 코드 정하는 시간이 추가적으로 들어가고 무엇을 의미하는지 가독성이 없다(표준화가 가능한 대기업만 적절한 방식)
}
int, boolean 대신에 Enum을 사용해서 가독성 좋은 Error 응답을 구성한다. (enum이라 프론트에서 받아 처리하기도 좋다.)
400 Bad Request
{
"errorCode": "BAD_REQUEST", # Enum으로 구현 -9999 같은 int 상수 보다 가독성이 훨씬 좋다.
"detail": "Bad Request",
"errors": [ // 마틴파울러의 Notification 패턴
{
"field": "password",
"value": "",
"reason": "password is required"
},
...
(구현 참고)
// Service 계층에서 RuntimeException 사용
val member = memberRepository.findByLoginId(loginId)
?: throw BusinessException(ErrorCode.MEMBER_NOT_FOUND, HttpStatus.NOT_FOUND)
// RuntimeException 상속하여 만든 BusinessException class
class BusinessException(
val errorCode: ErrorCode,
val httpStatus: HttpStatus
) : RuntimeException(errorCode.message) // 예시 exception은 cause가 필요 없어서 생략
// ErrorCode Enum
enum class ErrorCode(val message: String) {
BAD_REQUEST("Bad Request"),
UNAUTHORIZED("Unauthorized"),
FORBIDDEN("Forbidden"),
MEMBER_NOT_FOUND("Member not found"),
// Add other error codes as needed
}
결론
예외 처리에 정답은 없다.
입력 값은 반드시 검증하자. (값, type, range)
int 상수, boolean 대신에 Enum을 클라이언트에게 넘겨서 가독성 좋고, 분기하기 좋게 만들자.
중요한 예외는 재귀적으로 cause를 넘기도록 만든다. 되어있지 않아면 root exception 식별 도움주는 도구를 도입하자.