Programming/Clean Code

Clean Code - 예외 처리 시 놓치는 부분 정리 (Log, Null 검사, Checked exception)

JaeHoney 2022. 1. 26. 23:48

반드시 Log를 남겨라

예외가 발생하면 대부분 언어에서는 호출 스택을 남깁니다. 하지만, 호출 스택만으로 왜 예외가 발생했는지를 알려면 많은 노력이 필요합니다. 따라서 반드시 catch문에서 충분한 로그를 남기는 것이 좋습니다. 

public void sendShutDown() {
    try {
        tryToShutDown();
    } catch (DeviceShutDownError e) {
    	logger.log(e);
    }
}

 

null을 반환하지 마라

아래의 코드를 보면 뭔가를 get 할때마다 null이 아닌지 검사를 하고 있습니다. 이렇게 코드를 구현한다면 프로젝트 하나에 거의 모든 곳에 null 검사가 들어가야 합니다. 누락되서 말썽을 일으키기도 쉽고, 코드도 더러워집니다.

public String getCompanyAddress(Person person) {
    if(person != null) {
        Company company = person.getCompany();
        if(company != null) {
             String address = company.getAddress();
             if(address != null) {
                return address;
             }
        }
    }
}

 

따라서 빈 배열을 사용하던지, 아니면 메서드에서 null을 반환하지 않게 수정해야 합니다. 만약 외부 API가 null을 반환한다면 메서드를 감싸서 예외를 던지는 게 바람직합니다. 자바라면 Optional 클래스를 사용하는 방법도 있습니다.

public String getCompanyAddress(Person person) {
    return Optional.ofNullable(person)
        .map(Person::getCompany())
        .map(Company::getAddress())
        .orElseThrow(NotFoundException::new);
}

 

Checked Exception -> Unchecked Exception 핸들링

Checked exception은 컴파일 시점에 확인되고, 반드시 예외 처리를 해야 합니다.

Unchecked exception은 런타임 시점에 확인되고, 예외처리를 하지 않아도 앱을 띄울 수 있습니다.

 

코드 유지보수성 관점으로 봤을 때 Checked exception을 Unchecked exception으로 핸들링했을 때 어떤 이점이 있을까요?

 

Checked exception의 경우는 예외 처리가 최상단의 코드까지 전부 이어진다는 문제가 있습니다. 아래의 코드 예시를 보겠습니다.

public void printCharactersFromFile(String path) throws FileNotFoundException {
    String fileString = getStringFromPath(path);
    for(char c : fileString.toCharArray()) {
        System.out.println(c);
    }
}

public String getStringFromPath(String path) throws FileNotFoundException {
    FileInputStream file = getFileStream(path);
    InputStreamReader inputStreamReader = new InputStreamReader(file);
    Stream<String> streamOfString= new BufferedReader(inputStreamReader).lines();
    String streamToString = streamOfString.collect(Collectors.joining());
    return streamToString;
}

public FileInputStream getFileStream(String path) throws FileNotFoundException {
    FileInputStream stream = new FileInputStream(path);
    return stream;
}

이 경우 해당 메서드를 호출하는 코드까지 계속해서 예외처리를 반드시 해야합니다. 즉, 상위 메서드에서도 하위 메서드의 예외 처리를 해야하는 상황인거죠.

 

그래서 Unchecked exception으로 감싸는 것이 바람직합니다.

public void printCharactersFromFile(String path) {
    String fileString = getStringFromPath(path);
    for (char c : fileString.toCharArray()) {
        System.out.println(c);
    }
}

public String getStringFromPath(String path) {
    FileInputStream file = getFileStream(path);
    InputStreamReader inputStreamReader = new InputStreamReader(file);
    Stream<String> streamOfString = new BufferedReader(inputStreamReader).lines();
    String streamToString = streamOfString.collect(Collectors.joining());
    return streamToString;
}

public FileInputStream getFileStream(String path) {
    try {
        FileInputStream stream = new FileInputStream(path);
        return stream;
    } catch (FileNotFoundException e) {
        // throws Unchecked exception
    }
}

수정된 예제를 보시면, 최하위 메서드에서 발생한 예외 처리는 해당 메서드에서만 처리하고 있습니다. 즉, 코드 가독성이 높아졌다고 볼 수 있겠습니다.

 

 

감사합니다

 

.

.

.