-
Notifications
You must be signed in to change notification settings - Fork 60
[그리디] 하수한 사다리 미션 제출합니다. #83
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: chemistryx
Are you sure you want to change the base?
Conversation
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.
안녕하세요 수한님! 미션 하시느라 고생 많으셨어요😃
앞으로 함께 많은 이야기 나눌 수 있으면 좋겠습니다. 잘 부탁드립니다!
우선 PR에 각 클래스별로 코드의 구조와 흐름이 꼼꼼하게 정리되어 있어서 읽기 편했어요.
추가로, 저는 개인적으로 README에 코드에서 사용되는 용어나 클래스 또는 변수명 등을 정리해두면 다른 분들이 이해하기 더 쉽더라구요. 나중에 한 번 적용해보셔도 좋을 것 같습니다!
로직 전반적으로 객체가 잘 분리되어 있고 VO도 적절하게 활용하신 점이 깔끔하니 아주 좋았습니다!
입력값에 대한 검증 로직이 조금 더 보강되면 더 견고한 코드가 될 것 같아요. 미션 요구사항에 명시되지 않았더라도, 수한님께서 필요하다고 판단되는 검증은 자유롭게 추가해보셔도 좋을 것 같습니다 :)
이번 리뷰에서는 Controller의 흐름을 따라가며, 비즈니스 로직을 제외한 부분을 중심으로 피드백을 남겨보았습니다. 다음 리뷰에서는 비즈니스 로직을 위주로 읽어보겠습니다!
🤔 질문사항 답변
아래는 질문사항에 대한 답변입니다
- 이번 미션에서는 함수형 프로그래밍 문법을 적극적으로 사용해보았는데, 사용 과정에서 로직이 복잡해질수록 depth 자체는 감소할 수 있어도 가독성 측면에서는 오히려 알아보기가 힘들어진다는 느낌을 받았습니다. (e.g., LadderController::handleOutcomeQuery) 따라서 이러한 부분에 대해 어느 정도 trade-off가 존재한다고 생각하는데, 해윤님은 어떠한 생각을 가지고 계신지 궁금합니다..!
새로운 방식을 시도해보신 점이 정말 좋네요👍
저는 함수형 프로그래밍 방식을 도입했더니 코드의 가독성이 좋아지지 않았다면, 다른 방법도 고안해볼 것 같아요! 예를 들어 depth를 줄이기 위해, stream등의 문법을 사용했는데 가독성이 안좋아졌다면, 다른 방법으로 depth를 줄일 수 있는 방법을 생각해보는거죠.
또한 어떤 로직이 너무 길고 복잡해보인다 하면, 한 메서드 또는 절이 하나의 역할만 하는가? 를 먼저 고민해보면 좋을 것 같아요. 하나의 절이 많은 역할을 수행하게되면 읽기 힘든 복잡한 코드가 되기 쉬우니까요!
말씀해주신 LadderController의 handleOutcomeQuery 메서드를 보도록하겠습니다. 해당 메서드는 입력 + 반복 제어 + 결과 조회 + 출력의 역할을 하고있어요. 근데 네가지 기능을 하나의 stream절에서 실행하고 있으니 딱 봤을 때 흐름이 눈에 쉽게 들어오지 않는 것 같아요. 이러한 점들을 고려하여 리팩토링 진행해보시면 좋을 것 같습니다.
결론은 읽기 쉬운 코드를 작성하는 것은 매우 중요하기 때문에, 가독성에서의 문제를 느꼈다면 사용하지 말자(=대안을 찾자)! 였습니다
- 이번 미션의 경우에도 depth가 2를 넘지 않게 구현하도록 하는 조건이 명시되어 있었습니다. LadderFactory::createConnections() 메소드의 경우 고민을 해보았으나 도무지 depth를 여기서 더 줄일 방법이 떠오르지 않았습니다 ㅠ 혹시 해윤님은 이와 관련해서 다른 아이디어가 있으신지 궁금합니다!
생각나는 방법은
- Early return
for (int i = 0; i < width - 1; i++) {
if (!shouldConnect(random)) continue;
connections.add(new Connection(i, i + 1));
i += 1; // skip the right next line
}
'어떤 조건을 만족한다면 ~~해라' 가 아닌, 기본적으로 ~~를 하지만 '어떤 조건을 만족하지 않는다면 그 전에 반환 또는 break 또는 continue'를 하는 방법입니다.
조금 억지같지만 depth는 1로 만들 수 있을 것 같아요!
- 메서드 분리
if (shouldConnect(random)) {
connections.add(new Connection(i, i + 1));
i += 1; // skip the right next line
}
가장 많이 사용해 보셨을 방법인데요, 위 로직을 메서드로 분리하면 depth가 줄어들게 됩니다. 이렇게 되면 반복과, 연결 생성을 별도의 메서드로 분리할 수 있다는 장점도 있겠네요!
| public class Participant { | ||
| public static final int PARTICIPANT_NAME_MAX_LENGTH = 5; | ||
| private final String name; | ||
|
|
||
| public Participant(String name) { | ||
| if (name.length() > PARTICIPANT_NAME_MAX_LENGTH) { | ||
| throw new IllegalArgumentException("참가자의 이름은 최대 " + PARTICIPANT_NAME_MAX_LENGTH + "자만 가능합니다."); | ||
| } | ||
|
|
||
| this.name = name; | ||
| } | ||
|
|
||
| public String getName() { | ||
| return name; | ||
| } | ||
| } |
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.
수정할 필요는 없지만 같은 단순한 값을 나타내는 객체를 VO라고 하는데요, 값 객체는 class가아닌 record로 사용해도 좋습니다!
record는 모든 필드값이 불변 객체로 선언되고, equals(), hashCode(), toString()등의 코드가 자동 생성된다는 장점이 있습니다.
이러한 개념이 있다고 알고 넘어가면 좋을 것 같아요~
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.
앗 저번 스터디에서도 잠깐 언급되었던 내용이었던 것 같은데, 아무래도 적용해주는편이 반복적인 getter 메소드도 줄일 수 있어 훨씬 좋은 것 같습니다!
f1244e4 커밋에 반영했습니다~
| // method chaining으로 구성하려고 하였으나 Input 검증으로 인해 각각 따로 받음 | ||
| GameConfigurationBuilder builder = new GameConfigurationBuilder(); | ||
|
|
||
| List<Participant> participants = InputView.getParticipants().stream().map(Participant::new).toList(); |
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.
입력받은 String을 List로 변환하는 과정을 두 단계로 나누어
string을 List으로 변환하는 로직은 inputView에 위치하고,
List을 List와 같이 객체들의 리스트로 저장하는 로직은 컨트롤러에 작성하셨네요!
해당 로직을 모두 controller에서 진행하지 않고, 단계 별로 계층을 다르게 한 이유가 궁금합니다!
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.
추가로 participant 입력에 대한 검증 로직이 없어
,또는,,(,만으로 이루어진 문자열) 입력한 경우- 공백 혹은 아무것도 입력하지 않은 경우
에 예외를 던지지 않고 다음 단계 입력 로직이 실행되네요! 적절한 위치에 검증 로직을 추가해 위와 같은 경우에도 예외문을 던지면 좋을 것 같은데, 어디에 위치시키면 좋을 것 같나요?
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.
입력받은 String을 List로 변환하는 과정을 두 단계로 나누어 string을 List으로 변환하는 로직은 inputView에 위치하고, List을 List와 같이 객체들의 리스트로 저장하는 로직은 컨트롤러에 작성하셨네요!
해당 로직을 모두 controller에서 진행하지 않고, 단계 별로 계층을 다르게 한 이유가 궁금합니다!
해당 방식의 경우 InputView에서는 정말 기본적인 입력값에 대한 처리(e.g., 목록의 경우 split 후 List로 변환)만 담당하고, 이후 해당 값들을 의미있는 객체로 변환하는 과정은 컨트롤러와 같이 관련 있는 로직에서 담당하는 것이 더 좋다고 판단하여 두 단계로 나누어서 이루어질 수 있도록 작성하였습니다!
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.
추가로 participant 입력에 대한 검증 로직이 없어
,또는,,(,만으로 이루어진 문자열) 입력한 경우- 공백 혹은 아무것도 입력하지 않은 경우
에 예외를 던지지 않고 다음 단계 입력 로직이 실행되네요! 적절한 위치에 검증 로직을 추가해 위와 같은 경우에도 예외문을 던지면 좋을 것 같은데, 어디에 위치시키면 좋을 것 같나요?
제가 테스트 해보았을때는 두 케이스(,, ,,) 모두 정상적으로 검증 로직(GameConfigurationBuilder::participants())이 실행되는데, 혹시 제가 놓친 부분이 있을까요?
공백 입력의 경우 #83 (comment) 에서 이어서 다루도록 하겠습니다!!
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.
제가 테스트 해보았을때는 두 케이스(,, ,,) 모두 정상적으로 검증 로직(GameConfigurationBuilder::participants())이 실행되는데, 혹시 제가 놓친 부분이 있을까요?
다시 실행해보니 정상 작동하네요😂
| public static List<String> getParticipants() { | ||
| System.out.println("\n참여할 사람 이름을 입력하세요. (이름은 " + INPUT_DELIMITER + "로 구분하세요)"); | ||
|
|
||
| return Arrays.stream(scanner.next().split(INPUT_DELIMITER)).toList(); |
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.
저라면 해당 로직을 이렇게 작성할 것 같아요!
return Arrays.asList(scanner.nextLine().split(INPUT_DELIMITER));
저는 입력을 받을 때 한 줄씩 받기 위해 scanner.nextLine으로 받는 편인데 scanner.next를 선택한 이유가 있을까요?
또한 고정된 리스트인 Arrays.asList()대신 Stream.toList()를 사용한 특별한 이유가 있다면 궁금합니다!
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.
scanner.next()의 경우 사실 별 다른 의미가 있어서 선택한 것은 아닙니다..! 이와 관련해서 찾아보니 next()의 경우 공백 문자 이전까지 입력을 받고, nextLine()의 경우 개행 문자 이전까지 입력을 받는것으로 이해했습니다. 현재 미션의 경우 한 라인 별로 구분하여 입력 받는 것이 더 적절하다고 판단하여 nextLine()으로 수정하도록 하겠습니다!
Stream.toList()는 최초 구현 당시에 InputView에서 Participant에 대한 맵핑도 함께 수행하다보니 스트림 연산의 용이성을 위해 해당 방식으로 구현을 진행했었습니다. 하지만 맵핑 로직을 컨트롤러로 분리함에 따라 더 이상 스트림 연산을 수행할 필요가 없어 말씀하신 Arrays.asList()를 사용하는 방식도 문제가 없을 것 같아 해당 방식으로 진행해보겠습니다!
| public GameConfigurationBuilder participants(List<Participant> participants) { | ||
| if (participants == null || participants.isEmpty()) { | ||
| throw new IllegalArgumentException("참가자 목록은 비어 있을 수 없습니다."); | ||
| } | ||
|
|
||
| this.participants = participants; | ||
|
|
||
| return this; | ||
| } |
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.
엔터를 입력 받은 후의 participants 배열을 찍어보니 아래와 같이 name이 공백인 객체가 들어가 있는 것을 확인했습니다.

따라서 Participant의 생성자에 공백 유무를 검사하는 로직을 추가하여 수정하였습니다~!
반영 커밋: d3d50f7
| public class LadderController { | ||
| public void run() { | ||
| try { | ||
| GameConfiguration configuration = readConfiguration(); |
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.
개별 값(participants, outcomes, width, height)을 바로 넘기지 않고, GameConfiguration 객체를 통해 입력값을 한 번에 전달하도록 설계하신 이유가 궁금합니다!
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.
제가 생각해보았을때 GameConfiguration객체를 도입한 이유는 다음과 같습니다:
-
입력 검증 로직과 게임 로직 분리
현재GameConfiguration객체의 경우GameConfigurationBuilder를 통해 생성되도록 의도하였는데, 검증 로직의 경우 빌더가 담당함으로써 컨트롤러에서는 게임 실행 자체만을 담당할 수 있도록 구성하였습니다. -
입력 값에 대한 순차적 검증
사실 빌더 패턴을 도입하게된 이유라고 볼 수도 있을 것 같은데, 기존validate()함수와 같은 방식으로 사용하게 되면, 아래와 같이 검증이 필요한 값들에 대하여 대체로 한 곳에서 검증을 수행합니다.
private static void validateParams(List<Participant> participants, List<String> outcomes, int width, int height) {
if (participants.size() != outcomes.size()) {
throw new IllegalArgumentException("참가자의 수와 실행 결과의 수는 같아야 합니다.");
}
if (width <= 0 || height <= 0) {
throw new IllegalArgumentException("크기는 양수만 입력할 수 있습니다.");
}
}해당 방식의 경우 검증할 값이 많아질 수록 가독성이 떨어진다고 판단했고, 값이 많아짐에 따라 메소드 별로 분리하는 방법도 생각해볼 수 있으나 이 방식 또한 결국 각각의 검증 메소드를 호출해야 한다는 부분이 효율적이지 못하다고 판단했습니다.
따라서 빌더를 통해 값 할당과 동시에 검증 로직을 수행하는 방식이 위 두가지 단점을 해결할 수 있다고 판단하여 빌더 패턴 방식을 도입해보았습니다.
Game생성의 단순화
만약 인자 수가 많아진다면 인자의 순서가 헷갈릴 수도 있고, 새로운 필드가 추가된다면 이에 따라 계속 수정해야 한다는 것이 번거롭다고 판단했습니다. 따라서 객체 하나만을 넘겨주는 방식을 통해 이러한 문제를 해결할 수 있었습니다.
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.
로직 생성의 이유를 명확히 고민하고 코드로 잘 구현하신 점이 정말 좋네용👍
2번에 관해서는
해당 방식의 경우 검증할 값이 많아질 수록 가독성이 떨어진다고 판단했고, 값이 많아짐에 따라 메소드 별로 분리하는 방법도 생각해볼 수 있으나 이 방식 또한 결국 각각의 검증 메소드를 호출해야 한다는 부분이 효율적이지 못하다고 판단했습니다.
라는 의견에 동의합니다! builder로 검증 로직을 분리하여 현재는 도메인이 더욱 비즈니스 로직에 집중할 수 있도록 설계된 것 같아요.
다만 매우매우 개인적으로는 도메인이 유효성까지 스스로 보장하는 것도 DDD 관점에서 괜찮은 접근이라고 생각합니다! 그래도 지금은 설계 의도도 명확하고, 충분히 적절해 보여요👍
| Stream.generate(InputView::getParticipantForResult) | ||
| .takeWhile((input) -> !input.equals("all")) | ||
| .forEach((input) -> result.getResults().keySet().stream() | ||
| .filter((participant) -> participant.getName().equals(input)) | ||
| .findFirst() | ||
| .ifPresentOrElse( | ||
| (target) -> OutputView.printGameResultOf(target, result), | ||
| () -> System.out.println("존재하지 않는 참가자입니다.") | ||
| ) | ||
| ); |
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.
하나의 stream에서 너무 많은 역할을 하고 있는 것 같아요! 로직을 분리해보면 좋을 것 같습니다~
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 class InputView { | ||
| private static final Scanner scanner = new Scanner(System.in); | ||
| private static final String INPUT_DELIMITER = ","; |
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.
구분자를 view계층에도 상수화시켜 관리 포인트를 줄인점이 매우 좋네요~
|
해윤님 작성해주신 리뷰에 대한 반영 및 질문에 대한 코멘트 달아놓았습니다! depth의 경우에도 해윤님께서 주신 의견과 더불어 한번 생각을 해보았는데, 2번 아이디어의 경우 현재 로직 상 반복문 내 인덱스( 또한 입력값에 대한 검증 로직 보강 관련해서도 말씀해주셨는데요, 현재 결과 값에 대한 검증은 별도로 이루어지고 있지 않아(단순 String으로 취급), 이에 대한 VO를 만들어서 검증 로직을 적용시켜 보았는데 (9648d42), 이외에 더 필요하다고 생각되는 검증 로직이 있을까요? 마지막으로 |
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.
리뷰 꼼꼼히 잘 반영해주셨네요👍 커밋을 기능 단위로 잘 찍어주셔서, 수정 범위를 확인하기 수월했어요~
또한 입력값에 대한 검증 로직 보강 관련해서도 말씀해주셨는데요, 현재 결과 값에 대한 검증은 별도로 이루어지고 있지 않아(단순 String으로 취급), 이에 대한 VO를 만들어서 검증 로직을 적용시켜 보았는데 (9648d42), 이외에 더 필요하다고 생각되는 검증 로직이 있을까요?
출력값 객체(Outcome)에 대한 코멘트는 아래 추가해두었습니다!
추가 검증으로는

높이 입력 시, 정수가 아닌 자료형이 입력됐을 경우 예외를 던지는 로직도 추가되면 좋을 것 같아요!
그 외에는 검증로직을 세세하게 잘 작성해주신 것 같습니다 👏
| if (name.length() > PARTICIPANT_NAME_MAX_LENGTH) { | ||
| throw new IllegalArgumentException("참가자의 이름은 최대 " + PARTICIPANT_NAME_MAX_LENGTH + "자만 가능합니다."); | ||
| } | ||
|
|
||
| if (name.isBlank()) { | ||
| throw new IllegalArgumentException("참가자의 이름은 공백일 수 없습니다."); | ||
| } |
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.
수한님께서 어떤 검증 로직은 도메인 내부에 두고, 어떤 검증 로직은 GameConfigurationBuilder 내부에 두었는지 기준이 궁금합니다!
도메인 값 자체의 유효성 검증은 도메인 내부에서 이루어졌다고 한다면(Participant나 Outcome)
GameConfigurationBuilder의 participants와 height도 도메인 값 자체의 유효성 검증이라는 생각이 들어서요. 의견이 궁금합니다 💭
| private static final String INPUT_DELIMITER = ","; | ||
|
|
||
| public static List<String> getParticipants() { | ||
| System.out.println("\n참여할 사람 이름을 입력하세요. (이름은 " + INPUT_DELIMITER + "로 구분하세요)"); |
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.
개발환경마다 줄바꿈 문자가 다르기에 \n을 직접 문자열 안에 넣어서 사용하는 것 보다는
System.out.println();
System.out.println("참여할 사람 이름을 입력하세요. (이름은 " + INPUT_DELIMITER + "로 구분하세요)");
다음과 같이 작성하는 것이 더욱 직관적이고 정확할 것 같아요!
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.
해당 클래스는 객체로 관리하기에는 이유가 부족해 보입니다.
검증 로직만을 담당하고 있다면, GameConfigurationBuilder::outcomes로 옮기는 것도 괜찮을 것 같아요!
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.
메서드 호출 체인이 다소 깊은 것 같은데
indent(인덴트, 들여쓰기) depth를 2를 넘지 않도록 구현한다. 1까지만 허용한다.
위 요구사항 때문인가요? 저도 비슷한 로직 (createLine와 createConnections)은 합치려고 고민해보았지만 depth를 1까지만 허용한다는 조건때문에 쉽지 않은 것 같긴하네요🤔
| List<Outcome> outcomes = configuration.outcomes(); | ||
| Map<Participant, Outcome> result = new LinkedHashMap<>(); | ||
|
|
||
| OutputView.printLadderResult(this); |
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.
view가 domain 내부에서 호출되고있네요! 어떻게 수정할 수 있을까요?
| public GameResult execute() { | ||
| List<Participant> participants = configuration.participants(); | ||
| List<Outcome> outcomes = configuration.outcomes(); | ||
| Map<Participant, Outcome> result = new LinkedHashMap<>(); | ||
|
|
||
| OutputView.printLadderResult(this); | ||
|
|
||
| for (int start = 0; start < configuration.width(); start++) { | ||
| int end = traverse(start); | ||
| Participant participant = participants.get(start); | ||
| Outcome outcome = outcomes.get(end); | ||
|
|
||
| result.put(participant, outcome); | ||
| } | ||
|
|
||
| return new GameResult(result); | ||
| } |
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.
현재 execute()가 사다리 출력, 경로 탐색, 결과 매핑까지 모두 담당하고 있어, 책임의 범위가 넓어보입니다!
Map<Participant, Outcome> result = new LinkedHashMap<>();
result에 관련된 로직은 Map<Participant, Outcome> results를 필드로 가지고있는 GameResult 내부로 옮겨도 될 것 같은데 어떻게 생각하시나요?
| return line.connections().stream() | ||
| .filter((connection) -> connection.left() == col || connection.right() == col) | ||
| .findFirst() | ||
| .map((connection) -> getConnectedColumn(connection, col)) | ||
| .orElse(col); |
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.
stream이 길어지면 중간에
private int findNextColumn(Line line, int col) {
Optional<Connection> connected = line.connections().stream()
.filter(conn -> conn.left() == col || conn.right() == col)
.findFirst();
return connected
.map(conn -> getConnectedColumn(conn, col))
.orElse(col);
}
이렇게 변수로 분리해서 한번 끊어주는 것 도 좋을 것 같아요
| private static String getConnectionSegment(Line line, int index) { | ||
| if (isConnected(line, index)) { | ||
| return "-----"; | ||
| } | ||
|
|
||
| return " "; | ||
| } |
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.
"-----" " "는 상수화하면 좋을 것 같아요!
| private static void printParticipants(List<Participant> participants) { | ||
| String line = participants.stream() | ||
| .map((participant -> OutputView.centerAlign(participant.name()))) | ||
| .collect(Collectors.joining(" ")); | ||
|
|
||
| System.out.println(line); | ||
| } | ||
|
|
||
| private static void printOutcomes(List<Outcome> outcomes) { | ||
| String line = outcomes.stream() | ||
| .map((outcome -> OutputView.centerAlign(outcome.value()))) | ||
| .collect(Collectors.joining(" ")); | ||
|
|
||
| System.out.println(line); | ||
| } |
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.
두 로직 모두 centerAlign와 같은 클래스에 있기 때문에, .map 부분에서 OutputView.centerAlign -> centerAlign 바꿔도 정상작동 합니다!
또한 centerAlign메서드 내부에서 매번 width를 가져오는 것 보다는, 해당 두 메서드에서 한번 가져오고 매개변수로 넘겨주는 것이 더욱 효율적일 것 같습니다~
private static void printOutcomes(List<Outcome> outcomes) {
int width = Participant.PARTICIPANT_NAME_MAX_LENGTH;
String line = outcomes.stream()
.map((outcome -> centerAlign(outcome.value(), width)))
.collect(Collectors.joining(" "));
System.out.println(line);
}
해윤님 안녕하세요! 리뷰어로써는 처음 뵙는 것 같은데, 이번 사다리 미션도 잘 부탁드리겠습니다 🙃
아래는 현재 프로젝트 구조입니다:
애플리케이션 실행 흐름은 다음과 같습니다:
InputView)GameConfigurationBuilder로 넘겨주어GameConfiguration생성과 동시에 값 검증을 수행합니다. (LadderController::readConfiguration())Game객체를 생성하여 게임을 실행합니다.다음은 사다리 생성 및 이동 로직에 대한 부가 설명입니다:
사다리 생성
사다리 이동
아래는 구현 과정에서 발생한 질문 사항입니다:
이번 미션에서는 함수형 프로그래밍 문법을 적극적으로 사용해보았는데, 사용 과정에서 로직이 복잡해질수록 depth 자체는 감소할 수 있어도 가독성 측면에서는 오히려 알아보기가 힘들어진다는 느낌을 받았습니다. (e.g.,
LadderController::handleOutcomeQuery)따라서 이러한 부분에 대해 어느 정도 trade-off가 존재한다고 생각하는데, 해윤님은 어떠한 생각을 가지고 계신지 궁금합니다..!
이번 미션의 경우에도 depth가 2를 넘지 않게 구현하도록 하는 조건이 명시되어 있었습니다.
LadderFactory::createConnections()메소드의 경우 고민을 해보았으나 도무지 depth를 여기서 더 줄일 방법이 떠오르지 않았습니다 ㅠ 혹시 해윤님은 이와 관련해서 다른 아이디어가 있으신지 궁금합니다!작성하다 보니 내용이 길어졌는데, 혹시 코드 관련해서 부가적인 설명이 필요한 부분이 있으시면 언제든지 말씀해주세요!
긴 글 읽어주셔서 감사합니다~!