About Me
임도형
딴거 재미없어 오직 개발만 하는
삽질 무쟈게 싫어하는.
dh-rim@hanmail.net
예외?
. . .
모든 일에는 예외가 있다.
모든 시스템에는 예측하지 못하는 상황
이 있다.
작업의 절차를 정의하는 프로그래밍에도
예외가 있을 수 밖에 없다.
프로그래밍에서의 예외
입력, 출력 그리고 제 3의 당당한 IO .
예외가 없이는 출력의 특정 값을 사용.
if(read()==-1)
정보가 빈약하다.
입력
출력
예외
예외의 종류?
예측 가능한 예외
예측 가능하지 않은 예외
예측 가능한 예외
예측 가능한 만큼 그 처리 자체가 개발
의 일부이다.
그런 상황을 처리하는 것이 당연하다.
로그인이 실패했다.
DB에 레코드가 없다.
파일을 찾을 수 없다.
예측 가능하지 않은 예외
버그 아니면 시스템 환경에 기인한다.
실시간 처리는 불가능하다. 대신 개선해
야 한다.
예외 처리가 제대로 되지 않으면?
문제가 발생해도 로그를 보지 않는다.
로그가 중복되거나 누락.
로그에 도움되는 내용이 없다.
println(), 혹은 break point에 의지한 디
버깅.
시스템 개선
한번 발생한 예측하지 못하는 상황이 다
시 발생하지 않도록 하는 것.
발생하지 않도록 한다.
발생한다 해도 예측 가능하게 한다.
오로지 로그에 의존한다. 로그는 예외에
의존한다.
잡을 때의 가이드.
먹지 말자.
정보를 누락하지 말자.
GUIDE : 예외를 잡았으면
처리하라.
게으르지 말자.
다음과 같은 코드는 절대 있어선 안 된다.
예외 잡아서 걍 콘솔로 출력하면 될 경우는 거의 없다.
컴파일 된다고 코딩이 끝난 게 아니다.
어찌 처리할 지 모른다면 잡지 말자.
try {
…
} catch(SomeException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Anti Patterns
} catch(SomeException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 잡았으면 무언가 해야 한다.
잡은 후의 유형
처리하고 예외상황 종료
절대적으로 로그를 남겨야 한다.
다시 던진다.
로그를 위해 잡아서 메시지만을 보고 다시 던진다.
메시지를 변경하는 것은 바람직하지 않다.
새 예외로 던진다.
세세한 예외를 더 추상화된 예외로 던질 경우
이 예외를 받은 쪽에서는 cause 예외만으로는 정보가 부족하
다.
추가적인 상황정보 추가는 필수적이다.
무시한다.
뭘 더 해볼 것이 없는 경우.
이 경우 로그조차 필요 없는 경우이다.
GUIDE : 예외를 처리했으
면 로그를 남겨라.
로그 유형
로그도 없고 처리도 안 함. -> XXX 최악이다.
차라리 안 잡는 것이 낳다.
뭔가 잘못됐다고 리포팅은 되지만, 재현할 방법이
없다.
처리는 했는데 로그가 없다. -> XX 아주 나쁨
예외가 발생한 상황을 먹어버렸다.
시스템 개선할 여지가 없다.
로그 유형
처리도 하고 로그도 있다. : 다양한 경우가 있다.
한심한 로그(“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
Anti Patterns
} catch(SomeException e) {
// do nothing
}
// 차라리 잡지 말아야 한다.
} catch(IOException e) {
doOtherInstead();
}
// 정상처리가 되었지만, 그 상황에 대한 로그는 없다.
} catch(IOException e) {
doOtherInstead();
Log.warn(“exception occurred.”);
}
// 예외가 발생했다는 것 외에는 정보가 없다.
Anti Patterns
} catch(IOException e) {
doOtherInstead();
Log.warn(“IOException occurred.”);
}
// IOException이란 것만 알지 기타 정보가 없다.
} catch(IOException e) {
doOtherInstead();
Log.warn(e.toString());
}
// 예외 클래스와 예외 메시지는 알지만, 상황을 모른다.
// 더욱이 예외의 stack 정보조차 누락되었다.
GUIDE : 원 예외의 정보
누락 금지
정보 누락 금지
예외의 목적이 무엇? : 시스템 개선
절대, 잡은 예외의 정보를 누락하지 말자. 반드
시 cause로 설정하라.
많은 것을 요구하는 것이 아니다. 친절한 메시
지까지는 몰라도 최소한 잡은 예외를 누락하지
말자.
Anti Patterns
catch(SomeEception e) {
throw new OtherException(“blar”);
}
catch(SomeEception e) {
throw new OtherException(“blar. e=“+e);
}
GUIDE : java.lang의 추상
예외로 잡지 말자.
추상 예외로 잡는다면?
코딩하기는 편하다고 착각할 수도 있다.
try – catch 블럭에 어떤 코드를 넣어도 컴파일 된다.
이렇게 잡은 예외의 처리 방법이 오직 한가지라고 확신
하지 못한다면(대부분의 그렇다) 언젠가 세세한 예외로
나누게 될 것이다.
편하게 아니다.
try {
…
} catch(Throwable e){
…
}
Anti Patterns
public void doSomething() throw SomeException {
try {
doIt();
doOther();
doMore();
doAgain();
} catch(Exception e) {
. . .
}
}
Anti Patterns
try {
doIt();
} catch(Exception e) {
if(e instanceof SomeExceptoin) {
. . .
}
else if(e instanceof OtherExceptoin) {
. . .
}
else if(e instanceof AnotherExceptoin) {
. . .
}
}
// 예외 마다 처리할 방법이 다르다면 편한게 아니다.
GUIDE : 외부와의
interface에서는
RuntimeException도 잡
아라.
예외 클래스 계층
Throwable
Error Exception
RuntimeException
NullPointerException
IllegalArgumentException
일반 예외 클래스
발생했다면 이미 처리할
수 있는 상황이 아니다.
따로 catch하지 않
아도 컴파일 된다.
RuntimeException?
RuntimeException은 따로 catch하지 않아도 컴파일된다.
그래서 별로 관심을 두지 않는다.
그런데 RuntimeException이 발생했다는 것 자체는 버그
가 있다는 것을 의미한다.
예 : NullPointerException. 요놈이 발생했다는 것은 null 체크를 제
대로 하지 않은 경우이다. 버그이다.
버그는 발생할 수도 있다. 잡으면 그만이다. 하지만 로그
가 없으면 잡기 힘들다. 외부 인터페이스에서
RuntimeException을 잡아서 로그에 남기자.
 여기서 말하는 인터페이스는 외부에서 호출하는 경계를 의미한다.
외부 시스템
시스템,
컴퍼넌트,
패키지
GUIDE : 하나의 try 블록
은 적당한 크기를 유지
하라.
try – catch 블럭이 너무 크다면
catch한 예외를 어디서 던졌는지 눈으로 안 보인다.
혹시 2군데에서 같은 예외를 던진 경우 처리 방법이
틀릴 수도 있다.
유지보수 혹은 코드 파악이 어려워 진다.
하나의 try – catch 블록이 30 line을 넘어가지 않게 하
라.
만약 로직상 어쩔 수 없다면? -> 아마도 중첩된 loop가 있는
경우 일 것이다. 예외와 별개로, 새로운 메소드로 뽑는 리팩터
링을 고려해 보라.
Anti Patterns
public void doSomething() throw SomeException {
try {
doIt();
doOther();
doMore();
doAgain();
} catch(SomeException e) {
. . .
} catch(OtherException e) {
. . .
} catch(AnotherException e) {
. . .
} catch(MoreException e) {
. . .
}
}
던질 때의 가이드.
메시지 충실.
잡을 곳을 기준으로 예외 객
체 선택.
메시지를 파싱하게 하지 말
자.
추상예외로 던지지 말자.
GUIDE : 예외를 던질 때
는 반드시 message를
설정한다. 충실하게
메시지를 충실하게.
예외를 왜 던질까?
바라던 행위를 스스로 할 수 없기 때문이다.
밖의 누군가가 처리하라고 던진다.
던진 예외를 잡아서 처리할 수 있을 만
큼 충분한 정보를 제공해야 한다.
정보가 충분하지 않으면 예외를 잡아도 처
리할 수 없다.
메시지를 꼭 왜?
메시지가 자세하지 않아도 처리할 수는 있다.
사용자에게 알려주기.
기본값 사용.
재시도.
그러나 적지 않은 경우 분석할 필요가 있다. 이
때 로그에 가장 좋은 내용은 예외의 메시지 이다
.
정말로 필요가 없을 수도 있다. 그러나 습관을
위해서라도 설정하라. 대부분인 필요한 경우를
위하여
메시지를 사칭한 메시지 예
예외 자체만으로도 다음과 같은 정보를 얻을 수 있다.
Exception 클래스 이름
발생한 클래스 이름과 메소드 이름
호출한 stack
다음과 같이 던져진 예외는 무슨 정보를 제공하나?
runtime이 아닌경우가 있을까? 컴파일 타임을 말하나?
예외라고 말할 것 없다. 이미 예외인 것 안다.
occurred라고? 이미 발생한 것 알고 있다.
더군다나 예외 클래스에서도 정보를 얻지 못한다.
if(…) {
throw new Exception(“runtime exception occurred”);
}
메시지에 따른 유지보수의 차이
메시지가 부실하면
뭐가 안 된다는 보고를 받고
코드 들여다 보면서 상황을 짐작만 하고
그런 짐작을 확인하기 위한 로그 코드를 추가하고
시스템에 배치하고
혹시 짐작이 맞으면 버그 패치하고
시스템에 배치하고 버그 패치가 확인되면 로그 남기는 코딩을 삭제
하여 다시 올리고
짐작 틀리면 계속 반복하고
메시지가 충실하다면. 그리고 로그 남김이 충실하다면
로그 파일로 상황을 파악하고
재현하고
버그 패치하고 시스템에 배치하고
버그 패치만 확인하고.
충실한 메시지
그 예외만으로도 상황을 파악할 수 있게
하는 메시지
시스템을 개선할 수 있을 만큼 충분한
정보를 제공해야 한다.
단지 뭐가 실패했다는 정도로는 부족하
다. 상황이 포함되어야 한다.
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 누락, 그리고 상황에 대한 정보가 없다.
GUIDE : 메시지 템플릿
“some tasking failed.
name=tom, age=10”
메시지 템플릿.
“TASK_NAME failed.” + (name=value)쌍 반복
• TASK_NAME을 결정할 때는 포함된 메소드 이름 활용 강추.
살짝 읽기 쉽게 풀어 주자.
• 만약 메소드 이름이 process13과 같다면 메소드 이름 리팩
토링도 고려해 보자.
• 어차피 TASK_NAME은 stack trace를 통해서 알 수 있지만,
고민하지 말고 메소드 이름을 보고 현재 하던 것을 적어주
자. 습관처럼 작성하자.
• 예 :“config loading failed.”, “authentication failed”,
“message sending failed.”
GUIDE : 외부에서 예외를
catch하여 처리할 상황
을 기준으로 던질 예외
클래스를 선택하라.
그대로 던질까 하나로 던질까
특정 로직을 수행 중에 다양한 예외가
발생할 경우 어떤 예외를 던져야 하나?
외부에서 어떻게 처리할 지가 기준이다.
각 예외 별로 처리방법이 틀릴 경우 각 예외
그대로 던져야 한다.
예외가 뭐였든 간에 처리방법이 동일하다면
하나의 예외로 던지면 된다.
그대로 던질까 하나로 던질까
• 예 : xml config파일을 로딩하는 loadConfig() 메소드
– 다양한 예외(file io, xml parsing, config parsing)가 발생한다.
– 외부에서는 loadConfig()를 호출 시 단지 로딩 성공이냐 혹은
실패냐 만이 관심이다.
– 다양한 예외가 던져진다 하더라도 처리 방법은 동일하다. 로그
를 남기고 시스템을 종료.
• 예 : 레코드 insert 시의 실패
– key가 중복되는 경우의 처리와 DBMS와의 통신 실패 시의 처
리 방법이 다르다.
– 같은 예외로 던진다면 메시지를 파싱해야 한다.
Anti Patterns
try {
doSomething();
} catch(AException e) {
treatIt();
} catch(OtherException e) {
treatIt();
} catch(TheOtherException e) {
treatIt();
} catch(AnotherException e) {
treatIt();
}
// 예외의 종류에 관계없이 처리할 방법이
// 똑같은 경우가 된다면 던지자.
Anti Patterns
try {
doOther();
} catch(SomeException e) {
String message = e.getMessage();
if(message … ) {
treatAsThis();
}
else if(message … ) {
treatAsThat();
}
else if(message … ) {
treatAsIt();
}
}
// 처리할 방법이 다양하다면 복수의 예외를 던져야 한다.
// 그렇지 않을 경우 메시지를 파싱하게 된다.
GUIDE : java.lang의 추상
적인 예외로 던지지 말
자.
추상적인 예외?
Error, Exception, Throwable, RuntimeException과 같
은 추상적인 클래스로 예외를 던지지 말자.
외부 라이브러리를 가져와서 그 모양새를 봤더니.
타 예외의 경우를 먹은 경우다.
상황에 따른 처리를 하려면 별도의 처리가 불가피하다.
예외 클래스는 그 이름만으로도 정보를 제공한다. 그런
데 그 정보제공자체를 포기하는 것이다.
public void someMethod() throws Exception;
Anti Patterns
public void doSomething() throws Exception;
GUIDE : 메시지를 파싱하
게 하지 말자.
메시지 파싱?
메시지는 예외를 처리할 수 있을 만큼 상세하여야 한
다.
그러나 String 메시지를 파싱하여 정보를 추출하게 하
지 말자.
메시지 파싱이 필요한 경우? : 메시지의 내용에 따라
처리할 로직이 달라야 하는 경우
만약 이러한 경우가 발생한다면 예외 클래스에 이러한
정보를 담을 속성을 추가하라. (ex : code)
혹은 별개의 예외로 던져라.
Anti Patterns
if(isRecordExist(record)) {
throw new InsertFailException(“duplicated id”);
}
try {
insertRecored(record);
} catch(SQLException e) {
throw new InsertFailException(“sql failed.”);
}
// 호출하는 쪽에서는 메시지를 파싱해서 처리할 수 밖에 없다.
GUIDE : UI 개발이 아니
라면 message는 오직
디버깅을 위한 것이다.
이에 맞게 message를
작성하라.
디버깅을 위한 메시지?
예외의 메시지는 사용자에게 보여주기에 적당치 않다.
여기서의 사용자는 개발된 시스템을 사용하는 최종 사용자.
메시지의 목적은 시스템 개선이다.
사용자에게 보여주는 내용은 변경되기 쉽고, 다국어도
고려해야 하고, 상황에 종속적일 수 있다.
예외의 메시지를 작성 시에 사용자가 볼지도 모른다는
우려할 필요 없고, 관련된 시간낭비 할 필요 없다.
따라서 굳이 한국어로 할 필요도 없고, 대화체라든가,
하여간에 시간 낭비할 필요 없다.
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에서 직접 보여주어서는 않된다.
로그 관련
예외가 처리되었으면 반드시 로
그를 남겨라.
정보를 누락하지 말고 로그로
남겨라.
GUIDE : 예외를 로그로
남길 때 모든 정보를 로
그에 남겨라. stack trace
포함해서.
정보 출력
새로 정의한 예외 클래스이고, 별개의 정보를 가지고
있는 속성이 추가되었다면, 모든 정보를 출력할
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();
}
stack trace
예외가 발생한 메소드가 호출된 역사.
상황을 파악하는데 아주 중요하다.
사뭇 길어 보이고 로그가 지저분해 보일 수도
있다.
하지만 예외의 목적이 뭐? : 시스템 개선
Writer writer = new StringWriter();
e.printStackTrace(new PrintWriter(writer));
String stackTrace = writer.toString();
GUIDE : 예외의 로그출력
은 정해진 곳에서만 한
다.
로그 출력 위치
예외를 처리한 곳
아무 곳에서나 남기면 로그가 중복된다.
외부와의 인터페이스
이곳에서조차 안 남기면 로그가 남을 지에
대하여 보장하지 못한다.
Anti Patterns
catch(SomeEception e) {
logger.debug(e);
throw new OtherException(“blar.”, e);
}
로그 레벨의 의미
• DEBUG
– 말 그대로 디버깅을 위한 목적이다.
– 아직도 DEBUG로 로그를 출력할 필요가 있다면 아직 안정화가 안된 것이다.
• INFO
– 시스템 동작에 대한 정보를 제공한다.
• WARN
– 현재 운영에는 문제가 없지만, 문제가 될 수 있는 사항
• ERROR
– 시스템 혹은 기타 오류로 운영에 문제가 있는 사항.
– 예외를 잡아서 정상처리한 경우.
• FATAL
– 시스템 운영이 불가능한 경우.
– 예측되지 못한 예외를 잡아서 정상처리 못한 경우.
로그 레벨 선택 방법
버그나 시스템 문제는 아니고, 단지 운영자에게 정보를
제공 INFO
지금은 로그를 남겨두지만, 좀 그렇다 싶으면 DEBUG
디버깅을 위해서 잠시 속살을 보여주는 것이다.
예측되는 예외가 발생하여 알려 주어야 한다 싶으면
WARN
예측되지 않은 예외가 발생하고 정상처리되면 ERROR,
그렇지 않으면 FATAL
로그 레벨의 예
• DEBUG
– 예: 입력받은 값
– 예 : DB에서 읽어온 값.
• INFO
– 예 : 시스템 구동 시간, 읽은 설정파일 위치, 캐싱 리프레쉬 사실.
• WARN
– 예 : 파일 시스템이 10% 남았다.
– 예 : 설정 값을 읽었지만 사용되지 않는다.
• ERROR
– 예 : 설정값이 없어서 default 값을 사용했다.
– 예 : DB 접속이 끊겨서 다시 재접속하였다.
• FATAL
– 설정된 컴퍼넌트를 로딩하지 못했다.
– 예 : DB 접속이 되지 않는다.
다시 강조
예외와 정보를 먹지 말자.
예외 메시지를 충실히.
MORE
예외와 로그도 프로젝트 차원으
로 정책이 마련되어야 한다.
설계 시 입력, 출력과 더불어 예
외도 포함되어야 한다.

예외처리가이드

  • 2.
    About Me 임도형 딴거 재미없어오직 개발만 하는 삽질 무쟈게 싫어하는. dh-rim@hanmail.net
  • 3.
  • 4.
    모든 일에는 예외가있다. 모든 시스템에는 예측하지 못하는 상황 이 있다. 작업의 절차를 정의하는 프로그래밍에도 예외가 있을 수 밖에 없다.
  • 5.
    프로그래밍에서의 예외 입력, 출력그리고 제 3의 당당한 IO . 예외가 없이는 출력의 특정 값을 사용. if(read()==-1) 정보가 빈약하다. 입력 출력 예외
  • 6.
    예외의 종류? 예측 가능한예외 예측 가능하지 않은 예외
  • 7.
    예측 가능한 예외 예측가능한 만큼 그 처리 자체가 개발 의 일부이다. 그런 상황을 처리하는 것이 당연하다. 로그인이 실패했다. DB에 레코드가 없다. 파일을 찾을 수 없다.
  • 8.
    예측 가능하지 않은예외 버그 아니면 시스템 환경에 기인한다. 실시간 처리는 불가능하다. 대신 개선해 야 한다.
  • 9.
    예외 처리가 제대로되지 않으면? 문제가 발생해도 로그를 보지 않는다. 로그가 중복되거나 누락. 로그에 도움되는 내용이 없다. println(), 혹은 break point에 의지한 디 버깅.
  • 10.
    시스템 개선 한번 발생한예측하지 못하는 상황이 다 시 발생하지 않도록 하는 것. 발생하지 않도록 한다. 발생한다 해도 예측 가능하게 한다. 오로지 로그에 의존한다. 로그는 예외에 의존한다.
  • 11.
    잡을 때의 가이드. 먹지말자. 정보를 누락하지 말자.
  • 12.
    GUIDE : 예외를잡았으면 처리하라.
  • 13.
    게으르지 말자. 다음과 같은코드는 절대 있어선 안 된다. 예외 잡아서 걍 콘솔로 출력하면 될 경우는 거의 없다. 컴파일 된다고 코딩이 끝난 게 아니다. 어찌 처리할 지 모른다면 잡지 말자. try { … } catch(SomeException e) { // TODO Auto-generated catch block e.printStackTrace(); }
  • 14.
    Anti Patterns } catch(SomeExceptione) { // TODO Auto-generated catch block e.printStackTrace(); } // 잡았으면 무언가 해야 한다.
  • 15.
    잡은 후의 유형 처리하고예외상황 종료 절대적으로 로그를 남겨야 한다. 다시 던진다. 로그를 위해 잡아서 메시지만을 보고 다시 던진다. 메시지를 변경하는 것은 바람직하지 않다. 새 예외로 던진다. 세세한 예외를 더 추상화된 예외로 던질 경우 이 예외를 받은 쪽에서는 cause 예외만으로는 정보가 부족하 다. 추가적인 상황정보 추가는 필수적이다. 무시한다. 뭘 더 해볼 것이 없는 경우. 이 경우 로그조차 필요 없는 경우이다.
  • 16.
    GUIDE : 예외를처리했으 면 로그를 남겨라.
  • 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(SomeExceptione) { // do nothing } // 차라리 잡지 말아야 한다. } catch(IOException e) { doOtherInstead(); } // 정상처리가 되었지만, 그 상황에 대한 로그는 없다. } catch(IOException e) { doOtherInstead(); Log.warn(“exception occurred.”); } // 예외가 발생했다는 것 외에는 정보가 없다.
  • 20.
    Anti Patterns } catch(IOExceptione) { doOtherInstead(); Log.warn(“IOException occurred.”); } // IOException이란 것만 알지 기타 정보가 없다. } catch(IOException e) { doOtherInstead(); Log.warn(e.toString()); } // 예외 클래스와 예외 메시지는 알지만, 상황을 모른다. // 더욱이 예외의 stack 정보조차 누락되었다.
  • 21.
    GUIDE : 원예외의 정보 누락 금지
  • 22.
    정보 누락 금지 예외의목적이 무엇? : 시스템 개선 절대, 잡은 예외의 정보를 누락하지 말자. 반드 시 cause로 설정하라. 많은 것을 요구하는 것이 아니다. 친절한 메시 지까지는 몰라도 최소한 잡은 예외를 누락하지 말자.
  • 23.
    Anti Patterns catch(SomeEception e){ throw new OtherException(“blar”); } catch(SomeEception e) { throw new OtherException(“blar. e=“+e); }
  • 24.
    GUIDE : java.lang의추상 예외로 잡지 말자.
  • 25.
    추상 예외로 잡는다면? 코딩하기는편하다고 착각할 수도 있다. try – catch 블럭에 어떤 코드를 넣어도 컴파일 된다. 이렇게 잡은 예외의 처리 방법이 오직 한가지라고 확신 하지 못한다면(대부분의 그렇다) 언젠가 세세한 예외로 나누게 될 것이다. 편하게 아니다. try { … } catch(Throwable e){ … }
  • 26.
    Anti Patterns public voiddoSomething() throw SomeException { try { doIt(); doOther(); doMore(); doAgain(); } catch(Exception e) { . . . } }
  • 27.
    Anti Patterns try { doIt(); }catch(Exception e) { if(e instanceof SomeExceptoin) { . . . } else if(e instanceof OtherExceptoin) { . . . } else if(e instanceof AnotherExceptoin) { . . . } } // 예외 마다 처리할 방법이 다르다면 편한게 아니다.
  • 28.
  • 29.
    예외 클래스 계층 Throwable ErrorException RuntimeException NullPointerException IllegalArgumentException 일반 예외 클래스 발생했다면 이미 처리할 수 있는 상황이 아니다. 따로 catch하지 않 아도 컴파일 된다.
  • 30.
    RuntimeException? RuntimeException은 따로 catch하지않아도 컴파일된다. 그래서 별로 관심을 두지 않는다. 그런데 RuntimeException이 발생했다는 것 자체는 버그 가 있다는 것을 의미한다. 예 : NullPointerException. 요놈이 발생했다는 것은 null 체크를 제 대로 하지 않은 경우이다. 버그이다. 버그는 발생할 수도 있다. 잡으면 그만이다. 하지만 로그 가 없으면 잡기 힘들다. 외부 인터페이스에서 RuntimeException을 잡아서 로그에 남기자.  여기서 말하는 인터페이스는 외부에서 호출하는 경계를 의미한다. 외부 시스템 시스템, 컴퍼넌트, 패키지
  • 31.
    GUIDE : 하나의try 블록 은 적당한 크기를 유지 하라.
  • 32.
    try – catch블럭이 너무 크다면 catch한 예외를 어디서 던졌는지 눈으로 안 보인다. 혹시 2군데에서 같은 예외를 던진 경우 처리 방법이 틀릴 수도 있다. 유지보수 혹은 코드 파악이 어려워 진다. 하나의 try – catch 블록이 30 line을 넘어가지 않게 하 라. 만약 로직상 어쩔 수 없다면? -> 아마도 중첩된 loop가 있는 경우 일 것이다. 예외와 별개로, 새로운 메소드로 뽑는 리팩터 링을 고려해 보라.
  • 33.
    Anti Patterns public voiddoSomething() throw SomeException { try { doIt(); doOther(); doMore(); doAgain(); } catch(SomeException e) { . . . } catch(OtherException e) { . . . } catch(AnotherException e) { . . . } catch(MoreException e) { . . . } }
  • 34.
    던질 때의 가이드. 메시지충실. 잡을 곳을 기준으로 예외 객 체 선택. 메시지를 파싱하게 하지 말 자. 추상예외로 던지지 말자.
  • 35.
    GUIDE : 예외를던질 때 는 반드시 message를 설정한다. 충실하게
  • 36.
    메시지를 충실하게. 예외를 왜던질까? 바라던 행위를 스스로 할 수 없기 때문이다. 밖의 누군가가 처리하라고 던진다. 던진 예외를 잡아서 처리할 수 있을 만 큼 충분한 정보를 제공해야 한다. 정보가 충분하지 않으면 예외를 잡아도 처 리할 수 없다.
  • 37.
    메시지를 꼭 왜? 메시지가자세하지 않아도 처리할 수는 있다. 사용자에게 알려주기. 기본값 사용. 재시도. 그러나 적지 않은 경우 분석할 필요가 있다. 이 때 로그에 가장 좋은 내용은 예외의 메시지 이다 . 정말로 필요가 없을 수도 있다. 그러나 습관을 위해서라도 설정하라. 대부분인 필요한 경우를 위하여
  • 38.
    메시지를 사칭한 메시지예 예외 자체만으로도 다음과 같은 정보를 얻을 수 있다. Exception 클래스 이름 발생한 클래스 이름과 메소드 이름 호출한 stack 다음과 같이 던져진 예외는 무슨 정보를 제공하나? runtime이 아닌경우가 있을까? 컴파일 타임을 말하나? 예외라고 말할 것 없다. 이미 예외인 것 안다. occurred라고? 이미 발생한 것 알고 있다. 더군다나 예외 클래스에서도 정보를 얻지 못한다. if(…) { throw new Exception(“runtime exception occurred”); }
  • 39.
    메시지에 따른 유지보수의차이 메시지가 부실하면 뭐가 안 된다는 보고를 받고 코드 들여다 보면서 상황을 짐작만 하고 그런 짐작을 확인하기 위한 로그 코드를 추가하고 시스템에 배치하고 혹시 짐작이 맞으면 버그 패치하고 시스템에 배치하고 버그 패치가 확인되면 로그 남기는 코딩을 삭제 하여 다시 올리고 짐작 틀리면 계속 반복하고 메시지가 충실하다면. 그리고 로그 남김이 충실하다면 로그 파일로 상황을 파악하고 재현하고 버그 패치하고 시스템에 배치하고 버그 패치만 확인하고.
  • 40.
    충실한 메시지 그 예외만으로도상황을 파악할 수 있게 하는 메시지 시스템을 개선할 수 있을 만큼 충분한 정보를 제공해야 한다. 단지 뭐가 실패했다는 정도로는 부족하 다. 상황이 포함되어야 한다.
  • 41.
    Anti Patterns if(name==null) { thrownew 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(); } } // 처리할 방법이 다양하다면 복수의 예외를 던져야 한다. // 그렇지 않을 경우 메시지를 파싱하게 된다.
  • 49.
    GUIDE : java.lang의추상 적인 예외로 던지지 말 자.
  • 50.
    추상적인 예외? Error, Exception,Throwable, RuntimeException과 같 은 추상적인 클래스로 예외를 던지지 말자. 외부 라이브러리를 가져와서 그 모양새를 봤더니. 타 예외의 경우를 먹은 경우다. 상황에 따른 처리를 하려면 별도의 처리가 불가피하다. 예외 클래스는 그 이름만으로도 정보를 제공한다. 그런 데 그 정보제공자체를 포기하는 것이다. public void someMethod() throws Exception;
  • 51.
    Anti Patterns public voiddoSomething() throws Exception;
  • 52.
    GUIDE : 메시지를파싱하 게 하지 말자.
  • 53.
    메시지 파싱? 메시지는 예외를처리할 수 있을 만큼 상세하여야 한 다. 그러나 String 메시지를 파싱하여 정보를 추출하게 하 지 말자. 메시지 파싱이 필요한 경우? : 메시지의 내용에 따라 처리할 로직이 달라야 하는 경우 만약 이러한 경우가 발생한다면 예외 클래스에 이러한 정보를 담을 속성을 추가하라. (ex : code) 혹은 별개의 예외로 던져라.
  • 54.
    Anti Patterns if(isRecordExist(record)) { thrownew 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) { Stringmessage = null; if(language.equals(“korean”)) { message = “소켓 읽기에 실패했습니다.” } else if(language.equals(“english”)) { message = “reading socket failed.”; } throw new SomeException(message); } // 던지는 예외의 메시지를 UI에서 직접 보여주어서는 않된다.
  • 58.
    로그 관련 예외가 처리되었으면반드시 로 그를 남겨라. 정보를 누락하지 말고 로그로 남겨라.
  • 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();
  • 62.
    GUIDE : 예외의로그출력 은 정해진 곳에서만 한 다.
  • 63.
    로그 출력 위치 예외를처리한 곳 아무 곳에서나 남기면 로그가 중복된다. 외부와의 인터페이스 이곳에서조차 안 남기면 로그가 남을 지에 대하여 보장하지 못한다.
  • 64.
    Anti Patterns catch(SomeEception e){ logger.debug(e); throw new OtherException(“blar.”, e); }
  • 65.
    로그 레벨의 의미 •DEBUG – 말 그대로 디버깅을 위한 목적이다. – 아직도 DEBUG로 로그를 출력할 필요가 있다면 아직 안정화가 안된 것이다. • INFO – 시스템 동작에 대한 정보를 제공한다. • WARN – 현재 운영에는 문제가 없지만, 문제가 될 수 있는 사항 • ERROR – 시스템 혹은 기타 오류로 운영에 문제가 있는 사항. – 예외를 잡아서 정상처리한 경우. • FATAL – 시스템 운영이 불가능한 경우. – 예측되지 못한 예외를 잡아서 정상처리 못한 경우.
  • 66.
    로그 레벨 선택방법 버그나 시스템 문제는 아니고, 단지 운영자에게 정보를 제공 INFO 지금은 로그를 남겨두지만, 좀 그렇다 싶으면 DEBUG 디버깅을 위해서 잠시 속살을 보여주는 것이다. 예측되는 예외가 발생하여 알려 주어야 한다 싶으면 WARN 예측되지 않은 예외가 발생하고 정상처리되면 ERROR, 그렇지 않으면 FATAL
  • 67.
    로그 레벨의 예 •DEBUG – 예: 입력받은 값 – 예 : DB에서 읽어온 값. • INFO – 예 : 시스템 구동 시간, 읽은 설정파일 위치, 캐싱 리프레쉬 사실. • WARN – 예 : 파일 시스템이 10% 남았다. – 예 : 설정 값을 읽었지만 사용되지 않는다. • ERROR – 예 : 설정값이 없어서 default 값을 사용했다. – 예 : DB 접속이 끊겨서 다시 재접속하였다. • FATAL – 설정된 컴퍼넌트를 로딩하지 못했다. – 예 : DB 접속이 되지 않는다.
  • 68.
    다시 강조 예외와 정보를먹지 말자. 예외 메시지를 충실히.
  • 69.
    MORE 예외와 로그도 프로젝트차원으 로 정책이 마련되어야 한다. 설계 시 입력, 출력과 더불어 예 외도 포함되어야 한다.