오늘은 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시간동안 팀원들을 속 썩이던 테스트가 한 번에 통과되었다.
사실 오직 테스트만을 위해 매번 박싱하는 것(그리 큰 비용은 아니겠지만)도 탐탁지 않기는 한데.. 당장은 뾰족한 수가 없다.
이 당시에는 놀라운 일이였지만 지금 다시 생각해보면 "지극히 당연한 것"으로 고생한 것 같기도 하다..
자바의 작동 원리를 생각했을 때 차분히 생각해보면 지극히 당연한 일이기는 한데,
한 번도 삼항연산자를 쓰다가 문제 생긴 적이 없다보니 저쪽에서 뭔가 잘못됐을 거라는 생각 자체를 하기가 힘들었다..
아무튼 누군가가 같은 일로 고생한다면 내 글이 도움이 되기를 바란다.
'Language & Framework > 삽질기록' 카테고리의 다른 글
삽질 기록 (11) 로컬에서는 잘만 되는 WebSocket 채팅 EC2에서는 외않뒈? (0) | 2022.11.23 |
---|---|
삽질 기록(9) PasswordEncoder 테스트는 왜 실패하는 걸까? 해시 함수와 암호화 알고리즘 간단하게 알아보기 (0) | 2022.11.20 |
삽질기록 (7) 스프링 시큐리티(JWT)적용 이후 깨지는 테스트들 복구하기.. (0) | 2022.10.04 |
삽질 기록 (6) 실수로 삭제된(?) 깃허브 팀 레파지토리 살리기 (3) | 2022.09.23 |
삽질 기록 (5) Spring Security의 UserDetails를 만지작거리다가 만난 LocalDate관련 예외 (0) | 2022.09.03 |