[Recruit] Null 처리는 어떻게..? (1)

Bellmin·2024년 9월 8일

Econovation

목록 보기
1/3

상황

recruit 플랫폼에서, 지원 기수에 해당하는 card들을 필터링해야 하는 기능을 구현해야 했다.

나는 기존에 작성된 코드를 읽고 어느 정도 로직을 파악한 후에 코드를 수정했다.

 List<Board> boards = boardLoadUseCase.getBoardByColumnsIds(columnsIds);

        List<MongoAnswer> mongoAnswers = answerAdaptor.findAll();

        Map<String, Integer> yearByAnswerIdMap = mongoAnswers.stream()
                .collect(Collectors.toMap(MongoAnswer::getId, MongoAnswer::getYear));

        boards = boards.stream()
                .filter(
                        board -> {
                            Long cardId = board.getCardId();
                            String applicantId = cardLoadPort.findById(cardId).getApplicantId();
                            return yearByAnswerIdMap.get(applicantId).equals(year);
                        })
                .toList();
  

위 코드가 내가 수정한 코드이다.

Stream을 이용해서 보기 쉽게 작성하려고 노력했다.

그리고 로컬에서 테스트를 해봤을 때 제대로 동작했다. 바로 PR을 날려서 Merge 후 개발용에서 테스트를 하고 싶었다.

그래서 개발용 서버에 배포를 하고 바로 테스트를 해보았다.

결과는..?

NPE (Null Pointer Exception)이 발생하는 것이다.

그 이유는 바로 아래이다.

String applicantId = cardLoadPort.findById(cardId).getApplicantId();

board에서 가져온 cardId를 데이터베이스에서 조회하는데, board가 가지고 있는 cardId가 NULL인 경우이다.

board 자체도 데이터베이스에서 가져온 데이터인데, getCardId를 했을 때 어떻게 NULL이 나오는지 궁금했다.

그 이유는 바로 데이터베이스 자체에서 cardId 속성 값이 NULL로 저장되어 있었기 때문이다.

그래서 나는 다음과 같이 if로 null인 경우를 분기처리 했다.

		List<Card> cards = cardLoadPort.findAll();

        Map<Long, String> answerIdByCardIdMap = cards.stream()
                .collect(Collectors.toMap(Card::getId, Card::getApplicantId));

        boards = boards.stream()
                .filter(
                        board -> {
                            Long cardId = board.getCardId();

                            if(cardId!=null) {
                                String applicantId = answerIdByCardIdMap.get(cardId);
                                return yearByAnswerIdMap.get(applicantId).equals(year);
                            }

                            return false;
                        })
                .toList();

이렇게 cardId가 null인 경우에만 분기 처리를 해주었다.

이렇게 해서 로컬로 테스트 했는데 잘 동작했다. 바로 개발용에 배포해서 테스트를 해보았다.

결과는..??

NPE.....

if 문 내부에서 NPE가 일어났다.

return yearByAnswerIdMap.get(applicantId).equals(year);

여기 yearByAnswerIdMap.get(applicantId) <- 이 과정에서 NPE가 발생했다.

key에 해당하는 value가 null인 것이다.

도대체 왜...??

Map<String, Integer> yearByAnswerIdMap = mongoAnswers.stream()
                .collect(Collectors.toMap(MongoAnswer::getId, MongoAnswer::getYear));

Map을 만들 때, stream의 Map Collector를 사용했다.

keyMapper, valueMapper 모두 원하는대로 잘 작성했고 MongoAnswer 또한 MongoDB에 저장된 정보를 그대로 가져온 형태이다.

MongoDB의 year 필드에 null이 저장되어 있지 않는 한, NPE가 발생하지 않을 것 같은데 NPE가 발생하는 이유가 뭔지 모르겠다.

그래서 어떻게?

그래서 나는 생각했다. 데이터베이스 오류든, 서버 오류든 null값이 충분히 저장될 수 있는 상황이 있으리라고.

결국 null 값 처리를 유연하게 하기위해 만들어진 Optional을 사용해서 조금 더 유연하게 처리하도록 했다.

boards = boards.stream()
                .filter(
                        board ->
                                Optional.ofNullable(board.getCardId())
                                .map( answerIdByCardIdMap::get(id))
                                .map( yearByAnswerIdMap::get(id))
                                .map(y -> y.equals(year))
                                .orElse(false))
                .toList();

이렇게 Optional을 이용하고, 람다를 통해 읽기 쉽게 작성할 수 있었다.

아직 미숙하지만, Optional을 이러한 형태로 사용하는 것임을 알 수 있었다.

0개의 댓글