본문 바로가기

Java

예외 처리 가이드 - 임도형님 글 정리

예외 처리에 대해 알아보다 좋은 가이드라인이 있길래 정리를 해보았다.

보라색 가이드글을 먼저 읽고 빨강색 가이드글을 읽으면 이해가 잘된다.

 

 

예외 처리 가이드
- 모든 일에는 예외가 있다.
- 모든 시스템에는 예측하지 못하는 상황이 있다.
- 작업의 절차를 정의하는 프로그래밍에도 예외가 있을 수밖에 없다.

예외의 종류
- 예측 가능한 예외
예측 가능한만큼 예외처리 자체가 개발의 일부이다.
ex) 로그인을 실패했다.  DB에 레코드가 없다.  파일을 찾을 수 없다.

 

- 예측 불가능한 예외
버그 아니면 시스템 환경에 기인한다.
실시간 처리는 불가능하다, 대신 개선되어야 한다.

예외 처리가 제대로 되지 않으면?
- 문제가 발생해도 로그를 보지 않는다.
- println(), break point에 의지한 디버깅


시스템 개선
- 한번 발생한 예외상황이 다시 발생하지 않도록 하는 것.
- 발생한다 해도 예측이 가능하게 한다.
- 로그에 의존한다. 로그는 예외에 의존한다.

예외처리 잡을 때의 가이드
- 먹지 말자.
- 정보를 누락하지 말자.

 

예외를 잡은 후 유형
- 처리하고 예외상황 종료
절대적으로 로그를 남겨야 한다.


- 다시 던진다.
로그를 위해 잡아서 메시지를 출력하기 위해 다시 던진다.
메시지를 변경하는 것은 바람직하지 않다.


- 새 예외로 던진다.
세세한 예외를 더 추상화된 예외로 던질 경우 이 예외를 받은 쪽에서는 cause 예외만으로는 정보가 부족하다, 추가적인 상황 정보 추가는 필수적이다.


- 무시한다.
뭘 더 해볼 것이 없는 경우, 이 경우 로그조차 필요 없는 경우이다.

 

로그 레벨의 의미
- DEBUG
말 그대로 디버깅을 위한 목적이다.

DEBUG로 로그를 출력할 필요가 있다면 아직 안정화가 안된 것이다. 


- INFO
시스템 동작에 대한 정보를 제공한다. 


- WARN
현재 운영에는 문제가 없지만, 문제가 될 수 있는 사항 


- ERROR 
시스템 혹은 기타 오류로 운영에 문제가 있는 사항.
예외를 잡아서 정상 처리한 경우.


- FATAL
시스템 운영이 불가능한 경우.
예측되지 못한 예외를 잡아서 정상처리 못 한 경우.



1. GUIDE : 예외를 잡았으면 처리하라.
- 게으르지 말자.
- 예외 잡아서 콘솔로 출력하면 될 경우는 거의 없다.
- 컴파일된다고 코딩이 끝난 게 아니다.
- 처리할지 모른다면 예외도 잡지 말자.
- 다음과 같은 코드는 절대 있어선 안 된다.
ex) try { … } catch(SomeException e) { e.printStackTrace(); } //잡았으면 무언가 해야 한다.

 


2. GUIDE : 예외를 처리했으면 로그를 남겨라.
- 로그도 없고 처리도 안 함. 
차라리 안 잡는 것이 낫다.
결과를 보고 잘못됐다고 생각은 하지만, 재현할 방법이 없다. 
- 처리는 했는데 로그가 없다.
예외가 발생한 상황을 먹어버렸다.
시스템 개선할 여지가 없다.
- 처리도 하고 로그도 있다.
로그 유형
한심한 로그(“exception occurred”) -> 있으나 마나 한 로그 
무심한 로그(“IOException occurred”) -> 대충 감은 잡힌다. “데이터를 못 받았는데, IOException 발생했다면…” 언제까지 감으로 일한 텐가 
당연한 로그 -> 좋은 게 아니다.  ex) “IOException occurred. message=“socket read failed”” // 메시지 남기는 것은 기본이다. 
친절한 로그 -> 상황을 알려준다. ex) “second data reading failed. cause=[IOException, message=“socket read failed.”]” 
충분한 로그 -> OK ex) “second data reading failed. cause=IOException, message=“socket read failed.”], peer=“10.10.10.13”, port=1234, auth=“base123”, thread=32"

ex)
} catch(SomeException e) { // do nothing } // 차라리 잡지 말아야 한다. 
} catch(IOException e) { doOtherInstead(); } // 정상처리가 되었지만, 그 상황에 대한 로그는 없다. 
} catch(IOException e) { doOtherInstead(); Log.warn(“exception occurred.”); } // 예외가 발생했다는 것 외에는 정보가 없다.
} catch(IOException e) { doOtherInstead(); Log.warn(“IOException occurred.”); } // IOException이란 것만 알고 기타 정보가 없다. 
} catch(IOException e) { doOtherInstead(); Log.warn(e.toString()); } // 예외 클래스와 예외 메시지는 알지만, 상황을 모른다. 예외의 stack 정보조차 누락되었다.

 


3. GUIDE : 예외처리의 정보 누락 금지
- 예외의 목적이 무엇? : 시스템 개선
- 절대, 잡은 예외의 정보를 누락하지 말자. 반드시 cause로 설정하라.
- 많은 것을 요구하는 것이 아니다. 친절한 메시지까지는 몰라도 최소한 잡은 예외를 누락하지 말자.

 


4. GUIDE : java.lang의 추상 예외로 잡지 말자.
- 추상 예외로 잡는다면 코딩하기는 편하다고 착각할 수도 있다. 편한 게 아니다.
- try – catch 블록에 어떤 코드를 넣어도 컴파일된다. 이렇게 잡은 예외처리 방법이 오직 한가지가 아니라면 언젠가 세세한 예외로 나누게 될 것이다.
- Error, Exception, Throwable, RuntimeException과 같은 추상적인 클래스로 예외를 던지지 말자.
ex) 외부 라이브러리를 보니 타 예외를 먹은 경우다.
ex) 상황에 따른 처리를 하려면 별도의 처리가 불가피하다. 
ex) 예외 클래스는 그 이름만으로도 정보를 제공한다. 그런데 그 정보제공자체를 포기하는 것이다.

ex) public void doSomething() throw SomeException { try { doIt(); doOther(); doMore(); doAgain(); } catch(Exception e) { . . . } }
ex) public void doSomething() throws Exception;
ex) try { doIt(); } catch(Exception e) { if(e instanceof SomeExceptoin) { . . . } else if(e instanceof OtherExceptoin) { . . . } } // 예외마다 처리할 방법이 다르다면 편한 게 아니다.

 


5. GUIDE : 외부와의 interface에서는 RuntimeException도 잡아라.
- RuntimeException은 따로 catch하지 않아도 컴파일된다. 그래서 별로 관심을 두지 않는다.
- RuntimeException이 발생했다는 것 자체는 버그가 있다는 것을 의미한다. ex) NullPointerException이 발생했다는 것은 null 체크를 안 한 경우
- 버그는 발생할 수 있다. 잡으면 그만이다. 하지만 로그가 없으면 잡기 힘들다. 외부 인터페이스에서 RuntimeException을 잡아서 로그에 남기자. 여기서 말하는 인터페이스는 외부에서 호출하는 경계를 의미한다. ex) 외부 시스템  >>> 시스템, 컴포넌트, 패키지

 


6. GUIDE : try 블록은 적당한 크기를 유지하라.
- try 블록이 너무 크다면 catch를 어디서 던졌는지 안 보인다.
- 유지보수 혹은 코드 파악이 어려워진다.
- 하나의 try catch 블록이 30 line을 넘어가지 않게 하라.
- 만약 로직 상 어쩔 수 없다면 (아마도 중첩된 loop절이 있는 경우), 예외와 별개로 새로운 메소드로 뽑는 리팩토링을 고려해보라.

 


예외처리 던질 때 가이드
- 메시지 충실.
- 잡을 곳을 기준으로 예외 객체 선택.
- 메시지를 파싱하게 하지 말자.
- 추상 예외로 던지지 말자.



7. GUIDE : 예외를 던질 때는 반드시 message를 충실하게 설정한다.
- 예외를 왜 던질까?
바라던 행위를 스스로 할 수 없기 때문이다.
예외를 잡아서 처리할 수 있을 만큼 충분한 정보를 제공해야 한다. 정보가 충분하지 않으면 예외를 잡아도 처리할 수 없다.
- 메시지를 왜?
시간이 오래 걸릴 뿐 메시지가 자세하지 않아도 처리할 수는 있다.
로그에 가장 좋은 내용은 예외메시지이다. 정말로 필요가 없을 수도 있다. 그러나 대부분인 필요한 경우를 위하여 습관을 위해서라도 설정하라.
- 예외 자체만으로도 다음과 같은 정보를 얻을 수 있다.
Exception
클래스 이름
발생한 클래스 이름과 메소드 이름
호출한 stack
- 메시지에 따른 유지보수의 차이
메시지가 부실하면 >> 프로세스가 안 된다는 보고를 받고 코드 들여다보면서 상황을 짐작만 하고 그런 짐작을 확인하기 위한 로그 코드를 추가하고 시스템에 배치하고 맞으면 버그와 시스템을 패치하고 틀리면 계속 반복
메시지가 충실하다면 >> 그리고 로그 남김이 충실하다면 로그 파일로 상황을 파악하고 재현하고 버그 패치하고 시스템에 배치하고 버그 패치만 확인


ex) if(name==null) { throw new IOException(); } // IOException 이름 말고는 정보가 없다. }
ex) catch(IOException e) { throw new OtherException(e); } // 상황에 대한 정보가 없다. }
ex) catch(IOException e) { throw new OtherException(“runtime exception occurred”); } // 무의미한 메시지, cause 누락, 그리고 상황에 대한 정보가 없다.

8. GUIDE : 메시지를 파싱하게 하지 말자.
- 메시지는 예외를 처리할 수 있을 만큼 상세하여야 한다. 그러나 String 메시지를 파싱하여 정보를 추출하게 하지 말자.

9. GUIDE : UI 개발이 아니라면 message는 오직 디버깅을 위한 것이다. 이에 맞게 message를 작성하라.
- 예외 메시지는 사용자에게 보여주기에 적당치 않다.
- 메시지의 목적은 시스템 개선이다.
- 사용자에게 보여주는 내용은 변경되기 쉽고, 다국어도 고려해야 하고, 상황에 종속적일 수 있다. 

- 예외메시지 작성 시에 사용자가 볼지도 모른다는 우려할 필요 없고, 관련된 시간 낭비 할 필요 없다.
- 굳이 한국어로 할 필요도 없고, 대화체라든가, 하여간에 시간 낭비할 필요 없다.


10. GUIDE : 예외를 로그로 남길 때 모든 정보를 로그에 남겨라.
- 버그나 시스템 문제는 아니고, 단지 운영자에게 정보를 제공 INFO
- 지금은 로그를 남겨두지만, 좀 그렇다 싶으면 DEBUG
- 예측되는 예외가 발생하여 알려 주어야 한다 싶으면 WARN
- 예측되지 않은 예외가 발생하고 정상 처리되면 ERROR, 그렇지 않으면 FATAL
ex) DEBUG – 입력받은 값, DB에서 읽어온 값.
ex) INFO – 시스템 구동 시간, 읽은 설정 파일 위치, 캐싱 리프레쉬 사실. 
ex) WARN – 파일 시스템이 10% 남았다. 설정 값을 읽었지만 사용되지 않는다. 
ex) ERROR – 설정 값이 없어서 default 값을 사용했다. DB 접속이 끊겨서 다시 재접속하였다. 
ex) FATAL – 설정된 컴포넌트를 로딩하지 못했다. DB 접속이 되지 않는다.

설계 시 입력, 출력과 더불어 예외도 포함되어야 한다.

 

https://www.slideshare.net/dhrim/ss-2804901

 

예외처리가이드

java에서 예외를 처리하기 위한 가이드 요약하면 - 잡아서는 먹지 말자. - 던질 때는 메시지 충실히 - 로그는 정해진 곳에서만

www.slideshare.net