예외처리 원칙
이전에 작성했던 예외 관련 글이 있었지만, 시간이 지나고 경험을 통해 얻은 생각들을 다시 한번 명확하게 정리하고자 한다.
예외 처리는 크게 두 가지 방식으로 나눌 수 있다.
if
문을 통한 조건 분기try-catch
를 이용한 예외 처리
결론부터 말하자면, 필요에 따라 두 가지 모두 적절하게 사용해야 한다.
기본 원칙
- 내부 데이터는 항상 유효하게 유지한다.
null
값의 사용을 최대한 지양한다.
-
의도적인 크래시가 목적이 아니라면, 예외(
throw
)를 발생시키지 않는다. - 라이브 환경에서 발생 가능한 케이스는
if
문으로 분기 처리한다.- 예측 가능한 오류는 예외가 아닌 흐름 제어로 처리한다.
- 작업 중 가정한 “절대 발생해서는 안 되는” 케이스에는
ASSERT
를 사용한다.- 이
ASSERT
가 실패하면, 로직에 버그가 있다는 신호이며 의도적으로 크래시를 발생시켜 문제를 즉시 인지하도록 한다.
- 이
-
기본적인 로직은 항상 Happy Path를 기준으로 작성한다.
- 입력값에 따라 성공 또는 실패할 수 있는 함수는
Try
접두사를 붙여 명확하게 표현한다.- 예:
bool TryGetValue(Key key, out Value value)
- 예:
- 로직적으로 발생 가능한 케이스는
if
문으로 처리한다.- 예: 사용자가 자료구조에 없는 데이터를 요청하는 경우
try-catch
는 예외가 발생할 수 있는 최소한의 범위에만 사용한다.- 외부 라이브러리 호출 등 제어할 수 없는 코드에서 발생하는 예외를 처리하는 데 집중한다.
대원칙
- 개발자는 발생 가능한 모든 케이스를 최대한 고려하고, 각 상황에 맞는 처리 방식을 결정해야 한다.
- 때로는 “아무것도 처리하지 않는 것”도 하나의 유효한 처리 방식일 수 있다.
예시 1: 복구 불가능한 시스템 오류
동적 메모리 할당 실패와 같이 프로세스 실행을 더 이상 신뢰할 수 없는 상황이 발생했다고 가정해보자.
if
나try-catch
로 이 상황을 처리하더라도, 근본적인 문제가 해결되지 않았기 때문에 결국 다른 곳에서 크래시가 발생하게 된다.- 이런 경우, 오류를 처리하지 않고 즉시 크래시를 발생시키는 것이 오히려 가장 빠른 원인 파악 방법일 수 있다.
ASSERT
조차 필요 없는 명백한 실패 상황이다.
예시 2: 서버 초기화 실패
서버가 시작될 때 데이터를 로드하는 과정에서 실패했다고 가정해보자.
try-catch
로 예외를 잡아 정상적으로return
처리를 하면, 서버는 일단 실행될 수 있다. 하지만 데이터가 없기 때문에 아무 기능도 수행하지 못한다.- 또한, 서버가 조용히 종료되면 구체적인 원인 파악을 위해 원격 디버깅과 같은 복잡한 과정을 거쳐야 할 수 있다.
- 차라리 크래시를 발생시켜 콜스택(Call Stack)을 확보하는 것이 원인을 가장 빠르고 명확하게 파악하는 방법이다.
ASSERT
를 적극적으로 사용해야 하는 이유
코드 작성 시점에 “나의 가정이 맞다”는 것을 ASSERT
로 명시하면 다음과 같은 이점을 얻을 수 있다.
- 버그 조기 발견: 로컬 테스트 단계에서 잘못된 가정을 즉시 발견하고 수정할 수 있다.
- 코드 가독성 향상: 코드를 읽는 사람에게 “이 변수는 절대
null
이 아니어야 한다” 또는 “이 함수는 특정 상태에서만 호출되어야 한다”와 같은 개발자의 의도를 명확하게 전달한다. - 문제 확산 방지: 문제가 있는 로직이 억지로 실행되는 것을 막아, 발견이 늦어지고 영향 범위가 눈덩이처럼 불어나는 것을 방지한다.
- 빠른 이슈 대응:
ASSERT
로 인한 크래시는 발생 즉시 인지되고 수정 또한 빠르게 이루어질 수 있다.- 만약 크래시가 너무 치명적이라면,
ASSERT
발생 시 콜스택과 덤프를 남기고, 이를 즉시 인지할 수 있는 시스템을 갖춘 후 예외 처리를 통해 다른 로직으로 우회하는 것도 고려해볼 수 있다.
- 만약 크래시가 너무 치명적이라면,
try-catch
는 언제 사용해야 하는가?
- 스스로 작성한 코드 로직에서는 예외가 발생하지 않도록 설계해야 한다. 성공/실패 여부가 필요한 함수는
int.TryParse
처럼 결과를 반환하도록 하고, 호출부에서if
문으로 처리하는 것이 바람직하다. - 이렇게 설계하면
try-catch
는 대부분 제어할 수 없는 외부 코드(표준 라이브러리, 서드파티 라이브러리 등)를 호출하는 경우로 사용이 제한된다.
예시: 파일 읽기
- 파일을 읽기 전, 파일이 존재하는지
if
문으로 확인하는 것은 당연한 기본 처리다.- 하지만 그럼에도 불구하고 파일을 읽는 과정에서 디스크 오류 등 예측하지 못한 예외가 발생할 수 있다.
- 따라서
if
문으로 기본적인 체크를 하고,try-catch
로 예상치 못한 예외를 감싸는 두 가지 처리가 모두 필요하다.
정리
작업시 순서는 이렇게 된다.
- Debug.Assert를 고려
- -> Assert가 아닌 라이브 중 발생가능한 케이스 if문으로 처리 고려
- –> throw 발생 가능한 로직은 try catch로 감싼다
모든 코드 로직에는 작성자의 의도가 명확히 담겨야 하며, “왜 이렇게 코드를 작성했는가”에 대해 스스로 설명할 수 있어야 한다.
Posted 2025-10-01