-
Notifications
You must be signed in to change notification settings - Fork 0
[미션 Day 7] 리팩토링 연습 #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: footprint/week1
Are you sure you want to change the base?
Conversation
| public class ProvideException extends RuntimeException { | ||
|
|
||
| public ProvideException(String message) { | ||
| super(message); | ||
| } | ||
|
|
||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AppException 성격이랑 다른 것 같다고 생각되어 Provider 인터페이스에서 발생하는 예외 클래스 ProvideException를 별도로 생성하였습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[우빈님 리뷰]
우빈님께서 작성하신 AppException은 해당 프로그램에서 발생할 수 있는 최상위 예외 클래스 의도를 가지고 있다. ProvideException으로 만든 것은 괜찮은데 구체적으로 AppException의 상속을 받아야 한다.
커스텀 예외 만들 때 마다 catch를 다 잡아주어야 한다.
추가로, "이용권을 제공받을 수 없습니다." 예외 메세지는 사용자 친화적이지 않은 것 같다. 누가 보는지가 중요하다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
저도 커스텀 예외를 만들어서, 저에게도 해당되는 리뷰이네요..!
덕분에 리마인드 합니닷
| public interface PassTypeFormatter { | ||
|
|
||
| String format(StudyCafePass pass); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
네이밍이 모호하지만.. 😂
OutputHandler에서 display() 메서드의 분기문의 OCP를 적용하기 위해 인터페이스를 작성하였고,
StudyCafePassType 클래스에서 인터페이스를 구현하고 있습니다.
| public class ConsoleInputHandler implements InputHandler { | ||
|
|
||
| private static final Scanner SCANNER = new Scanner(System.in); | ||
|
|
||
| @Override | ||
| public StudyCafePassType getPassTypeSelectingUserAction() { | ||
| String userInput = SCANNER.nextLine(); | ||
| return StudyCafePassType.findBy(userInput); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
StudyCafePassType 클래스 내에서 사용자 입력값으로 타입을 찾는 메서드로 리팩토링 하였습니다.
| private String display(StudyCafePass pass) { | ||
| StudyCafePassType passType = pass.getPassType(); | ||
| return passType.format(pass); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
위와 마찬가지로 StudyCafePassType 내부에서 포맷팅하는 로직을 구현하여 OCP를 적용하였습니다.
| public static final String CSV_FILE_NAME = "locker.csv"; | ||
|
|
||
| @Override | ||
| public StudyCafeLockerPasses provide() { | ||
| try { | ||
| List<String> allLines = CsvFileReadSupport.readAllLinesBy(CSV_FILE_NAME); | ||
| ReadLockerPasses readSeatPasses = ReadLockerPasses.ofLines(allLines); | ||
|
|
||
| return readSeatPasses.toPasses(); | ||
| } catch (IOException e) { | ||
| throw new ProvideException(e.getMessage()); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
해당 메서드 내에 많은 책임이 있다고 생각되어..
CSV 파일을 읽고 라인을 반환하는 유틸성 클래스인 CsvFileReadSupport를 분리하였으며,
읽은 라인의 내용을 읽어 StudyCafe*Passes 객체로 반환하는 VO 클래스 개념을 도출하였습니다.
| public enum StudyCafePassType implements PassTypeSelectable, PassTypeFormatter { | ||
|
|
||
| HOURLY("시간 단위 이용권") { | ||
| @Override | ||
| public boolean selected(String userInput) { | ||
| return "1".equals(userInput); | ||
| } | ||
|
|
||
| @Override | ||
| public String format(StudyCafePass pass) { | ||
| return String.format("%s시간권 - %d원", pass.getDuration(), pass.getPrice()); | ||
| } | ||
| }, | ||
| WEEKLY("주 단위 이용권") { | ||
| @Override | ||
| public boolean selected(String userInput) { | ||
| return "2".equals(userInput); | ||
| } | ||
|
|
||
| @Override | ||
| public String format(StudyCafePass pass) { | ||
| return String.format("%s주권 - %d원", pass.getDuration(), pass.getPrice()); | ||
| } | ||
| }, | ||
| FIXED("1인 고정석") { | ||
| @Override | ||
| public boolean selected(String userInput) { | ||
| return "3".equals(userInput); | ||
| } | ||
|
|
||
| @Override | ||
| public String format(StudyCafePass pass) { | ||
| return String.format("%s주권 - %d원", pass.getDuration(), pass.getPrice()); | ||
| } | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
클래스 내부에서 사용자 입력값 및 출력값에 사용하는 인터페이스를 구현함으로써 오버 엔지니어링이 된 것 같은 느낌이 드네요.. 🤦♂️
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[우빈님 리뷰]
오버 엔지니어링이기보다 PassType은 중요한 도메인 모델인데, Input에서만 의미를 가지는 사용자 선택지가 침투하고 있다.
사용자 선택 방법이 "a", "b", "c"로 바뀐다면?
단순히 입력 방식을 바꿨을 뿐인데 무료 도메인 모델이 수정되어야 하는 엄청난 사태가 발생한다.
항상 구조화를 하는 것이 정답은 아니다.
Output format도 마찬가지이다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
책임이 우선이다. 적절한 책임의 분배가 결합도를 낮추고 응집도를 높이는 것이다.
| public StudyCafeLockerPasses toPasses() { | ||
| return StudyCafeLockerPasses.of(passes); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
일급 컬렉션을 적용하기 위해toPasses() 메서드를 생성했는데
Read~라는 네이밍을 가진 클래스에 많은 책임이 부여된 것 같아 네이밍이 모호한 것 같습니다.
좋은 방법이 있을까요..? 🧐
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[우빈님 리뷰]
좋은 설계 방식이였다. 테스트 코드 작성도 가능하다.
많은 책임이라고 생각하지 않는다.
그와 별개로 CSV라는 방식에 종속되어있다. CSV형식이 다른 방식으로 바뀌었을 때 같이 바뀌어야 하는 부분이 CSV_SPLITTER 부분이다. 의도한 것 이라면 상관없다.
📅 미션 일자
✅ 미션 내용
💭 리뷰
✨ 추가 리팩토링