3.7 KiB
3.7 KiB
Optional 사용 예시
이 문서는 Optional 사용 기준을 코드 예시로 확인하기 위한 자료입니다.
핵심 기준은 Optional을 값의 부재가 가능한 단건 반환 타입에 쓰고, 필드/파라미터/직렬화 경계에는 기본적으로 쓰지 않는 것입니다.
좋은 예시 1: 단건 조회 결과 없음 표현
public Optional<User> findByEmail(String email) {
return userRepository.findByEmail(email);
}
왜 좋은가:
- 단건 조회 결과의 부재를 반환 타입에서 명시한다.
- 호출자에게 "없을 수 있음"을 강제한다.
좋은 예시 2: transform chain
public Optional<String> findActiveUserEmail(Long userId) {
return userRepository.findById(userId)
.filter(User::isActive)
.map(User::getEmail);
}
왜 좋은가:
isPresent() + get()없이 선언적으로 표현한다.- 값이 없으면 자연스럽게 empty로 전파된다.
좋은 예시 3: nested Optional 방지
public Optional<Token> resolveToken(Long userId) {
return userRepository.findById(userId)
.flatMap(tokenService::findValidToken);
}
왜 좋은가:
flatMap으로Optional<Optional<Token>>를 만들지 않는다.
좋은 예시 4: expensive default는 orElseGet
UserProfile profile = profileRepository.findByUserId(userId)
.orElseGet(() -> profileFactory.createDefault(userId));
왜 좋은가:
- 기본값 생성 비용이 있을 때 lazy supplier를 사용한다.
좋은 예시 5: 단건은 Optional, 다건은 빈 컬렉션
public List<Role> findRoles(Long userId) {
return roleRepository.findAllByUserId(userId);
}
왜 좋은가:
- 다건 결과의 부재를
Optional<List<Role>>로 감싸지 않는다. - 호출자는 빈 컬렉션으로 처리하면 된다.
나쁜 예시 1: Optional 반환인데 null 반환
public Optional<User> findByEmail(String email) {
return null;
}
문제:
Optional자체가null이 되어 의미가 깨진다.- 호출자는
Optional과null을 둘 다 처리해야 한다.
개선:
public Optional<User> findByEmail(String email) {
return Optional.empty();
}
나쁜 예시 2: 필드에 Optional 저장
public class UserResponse {
private Optional<String> nickname;
}
문제:
- DTO 경계에서 표현이 복잡해진다.
- 직렬화/스키마/API 계약이 불명확해질 수 있다.
개선:
public class UserResponse {
private String nickname;
}
또는 nullable 여부를 API 계약에서 명시한다.
나쁜 예시 3: 파라미터에 Optional 사용
public User createUser(Optional<String> nickname) {
// ...
}
문제:
- 호출자가
Optional.empty()와null실수를 섞기 쉽다. - 오버로드/명시적 request object보다 의도가 약하다.
개선:
public User createUser(String nickname) {
// ...
}
또는:
public User createUser(CreateUserCommand command) {
// ...
}
나쁜 예시 4: isPresent + get
if (userOpt.isPresent()) {
return userOpt.get().getEmail();
}
return "unknown";
문제:
- imperative null-check와 다를 바 없는 패턴이다.
get()의존이 생긴다.
개선:
return userOpt.map(User::getEmail)
.orElse("unknown");
나쁜 예시 5: map 결과를 안 쓰고 side-effect
userOpt.map(user -> {
audit(user.getId());
return user;
});
문제:
map은 값 변환인데 반환값을 사용하지 않는다.- side-effect 목적이면
ifPresent가 더 맞다.
개선:
userOpt.ifPresent(user -> audit(user.getId()));