13. 게으르지 말자.
다음과 같은 코드는 절대 있어선 안 된다.
예외 잡아서 걍 콘솔로 출력하면 될 경우는 거의 없다.
컴파일 된다고 코딩이 끝난 게 아니다.
어찌 처리할 지 모른다면 잡지 말자.
try {
…
} catch(SomeException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
15. 잡은 후의 유형
처리하고 예외상황 종료
절대적으로 로그를 남겨야 한다.
다시 던진다.
로그를 위해 잡아서 메시지만을 보고 다시 던진다.
메시지를 변경하는 것은 바람직하지 않다.
새 예외로 던진다.
세세한 예외를 더 추상화된 예외로 던질 경우
이 예외를 받은 쪽에서는 cause 예외만으로는 정보가 부족하
다.
추가적인 상황정보 추가는 필수적이다.
무시한다.
뭘 더 해볼 것이 없는 경우.
이 경우 로그조차 필요 없는 경우이다.
17. 로그 유형
로그도 없고 처리도 안 함. -> XXX 최악이다.
차라리 안 잡는 것이 낳다.
뭔가 잘못됐다고 리포팅은 되지만, 재현할 방법이
없다.
처리는 했는데 로그가 없다. -> XX 아주 나쁨
예외가 발생한 상황을 먹어버렸다.
시스템 개선할 여지가 없다.
18. 로그 유형
처리도 하고 로그도 있다. : 다양한 경우가 있다.
한심한 로그(“exception occurred”) -> XX
있으나 마나 한 로그
무심한 로그(“IOException occurred”) -> X
대충 감은 잡힌다. “2번 째 데이터를 못 받았는데, IOException 발생했다면…”
언제까지 감으로 일한 텐가
당연한 로그 -> X 좋은 게 아니다.
예 : “IOException occurred. message=“socket read failed””
메시지 남기는 것은 기본이다.
친절한 로그 -> O 상황을 알려주어야 한다.
예 : “second data reading failed. cause=[IOException, message=“socket read
failed.”]”
어떤 상황인지 알려준다.
충분한 로그 -> OK
예 : “second data reading failed. cause=[IOException, message=“socket read
failed.”], peer=“10.10.10.13”, port=1234, auth=“base123”, thread=32
19. Anti Patterns
} catch(SomeException e) {
// do nothing
}
// 차라리 잡지 말아야 한다.
} catch(IOException e) {
doOtherInstead();
}
// 정상처리가 되었지만, 그 상황에 대한 로그는 없다.
} catch(IOException e) {
doOtherInstead();
Log.warn(“exception occurred.”);
}
// 예외가 발생했다는 것 외에는 정보가 없다.
20. Anti Patterns
} catch(IOException e) {
doOtherInstead();
Log.warn(“IOException occurred.”);
}
// IOException이란 것만 알지 기타 정보가 없다.
} catch(IOException e) {
doOtherInstead();
Log.warn(e.toString());
}
// 예외 클래스와 예외 메시지는 알지만, 상황을 모른다.
// 더욱이 예외의 stack 정보조차 누락되었다.
25. 추상 예외로 잡는다면?
코딩하기는 편하다고 착각할 수도 있다.
try – catch 블럭에 어떤 코드를 넣어도 컴파일 된다.
이렇게 잡은 예외의 처리 방법이 오직 한가지라고 확신
하지 못한다면(대부분의 그렇다) 언젠가 세세한 예외로
나누게 될 것이다.
편하게 아니다.
try {
…
} catch(Throwable e){
…
}
29. 예외 클래스 계층
Throwable
Error Exception
RuntimeException
NullPointerException
IllegalArgumentException
일반 예외 클래스
발생했다면 이미 처리할
수 있는 상황이 아니다.
따로 catch하지 않
아도 컴파일 된다.
30. RuntimeException?
RuntimeException은 따로 catch하지 않아도 컴파일된다.
그래서 별로 관심을 두지 않는다.
그런데 RuntimeException이 발생했다는 것 자체는 버그
가 있다는 것을 의미한다.
예 : NullPointerException. 요놈이 발생했다는 것은 null 체크를 제
대로 하지 않은 경우이다. 버그이다.
버그는 발생할 수도 있다. 잡으면 그만이다. 하지만 로그
가 없으면 잡기 힘들다. 외부 인터페이스에서
RuntimeException을 잡아서 로그에 남기자.
여기서 말하는 인터페이스는 외부에서 호출하는 경계를 의미한다.
외부 시스템
시스템,
컴퍼넌트,
패키지
32. try – catch 블럭이 너무 크다면
catch한 예외를 어디서 던졌는지 눈으로 안 보인다.
혹시 2군데에서 같은 예외를 던진 경우 처리 방법이
틀릴 수도 있다.
유지보수 혹은 코드 파악이 어려워 진다.
하나의 try – catch 블록이 30 line을 넘어가지 않게 하
라.
만약 로직상 어쩔 수 없다면? -> 아마도 중첩된 loop가 있는
경우 일 것이다. 예외와 별개로, 새로운 메소드로 뽑는 리팩터
링을 고려해 보라.
36. 메시지를 충실하게.
예외를 왜 던질까?
바라던 행위를 스스로 할 수 없기 때문이다.
밖의 누군가가 처리하라고 던진다.
던진 예외를 잡아서 처리할 수 있을 만
큼 충분한 정보를 제공해야 한다.
정보가 충분하지 않으면 예외를 잡아도 처
리할 수 없다.
37. 메시지를 꼭 왜?
메시지가 자세하지 않아도 처리할 수는 있다.
사용자에게 알려주기.
기본값 사용.
재시도.
그러나 적지 않은 경우 분석할 필요가 있다. 이
때 로그에 가장 좋은 내용은 예외의 메시지 이다
.
정말로 필요가 없을 수도 있다. 그러나 습관을
위해서라도 설정하라. 대부분인 필요한 경우를
위하여
38. 메시지를 사칭한 메시지 예
예외 자체만으로도 다음과 같은 정보를 얻을 수 있다.
Exception 클래스 이름
발생한 클래스 이름과 메소드 이름
호출한 stack
다음과 같이 던져진 예외는 무슨 정보를 제공하나?
runtime이 아닌경우가 있을까? 컴파일 타임을 말하나?
예외라고 말할 것 없다. 이미 예외인 것 안다.
occurred라고? 이미 발생한 것 알고 있다.
더군다나 예외 클래스에서도 정보를 얻지 못한다.
if(…) {
throw new Exception(“runtime exception occurred”);
}
39. 메시지에 따른 유지보수의 차이
메시지가 부실하면
뭐가 안 된다는 보고를 받고
코드 들여다 보면서 상황을 짐작만 하고
그런 짐작을 확인하기 위한 로그 코드를 추가하고
시스템에 배치하고
혹시 짐작이 맞으면 버그 패치하고
시스템에 배치하고 버그 패치가 확인되면 로그 남기는 코딩을 삭제
하여 다시 올리고
짐작 틀리면 계속 반복하고
메시지가 충실하다면. 그리고 로그 남김이 충실하다면
로그 파일로 상황을 파악하고
재현하고
버그 패치하고 시스템에 배치하고
버그 패치만 확인하고.
40. 충실한 메시지
그 예외만으로도 상황을 파악할 수 있게
하는 메시지
시스템을 개선할 수 있을 만큼 충분한
정보를 제공해야 한다.
단지 뭐가 실패했다는 정도로는 부족하
다. 상황이 포함되어야 한다.
41. Anti Patterns
if(name==null) {
throw new IOException();
}
// IOException 이름 말고는 정보가 없다.
} catch(IOException e) {
throw new OtherException(e);
}
// 상황에 대한 정보가 없다.
} catch(IOException e) {
throw new OtherException(“runtime exception occurred”);
}
// 무의미한 메시지, cause 누락, 그리고 상황에 대한 정보가 없다.
42. GUIDE : 메시지 템플릿
“some tasking failed.
name=tom, age=10”
43. 메시지 템플릿.
“TASK_NAME failed.” + (name=value)쌍 반복
• TASK_NAME을 결정할 때는 포함된 메소드 이름 활용 강추.
살짝 읽기 쉽게 풀어 주자.
• 만약 메소드 이름이 process13과 같다면 메소드 이름 리팩
토링도 고려해 보자.
• 어차피 TASK_NAME은 stack trace를 통해서 알 수 있지만,
고민하지 말고 메소드 이름을 보고 현재 하던 것을 적어주
자. 습관처럼 작성하자.
• 예 :“config loading failed.”, “authentication failed”,
“message sending failed.”
44. GUIDE : 외부에서 예외를
catch하여 처리할 상황
을 기준으로 던질 예외
클래스를 선택하라.
45. 그대로 던질까 하나로 던질까
특정 로직을 수행 중에 다양한 예외가
발생할 경우 어떤 예외를 던져야 하나?
외부에서 어떻게 처리할 지가 기준이다.
각 예외 별로 처리방법이 틀릴 경우 각 예외
그대로 던져야 한다.
예외가 뭐였든 간에 처리방법이 동일하다면
하나의 예외로 던지면 된다.
46. 그대로 던질까 하나로 던질까
• 예 : xml config파일을 로딩하는 loadConfig() 메소드
– 다양한 예외(file io, xml parsing, config parsing)가 발생한다.
– 외부에서는 loadConfig()를 호출 시 단지 로딩 성공이냐 혹은
실패냐 만이 관심이다.
– 다양한 예외가 던져진다 하더라도 처리 방법은 동일하다. 로그
를 남기고 시스템을 종료.
• 예 : 레코드 insert 시의 실패
– key가 중복되는 경우의 처리와 DBMS와의 통신 실패 시의 처
리 방법이 다르다.
– 같은 예외로 던진다면 메시지를 파싱해야 한다.
47. Anti Patterns
try {
doSomething();
} catch(AException e) {
treatIt();
} catch(OtherException e) {
treatIt();
} catch(TheOtherException e) {
treatIt();
} catch(AnotherException e) {
treatIt();
}
// 예외의 종류에 관계없이 처리할 방법이
// 똑같은 경우가 된다면 던지자.
48. Anti Patterns
try {
doOther();
} catch(SomeException e) {
String message = e.getMessage();
if(message … ) {
treatAsThis();
}
else if(message … ) {
treatAsThat();
}
else if(message … ) {
treatAsIt();
}
}
// 처리할 방법이 다양하다면 복수의 예외를 던져야 한다.
// 그렇지 않을 경우 메시지를 파싱하게 된다.
50. 추상적인 예외?
Error, Exception, Throwable, RuntimeException과 같
은 추상적인 클래스로 예외를 던지지 말자.
외부 라이브러리를 가져와서 그 모양새를 봤더니.
타 예외의 경우를 먹은 경우다.
상황에 따른 처리를 하려면 별도의 처리가 불가피하다.
예외 클래스는 그 이름만으로도 정보를 제공한다. 그런
데 그 정보제공자체를 포기하는 것이다.
public void someMethod() throws Exception;
53. 메시지 파싱?
메시지는 예외를 처리할 수 있을 만큼 상세하여야 한
다.
그러나 String 메시지를 파싱하여 정보를 추출하게 하
지 말자.
메시지 파싱이 필요한 경우? : 메시지의 내용에 따라
처리할 로직이 달라야 하는 경우
만약 이러한 경우가 발생한다면 예외 클래스에 이러한
정보를 담을 속성을 추가하라. (ex : code)
혹은 별개의 예외로 던져라.
54. Anti Patterns
if(isRecordExist(record)) {
throw new InsertFailException(“duplicated id”);
}
try {
insertRecored(record);
} catch(SQLException e) {
throw new InsertFailException(“sql failed.”);
}
// 호출하는 쪽에서는 메시지를 파싱해서 처리할 수 밖에 없다.
55. GUIDE : UI 개발이 아니
라면 message는 오직
디버깅을 위한 것이다.
이에 맞게 message를
작성하라.
56. 디버깅을 위한 메시지?
예외의 메시지는 사용자에게 보여주기에 적당치 않다.
여기서의 사용자는 개발된 시스템을 사용하는 최종 사용자.
메시지의 목적은 시스템 개선이다.
사용자에게 보여주는 내용은 변경되기 쉽고, 다국어도
고려해야 하고, 상황에 종속적일 수 있다.
예외의 메시지를 작성 시에 사용자가 볼지도 모른다는
우려할 필요 없고, 관련된 시간낭비 할 필요 없다.
따라서 굳이 한국어로 할 필요도 없고, 대화체라든가,
하여간에 시간 낭비할 필요 없다.
57. Anti Patterns
if(someIsFailed) {
String message = null;
if(language.equals(“korean”)) {
message = “소켓 읽기에 실패했습니다.”
}
else if(language.equals(“english”)) {
message = “reading socket failed.”;
}
throw new SomeException(message);
}
// 던지는 예외의 메시지를 UI에서 직접 보여주어서는 않된다.
59. GUIDE : 예외를 로그로
남길 때 모든 정보를 로
그에 남겨라. stack trace
포함해서.
60. 정보 출력
새로 정의한 예외 클래스이고, 별개의 정보를 가지고
있는 속성이 추가되었다면, 모든 정보를 출력할
getMessage() 메소드를 override하면 편하다.
private String host;
private int port;
@Override
public String getMessage() {
StringBuffer messageSb = new StringBuffer();
messageSb.append(super.getMessage());
messageSb.append(“, host=“).append(host);
messageSb.append(“, port=“).append(port);
return messageSb.toString();
}
61. stack trace
예외가 발생한 메소드가 호출된 역사.
상황을 파악하는데 아주 중요하다.
사뭇 길어 보이고 로그가 지저분해 보일 수도
있다.
하지만 예외의 목적이 뭐? : 시스템 개선
Writer writer = new StringWriter();
e.printStackTrace(new PrintWriter(writer));
String stackTrace = writer.toString();
65. 로그 레벨의 의미
• DEBUG
– 말 그대로 디버깅을 위한 목적이다.
– 아직도 DEBUG로 로그를 출력할 필요가 있다면 아직 안정화가 안된 것이다.
• INFO
– 시스템 동작에 대한 정보를 제공한다.
• WARN
– 현재 운영에는 문제가 없지만, 문제가 될 수 있는 사항
• ERROR
– 시스템 혹은 기타 오류로 운영에 문제가 있는 사항.
– 예외를 잡아서 정상처리한 경우.
• FATAL
– 시스템 운영이 불가능한 경우.
– 예측되지 못한 예외를 잡아서 정상처리 못한 경우.
66. 로그 레벨 선택 방법
버그나 시스템 문제는 아니고, 단지 운영자에게 정보를
제공 INFO
지금은 로그를 남겨두지만, 좀 그렇다 싶으면 DEBUG
디버깅을 위해서 잠시 속살을 보여주는 것이다.
예측되는 예외가 발생하여 알려 주어야 한다 싶으면
WARN
예측되지 않은 예외가 발생하고 정상처리되면 ERROR,
그렇지 않으면 FATAL
67. 로그 레벨의 예
• DEBUG
– 예: 입력받은 값
– 예 : DB에서 읽어온 값.
• INFO
– 예 : 시스템 구동 시간, 읽은 설정파일 위치, 캐싱 리프레쉬 사실.
• WARN
– 예 : 파일 시스템이 10% 남았다.
– 예 : 설정 값을 읽었지만 사용되지 않는다.
• ERROR
– 예 : 설정값이 없어서 default 값을 사용했다.
– 예 : DB 접속이 끊겨서 다시 재접속하였다.
• FATAL
– 설정된 컴퍼넌트를 로딩하지 못했다.
– 예 : DB 접속이 되지 않는다.