* 이 글은 이번 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는 책만 봤던 수준이고 짜본 프로그램도 학교 프로젝트로 몇개 짜보고 거의 없는 터라, 내용이 많이 부실한 것 같습니다. 부족한 내용이 있다면 추가 해주시면 감사하겠습니다!

크리에이티브 커먼즈 라이센스
Creative Commons License

Posted by 장현준

2007/11/14 01:07 2007/11/14 01:07
, , , ,
Response
No Trackback , No Comment
RSS :
http://b4you.net/blog/rss/response/152

API 디자인 스터디 그룹 시작

"API 디자인은 한번의 기회만 있다"

어제 참여했던 API design study group에서 들은 말이다. 오늘은 스터디 그룹에서 공부한 내용을 적어보기로 하겠다.

그동안 개발을 하며 추상적으로 생각해 왔던, Windows API나 MFC, Java 를 이용하면서

"왜 이런 구조/코드를 사용했을까?"
"c에서 제공하는 api 중 memcpy와 memset등 첫번째 인자로 dst가 오는것은, 우연일까"

라는 생각을 해본적이 있는가?

API design study group는 이러한 질문에 대한 해답을 찾고자 시작된 study group이다.


흔히 API라고 한다면 윈도우 개발자는 MSDN에 자세히 나와있는 Windows API를 생각할 것이고, 리눅스 개발자라면 man페이지와 같은 곳에 자세히 나와있는 c library + linux kernel API를 생각할 것이고, JSP 개발자라면 JDK 문서에 언급된 Java API를 생각 할 것이다.

그런데 여기서 말하는 API는 위에서 설명한 API 같이 광범위한 API 뿐만이 아닌 외부에 표출되는 API도 포함이 되는데 예를 들면 옆의 사람에게,

"이러한 인자들을 주면 이런식으로 동작하는 함수를 만들어줘"

라고 하여 옆 사람이 a()라는 함수를 제작하였다고 해 보자.
a()를 다른 사람이 이용한다면 해당 함수는 API가 될 수 있다는 의미이며, 이 때부터 a()를 제작한 사람의 긴장(^^;)은 시작된다.

보통 API가 제작되는 순서는

1. 요구사항 수집 (무엇때문에 API를 사용하려고 하나?)
2. 명세
3. feedback
4. 살을 붙이면서 예제코드 작성

과 같은 과정을 거치는데, 이 때 요구사항 수집을 할 때에는 실제 API가 어떻게 사용되어야 하는지를 알아야 한다.
지금까지 함수 쯤이야 대충 만들면 되지 (물론 발가락으로 만드는 대충이라는 의미는 아니다. 이런걸 고려 하지 않았다는 말이다) 라고 생각해왔는데 생각을 해보니 (그렇다면 평소에 생각을 안한것이란 말인가? 그건 또 아니다) 맞는 말이다.
API는 기능 구현도 우선시 되어야 하지만 사용자(=개발자)의 편의성도 있어야 한다.
생각하면 당연하지 않은가?
하지만, 당장 지금까지 자신이 짰던 API들을 보라. 사용하기가 쉬운가?

몇년 전에 많이 하던 wrapper class 만드는 작업을 하면서 어떻게 클래스를 만들어야 나중에 사용하기가 쉬울까? 라는 생각을 한 적이 있다.
이런건 학교에서는 절대 가르쳐 주지 않는 내용이기 때문에 자신이 스스로 터득해 나가야 된다고 생각을 하며 "나름대로" 공부를 했는데..
처음에는 클래스를 생성할 때 생성자로 데이터를 준 뒤 객체를 초기화 하는 방식의 API를 즐겨 사용하였다.
그 다음에는 set/get을 주로 사용하였고, 이제는 두가지를 병행해서 사용한다.
어느 패턴이 좋은지는 상황마다 다르지만, 객체를 생성할 때 같이 초기화 하는 방법은 Java에서는 좋은 방법일 지 모르지만, C++에서는 불편한 방법이었기 때문에 set/get를 주로 사용하였다.
좀 더 구체적으로 설명하자면 Java에서는 null 상태의 레퍼런스 변수만 만들어 놓고 나중에 new 하면 되기 때문에 문제가 될 께 없다지만 C++에서는 변수의 생성과 함께 객체가 초기화 되어 버리니, 멤버 변수로 객체를 선언하면 문제가 발생할 수 있다. C++에서 has-a 관계를 만드려고 하는데.. 생성자에서 초기화 하려면 이건 포함되어 있는 클래스에 붙어서 동작하는 객체같이 보여지게 되므로, 생성자로 초기화 하는 방법을 쓰는게 애매하다는거다. 포인터 변수를 이용하면 Java를 따라할 수 있지만 포인터 관리가 문제가 된다.


이럴 때 어떤 방식으로 API나 class를 디자인 하는게 좋을까? 라는 생각을 갖게 되는데, 스터디 그룹에서 이러한 상황에서 사용할 수 있는 원칙을 제시하였다.

1. 3개의 vender에 맞게 먼저 제작하여 본다. (3의 법칙)
2. API는 한가지 일을 하게 한다.
3. 기능은 최대한으로 하지만 확실하지 않는 기능은 추가하지 않는다. (중요!)
4. Conceptual weight를 줄인다.
 - Conceptual weight: 개발자가 API를 이용하기 위해 새로 만들어야 하는 개념. class나 function의 개수와는 별개이다.
5. 구현 세부사항을 알리지 않으며 기능대비 복잡도를 유지한다. Java에서 hash는 내용이 spec에 나와 있어서 알고리즘을 바꿀 수 없다. 다시 말해, 추 후 성능이 좋은 hash 알고리즘이 나오더라도 바꿀 수 없다.
6. Wire format? (serialize 할 때)
7. Exception 처리는 API 내부에서 모두 처리해야 한다. API가 수행될 때 외부로 예외가 발생하면 안된다.
8. add/remove, get/set, register/unregister와 같이 대칭을 이루어야 하며, 대칭이 되지 않았다면 문서화 해야 한다.
9. 언어마다의 규칙이 있는데, 이것이 꼬이지 않게 한다.

이 중 3번은 굉장히 중요한 사실인데, API를 제작하고 난 뒤 기능의 추가는 가능하지만 기존에 제작된 기능은 불가능 하기 때문이다.
실제로 Java를 보면 많은 method가 deprecated 된 것을 볼 수 있는데, 처음 설계를 잘못했기(했다고 하기에도 뭐하지만) 때문이다.

지금까지 API라고 생각하면 그냥 함수 만들듯 만들면 되는게 아닌가? 라고 생각을 하였지만,
생각해보면 너무나도 당연한 3번, 4번을 생각하지 못하며 API를 디자인 하고 있었다는 게
자칫 큰 실수를 일으켰을지도 모른다는 생각을 하였다.

앞으로 개발을 많이 하게 되겠지만, 내가 제작하게 될 프로그램들을 위해 API 디자인은 꼭 공부해놔야 되는 (디자인 패턴같이) 학문(?) 이라는 생각이 든다.

P.S. 많은 내용을 배울 수 있는 기회를 만들어 주신 서광열(http://skyul.tistory.com/) 님께 감사의 말씀을 드립니다~

P.S. 첫 study 모임은 2007/09/18일 모임이었지만, 프로젝트 마무리 때문에 포스팅이 많이 늦게 되었습니다(__) 정신없이 쓴 글이라 두서없더라도 이해해 주시면 감사드리겠습니다.

크리에이티브 커먼즈 라이센스
Creative Commons License

Posted by 장현준

2007/10/29 16:46 2007/10/29 16:46
Response
No Trackback , 2 Comments
RSS :
http://b4you.net/blog/rss/response/146


블로그 이미지

빗소리를 먹는 사람.

- 장현준

Notices

Archives

Authors

  1. 장현준

Recent Trackbacks

  1. 듀얼클러치의 생각 rsvin28's me2DAY 2009

Calendar

«   2012/02   »
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29      

Site Stats

Total hits:
158026
Today:
96
Yesterday:
228