Skip to content
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

[2단계 - 사다리 게임 실행] 조이(김성연) 미션 제출합니다. #225

Merged
merged 65 commits into from
Feb 28, 2023

Conversation

yeonkkk
Copy link

@yeonkkk yeonkkk commented Feb 23, 2023

안녕하세요 춘식🎶! 조이입니다.

기능 구현 방법에 대해 고민하고, 리팩터링 과정에서 어려움이 많아 2번째 PR이 늦어졌습니다.
부족함이 많지만 2단계 미션도 리뷰 부탁드리겠습니다!

미션 관련 내용, 질문은 하단에 작성하였습니다.



변경 사항

  1. LadderGame

구현 요구 사항이 추가되어 전체 게임을 관리하는 LadderGame을 추가로 생성하였습니다. LadderGame의 역할은 사용자의 위치를 변화시키는 것, 각 사용자에 맞는 결과를 찾아주는 것입니다.

  1. LadderConnectionStatus

이전에 리뷰해주신 내용을 반영하여 LadderFormatLadderConnectionStatus로 수정하였습니다.

  1. User(사용자)에게 Position(위치) 값을 추가로 부여하여 결과 산출

사용자의 위치 값과 사다리의 connection 인덱스를 비교하여 위치값을 변경하는 방식으로 이동하게 하였습니다. 최종 Position 값을 통해 알맞은 실행 결과(Reward)를 산출하도록 하였습니다.



질문 사항

  1. InputValidator의 존재와 위치

InputView에서 입력값에 대한 기본 검증을 진행하고자 하였습니다.
그런데 입력값이 늘어나다보니 InputView에 validation을 위한 코드가 너무 많아졌다고 느껴 InputValidator 클래스를 생성하게 되었습니다. 오래 고민하다가 가독성과 유지보수를 위해 Validator 를 분리하는게 좋을 것 같다는 생각을 하여 이와 같은 결정을 하게 되었습니다.
그런데 코드의 양이 증가하면서 Validator를 분리하는 것이 좋은 것인지 정확한 판단이 서지 않아서 춘식의 의견을 여쭙고 싶습니다!

만약 이렇게 분리하는 것이 괜찮다면 Validator가 어느 위치에 있어야 적절한지도 궁금합니다.

  1. 객체 생성 방법

Ladder, Line, Rewards를 생성할 때 사용자의 수를 생성자의 파라미터로 사용하다는 것이 어색하다고 생각하여 static 메서드(of())를 통해 객체를 생성하도록 하였습니다.
같은 조원 분이 정적 팩토리 메서드에 대해 말씀해주셔서 찾아보고 적용해 보았는데 적절하게 적용한 것인지 궁금합니다!

  1. getter의 사용

이번 미션을 진행하면서 getter의 사용을 많이 했다고 느꼈습니다.
getter는 지양하는 것이 좋다고 알고 있어서 가능하면 메세지를 던져서 값을 얻을 수 있게 작성하고자 하였으나 전체적으로 많이 사용한 것 같습니다. 혹시 제 코드를 보시면서 getter가 불필요하게 사용되는 부분이 있어 알려주신다면 고민해보고 수정하고자 합니다!



이번 리뷰도 잘 부탁드리겠습니다:)
그럼 오늘도 좋은 하루 되세요! 🫧🍀

@bimppap
Copy link

bimppap commented Feb 24, 2023

리뷰에 시간이 조금 걸릴 것 같아 질문에 대한 답변 먼저 하겠습니다 😊

InputValidator의 존재와 위치

먼저 Validator가 꼭 필요한 지부터 고민해보면 좋겠습니다. Validator가 검증하고 있는 것들 중엔 각 객체가 생성될 때 필요한 조건들이 있는데요, 이걸 객체 내부에서 검증할 순 없는 걸까요? Validator로 검증을 할 때와 객체에서 검증을 할 때의 차이는 뭐가 있는지 한번 알아보는 건 어떨까요?

(+) 코드를 보니 Name, Rewards에서는 검증을 객체에서 해주고 있는데, 검증 방식은 통일해주세요!

객체 생성 방법

이 미션에서는 적절하게 구현해주신 것 같습니다 👍 그런데 왜 생성자를 통해 넣는 게 어색한 걸까요? 둘 다 같은 값을 넣어 객체를 생성하는 건 똑같은데 말이에요. 🤔 이유와 함께 생성자, 정적 팩토리 메서드의 차이와 각각의 장단점을 정리해보면 좋을 것 같습니다.

(+) 정적 팩토리 메서드는 명명방식에 따라 사용하는 방식이 달라지는 부분이 있는데요. 그것도 한번 찾아보면 좋을 것 같습니다. 그 외에도 이펙티브 자바를 읽으면 도움 되는 내용이 많으니, 시간 날 때 읽어보는 걸 추천드립니다. 😎

getter의 사용

빠르게 훑어봤을 때 디미터 법칙도 없고, 크게 눈에 띄는 점은 없었습니다. (제대로 리뷰하면서 의견이 달라질 수도 있습니다ㅋㅋ) 그럼에도 불구하고 get~ 메서드가 많이 보이는 것은 사실입니다.

그럼 getter를 어떻게 줄일 수 있을까요? 엘레강트 오브젝트에서는 getter/setter 안티 패턴에서 유해한 부분은 두 접두사인 get과 set이라고 합니다. 이 접두사들 때문에 객체가 진짜 객체가 아닌 데이터 저장소로 취급 받는다는 거죠. 책에서 내놓은 해결책은 간단합니다. 접두사를 제거하는 겁니다.

  • getDollars() 는 ‘데이터 중에 dollars를 찾은 후 반환하세요’ 라는 의미지만,
  • dollars() 라고 한다면 ‘dollar가 얼마나 있는가?’라고 묻는 거니 객체에 dollars 라는 필드가 있는지 알 수 없습니다. 이러한 방식으로 객체의 캡슐화를 존중하는 겁니다.

위 방법이 정답은 아니니 꼭 적용할 필요는 없습니다만, get이라는 접두사는 어디에 붙여야 가장 적절한지에 대해 고민해보면 좋을 것 같습니다.

Copy link

@bimppap bimppap left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

안녕하세요 조이! 춘식입니다.
깔끔하게 기능 추가해주셨네요. 👍
질문에 대한 답과 코멘트를 남겨봤으니 확인 부탁드립니다.
이해가 안 되는 부분은 언제든지 편하게 DM 주세요! 😎

@@ -1,11 +1,12 @@
import controller.LadderGameController;
import domain.InputValidator;
import view.InputView;
import view.OutputView;

public class Main {
public static void main(String[] args) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

흠 결과가 다르게 나오는 때도 있는 것 같습니다?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

현재 execute 함수에서 illegalArgumentException 만 잡고있는데요, 가장 상위 레벨에서 catch를 하고 있기 때문에 예상치 못한 에러를 대비해 타입을 Exception 으로 바꾸는 게 나을 것 같습니다.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

커밋이 동일한 메세지명이 있어서 살펴보니 다른 파일들을 같은 이유로 변경했던데, 원격 저장소에 아직 push를 안 한 상태라면 터미널에서 git commit --amend 를 쓰거나 인텔리제이에서 커밋 수정 기능을 활용해 직전 커밋에 현재의 변경 사항을 반영할 수 있습니다! (push를 했다면 추후 force push를 해야 하니 참고 바랍니다.)

import static org.junit.jupiter.api.Assertions.*;


class InputValidatorTest {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

72개나...! 👍👍

@@ -21,31 +26,83 @@ public LadderGameController(InputView inputView, OutputView outputView) {
}

public void run() {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

하나라도 틀리면 얄짤없이 프로그램이 종료되는군요... 🥹(필수는 아니기 때문에 꼭 반영할 필요는 없습니다!)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저도 필요한 기능이라고 생각되어 이번 리팩터링에 추가해보았습니다☺️

Comment on lines 80 to 88
private List<User> generateUsers(List<String> userNames) {
return userNames.stream()
.map(Name::new)
.map(User::new)
.collect(Collectors.toUnmodifiableList());
List<User> users = new ArrayList<>();

for (int i = 0; i < userNames.size(); i++) {
users.add(new User(new Name(userNames.get(i)), new Position(i)));
}

return users;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

생성자 오버로딩을 사용하면 굳이 컨트롤러에 둘 필요가 없을 것 같습니다. 😎

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

생성자 오버로딩을 사용하려다가 아래 코멘트를 참고하여 정적 팩토리 메서드를 적용하였습니다.

  1. 전체 코드 스타일 통일 2. 객체 생성 캡슐화 가 주된 이유입니다.


public User(Name name) {
public User(Name name, Position position) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

어떤 건 정적 팩토리 메서드를 쓰고 어떤 거 아닌데 기준이 있을까요?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

정적 팩토리 메서드를 적용한 객체들은 생성자에 해당 객체가 알 필요가 없는 인자가 들어간다고 생각해서 사용했습니다. 그런데 이번 리팩터링 과정에서 다시 정적 팩토리 메서드에 대해서 공부해보고 생각이 변하게 되었습니다.

정적 팩토리 메서드의 이점은 많지만 해당 미션에서 제가 생각한 이점은 2가지 입니다.
1. 정적 팩토리 메서드를 사용하게 되면 객체를 생성하는 메서드에 네이밍을 할 수 있습니다.

  • 저는 네이밍 컨벤션을 적용하였습니다.
  • 하나의 매개변수를 받아 객체를 생성하는 경우 from 으로, 여러개의 매개 변수를 받아서 객체를 생성하는 경우 of 로 이름을 작성하였습니다.

2. 객체 생성을 캡슐화 할 수 있다.

  • 전체 도메인 객체 생성에 정적 팩토리 메소드를 적용한 이유는 코드의 통일성을 고려한 점도 있지만 해당 이점을 크게 생각했습니다. 생성자를 단순화 시킬 수 있고, 은닉할 수 있습니다.

추천해주신대로 이펙티브 자바의 정적 팩토리 메소드 부분을 읽어보았습니다. 아직 제가 부족함이 많아 바로 이해는 못했습니다.. 그래서 Tecoble을 추가로 참고하여 학습해 보았습니다!

혹시 제가 놓친 부분이 있다면 말씀주세요!

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

잘 적용해주셨습니다. 👏
정적 팩토리 메서드는 잘 응용하면 싱글턴 패턴으로 사용할 수도 있으니 참고 바랍니다. 😉

Comment on lines +3 to +6
public enum LadderConnectionStatus {
CONNECTION("-----"),
NON_CONNECTION(" ");

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

멋지게 리팩토링해주셨네요. 👍

Comment on lines 35 to 52
private void validateRewardLengthByMaximumLimit(String reward) {
if (reward.length() > MAXIMUM_REWARD_LENGTH_LIMIT) {
throw new IllegalArgumentException(
String.format(
INVALID_REWARD_LENGTH_BY_MAXIMUM_LIMIT.getMessage()
, MAXIMUM_REWARD_LENGTH_LIMIT));
}
}

private void validateRewardLengthByMinimumLimit(String reward) {
if (reward.length() < MINIMUM_REWARD_LENGTH_LIMIT) {
throw new IllegalArgumentException(
String.format(
INVALID_REWARD_LENGTH_BY_MINIMUM_LIMIT.getMessage(),
MINIMUM_REWARD_LENGTH_LIMIT));
}
}

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1단계에서 예외 처리에 관해 드린 코멘트 링크를 달고 갑니다!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

숙지하고 진행했다고 생각했는데 코드를 작성하면서 다시 돌아가 버렸네요.. 이전에 리뷰해주신 내용 다시 읽어보았습니다. 고민하다가 최대 값, 최소 값에 대한 검증을 굳이 분리해서 할 필요가 없다고 느껴 합쳤습니다.
주요한 이유는 1. 메서드를 통합하여 가독성을 높이고 2. 사용자가 값을 잘못 입력했을 때 한번에 입력값의 요구사항을 파악할 수 있도록 하는 것입니다.
위와 같은 이유로 길이, 범위에 대한 검증 메서드는 모두 통합 하였습니다. 혹시 주신 코멘트에서 의도하신 것이 이러한 변경이 아니라면 말씀주세요!

return connections;
}

private void addIndex(boolean point, List<Integer> connections, int i) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getConnection의 경우 for문에서 쓰이는 인덱스(=보편적인 약속)이기에 약자로 써도 되지만 addIndex에서는 for문에서 온 인덱스라는 정보가 없으므로 약자로 쓰지 않고 index라고 명시해 주는 게 좋습니다.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

메소드 추출 기능을 써서 하다가 변수명을 놓친 것 같습니다. 감사합니다 춘식!

private final String name;

public Name(String name) {
name = name.trim();
name = name.strip();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

자바11에 이런 기능도 있었군요! 배우고 갑니다. 😎

@yeonkkk
Copy link
Author

yeonkkk commented Feb 27, 2023

안녕하세요 춘식! 🐱

지난 리뷰를 통해 사다리가 제대로 출력되지 않는 것을 발견하게 되었습니다.
getter 사용에 대한 고민도 많았는데 좋은 인사이트 주셔서 감사합니다!

그리고 이번 리팩터링 과정에서 진행한 모든 리뷰에서 질문드렸던 검증 위치에 대한 고민을 많이 해보았습니다.
이전에 주신 리뷰를 시간을 가지고 곱씹으니 생각이 정리되는 것 같았습니다.
그래서 이번 리팩터링에서는 아주 기본적인 검증만 inputView에서 담당하고, 이외의 검증은 도메인에서 하도록 적용 하였습니다.

그럼 이번에도 리뷰 잘 부탁드리겠습니다 🙂
좋은 하루 되세요!🎶

Copy link

@bimppap bimppap left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

안녕하세요 조이🎶! 춘식입니다.
리뷰 잘 반영해주셔서 이만 머지하도록 하겠습니다.
수고하셨습니다! 다음 미션도 파이팅입니다 🔥

@bimppap bimppap merged commit e16a56d into woowacourse:yeonkkk Feb 28, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants