Optional을 막 쓰지 말자
개요
- 작성 계기
- Optional이란?
- 왜 막 쓰면 안되지?
- 결론
0. 작성 계기
회사에서 작업 도중 위와 같이 코드 리뷰가 들어왔다.Java
나 Kotlin
으로 개발할때 Null Check 로직이 필요되는 부분이라면 Optional
을 빈번하고 사용하고 있었는데,Optional
의 장점만 알고있었지 단점은 잘 모르고 있다는 생각이 들었다.
이 기회에 Optional
의 장점과 단점을 정리해보자.
1. Optional이란?
Optional
은 Java 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);