그래서 예외는 어떻게 처리하는 것이 좋은걸까?
오류 상황
- 예외 상황과 오류 상황을 다른 의미로 사용할 것
- 오류 상황은 error condition을 말함
- 이 오류 상황을 처리하는 방법 중에 예외가 있음
오류 상황은 예측 가능한 상황을 의미함
- 프로그램 실행 중에 기본적으로는 일어나지 않음
- 하지만 일어날 수 있는 일
- 따라서 이러한 상황을 처리하는 코드는 프로그램 기능의 일부임
- 프로그래머가 이를 예측하지 못했다면?
오류 상황을 처리하는 4가지 방법
무시
- 곧바로 크래시
- 일단은 작동하는데 언젠가는 크래시
- 안정적이지 못한 상태로 계속 동작
- 데이터가 망가진 상태
- 올바르지 않은 결과가 나올 수 있음
- 은행 출금 서비스?
검사 후 정상적 프로그램 종료
- 사용자에게 문제 상황을 보여주고 종료
- 크래시보다 나은점
- 제대로 시스템 상태를 정리하고 프로그램 종료할 가능성이 높음
- 종료 후 시스템이 좀 더 안정적인 가능성이 높음
- 작업했던 내용을 날리지 않고 저장해줄 수 있음
미리 검사 후 문제를 고친다.
- 분모가 0일 경우 1로 바꾼다.
- 사용자에게 올바른 값을 입력하라고 다시 요청한다.
- 누군가는 이런 방식을 개체지향적이 아니라며 배척함
- 하지만 UX를 포기하면서까지 OO를 계승할 이유가 없음
- 예외는 다른 방법에 비해 성능이 가장 느림
- 설계와 필요한 성능 수준에 따라 OO에서도 충분히 좋은 방법이다.
단점
- 문제가 처음 발생한 곳을 파악하기가 쉽지 않다.
- 파일 읽는 함수에서 파일 못찾은 경우 빈 문자열을 반환한다면?
- 그 당시에는 맞을 수 있음
- 근데 나중에 그 파일에 문자열을 덧붙이려고 하면 불가능함
- 프로그래머는 있던 파일이 사라졌다 생각할 수 있음
- 즉, 디버깅이 어려워짐
예외를 던진다.
- 문제가 발생했다는 사실을 알려줄 수는 있음
- 그 예외를 처리할 수도 있음
오류 상황을 피하는게 최고
- 처음부터 문제가 없는 것이 가장 좋음
- 내가 가진 가정이 옳은지부터 검토하는 것이 좋다.
- 파일이 항상 있는가?
- 0으로 나눌 가능성은 없는가?
- 데이터는 항상 들어와 있는가?
프로그램은 여러 시스템으로 구성되어 있다.
- 각각의 모듈은 완전하다고 가정하자.
- 문제는 경계에서 생기는데, 이럴 때 좋은 것은
- 내 영역으로 들어오는 데이터는 언제나 유효하게 만드는 것이다.
- 경계에서 들어오는 데이터들은 반드시 검증해야 한다.
- 만약 잘못된 데이터라면 거부해버린다.
- 즉, 내가 통제 가능하도록 하는것이 중요.
남에게 문제를 알려주는 방법
- 참/거짓이나 null을 반환
- 오류 코드 (int, enum)을 반환
- 예외를 던짐
예외는 OOP의 일부가 아니다.
- 비슷한 시기에 나와 예외가 OO의 일부라 생각하는 사람들이 있음
- 극단적인 진영은 OO에서 무조건 예외만 사용해야 한다고 함
예외 외에는 해결책이 없는 경우: 생성자
- null을 반환하여 생성시 문제가 없다는 것을 알려줄 수 없음
- 위와 같은 언어의 경우 문제가 발생했다는 사실을 알려주려면 예외가 유일한 방법
- 왜냐하면 결국 위와 같은 언어도 C로 구현되었을 가능성이 높기 때문
- C의 경우 메모리 할당 > 메모리를 개체에 배정 > 생성자를 통해 상태 초기화 의 흐름을 갖기 때문
- 일단 메모리 할당을 시켜놨는데 생성자 블록 안에서 문제가 생기면 되돌릴 방법이 없음
잘못된 예외처리보다 크래시가 낫다.
- 잘못 처리하면 크래시가 안나고 프로그램이 계속 실행되곤 함
- 당연히 안정적이지 않은 상태임
크래시를 예전보다 덜 심각하게 생각함
- 크래시의 문제는 작업물을 잃어버리는 것
- 그런데 요즘은 차라리 크래시가 나게하고 자동 세이브로 해결함
- 심지어 온라인 문서 편집기는 언제나 저장됨
- 즉, 사용자들은 더더욱 크래시에 신경 안 씀 (옛날에 비해)
요새 OS, 하드웨어는 많이 발전됨
- 이제는 다음의 두 사항이 안정성 측면에서 큰 차이가 없다.
- 크래시 발생 > 프로그램 종료
- 발생한 예외를 처리 안함 > 프로그램 종료
- 예전에는 서버 앞에서 사람이 있었어야 했으니 둘이 차이가 난다.
- 하지만 이제는 OS에서 처리해줌
- 디버깅 잘하는 프로그래머들은 오히려 크래시를 선호
- 문제를 일찍 발견
- 크래시 발생 이유를 곧바로 조사 가능
크래시와 메모리 덤프
- 메모리 덤프: 거의 모든 정보
- 예외: 정보가 적음
- 호출 스택 정보 (문자열)
- 메시지 (null인 경우가 빈번)
- 예외 타입
- 예외를 던지게 되면 많은 내용을 담고 있지 않기 때문에 크래시 나는 순간 그냥 메모리 덤프를 보내주는게 낫다.
프로그램 종료도 올바른 방법이다.
- 잘못된 예외 처리 때문에 이상한 상태에 빠지는 일을 방지하자.
- 물론 크래시보다도 좋다.
- 최근 5분 동안의 작업물도 다 저장해 줄 수 있으니
- 그런데 자동 세이브 기능만으로 충분하다.
오류 처리 방법의 순위: 책임감 순위
- 수정
- 종료
- 예외: 남에게 폭탄 돌리는 꼴
- 무시: 말할 필요 없음
오류 처리 방법의 순위: 오지랖 순위
클라이언트가 할 일을 내가 해줌
- 종료
- 수정: 호출자가 수정을 원하지 않을 수도? 혹은 내가 잘못 고치는 걸 수도?
- 예외
- 무시
오류 처리 방법의 순위: 명확성 순위
코드를 보는 것만으로 어떤 일이 일어나는지 알 수 있는가?
- 종료 / 수정
- 무시
- 예외: 예외가 온 경우, 어떻게 처리..? 애매함.
- 폭탄 돌리기 (그냥 위쪽으로 전파)
- 예외에 대해 안전하게 작성하도록 노력 (난 예외가 와도 복구시킬 수 있어)
예측 가능한 상황의 처리법
- 위에 소개한 4가지 처리 방향은 언제나 4가지 방법이 있는게 아니다.
- 즉, 상황따라 사용할 수 있는 방법의 개수가 달라진다.
- 오류를 예측O: 기능의 일부
- 오류를 예측X: 버그
이미 예측 + 고치기 쉬움
- 안전하게 고칠 수 있는 자신이 있어야 함, 어렵지 않아야 함
- 쉬운 경우 고치고 계속 프로그램 진행
- 다른 시스템에서 발생한 예외 역시 경계에서 처리하고 계속 진행시킴
- 즉, 이런 경우 수정하여 진행시킴
이미 예측 + 고치기 어려운 경우
- 최종 사용자에게 메시지를 보여주고 싶다면? (예외를 던졌다면)
- 중간에서 아무데도 catch안하고 최상위까지 예외를 전달
- 최상위 함수에서 팝업창을 띄워 메시지를 보여줌
- GUI가 아니라면
main
함수에서 로그파일에 기록
- 최종 사용자에게 메시지를 보여주기 싫다면?
- 문제 발생 시점에서 로그를 남기고 프로그램 종료
- 예외를 던져
main
에서 catch 후 로그남기고 프로그램 종료
예측하지 못한 상황
- 처리 코드는 당연히 없음
- 수정도 당연히 불가능
- 프로그래밍 언어 따라 달라짐
- 혹시라도 발생할 수 있는 모든 예외를
main
에서 catch하고 로그를 남겨줌
- 예외로 남기기 때문에 자세한 디버깅 정보는 없음
- 크래시가 나서 바로 메모리 덤프가 생김
정리
- 실행 중에 문제를 고치려 한다고 프로그램의 안정성이 높아지는 것은 아니다.
- 무조건 고치라는 말은 잘못된 조언이다.
- 좀비 상태가 되는 것이 더 큰 문제다.
Reference