오류 상황 대처법 (013-012~025)
서술 개념(원칙·4방법·순위·예측 가능/불가능)은 ANKI 참고: 48-예외 사용 원칙 이후 카드들
Java에서 예외를 제어 흐름으로 쓰지 않도록 tryParseInt() 메서드를 작성하세요 (코드 작성)
Java의 Integer.parseInt() 메서드는 파싱에 실패하면 무조건 NumberFormatException을 던진다. 그래서 “문자열이 정수인지 아닌지에 따라 분기”하고 싶을 때도 예외를 제어 흐름용으로 쓸 수밖에 없다. C#의 TryParse() 메서드처럼 호출자가 boolean 값으로 분기할 수 있는 tryParseInt() 정적 메서드를 작성하라. 호출자는 내부에서 예외가 쓰이는지 몰라야 한다.
public static boolean tryParseInt(String str) {
try {
Integer.parseInt(str);
return true;
} catch (NumberFormatException e) {
return false;
}
}- 예외를 제어 흐름용으로 사용하는 것은 ==
goto와 개념이 같고 오히려 더 hack== — 재귀 콜스택에서 한 번에 빠져나오려고 예외를 쓰는 것이 정말 나쁜 예 try/catch를 메서드 안에 숨기는 것도 캡슐화 — 호출자는 내부에서try/catch를 쓰는지 몰라도 되고,boolean값에 따라 다음 로직을 진행하면 됨- 예외를 예외답게(오류 상황에만) 써야 하는 이유: 예외가 오류 상황이라는 전제로 모든 툴이 개발됨(예외 중단점 등) — 제어 흐름으로 쓰면 협업할 때 남이 디버깅하다 민폐를 겪음
서술형: 오류 상황을 처리하는 4가지 방법을 각각 한두 문장으로 설명하세요
모범답안
- 무시: 오류 상황을 무시하고 넘어감. 곧바로 크래시가 나거나, 일단은 작동하지만 언젠가 크래시가 나거나, 안정적이지 못한 상태로 계속 동작하게 됨 (보통은 앞의 두 가지가 발생).
- 종료: 문제를 일으킬 수 있는 상황이 있는지 검사하고, 그렇다면 사용자에게 문제를 알리고 프로그램을 정상 종료함. 크래시와 달리 저장 등 정리(graceful shut-down)를 할 수 있음.
- 수정: 문제를 일으킬 수 있는 상황이 있는지 검사하고, 그렇다면 실수를 고친 뒤 프로그램이 계속 실행되게 함. 단, 처음 문제 발생 상황을 알기 어려워질 수 있어 클라이언트에게 무엇이 문제였는지 알려주는 것이 좋음.
- 예외: 문제가 발생하면 예외를 던져 호출자에게 알림. 1~3번은 오래되어 대응 방법이 정립됐지만 예외는 아직 올바른 방법이 정립되지 않음.
서술형: “예외도 위로 던지면 JVM이 종료해주니 ‘종료’ 방법과 다를 게 없다”는 주장을 반박하세요 (다섯 문장 이내)
모범답안
종료는 문제를 발견한 지점에서 프로그램을 끝내므로 방향이 명확하고 반드시 실행됨. 반면 예외는 던지는 쪽이 통제할 수 없음 — 호출자 중 누군가 중간에 catch해서 삼켜버리면(swallow) JVM까지 전파된다는 보장이 없음. 게다가 클라이언트는 그 예외를 catch해서 처리해야 할지 rethrow해서 전파해야 할지 알 수 없어, 4가지 방법 중 객관성이 가장 낮은 “폭탄 돌리기”가 됨. 어설프게 catch해서 회복을 시도하다 실패하면 이상한 상태로 계속 동작하는 좀비 프로그램이 되어 크래시보다 심각해짐. 따라서 종료를 원한다면 예외에 의존하지 말고 직접 종료하는 것이 확실함.
다음 각 명제의 O/X는?
-
재귀 콜스택에서 한 번에 빠져나오기 위해 예외를 사용하는 것은 좋은 활용이다
-
예측하지 못한 오류 상황은 버그로 취급해 고치고 다시 빌드해야 한다
-
내 시스템 안에 들어온 데이터도 언제나 유효하지 않다고 의심하고 검증해야 한다
-
클라이언트 입장의 객관성 순위에서 예외는 4가지 방법 중 최하위다
-
예외를
main()함수까지 던지면 JVM이 종료해주므로, 예외를 던지는 쪽은 프로그램이 종료된다고 보장할 수 있다 -
디버깅 관점에서는 잘 작성된 예외 메시지가 크래시로 얻는 메모리 덤프보다 정보가 많다
-
X — 제어 흐름용 예외의 대표적인 나쁜 예. 반환값을 사용해야 함
-
O — 예측했으면 그 처리는 기능의 일부, 예측 못했으면 버그 (미리 코드로 대응하지 못했으므로 고치고 다시 빌드)
-
X — 내 시스템 안의 데이터는 언제나 유효하다고 가정. 의심해야 하는 것은 남으로부터 받아오는(통제할 수 없는) 데이터이고, 검증은 경계에서 수행 — 잘못된 데이터는 거부하고 남에게 알림 (
boolean·null반환, 오류 코드 반환, 예외 던지기) -
O — 예외는 클라이언트가 어떻게 처리해야 할지 모름 (catch로 처리? rethrow로 전파?) — “폭탄 돌리기”. 종료·수정이 공동 1위, 무시가 3위
-
X — 호출자가 중간에 catch해서 삼켜버리면(swallow) JVM까지 전파된다는 보장이 없음 — 종료를 원한다면 예외에 의존하지 말고 직접 종료해야 확실함
-
X — 반대. 메모리 덤프는 기기 메모리 전체의 스냅샷이라 예외보다 정보가 많고, 사용자의 리포트에 의존하지 않아 정확함 — 디버깅에는 크래시가 유리한 이유. 참고: 앱이 크래시 나도 요즘 OS는 해당 프로그램만 종료함 (블루 스크린은 운영체제 자체의 크래시로 별개)