작업 일지#4 에러 메세지 -1-

2021. 10. 29. 18:34작업일지

개요

그동안 프로젝트 작업을 하면서, 여러 종류의 에러를 받고 디버깅 작업을 해왔다. 당연히 출시를 하기 전에는 에러를 고쳐야하는 것이 맞지만, 열심히 고치고 고쳐서 출시를 하여도, 여러가지의 원인으로 에러가 발생한다.

에러를 알리고, 해결방안을 제시하는 방법은 매우 다양하고, 사실 표준 스펙도 없다. 다만, 지금과 같이 협업 프로젝트를 진행해야하 하는 경우, 혼란을 피하기 위해선, 어느정도 일관적인 기준이 필요하다고 판단을 하게 되었다.

이번 일지는 내외부에서 api를 사용하는 개발자의 실수(혹은 서버의 문제로)로 발생하는 버그를 좀더 효율적으로 다룰 수 있도록 하는 목적을 가진 '에러 메세지'를 어떻게 다루느냐에 대한 나의 고민과 특정 결과물을 만들게 된 과정을 서술해볼 까 한다.

INDEX

  1. Exception
  2. HTTP Status Code
  3. Exception & HTTP Status Code
  4. 응답 에러 메세지 정책
  5. Handling Exception with Spring Boot

Exception

Java에서 Exception은 직역하면, '에외'이다. Java 공부를 해온 사람은 다 알겠지만, 예외에는 크게 Checked ExceptionUnchecked Exception이 있다. 둘의 차이점은 전자는 예외를 컴파일러에게 처리하도록 강요를 하는 예외이고, 후자는 예외만 던지고, 후속 처리는 사용자(개발자 포함)에게 맡기는 성질을 갖고 있다. 둘의 차이점을 이해하고 적절하게 활용하면 좋겠지만, 사실 이 방식은 생각보다 쉽지 않았다. 그래서 나 같은 경우에는 Effective Java 2nd Edition Chapter 9 (Item 57 ~ 65) 를 참고하면서, 앞으로 서술할 에러 메세지 정책의 틀을 마련하는데 도움을 받았다. 가져온 내용을 참고하자면...

  • item 57: 예외는 예외상황에만 사용해야 한다. 예외를 생성하고 던지는 것은 비용이 많이 드는 작업이다. 프로그램의 흐름을 예외로 제어하려고 하면 안된다. 좋은 API는 클라이언트가 프로그램 흐름을 제어할 때 예외를 쓸 수 밖에 없도록 만들지 않는다.
  • item 58: 복구가 가능하다면 Checked Exception을, 프로그래밍 에러는 RuntimeException을 사용하라, Error의 하위 클래스는 만들지 말고 처리할 수 없거나 처리하지 않는 예외는 모두 RuntimeException을 사용하라.
  • item 60: 표준 예외를 선호하라. IllegalArgumentException, IllegalStatementException, UnsupportedOperationException, ConcurrentModificationException 등 Java의 표준예외를 활용해라.
  • item 61: 예외를 적절하게 추상화 하라. 높은 계층에서 낮은 계층의 예외를 잡아서 높은 계층의 추상화 수준에 맞게 변환해서 던져야 한다. 예외변환(Exception translation) 패턴은 하위 레벨에 영향받지 않는 Exception 전파에 유리하지만 너무 남용하지는 마라. 가능하면 low-level exception이 없이 성공하도록 유도하는 것이 바람직하다. Exception chaining은 적절한 변환을 하면서도 세부 원인을 보존하는 장점이 있다.
  • Item 62: 메소드가 던지는 모든 예외를 명세문서에 기술하라 Checked exception은 메소드 선언부에 하나씩 선언하고, @throws 태그를 써서 모든 예외가 발생하는 상황을 정확하게 문서화하라. 단지 귀찮다는 이유만으로, 공통 상위타입으로 예외를 던지려 하지 마라. unchecked exception은 @throws 태그를 써서 명세문서에 기술하지만, 메소드 선언의 throws 절에는 나타나지 말아야 한다.
  • Item 63: 실패에 대한 자세한 정보를 상세 메시지에 담아라 실패원인을 포착하려면, 예외의 문자열 표현에 반드시 예외 발생에 영향을 준 모든 필드와 인자의 값이 들어 있어야 한다.
  • Item 64: 실패 원자성을 얻기위해 노력하라. 메소드 호출이 실패하더라도 객체상태는 메소드 호출 전과 같아야 한다. 오류(error)는 예외(exception)와 달리 보통 복구할수 없기 때문에 오류가 발생했을 때 실패 원자성을달성하기 위해 애쓸 필요가 없다.
  • Item 65: 예외를 잡아서 버리지 마라. 빈 catch block은 "예외 사항을 처리하라"라고 알려주는 예외의 존재 이유 자체를 짓밟는 것이다. catch block 안에서 정말 아무 것도 할 것이 없다면, 최소한 왜 예외를 잡아서 처리하지 않고 버리는지 그 이유라도 주석으로 달아 놓아야 한다.

나름 요약을 했어도 내용이 생각보다 많은데, 키 포인트만 추려보자면, 정말 필요한 경우가 아니라면, Checked Exception은 지양하고, RuntimeException을 활용하여 구체적인 메세지를 전달하고, 문제가 있는 필드와 인자값을 전달해야한다는 것이다. 좀더 디테일하게 이야기 하자면, Java의 표준 예외를 최대한 활용하며, 문서화를 꾸준히 하여, 예외를 던지는 것만으로 끝내지 말고, 해결할 수 있는 방향도 같이 전달해야한 정도로 볼 수 있을 것 같다.

HTTP Status Code

상태 코드는 어떻게 보면, 많은 개발자들이 성능을 포기하고, TCP/IP 기반의 HTTP 프로토콜로 api를 개발하는 이유들 중의 하나라고 생각한다. 이 상태코드를 통해, 개발자들은 자신이 개발한 애플리케이션 코드가 제대로 네트워크를 타고 클라이언트와 서버가 제대로 통신을 하는 지 확인할 수 있고, 만약 잘못 되더라도, 어디에서 문제가 생겼는지 금방 확인할 수 있다. 하지만, 이러한 기능도 결국엔 제대로 이해하지 못하면, 무용지물이니, 어느정도 학습할 필요를 느껴 다음과 같이 내용을 정리해보았다.

1XX (informational) : 요청이 수신되어 처리중

  • 연결된 디바이스간의 프로토콜 상태와 관련된 정보의 전송과 관련된 상태 코드이다. (핸드셰이킹 과정에서 수신정보 교환 같은 걸 예로 들 수 있다.)

2XX (successful) : 요청 정상 처리

  • 200 OK
    • 요청 성공
  • 201 Created
    • 요청이 성공했고, 그 결과 새로운 리소스가 생성됨.
  • 202 Accepted
    • 요청이 접수되었지만, 처리가 완료되지 않음. (배치 처리를 예로들 수 있다.)
    • 요청 접수 후 1시간 뒤에 배치 프로세스가 요청을 처리하는 경우.
  • 204 No Content
    • 서버가 요청을 성공적으로 수행했지만, 응답 바디에 보낼 데이터가 없다.
    • 문서 편집기의 save 버튼을 예로들 수 있다.
      • save버튼을 눌러도 같은 화면을 유지해야 하는 경우, 결과 내용이 없어도 204 메시지 만으로 성공을 인식할 수 있다.

3XX (redirection) : 요청을 완료하려면 추가 행동 필요

웹 브라우저는 3xx 응답의 결과에 Location 헤더가 있으면, Location 위치로 이동한다.

리다이렉션의 이해

  • 영구 리다이렉션 - 특정 리소스의 URI가 영구적으로 이동 (301, 308)
  • 일시 리다이렉션 - 일시적인 변경 (PRG) (302, 307, 303)
  • 300 Multiple Choices
  • 301 Moved Permanently
  • 302 Found
  • 303 See Other
  • 304 Not Modified
  • 307 Temporary Redirect
  • 308 Permanent Redirect

4XX (client error) : 클라이언트 사이드 오류, 요청 파라미터 등의 잘못된 입력으로 서버가 요청을 수행할 수 없음.

클라이언트의 잘못된 요청으로 서버가 요청을 수행할 수 없음.

이미 실패한 4XX 코드는 똑같은 재시도를 해도 실패한다.

  • 400 Bad Request : 요청 구문, 메시지 등등의 오류, 요청 파라미터가 잘못되거나, API 스펙이 잘못된 경우가 많다.
  • 401 Unauthorized : 클라이언트가 해당 리소스에 대한 인증이 필요함, 오류 발생시 WWW-Authenticate 헤더와 함께 인증 방법을 설명.
  • 403 Forbidden : 서버가 요청을 이해했지만, 승인을 거부. 인증 자격을 증명했지만, 접근 권한이 불충분한 경우
  • 404 Not Found : 요청 리소스가 서버에 없다. 혹은 권한이 부족한 클라이언트가 리소스에 접근할 때 해당 리소스를 숨기고 싶을 때도 사용.

5XX (server error) : 서버 가이드 오류, 서버가 정상적인 요청을 처리하지 못함.

서버 문제로 오류 발생

서버에 문제가 있기 때문에 재시도를 하면 성공할 수도 있다.

  • 500 Internal Server Error : 서버 문제. 애매하녀 대체로 500 오류
  • 503 Service Unabailable : 일시적인 과부하, 혹은 예정된 작업으로 잠시 요청을 처리할 수 없음. Retry-After 헤더 필드로 얼마뒤에 복구 되는지 보낼 수도 있다.

Exception & HTTP Status Code

위에서 Exception과 HTTP Status Code를 살펴 보았다. 서버에서 제공하는 기능에 문제가 생겼을 때의 상황을 가정해보았을 때, 둘다 무언가의 메세지를 보낸다는 점에서 별 차이가 없어보인다. 하지만 정말로 둘의 차이가 없을까? 고민해보고 정리한 결과 다음과 같은 결론을 내릴 수 있었다.

  • Status Code를 통해, 에러가 발생한 위치를 파악할 수 있고, Exception은 에러가 발생한 코드를 발견할 수 있다.

마무리

이번 일지를 통해, Exception과 HTTP Status Code의 기능과 역할을 구분할 기준을 어느정도는 정할 수 있게 되었다. 다음 일지에는 이러한 에러 메세지를 Spring Boot는 어떻게 관리하는지, 혹은 어떤 원리로 작동하는지 파악하는 시간을 갖고, 에러 메세지 정책을 정하여 spring boot에 어떻게 적용할지에 대해 내용을 서술하도록 하겠다.