본문 바로가기

[Java] Optional의 Best Practice

@xuv22025. 8. 9. 14:48

옵셔널이 안정성은 높지만, 그렇다고 무분별하게 쓰면 가독성과 유지보수가 구려진다.

옵셔널이 왜 생겼는지 근본을 생각해보자. 제일 큰 이유는 null 여부의 명확성 + NPE 방지를 위해 등장한 개념이다.

즉, 우리는 이 옵셔널을 웬만하면 반환값에 사용하도록 해야한다.


Optional 의 베스트 프랙티스

1. 반환 타입으로 사용 , 필드엔 쓰지 말자

옵셔널은 메서드의 반환 값에 대해 값이 없을 수도 있다를 표현하기 위해 등장했기 때문에, 필드에 직접 옵셔널을 두는 것은 권장되지 않는다.

private Optional<String> name; -> 이런식으로 쓰지말고

public Optional<String> getNameWithOptional(){
	return Optional.ofNullable(name)
} -> 이런식으로 값을 반환할 때 쓰자

옵셔널도 참조 타입이기 때문에 필드에 null 을 방지한답시고 옵셔널을 썼다가 만약 null을 집어넣게 되면 그 자체로 NPE를 터트릴 수도 있다.

 

2. 파라미터로 Optional 받지 말기

자바 공식문서에 Optional은 메서드의 반환 값으로만 쓰길 권장한다고 대놓고 나와있고, 매개변수로 쓰지 말라고도 나와있다.

메서드를 호출하는 쪽에서 null을 전달해도 되는데 굳이 Optional을 통해 메서드를 호출하게 되면 호출측에서 부담이 생기고, 코드의 가독성이 줄어든다.

public void processOrder(Optional<Long> orderId) {
	if (orderId.isPresent()) {
		System.out.println("Order ID: " + orderId.get());
	} else {
		System.out.println("Order ID is empty!");
	}
}

위 코드처럼 파라미터로 옵셔널을 쓰지말잔 소리다.

권장되는 해결 방법은 메서드를 오버로드 하거나, 명시적으로 null 허용 여부를 적는 것이다.

public void processOrder(Long orderId) {
	if (orderId == null) {
		System.out.println("Order ID is empty!");
		return;
	}
	System.out.println("Order ID: " + orderId);
}

오히려 이런식으로 방어적으로 짜는 로직이 훨씬 낫다는 소리이다.

 

3. Collection이나 배열등의 자료구조에는 Optional 쓰지 말기

컬렉션 자체는 이미 비어있는 상태를 표현할 수 있다. 이를 옵셔널로 감싸게 되면 빈 리스트 + Optional.empty라는 이중 표현이 되어 혼란이 야기 된다.

이렇게 되면 반환 받은 측에서는 옵셔널을 푸는 작업을 추가로 해야하기 때문에 유지보수가 구려진다.

컬렉션 국룰은 null보단 빈 자료구조를 반환하는 것이기 때문에, 정말 컬렉션이 비었다면 다음과 같이 사용하여 비었음을 표현한다.

public List<String> getUserRoles(String userId) {
	// ...
	if (!foundUser) {
	// 권장: 빈 리스트 반환
	return Collections.emptyList();
	}
	return userRolesList;
}

 

4. get() 보단 orElse() 또는 orElseGet() 이해하고 쓰기

get() 메서드를 쓰게 되면 어쩔 수 없이 null 방어로직을 짜야한다. 즉 그냥 null을 쓰는 것과 별 차이가 없기 때문에 orElse나 orElseGet을 쓰도록 하자.

단 이전 포스팅에서 언급했던 것 처럼 즉시평가와 지연평가 전략을 반드시 이해하고 사용해야한다.

요약하면 대체값의 생성 비용이 크지 않다면 orElse를 써도 되고, 아니라면 orElseGet을 사용하여 지연평가 전략을 택하자.

 

5. 명분은 좋지만 무조건 좋은건 아님

Optional이 안정성을 높이는 것은 분명하지만 무분별하게 무조건 사용하면 오히려 코드 복잡도가 높아진다.

Optional이 굳이 필요없는 상황을 보자

1. 항상 값이 있으면 필요 없음 -> 로직상 절대 null이 나오지 않는다면 그냥 raw타입을 사용하거나 방어적 코드로 예외를 던지는 편이 낫다.

2. 값이 없으면 예외를 던지는게 분명 더 나은 상황 -> DB등에서 ID를 통해 무조건 존재하는 엔티티를 찾아야하는경우 Optional을 반환 하기 보단 NoSuchElementException등을 던지는 편이 나을 수도 있다.

3. 값이 대부분 채워져 있는 경우에는 안쓰는게 낫다 -> 옵셔널을 쓰게 되면 해당 내부 값을 얻기 위한 로직이 반드시 필요하기 때문에 코드가 장황해질 수 있다.

4. 성능이 극도로 중요할 때 -> 어째 되었든간 옵셔널도 객체이기 때문에 for문 내부 등 단기간에 만은 객체가 생겨나는 영역에 사용시 성능 영향을 줄 수 있다.


정리

1. 필드로 쓰지말고 반환값에 쓰자

2. 메서드 파라미터로 옵셔널 받지 말자

3. 컬렉션이 비었다는 옵셔널보단 빈 컬렉션 반환

4. 즉시 평가와 지연 평가 개념을 알고 get() 대신 사용 -> 안정성

5. 무조건 Optional이 좋은건 아님 -> 예외가 적절하면 예외를 던지는게 나을 때도 있다.

 

옵셔널 끝 ~~~~

xuv2
@xuv2 :: xuvlog

폭싹 늙었수다

목차