[기타] 에러 핸들링 (Error Handling) 처리 경험기
에러 핸들링 구현기
프로젝트를 진행하며 초기 셋팅시, 에러에 대한 핸들링 처리를 통해 서버에 대한 신뢰도와 안정성을 높이는 작업을 진행하였고, 해당 과정에서 최종 정의한 에러 범위에 대한 정의, 정의 간 고려사항, 처리 방법 등에 대해 정리 해보았다.
에러 핸들링 처리는 목적은 다 똑같지만.. 범위나 방법 등에 대해서는 기업마다 팀마다 다를 것이라고 생각한다.
에러 핸들링 이란?
런타임 과정, 혹은 특정 예외 상황에서 발생하는 에러/비정상적인 상황들 (ValidationError, AuthError, ServiceLogicError 등)에 대해 직접 의도된 동작으로 처리 하는 기술
에러 핸들링의 주요 목표는 다음과 같다.
- SW의 비정상적인 종료들로 인한 사용자 경험 저해 방지
- 프로그램에 대한 안정성 및 신뢰도 향상
- 오류에 대한 정보를 전달하여 디버깅 및 유지보수 간 문제 해결에 도움 목적
에러 핸들링 간 고려 사항
에러 핸들링 정의를 위해 고려 되는 건, 상태 코드 (status) 및 언어나 프레임워크, 패키지 레벨에서 발생하는 일반적인 에러 들 (VadliationError, AuthError) 이 고려 된다. 추가로 직접 예외 처리를 하고자 하는 경우 MyCustomError 등을 통해 발생하는 에러에 대해(=예외에 대해) 의도하는 방향으로 처리 할 수 있다.
각 역할을 간단히 설명하면
📘 응답 상태 코드
- 200, 201, 204 : 성공, 생성 성공, 삭제 성공 등 (전반적인 성공에 대한 의미)
- 400, 401, 404, 422 : 잘못된 요청, 인증 에러, NotFound 에러, 엔티티 에러
- 500 : 서버에서 발생하는 에러
상태 코드는 서버로 접근 하여 얻을 수 있는 응답에 대한 대분류로, 어떤 응답 상태 코드 까지 의도한 상황으로 보고 에러 핸들링을 할지에 대한 의사 결정 과정이 필요하다.
📘 일반적인 에러
- 일반적인 에러는 언어 (ex. Python)나 프레임워크(Django), 패키지(Package) 레벨에서 발생하는 에러를 의미한다.
- 해당 에러 클래스들은 Exception 이란 클래스를 상속 받아 만들어지고, 각 언어/프레임워크/패키지 에 종속되어 있다.
- ex) VadliationError, AuthError, ParameterError, JWTTokenError 등
- Python Error [링크]
- Django Error [링크]
📘 커스텀 에러 (MyCustomError)
- 요청에 대한 서비스 로직 수행 중 발생하는 에러에 대한 정의
- 일반 에러와 같이 Exception를 상속받아 정의되며, 서비스에 따라 일정 정의 규칙에 따라 정말 커스텀하게 정의된다.
- 이름은 에러지만 성공에 대한 컨트롤을 맡기도 하며, 특정 규칙에 따라 UI 페이지 이동 등의 액션이 연결 될 수도 있다.
- 정확하게 구분하면 예외 핸들링 (Exception Handling) 이라고도 볼 수 있다.
최종 진행 한 에러 핸들링
팀 내에서는 서버로 들어오는 모든 요청에 대해 처리를 하고자 했고, 정의한 에러 핸들링 범위는 다음과 같았다.
📘 응답 상태 코드
- 200 번대, 400번대, 500번대에 대해 상관없이 서버로 들어왔다가 나가는 모든 요청은 응답 상태코드를 200으로 반환
- 단 404, 405 등 endpoint 주소, 스펙이 맞지 않아서 팅겨 나가지거나, 502, 503 처럼 어플리케이션 레벨에서 알 수 없는 서버 상태 등의 코드 들은 제외된다.
- 모든 상태 코드를 200으로 반환한 것은,
장단이 있겠지만, 서버로 보낸 API 요청에 대해 응답 상태 코드를 받고 처리할 때, 상태 코드별로 분기 처리를 하길 원치 않았기 때문이였다.
📔 세부 에러 코드의 사용
응답 상태 코드가 200으로 통일 되는 상황에서, 발생하는 에러에 대해 성공/실패 여부를 알기 위해
응답에 대한 세부 결과 코드가 필요하였고, 세부 에러 코드 규칙 (=0~9999 의 임의의 숫자)을 내부적으로 정의하고
해당 규칙에 맞게 응답을 내려주었다.
📘 일반적인 에러
- 일반적인 에러는 사실 언어/프레임워크/패키지 레벨에서 정의 되어 있는 모든 에러에 대해 핸들링을 정의 하는 것은 불필요 하다.
- 따라서 큰 갈래에 따라 에러 처리를 하고 나머지는 묶어 처리를 하였다.
ex) ValidationError -> 상태코드 200 / 세부 에러코드 3
ex) AuthError -> 상태코드 200 / 세부 에러코드 4
ex) 나머지 (정의되지 않은 에러) -> 상태코드 200 / 세부 에러 코드 9999
여기서의 나머지에 대한 정의된 적 없는 에러는 추후 디버깅을 통해 이슈를 파악한 후, 개발자가 추가적으로 보완한다. (개발자가 예측하지 못했던 에러로 궁극적으로는 999가 발생하지 않는 경우를 지향)
📘 커스텀 에러
- 일반적인 에러를 제외한, 서비스 로직 수행 중 발생하는 에러에 대해, 1000~9999 사이의 숫자로 자유롭게 정의 하였다.
ex) 상태코드 200 / 세부 에러코드 1001 / 메시지 : "회원가입 간 유저의 패스워드가 일치 하지 않음" -> "비밀 번호를 다시 확인해 주세요" 등의 추가 팝업 처리 가능
ex) 상태코드 200 / 세부 에러코드 1002 / 메시지 : "회원가입 성공!" -> 가입 성공 후 페이지 redirection 등의 처리 가능
즉 커스텀 에러는 성공/실패에 대해 세부 에러코드를 기준으로 처리하고 각 상황에 맞는 Action을 연결하게 된다.
📘 추가 예시
404로 처리 될 수 있는 API를 요청한다고 가정
ex) GET /user/3/mypages (요청 url endpoint를 mypages로 잘못 입력 한 경우)
-> 자원을 요청하는 입력한 주소 자체가 잘못됨 (경로가 존재하지 않는 경우)
-> 상태코드 404
-> 클라이언트 : 해당 에러 코드에 대해 404 처리 (페이지 없음)
ex) GET /user/{user_id}/mypage (user_id를 잘못 입력했다고 가정)
-> 입력한 주소에 해당되는 자원이 존재 하지 않거나, 유효하지 않음 (or 접근권한이 없음)
-> 서버에서는 user_id가 유효하지 않음 확인
-> 상태코드 200 / 세부 에러코드 1005 / 메시지 : "유저 id가 유효하지 않습니다!" 응답
-> 클라이언트 : 특정 팝업 처리 or 해당 에러 코드에 대해 404 처럼 처리(페이지 없음)
등의 방식으로 처리 가 가능하다.
물론 여기서의 404는 특정 세부 코드로 판별하는 것이 아닌 Http404 에러를 발생시켜 404 상태 코드로도 처리가 가능하다.