발자국 2주차 [인프런 워밍업 클럽 2기 백엔드(클린코드/테스트)]
이번 주 강의
섹션6
섹션 5에 이어서 지뢰게임 도메인 계속 리펙터링 중
주석
주석은 추상화를 해칠 수 있는 위험요소가 있다
주석도 버전 관리의 대상이다. 업데이트되지 않은 주석은 클라이언트를 혼란스럽게 할 수 있다
추상화 가능한 부분을 주석으로 대체하고 있지 않은 지 고민해보자
패키지
패키지 이름도 문맥에 대한 정보를 가질 수 있다
"Level" 패키지의 클래스는 "BegineerLevel" 이 아닌 "Begineer" 라고 써도 의미가 전달된다
패키지는 적정 수준으로 분리해야 된다 너무 많으면 관리가 어렵고 너무 적으면 응집도가 낮아진다
버그 잡기, 알고리즘 개선
게임 종료조건 버그 수정
게임 보드의 크기가 커지면
stackoverflowerror
발생하는 부분을 알고리즘 변경으로 개선
섹션7
스터디카페 이용권 판매라는 새로운 도메인에서 리펙토링 실습을 계속하였고 강의를 보기 전에
스스로 리펙토링을 진행하면서 강의를 볼 때 중점적으로 볼 부분을 정리했다
섹션 7의 리펙토링 내용
중복 제거, 메서드 추출
객체에 메시지 보내기
IO 통합
일급 컬렉션
display 로직 이관 - display 는 다양한 플랫폼에서 이뤄질 수 있으므로 그 동작을 객체가 하는 것보다 입출력을 담당하는 곳에서 하는 게 적당하다
passOrder 개념 추출 -중요한 도메인 로직을 입출력에서 담당하지 말고 별도의 도메인 객체로 추상화하자
FileReader 인터페이스를 Provider 인터페이스를 도입하여 분리 (이 부분이 정말 대단하다!)
섹션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("알 수 없는 오류가 발생했습니다.");
}
}
최종 코드는 다음과 같고 내가 시도해본 것은 다음과 같다
StudyCafeFileHandler
에서 csv 파일을 불러오는 경로를 매직 스트링으로 변경StudyCafeFileHandler
를 구현하는 상위 인터페이스initHandler
를 생성강의와 중간점검 피드백을 보면 다형성은 좋았는데 추상화의 목적을 잘못 설정했다
묻지 말고 시켜라
원칙과 비슷한 결이라고 생각되는데 데이터를 가져오는 방법을 추상화하는 것보다는
필요한 데이터 요구하는 메시지를 추상화하는 게 응집도 면에 더 낫다
인터페이스를 추상 클래스로 변경했다 그 이유는
pass
저장된 csv 파일을 읽는 로직을public
으로 제공하지 않고protected
로 사용하고 사용자가 사용가능한 pass 의결과만 클라이언트가 이용하도록 하고 싶었다
이렇게 하고 싶었던 것은 내가
FileHandler
를 로직이 시작될 때 필요한 데이터를 초기화하는 역할로 생각했었는데
너무 많이 추상화시킨 것 같다. 강의를 보고 많은 공부가 되었다
일부 메시지를 수정했다
studyCafeInitHandler.getSelectableLockerPassForUserSelection
같이 명확하게 하려고 했다null
처리를 하고 싶어서Optional
을 이용하여lockerPass
가null
이면isLockerPurchased
를 불리언으로 해서
오버로딩된showPassOrderSummary
를 쓰도록 했다
근데 멘토님을 보니까lockerPass
자체를 Optional 로 사용하셨다 조금 더 생각해볼 걸 그랬다
미션을 수행하면서InputHandler
와OutputHandler
가 따로 있는 게 코드를 읽을 때 난잡하다고 느껴졌는데
멘토님은 이를 하나의 클래스로 합치셔서 인상적이었다 습관적으로 입력 클래스와 출력 클래스를 분리했는데
꼭 그럴 필요는 없다는 걸 배웠다
댓글을 작성해보세요.