* 이 글은 이번 API 디자인 스터디 그룹에서 Java에서 사용되는 Exception에 대해 황상철(http://moai.tistory.com/)님께서 발표를 해 주신 내용을 참고하여 제작되었습니다.
먼저 교과서에 나올법한 이야기를 먼저 해보자.
Java에는 Checked exception과 Unchecked exception 두가지가 존재한다.
Checked exception 이라 함은, 개발자가 반드시 try/catch를 통해 error를 handling 해야 하는 exception을 뜻하며, Unchecked exception 이라 함은 개발자가 "오류가 발생할 수 있는" 가능성을 판단하여 handling 해야 하는 exception을 뜻한다.
Java에서 exception이 issue가 되는 이유는, exception을 compiler 차원에서 처리하는 최초의 언어이기 때문이라고 한다. Java가 나오기 전에 많이 사용된 C++에서 이미 exception 개념을 제시하였으며, Java와 비슷하게 try/catch를 통해 exception을 handling 할 수 있다. (하지만 Java와 C++에서 exception이 처리되는 방식은 분명히 다르며 앞에서 언급하였듯이 compiler 자체에서 exception 처리를 강요하였기 때문에 Java의 exception이 C++의 exception보다 유명 할지도 모르겠다.)
Java에서 사용되는 exception은 exception이 언제, 어디서, 왜 일어났는지의 정보를 포함하고 있다.(각각의 exception은 java.lang.Exception을 상속) 이를 runtime이 아닌 compile time에서 try/catch 하여 발생 가능한 오류를 잡아보자는게 Checked exception인데... 그렇다면 Checked exception이 왜 문제가 되는것일까?
오늘 세미나 주제에 관심이 있었던 이유도, Java를 사용하면서 왜 이 두가지를 나누어 놓았을까? 라는 의문 때문이었다.
일반적으로 c에서는 return value를 통하여 오류를 판단하는 구조를 가지고 있는데, Java의 exception과 비교하면 return value는 계층적인 exception handling를 하지 못하기 때문에 단점이 될 수도 있다. 하지만, API가 정상적으로 동작하거나 비정상적인 동작을 보였을 때 return false; 를 하는것과 exception을 발생하는 것은 무엇이 다를까? 만약 다른게 없다면 exception model을 사용한 이유나 exception을 발생해야 되는 경우는 언제일까?
Java에서 현재 사용되고 있는 exception model을 채택한 이유는 runtime때 발생하는 exception을 최소화 하고 error-free software를 개발하고자 하는 목적이었다. 목적은 좋지 않은가? 이러한 사항은 어디서 발생하는지 모르는 버그때문에 몇일 밤을 꼬박 샌 개발자들한테 분명히 달콤한 유혹이었을 것이다. runtime exception을 compile time에서 처리하다니!
compile time에서 exception을 처리함으로써 개발자들은 한시름 놓는가 싶었지만.. Java의 API에서 Checked exception을 사용하는 바람에... 게으른 개발자들은 단순히 try/catch를 넣어놓고 exception 처리를 전혀 하지 않는 일이 발생하고야 말았다. (항상 게으른 사람들 때문에 문제가 발생한다)
이게 무엇이 문제가 되냐고? 처리하지도 않을 오류를 왜 try/catch 하는건지? 무엇인가 잘못 설계 된것이 아닐까...?
개인적으로 Java를 배울때 catch에서 받은 exception은 단순히 printStackTrace()를 하여 표시하는 정도로 사용하였는데,
이정도로 사용한다면... 도대체 뭐하러 코드가 길어지게 catch하느냐?가 의문이었다. 귀찮게 말이다. 기본적으로 exception을 처리하지 않더라도 저런 오류 메시지는 나온다. 실제로 error handling해야 하는 경우는 별로 없다는 말이다.
C++에서는 exception handling을 위해 __except를 사용하는 경우가 있다. return value만 살펴보면 되지 왜 __except를 사용하냐고?
가끔은.. 아주 가끔은 multi-thread 상에서 callback을 호출하여 수행중인 동시에 해당 callback에서 참조하고 있는 값을 free 할 경우에 오류가 발생 한다.
이는 critical section이나 mutex, semaphore를 이용하여 어느정도 제어할 수 있는 사항이기도 하지만, 정말 가끔 구조가 잘못되었을 때 또는 반드시 non-blocking 상태에서 동작해야 하는 프로그램인데도 불구하고 해당 데이터를 처리하는데 시간이 오래걸리는 등 정상적인 방법으로는 잡을 수 없는 exception을 처리해야 하는 경우가 있다. 이럴 때 __except를 이용하게 된다.
물론 이것은 구조상 설계가 잘못되었을 때 나타날 수 있는 상황이며 설계를 잘 하면 이러한 exception은 피할 수 있는 경우가 대부분이다. 이런 경우가 아니면 할당되지 않은 메모리에 접근하는 것을 확인/방지 하기 위해 사용하는 경우도 있겠지만... try/catch __except가 반드시 필요한 경우는 거의 없다고 봐도 무방하다. (내가 아는 짧은 지식에 한해서는) 가끔 사용하면 exception 부분이 굉장히 간단해진다는 장점이 있는 정도랄까?
(C++에서는 flow control을 위하여 __except나 try를 이용하는 경우도 있는데, Java와 같이 오류가 발생하였을 때 호출되는 것이 아니라, 명령 수행 도중 break; 기능으로 이용하는 경우도 있다.)
그렇다면 이번엔 Java에서 살펴보자. Java는 앞에서 말한 C++의 경우와는 다르게 메모리에 관련하여 저런 문제가 없다. 그럼 왜 exception을 사용하거나 발생하는 것일까?
exception 객체를 사용하면, exception을 계층적으로 표현할 수 있도록 설계를 할 수 있기 때문이라는 장점이 존재한다. (물론 복잡해지지만) 또한, Windows에서 제공하는 Error code(GetLastError()로 얻을 수 있는 수치)에 비해 좀 더 나은 exception 설명이 가능하다.
하.지.만. 현재 Java에서 사용된 exception model에는 찬성이지만, 사용하는 API 디자인이 잘못 되었다고 생각한다. Checked exception을 너무 남용하였다는 생각이 들지 않는가?
아마도 Java 설계한 사람들이 조금만 더 오버했으면 배열을 사용하는 곳에는 모두 ArrayIndexOutOfBoundsException 를 try/catch를 했을지도 모르는 일이다;;(끔찍하다)
아주 필요한 곳에만 Checked를 이용하고 나머지는 Unchecked로 되어야 한다고 생각한다. IO에 관련하여 "자주 발생될 수 있는" exception에 대해서 Checked가 되어야 한다는 것이다. (당연한가?^^;)
"개인적으로 생각하기에" Checked exception이 사용되어야 하는 경우는 초보 개발자에게 필요하다고 생각한다. 개발을 많이 한 사람이라면 이 부분에서는 어느정도 runtime-error가 발생하는 것을 알 수 있을 것이라 생각하기 때문인데, 이 부분은 Checked exception의 부작용에 비하면 더 낫다고 생각한다. 이것은 우리가 C에서 fopen()을 이용하여 파일을 열 때 return value가 NULL인지를 확인하여 정상적으로 명령이 수행되는지를 판단하는 루틴을 추가한다고 생각하면 될 것 같다.
이에 따른 trade-off가 발생할 지 모르지만.. Java를 하면서 느꼈던 바에 의하면 Checked 보다는 Unchecked가 더 자연스럽다고 생각한다. 왜? 가끔은 아주 당연한 가정에 의해 제작되기 때문에 exception을 검사하지 않아도 됨에도 불구하고 try/catch를 넣어야 하는 경우가 있기 때문이다.
C/C++에서는 warning 개념으로, 개발자가 실수할 수 있는 부분을 알려준다.(일반적으로 warning level 4로 놓으면 많은 warning이 발생한다) Java의 Checked exception과 warning을 비교한다는 것 자체가 무리인 것 같지만, warning 개념을 좀 더 발전시키기만 하였어도 이렇게 불편하진 않았을 것 같다.
Checked exception에 대한 이야기는 여기까지로 하기로 하고, 다른 주제로 넘어가 보자.
"Fault Barrier Pattern". 오늘 세미나에서 이 패턴을 알게 되었다.
예전에 ASP.NET에서 형 변환을 시도할 때 발생하는 (Convert class를 이용할 때) exception을 쉽게 처리하기 위해서 일종의 adapter를 작성하여 사용한 적이 있었는데,
그 class를 제작하게 된 동기가 casting 할 때마다 try/catch를 하기 불편해서였다.
("asdasf1234"와 같은 문자열을 숫자로 변환하면 exception이 발생하기 때문에 try/catch를 했었다.
아마도 그때 C++에서 casting 하듯 형 변환을 하면 exception이 발생하지 않는것으로 기억하는데 더 많은 형을 변환하기 위해 Convert class를 사용한 것 같다.
더 쉬운 방법이 있는지도 모르겠지만, 아직 C#에 대한 내공이 부족하여 여기까지 밖에 생각해내질 못했다.)
지금와서 생각해보면 아마도 앞에서 제작했던 class가 fault barrier 역할을 했었을 것이라는 생각이 드는데 Java에서 적절한(애매하지만) fault barrier를 구축 하여 놓는다면,
즉 Java에서 제공하는 자주 사용되는 기본 class를 adapter pattern을 이용하여 fault barrier를 만들면 사용하기 편리하지 않을까 라는 생각을 했었다.
파일 입출력 시 try/catch를 안하면 좀 더 낫지 않을까? 이렇게 하더라도 exception 객체 생성에 따른 overhead는 줄일 수 없지만, 최소한 개발자들은 편해지지 않을까 라는 생각을 해본다.
P.S. Java는 책만 봤던 수준이고 짜본 프로그램도 학교 프로젝트로 몇개 짜보고 거의 없는 터라, 내용이 많이 부실한 것 같습니다. 부족한 내용이 있다면 추가 해주시면 감사하겠습니다!
Posted by 장현준


