본문 바로가기
Java

method return에 대한 고민

by ybs 2021. 2. 7.
반응형

method 를 만들 때 정상적인 flow 가 아닌 상황을 고려하다보면 null, Optional<T>, exception 3가지를 활용하게 된다.

 

rsa 키를 달라는 요청을 받을 때, 미리 만들어둔 키를 캐시에서 가져오고 캐시에 없으면 새롭게 만드는 로직을 예제로 살펴보자.

public RsaKeyData getRsaKeyData() {
    RsaKeyData rsaKeyData = getCacheData();
    if (rsaKeyData == null) {
        return generateRsaKeyData();
    }

    return rsaKeyData;
}

 

위 코드에서 이 메서드가 null 을 리턴하면 새롭게 rsa 키를 만들도록 되어있다.
그런데 getCacheData 메서드 결과가 null 이라는건 무슨뜻일까?

 

1. 캐시에 rsa 키들이 아예 없는걸까
2. 캐시를 조회 하다가 실패한걸까(예를 들어 네트워크 오류)
3. 캐시에 있는 rsa 키가 만료일이 다되는 등 정책적인 비지니스 문제일까

 

어떤 이유가 됐든 정상적으로 캐시에서 가져오지 못했으니 새롭게 키를 만들도록 하는건 똑같지만 의미는 명확하지 않다.
그럴 수밖에 없는게 아래 getCacheData 코드를 보면 각각 다른 예외적 상황에서도 null 을 리턴하도록 되어있다.

private RsaKeyData getCacheData() {
    List<RsaKeyData> cacheList;
    try {
        cacheList = selectCacheList();
    } catch (Exception e) {
        return null;
    }

    if (cacheList.size() == 0) {
        return null;
    }

    // 예제 단순화를 위해 첫번째꺼만 검사 및 리턴
    if (LocalDateTime.now().isAfter(cacheList.get(0).getExpireDateTime())) {
        return null;
    }

    return cacheList.get(0);
}

 

만약 null 대신 Optional<RsaKeyData> 을 리턴하게 하면 어떨까?

public RsaKeyData getRsaKeyData() {
    Optional<RsaKeyData> rsaKeyData = getCacheData();
    if (rsaKeyData.isPresent()) {
        return rsaKeyData.get();
    }

    return generateRsaKeyData();
}

 

getCacheData 메서드의 결과를 Optional 로 받아서 isPresent 메서드로 키 값을 확인하고 없으면 새롭게 만든다.
Optional 타입을 씀으로써 키가 없을 수 도 있다는걸 명시적으로 알 수 있게 해줬다.

그런데 사실 null 리턴하고 null 체크하는것과 크게 달라지진 않는다.

 

아래 코드처럼 orElseGet 메서드를 활용하는 방법도 있다.

public RsaKeyData getRsaKeyData() {
    Optional<RsaKeyData> rsaKeyData = getCacheData();
    return rsaKeyData.orElseGet(() -> generateRsaKeyData());
}

그러나 개인적으로 orElseGet 네이밍이 한번에 와닿지 않아서 차라리 if 문 한번 더 쓰고 있다.

cf) 이펙티브 자바에서는 orElseCompute 이름이 더 낫다고 말한다.

 

getCacheData 메서드는 null 리턴 대신 Optional.empty() 리턴으로 바뀐게 전부다.

private Optional<RsaKeyData> getCacheData() {
    List<RsaKeyData> cacheList;
    try {
        cacheList = selectCacheList();
    } catch(Exception e) {
        return Optional.empty();
    }

    if (cacheList.size() == 0) {
        return Optional.empty();
    }

    // 예제 단순화를 위해 첫번째꺼만 검사 및 리턴
    if (LocalDateTime.now().isAfter(cacheList.get(0).getExpireDateTime())) {
        return Optional.empty();
    }

    return Optional.of(cacheList.get(0));
}

 

마지막으로 예외 처리에 대해서 살펴보자.


캐시에 키가 없을 경우 generateRsaKeyData 메서드를 통해서 새롭게 키를 만드는데 rsa 키를 생성하면서 checked exception 로 인해 강제적으로 예외처리를 해줘야 한다.

private RsaKeyData generateRsaKeyData() {
    try {
        KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");

        // ... 생략
    } catch (NoSuchAlgorithmException e) {
        throw new RsaKeyGenerateException("fail to generate rsa key", e);
    }

    RsaKeyData rsaKeyData = new RsaKeyData();
    rsaKeyData.setKey("test");
    return rsaKeyData;
}

 

키 알고리즘 예외가 발생했을 때는 복구 로직을 두지 않고 빠르게 예외를 알려서 요청 실패로 처리하는게 낫다고 생각한다. rsa 키를 만들라는 요청에서 키를 만드는게 불가능하다면 복구가 불가능하다고 봐야한다. 더 실행해봐야 득보다는 실이 많다. 그래서 unchecked exception 으로 exception translation 을 하게 했다. 또한 NoSuchAlgorithmException(저수준 레벨 예외) 을 RsaKeyGenerateException 로 전환하면서 추상화 수준을 맞췄다.

 

 

아까 캐시에서 조회할 때 try-catch 와 비교하면, 캐시 조회가 실패할 때는 새롭게 키를 만드는 방식으로 복구가 가능하기 때문에 예외 전환을 하지 않았다.

 

try {
    cacheList = selectCacheList();
} catch(Exception e) {
    return Optional.empty();
}

 

그리고 Optional 에 관해 이펙티브 자바를 읽어봤는데 아래와 같이 복잡하게 쓰지 말라고 한다.

// 쓸데없이 복잡성만 높여서 혼란과 오류 가능성을 키울 뿐
Map<String, Optional<String>> badMap = new HashMap<>();
Map<Optional<String>, String> badMap2 = new HashMap<>();

Optional<List<String>> badOptional;
Optional<String[]> badOptional2;

또 Optional 에 대해서 Nomad 관점으로 볼 수 있는데 yangbongsoo.tistory.com/24 여기에 따로 정리했다.

 

마지막으로 도메인 모델에 Optional 을 사용했을 때 데이터 직렬화를 못한다. 자바 언어 아키텍트인 브라이언 고츠가 Optional 의 용도를 선택형 반환값을 지원하는 것으로 명확하게 못박고 필드 형식으로 사용할 것을 가정하지 않았다. 그래서 Serializable 인터페이스를 구현하지 않는다. 자바8인액션 저자는 그럼에도 Optional 을 사용해서 도메인 모델을 구성하는걸 바람직하게 생각한다. 그래서 직렬화 모델이 필요하면 아래와 같이 Optional 로 값을 반환받을 수 있는 메서드를 추가하는 방식을 권장한다.

public class Person {
	private Car car;
    public Optional<Car> getCarAsOptional() {
    	return Optional.ofNullable(car);
    }
}

 

참고 : 이펙티브 자바 item 55, 10장 예외
참고 : 토비스프링 예외

참고 : 자바8인 액션

반응형

'Java' 카테고리의 다른 글

Singleton(면접단골질문)  (0) 2021.05.09
Optional 형태가 가지는 의미(Monad)  (0) 2021.05.06
reflection 사용해서 api 문서화  (0) 2021.05.02
오버로딩vs오버라이딩(면접단골질문)  (2) 2021.01.22
Finalizer attack  (3) 2021.01.17