본문 바로가기

Language & Framework/삽질기록

삽질 기록 (8) 삼항연산자의 기이한 NullPointerException을 겪고 있다면.. 이리오십쇼

 

 

 

오늘은 22년 10월 14일이다.

아마 한 달 뒤에나 예약글로 올라갈 것 같다..

 

아무튼 오늘 팀원 분께서 귀신 들린 에러가 났다고 도움을 청하셨다.

그런데 정말 귀신들린 에러라는 말이 딱 어울리는 상황이였다.

 

 

 

 

이게 기존 코드다.

주목해야할 부분은 UserInfo.getId()이며, 우리는 해당 부분을 별도로 테스트를 분리했기 때문에 저 부분은 테스트 시에 늘 Null이 들어가며, Null이 들어가도 문제가 없는 로직이다.

이 상태에서는 아무런 문제 없이 테스트가 실행된다.

 

그런데 버그가 발견되어서 코드를 일부 수정해야 했다.

 

 

 

이렇게 말이다.

근데 이 상태에서 테스트를 돌리면 NullPointerException이 발생한다.

 

정말 귀신 들렸다는 말이 딱 맞다.

무슨 상황인지 정리하자면..

 

1. userInfo.getId() (null) 
아무 문제 없이 잘 된다.
2. userInfo==null ? 0L : userInfo.getId()
테스트 시 멤버 변수(id)가 Null일 뿐 userInfo는 존재하여 userInfo.getId가 선택된다.
3. NullpointException
???

 

 

userInfo.getId()는 되는데, userInfo==null ? 0L : userInfo.getId()에서 userInfo.getId()가 선택되면 NullpointerException이 터진다고?

게다가 비슷한 로직이 다른 곳에도 있는데 그 쪽은 아무 문제 없이 테스트가 실행된다.

 

postman으로 기능테스트를 해봐도 아무 문제가 없고, 디버깅을 해봐도 마땅히 나오는 게 없었다.

 

그렇게 다 같이 여러모로 찾아보고 고민하기를 1시간 가량 보냈고 도움을 요청하셨던 팀원분이 일단은 넘길까요..?라고 하실때쯤 문득 생각이 나는 게 있어서 "잠시만요 잠시만요 제가 하나만 해볼게요!"라고 외치고 시도해본 그것..

 

 

 

이렇게 수정하니 잘만 통과한다.

근데 테스트는 통과했지만 마음은 더 괴로워졌다.

이렇게 지저분한 코드는 용납할 수 없고, 왜 이렇게 된 건지 모르니 도저히 놔줄 수가 없었다.

 

userInfo.getId() => 잘 된다

if (userInfo!=null) { userInfo.getId() } => 잘 된다

userInfo==null ? 0L : userInfo.getId() => 안된다

 

ㅋㅋㅋㅋㅋㅋㅋㅋㅋ

헛웃음만 나오는 결과다.

 

그러나 다시 생각해보니, 이전까지는 삼항 연산자의 결과물을 너무 당연하게 믿고 있었기 때문에 그 쪽은 의심조차 하지 않았는데, if문으로는 되고 삼항연산자로 안 된다는 것은 우리가 뭔가 모르는 게 있는 것이라는 생각이 들었다.

 

 

 

 

Java ternary conditions strange null pointer exception?

누가 봐도 내가 애타게 찾던 글이다.

 

 

 

 

 

처음엔 어이가 없어서 웃음만 나왔지만 규칙을 찾아내고 말았다.

 

 

 

final Integer a = null;
final Integer b = false ? 0 : a;
-> NullPointerException
final Integer b = false ? 0 : null;
-> 정상
final Integer a = null;
final Integer b = true ? 0 : a;
-> 정상
final Integer a = null;
final Integer b = false ? new Integer(0) : a;
-> 정상

 

NullPointerException이 발생하는 코드와 그렇지 않은 코드의 차이를 찾아보자.

 

1. 예외가 발생하는 코드는 false이기 때문에 먼저 0을 만나고 그 다음 null인 a를 만났다, 이후 Exception이 발생한다.

2. b는 false이기 때문에 먼저 0을 만나고, null을 만났다. 예외가 발생하지 않았다.

3. b = true이기 때문에 0을 읽고 바로 연산이 끝났고, null인 a를 만나지 않으니 예외가 발생하지 않았다.

4. b = false이기 때문에 new Integer(0)을 먼저 읽고, 그 다음 Null인 a를 만났으나 예외가 발생하지 않았다.

 

가장 유사한 1번과 4번의 차이는 무엇일까?

1번은 그냥 primitive type인 0을 사용하고 있고 4번은 Integer(0)을 사용하고 있다는 것이다.

 

그렇다..

삼항연산자에서 비교를 위해 둘 중 하나가 primitive type이면 나머지 하나도 primitive type으로 비교하려고 하는데, Integer 내부에 null이 있으니 int형으로 비교할 수 없어 NullPointEception이 발생하는 것이다..

 

 

 

 

 

그리고 정말 놀랍게도 (Long) 하나 붙이니 1시간동안 팀원들을 속 썩이던 테스트가 한 번에 통과되었다.

사실 오직 테스트만을 위해 매번 박싱하는 것(그리 큰 비용은 아니겠지만)도 탐탁지 않기는 한데.. 당장은 뾰족한 수가 없다.

 

이 당시에는 놀라운 일이였지만 지금 다시 생각해보면 "지극히 당연한 것"으로 고생한 것 같기도 하다..

 

자바의 작동 원리를 생각했을 때 차분히 생각해보면 지극히 당연한 일이기는 한데,

한 번도 삼항연산자를 쓰다가 문제 생긴 적이 없다보니 저쪽에서 뭔가 잘못됐을 거라는 생각 자체를 하기가 힘들었다..

 

아무튼 누군가가 같은 일로 고생한다면 내 글이 도움이 되기를 바란다.