PostIT

[Java]초보자를 위한 예외 처리 이야기 - 퍼옴 본문

Java

[Java]초보자를 위한 예외 처리 이야기 - 퍼옴

shun10114 2016. 11. 28. 08:08

* 예외 처리에 대한 6 가지 이야기...

http://okky.kr/article/362305


 일단 예외에 대한  글을 쓰려고 마음은 먹고 편집기를 열고 나서  리얼타임으로 생각하면서  손가락을 움직거려 본다. 따라서 생동감은 넘치는 글이 될거 같긴한데 오류도 있을 수 있겠고 내 밑천이 그닥 많지 않아서 높은 수준의 글은 되지 못할것이다.  그리고 문법을 말하는 글이 아니며 무엇이 옳고 무엇은 안되~라는 글도 아니다.  이런것도 있고 같이 생각해보자는 글이다. 

 예외는 아시다시피 try ~catch 이다. 예외를 잡고 싶은 부분을 try 로 감싸고 예외를 잡았을 경우 catch 문 안에서 처리해주는 방식이다. 어떤 예외를 잡을지와 어떤식으로 처리 할 지에 대한 고민이 들어가야하는 부분이다.  처리는 catch 안에서 직접 할 수 도 있고 catch 안에서  처리할 책임을 그 함수를 호출한 이전 함수에게 공을 돌릴 수 도 있다. 그때 이전 함수는 그 예외를 처리 할 수도 있고 무시할 수도 있고 다시 그 이전 함수에게 공을 또 다시 돌릴 수도 있다. 어떤 언어는 예외라는게 없기도 하고 어떤 언어는 예외처리를 강제하기도 하며 어떤 언어는 예외를 예외가 발생한 근처에서 처리하길 유도하며 어떤 언어는 예외가 발생한 최대한 먼 지점에서 전지적인 시점에서 처리하게 유도하기도 한다. 또한 '고장나게 납둬라' 라는 전략도 있다. 물론 광고성 멘트이다. 어떻게든 고장난것에 대한 처리는 해야하니깐..

이러한 내용이 많은 책에서나 다들 알고 있는 예외의 매커니즘들인데 여기서 6가지 작은 주제로 간추려 본다.


1. 에러인가 예외인가?

이걸 구분하는 것부터 문제이다. 내가 당신에게 1부터 10까지의 숫자중 하나를 말해주세요 할때 100을 말하면 에러이며 갑자기 누군가 당신의 목을 졸라서 아무 말 안하거나 '으악' 이라는 소리를 듣게 되면 예외인가? 좀 더 SW 스러운 예제를 생각해보면 어떤 메소드가 있다고 하자. 파일을 열어서 그 안의 IPaddress: 이후의 값을 리턴하는 계약이 맺어진 메소드라고 할때 값이 192.168  ~ 이런게 아니라 "길라임" 이라면 이게 에러인가 예외인가?  혹은 파일을 누가 지워서 아예 아무것도 못하면 이게 에러인가? 예외인가?  그 메소드를 호출하는 호출자와 그 메소드의 계약관계에  '파일이 거기 있을 수도 있고 없을 수도 있다' 는 것이 포함되어 있다고 할 수 있을까 없을까? 

보통 전자 즉 길라임이 들어 있으면 에러라고 보고 , 후자라면 즉 파일이 없으면 예외라고 본다. 이 함수에서 나타날 수 있는 모든 예상 가능한것들은 예외라고 보며 그렇지 않은 경우 즉 '길라임' 일지 '순시리' 일지  알 수 없는 것 일때 에러라고 말한다. (혹시나 하는 마음에 추가하는데 문법에러는 이 주제와는 전혀 다른 1차원적인 문제이다.) 

그렇지만 많은 경우 어떤게 계약위반인지 예상가능한지 예상못하는지에 대해선 구분지을수 없다. 또한 이런 말장난에 휘둘리지 말고 본질에 대해 중심을 잡아야한다. 


2. 죽일 것 인가? 살릴 것 인가? 

최근 개발에서 나는 예외에 대해 고민하길 포기해버렸다.  따라서 예외를 단순히 프로그램이 죽지 않게 하기위해 예외처리를 한다. 잘죽는 부분은 이렇다.

* JSON 변환을 하는 부분  
* 소켓 처리부분, 대부분의 io 부분. (외부의 파일이 어떻게 될지 모름)
* 널포인터가 없을거라고 확신하지 못하는 부분 모두 (이건 좀 너무 광범위한데 C++ 로 개발할때  ASSERT 로 일단 도배한다.) 
* 여러 쓰레드가 접근되는 부분   
* 사용자가 주는 값에 대해 신뢰해선 안되는 부분 
* 컬렉션 처리중 먼가 냄새가 나는 부분

뭐 더 많겠지만 요즘 내가 짜는 프로그램은 이런거 같다. 이렇게 써 놓고 보니 예외를 예외로 생각하지 않고 프로그램 시퀀스의 일부분으로 좀 더 오버해서 즉 if 문 처럼 생각하고 쓰는게 아닌가 싶기도 한데 좀 더 고찰해 볼 시간은 없다.

암튼 위의 리스트에서 느껴지듯이 쓰레드,IO 등에서 문제가 생길 소지가 많은데 그래서 웹개발/서버개발에 있어서는 죽이지 않기 위해 예외처리를 하는 경우가 많은거 같다. 일처리 프로세스 과정이 복잡하지 않고 하나의 시퀀스에 대해 완료하지 못하더라도 큰 타격을 받지 않으며 어차피  다음에 시도하면 되는 것은 그냥 생활의 일부분이야 ~~그런 사소한 이유로 프로그램을 죽일 순 없어. 프로그램 죽는것은 내가 죽는것. 이런거다.

근데 그냥 빨리 죽여서 문제를 빨리 찾으려는 응용프로그램도 많으며 예러 발생해선 절대 안되는 프로그램이 있으며 , 예외도 거의 없어야 하는 프로그램도 있다. 공장이나 의료쪽에서 사용되는 응용프로그램들이 그러한데 이런 프로그램에서는 저 위의 예처럼 모든 경우에 대해 예외처리를 하여 자신도 모르게 넘어가면 안된다. 예외가 로그에 남겠지만 그것만으로 부족하다. 개발자에게  문제가 생겼다는것을 즉시 각인 시켜야한다. 프로그램을 그 즉시 죽임으로써 말이다. 그리고 최대한 에러/예외가 나지 발생하지  않도록 설계를 해야하며 오류를 잡아야한다. 사실 이런 프로그램 경우 웹이나 게임처럼 외부와의 잦은 커뮤니케이션이 없고 안정된 하드웨어 등 시스템  때문에  예외가 발생할 가능성은 크게 적다.  


3. 가까운 곳에서 즉시 처리할 것인가? 상위로 위임할 것인가?

A 라는 메소드에서 예외가 발생했다고 하자.  그 예외에 대해 그 스스로 처리할 것인가? 혹은 그 메소드를 호출한 근접메소드가 처리해야하나? 혹은 콜스택의 가장 상위에서 처리해야하나? 예외가 일어난 곳에서 가장 가까운 놈이 처리해야하는게 순리에 맞나? 가장 멀리 즉 전지적 시점에서 처리해야하는게 순리에 맞는것일까?  

예를들어 국내 원자력 발전을 통제하는 프로그램이 있다고 하자. A 발전소의  내일  발전량을  10->100 으로 바꾸는 기능이 있는데 100으로 바꾼후 Apply 버튼을 눌렀다고 했을때   a->b->c->d->d 라는 일들이 일어 난다고 하자.  근데 c 에서 무슨 문제가 생겼다.  c 에서만  처리해야하면 되나? a 를 호출하는 시점으로 거슬러 올라가서 전체적으로 관리해야하나?

문법적으로 보면 모든 개별 하위에서 catch 를 처리 할것인가 아니면 하위는 모두 finally 로 리소스에 대한 종결만 짓고 예외를 상위로 전파해서  상위에서만 각각의 예외에 대해서 구분 처리 하자에 대한 것이다. 각각 모든 메소드들에서 먼가를 처리 한다는것은 너무 복잡해진다는것을 의미하며 현실적으로 불가능하며 개발자에게 피로함만 가중시킨다고 보여 진다. 하지만 예외는 발생된 곳에서 처리하는게 분명하지 않겠나? 하는 입장도 맞을 수 있다. 혼자 개발하는게 아니라면 누가 상위를 개발하며 하위에서 일어난 혹은 일어날 일을 콘트롤 가능할까?


4. 강제적으로 처리 시킬 것인가? 아닌가?  (체크드 / 언체크드) 

이 문제는 예외 처리의 아주 전통적인 시각차가 존재하는 화두이다. 가장 많이 사용되는 언어인 자바와 C# 의 창시자들도 서로 다른 시각을 가지고 있는데  C# 의 창시자 앤더스 하일스버그의 경우 자바의 체크드 예외 (강제적으로 호출한 함수에서  처리하게 만드는) 는 여러모로 문제점을 가지고 있는데 확장성이 그 중 하나로 본다. 즉 라이브러리를 사용하는 개발자의 코드가 라이브러리의 체크드 예외의 변화에 따라서 에러가 생길 여지가 있다는 것이다. 라이브러리에서 B 라는 예외를 추가했는데 사용자 코드에는 그것이 없으니 말이다. 이런 문제는 상속에서 생기는 것과 비슷한데  상위 클래스에 추상메소드를 함부러 넣는게 힘든 이유와 같다. (자바 같은 경우는 가상함수의 상속구현에 대한 강제적인 키워드가 없어서 뒤통수 맞을  소지가 많다.)  하일스버그는 또한 그런 강제적인 예외처리는 개발자에게 예외에 대한 피로감을 주게되어 그냥 모든 예외를 하나의 catch문에 때려박고 로깅한번하고 잊어버리자. 라는 유혹에 빠지게 한다고 말한다.개인적으로 자바의 체크드예외 시스템은 불필요 하다고 본다.다만 예외라는 덕목을 컴파일타임에 강제하여  초보개발자들에게 경각심을 심어 준다는 측면은 좋은거 같기도 하다.

현재 자바에서  RuntimeException ( 널포인트체크,비정상인자,배열오버플로우,시스템)  은 언체크드 예외이고 그 나머지는 SQLException, IOException 등은 체크드예외 즉 강제로 처리해 줘야하는것이다.  이것에 대한 느낌을 말해보면 체크드예외는 그 메소드에서 그런 잘못된 일들이  일어날것이라고 예측되는것들이라는 것이고  언체크드예외는 그렇지 않은것들이다. 즉 미리 예측 불가한것들이란 것이다. 예를들어  숫자만 받기로 되어 있는 메소드에 호출자(사용자) 가 문자를 넣어버리는걸 어떻게 미리 예측하나? (꼬아서 생각하면 그것도 예측해야지 할 수도 있겠다만) 그것은 메소드에서 생겨난 오동작이 아니라 호출자놈이 잘못한거. 다른 예로 파일이나 데이터베이스 입출력을 할 때 발생하는 문제들은 있을거라고 대비하고 코드를 짜야하는건 상식이지만 갑자기 하드가 꽉 차버렸다던가 번개가 쳐서 컴퓨터가 망가진것 까지~ 예측되야되는 범위일까? 아니다. 그래서 그런것은 에러이며 언체크드 예외로 다룰일이다. 상대적으로 자주 일어나지 않는 다는 것이지 처리 하지 않아도 된다는 것은 절대 아니다. 또는  체크드 예외는 강제적이고 언체크드 예외는 해도되도 안해도 되니깐 하지말자? 이렇게 생각하면 안된다, 오히려 그 반대로 생각해야한다. 세상에서 무서운것은 예측하지 못하는 것들이기 때문에~


5. 모두 되돌릴 것인가? 이미 벌어진 일은 무시,포기할 것인가? 

 그냥 살리는게 중요 포인트라면 예외 발생시 그냥 로그정도 출력하고  지나치면 된다. 그 메소드를 호출한 사람은 아마 한번 더 호출해 보겠지. 아니면 그 사람은 망하더라도 다른 모든 사람들에게는 서비스가 진행 될 수도 있고.. 하지만 되돌려야한다는 문제의식을 갖게되는 어플리케이션이라면 정말 거대한 도전이 된다. 트랜잭션,Redo,Undo,커맨드패턴 같은 단어들을  떠올려야 하기때문이다.

자 갤럭시 7 을 생산하는 공장에서 갤럭시 7 에 들어가는 전자기판(pcb)을 검사하는 프로그램이 있다고 해보자.  그 프로그램은 기판을 검사하기 위해 많은 지식이 사용자에 의해 학습되어야 한다. 그 기판에는 어떤 부품들이 있고 어떻게 생겼고 어떤 방식으로 불량이 아닌지를 확인하는등.. 이것을 학습된 검사조건들 이라고 하자.

이 검사조건 중 하나의 값을 30-> 50 으로 올릴때  발생하는  시나리오가 아래와 같이  흘러 간다고 하자.

a. 50을 입력받아서 
b. 50을 포함한 새로운 임시 객체를 만들어서 적용해 나간다.
c. 변경 된 데이터를 많은 뷰에 적용시키고 (즉 화면뷰에도 50으로 바뀌고 , 리스트뷰에도 바뀌고) 
d. 네트워킹을 통해 중앙저장소에도 바꾸어주고 
e. 검사조건을 모아놓은 메모리 자료구조를 수정한다. 
f. 로컬 xml 파일에 새로운 50을 써준다.
g.완료 (버튼 활성화)

이렇게 메소드들이 호출된다고 할때 갑자기  d에서 문제가 생기면 어떻게 될까?  메모리에는 이 어플리케이션이 갖는 최종적인 마지막 값이 50으로 갱신되지 못한 채 예외가 생겨서 30으로 남겨져 있다. 그리고 검사조건들을 저장해놓은 파일도 갱신되지 못하여 나중에 프로그램을 시작할때 30으로 읽어들일 것이다. 하지만 화면뷰와 중앙저장소의 값은 50인데??

먼가 문제가 생겼을때 돌이키는 방법으로는 메멘토패턴과 커맨드패턴을 주로 사용하는데 커맨드 패턴인 경우 undo / redo 를 처리 할때 execute 와 unexecute 함수를 가지고 내부에 이전정보,이후정보를 속성으로 가진 커맨드 객체를 이용한다. 근데 이때 unexecute 에서 처리해 줘야할 것들이 간단치 않다는게 문제이다.  저 전체를 한 묶음으로 묶어서 처리하는 트랜잭션 처리시  데이타베이스처럼 상대적이고 일관되고 한정된 로직에 있는 것도 아니고 메소드의 역할에 따라 다른 행동에 대해 어떻게 트랜잭션 처리를 일관되게 할 수 있을까?  맨붕이다..

6.  리턴으로 처리 또는 예외 개념 도입  

지금까지 예외가 모든 언어의 필수발가결한 요소인듯 글이 작성 되었지만 사실 예외 시스템이 없는 언어도 있으며 대표적으로 C, 예전 파스칼언어이다. 우리는 보통 즉 A 에서 B 라는 메소드(JDBC 를 이용하여 DB에서 데이터를 쿼리해서 결과를 돌려주는) 를 호출 했을때 , SQL예외가 당연히 일어날 것을 예상해서 컴파일하기전에 SQLException 에 대처하는 코드를 추가 해준다. 근데 이미 코딩시점에 db 관련 문제가 생길 수도 있음을 인지하고 있다면 , 그 문제가 생겼을때  return "disconnection fail"  혹은 return -1 이라고 리턴해주는 함수로 만들거나 리턴값 리스트를 조사해보면 되지 굳이 예외라는 키워드등을 배울 필요가 없지 않을까? 사용자가 필요로 하는 데이터는 모두 파라미터 (inout) 을이용해서 처리하고 리턴값은 단지 성공했는지 실패했는지 혹은 어떤식으로 실패했는지를 표시해서 처리하면 되지 않느냐는 말이다. 

 즉 예외 이전의 처리 방식은   (The C++ Programming Language 참고) 

* 프로그램을 종료하거나
* '에러' 를 나타내는 값을 반환하거나
* 정상적인 값을 반환하고 비정상적인 상태로 프로그램을 끝내거나
* '에러' 의 경우에 호출되도록 만들어 둔 함수를 호출한다.

이었는데 이거면 충분하지 않을까 하는 문제이다. 사실 자신이 하는 프로젝트에 따라서 충분할 수도 있다.세상의 모든것이 케이스바이케이스이지 않겠는가. 

예외처리는 저런 정해진 규칙이 모호한 개념보다는 좀 더 명확한 개념을 개발자에게 제공하므로써 좋은 코딩 스타일, 잘 구조화된 훌륭한 제품을 만들도록 유도하는데 도움을 준다. (스택풀기 같은 기술적인 부분은 중요치 않다) 자바의 인터페이스 처럼 말이다. C++ 은 그 비슷한것을 할 수 있긴 하지만 추상클래스를 사용해야한다. 추상클래스를 사용하다보면 상속과 인터페이스를 혼동하기 쉽다. 인터페이스는 상속과 전혀 상관이 없는 주제이며 일종의 프로토콜인데 말이다. (Swift 언어에서는 아예 그런 역할을 하는 키워드가 프로토콜. 스칼라는 trait 이다) 



마무리 

이렇게 예외는 try catch 문법을 공부하는것으로 그치지 않는 아주 어렵고 다양한 스토리가 있다. 기회가 될 때 전체 시스템에 대한 예외 처리 전략에 대해 종이에 스케치해보고  작성도 해본다면 단단한 프로그램을 만드는데 도움이 될 것이다. 물론 그것을 알아주는 사수와 갑을 만나길 바란다. 


- 댓글 참고사항 - 

체크드 예외 부분이 잘 이해되지 않는 분들을 위해 예제를 통해 조금 보충해 보겠습니다.

예를들어 온라인 쇼핑몰의 장바구니 모듈이 내부적으로 상품 정보 서비스를 참조하고, 해당 서비스에서는 유효하지 않은 상품 정보에 대해 체크드 예외를 발생시킨다고 가정하겠습니다. (일반적인 설계는 아니지만 설명을 위한 것이니 양해 부탁드립니다):

public interface ShoppingCart {
    void add(long productId, int count) throws NoSuchProductException;
}

public interface Inventory {
    Product getProductDetails(long productId) throws NoSuchProductException;
}

이 경우 인벤토리의 `getProductDetails`메서드는 체크드 예외를 발생시키기 때문에, 이를 호출하는 쪽에서는 반드시 try-catch로 처리하거나 다시 throw해야합니다.

참고로, 이런 예제에서 `ShoppingCart`의 구현체의 `add` 메서드 내부에서 try-catch를 하는 것은 일반적으로 말하면 좋지 못한 접근입니다. 예외처리의 중요한 원칙 중 하나는 책임지지 않을 바엔 잡지도 말라는 내용이 있습니다.

즉, 예외는 항상 의미있는 방식으로 처리해야한다는 것이고 보통의 경우 이는 사용자에게 메시지를 보여주거나 다른 페이지로 리다이렉트 시키거나 아니면 디버깅을 위해 로그를 남기는 등의 작업을 뜻합니다.

문제는 해당 작업은 모두 호출 체인의 가장 앞 쪽에 위치한다는 것이고, 따라서 중간에 있는 `ShoppingCart` 같은 계층에서 처리할 수 없다는 것입니다. 예컨대 리다이렉트를 하려면 HttpResponse를 참조해야하는데 서비스 계층에서 웹 계층의 그런 객체를 참조하는 건 바람직하지도 않고 정상적인 설계에서 가능하지도 않습니다.

예제로 돌아와서 보면, 이 경우 `ShoppingCart` 입장에서 최상의 선택은 단순히 해당 예외를 다시 던지는 것임을 알 수 있습니다. 이는 설계적 관점으로 봐도 만족스러운 결과인데, 왜냐하면 `ShoppingCart`의 `add`메서드의 시그네쳐에 `throws NoSuchProductException`이 포함됨으로 인해 이제 호출자에게 '존재하지 않는 상품은 카트에 담을 수 없다'는 비즈니스 규칙을 명시적으로 밝힐 수 있기 때문입니다.

하지만 체크드 예외가 비즈니스 규칙이 아닌 오류를 나타내는 경우라면 이야기가 조금 달라집니다. 이젠 `Inventory`의 구현체가 데이터베이스를 통해 상품 정보를 조회한다고 가정해보겠습니다.

대부분의 JDBC API는 체크드 예외인 SQLException을 시그네쳐에 포함하고 있는 것은 잘 아시는 내용일 것입니다. 그럼 'Inventory'의 'getProductDetail' 구현에서 해당 예외를 어떻게 처리해야할까요?

우선 try-catch로 잡는 방법을 생각해볼 수 있지만, 이는 앞서 이야기한 이유로 좋은 선택이 아닙니다. 만일 비즈니스 요구조건이 장바구니 추가시 오류가 발생하면 이전 페이지로 리다이렉트 하는 것이라면 컨트롤러 계층에서 세 단계나 떨어져 있는 Inventory에서 이를 어떻게 처리할 수 있을까요?

사실 애초에 화면에 대한 내용은 해당 계층의 역할이 아닙니다. 계층화된 설계를 하는 이유 자체가 각 계층은 자신의 계층이 맡은 역할과 책임(role and responsibility)을 명확히 분리하기 위함이고, 그런 면에서 보면 예시의 설계에서 Inventory의 책임은 상품 조회 요청을 처리하는 것이지, 해당 요청이 웹 MVC에서 왔던 외부 모듈의 RMI 호출에서 왔건 간여할 바가 아닌 것입니다.

그렇다면 앞선 NoSuchProductException의 예에서 처럼 throws를 하는 것은 어떨까요?

이 경우 우선 `Inventory` 인터페이스의 구현체는 반드시 데이터베이스 기반으로 구현해야한다는 의도치 않은 제약이 생겨버립니다. 최초의 예제에서는 해당 인터페이스의 요구조건을 충족하기만 하면 구현체 입장에선 데이터베이스를 쓰건 NoSQL을 쓰건 XML 파일에서 읽어오건 전혀 문제될 것이 없었습니다.

하지만 인터페이스의 'add'메서드 시그네쳐에 SQLException이 들어가는 순간 해당 인터페이스는 JDBC API에 의존성이 생겨버리는 것입니다.

더 안좋은 것은, 이런 문제는 계층을 타고 상위로 전파된다는 것입니다. `Inventory.getProductDetails()`가 위와 같은 이유로 SQLException을 시그네쳐에 포함할 수 밖에 없었다면 마찬가지 이유로 'ShoppingCart.add()' 또한 해당 예외를 내부에서 처리하지 못하고 시그네쳐에 올릴 수 밖에 없습니다.

이 경우 예컨대 쇼핑 카트 서비스를 호출하는 MVC의 컨트롤러 계층에 최종 try-catch를 할 책임이 넘어가게 되는데, 이는 명백하게 잘못된 설계임을 어렵지않게 짐작할 수 있을 것입니다.

컨트롤러 입장에서 아는 것은 `ShoppingCart.add`에 제품 번호와 수량을 넣으면 장바구니에 담긴다는 것 뿐이지, 해당 서비스가 내부에서 `Inventory`라는 다른 서비스를 참조하는지, 또 그 서비스의 특정 구현체가 데이터베이스를 기반으로 작성되었는지는 관심도 없고 알아서도 안됩니다.

이렇게 계층의 역할과 책임이 불필요하게 다른 계층으로 전이되는 문제를 추상화의 누수(leaky abstraction)라고 부르기도 합니다.

이는 단순히 구조의 좋고 나쁨보다 큰 문제가 될 수도 있는데, 예컨대 위의 예에선 만일 추후 인벤토리 서비스 구현을 데이터베이스 기반에서 JDBC와 무관한 구현체로 바꾸어야 한다면 구현체 하나만 갈아 끼우는 게 아니라 해당 계층을 참조하는 모든 API의 시그네쳐와 클라이어트의 try-catch 구문을 수정해야하는 일이 되기 때문입니다.

더구나 try-catch의 경우 호출하는 메서드 시그네쳐에서 있던 예외가 빠졌다고 컴파일 오류가 발생하는 것도 아니기 때문에 일일이 IDE의 도움을 받아 고치지 않으면 찾기도 어려울 수 있습니다.

어느 쪽이건 구현체를 바꾸었다고 모든 계층의 소스를 다 바꾸어야 한다면 애초에 계층화, 추상화의 의미가 없어진다고 할 수 있을 것입니다.

이런 문제를 해소하기 위해 불필요한 체크드 예외는 사용하지 않는 정책을 택하는 프로젝트들이 많고, 예컨대 스프링 프레임워크 같은 경우 데이터관련 모듈에서도 SQLException을 추상화한 DataAccessException이 Exception이 아닌 RuntimeException을 상속하도록 설계된 것입니다.

한 편, 비즈니스 예외를 체크드 예외로 구현할 경우 앞서 언급한 대로 이러한 문제에서 자유로운 편인데, 이 경우 API는 비즈니스 규칙을 최대한 상세하게 명시하고 구현상의 세부사항은 감추는 것이 좋다는 원칙에도 부합하기 때문에 체크드 예외를 전혀 사용하지 않는 대신 비즈니스 규칙에만 사용하는 절충을 하기도 합니다.

보통 체크드 예외를 배울 때 "이런 예외는 try-catch나 throws로 처리해야한다'라는 '어떻게'만 가르치지 '왜'에 대한 내용을 다루지 않는 것 같아서 조금 내용을 보충해보았습니다.

Comments