inflearn logo
강의

강의

N
챌린지

챌린지

멘토링

멘토링

N
클립

클립

로드맵

로드맵

지식공유

발자국 2주차 [인프런 워밍업 클럽 2기 백엔드(클린코드/테스트)]

sodychoe
0

이번 주 강의

 

섹션6

섹션 5에 이어서 지뢰게임 도메인 계속 리펙터링 중


주석


패키지

 

 

섹션7

스터디카페 이용권 판매라는 새로운 도메인에서 리펙토링 실습을 계속하였고 강의를 보기 전에

스스로 리펙토링을 진행하면서 강의를 볼 때 중점적으로 볼 부분을 정리했다

 

 


섹션 7의 리펙토링 내용

 

섹션8

능동적 읽기

오버 엔지니어링

은탄환은 없다

섹션9

마무리

강의 회고

섹션 7이 지금까지 배운 것을 적용할 수 있는 기회였어서 많이 기억에 남는다

강의와 중간점검에서 멘토님이 하신 것과 똑같이 변경한 부분도 있었지만 멘토님이 인터페이스의 책임을 생각해서

다시 리펙토링하는 것을 보고 아직 부족함을 깨닫고 더 많이 공부해야겠다고 생각이 들었다

 

이번 주 미션

[섹션 7. 리팩토링 연습]의 "연습 프로젝트 소개" 강의를 보고, '스터디 카페 이용권 선택 시스템' 프로젝트에서 지금까지 배운 내용을 기반으로 리팩토링을 진행해 봅시다.

오늘 1차 리팩토링을 마치고, 다음날 자고 일어나서 다시 한번 내가 리팩토링한 코드를 살펴봅니다.

자고 일어나서 뇌가 맑아지면 새로운 시야가 열릴 때가 많거든요.

만약 추가로 수정하고 싶은 부분이 보인다면, 2차 리팩토링을 진행합니다.

 


일단 결과적으로 StudyCafePssMachine.run 의 중복되는 코드를 리펙터링 하는 것이 가장 중요한 목표인 것 같았다

 

    public void run() {
        try {
            outputHandler.showWelcomeMessage();
            outputHandler.showAnnouncement();

            outputHandler.askPassTypeSelection();
            StudyCafePassType studyCafePassType = inputHandler.getPassTypeSelectingUserAction();

            if (studyCafePassType == StudyCafePassType.HOURLY) {
                StudyCafeFileHandler studyCafeFileHandler = new StudyCafeFileHandler();
                List<StudyCafePass> studyCafePasses = studyCafeFileHandler.readStudyCafePasses();
                List<StudyCafePass> hourlyPasses = studyCafePasses.stream()
                    .filter(studyCafePass -> studyCafePass.getPassType() == StudyCafePassType.HOURLY)
                    .toList();
                outputHandler.showPassListForSelection(hourlyPasses);
                StudyCafePass selectedPass = inputHandler.getSelectPass(hourlyPasses);
                outputHandler.showPassOrderSummary(selectedPass, null);
            } else if (studyCafePassType == StudyCafePassType.WEEKLY) {
                StudyCafeFileHandler studyCafeFileHandler = new StudyCafeFileHandler();
                List<StudyCafePass> studyCafePasses = studyCafeFileHandler.readStudyCafePasses();
                List<StudyCafePass> weeklyPasses = studyCafePasses.stream()
                    .filter(studyCafePass -> studyCafePass.getPassType() == StudyCafePassType.WEEKLY)
                    .toList();
                outputHandler.showPassListForSelection(weeklyPasses);
                StudyCafePass selectedPass = inputHandler.getSelectPass(weeklyPasses);
                outputHandler.showPassOrderSummary(selectedPass, null);
            } else if (studyCafePassType == StudyCafePassType.FIXED) {
                StudyCafeFileHandler studyCafeFileHandler = new StudyCafeFileHandler();
                List<StudyCafePass> studyCafePasses = studyCafeFileHandler.readStudyCafePasses();
                List<StudyCafePass> fixedPasses = studyCafePasses.stream()
                    .filter(studyCafePass -> studyCafePass.getPassType() == StudyCafePassType.FIXED)
                    .toList();
                outputHandler.showPassListForSelection(fixedPasses);
                StudyCafePass selectedPass = inputHandler.getSelectPass(fixedPasses);

                List<StudyCafeLockerPass> lockerPasses = studyCafeFileHandler.readLockerPasses();
                StudyCafeLockerPass lockerPass = lockerPasses.stream()
                    .filter(option ->
                        option.getPassType() == selectedPass.getPassType()
                            && option.getDuration() == selectedPass.getDuration()
                    )
                    .findFirst()
                    .orElse(null);

                boolean lockerSelection = false;
                if (lockerPass != null) {
                    outputHandler.askLockerPass(lockerPass);
                    lockerSelection = inputHandler.getLockerSelection();
                }

                if (lockerSelection) {
                    outputHandler.showPassOrderSummary(selectedPass, lockerPass);
                } else {
                    outputHandler.showPassOrderSummary(selectedPass, null);
                }
            }
        } catch (AppException e) {
            outputHandler.showSimpleMessage(e.getMessage());
        } catch (Exception e) {
            outputHandler.showSimpleMessage("알 수 없는 오류가 발생했습니다.");
        }

    }

딱 보기에도 난잡한 조건문과 중복 로직이 보인다 이를 적절하게 공통 로직으로 묶고 추상화하려고 시도했다

 

    public void run() {
        try {
            outputHandler.showAnnouncementMessageAtFirst();

            StudyCafePassType passTypeSelectingUserAction = inputHandler.getPassTypeSelectingUserAction();
            List<StudyCafePass> selectablePassesForUserSelection = studyCafeInitHandler.getSelectablePassesForUserSelection(
                passTypeSelectingUserAction);
            outputHandler.showPassListForUserSelection(selectablePassesForUserSelection);

            StudyCafePass selectedPassForUser = inputHandler.getSelectPass(selectablePassesForUserSelection);
            StudyCafeLockerPass selectableLockerPassForUserSelection = studyCafeInitHandler.getSelectableLockerPassForUserSelection(
                selectedPassForUser);

            boolean isLockerPurchased = isLockerPurchasedIfExists(selectableLockerPassForUserSelection);

            if (isLockerPurchased) {
                outputHandler.showPassOrderSummaryIfLockerPass(selectedPassForUser, selectableLockerPassForUserSelection);
            }

            outputHandler.showPassOrderSummary(selectedPassForUser);
        } catch (AppException e) {
            outputHandler.showSimpleMessage(e.getMessage());
        } catch (Exception e) {
            outputHandler.showSimpleMessage("알 수 없는 오류가 발생했습니다.");
        }
    }

 최종 코드는 다음과 같고 내가 시도해본 것은 다음과 같다

  1. StudyCafeFileHandler 에서 csv 파일을 불러오는 경로를 매직 스트링으로 변경

  2. StudyCafeFileHandler 를 구현하는 상위 인터페이스 initHandler 를 생성

    1. 강의와 중간점검 피드백을 보면 다형성은 좋았는데 추상화의 목적을 잘못 설정했다

    2. 묻지 말고 시켜라 원칙과 비슷한 결이라고 생각되는데 데이터를 가져오는 방법을 추상화하는 것보다는
      필요한 데이터 요구하는 메시지를 추상화하는 게 응집도 면에 더 낫다

       

  3. 인터페이스를 추상 클래스로 변경했다 그 이유는 pass 저장된 csv 파일을 읽는 로직을 public 으로 제공하지 않고 protected 로 사용하고 사용자가 사용가능한 pass 의

    결과만 클라이언트가 이용하도록 하고 싶었다

    1. 이렇게 하고 싶었던 것은 내가 FileHandler 를 로직이 시작될 때 필요한 데이터를 초기화하는 역할로 생각했었는데
      너무 많이 추상화시킨 것 같다. 강의를 보고 많은 공부가 되었다

  4. 일부 메시지를 수정했다 studyCafeInitHandler.getSelectableLockerPassForUserSelection 같이 명확하게 하려고 했다

  5. null 처리를 하고 싶어서 Optional 을 이용하여 lockerPassnull 이면 isLockerPurchased 를 불리언으로 해서


    오버로딩된 showPassOrderSummary 를 쓰도록 했다


    근데 멘토님을 보니까 lockerPass 자체를 Optional 로 사용하셨다 조금 더 생각해볼 걸 그랬다




    미션을 수행하면서 InputHandlerOutputHandler 가 따로 있는 게 코드를 읽을 때 난잡하다고 느껴졌는데


    멘토님은 이를 하나의 클래스로 합치셔서 인상적이었다 습관적으로 입력 클래스와 출력 클래스를 분리했는데
    꼭 그럴 필요는 없다는 걸 배웠다

     

 

답변 0