예외처리가이드

69

description

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

Transcript of 예외처리가이드

Page 1: 예외처리가이드
Page 2: 예외처리가이드

About Me

임도형

딴거 재미없어 오직 개발만 하는

삽질 무쟈게 싫어하는 .

[email protected]

Page 3: 예외처리가이드

예외 ?

. . .

Page 4: 예외처리가이드

모든 일에는 예외가 있다 .

모든 시스템에는 예측하지 못하는 상황이 있다 .

작업의 절차를 정의하는 프로그래밍에도 예외가 있을 수 밖에 없다 .

Page 5: 예외처리가이드

프로그래밍에서의 예외

입력 , 출력 그리고 제 3 의 당당한 IO .

예외가 없이는 출력의 특정 값을 사용 .if(read()==-1) 정보가 빈약하다 .

입력

출력

예외

Page 6: 예외처리가이드

예외의 종류 ?

예측 가능한 예외

예측 가능하지 않은 예외

Page 7: 예외처리가이드

예측 가능한 예외

예측 가능한 만큼 그 처리 자체가 개발의 일부이다 .

그런 상황을 처리하는 것이 당연하다 .로그인이 실패했다 .DB 에 레코드가 없다 .파일을 찾을 수 없다 .

Page 8: 예외처리가이드

예측 가능하지 않은 예외

버그 아니면 시스템 환경에 기인한다 .

실시간 처리는 불가능하다 . 대신 개선해야 한다 .

Page 9: 예외처리가이드

예외 처리가 제대로 되지 않으면 ?

문제가 발생해도 로그를 보지 않는다 .로그가 중복되거나 누락 .로그에 도움되는 내용이 없다 .

println(), 혹은 break point 에 의지한 디버깅 .

Page 10: 예외처리가이드

시스템 개선

한번 발생한 예측하지 못하는 상황이 다시 발생하지 않도록 하는 것 .발생하지 않도록 한다 .발생한다 해도 예측 가능하게 한다 .

오로지 로그에 의존한다 . 로그는 예외에 의존한다 .

Page 11: 예외처리가이드

잡을 때의 가이드 .

먹지 말자 .정보를 누락하지 말자 .

Page 12: 예외처리가이드

GUIDE : 예외를 잡았으면 처리하라 .

Page 13: 예외처리가이드

게으르지 말자 .

다음과 같은 코드는 절대 있어선 안 된다 .

예외 잡아서 걍 콘솔로 출력하면 될 경우는 거의 없다 .컴파일 된다고 코딩이 끝난 게 아니다 .어찌 처리할 지 모른다면 잡지 말자 .

try {…

} catch(SomeException e) {// TODO Auto-generated catch blocke.printStackTrace();

}

Page 14: 예외처리가이드

Anti Patterns

} catch(SomeException e) {

// TODO Auto-generated catch blocke.printStackTrace();

}// 잡았으면 무언가 해야 한다 .

Page 15: 예외처리가이드

잡은 후의 유형처리하고 예외상황 종료

절대적으로 로그를 남겨야 한다 .

다시 던진다 .로그를 위해 잡아서 메시지만을 보고 다시 던진다 .메시지를 변경하는 것은 바람직하지 않다 .

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

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

Page 16: 예외처리가이드

GUIDE : 예외를 처리했으면 로그를 남겨라 .

Page 17: 예외처리가이드

로그 유형

로그도 없고 처리도 안 함 . -> XXX 최악이다 .차라리 안 잡는 것이 낳다 .뭔가 잘못됐다고 리포팅은 되지만 , 재현할 방법이 없다 .

처리는 했는데 로그가 없다 . -> XX 아주 나쁨예외가 발생한 상황을 먹어버렸다 .시스템 개선할 여지가 없다 .

Page 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

Page 19: 예외처리가이드

Anti Patterns

} catch(SomeException e) {

// do nothing}// 차라리 잡지 말아야 한다 .

} catch(IOException e) {doOtherInstead();

}// 정상처리가 되었지만 , 그 상황에 대한 로그는 없다 .

} catch(IOException e) {doOtherInstead();Log.warn(“exception occurred.”);

}// 예외가 발생했다는 것 외에는 정보가 없다 .

Page 20: 예외처리가이드

Anti Patterns

} catch(IOException e) {

doOtherInstead();Log.warn(“IOException occurred.”);

}// IOException 이란 것만 알지 기타 정보가 없다 .

} catch(IOException e) {doOtherInstead();Log.warn(e.toString());

}// 예외 클래스와 예외 메시지는 알지만 , 상황을 모른다 .// 더욱이 예외의 stack 정보조차 누락되었다 .

Page 21: 예외처리가이드

GUIDE : 원 예외의 정보 누락 금지

Page 22: 예외처리가이드

정보 누락 금지

예외의 목적이 무엇 ? : 시스템 개선절대 , 잡은 예외의 정보를 누락하지 말자 . 반드시

cause 로 설정하라 .많은 것을 요구하는 것이 아니다 . 친절한

메시지까지는 몰라도 최소한 잡은 예외를 누락하지 말자 .

Page 23: 예외처리가이드

Anti Patterns

catch(SomeEception e) {

throw new OtherException(“blar”);}

catch(SomeEception e) {throw new OtherException(“blar. e=“+e);

}

Page 24: 예외처리가이드

GUIDE : java.lang 의 추상 예외로 잡지 말자 .

Page 25: 예외처리가이드

추상 예외로 잡는다면 ?코딩하기는 편하다고 착각할 수도 있다 .

try – catch 블럭에 어떤 코드를 넣어도 컴파일 된다 .

이렇게 잡은 예외의 처리 방법이 오직 한가지라고 확신하지 못한다면 ( 대부분의 그렇다 ) 언젠가 세세한 예외로 나누게 될 것이다 .

편하게 아니다 .

try {…

} catch(Throwable e){…

}

Page 26: 예외처리가이드

Anti Patterns

public void doSomething() throw SomeException {

try {doIt();doOther();doMore();doAgain();

} catch(Exception e) {. . .

}}

Page 27: 예외처리가이드

Anti Patterns

try {

doIt();} catch(Exception e) {

if(e instanceof SomeExceptoin) {. . .

}else if(e instanceof OtherExceptoin) {

. . .}else if(e instanceof AnotherExceptoin) {

. . .}

}// 예외 마다 처리할 방법이 다르다면 편한게 아니다 .

Page 28: 예외처리가이드

GUIDE : 외부와의 interface 에서는 RuntimeException 도 잡아라 .

Page 29: 예외처리가이드

예외 클래스 계층Throwable

Error Exception

RuntimeException

NullPointerException

IllegalArgumentException

일반 예외 클래스발생했다면 이미 처리할 수

있는 상황이 아니다 .

따로 catch 하지 않아도 컴파일 된다 .

Page 30: 예외처리가이드

RuntimeException?RuntimeException 은 따로 catch 하지 않아도 컴파일된다 .

그래서 별로 관심을 두지 않는다 .

그런데 RuntimeException 이 발생했다는 것 자체는 버그가 있다는 것을 의미한다 .예 : NullPointerException. 요놈이 발생했다는 것은 null 체크를

제대로 하지 않은 경우이다 . 버그이다 .

버그는 발생할 수도 있다 . 잡으면 그만이다 . 하지만 로그가 없으면 잡기 힘들다 . 외부 인터페이스에서 RuntimeException 을 잡아서 로그에 남기자 . 여기서 말하는 인터페이스는 외부에서 호출하는 경계를 의미한다 .

외부 시스템시스템 ,

컴퍼넌트 ,

패키지

Page 31: 예외처리가이드

GUIDE : 하나의 try 블록은 적당한 크기를 유지하라 .

Page 32: 예외처리가이드

try – catch 블럭이 너무 크다면

catch 한 예외를 어디서 던졌는지 눈으로 안 보인다 .

혹시 2 군데에서 같은 예외를 던진 경우 처리 방법이 틀릴 수도 있다 .

유지보수 혹은 코드 파악이 어려워 진다 .

하나의 try – catch 블록이 30 line 을 넘어가지 않게 하라 .만약 로직상 어쩔 수 없다면 ? -> 아마도 중첩된 loop 가 있는 경우

일 것이다 . 예외와 별개로 , 새로운 메소드로 뽑는 리팩터링을 고려해 보라 .

Page 33: 예외처리가이드

Anti Patterns

public void doSomething() throw SomeException {

try {doIt();doOther();doMore();doAgain();

} catch(SomeException e) {. . .

} catch(OtherException e) {. . .

} catch(AnotherException e) {. . .

} catch(MoreException e) {. . .

}}

Page 34: 예외처리가이드

던질 때의 가이드 .

메시지 충실 .잡을 곳을 기준으로 예외 객체

선택 .메시지를 파싱하게 하지 말자 .추상예외로 던지지 말자 .

Page 35: 예외처리가이드

GUIDE : 예외를 던질 때는 반드시 message를 설정한다 . 충실하게

Page 36: 예외처리가이드

메시지를 충실하게 .

예외를 왜 던질까 ?바라던 행위를 스스로 할 수 없기 때문이다 .밖의 누군가가 처리하라고 던진다 .

던진 예외를 잡아서 처리할 수 있을 만큼 충분한 정보를 제공해야 한다 .정보가 충분하지 않으면 예외를 잡아도 처리할 수

없다 .

Page 37: 예외처리가이드

메시지를 꼭 왜 ?

메시지가 자세하지 않아도 처리할 수는 있다 .사용자에게 알려주기 .기본값 사용 .재시도 .

그러나 적지 않은 경우 분석할 필요가 있다 . 이때 로그에 가장 좋은 내용은 예외의 메시지 이다 .

정말로 필요가 없을 수도 있다 . 그러나 습관을 위해서라도 설정하라 . 대부분인 필요한 경우를 위하여

Page 38: 예외처리가이드

메시지를 사칭한 메시지 예

예외 자체만으로도 다음과 같은 정보를 얻을 수 있다 .Exception 클래스 이름발생한 클래스 이름과 메소드 이름호출한 stack

다음과 같이 던져진 예외는 무슨 정보를 제공하나 ?

runtime 이 아닌경우가 있을까 ? 컴파일 타임을 말하나 ?예외라고 말할 것 없다 . 이미 예외인 것 안다 .occurred 라고 ? 이미 발생한 것 알고 있다 .더군다나 예외 클래스에서도 정보를 얻지 못한다 .

if(…) {throw new Exception(“runtime exception

occurred”);}

Page 39: 예외처리가이드

메시지에 따른 유지보수의 차이

메시지가 부실하면뭐가 안 된다는 보고를 받고코드 들여다 보면서 상황을 짐작만 하고그런 짐작을 확인하기 위한 로그 코드를 추가하고시스템에 배치하고혹시 짐작이 맞으면 버그 패치하고시스템에 배치하고 버그 패치가 확인되면 로그 남기는 코딩을 삭제하여

다시 올리고짐작 틀리면 계속 반복하고

메시지가 충실하다면 . 그리고 로그 남김이 충실하다면로그 파일로 상황을 파악하고재현하고버그 패치하고 시스템에 배치하고버그 패치만 확인하고 .

Page 40: 예외처리가이드

충실한 메시지

그 예외만으로도 상황을 파악할 수 있게 하는 메시지

시스템을 개선할 수 있을 만큼 충분한 정보를 제공해야 한다 .

단지 뭐가 실패했다는 정도로는 부족하다 . 상황이 포함되어야 한다 .

Page 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 누락 , 그리고 상황에 대한 정보가 없다 .

Page 42: 예외처리가이드

GUIDE : 메시지 템플릿 “some tasking failed. name=tom, age=10”

Page 43: 예외처리가이드

메시지 템플릿 .

“TASK_NAME failed.” + (name=value)쌍 반복

• TASK_NAME 을 결정할 때는 포함된 메소드 이름 활용 강추 . 살짝 읽기 쉽게 풀어 주자 .

• 만약 메소드 이름이 process13 과 같다면 메소드 이름 리팩토링도 고려해 보자 .

• 어차피 TASK_NAME 은 stack trace 를 통해서 알 수 있지만 , 고민하지 말고 메소드 이름을 보고 현재 하던 것을 적어주자 . 습관처럼 작성하자 .

• 예 :“config loading failed.”, “authentication failed”, “message sending failed.”

Page 44: 예외처리가이드

GUIDE : 외부에서 예외를 catch 하여 처리할 상황을 기준으로 던질 예외 클래스를 선택하라 .

Page 45: 예외처리가이드

그대로 던질까 하나로 던질까

특정 로직을 수행 중에 다양한 예외가 발생할 경우 어떤 예외를 던져야 하나 ?

외부에서 어떻게 처리할 지가 기준이다 .각 예외 별로 처리방법이 틀릴 경우 각 예외

그대로 던져야 한다 .예외가 뭐였든 간에 처리방법이 동일하다면

하나의 예외로 던지면 된다 .

Page 46: 예외처리가이드

그대로 던질까 하나로 던질까

• 예 : xml config 파일을 로딩하는 loadConfig() 메소드– 다양한 예외 (file io, xml parsing, config parsing) 가

발생한다 .– 외부에서는 loadConfig() 를 호출 시 단지 로딩 성공이냐 혹은

실패냐 만이 관심이다 .– 다양한 예외가 던져진다 하더라도 처리 방법은 동일하다 . 로그를

남기고 시스템을 종료 .

• 예 : 레코드 insert 시의 실패– key 가 중복되는 경우의 처리와 DBMS 와의 통신 실패 시의 처리

방법이 다르다 .– 같은 예외로 던진다면 메시지를 파싱해야 한다 .

Page 47: 예외처리가이드

Anti Patterns

try {

doSomething();} catch(AException e) {

treatIt();} catch(OtherException e) {

treatIt();} catch(TheOtherException e) {

treatIt();} catch(AnotherException e) {

treatIt();}// 예외의 종류에 관계없이 처리할 방법이 // 똑같은 경우가 된다면 던지자 .

Page 48: 예외처리가이드

Anti Patterns

try {

doOther();} catch(SomeException e) {

String message = e.getMessage();if(message … ) {

treatAsThis();}else if(message … ) {

treatAsThat();}else if(message … ) {

treatAsIt();}

}// 처리할 방법이 다양하다면 복수의 예외를 던져야 한다 .// 그렇지 않을 경우 메시지를 파싱하게 된다 .

Page 49: 예외처리가이드

GUIDE : java.lang 의 추상적인 예외로 던지지 말자 .

Page 50: 예외처리가이드

추상적인 예외 ?

Error, Exception, Throwable, RuntimeException과 같은 추상적인 클래스로 예외를 던지지 말자 .

외부 라이브러리를 가져와서 그 모양새를 봤더니 .

타 예외의 경우를 먹은 경우다 .상황에 따른 처리를 하려면 별도의 처리가 불가피하다 .

예외 클래스는 그 이름만으로도 정보를 제공한다 . 그런데 그 정보제공자체를 포기하는 것이다 .

public void someMethod() throws Exception;

Page 51: 예외처리가이드

Anti Patterns

public void doSomething() throws Exception;

Page 52: 예외처리가이드

GUIDE : 메시지를 파싱하게 하지 말자 .

Page 53: 예외처리가이드

메시지 파싱 ?

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

말자 .메시지 파싱이 필요한 경우 ? : 메시지의 내용에 따라

처리할 로직이 달라야 하는 경우만약 이러한 경우가 발생한다면 예외 클래스에 이러한

정보를 담을 속성을 추가하라 . (ex : code)혹은 별개의 예외로 던져라 .

Page 54: 예외처리가이드

Anti Patterns

if(isRecordExist(record)) {throw new InsertFailException(“duplicated id”);

}try {

insertRecored(record);} catch(SQLException e) {

throw new InsertFailException(“sql failed.”);}

// 호출하는 쪽에서는 메시지를 파싱해서 처리할 수 밖에 없다 .

Page 55: 예외처리가이드

GUIDE : UI 개발이 아니라면 message 는 오직 디버깅을 위한 것이다 . 이에 맞게 message 를 작성하라 .

Page 56: 예외처리가이드

디버깅을 위한 메시지 ?

예외의 메시지는 사용자에게 보여주기에 적당치 않다 .여기서의 사용자는 개발된 시스템을 사용하는 최종 사용자 .

메시지의 목적은 시스템 개선이다 .사용자에게 보여주는 내용은 변경되기 쉽고 , 다국어도

고려해야 하고 , 상황에 종속적일 수 있다 .예외의 메시지를 작성 시에 사용자가 볼지도 모른다는

우려할 필요 없고 , 관련된 시간낭비 할 필요 없다 .따라서 굳이 한국어로 할 필요도 없고 , 대화체라든가 ,

하여간에 시간 낭비할 필요 없다 .

Page 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 에서 직접 보여주어서는 않된다 .

Page 58: 예외처리가이드

로그 관련

예외가 처리되었으면 반드시 로그를 남겨라 .

정보를 누락하지 말고 로그로 남겨라 .

Page 59: 예외처리가이드

GUIDE : 예외를 로그로 남길 때 모든 정보를 로그에 남겨라 . stack trace 포함해서 .

Page 60: 예외처리가이드

정보 출력

새로 정의한 예외 클래스이고 , 별개의 정보를 가지고 있는 속성이 추가되었다면 , 모든 정보를 출력할 getMessage() 메소드를 override 하면 편하다 .

private String host;private int port;

@Overridepublic String getMessage() {

StringBuffer messageSb = new StringBuffer();messageSb.append(super.getMessage());messageSb.append(“, host=“).append(host);messageSb.append(“, port=“).append(port);return messageSb.toString();

}

Page 61: 예외처리가이드

stack trace

예외가 발생한 메소드가 호출된 역사 .상황을 파악하는데 아주 중요하다 .사뭇 길어 보이고 로그가 지저분해 보일 수도 있다 .하지만 예외의 목적이 뭐 ? : 시스템 개선

Writer writer = new StringWriter();e.printStackTrace(new PrintWriter(writer));String stackTrace = writer.toString();

Page 62: 예외처리가이드

GUIDE : 예외의 로그출력은 정해진 곳에서만 한다 .

Page 63: 예외처리가이드

로그 출력 위치

예외를 처리한 곳아무 곳에서나 남기면 로그가 중복된다 .

외부와의 인터페이스이곳에서조차 안 남기면 로그가 남을 지에 대하여

보장하지 못한다 .

Page 64: 예외처리가이드

Anti Patterns

catch(SomeEception e) {

logger.debug(e);throw new OtherException(“blar.”, e);

}

Page 65: 예외처리가이드

로그 레벨의 의미• DEBUG

– 말 그대로 디버깅을 위한 목적이다 .– 아직도 DEBUG 로 로그를 출력할 필요가 있다면 아직 안정화가 안된 것이다 .

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

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

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

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

Page 66: 예외처리가이드

로그 레벨 선택 방법

버그나 시스템 문제는 아니고 , 단지 운영자에게 정보를 제공 INFO

지금은 로그를 남겨두지만 , 좀 그렇다 싶으면 DEBUG디버깅을 위해서 잠시 속살을 보여주는 것이다 .

예측되는 예외가 발생하여 알려 주어야 한다 싶으면 WARN

예측되지 않은 예외가 발생하고 정상처리되면 ERROR, 그렇지 않으면 FATAL

Page 67: 예외처리가이드

로그 레벨의 예• DEBUG

– 예 : 입력받은 값– 예 : DB 에서 읽어온 값 .

• INFO– 예 : 시스템 구동 시간 , 읽은 설정파일 위치 , 캐싱 리프레쉬 사실 .

• WARN– 예 : 파일 시스템이 10% 남았다 .– 예 : 설정 값을 읽었지만 사용되지 않는다 .

• ERROR– 예 : 설정값이 없어서 default 값을 사용했다 . – 예 : DB 접속이 끊겨서 다시 재접속하였다 .

• FATAL– 설정된 컴퍼넌트를 로딩하지 못했다 .– 예 : DB 접속이 되지 않는다 .

Page 68: 예외처리가이드

다시 강조예외와 정보를 먹지 말자 .

예외 메시지를 충실히 .

Page 69: 예외처리가이드

MORE

예외와 로그도 프로젝트 차원으로 정책이 마련되어야 한다 .

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