ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Optional 아무렇게나 사용하고, 장애 발생시킨 이야기
    Java & Kotlin 2022. 3. 15. 01:09
    반응형

    1) 서론

    당연한 것이라고 생각하며 한 번도 의심해보지 않은 것이, 사실은 아니었던 경험이 있으신가요? 저는 TV에서 나오는 맞춤법 퀴즈를 볼 때 종종 "이게 정답이라고?" 라는 생각을 하기도 하는데요.

     

    최근 개발을 하던 중 사용한 Java의 Optional이 저에게는 그런 존재입니다. 

     

    Java8의 시대부터, 아마도 자바 개발자에게 Optional의 사용은 필수가 됐을 것 같습니다. 저는 NPE를 방지하기 위해서, 습관적으로 사용하곤 했었는데요.

     

    이러한 Optional을 무분별하게 사용하다가, 시원하게 장애를 발생시킨 경험입니다.

     


    2) 상황 설명

    1. 값을 조회합니다
    2. 값이 있다면, Exception 발생합니다.

    • Post 테이블의 title이 'title'인것을 조회합니다.

    • 데이터는 존재하지 않습니다.

    • 앞서 'title'에 해당하는 게시글이 없는것을 확인했습니다.
    • isPresent = false 예상합니다.
    • 테스트는 성공 예상합니다.

     

    • 결과는 isPresent = true 입니다.
    • 예상과 다르게, 테스트 실패합니다.

    3) 장애 상황

    실제 서비스에 적용된 로직은 위와 같습니다. 있다면 Exception을 던지고, 없다면 정상입니다. 

     

    하지만 실제 서비스에서는 해당 부분에서 항상 true, 즉 항상 존재한다는 결과가 나오고, 계속해서 Exception이 발생했습니다.

     

    처음에는 실제 DB 데이터 조회 및 로직을 눈으로 봐서는 도저히 파악할 수 없었습니다. 복잡한 로직도 아니고, 왜 모든 경우에 true인지 도저히 이해하지 못했는데요.

     

    비슷한 로직을 만들고, 단위 테스트를 통해 재현 후 구글 검색을 아주 열심히 한 뒤에 범인을 찾았습니다.


    4) 문제 원인

    원인은 Optional<List<Object>> 입니다. 

     

    Spring Data JPA의 query method는 Collections, Stream 타입일 때는 null이 아닌, empty 객체를 반환합니다. 

     

    하지만 Optional.isPresent()는 해당 값의 null 여부만 확인합니다. 즉, emtpy 객체는 항상 true를 반환할 수밖에 없었습니다.

     


    5) 해결 방법

    • List.isEmtpy()를 사용해서 List 객체의 element 존재 여부를 확인합니다.
    • 만약 값이 존재한다면, Exception 발생합니다.

     

    • 테스트 성공합니다.

     

    사실 더 간단한 해결법은 List를 사용하지 않으면 됩니다. 그렇다면 값이 없을 때 null 객체로 반환되고, 쉽게 확인 가능합니다. 지금 회고를 하면서 생각했을 때는 값이 존재하면 exception 발생하기 때문에, List가 아닌 객체를 사용해도 됐을 것 같습니다.


    6) Optional.isEmpty() 사용하면, 더 간단한데요?

     

    위와 같이 Optional.isEmtpy()를 사용하면, 편할 것 같습니다. 하지만 이름과 다르게 List 객체에 대한 empty를 확인할 수 없습니다.

     

    • Optional.isEmtpy() 입니다.
    • 반환된 값의 null 여부만 확인합니다.
    • 즉 여전히 emtpy를 확인할 수 없습니다.

     

    • 해결 방법에서 사용한 List.isEmtpy() 입니다.
    • List 객체에 element 존재 여부를 확인합니다.

     

    위와 같은 이유 때문에 보기에는 조금 불필요해 보이지만, List.isEmtpy를 활용해서 해결했었습니다.


    7) 결론

    사실 그동안 Optional은 약간 "부적"처럼 습관적으로 사용했습니다. Optional을 사용하면 왠지 모든 장애를 방지할 수 있을 것 같았습니다. 정확히 어떻게 동작하는지도 모른 채요.

     

    하지만 믿는 도끼에 발등이 찍히고, 저는 서비스 장애를 발생시켰습니다.

     

    이번 장애를 바탕으로 많이 느꼈습니다.

     

    아주 단순해 보이는 코드 한 줄이라도 이해하지 못 한채 작성하면, 장애를 발생시킬 수 있습니다.

     

    또한 로직의 실패, 성공 케이스에 해당하는 단위 테스트는 필수일 것 같습니다. 사실 바쁘다는 핑계로 오직 "성공" 케이스에 해당하는 테스트 코드만 작성했습니다. 이렇게 하면 무조건 성공이라고 가정한 채로요. 하지만 이러한 테스트 코드는 진정한 테스트가 되지 못했습니다.

     

    마지막으로 회고의 중요성입니다. 장애의 원인을 재현 후 다시 실수하지 않도록 학습하는 것도 중요하고, 과거보다 더 좋은 해결책이 존재했을지 한번 더 생각할 수 있습니다.


    8) 참고 문헌

    https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.nullability

     

    반응형

    댓글

Designed by Tistory.