[사다리 - 함수형 프로그래밍] 박진형 미션 제출#85
Conversation
- 각 값들은 생성자를 private로 하고 정적 팩터리 메서드를 통해 검증 후 생성 - 가로 검증: 최소 두 명의 참가자가 있도록 value < 2 - 세로 검증: 1 이상의 값을 입력하도록 value < 1
- 불필요한 객체 생성을 방지하기 위해 정적 팩터리 메서드 및 캐싱 적용 - boolean 원시 값 포장 및 캡슐화
- 정적 팩터리 메서드를 통해 Point 리스트 생성 및 객체 조립 로직 구현 - 가로 라인이 겹치지 않도록 연속 생성 방지 로직 적용
- LadderWidth와 LadderHeight를 기반으로 사다리 조립 로직 구현 - List<Line>을 관리하는 일급 컬렉션 구조 설계
- 반복적으로 쓰이는 사다리 시각화 문자열에 대한 매직넘버 처리 - printLadder를 통해 각 Line들을 구현 - 각 Line은 세로줄 및 Point에 따라 가로줄 출력
- 예외 발생 시 재입력을 유도하는 공통 입력 유틸리티 메서드 구현 (Supplier 활용) - 사다리 너비 및 높이 입력 기능 구현 - 정수 입력값에 대한 기본 형식 검증 및 에러 메시지 처리
- InputView, Ladder, OutputView를 연결하여 전체 게임 흐름 제어
- 외부에서 boolean을 주입하는 strategy로 변경 - 차후 용이한 테스트코드 작성을 위함
- 우측에 여유 공간이 있고 포인트가 있으면 우로 이동 - 좌측에 여유 공간이 있고, 포인트가 있으면 좌로 이동 - 그렇지 않으면 그대로 내려오기
- climb: 각 인덱스에서 시작하여 Line을 모두 거쳐 도착지의 인덱스를 반환 - generateResults: 모든 인덱스에 대해 climb를 호출하여 그 결과를 반환
- Point를 3개로 지정하고 전략을 true로 하여 기둥이 true, false, true로 생성되게 함 - 각 기둥에서 이동하여 도착한 인덱스를 검증
- climb: 참여자를 2, 높이를 1, 전략을 true로 설정하여 교차해서 내려감을 검증 - generateResults: 참여자를 3, 높이를 1, 전략을 true로 설정하여 결과의 크기와 그 값을 검증
- 기존 LadderWidth는 사용 X - Participants를 받아 그 크기로 반복
- Name: 빈 값이거나 5글자를 넘을 수 없음 - Participants: 최소 2명이며, 이름은 중복될 수 없음
- Participants와 LadderResults를 인덱스 경로(Map)를 기준으로 매핑하는 로직 구현 - 특정 참여자의 이름을 검색하여 결과를 반환하는 기능 추가
- 'all' 입력 전까지 반복해서 결과를 조회하는 runInquiry 로직 구현 - 사용자 입력값에 따라 단일 결과 또는 전체 결과 출력 기능 연결를 반환하는 기능 추가
- 이름과 실행 결과의 가로 정렬을 위한 6칸 포매팅(%6s) 적용 - 사다리 기둥과 이름의 위치를 맞추기 위한 라인 시작 여백 처리 - 개별 결과 및 전체 결과 출력 메서드 추가
- 1~5자 사이의 이름 생성 성공 케이스 검증 - 입력값 앞뒤 공백 제거(trim) 로직 확인 - 빈 값(blank) 입력 시 예외 발생 및 메시지 검증 - 5자 초과 입력 시 예외 발생 및 메시지 검증
- 사다리 경로에 따른 결과 매핑 검증 - 존재하지 않는 이름으로 결과 조회시 예외처리 - 전체 결과 조회시 모든 결과 반환
- 2명 이상, 중복 불가에 대한 검증
CodingMasterLSW
left a comment
There was a problem hiding this comment.
수고하셨습니다! 너무 세부적으로는 리뷰를 안 남겼고, 눈에 보이는것만 간단하게 남겨봤습니다ㅎㅎ
이번 구현에서는 원시값 포장을 한 클래스들이 많아 domain 위주의 테스트코드가 주를 이뤘는데, 이런 클린 아키텍쳐 미션 형태의 코드에서도 controller에 대한 테스트코드가 필수일까요?
- 상단에 코멘트를 남겨놓았는데요, 진형님이 생각하시는 Controller의 책임에 대해 이야기 한 다음에 답변드리는게 좋을 것 같네요ㅎㅎ
코멘트에 관련해 제 생각을 간단하게 말해보자면, 현재 모든 메서드에서 정적 팩토리 메서드를 사용해 생성자를 사용하고 있는데요, 이 생성 책임을 외부로 분리하면 코드가 한결 간단해질 것 같기도 하네요ㅎㅎ
해당 리뷰를 토대로 2차 리팩토링을 진행하면 될 것 같습니다. 혹 이해가 안 되는 부분이 있다면 언제든 연락주십쇼!
그리고 진형님이 생각했을 때 수정해야 할 부분이 있다면, 그 부분도 함께 고쳐보시죠!
| import ladder.view.InputView; | ||
| import ladder.view.OutputView; | ||
|
|
||
| public class LadderGameController { |
There was a problem hiding this comment.
Controller는 어떤 책임을 가지고 있고, Application은 어떤 책임을 가지고 있는건가요??
진형님이 생각하시는 Controller는 어떤 책임을 가지고 있는거에요?
There was a problem hiding this comment.
이런 미션을 하기 전에는 Controller-Service-Repository의 구조에서만 개발을 하던 입장이라..
API 요청을 받아서 필요한 Service를 호출하는 역할로만 생각해왔습니다.
자체적으로 로직 수행이나 기능을 담지는 않고 로직을 담은 클래스들을 연결하는 책임으로 생각됩니다!
| private LadderResults(List<LadderResult> results) { | ||
| this.results = results; | ||
| } |
There was a problem hiding this comment.
외부에서 Results 값을 조작하거나 변경될 수 있는데, 이에 대해서 어떻게 생각하시나요??
| private static List<Line> generateLines(int participantCount, LadderHeight height, BooleanSupplier strategy) { | ||
| List<Line> lines = new ArrayList<>(); | ||
| int pointCount = participantCount - 1; | ||
| for (int i = 0; i < height.getValue(); i++) { | ||
| lines.add(Line.from(pointCount, strategy)); | ||
| } | ||
| return lines; | ||
| } |
There was a problem hiding this comment.
List을 일급컬렉션으로 별도로 관리하면, 해당 코드에 대한 책임 분리를 할 수 있을 것 같네요!
| public int climb(int startIndex) { | ||
| int currentIndex = startIndex; | ||
| for (Line line : lines) { | ||
| currentIndex = line.move(currentIndex); | ||
| } | ||
| return currentIndex; | ||
| } |
There was a problem hiding this comment.
해당 코드가 pubic으로 열려있을 필요가 있나요?? generateResults() 내부에서만 쓰이는 값 아니에요??
| public class Point { | ||
|
|
||
| private static final Point BRIDGE = new Point(true); | ||
| private static final Point EMPTY = new Point(false); |
There was a problem hiding this comment.
해당 클래스는 Enum으로 관리할 수도 있을 것 같은데, 상수 클래스로 관리하게 된 특별한 이유가 있을까요?
| public static Participants from(List<String> names) { | ||
| List<Name> nameList = names.stream() | ||
| .map(Name::from) | ||
| .collect(Collectors.toList()); | ||
| return new Participants(nameList); | ||
| } |
There was a problem hiding this comment.
가변으로 설정한 이유가 있나요??
조금 더 자세하게 말해보자면, toList() 대신, .collect(Collectors.toList())를 사용한 이유를 물어보는 겁니다!
다른 코드에서 방어 로직이 안 보여서요...!
| public static Line from(int size, BooleanSupplier strategy) { | ||
| return new Line(generatePoints(size, strategy)); | ||
| } |
There was a problem hiding this comment.
단순히 from이라는 네이밍을 하기에는, 내부적으로 너무 많은 일을 하고 있는 것 같아요. 생성자 네이밍을 보다 구체적으로 적어주는것도 좋아보입니다!
| public static Ladder of(Participants participants, LadderHeight height, BooleanSupplier strategy) { | ||
| int participantCount = participants.size(); | ||
| List<Line> lines = generateLines(participantCount, height, strategy); | ||
|
|
||
| return new Ladder(lines, participantCount); | ||
| } |
There was a problem hiding this comment.
BooleanSupplier가 현재 Ladder -> Lines -> Line 이렇게 타고 들어가는 것 같은데, 이거 외부로 분리할 수는 없을까요??
불필요한 매개변수가 계속 생긴다는 느낌이 드네요!
사다리 게임 구현
1. 로또 게임 실행 흐름
-참여 인원 및 실행 결과 입력: InputView를 통해 참여자 이름과 결과 항목을 쉼표(,) 기준으로 입력받습니다. Name과 LadderResult 객체 생성 시 각각 5자 이하의 길이 제한 및 빈 값 검증을 수행.
2. 주요 클래스 설명
3. 궁금한 점