Troubleshooting: 무엇이 문제였는가?/본캠프 3주 차: 일정 관리 앱 만들기

1단계: "Condition '(name != null) && (updatedDate != null)' is always 'false'"

writingforever162 2024. 12. 7. 17:16

[문제]

package com.spring.weekthree.repository;

import com.spring.weekthree.dto.PlanResponseDto;
import com.spring.weekthree.entity.Plan;
import org.springframework.stereotype.Repository;

import java.time.LocalDate;
import java.util.*;

// Data Access Layer(Repository Layer)
@Repository
public class PlanRepositoryImpl implements PlanRepository {
    // 속성
    private final Map<Long, Plan> planList = new HashMap<>();

    // 생성자

    // 기능
    @Override
    public Plan save(Plan plan) {
        Long planId = planList.isEmpty() ? 1 : Collections.max(planList.keySet()) + 1;
        
        plan.setId(planId);
        
        planList.put(planId, plan);
        
        return plan;
    }

    @Override
    public List<PlanResponseDto> fetchAllPlans(String name, LocalDate updatedDate) {
        List<PlanResponseDto> allPlans = new ArrayList<>();
        if (name != null) {
            for (Plan plan : planList.values()) {
                if (plan.getName().equals(name)) {
                    allPlans.add(new PlanResponseDto(plan));
                }
            }
        } else if (updatedDate != null) {
            for (Plan plan : planList.values()) {
                if (plan.getUpdatedDateTime().toLocalDate().equals(updatedDate)) {
                    allPlans.add(new PlanResponseDto(plan));
                }
            }
        } else if ((name != null) && (updatedDate != null)) {
        /*
        [문제] 
        노란 줄이 뜬 부분
        마우스로 가리키면 조건식이 항상 false라는 문구가 떴다.
         */
            for (Plan plan : planList.values()) {
                if ((plan.getUpdatedDateTime().toLocalDate().equals(updatedDate))
                        && (plan.getName().equals(name))) {
                    allPlans.add(new PlanResponseDto(plan));
                }
            }
        } else {
            for (Plan plan : planList.values()) {
                PlanResponseDto responseDto = new PlanResponseDto(plan);
                allPlans.add(responseDto);
            }
        }
        return allPlans;
    }

    @Override
    public Plan fetchPlanById(Long id) {
        return planList.get(id);
    }

    @Override
    public void deletePlan(Long id) {
        planList.remove(id);
    }
}

전체 일정을 조회하는 기능을 구현할 때 '수정일'과 '작성자명'을 모두 충족하거나, 둘 다 충족하지 않거나, 둘 중 하나를 충족할 때 각각 목록을 조회할 수 있도록 구현해야 했다. 이때 조건을 어떻게 해야 할지 몰라 우선 CRUD 기능을 구현한 다음 리팩토링(refactoring)하는 식으로 조건을 추가했다. 작성자명인 name이 null이 아닐 때와 수정일인 updatedDate가 null이 아닐 때, 즉 값이 있을 때 저장된 값과 동일한 일정을 목록으로 뽑아내도록 했는데, 갑자기 노란 줄이 떴다. 

 

[원인]

package com.spring.weekthree.repository;

import com.spring.weekthree.dto.PlanResponseDto;
import com.spring.weekthree.entity.Plan;
import org.springframework.stereotype.Repository;

import java.time.LocalDate;
import java.util.*;

// Data Access Layer(Repository Layer)
@Repository
public class PlanRepositoryImpl implements PlanRepository {
    // 속성
    private final Map<Long, Plan> planList = new HashMap<>();

    // 생성자

    // 기능
    @Override
    public Plan save(Plan plan) {
        Long planId = planList.isEmpty() ? 1 : Collections.max(planList.keySet()) + 1;

        plan.setId(planId);

        planList.put(planId, plan);

        return plan;
    }

    @Override
    public List<PlanResponseDto> fetchAllPlans(String name, LocalDate updatedDate) {
        List<PlanResponseDto> allPlans = new ArrayList<>();
        /*
        [첫 번째] 
        - name이 null이 아니면 조건식이 true가 된다.
        - name이 null이면 false이므로 다음 조건문으로 넘어간다.
         */
        if (name != null) {
            for (Plan plan : planList.values()) {
                if (plan.getName().equals(name)) {
                    allPlans.add(new PlanResponseDto(plan));
                }
            }
          /*
          [두 번째]
          - if문이 실행되지 않았으므로 name은 null이다.
          - updatedDate가 null이 아니면 조건식이 true가 된다.
          - updatedDate가 null이면 false이므로 다음 조건문으로 넘어간다.
           */
        } else if (updatedDate != null) {
            for (Plan plan : planList.values()) {
                if (plan.getUpdatedDateTime().toLocalDate().equals(updatedDate)) {
                    allPlans.add(new PlanResponseDto(plan));
                }
            }
          /*
          [세 번째]
          Q. 그렇다면 세 번째에 도달했다는 말은?
          A. 첫 번째 조건식도 false가 되었고,
             두 번째 조건식도 false가 되었단 뜻이다.
             즉, 이미 name도 null이고 updatedDate도 null이란 뜻이다. 
           */
        } else if ((name != null) && (updatedDate != null)) {
            for (Plan plan : planList.values()) {
                if ((plan.getUpdatedDateTime().toLocalDate().equals(updatedDate))
                        && (plan.getName().equals(name))) {
                    allPlans.add(new PlanResponseDto(plan));
                }
            }
        } else {
            for (Plan plan : planList.values()) {
                PlanResponseDto responseDto = new PlanResponseDto(plan);
                allPlans.add(responseDto);
            }
        }
        return allPlans;
    }

    @Override
    public Plan fetchPlanById(Long id) {
        return planList.get(id);
    }

    @Override
    public void deletePlan(Long id) {
        planList.remove(id);
    }
}

원인은 논리 흐름, 즉 로직(logic)이 위에서 아래로 흐른다는 데에 있었다. 주석으로 적었다시피, 두 번째 else if문의 ((name != null) && (updatedDate != null)) 조건식에 이를 때에는 이미 (name != null) 조건식도 false이고, (updatedDate != null) 조건식도 무조건 false일 수밖에 없었다. 앞에서 조건식이 true였으면 메서드(method)가 실행되었을 테니까. 

 

[해결]

① if문과 else if문의 조건식 수정하기 

package com.spring.weekthree.repository;

import com.spring.weekthree.dto.PlanResponseDto;
import com.spring.weekthree.entity.Plan;
import org.springframework.stereotype.Repository;

import java.time.LocalDate;
import java.util.*;

// Data Access Layer(Repository Layer)
@Repository
public class PlanRepositoryImpl implements PlanRepository {
    // 속성
    private final Map<Long, Plan> planList = new HashMap<>();

    // 생성자

    // 기능
    @Override
    public Plan save(Plan plan) {
        Long planId = planList.isEmpty() ? 1 : Collections.max(planList.keySet()) + 1;

        plan.setId(planId);

        planList.put(planId, plan);

        return plan;
    }

    @Override
    public List<PlanResponseDto> fetchAllPlans(String name, LocalDate updatedDate) {
        List<PlanResponseDto> allPlans = new ArrayList<>();
        if ((name != null) && (updatedDate == null)) {
        /*
        [수정 전]
        if (name != null)
        [수정 후]
        if ((name != null) && (updatedDate == null))
         */
            for (Plan plan : planList.values()) {
                if (plan.getName().equals(name)) {
                    allPlans.add(new PlanResponseDto(plan));
                }
            }
        } else if ((name == null) && (updatedDate != null)) {
          /*
          [수정 전]
          if (updatedDate != null)
          [수정 후]
          else if ((name == null) && (updatedDate != null))
           */
            for (Plan plan : planList.values()) {
                if (plan.getUpdatedDateTime().toLocalDate().equals(updatedDate)) {
                    allPlans.add(new PlanResponseDto(plan));
                }
            }
        } else if ((name != null) && (updatedDate != null)) {
            for (Plan plan : planList.values()) {
                if ((plan.getUpdatedDateTime().toLocalDate().equals(updatedDate))
                        && (plan.getName().equals(name))) {
                    allPlans.add(new PlanResponseDto(plan));
                }
            }
        } else {
            for (Plan plan : planList.values()) {
                PlanResponseDto responseDto = new PlanResponseDto(plan);
                allPlans.add(responseDto);
            }
        }
        return allPlans;
    }

    @Override
    public Plan fetchPlanById(Long id) {
        return planList.get(id);
    }

    @Override
    public void deletePlan(Long id) {
        planList.remove(id);
    }
}

노란 줄이 뜬 부분은 true로 고칠 수 있었지만, 어느 부분이 true인지 잘 보이도록 그대로 두었다. 문제는 if문과 향상된 for문을 사용할수록 코드가 한눈에 보이지 않았다. 소괄호가 두 개, 세 개 겹치니까 이미 내 마음에 들지 않는다는 점을 튜터님도 눈치채신 걸까? 두 번째 해결책을 쓰기로 했다.

 

② 스트림(Stream) 사용하기

package com.spring.weekthree.repository;

import com.spring.weekthree.dto.PlanResponseDto;
import com.spring.weekthree.entity.Plan;
import org.springframework.stereotype.Repository;

import java.time.LocalDate;
import java.util.*;
import java.util.stream.Stream;

// Data Access Layer(Repository Layer)
@Repository
public class PlanRepositoryImpl implements PlanRepository {
    // 속성
    private final Map<Long, Plan> planList = new HashMap<>();

    // 생성자

    // 기능
    @Override
    public Plan save(Plan plan) {
        Long planId = planList.isEmpty() ? 1 : Collections.max(planList.keySet()) + 1;

        plan.setId(planId);

        planList.put(planId, plan);

        return plan;
    }

    @Override
    public List<PlanResponseDto> fetchAllPlans(String name, LocalDate updatedDate) {
        Stream<PlanResponseDto> allPlans;

        allPlans = planList.values().stream().map(PlanResponseDto::new);

        if (name != null) {
            allPlans = allPlans.filter(x -> x.getName().equals(name)
            );
        }
        if (updatedDate != null) {
            allPlans = allPlans.filter(y -> {
                        return y.getUpdatedDateTime().toLocalDate().equals(updatedDate);
                    }
            );
        }
        return allPlans.toList();
    }

    @Override
    public Plan fetchPlanById(Long id) {
        return planList.get(id);
    }

    @Override
    public void deletePlan(Long id) {
        planList.remove(id);
    }
}

확실히 if문을 썼을 때보다 간결하고 논리 흐름이 한눈에 보여서 마음에 들었다. 대신 스트림(Stream)이 무엇인지 강의를 듣지 않았기 때문에, 튜터님들이 알려주신 개념과 특징을 바탕으로 꼭 공부하기로 했다. 역시 코드를 더 잘 쓰려면, 다양한 작성 방법을 알려면 그 방법을 익혀야 했다. 너무나 먼 얘기이고 언제 접하기는 할까 싶은 스트림에 한 발짝 다가선 느낌이 들었다.

 

[결과 수치화] 

[수정 전] 자바(Java) 인텔리제이(IntelliJ) 노란 줄 경고 표시 1개 발생

[수정 ] 자바(Java) 인텔리제이(IntelliJ) 노란 줄 경고 표시 0개 발생