자바/자바 코딩의 기술

자바 코딩의 기술 - 5장 ( 문제 발생에 대비하기 )

끄적끄적 2022. 4. 9. 14:19

오류가 없는 프로그램을 작성하는 두 가지 방법이 있는데 사실 세 번째 방법만 통한다. 
- 앨런J 펄리스

 

5-1. 빠른 실패

void setTargetSpeedKmh(double speedKmh) {
    if (speedKmh < 0) {
        throw new IllegalArgumentException();
    } else if (speedKmh <= SPEED_LIMIT) {
        targetSpeedKmh = speedKmh;
    } else {
        throw new IllegalArgumentException();
    }
}

불필요하게 분기가 세번으로 나누어져 있다. 아래와 같이 매개변수 검증부분과 일반 로직부분을 분리해야 명확하다.

void setTargetSpeedKmh(double speedKmh) {
    if (speedKmh < 0 || speedKmh > SPEED_LIMIT) {
        throw new IllegalArgumentException();
    }

    targetSpeedKmh = speedKmh;
}

5-2. 항상 가장 구체적인 예외 잡기

예외를 단순히 가장 큰 개념인 Exception으로 받기 보다는, 구체적인 예외인 NumberFormatException 등으로 받아라. 그 외에 예외(ex. NullPointException)는 숨기기 보다는 예외가 터져서 노출되는게 낫다.

try {
    int id = Integer.parseInt(rawId);
    String content = rawContent.trim();
    return new Transmission(id, content);
} catch (Exception e) {
    throw new IllegalArgumentException("Bad message received!");
}

기존 Exception으로 받는 부분을 개선 후에 NumberFormatException만 받는다.

try {
    int id = Integer.parseInt(rawId);
    String content = rawContent.trim();
    return new Transmission(id, content);
} catch (NumberFormatException e) {
    throw new IllegalArgumentException("Bad message received!");
}

5-3. 메시지로 원인 설명

Exception 메시지를 생략하거나 단순 용어를 쓰기보다는 바라는 것, 받은 것, 전체 맥락 등 근본 원인을 추적할 수 있도록 설명을 추가하라.

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));
}

5-4. 원인 사슬 깨지 않기

 catch문에서 예외를 throw할때 받은 e(Exception)을 추가로 다시 던져서, 메시지가 추적되도록 하라

try {
} catch (NumberFormatException e) {
    // BAD! Cause chain interrupted!
    throw new IllegalArgumentException(e.getCause());
}

위와 같이 하기보다는 아래와 같이 e 를 추가로 넘기기

try {
    
} catch (NumberFormatException e) {
    throw new IllegalArgumentException("Message", e);
}

5-5. 변수로 원인 노출

필요시 별도 Exception을 추가로 정의하여, 향후 변경에 대비하라.

final class MalformedMessageException extends IllegalArgumentException {
    final String raw;

    MalformedMessageException(String message, String raw) {
        super(String.format("%s in '%s'", message, raw));
        this.raw = raw;
    }
    MalformedMessageException(String message, String raw, Throwable cause) {
        super(String.format("%s in '%s'", message, raw), cause);
        this.raw = raw;
    }
}

5-6. 타입 변환 전에 항상 타입 검증하기

void listen() throws IOException, ClassNotFoundException {
    while (true) {
        Object signal = inputStream.readObject();
        CrewMessage crewMessage = (CrewMessage) signal;
        interCom.broadcast(crewMessage);
    }
}

아래와 같이 런타임에 형변환을 하려면 해당 객체타입인지 체크하는게 안전하다.

void listen() throws IOException, ClassNotFoundException {
    while (true) {
        Object signal = inputStream.readObject();
        
        if(signal instanceof CrewMessage){
            CrewMessage crewMessage = (CrewMessage) signal;
            interCom.broadcast(crewMessage);
        }
    }
}

5-7. 항상 자원 닫기

자원 누출(IO처리 등)이 발생할 수 있는 부분은 try-with-resources 문으로 묶어서, try블록이 긑나면 알아서 close()되도록 처리하라.

try (DirectoryStream<Path> directoryStream =
             Files.newDirectoryStream(LOG_FOLDER, FILE_FILTER)) {
    for (Path logFile : directoryStream) {
        result.add(logFile);
    }
}

5-8. 항상 다수 자원 닫기

try ( open resource1; open resource2 ) {  

}  // try-with-resources 블록

try (DirectoryStream<Path> directoryStream =
             Files.newDirectoryStream(LOG_FOLDER, FILE_FILTER);
     BufferedWriter writer =
             Files.newBufferedWriter(STATISTICS_CSV)) {
    for (Path logFile : directoryStream) {
        String csvLine = String.format("%s,%d,%s",
                logFile,
                Files.size(logFile),
                Files.getLastModifiedTime(logFile));
        writer.write(csvLine);
        writer.newLine();
    }
}

5-9. 빈 catch 블록 설명하기

catch블록에서 특별히 할 것이 없다면 e대신 ignored로 명명하고, 왜 무시하는지 주석을 추가하라.

} catch (NotDirectoryException ignored) {
    // No directory -> no logs!
}

 

반응형