자바 옵셔널에 대하여

seungho choi·2022년 5월 6일
1

개요

코드숨 과제를 하던 중에 null safty에 대한 피드백을 받았다.

   private Map<Long, Task> taskMap = new HashMap<>(); 
    
   public Task findById(Long id) {
        return taskMap.get(id);
    }

findByIdtaskMap Task 를 반환하는 메소드이다. 만약에 taskMapTask가 존재하지 않으면 사용자 입장에서 반환하는 Task의 메소드의 호출시 NullPointException 을 던진다. 이런 부분에서 윤석님께서 이런 피드백을 주셨다.

taskMap에서 우리가 원하는 task를 찾지 못할 경우 null을 반환하고 있는 것 같아요.이 함수를 사용하는 입장에서는 이 함수가 null을 반환할 수 있는지 예상할 수 없고, 그리고 null처리를 하지 않아도 자바 컴파일러는 아무런 불만도 표현하지 않기 때문에 위험성이 있는 것 같아요. 만약 반환 타입을 Optional 그대로 반환한다면, 함수의 Signature에서 이 값이 있을 수도 있고 없을 수도 있다는 의도를 드러내고 있고, 없는 경우를 처리하지 않으면 컴파일도 되지 않아서 에러 처리를 강제할 수 있어요
하지만 Optional 반환은 다른 부분이 어떻게 만들어졌냐가 관건이라고 보는데, 함수형으로 처리 중이 아니라면 예외가 가장 일반적이죠. Optional을 리턴하면 사용하는 쪽에서 비슷한 코드를 작성해야 하기 때문에 사용하기 힘든 인터페이스가 될 가능성이 커요.

말씀하신 내용을 이렇게 해석했다.

  1. null을 반환 할 수 있는 메소드에 Optional 로 반환하면 메소드의 의도를 명확하게 드러낼 수 있고 처리를 강제할 수 있다.

  2. Optional의 사용방법이 여러가지 있으므로 비슷한 코드를 작성하기 힘들 수 있다.

사실 옵셔녈이 뭔지는 알고 있었지만 어떻게 사용할지는 고민을 해본적이 없었던거같아 요번 기회에 정리를 해보았다.

옵셔널은 언제 사용해야 할까?

Optional 이거 너무 좋은거 같은데? 모든 객체에다가 Optional을 래핑하면 되지 않냐라고 생각 해봤다.

먼저 자바의 아키텍처인 Brian Goetz 라는 아저씨가 이런 말을 하셨다.

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

번역기를 돌려봤다.

Optional은 null을 나타내는 명확한 방법이 필요한 라이브러리 메서드 반환 유형에 대해 제한된 메커니즘을 제공하기 위한 것이다 라고 했다.

결론은 메서드 반환 타입에 대해 제한하고 명확하게 할려고 만든것이라고 한다.
필드나 매개변수에 적용하는 것은 수 많은 곳을 뒤져 보았지만 전부 안티패턴이라고 한다. 왜 안티패턴 일까?

첫번째 필드 같은경우에는 Optional 으로 래핑된 필드는 객체 직렬화가 지원이 안된다.

그리고 모든 필드 메서드에 Optional 으로 되어있다고 생각하고 그걸 사용 한다니까 너무 끔직하다...

그리고 개인적인 생각이지만 필드와 매개변수에 Optional 을 사용하며 안되는 가장 큰 이유는 책임 이라고 생각한다.

먼저 필드 값 같은 경우에는 올바른 생성자를 메소드를 제공 함으로써 필드 값을 할당하는 책임이 있다. 메소드 또한 들어온 매개변수를 바탕으로 적절한 행동을 해야할 의무가 있다.

하지만 생성자 메소드나 메소드를 사용하는 입장에서 매개변수를 null를 넣는다면? 그거는 온전히 사용자의 잘못이라고 생각한다. 메소드 입장에서는 친절히 적절한 타입의 변수를 넣으라고 했는데 null을 넣어버리고 올바른 결과 값을 기대하는 것은 자판기에 돌맹이를 넣고 음료수가 나오길 기대하는것과 같다고 생각한다.

하지만 올바른 매개변수가 들어왔을 때 메소드 입장에서는 그에 합당한 일을 해야하는게 맞다. 여러 문제로 올바른 값을 반환하지 못할 수도 있다. 예를 들어 데이터베이스에 값이 없거나, 위에 같은 문제인 Map 에 키 값에 매칭되는 값이 없거나 할 때이다.
그럴 경우 적절한 Excpetion (언체크 예외는 주석으로 작성해 IDE의 도움을 받을 수 있게 하자) 을 던지거나 Optional 을 반환해 사용자가 적절한 행동을 할 수 있게끔 하게 하는것이 메소드의 책임이라고 생각한다.

올바른 사용 방법들

  • Optional 대신 빈 컬렉션을 반환하자 빈 컬렉션을 반환하는것 만으로 충분히 의미를 분명하게 할 수 있다. 컬렉션 API는 isEmpty()size() 라는 메소드를 제공하고 있지 않은가

  • 옵셔널 메소드의 get() 이나 isPresent() 사용하지 말고 대신 orElseXXX() 사용하자 코드 가독성 측면에서 굉장히 좋아 질것이다.

    Optional<Task> optionalTask = findById(id);
    Task task;
    
    if (task.isPresent) {
        task = optionalTask.get();
    } else {
        throw new TaskNotFoundException();
    }

    이런식으로 굉장히 지저분한 코드가 작성되는데

    Task task = findById(id)
        .orElseThrow(TaskNotFoundException::new);

    이런식으로 한번에 짤 수가 있다.

  • 원시 타입은 OptionalXXX 를 고려 하자

    OptionalInt maybeInt = OptionalInt.of(2);

참고 자료

0개의 댓글