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을 이러한 형태로 사용하는 것임을 알 수 있었다.