5장 문제 발생에 대비하기
5-1 빠른 실패
class CruiseControl {
static final double SPEED_OF_LIGHT_KMH = 1079252850;
static final double SPEED_LIMIT = SPEED_OF_LIGHT_KMH;
private double targetSpeedKmh;
void setTargetSpeedKmh(double speedKmh) {
if (speedKmh < 0) {
throw new IllegalArgumentException();
} else if (speedKmh <= SPEED_LIMIT) {
targetSpeedKmh = speedKmh;
} else {
throw new IllegalArgumentException();
}
}
}
위의 코드는 앞뒤로 두개의 예외처리 분기에 둘러싸여있어 정상적인 제어 흐름 경로가 눈에 잘 들어오지 않습니다. 따라서 매개변수 검증 사이에서 정상적인 경로를 찾는데 시간이 낭비하게 됩니다.
이런식의 낭비를 줄이기 위해서 실패 상황을 빠르게 처리하여 메서드 전체를 읽고 이해하기 쉽게 하는 것이 좋습니다.
class CruiseControl {
static final double SPEED_OF_LIGHT_KMH = 1079252850;
static final double SPEED_LIMIT = SPEED_OF_LIGHT_KMH;
private double targetSpeedKmh;
void setTargetSpeedKmh(double speedKmh) {
// 매개변수 검증
if (speedKmh < 0 || speedKmh > SPEED_LIMIT) {
throw new IllegalArgumentException();
}
// 일반적인 경로
targetSpeedKmh = speedKmh;
}
}
위의 코드 처럼 매개변수 검증과 일반적인 경로를 분리하여 예외 상황일 때 빠르게 실패 하도록 하는 것이 좋습니다.
5-2 항상 구체적인 예외 잡기
class TransmissionParser {
static Transmission parse(String rawMessage) {
if (rawMessage != null
&& rawMessage.length() != Transmission.MESSAGE_LENGTH) {
throw new IllegalArgumentException("Bad message received!");
}
String rawId = rawMessage.substring(0, Transmission.ID_LENGTH);
String rawContent = rawMessage.substring(Transmission.ID_LENGTH);
try {
// rawId 문자열 안에 int형으로 바꿀 수 없는 문자가 있을 때 오류가 발생!!
int id = Integer.parseInt(rawId);
String content = rawContent.trim();
return new Transmission(id, content);
} catch (Exception e) {
throw new IllegalArgumentException("Bad message received!");
}
}
}
자바의 예외는 복잡한 타입 계층 구조를 띄우고 있기에 예외를 잡으려면 항상 구체적인 예외 타입을 잡아야 합니다. 일반적으린 타입을 잡으면 잡아선 안 될 오류 까지 잡힐 위험이 있습니다.
위의 코드는 Integer의 parserInt에서 오류가 발생 할 때 예외를 처리하기 위해 기본적인 예외 Exception을 사용하였습니다. 하지만 이 경우 parserInt의 예외 뿐만아니라 OutofMemortError 같은 머신의 오류에서도 예외처리가 발생하게 됩니다.
class TransmissionParser {
static Transmission parse(String rawMessage) {
if (rawMessage != null &&
rawMessage.length() != Transmission.MESSAGE_LENGTH) {
throw new IllegalArgumentException("Bad message received!");
}
String rawId = rawMessage.substring(0, Transmission.ID_LENGTH);
String rawContent = rawMessage.substring(Transmission.ID_LENGTH);
try {
// 예외 발생!!
int id = Integer.parseInt(rawId);
String content = rawContent.trim();
return new Transmission(id, content);
} catch (NumberFormatException e) { // 예외 구체화
throw new IllegalArgumentException("Bad message received!");
}
}
}
이를 방지하기 위해서 구체적인 예외를 잡도록 해야합니다. parseInt에서 발생하게 될 NumberFormatException으로 예외처리를 구체적을 잡는 편이 좋습니다.
5-3 메시지를 자세하게 적는다.
예외가 발생하면 메시지를 보고 처리할 수 있도록 어떤 상황에서 예외가 발생했는지 1~2문장 정도로 간결하게 잘 적어둬야 합니다. 단 너무 장황하게 쓰지는 말아야 합니다.
class TransmissionParser {
static Transmission parse(String rawMessage) {
if (rawMessage != null
&& rawMessage.length() != Transmission.MESSAGE_LENGTH) {
throw new IllegalArgumentException(
// 메시지 구체화
String.format("Expected %d, but got %d characters in '%s'",
Transmission.MESSAGE_LENGTH, rawMessage.length(),
rawMessage));
}
String rawId = rawMessage.substring(0, Transmission.ID_LENGTH);
String rawContent = rawMessage.substring(Transmission.ID_LENGTH);
try {
int id = Integer.parseInt(rawId);
String content = rawContent.trim();
return new Transmission(id, content);
} catch (NumberFormatException e) {
throw new IllegalArgumentException(
// 메시지 구체화
String.format("Expected number, but got '%s' in '%s'",
rawId, rawMessage));
}
}
}
위의 코드는 IllegalArgumentException를 2번 throw 하지만 각 예외처리 메시지를 다르게 해서 어떤 원인인지 자세하게 파악하도록 하였습니다.
5-4 원인 사슬 깨지 않기
예외는 또다른 예외를 발생 시킬수 있습니다. 이 경우 새로운 예외를 던져야 합니다.
try {
} catch (NumberFormatException e) {
// BAD! Cause chain interrupted!
throw new IllegalArgumentException(e.getCause());
}
언뜻 보면 괜찮아 보입니다. e.getCause()를 통해 원인을 넘겨 줍니다. 하지만 NumberFormatException이 throw에서 빠짐으로 원인만 연결되고 예외가 연결 되지는 않는 상황입니다. 잘못된 생략으로 오해를 부를 수 있는 상황입니다.
try {
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Message", e);
}
그래서 새로운 예외를 발생시키는 경우 예외를 통채로 원인으로 전달 하는 것이 원인 사슬을 끊지 않고 전달 할 수 있는 방법입니다.