👩🏻‍💻 Programming/Java

Optional을 막 쓰지 말자

한국의 메타몽 2024. 4. 4. 12:23

개요

  1. 작성 계기
  2. Optional이란?
  3. 왜 막 쓰면 안되지?
  4. 결론

 

0. 작성 계기

회사에서 작업 도중 위와 같이 코드 리뷰가 들어왔다.
JavaKotlin으로 개발할때 Null Check 로직이 필요되는 부분이라면 Optional을 빈번하고 사용하고 있었는데,
Optional의 장점만 알고있었지 단점은 잘 모르고 있다는 생각이 들었다.
이 기회에 Optional의 장점과 단점을 정리해보자.

 

1. Optional이란?

OptionalJava 8부터 추가된 컨테이너 클래스이다.

// 기존 null 체크
if (account != null) {
    processAccount(account);
}

// Java8의 Optional 적용
var accountType = Optional.ofNullable(account)
    .map(acc -> acc.getAccountType())
    .orElse(DEFAULT_ACCOUNT_TYPE);

Java의 아키텍트(= 설계자) Brian Goetz는 Optional에 대해 Java Coding Problems에서 다음과 같이 정의 했다.

 

"Optional is intended to provide a limited mechanism for library method return types where there needed to be a clear way to represent no result, and using null for such was overwhelmingly likely to cause errors." - Brian Goetz

 

다시 말해 null을 반환하면 오류가 발생할 가능성, 이를테면 NPE가 발생할 가능성이 높은 상황을 방지하고자 사용하는 것으로 요약될 수 있다.

 

2. 왜 막 쓰면 안되지?

가독성이 오히려 떨어짐

// 기존 null 체크
if (account != null) {
    processAccount(account);
}

// Java8의 Optional 적용
var accountType = Optional.ofNullable(account)
    .map(acc -> acc.getAccountType())
    .orElse(DEFAULT_ACCOUNT_TYPE);

위에서 언급된 코드 처럼, Optional을 적용하지 않고 null check를해도 아무런 문제는 없다.
오히려 Optional을 사용하지 않는게 더 가독성이 높기도 하다.
그럼에도 개인적으로 Optional을 사용했던건, 하나 하나 if문으로 null check를 하기 보다는 Lambda식으로 null check를 하는게 더 좋아보였기 때문이었다. 😅

그러나 위의 코드만 봐도 알 수 있는 사실은, 굳이 Optional을 사용하지 않아도 단순 if문을 통해 null check를 하는게 더 간단하고 명시적이다.

 

NPE를 피하려다가 NSEE가 발생

Optional<User> optionalUser = ... ;

// optional이 갖는 value가 없으면 NoSuchElementException 발생
User user = optionalUser.get();

위와 같이 null safe하기 위해 Optional을 사용했는데, 값의 존재 여부를 판단하지 않고 접근하면 NPE는 피해도 NoSuchElementException이 발생할 수 있다.

 

시간적, 공간적 비용 (또는 오버헤드) 증가

  • 공간적 비용 : Optional은 객체를 감싸는 컨테이너 클래스 이므로, Optional 객체 자체를 저장하기 위한 메모리가 추가로 필요
  • 시간적 비용 : Optional 안에 있는 객체를 얻기 위해서는 Optional 객체를 통해 접근해야 하므로 접근 비용이 증가

결론적으로 Optional로 감싸는 비용으로 인해 오버헤드가 발생할 수 있다.

이 외에도 Optional은 직렬화를 지원하지 않아서 캐시나 메세지 큐 등과 연동할때 문제가 발생할 수 있는데, 디테일한 정보는 망나님 개발자 님의 언제 Optional을 사용해야 하는가 글을 참고해보자.

 

3. 결론

그래서 Optional은 언제쓰면 좋은가? 관련해서 Emanuel Trandafir은 Medium에 아래와 같이 내용을 정리했다.

 

메서드 리턴 타입으로 Optional을 사용 👍

public Account getAccount_classic() {
    Account account = accountRepository.get("johnDoe");
    if(account == null) {
        throw new AccountNotEligible();
    }
    return account;
}

public Account getAccount_optional() {
    return accountRepository.find("johnDoe")
        .orElseThrow(AccountNotEligible::new);
}

 

Getter 사용시 Wrapping 👍

Optional<Membership> getMembershipOptional() {
    return Optional.ofNullable(membership);
}

 

매우 단순한 로직에 Wrapping 👎

Optional.ofNullable(account)
    .ifPresent(acc -> processAccount(acc));

위와 같이 굳이 복잡하게 사용하지 말고, 클래식한 방법으로 null check를 하는 것을 권장한다.

 

// 👍
if (account != null) {
    processAccount(account);
}

// 👎
var accountType = Optional.ofNullable(account)
    .map(acc -> acc.getAccountType())
    .orElse(DEFAULT_ACCOUNT_TYPE);

출처