[삽질] 알고도 당하는 Optional의 함정

Java로 진행중인 개인 프로젝트에서 Optional을 요긴하게 쓰고 있다.

Null을 사용하였다면 Null check를 일일이 확인하고 테스트하면서 애로사항이 생겼을텐데, Optional 타입을 쓰면서 Null에 대한 확인이 강제되다보니 타입 에러를 따라가서 고쳐주기만 하면 되어서 나름대로 만족하면서 코딩을 하고 있다.

다만 Optional에도 함정이 있었으니...

그렇잖아도 Optional 바르게 쓰기 등의 글들을 보면서 조심해야겠다고 생각하고 있었는데 읽었고 아는 내용인데도 실수하여 이렇게 글을 남기게 되었다.

문제의 코드를 소개하자면 다음과 같다.

이렇게 보면 뻔할 수도 있겠지만 많은 코드 사이에 이게 끼어 있었을 때는 너무 자연스러워서 눈에 띄지 않았던 것 같다.

`catRepo`는 카테고리 Entity를 데이터베이스에 저장하는 Spring Data JPA의 Repository이다.

// 안중요한 부분들 과감히 생략

@Annotations

public class OscillatorService {

  private final CategoryRepository catRepo;

  public Category findOrSave(Category category) {
    return catRepo
        .findByRuleAndPeriodAndSparkType(
            category.getRule(), category.getPeriod(), category.getSparkType())
        .orElse(catRepo.save(category));
  }
}

위 코드에서 잘못된 점은 바로 `Optional::orElse`이다.

뭔가 읽기에는 Entity를 찾아보고 Entity가 있으면 그걸 쓰고 없으면 새로 저장하고 쓰라는 것 같지만, 실제로는 `Optional::orElse` 안의 값은 Optional 안의 값에 상관없이 evaluation된다. 본인의 경우 역시 값을 찾았는데도 불구하고 `catRepo.save(category)`가 실행해서 오류가 발생하였다.

그러면 위를 해결해주려면 어떻게 하면 좋을까.

`Optional::orElseGet`을 사용하여 다음과 같이 바꾸면 해결된다.

// 안중요한 부분들 과감히 생략

@Annotations

public class OscillatorService {

  private final CategoryRepository catRepo;

  public Category findOrSave(Category category) {
    return catRepo
        .findByRuleAndPeriodAndSparkType(
            category.getRule(), category.getPeriod(), category.getSparkType())
        .orElseGet(() -> catRepo.save(category));
  }
}

꼭 side effect가 있거나 계산이 시간이 많이 걸리는 작업이 아니더라도 그냥 `Optional::orElse` 대신 `Optional::orElseGet`을 쓰는 것이 마음이 편한 것 같아서 앞으로는 기본적으로 `Optional::orElseGet`을 쓰기로 마음먹었다.

댓글을 작성해보세요.