Java exception handling best practice

Tags:

Best Practices for Exception Handling

전 이글의 내용에 대체로 agree하는 편입니다. (2003년에 쓰여졌군요 ^^)

한가지 예외 처리 관련한 글에서는 늘 동의하기 어려운 점이 있는데, never catch all exceptions 와 같은 best practice입니다. 세부적인 레벨에서 예외를 다 일일히 처리할 필요가 있다면 하는거지만, 어쨌든 main에서는 try { … } catch(Exception e) { … } 로 ‘알 수 없는 에러입니다. 다시 시도해보세요.’ 정도 대화창은 띄워줘야하지 않을까요. 그렇다면 main에서는 모든 exception을 잡아야 할 수 밖에 없을텐데, 저렇게 단정적으로 이야기하는게 의아합니다.

Comments

10 responses to “Java exception handling best practice”

  1. iwongu Avatar

    좋은 글이네요.

    근데 main까지 올라오는 exception들 중에 다시 시도했을 때 의미가 있는 것도 있고 없는 것도 있지 않을까요?
    그럼 결국 main에서도 catch all은 사용할 수 없을 것 같습니다. 죽기 전에 log찍고 다시 rethrow하는 정도?

  2. mkseo Avatar
    mkseo

    그렇겠죠. 다시 log 찍고 rethrow 하는 정도.

    저 글을 쓸때는 클라이언트 pc에서 돌아가는 프로그램을 생각하고 적었습니다.

    클라이언트 프로그램이 Abnormal Program Termination: Indexing Out of Bounds 류의 불친절한 다이얼로그 창 하나 띄우고 죽는 경우를 몇번 봤거든요. 그러지 말고 main에서는 다 catch all 해주고 ‘에러가 났습니다. 다시하세요.’ 라고 한글로 적어줘야하지 않는가 생각했습니다.

    그리고 만약 서버 프로그램일 경우엔 프로그램이 계속 동작하려면 예외를 다 잡아야 하지 않을까요.

    만약 콘솔 작동하는 클라이언트 프로그램이라면 에러 뱉고 죽어도 괜찮다고 생각합니다만요.

  3. iwongu Avatar

    만약 정말 프로그램을 exception 관련해서 완벽하게 작성했다면, 즉, 해당 exception이 처리되어야 할 곳에서 모두 처리가 되었다면, main 함수까지 올라오는 exception들은 어떤 것들이 있을지 생각해봐야 할 것 같아요.

    제 생각엔 resource 부족으로 인한 에러나 자바라면 프로그래밍 에러로 인한 RuntimeException들 정도일 것 같은데 거의 retry가 불가능한 상황일 것 같습니다.

    예를 들면 main에서 주로 잡힐 exception들은 OutOfMemory나, AssertionError같은 것들일 것 같은데 retry가 의미가 있을까요?

  4. mkseo Avatar
    mkseo

    iwongu님은 저보다 더 자바스러운 각도에서 보시는거 같습니다. 물론 하신말씀은 말씀하신 시각에서 볼 때 구구절절하게 다 맞습니다.

    그런데 저는 아직까지는 메인 loop에서 exception을 잡고 친절하게 ‘이러이러하게 해서 잘못되었다’는 다이얼로그를 띄워주도록 해야지 어떤 client app이 코드의 이곳저곳에서 모두 dialog를 호출하게 하는 것은 옳지 않다고 생각합니다. 결국 main loop은 모든 exception을 잡아야한다는 생각입니다.

    예를들어 보죠. db connection을 하는 클래스 Foo가 있을때, Foo가 컨넥션에 실패했을다면 Foo는 한번 더 connection을 retry할 수 있을 것입니다. 이 경우가 iwongu님이 말씀하신대로 exception이 처리되어야할 곳에서 처리된 경우입니다. 그런데 retry마저 실패한다면 Foo는 별 수 없이 throw RuntimeException을 해야만 할 것입니다. 그러면 메인 loop은 이것을 catch하여 다이얼로그를 띄우면서 ‘db접속이 실패했습니다’라고 알려줘야합니다. db접속을 관리하는 Foo가 Foo 안에서 다이얼로그를 띄우는 코드를 갖게되면 Foo는 불필요한 ui 쪽으로의 dependency를 갖게 되기 때문이고, 다이얼로그를 바꾸려해도 골치아픈 일이 되버릴 수 있기 때문입니다. 이런 예가 실제로는 checked exception인 SQLException이면서 종국에는 handlng은 메인에서 되는 경우입니다.

    이런 개념하에서 hibernate같은 프레임웍은 아예 exception을 클라이언트가 대충 처리하지 못하는 fatal 한 것으로보고 HibernateException을 아예 RuntimeException에서 상속해버렸습니다. 다시 말해 HibernateException이 언제 발생하는지를 프레임워크의 사용자로부터 숨겨버렸고, 결국 exception이 발생할 수 있는 상황에서 컴파일러가 catch를하거나 throws를 명시하지 않는다고 해도 에러를 내지 않게 만들어버렸습니다.

    결국 hibernate 사용자들은 그들의 최종 방어선 – 즉 메인 – 에서 모든 exception을 catch할 수 밖에 없게 된 것입니다.

    exception을 사방에서 처리하는 것이 아니라, 정작은 메인이라는 특정 장소에서 최종적으로 잡아야한다는 본격적인 아이디어 실행은 C#에서 시작했다고 생각합니다. C#에서는 throws문을 명시하지 않아도 exception을 throw할 수 있게 해버렸고, 따라서 api 사용자들은 각 클래스에서 ‘내가 모든 exception을 잡고 있는가’에 대해서 생각하게 하는 대신, ‘내가 handle할 수 있는 exception은 다 handle했는가’로 사고하게 만들었습니다. 즉, 어떤 exception은 (예를들어 심지어 file not found exception마저도) handling되지 않은 채로 메인으로 넘어갈 수 있는 것입니다.

    결과적으로 C#은 메인 루프에서의 exception handling을 필연적이게 만들었고 저는 사방 팔방에서 별 쓸모도 없어보이는 exception hierarchy를 만들고 catch를 때에따라 대여섯개까지 나열해야하는 자바의 접근 방법보다는 C#의 접근 방법이 옳다고 생각하고 있습니다.

    이것은 심지어 python 마저도 그렇다고 생각하는데, 왜냐하면 python은 프로그래밍시에 내가 모든 exception을 잡고 있는지 프로그래밍 언어적으로 확실하게 알 수 있는 방법이 없기 때문입니다. 이 클래스 안에서 끝장을 보겠다고 덤비더라도 반드시 처리되지 않은 예외하나가 main으로 날라가게됩니다. 결국 파이썬에서도 최종 방어선 main에서는 깜빡하고 빼머은 index out of bound error마저도 catch 해야하고, main에서 사용자에게 다이얼로그를 띄우는 코드를 넣어야합니다.

    C# Exception handling에 대한 글 링크 하나 남깁니다. http://www.artima.com/intv/handcuffs.html

    만약 저에게 자바 exception handling을 맡기신다면 저는 각 클래스가 catch후 retry하게 맡길 것이고, 그것마저 실패한다면 unchecked exception을 던지게 만들것입니다. 그리고 최종 장소인 main에서 모두 다 잡은 뒤 다이얼로그를 띄워 ‘ooo인 이유로 실패했습니다. 잠시후 재시도하세요’라고 보여주겠습니다. 물론 main은 그 예외가 나도 죽으면 안되겠죠.

    어쩌면 iwongu님이나 저나 같은 얘기를 조금 다른 각도에서 하고 있는 것 같기도합니다.

  5. iwongu Avatar

    넵… 저도 GUI application에서는 그런 식으로 메시지를 표시해주는 것이 친절한 방법이라고 생각합니다.

    제 댓글에선 그걸 그냥 일종의 logging처럼 생각했었구요. -_-;

    근데… “내가 모든 exception을 잡고 있는가”라고 생각하는 사람이 있다면 어떤 언어를 사용하고 있던 이미 exception에 대한 개념이 잘 못 된것 같습니다.

    “내가 이 exception을 처리해야 하나?”, “내가 처리해야 할 exception을 제대로 처리하고 있는가?”, “처리할 수 없다면 다음 스택엔 어떤 방식으로 알릴것인가?” 같은 질문들이 중요한 것 같습니다.

    링크에서 얘기한 versioning문제는 정말 좀 문제가 될 것 같습니다. 그런데 scalability문제는 어차피 API의 레벨에 따라 그에 맞는 exception들로 변경이 되면서 압축이 되면 큰 문제는 없을 것 같은데…

  6. iwongu Avatar

    그러고 보니 원글에 never catch all exceptions라는 얘긴 없는데요? “Do not catch top-level exceptions”란 얘기만…

  7. mkseo Avatar
    mkseo

    아.. 저는 top level exception으로 모든 exception을 잡는 경우를 생각하고 있었어요.

  8. 이복연 Avatar

    (하이 민구!!)
    > “메인이라는 특정 장소에서 최종적으로 잡아야한다는 본격적인 아이디어 실행은 C#에서 시작했다고 생각합니다. C#에서는 throws문을 명시하지 않아도 exception을 throw할 수 있게 해버렸고”
    요 부분은.. C# 에서 시작했다기 보다.. Java 를 제외한 모든 언어에서 그렇다고 알고 있습니다.C++ 이나 Objective-C 도 그렇죠.

    Java 역시 예외를 두 가지 케이스로 나누어서, 명시하지 않으면 컴파일 에러가 나는 류의 예외가 있고, 아무때나 던질 수 있는 예외가 있습니다. Java 는 꼭 잡아야할 예외가 무엇인지 개발자가 신중히 생각하고, 혹시 잊어버리고 처리하지 않은 예외가 없도록 좀 더 강제한 케이스이죠. 물론 이를 플랫폼 전반적으로 일관성 있게 적용하고 있다고는 보이지 않고, 대부분의 개발자들이 예외에 대해 그렇게까지 열심히 고민하지 않기 때문에 원래 취지를 잘 이어가지 못하고 있는 면도 많습니다.

    민구님이 언급하신 ‘사방팔방에 별 쓸모도 없는 exception hierarchy 를 만들고’ 와 같은 상황은 원래 벌어져서는 안되는 것이죠. 원 의도를 제대로 살렸다면 쓸모 있는 것들만 만들어졌어야 하고, API 사용자 입장에서 별 쓸모 없는 low-level exception 들은 의미 있는 예외로 감싸지거나 하는 등의 손을 거쳤어야 하죠.

    그리고 top-level exception 으로 모든 exception 을 잡는다고 생각하셨다 해도.. main loop 이 아닌 다른 곳에서 마구 top-level exception 을 처리해도 좋다고 생각하시진 않을 겁니다. 인용하신 아티클에서의 ‘Do not catch top-level exceptions’ 는 일반적으로 아무곳에서나 top-level exception 으로 몰아서 처리하지 말라는 의미로 받아들이시는 것이 맞습니다.

  9. mkseo Avatar
    mkseo

    오랜만에 들러 남긴 덧글 감사합니다 복연군. 아기자기한 홈페이지도 오랜만에 재발견하고(!) RSS도 등록하였으니 종종 좋은 내용으로 나도 덧글 남기기 열심히 하도록 할께.

    요즘들어 생각인데, 내 블로그 벗들은 하나 둘 나를 떠나 어디서 무엇으로 포스팅하고 계신지들 모르겠다. 다들 바쁘신가.

Leave a Reply

Your email address will not be published. Required fields are marked *