JAVA/Optional

Optional 과 null 처리에 대한 부분

수달하나 2024. 7. 15. 14:48

Null 처리에 관한 탐구와 Optional

null 처리를 어떤 방식으로 진행 해야 할 것인가에 대한 부분은 언제나 고민이 된다.
함수형 프로그래밍을 도입하며 객체지향적 개발을 지향하는 현 시점의 프로그래밍 기법이 과연 null 을 명시적으로 처리하는 완벽한 방법을 제공하지 않는다는 것이 어쩌면 놀랍기도 하다.

물론 null 처리를 위한 Optional 이라는 강력한 기능이 추가되었지만 이 기능이란것이 생각보다 많은 부분에서 사용되지 않는다는점, 이미 진행된 프로그래밍에 적용함으로써 일관성을 해칠 수 있다는 점에서 사용을 꺼리게 된다.
또한 애초에 모든 null 처리를 Optional 로 진행하는 것을 권장하지 않다 보니 명시적 null 처리를 진행해야 하는 부분이 반드시 발생한다는 것을 생각해보면 Optional 은 아직까지 미완성인 느낌이 강하다.

그럼에도 불구하고 명시적 null 처리를 감소시킴으로써 코드의 위험성을 줄인다는 점은 명확하니 어떠한 방법으로 Optional 을 사용해야 하는지 기술적인 부분에서 벗어나 인문학적인 접근 방법을 통해 생각해 볼 필요가 있다고 느껴진다.

 

* 명시적 null 처리는 코드상에 null 을 직접적으로 선언하는 것을 기준으로 얘기했다.


트러블 슈팅

파라미터의 null 처리로직 추가

최근 특정 객체의 형태로 파라미터를 전달받아 특정 로직을 처리하는 매서드를 수정할 일이 있었다.
수정의 목적은 특정 객체가 완전히 null 값으로 전달 되는 상황을 추가하는 것 이었다.
가장 먼저 생각난 방법은 파라미터로 Optional 의 형태로 전달 받는 것 변경한 코드는 나쁘지 않은것 같았다

명시적으로 "해당 파라미터는 인스턴스객체가 올 수도 있고 null 이 전달 될 수도 있습니다" 라는 것을 한 눈에 파악 할 수 있으며 해당 Optional 이 주는 기능을 통해 null 처리로부터 벗어날 수 있었다.

warning 의 경고를 보기 전 까지는 말이다.
왜 Optional 의 파라미터화는 권장할 사항이 아닌것인가?


우선 문제 해결을 위해 그리고 null 처리를 피하기 위해 메서드 오버라이드를 도입하여 두 가지 메서드를 각각 만들어 문제를 해결하였지만 Optional 에 대한 처리가 명확하게 받아들여지지는 않았다.

 

Optional 의 파라미터화

해답은 명확했다.

Optional<Obeject> 또한 Object 이기 때문이었다
파라미터로 Optional<Object> 를 전달 받게 되면 값이 있는 Optional과 값이 없는 Optional 만 존재할 것 같지만 Optional 자체도 null 인 경우가 발생하게 된다.

아래와 같은 코드가 컴파일 에러로 잡혀있다면 파라미터의 Optional 처리가 가능할 것 같지만 안타깝게도 경고 수준의 에러를 발생시킬뿐 컴파일이 가능하다.

Optional<Test> optionalTest = null; // 가능

 

따라서 Optional 의 파라미터화는 아래와 같은 이중의 null 처리 과정을 진행하게 된다.

public void OPtionalTest(Optional<Test> optionalTest){
    if(optionalTest != null){
        if(optionalTest.ifPresent()){
        	// 해당 로직을 수행
        }else{
        	// optionalTest 의 값이 없을 경우
        }
    }{
    	// optionalTest 가 null 인 경우
    }
}

 

null 처리를 위해 Optional 을 도입했지만 Optional의 null 처리를 위해 null 체크를 다시 한 번 수행해야하는 초 비효율적인 코드를 적용할 수 밖에 없어진 것이다.
따라서 Optional의 파라미터화는 명확하게 권장하지 않는 것이었다.

 

그렇다면 파라미터의 null 처리를 효율적으로 할 수 있는 방법은 무엇일까?


파라미터의 null 처리

파라미터로 부터 넘어오는값을 null 처리 하기 위해서 어떤 효율적인 방법이 있을까에 대한 부분을 생각하다가 상황에 따라서 여러가지 방법을 적용해 보는것도 좋을것 같다고 생각했다.

 

첫 번째로 Optional 을 파라미터로 받지는 않지만 아래와 같은 방식으로 null 처리를 하는 것도 나쁘지 않을것 같다는 생각이 들었다.

public void optionalTest(Test test){
	Optional.ofNullable(test).orElseThrow(() -> new IllegalArgumentException());
}

 

하지만 명시적인 null 처리를 없애기 위해서 Optional 이라는 객체를 사용하는것은 배보다 배꼽이 더 클 수도 있는문제.

다른 방법이 좀 더 좋을것 같다.

 

두 번째는 javax.validation 패키지를 이용하여 어노테이션을 활용하는 방법도 괜찮을것 같다는  생각을 했다.

파라미터에 @NotNull 어노테이션을 달아 자동적으로 null 체크를 하면 명시적인 null 체크를 피할 수 있을 것 이다.

public void test(@NotNull Test test){
	// null 이 아닌 Test 객체만 허용 가능
}

 

혹은 전략 패턴을 이용하여 null 처리를 할 수도있겠지만 수많은 메서드의 수많은 파라미터들을 모두 전략화 한다는 것은 사실상 불가능할 것 이라는 생각이 들어서 그 방법은 고려하지 않는게 좋을것 같다.


Optional 을 이용한 null 처리

Optional 권장 사항

그럼 Optional을 이용하여 처리 할 수 있는 권장되는 상황은 무엇일까?

 

Optional 을 사용하는 궁극적인 목표는 여러가지가 있겠지만 코드의 가독성과 같은 뻔한 이야기는 더이상 의미가 없을것 같다.

왜 사용하는가 에 대한 궁극적 해답을 얻기 위해서는 Optional 로 얻을 수 있는 이점이 무엇인가에 대한 부분이 답이 될 수 있을것이라고 생각한다.

 

Optional 을 사용한다는 것은 해당 객체가 null 값인지 혹은 null 값이 아닌지 에 대한 부분을 잊지 않고 확인할 수 있기 때문이다. 

일반적으로 객체 그대로를 사용하면 개발자가 놓칠 수 있는 NullPointerException 에 대한 발생 위험이 존재 하지만 Optional을 사용함으로써 해당 객체를 가져오는 과정에서 null 체크를 진행하고 그에 따라 NullPointerException 에 대한 위험성으로 부터 자유로워 진다.

 

따라서 가장 기본적인 활용은 메서드의 반환값으로 사용하는것이 권장되는 사용방법이다.

메서드의 반환값을 Optional 로 반환받음으로써 그 값을 명시적 null 처리가 아닌 Optional 이 제공하는 메서드를 사용하여 우회함으로써 NullPointerException에 대한 위험성을 줄이고 동시에 함수형 프로그래밍이 가지는 장점까지 적용시킬 수 있기 때문이다.

 

모든 메서드에 Optional을 반환하는 것에 관하여

Optional 을 모든 메서드의 반환값에 적용한다면  NullPointerException 으로 부터 완벽하게 자유로워 질 수 있을까?

 

개인적인 생각은 많이 도움이 될 것이라고 생각한다.

최소한 메서드 호출로부터 오는 위험성은 줄일수 있을 것이기 때문이다.

 

그럼에도 불구하고 모든 상황에서 그런 방식의 접근을 사용하지 않는 이유는 굳이? 라는 생각과 성능의 저하 에 대한 부분이 걸리기 때문인것 같다.

 

맨처음 나는 많은 부분에 있어서 Optional로 null 처리를 하려고 했고 코드리뷰를 진행하면서 사수가 이런 의문을 제기했다.

굳이 null 처리를 하지 않아도 되는 부분에 Optional 을 사용하고 있다.

 

null 이 아닌 값이 명확한데 Optional로써 처리하는 부분에 있어서 문제가 있다고 얘기를 했고, 이 부분은 동의 할 수 있는 부분도 있었고 동의 할 수 없는 부분도 있었다.

왜냐하면 어느정도는 확장성을 열어두는것이 나쁘지 않다고 생각했기 때문이다.

 

요구조건은 추가 되고 코드는 항상 변화 한다.

내가 개발을 하면서 가장 신경쓰는 부분이다.

 

그 과정에서 null일 수 없던 값들이 null 이 될 수도 있고 null 일수 있던 값들도 null 이 될 수 없는 상황으로 변화하기도 하는데 이러한 것들을 유연하게 적용하려면 Optional 이 의미있게 적용 될 수도 있다고 생각했기 때문에 현재 상황에 있어서 null 이 될 수 없는 경우라도 확장의 가능성을 염려해 둔다면 Optional의 반환값을 가지는 형태로 구성해도 된다고 생각했다.

물론 실제로 변화의 가능성을 어느정도 까지 바라보는가에 대한 부분이 크게 영향을 미칠 것이라고 생각한다.

 

첫 번째 의문 대한 의구심을 뒤로한 채 두번째 질문은 명확했다.

"Optional 로 모든 함수의 메서드를 감싸는 것은 비용이 너무 발생합니다." 

 

그 당시 개발중인 부분은 성능이 매우 중요한 프로세스 였고 그 부분을 간과 하고 있었다.

코드를 수정하면서 얻은 것은 무조건 적인 함수형 프로그래밍의 도입이 항상 좋은 결과를 도출하는 것은 아니라는 것 이었다.


정리

명시적 null 처리에 관한 것이 옳은 것일까, 옳지 않은것일까를 떠나서 모든 코드 에서 없애는 것은 불가능하다.

 

어쩌면 null 처리를 진행함으로써 코드의 명확성을 보여주고 누군가에게는 그 방법이 오히려 유연한 처리를 할 수 있도록 한다는 점에서 과연 "이것이 정답이다" 라고 누가 말을 할수 있을까?

 

그럼에도 불구하고 null 처리를 유연하게 하기 위한 방법들로 객체지향 적이며 함수형 프로그래밍스러운 코드가 JAVA 뿐만 아니라 많은 언어와 프레임워크에서 계속적으로 업데이트 되고 있다는 것을 감안한다면 우리는 어떤 방식으로 null 처리를 해야 하는 것일까에 관한 고민을 계속적으로 해야 된 다고 생각한다.

 

정답은 없겠지만 그래도 조금 더 유연하게 해결 할 수 있는 방법론을 계속 고민하도록 해야겠다.