제발 catch 좀 줄여라¶
HJ, Ph.D. / Software Architect (js.seth.h@gmail.com) 초안: 2021년 07월 / 개정: 2025년 10월
Abstract¶
- try-catch에 대한 잘못된 사용 지침이나 권장이 너무 많다. 그 예시다.
- try-catch는 예상 가능한 예외가 발생할 수 있는 합리적 범위에 한정해서 사용한다.
- 최소한의 코드 블록을 try-catch로 감싸서 예외 상황을 명확히 파악할 수 있게 한다.
- 특정 예외만 Catch하라
- 복구 가능한 예외와 불가능한 예외 구분하라
- 복구 가능한 예외는 적절히 처리해 프로그램이 정상 동작하도록 하며, 복구 불가능한 예외는 로그를 남기고 필요시 프로그램을 종료한다
- 예외(또는 반환값)는 명확하고 구체적인 이름으로 정의한다
- 레이어에 따라 의미 있는 예외로 변환하여 상위 계층에 전달한다
- 현실을 전혀 다르다.
- 완벽히 예상 가능하면 예외가 아니다.
- 외부 서비스 경계 등에 방어벽(Crash Barrier)을 설치하지 않으면 전체가 멈추기 딱 좋다. 우선은 전체 커버를 해야 한다. 디테일은 그 다음.
- 모두 잡거나 아예 잡지 마라. 특정 오류를 잡아봐야 전체적인 견고성(Robustness) 관점에서 별 도움이 안 된다.
- 프로그램 기능으로 복구 가능하면 예외가 아니다. throw-catch의 메커니즘을 제대로 알면, 복구 가능성은 논의의 대상이 아니다.
- 복구 가능한 예외는 현실적으로 없다. 특히 외부 시스템(ex> DB, Web API)이 연결되면 항상 그렇다. 그리고 종료는 절대 절대 안 된다.
- 지엽적인 정보가 구체적이어도 많은 경우 문제 재현은 안 된다. 재현을 하려면 주변 정보까지 전부 필요하다. 그러니 오류 메시지에 에너지 낭비하지 마라.
- 상위로 향한다는 것은 더 추상적이 되고, 점점 구체성을 상실한다는 의미이다. 이것이 상황 파악이나 해결에 도움이 되는가?
- 우선 throw가 얼마나 막무가내인 매커니즘인지 제대로 이해해라.
- 예외를 극복하려고 하기보다, 상황을 파악하는데 주력하고, 적절히 대응해라.
예상이 가능하면 예외가 아니다.¶
물론 어떤 예외는 예상할수 있다. 그러나, 모든 예외는 예상할 수 없다. 단지, 이 사실 하나만으로도 우리는 예상하지 못하는 - 말 그대로 예외를 염두에 두고 방어 전략을 작성해야한다.
예외 대응에는 합리적 범위란 존재하지 않으며, 최종 저지선이 존재할 뿐이다. 합리적 사용을 고민할 시간에 우선 전역 예외 핸들러와 서비스 경계에 집중해라.
최종 저지선¶
시스템이 외부와 접하는, 특히 외부의 입력에 대응하는 부분에 있어 최대한 넓게 try-catch를 구성해야 한다.
예를 들어, 네트워크 receive라면, receive 이후 모든 것을 try-catch로 감싸고, 재접속 또는 신호 동기화될 때까지 모든 것은 폐기하여 서비스를 살려야 한다. try-catch는 좁게 구성하는 게 아니라, 넓게 구성해야 하며, 디테일보다 커버리지(Coverage)가 중요하다.
프랑스의 마지노선이 벨기에까지 막았다면, 역사는 아마 변했을 것이다.
새벽 3시에 전화받고 출동하기 싫으면, 진지하게 고민해두어야 한다.
Catch All or Nothing¶
"Chain is only as strong as its weakest link"
보안 분야에서 자주 언급되는 영어 속담이다.
보안의 경우 단 한 경로만 뚫리면 전체가 뚫린것이다.
마찬가지로 소프트웨어에게 예외,곧 불의의 사고 대응 또한 단 한 경로만 뚫리면, 새벽 3시에 출동해야 할 수 있다.
최종 저지선에선 무조건 모든 예외를 잡아야한다.
여기서 필요한 건, 우아하게 서비스를 거부하는 것이지, 일단 뭐라도 해주는게 아니기 때문이다.
그렇기에 가장 중요한 throw-catch인 최종 저지선은 무조건 All로 고정이다.
특정 오류를 골라서 적절히 대체하는 경우는 그게 있으나 없으나 별차이가 없을 때나 가능한 것이다. 그런 상황이 아예 없진 않지만, 대부분의 기능은 존재의 이유와 기능상의 목적이 명확하기 때문에 대체제가 없다. 그래서 보통의 예외는 전체적 기능 실패를 의미할 수 밖에 없고, 최종 저지선에서 처리하는 것이 바람직하다.
그렇게 되면 대부분의 루틴은 catch를 수행할 이유가 없다.
우아한 서비스 거부란 - 서비스가 죽지 않으고, 동시에 사용자가 답답해하지 않게 하지만, 결국 시킨 일을 하지 않는 것이다.
throw-catch의 메커니즘은 복구가능성을 염두에 두지 않는다¶
예외가 발생하면 throw는 현재 함수의 실행을 즉시 중단시키고, 예외를 처리할 수 있는 catch 블록이 나타날 때까지 함수 호출 스택(Call Stack)을 역순으로 언와인딩(unwind)한다. 이 과정에서 콜스택에 쌓여 있던 모든 함수의 지역 상태, 변수, 컨텍스트 정보는 사라진다.
즉, 예외가 발생한 시점부터 catch에 도달할 때까지의 모든 함수는 중간까지 실행되고, 그 사이에 어떤 자원이 열려 있었는지, 어떤 상태였는지 알 수 없게 된다. 어떤 처리가 어디까지 성공했는지도 알 수 없다. 특히 외부 라이브러리, 외부 시스템(DB, 파일, 네트워크 등)이 개입된 경우, throw 이후에 그 상태를 정확히 파악하는 것이 불가능하다. 이런 구조에서 "복구"란 사실상 불가능하다. 예외가 발생한 맥락과 복구가 필요한 자료가 완전히 사라지기 때문이다.
결국 예외 이후에 할 수 있는 유일한 "복구"란, 올바른 최종 결과를 다시 계산하여 내외부의 모든 것을 다시 덮어쓰는 것뿐이다. 좀 더 심도 있게 말하면, 연산 자체는 복구할 수 없고, 임의 시점 기준의 결과 데이터만 리셋하여 복구할 수 있다.
이러한 throw-catch의 본질적 메커니즘을 이해하면, 복구가능성 자체가 논의의 대상이 될 수 없다는 점이 명확해진다.
프로그램에게 throw catch란...
프로그램에게 throw란, '던전 탈출 아이템'과 같다. 모든 것을 포기하고, 목숨만 건지는 거다.
그리고 catch는 던전의 입구다. 콜스택에서 catch가 여러번 존재하는 것은 중첩 던전과 같다.
다 포기하고 탈출 아이템을 썼는데 다시 던전이라면, 무슨 의미가 있겠는가?
그러니까, 제발 적당히 써라.
복구 가능한 예외는 현실적으로 없다.¶
거의 모든 프로젝트가 쓰는 외부 시스템 '데이터베이스'를 생각해보자.
increment 연산, update tbl_X set a = a+1 where id = 99 를 호출하는 기능이 어딘가에 있고, 이 기능을 호출하기도 안하기도 하는 분기를 포함하는 어떤 기능을 호출한다. 어디선가 오류가 발생하여 최종 방어선에서 throw-catch를 접수했다고 하자.
그럼 디비는 +1을 했는가? 안했는가? 만약 AutoCommit 설정이라면?
오류 원인은 진즉에 네트워크가 단절되었을 수도 있고, 어쩌면, 디비가 Commit후 회신 패킷까지 OS에게 넘겼는데, 그뒤에 끊긴 걸 수도 있다. 이처럼 우리는 무엇을 어디까지 성공했는지 모두 알기 힘들다.
그런데, 연산 실패가 생각만큼 중요하진 않다. 정말 중요한건 최종 데이터이기 때문이다. 그렇기에 프로그램이 어떤 코드를 추가로 수행하여, 즉시 복구하려는 노력은 생각보다 중요성이 떨어진다. 가장 중요한 것은 최종 계산 결과가 정확한 것이다.
우선 가능한 빨리(ex> 24시간내) 결과를 정정해라, 비즈니스가 원하는 건 단지 그거다.
정말, 즉시 대체 가능한 제2의 방법이 있다면...¶
그건 개발비/운영비가 2배라는 소리다.
일정이 2배거나 사람이 2배가 아니라면, 주 80시간 근무라는 의미일 것이다.
Never Die¶
절대로 종료는 안된다. 무조건 죽이지 마라.
진짜로 새벽 3시에 출근하고 싶나?
문제를 재현하는 법¶
정말로 문제를 완벽하게 동일하게 재현하려면, 개념적으로 생각해서 2가지가 필요하다.
- 동일한 환경 - 환경 변수, DB에 저장된 정보, 메모리에 읽고 쓰던 흔적, 다른 스레드가 하던 일 등등.
- 동일한 입력
물론, 현실의 대부분의 문제는 적당히 비슷한 환경 + 동일한 입력이면 재현이 가능하다.
그러나 겨우 예외의 텍스트 메시지 가지고 할 수 있는 일은 별로 없다.
디테일한 예외 메시지를 작성할 시간에 더 필요한 건 제대로 된 로깅 시스템이다. 5분 단위 DB 스냅샷을 저장하고, 모든 입력을 기록하여, 특정 시간 구간의 시스템 상태를 5~10분간 그대로 재생해보는 수준은 바라지도 않는다.
문제 발생시와 비슷한 환경을 구성하여, 동일한 입력을 넣을 수 있으면 충분할 것이다.
구체성을 지켜라¶
상위 레이어에서 무엇을 할 수 있나? 기껏해야, '주문이 실패했습니다' 정도 아닌가?
실제로는 이런 저런 돈 계산을 하다보니, Divide By Zero 가 발생하거나, 입력 오류나 재고상황 변화로 Out of Index가 생길 수도 있다. 하지만 애초에 이런 메시지를 일일 파악하여 지정할 여유가 없을 뿐더러, 그만큼 발생 상황을 명확히 이해하면, 사전에 차단하여 로직에 진입시키지 않는 것이 옳다.
예외에 제대로 된 대응을 하려면 구체적 정보가 개발자의 손에 들어와야 한다. 하지만 개발자가 '주문이 실패했습니다' 메시지를 가지고 무엇을 할 수 있겠나? 우리에게 정말 필요한 건 구체적 정보이다. 오류가 발생한 콜스택이 100줄이든 200줄이든 전부 있는 것이 가장 좋고, 오류 발생 시간이 정확한 것이 가장 좋고, 외부 입력을 동일하게 넣을 만큼 기록되어 있으면 가장 좋다.
우리는 이 정보를 알아내야 하는데, 사용자가 단지 '주문이 실패했습니다'를 전달하는 것은 생산적이지 않다.
어떻게든 필요한 구체적 정보를 손에 넣어라. 그 방법이 예외 메시지일 필요는 없다.
추천 방법
내가 가장 좋아하는 방법은 최종 저지선에서 모든 요청에 일련번호와 외부 입력을 모두 저장하고,
예외 발생시 콜 스택을 그대로 저장한뒤,
사용자에게 '주문이 실패했습니다. 요청(오류)번호 00000. 관리자에게 문의하세요'를 출력해주는 것이다.
결론¶
throw-catch는 솔직히 아주 막무가내적인 기능이다. 현대 개발 언어의 대다수는 C/C++가 원류로 볼 수 있는데 C가 1972년, C++이 1980년에 등장했으나, throw는 1989년에서야 C++에 도입되었고, C언어는 아직도 존재하지 않는다.
당연한 언어의 기능이라고 생각할지 모르겠으나, 역사적으로 그리고 현실적으로 그렇지 않다. 프로그래밍 언어 이론 측면에서 throw-catch는 dynamic goto(이동 지점이 런타임에 결정되는 goto)와 다를 바가 없다.
강력한 만큼 적절히 사용했을 때, 이득이 큰 매커니즘이니, 이러한 특성을 이해하여 전략적으로 사용해야 한다.
- 예외는 남발하지 말고, 예상 불가능한 상황에 대비한 최종 저지선을 반드시 마련하라.
- 예외로 문제를 해결하려고 하지 말고, 비상 탈출 수단에 맞게 사용해라.
- 예외로만 상황을 파악하려고 하지 말고, 다른 수단과 조합을 준비해라.
- 비즈니스를 위해 예상 및 극복해야 하는 것은 정규 루틴에 넣고, 예외는 비즈니스가 가정하지 않는 케이스에 집중해라.
예외 메시지가 예쁜 것, try-catch가 꼼꼼하게 작성된 것, 가능한 많은 예외를 파악한 것은 중요한 부분이 아니다.
고작 그런 것이, 새벽 3시에 출근하지 않는 것이나 상황 파악할 단서도 없이 클레임 걸리지 않는 것보다 중요할 리 없다.
정말 해내야 하는 일이 무엇인지 확인하고, 그에 집중해라.
SeeAlso¶
- One Man Framework 가능성의 증명 - 예외처리가 탄탄할 때 가능한 것
- 불신의 코딩 vs 신뢰의 코딩 - 예외를 포괄한 더 거시적 개발 전략
- 아키텍처는 구조적 해결책이다 - 이 문서가 소개하는 내용이 예외에 대한 구조적 해결책이다
Author¶
HJ, Ph.D. / Software Architect
(js.seth.h@gmail.com)
https://js-seth-h.github.io/website/Biography/
Over 20 years in software development, focusing on design reasoning and system structure - as foundations for long-term productivity and structural clarity.
Researched semantic web and meta-browser architecture in graduate studies,
with an emphasis on structural separation between data and presentation.
Ph.D. in Software, Korea University
M.S. in Computer Science Education, Korea University
B.S. in Computer Science Education, Korea University
저작자표시-비영리-변경금지(CC BY-NC-ND 4.0)