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

[숫자 야구 게임] 변해빈 미션 제출합니다. #1613

Open
wants to merge 65 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
5e5ff7e
docs: 기능 구현 목록 최초 작성
h-beeen Oct 19, 2023
285df98
feat: Couputer 랜덤 숫자 뽑기 기능 구현
h-beeen Oct 19, 2023
87fa078
feat: Player 숫자 입력 및 검증 로직 추가
h-beeen Oct 19, 2023
403e53a
feat: Game 필수 기능 구현 (Player 입력 / Computer 난수 생성)
h-beeen Oct 19, 2023
cd65c41
docs: 구현 기능 목록 수정
h-beeen Oct 19, 2023
29a7d86
feat: Hint 필수 기능 구현 (볼, 스트라이크 카운트)
h-beeen Oct 20, 2023
d5e2f95
feat: 게임 breakPoint 설정
h-beeen Oct 20, 2023
d01607d
refactor: Constant 값 Config 분리 및 축약 문법 수정
h-beeen Oct 20, 2023
45e0473
feat: 힌트 출력 반복 루프 추가
h-beeen Oct 20, 2023
1447d6f
feat: 사용자의 중복 숫자 입력 예외 처리 추가
h-beeen Oct 20, 2023
02ff445
docs: 기능 요구사항 진행사항 갱신
h-beeen Oct 20, 2023
3cd75ee
refactor : MVC 패턴 리팩토링
h-beeen Oct 20, 2023
f65861b
feat: 시스템 설정(Config) 제약조건 추가
h-beeen Oct 20, 2023
641d1d3
refactor: Model Layer / View Layer 분리
h-beeen Oct 20, 2023
dbcdc54
fix: 게임을 새로시작하지 않는 버그 수정
h-beeen Oct 20, 2023
2bd8d97
fix: 숫자 입력 간 개행 오류 수정
h-beeen Oct 20, 2023
8306983
feat: PlayerNumber / ComputerNumber를 Number 객체로 병합
h-beeen Oct 20, 2023
f837542
refactor: Hint 로직 도메인 주도 설계로 개선
h-beeen Oct 20, 2023
daad19e
feat: 생성자 접근제한자 조정
h-beeen Oct 20, 2023
e16b264
refactor: 메소드명 직관적 수정
h-beeen Oct 20, 2023
058853e
refactor: 요구사항 기능 구현 완료 및 MVC 패턴 리팩토링
h-beeen Oct 20, 2023
c672022
fix: 테스트 실패 케이스 핸들링(메소드 변경)
h-beeen Oct 21, 2023
dd8cba7
refactor: 프로그램 종료 플래그 상수 선언
h-beeen Oct 21, 2023
990b7c7
refactor: 상수 변수 Enum으로 리팩토링
h-beeen Oct 21, 2023
f5fa1bc
refactor: validator 로직 개선
h-beeen Oct 21, 2023
3e84307
refactor: 미사용 함수 삭제
h-beeen Oct 21, 2023
6a46e8c
refactor: Enum을 활용한 불변 상수 패키징
h-beeen Oct 21, 2023
04d9229
refactor: 클래스 - 상수 선언부 개행 제거
h-beeen Oct 21, 2023
5e9b6c2
docs: 구현 기능 목록 update
h-beeen Oct 21, 2023
8962557
feat: Result 멤버변수 final 선언
h-beeen Oct 21, 2023
b84f780
style: 코드 포맷팅
h-beeen Oct 21, 2023
7acc36b
chore: 패키지 이름 변경 (model -> domain)
h-beeen Oct 22, 2023
ba6bd2c
feat: Number 일급 컬렉션 적용
h-beeen Oct 22, 2023
b1dd4e1
fix: BallCount / StrikeCount를 제대로 카운트 하지 못하는 이슈 수정
h-beeen Oct 22, 2023
fa90c35
refactor: 전역으로 선언되어있던 Config 설정을 수정
h-beeen Oct 22, 2023
3f7bb2e
feat: Getter 메소드 삭제
h-beeen Oct 22, 2023
7f7d7e8
refactor: 지역변수 재참조 이슈 해결을 위해 do-while문 제거
h-beeen Oct 22, 2023
ac8b5bd
refactor: 메소드명 직관적으로 개선
h-beeen Oct 22, 2023
4a03375
refactor: 게임 종료 플래그 지역변수로 변경
h-beeen Oct 22, 2023
febd594
refactor: 불필요 viewConfig 제거
h-beeen Oct 22, 2023
1999b2c
refactor: 변수, 메소드 명 직관적으로 개선 (request -> ask)
h-beeen Oct 22, 2023
99aa219
refactor: Result 출력 방식 개선을 위한 Enum 추가 및 로직 수정
h-beeen Oct 22, 2023
22f1aef
refactor: 출력 메소드 재사용성 확장 및 출력 메세지 열거형으로 변경
h-beeen Oct 22, 2023
575e4a5
refactor: 공통 정보 출력 메소드 명 변경(printGameInformation -> printInformation)
h-beeen Oct 22, 2023
a1780f0
docs: README.md 프로젝트 개요 작성
h-beeen Oct 22, 2023
26561ff
fix: 빌드 에러, 개행을 정상적으로 출력하지 않는 오류 수정
h-beeen Oct 23, 2023
7ac0f58
chore: 패키지명 변경 (config -> global)
h-beeen Oct 23, 2023
473e3cd
docs: README.md 패키지 변동사항 업데이트
h-beeen Oct 23, 2023
ab42278
refactor: model <-> view 의존 관계 제거
h-beeen Oct 23, 2023
0755554
refactor: 변수명 직관적 개선 (PrintMessage -> StaticNotice)
h-beeen Oct 23, 2023
c56887c
refactor: 정적 팩토리 메소드를 활용한 예외처리 메소드 커스텀
h-beeen Oct 23, 2023
a1307d6
refactor: BaseballException.of 형태 적용 예외처리
h-beeen Oct 23, 2023
9de442b
fix: 변수명 통일 (player, user -> player)
h-beeen Oct 23, 2023
fecb933
refactor: static import를 활용한 코드 간소화
h-beeen Oct 23, 2023
2b505a0
refactor: SRP 준수를 위해서 메소드 추가 분리
h-beeen Oct 23, 2023
f9a297c
feat: 멤버변수가 없는 클래스의 생성자 제한
h-beeen Oct 23, 2023
5fdb512
chore: 패키지 변경 (domain/exception -> global/exception)
h-beeen Oct 23, 2023
3fc9cd2
docs: README.md 업데이트
h-beeen Oct 23, 2023
caa0bd3
feat: 메소드 분리 및 else-if문 구조 변경
h-beeen Oct 23, 2023
1fa1b0b
feat: 변수명 변경 및 종료조건 예외처리 추가(1, 2 외의 값)
h-beeen Oct 23, 2023
0422d7b
feat: 메세지 출력이 GameConfig 전역 설정에 의존하도록 수정
h-beeen Oct 23, 2023
a51764b
docs: README.md 업데이트
h-beeen Oct 23, 2023
5d9eaf2
docs: README.md 업데이트
h-beeen Oct 23, 2023
e33b8a5
docs: README.md 업데이트
h-beeen Oct 23, 2023
6ae2ca1
docs: README.md 업데이트
h-beeen Oct 25, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
209 changes: 209 additions & 0 deletions docs/README.md

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.

많은 분들이 리드미에 좋은 평가를 남겨주시는 것 같아요!
가시성 높은 리드미를 만들고자 노력했는데 좋은 평가를 남겨주셔서 감사합니다!

Choose a reason for hiding this comment

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

너무나도 인상 깊은 기능 구현 목록인거 같습니다! 😀

Choose a reason for hiding this comment

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

저 같은 경우에는 리드미 파일을 추상적으로 작성했었는데
해빈님 리드미를 보니까 엄청 비교 되네요..!

2주차 미션에는 저도 본받아야겠어요!

Choose a reason for hiding this comment

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

리드미를 이렇게나 자세하게 쓸 수 있군요... 많이 배웠어요:)

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.

많은 노력을 기울였던 만큼, 좋은 리뷰를 받게되어 기분이 좋습니다 :)

Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
# ⚾&nbsp;&nbsp;Precourse-Week1 Mission **[숫자 야구]**

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.

감사합니다! 리드미에 상당한 공을 들였는데 예쁘게 봐주셔서 뿌듯합니다 :)

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.

리드미를 예쁘게 봐주셔서 감사합니다 :)

Choose a reason for hiding this comment

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

리드미가 너무 보기 좋네요! 나중에 협업할 때도 되게 좋을 것 같습니다

Copy link

Choose a reason for hiding this comment

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

README.md 를 이렇게 깔끔하고 예쁘게 작성할 수 있다니.. 너무 놀라워요.. 한 수 배워갑니다. 저는 너무 간결하게만 작성한 것 같아 반성하게 되네요.. ! 😅

Copy link
Author

Choose a reason for hiding this comment

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

좋게 봐주셔서 감사드립니다 :)


## 💌&nbsp;&nbsp;목차
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.

감사합니다 :)


- [📦&nbsp;&nbsp;패키지 구조](#패키지-구조)
- [✨&nbsp;&nbsp;기능 구현 목록](#기능-구현-목록)
- [🎨&nbsp;&nbsp;구현 간 고민했던 내용들](#구현-간-고민했던-내용들)

---

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.

예쁘게 설계한 만큼 좋은 평가를 받아 너무 뿌듯합니다 :)
감사합니다!

## 📦&nbsp;&nbsp;패키지 구조

<div align="center">
<table>
<tr>
<th align="center">Package</th>
<th align="center">Class</th>
<th align="center">Description</th>
</tr>
<tr>
<td><b>🕹&nbsp;&nbsp;controller</b></td>
<td><b>Game</b></td>
<td>게임 로직을 메인으로 동작하는 컨트롤러 클래스</td>
</tr>
<tr><td colspan="3"></td></tr>
<tr>
<td rowspan="2"><b>💻&nbsp;&nbsp;domain</b></td>
<td><b>Number</b></td>
<td>사용자에게 입력받는 숫자와, 컴퓨터가 생성하는 숫자 클래스
</td>
</tr>
<tr>
<td><b>Result</b></td>
<td>Ball Count와 Strike Count에 대한 결과 클래스</td>
</tr>
<tr>
<td><b>&nbsp;&nbsp;&nbsp;&nbsp;↘️&nbsp;&nbsp;/ constants</b></td>
<td><b>ResultType</b></td>
<td>각 결과에 따라 다른 출력 방법에 대해 정의된 Enum</td>
</tr>
<tr><td colspan="3"></td></tr>
<tr>
<td><b>📃&nbsp;&nbsp;global</b></td>
<td><b>GameConfig</b></td>
<td>전역으로 작용하는 설정과 제약조건에 대한 Enum</td>
</tr>
<tr>
<td rowspan="2"><b>&nbsp;&nbsp;&nbsp;&nbsp;↘️&nbsp;&nbsp;/ exception</b></td>
<td><b>BaseballException</b></td>
<td>전역으로 처리하는 예외상황에 대한 Exception 클래스<br/></td>
</tr>
<tr>
<td><b>ErrorMessage</b></td>
<td>각 예외 상황에서 전역으로 던져질 예외의 메세지 Enum</td>
</tr>
<tr><td colspan="3"></td></tr>
<tr>
<td><b>✅&nbsp;&nbsp;validator</b></td>
<td><b>InputValidator</b></td>
<td>사용자가 입력하는 숫자에 대한 제약조건 클래스</td>
</tr>
<tr><td colspan="3"></td></tr>
<tr>
<td rowspan="2"><b>💬&nbsp;&nbsp;view</b></td>
<td><b>InputView</b></td>
<td>사용자 요청을 처리하는 클래스</td>
</tr>
<tr>
<td><b>OutputView</b></td>
<td>사용자에게 응답을 출력하는 클래스</td>
</tr>
<tr>
<td><b>&nbsp;&nbsp;&nbsp;&nbsp;↘️&nbsp;&nbsp;/ constants</b></td>
<td><b>StaticNotice</b></td>
<td>사용자에게 응답할 정적 메세지를 담은 열거형 클래스</td>
</tr>
<tr><td colspan="3"></td></tr>
<tr>
<td colspan="3" align="center"><b>Package Structure Overview</b></td>
</tr>
<tr>
<td colspan="3"><img src="https://github.com/woowacourse-precourse/java-baseball-6/assets/112257466/f37d479a-d211-4c79-93cf-c0a4be1a7443" width="99%"></td>
</tr>

</table>
</div>

---

## ✨&nbsp;&nbsp;기능 구현 목록

✅ `a ~ b` 사이의 서로 값이 다른 `n자리`의 정수를 랜덤으로 생성한다.

▪️ Default Setting : `1 ~ 9`사이의 서로 값이 다른 `3자리`의 정수

✅&nbsp;&nbsp;게임 시작 문구 출력<br/>
✅&nbsp;&nbsp;사용자에게 `a ~ b 사이의 서로 값이 다른 n자리의 정수`를 입력 받는다.

▪️ 입력받은 input이 비어있을 경우 예외처리<br/>
▪️ 입력받은 input이 숫자가 아닌 문자가 포함될 경우 예외처리
▪️ 입력받은 input에 중복된 숫자가 있을 경우 예외처리

✅&nbsp;&nbsp;사용자 input 숫자와 랜덤 생성 정수의 자리수를 비교해 출력할 힌트를 계산한다.

▪️ 숫자의 값은 같지만 자리수가 다른 경우의 수 n개 : `n볼`
▪️ 숫자의 값과 자리수가 모두 같은 경우의 수 m개 : `n스트라이크`

✅&nbsp;&nbsp;계산된 힌트를 아래 양식으로 출력한다

▪️ 볼 n개, 스트라이크 0개가 존재할 때 : `n볼`
▪️ 볼 0개, 스트라이크 n개가 존재할 때 : `n스트라이크`
▪️ 볼 n개, 스트라이크 m개가 존재할 때 : `n볼 m스트라이크`
▪️ 볼 0개, 스트라이크 0개가 존재할 때 : `낫싱`

✅ 게임 클리어 여부 판단

▪️ n스트라이크가 아니라면`, 다시 사용자에게 입력을 숫자를 받고, 힌트를 출력한다.
▪️ n스트라이크를 맞추었다면`, 아래 메세지를 출력하고 사용자에게 플래그를 입력받는다.
▪️ n개의 숫자를 모두 맞히셨습니다! 게임 종료`
▪️ 게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요.`
▪️ 입력받은 input이 1과 2가 아닌 숫자일 경우 예외처리
▪️ 입력값에 따라 게임을 재시작하거나 종료한다.

Comment on lines +90 to +123
Copy link

Choose a reason for hiding this comment

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

기능 명세서가 자세히 되어 있는건 좋은 것 같습니다. 코드랑 비교하면서 보니 mvc 패턴으로 작성하셨던데, 어떤 객체가 위 기능들을 할당하는지도 같이 명시되어 있으면 좋을것 같아요!

예를 들어 계산된 사용자 input 숫자와 랜덤 생성 정수의 자리수를 비교해 출력할 힌트는 Number domain에서 구현하셨던데 이것을 표시할 수 있다면 좋을 것 같습니다.

기능명세서가 구현 전 어떤 기능이 필요할지 생각하고 구현에 도움을 주는 문서인 만큼 최대한 구현과 가깝게 작성하시면 좋을 것 같아요.

Copy link
Author

Choose a reason for hiding this comment

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

이 부분은 다음 과제에서 꼭 반영하도록 하겠습니다!
기능구현서 라고 정말 기능만 단순하게 나열했는데
이 부분을 링크해서 같이 표현한다면 더욱 직관적일 것 같아요!

특히나 도메인별로 분류해서 적었으면 더 좋지 않았을까..하는 아쉬움이 드네요!
다음에는 플로우 차트도 만들어보려고 해요.
더 많이 관심 가져주시면 더 좋은 코드로 뵙겠습니다!!

좋은 리뷰 감사합니다 :)

--------------------------------------------------------

## 🎨&nbsp;&nbsp;구현 간 고민했던 내용들

### 1️⃣&nbsp;&nbsp;&nbsp;확장에는 열려있고, 변경에는 닫혀있는 OCP 설계

- input 숫자의 범위가 변하더라도(1~9), 자리수가 변하더라도 대응이 손쉽게 가능해야 한다.</br>
`GameConfig` 파일에서 `NUMBER_LENGTH` 변수의 value를 변경해 손쉽게 변경이 가능하다.<br/>
개발 요구사항에서 자릿수 요청까지 처리하는 문제였다면, 더욱 OCP를 준수하는 코드 작성이 가능했을 것 같다.

<div align="center">

<table>
<tr>
<th align="center">숫자 3자리</th>
<th align="center">숫자 4자리</th>
<th align="center">숫자 5자리</th>
</tr>
<tr>
<td align="center"><img src="https://github.com/woowacourse-precourse/java-baseball-6/assets/112257466/0b5b10f6-357f-4274-9b42-ea68d18edf85" height="50%"/></td>
<td align="center"><img src="https://github.com/woowacourse-precourse/java-baseball-6/assets/112257466/33d19627-775a-4d56-b2c1-88c186a95336" height="50%"/></td>
<td align="center"><img src="https://github.com/woowacourse-precourse/java-baseball-6/assets/112257466/e41b3fc0-2d6f-4f6c-abe4-f765abcb7aad" height="50%"/></td>
</tr>
</table>


--------------------------------------------------------

### 2️⃣ 4번의 대규모 리팩토링, 그리고 얻어낸 값진 `Number`

- 영감을 얻게 해줬던 한 마디
```bash
객체는 '자율적인 존재'라는 점을 명심하라.
< 중략 >
객체는 스스로의 행동에 의해서만 상태가 변경되는 것을 보장함으로써 객체의 자율성을 보장한다.

- 객체지향의 사실과 오해 中
```

- First-class collection + Static Factory Method 활용
```java
public class Number {
private final List<Integer> numbers;

// Player Input Number Constructor
private Number(String input) {
validateEmpty(input);
validateNumberLength(input);
validateContainOnlyNumber(input);
validateContainDuplicatedNumber(input);

this.numbers = convertInputNumber(input);
}

// Computer Generated Number Constructor
private Number(List<Integer> computerNumber) {
this.numbers = computerNumber;
}
}
```

- 일급 컬렉션과 생성자 검증을 사용해 `numbers`에 유효하게 검증이 끝난 숫자만 들어오도록 설계
- playerNumber와, computerNumber의 생성자는 서로 다른 파라미터를 지니기 때문에, 개발자가 사용 간 혼동 가능<br/>
해당 문제를 해결하기 위해, 생성자를 `private`으로 제한하고, 의미있는 메소드로만 생성자를 호출하도록 설계

```java
// Computer Generated Number Constructor
public static Number generateRandomNumbers() {
List<Integer> randomNumbers = new ArrayList<>();
while (randomNumbers.size() < NUMBER_LENGTH.getValue()) {
int number = pickNumberInRange(RANDOM_NUMBER_MINIMUM.getValue(), RANDOM_NUMBER_MAXIMUM.getValue());
if (!hasDuplicatedNumber(randomNumbers, number)) {
randomNumbers.add(number);
}
}
return new Number(randomNumbers);
}

// Player Number Static Factory Method
public static Number inputPlayerNumbers() {
String playerNumbers = InputView.askPlayerNumbers();
return new Number(playerNumbers);
}
```
- 정적 팩토리 메소드 명에 의미를 부여하고, 개발자가 직관적으로 해석할 수 있도록 했고,<br/>
일급 컬렉션을 활용해 검증이 끝난 유효한 값만 리스트에 담을 수 있게 되었다!
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.

가시성 높은 리드미를 만들고자 노력했는데 좋은 평가를 남겨주셔서 감사합니다!

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.

껄껄 과찬이십니다 :)
좋은 리뷰 감사합니다!

5 changes: 4 additions & 1 deletion src/main/java/baseball/Application.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package baseball;

import baseball.controller.Game;

public class Application {
public static void main(String[] args) {
// TODO: 프로그램 구현
Game game = new Game();
game.start();
Comment on lines +7 to +8
Copy link

@minnim1010 minnim1010 Oct 25, 2023

Choose a reason for hiding this comment

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

입력에 사용한 scanner를 close하는 코드가 없습니다!

Copy link
Author

Choose a reason for hiding this comment

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

앗..Scanner를 사용한다는 점을 망각하고 사용했네요!
이건 정말 찾지 못했던 포인트였어요!!
정말 좋은 지적입니다! 감사합니다 :)

}
}
36 changes: 36 additions & 0 deletions src/main/java/baseball/controller/Game.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package baseball.controller;

import baseball.domain.Number;
import baseball.domain.Result;
import baseball.validator.InputValidator;
import baseball.view.InputView;
import baseball.view.OutputView;

import static baseball.view.OutputView.printStaticNotice;
import static baseball.view.constants.StaticNotice.GAME_START;

public class Game {

Choose a reason for hiding this comment

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

MVC모델을 잘 활용하신 것 같아 배울 점이 있다고 생각합니다.
컨트롤러가 모델과 뷰를 조작했고, Game이라는 클래스가 게임의 전체적인 흐름을 잘 나타냈다고 느꼈어요.

Copy link
Author

Choose a reason for hiding this comment

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

GameController와 Game에 대해서 고민을 많이 했던 것 같습니다!
GameController.start / Game.start 이 상호 클래스 명을 고민했을 때
Game이 더 적절하게 클래스의 이름을 나타냈다고 생각해서 설계하게 되었습니다.

제가 나타내고자 했던 의도가 분명히 전달된 것 같아 뿌듯합니다 :)
좋은 리뷰 감사드립니다!!

Choose a reason for hiding this comment

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

GameController와 Game에 대해서 고민을 많이 했던 것 같습니다! GameController.start / Game.start 이 상호 클래스 명을 고민했을 때 Game이 더 적절하게 클래스의 이름을 나타냈다고 생각해서 설계하게 되었습니다.

제가 나타내고자 했던 의도가 분명히 전달된 것 같아 뿌듯합니다 :) 좋은 리뷰 감사드립니다!!

저도 이부분 많이 고민했었는데 '흐름을 관리하는 역할을 하는 클래스' 를 생각했을 때 Controller 가 직관적으로 떠올라 클래스 이름에 Controller를 포함하여 이름 지었습니다. 이름에 나타내지 않아도 충분히 의미를 전달할 수 있을까? 가 고민이었는데 위에 남겨주신 리뷰가 그 답이 되었습니다!

public void start() {
printStaticNotice(GAME_START);
do {
Number computerNumber = Number.generateRandomNumbers();
play(computerNumber);
Comment on lines +15 to +17
Copy link

Choose a reason for hiding this comment

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

저는 computerNumberGame 클래스에서 미리 private 객체로 생성한 후 이후 따로 값을 할당하는 로직을 실행했는데
이 경우 NPE가 발생한다는 피드백을 받았었습니다!
해빈 님도 이 경우를 고려하셔서 이렇게 짜신 걸까요?

Copy link
Author

Choose a reason for hiding this comment

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

Game은 컨트롤러 클래스로 비즈니스 로직을 갖지 않아야 된다는 기조로 설계했습니다.
지역변수를 갖고, 재할당하면서 while문을 돌게되면, 이는 컨트롤러 자체가 비즈니스 로직을 갖는 설계로 이어지지 않았을까 싶습니다!
그 경우까지 고려하지는 않았지만, NPE에서는 자유로운 설계로 작성된 것 같습니다!

} while (!askRestartOrExit());

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.

오, 이 부분도 제가 체크하지 못했던 부분인데요!!
특히나 지금은 orExit라고 메소드명을 정했기때문에 부정문의 의미가 (Restart도 Exit도 아니라면) 이라고 오해할 수 있겠어요!
메소드명도 오류가 있고, 부정문 사용도 지양해야겠어요!
좋은 지적 감사합니다!

Choose a reason for hiding this comment

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

다른 분께서도 언급하신 내용이라서 반복되는 내용인 것 같지만,,

코드 컨벤션 중에서 긍정 조건을 사용하는 것을 권고하는 것으로 저도 알고 있습니다:)
do - while을 활용한 구조로 코드의 흐름이 명확하게 보이는 점 너무 좋은 것 같아요!

추가로 while()의 인자로 네이밍 측면에서 askRestart 보다는 isRestart를 사용하는 것으로 저는 생각해봤어요! 해빈님의 의견이 궁금합니다

Copy link
Author

Choose a reason for hiding this comment

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

맞아요. 긍정조건에 대한 내용은 이번 기회에 다시 한번 뇌에 강하게 각인! 하게 되었습니다.
메소드 명도 askRestartOrExit 의 부정이면 Not Restart And Not Exit 이라고 인지할 가능성이 높아보여요.
명백하게 잘못 설계된 메소드 명이 맞는 것 같습니다.

isRestart나, ask 로직과 같이 연계해서 설계해야 좋은 설계가 되었을 거라 생각합니다.

}
Copy link

Choose a reason for hiding this comment

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

true 도 상수화를 통해 true의 의미를 더 잘 나타내면 좋을 것 같습니다. 저 문장만 봤을때는 whie 블록이 어떤 코드인지 알기가 어렵네요.

Copy link
Author

Choose a reason for hiding this comment

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

이 부분도 많은 분들이 리뷰주셨던 내용인 것 같습니다!
개인적으로 !askRestartOrExit() 함수 부분의 설계가 조금 아쉬웠다고 생각합니다.

이 부분은 수정해서 더 좋은 코드로 로컬에 저장하고 다시 찾아뵙도록 하겠습니다!!
좋은 리뷰 감사합니다 :)


private void play(Number computerNumber) {
while (true) {
Copy link

Choose a reason for hiding this comment

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

아래 분기의 result.checkGameOver() 를 잘 활용하여서 while(true) 자리의 조건 자리에 넣으시면 더 좋은 코드가 될 수 있을 거 같아요!
true 일 경우 무한 루프라는 것을 인식하지만 그렇게 될 경우 분명히 아래 코드까지 전부다 지켜봐야 어떤 조건문에 따라 종료되는지 알 수 있을 거 같아요!

Copy link
Author

Choose a reason for hiding this comment

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

result라는 객체가 생성되지 않은 시점에서 조건문으로 분기할수 없기에 부득이 이렇게 설계하게 되었습니다 ㅠㅠ
result라는 객체가 생성된 이후, 그 객체가 직접 3스트라이크 여부를 판단해야하는데,
do-while 설계도 결국 Result를 생성하지 않은 상태에서는 동작하지 않더라구요...
그렇다고 지역변수로 선언해서 재참조하면서 사용하는 설계는 좋지 못하다구 생각했구요...!!

이 부분을 어떻게 더 매끄럽게 고칠 수 있을지 연구해보겠습니다!!
정말정말 좋은 리뷰 감사드려요! :)

Number playerNumber = Number.inputPlayerNumbers();
Result result = Result.create(playerNumber, computerNumber);
OutputView.printMessage(result.createResultMessage());
if (result.checkGameOver()) {
break;
Comment on lines +24 to +27
Copy link

@SuranS2 SuranS2 Oct 25, 2023

Choose a reason for hiding this comment

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

P4) MVC모델에 무지하고 대학생 지식이 다여서 도움이 될지는 잘 모르겠습니다.
초심자 레벨에서 본 리뷰라고 생각해주시고 너그럽게 봐주십사 합니다. :)

MVC모델의 개략적인 구조를 보았을때
데이터처리 M 전체 작동 C 외부 커뮤니케이션 V 이렇게 작동하는 것으로 생각됩니다.

입력을 받게되는 V => M 에서
Input => Number 로 처리되기에Number내에서 선언된 inputview로 입력 받고 있네요.

그렇다면 출력은 반대순서인 내부에서 바깥으로 M => V 일 것인데
result.printMessage 형태로 result에 넣어서 같은 사용법으로 만들면 안됐을까? 라는 생각을 해봅니다
소중한 리뷰 및 좋은 코드 감사합니다. 많이 배워갑니다! 🙏

Copy link
Author

Choose a reason for hiding this comment

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

MVC 모델의 연관관계에서 호출 위치에 대한 리뷰를 남겨주신 것으로 이해했습니다!
입력은 View -> Model 방향으로 전달되고,
출력은 Model -> View 방향으로 전달되어 출력해야 정석적인 설계라고 판단됩니다!

OutputView는 출력할 책임만 가진다! 라는 원칙에 입각해서, 설계했던 내용인 것 같아요.
만약 Result가 아니라 출력해야할 객체가 여러개 존재해야 했다면, 모든 객체가 print 메소드를 들고 있어야 하니까요!

그런 의미로 OutputView의 printMessage를 Static Method로 정의해
View의 Print 호출 -> Model의 출력 내용 -> View는 출력 내용 출력
이런 Flow를 그리게 되었는데요.

스크린샷 2023-10-26 오후 11 50 26

해당 그림에서 UserAction에 해당하는 부분은

Input View -> Controller
Controller -> OutputView

이런 방향으로 설계했는데, 역할과 책임의 분리는 적절했다고 생각하지만, 호출 방향은 조금 더 고차원적인 설계의 문제네요!
어떤게 정답인지는 아직 잘 감이 서지 않아요!!

MVC 패턴을 조금 더 깊이 공부해야 할 것 같아요! 저도 MVC는 문외한인 부분이라서....T_T
이 부분에 도움을 주실 수 있는 리뷰어 분이 계신다면 부연 설명 남겨주시면 도움이 많이 될 것 같습니다!

좋은 질문 감사드립니다 :)

}

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.

위에서 줄줄이 읽어도 딱 이해가는 코드로 만들고자 했던 제 노력을 이해해주셔서 너무 감사드립니다:)
좋은 칭찬 감사드립니다!

}
}

private boolean askRestartOrExit() {
String input = InputView.askRestartOrExit();
return InputValidator.isValidRestartFlag(input);
}
}
80 changes: 80 additions & 0 deletions src/main/java/baseball/domain/Number.java
Copy link

Choose a reason for hiding this comment

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

저도 일급컬렉션을 사용해서 numbers를 처리하고 싶었는데
ball과 strike를 계산하는 로직이 떠오르지 않아서 실패했어요..!
해빈 님 코드를 보고 많은 것을 배워갑니다🙏

Copy link
Author

Choose a reason for hiding this comment

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

좋게 봐주셔서 너무 감사합니다!
이번에 작성중인 레이싱카도 일급 컬렉션을 극한으로 활용하기 위해 노력중입니다!
다음 코드도 @kkonii 지켜봐주신다면, 많은 도움이 될 것 같아요!
정성스러운 리뷰 감사드립니다!!

Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package baseball.domain;

import baseball.view.InputView;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;

import static baseball.global.GameConfig.*;
import static baseball.validator.InputValidator.*;
import static camp.nextstep.edu.missionutils.Randoms.pickNumberInRange;

public class Number {

Choose a reason for hiding this comment

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

Number라는 이름은 너무 일반적이고 포괄적인 것 같습니다. 처음에 보고 int나 long, double에 대한 연산을 담은 객체인가 싶었네요. 포함하고 있는 연산들을 보면 숫자야구에서만 사용될 수 있는 것이 대부분이므로 좀 더 이름을 구체적으로 짓는 것이 어떨까요?

Copy link
Author

Choose a reason for hiding this comment

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

위에서 @zxcev , @Gyu-won 님께서 말씀주신 내용과 같은 맥락이라고 생각됩니다.
우리가 숫자야구라는 비즈니스 로직을 아는 상황에서 읽는 코드에서 나름 직관적인 네이밍이라고 생각해서 설계했는데,
최초 설계처럼 두 Number를 분리해서 가져가는게 좋을 것 같아요!

Number 설계 자체를 한 번 바꿔서 다시 설계해봐야겠어요!
추후 설계를 바꾼 뒤에, 다시 한 번 관심 가져주시면, 더 좋은 코드로 보답하겠습니다 :)

성실한 리뷰 감사드려요!!

Choose a reason for hiding this comment

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

Number라는 클래스명은 이미 추상 클래스로 정의되어 있어서 웬만해선 피하시는게 좋다는 생각이 들어요!!
또한 이것을 제외하고도 Number 객체가 현재 너무 많은 책임을 가지고 있다 느껴져서 조금씩 분리해보는 것도 좋다고 생각들어요!
부족한 저의 의견입니다.

Copy link
Author

@h-beeen h-beeen Oct 27, 2023

Choose a reason for hiding this comment

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

Number라는 클래스명은 이미 추상 클래스로 정의되어 있어서 웬만해선 피하시는게 좋다는 생각이 들어요!!

이 부분은 지금 제 설계에서 어떤 말씀인지 제대로 이해하지 못했습니다!
추가 설명 남겨주시면 많은 도움 구할 수 있을 것 같아요.

또한 이것을 제외하고도 Number 객체가 현재 너무 많은 책임을 가지고 있다 느껴져서 조금씩 분리해보는 것도 좋다고 생각들어요!

이 부분을 Number 추상화를 통해 해결할 수 있을 것 같아요! 공통 책임(숫자를 받아서 검증하고 객체화)을 추상화하고, 나머지를 구현체에서 정의하면 더욱 잘 분리된 설계가 되지 않을까 싶습니다!!

감사합니다 :)

private final List<Integer> numbers;

Choose a reason for hiding this comment

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

저도 일급 컬렉션을 사용했는데요, 생성자를 private으로 설정한게 인상적이었습니다!
싱글톤 패턴을 적용하는 방법도 생각해봤는데 헷갈려서 저는 사용 못했거든요ㅠ 고수에게 역시 배울 점이 많은 것 같아요.

Copy link
Author

Choose a reason for hiding this comment

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

사실 제가 작성한 코드가 싱글톤 패턴이라고 확신도 없었습니다...!!
무엇보다, 일급 컬렉션에 대한 불변성(Getter 없는 설계)에 대해서 리뷰어분들이 좋은 평가를 주신 것 같아요.
좋은 리뷰 감사드립니다 :)

Choose a reason for hiding this comment

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

List를 사용하셨는데, Integer를 포장해서 List 이런 타입으로 받는것이 더 좋았을까? 싶습니다. 포장하게 되면 1~9 사이의 숫자에 대한 검증을 해당 클래스에서 처리할 수 있기도 하지만, 어느 것이 좋을지는 저도 확신이 들지 않네요! 설계하기 나름이라고 생각합니다!

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.

@kunsanglee 이렇게 생각할 수도 있군요! 정확히 말씀주신 의도가 Integer 자체를 래핑한 엔티티 자체를 리스트에 넣는 것도 좋을 것 같다는 의견으로 이해했습니다! 저도 설계하기 나름이라고 생각하지만, 검증을 분리하는 것 또한 좋은 설계가 될 수 있을 것 같습니다! 좋은 리뷰 감사합니다 :)

Copy link
Author

Choose a reason for hiding this comment

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

@junest66 하지만 저는 여기서 다시 분기를 고민하고 있습니다!
아래 다른 리뷰어분들이 리뷰 주신 내용으로 Number를 추상 클래스로 추상화하고, Player, Computer를 각각 새로운 Number로 구체화해서 사용하려고 합니다!


// Player Number Constructor
private Number(String input) {
validateEmpty(input);
validateNumberLength(input);
validateContainOnlyNumber(input);
validateContainDuplicatedNumber(input);

this.numbers = convertInputNumber(input);
}

// Computer Number Constructor
private Number(List<Integer> computerNumber) {
this.numbers = computerNumber;
}

// Player Number Static Factory Method
public static Number inputPlayerNumbers() {
Copy link

Choose a reason for hiding this comment

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

정적 팩터리 팩터리 메서드명을 부여했다고 해주셨는데, 정적 팩터리 메서드의 경우 static으로 선언되어 추후 네이밍 컨벤션을 지키지 않으면 혼선이 올 수 있다고 합니다.
image

네이밍 컨벤션과 관련된 내용은 구글링 하시면 확인하실 수 있습니다.

Copy link
Author

Choose a reason for hiding this comment

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

저 궁금한게 CLI 환경이 아니면 왜 모두 수정해야 하나요?

가령 Spring 웹 환경에서는 HttpRequest, HttpResponse를 개행으로 처리하지 않고 실제 \n 이라는 문자열로 처리하게 됩니다.
이러한 부분에서 추후 웹 환경에 이 게임을 돌린다면, 개행을 개행으로 처리하지 못하는 이슈가 발생한다는 의미로 남겨주신 리뷰로 이해했습니다.

Copy link
Author

@h-beeen h-beeen Oct 25, 2023

Choose a reason for hiding this comment

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

저도 똑같은 레퍼런스를 봤던 경험이 있어요.
정적 팩토리 메소드는 장단점이 명확한 설계 방법인 것 같아요.

프로그래머가 찾기 힘들다는 말도 동의하지만, 이 부분은 팀의 네이밍 컨벤션이나, 기타 명세화 방법을 통해 정리할 수 있을 것 같아요!
저는 개인적으로 득보다는 실이 더 크다고 생각해요.
무분별한 new 연산자를 남발하는 것 보다, 의미있는 생성자 메소드를 통해서만 생성이 가능하게 강제하는 것도 매력적이고,
무엇보다 자식 클래스를 상속해서 리턴할 수 있다는 점과, 메소드 명으로 개발자가 의미를 부여 할 수 있다는 점이 가장 강렬하게 다가와요.

장단점이 명백하지만, 저는 장점이 더 명확해 보여요!
@OikimiO 님의 생각이 어떤지 더 남겨주세요 :)

Copy link

@OikimiO OikimiO Oct 25, 2023

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.

좋은 리뷰 감사합니다 :)

Choose a reason for hiding this comment

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

오버로드 사용하신건 엄청 인상 깊네요!

컴퓨터가 랜덤으로 생성하는 수에도 로직이 담겨 있는 점에 대해 이미 다른 분들이 말씀하셨네요..!
DDD이야기만 들었지 적용한 코드를 실제로 보는건 처음인데 많은 생각을 던지게 만들어주시네요!

추가로 저는 필요한 검증 메서드들을 모두 호출해주는 메서드를 생성해서 호출하니까
ex) integrateValidation()
도메인에서 검증로직에 관해 추상화가 가능하더라구요
그래서 그런데 혹시 검증하는 메서드들을 하나씩 직접 호출하신 이유가 있을까요?
의견이 궁금합니다!

Copy link
Author

Choose a reason for hiding this comment

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

integrateValidation() 와 같이 명명하게 된다면, 한 가지 고민해 볼 사항이 있을 것 같아요!
만약 숫자 외에 다른 예외처리가 존재한다면? 협업 과정이라고 가정했을 때, 다양한 케이스에 적절한 예외케이스를 조립하는데 어려움을 겪을 것 같아요.

적정 수준에서 Intergrate 하는 방법은 좋은 방법이라고 생각이 됩니다! 다만 예외처리 가짓수가 늘었을 때의 경우는 명명을 더 직관적으로 설정할 필요가 있을 것 같다는 개인적인 의견입니다!

좋은 질문 감사드립니다 :)

Copy link

Choose a reason for hiding this comment

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

integrateValidation() 와 같이 명명하게 된다면, 한 가지 고민해 볼 사항이 있을 것 같아요! 만약 숫자 외에 다른 예외처리가 존재한다면? 협업 과정이라고 가정했을 때, 다양한 케이스에 적절한 예외케이스를 조립하는데 어려움을 겪을 것 같아요.

저도 @sami355-24 님과 같은 점이 궁금했는데 답변 감사합니다👍 예외 케이스에 대한 로직을 모두 보여주는 것도 코드를 이해하기에 더 쉬울 것 같다는 생각이 드네요

String playerNumbers = InputView.askPlayerNumbers();
Copy link

Choose a reason for hiding this comment

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

전체적으로 MVC 패턴으로 구성하신 것 같은데 이 부분은 조금 거리가 있네요..!
모델에서 뷰에 대한 정보를 알아선 안 된다고 생각합니다!

Copy link
Author

@h-beeen h-beeen Oct 25, 2023

Choose a reason for hiding this comment

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

이런 부분에 대해서 리뷰를 받고 싶었습니다 ㅠ
제 가려운 부분을 정확히 긁어주셨군요..

다른 레이어보다 View 레이어의 입력 책임이 어디까지인가에 대한 부분이 제일 헷갈렸어요.
이 부분 꼭 리뷰 받고 싶었는데 긁어주셔서 감사합니다 :)

Choose a reason for hiding this comment

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

저도 @oxdjww 님의 의견에 공감합니다!
사용자로부터의 입력은 뷰에서 처리하는거라고 생각했어요!

Copy link
Author

Choose a reason for hiding this comment

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

이 부분을 그러면 컨트롤러로 빼야하는 걸까요...
이 부분이 잘못된 설계임은 인지하지만, 어떻게 다듬어야 할 지 잘 감이 오지 않아요. ㅠㅠ

추가적으로 도움 주시면 정말 감사할 것 같아요!

Choose a reason for hiding this comment

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

view에서 입력을 받고 그 정보를 컨트롤러로 넘겨주면 어떨까요?

Copy link

Choose a reason for hiding this comment

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

controller로 뺴는게 맞다고 생각합니다. 코드를 읽어보니 결과를 출력하는 부분은 outputView를 통해서 출력하셨는데 입력은 Number model에서 받고 있어서 view의 역할이 불분명 한 것 같아요.

Controller 코드를 읽을 때도 outputView를 통해 결과를 출력하긴 하는데 inputView를 통해 입력을 받는 부분은 없어서 의아했습니다.

입력 메시지를 출력해주고 사용자의 입력을 받는 부분은 view에서 처리하는게 좋을 것 같습니다.

Copy link
Author

Choose a reason for hiding this comment

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

@Gyu-won @jschoi-96

InputView 객체의 책임과, 호출 위치에 대해서 가려운 부분을 정확히 긁어주신 것 같아요!
어떻게 보면 단순한 내용인데, 조금 복잡하게 생각하다 보니까, 조금 헤멘 감이 있네요!

좋은 리뷰 감사합니다 :)

return new Number(playerNumbers);
}

// Computer Number Static Factory Method
public static Number generateRandomNumbers() {
Copy link

Choose a reason for hiding this comment

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

RandomNumbers 생성 역할을 별개의 클래스로 분리하면 좋을 것 같습니다.

Copy link
Author

Choose a reason for hiding this comment

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

Number를 ComputerNumber와 PlayerNumber로 초반에는 분리해서 설계했었어요!
그런데 이렇게 바꿨던 이유는 getter 없는 설계 때문이었어요.
같은 로직을 공유하는 엔티티에서 DDD 설계로 외부 클래스에서 값 타입을 건드리지 않게 설계하고 싶었어요!
그러다보니...일급 컬렉션을 적용하게 된 하나의 클래스로 탄생했고, 두개의 생성자로 분리해서 구분하게 되었습니다!

현 코드에서 생성의 역할은 정적 팩토리 메소드로 구분할 수 있다고 판단하는데,
클래스 분리가 필요하다고 판단하신 이유를 조금 더 세밀하게 여쭙고 싶습니다!

Copy link

Choose a reason for hiding this comment

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

protected List<Integer>를 가진 abstract class를 생성하고 ComputerNumberPlayerNumber를 생성하면 좀 더 명시적으로 의미 구분이 가능하고, getter 없는 구현도 가능할 것 같습니다.

PlayerNumber는 사용자가 입력한 숫자를 담을 목적이기 때문에 오직 ComputerNumber에만 정답을 비교하는 로직을 넣으면 실수를 줄일 수 있을 것 같네요.

Random 생성 클래스를 분리하게 되면 이후 PlayerNumber를 다른 클래스에서 박싱했을 때 Random 생성 클래스를 인터페이스를 추가하여 쉽게 교체할 수 있어서 테스트에 용이하다고 생각하였습니다.

Copy link

Choose a reason for hiding this comment

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

공감합니다. 지금의 코드에서는 사용자의 입력 인스턴스에도 ball과 strike를 계산하는 메소드도 포함되어 있습니다.
반대로 결과 인스턴스에도 랜덤넘버를 생성할 수 있는 기능이 포함되어 있고요.

getter를 쓰지 않기 위해서 구현하셨지만 객체의 의미가 불명확해 진 것 같습니다. 숫자야구라는 과제를 알고 있는 저의 입장에서도 처음 Number라는 객체를 봤을때 어떤걸 의미하는 건지 알기 어려웠어요.

저도 이부분을 고민하고 있었는데 앞에 분이 추상클래스 라는 좋은 답을 주신 것 같습니다. 다형성을 활용하여 코드를 짜보면 좋을 것 같아요!

Copy link
Author

Choose a reason for hiding this comment

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

@zxcev Number 자체를 추상 클래스로 정의하는 부분은 정말 생각하지 못했던 것 같아요..!
Getter를 사용하지 않는 것은 물론이고 명시적으로 의미 구분이 가능하다 는 부분이 제일 매력적인 것 같아요!
이 부분은 꼭 적용해서 로컬 레포지토리에서 작업해봐야겠어요!!

@Gyu-won 님께서 말씀해주신 내용도, Number라는 객체가 직관적인 네이밍이라기엔 너무 많은 역할과 책임(컴퓨터 숫자 생성, 검증, 유저 숫자 Input, 검증)을 갖고 있다는 의미로 이해했습니다.
이 부분도 추상 클래스를 통해서 도움을 얻을 수 있을 것 같아요!

두 분 께서 성실히 리뷰해주신 만큼, 로컬 레포지토리에서 꼭 작업해서 반영해보겠습니다!! 감사합니다 :-)

List<Integer> randomNumbers = new ArrayList<>();
while (randomNumbers.size() < NUMBER_LENGTH.getValue()) {
int number = pickNumberInRange(RANDOM_NUMBER_MINIMUM.getValue(), RANDOM_NUMBER_MAXIMUM.getValue());
if (!hasDuplicatedNumber(randomNumbers, number)) {
Copy link

Choose a reason for hiding this comment

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

해당 부분은 여기서만 사용하는 것이라면 특정 메소드로 분리하지 않고 contains 를 바로 사용해도 되지 않을가요??

Copy link
Author

Choose a reason for hiding this comment

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

메소드를 단일 호출로 사용함에도 분리했던 이유는, 명명을 통한 직관적인 해석이 가능하도록 만들기 위해서였습니다.
불필요한 코드 낭비라고 생각할 수도 있지만, 저는 개인적으로 위에서 부터 줄줄히 읽었을 때 제대로 읽히는 코드를 선호합니다 :)
그런 의미로, contain()의 의미를 해석하는 것 보다, hasDuplicatedNumber 메소드를 읽었을 때
개발자가 이런 목적으로 작성한 코드이구나! 라는 점을 명백하게 이해할 수 있을 거라고 생각했습니다.

물론 무분별한 분리와 분기는 레거시 코드 양을 늘리고, 자칫 더 복잡한 버그를 낳을 수도 있을거라 생각해요!
@IMWoo94 님의 생각이 더 궁금합니다!

Copy link

Choose a reason for hiding this comment

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

직관적인 해석 한눈에 보았을 때, 파악이 쉽게 되도록 구현하는 것 좋은 코드 입니다.
다만 저의 생각은 hasDuplicatedNumber 라는 메소드를 보았을때, "중복 숫자가 있는지 체크" 라는 의미로 해석이 되지만
메소드로 생성되어 있다보니 혹시나 중복체크 말고 또 다른 기능이 있지 않을까 라는 의심을 가지게 됩니다.
직관적인 메소드명을 가지고 있어도 개발자 입장에서는 곧이곧대로 믿지 않고 한번쯤은 의심을 해보고 직접 찾아가 보아야 한다고 생각합니다.
혹시나 hasDuplicatedNumber 리스트 값을 변경한다면? 값을 날려버린다면? 이러한 생각을 하게 되는 거 같습니다.
따라서 단순 contain 만 사용하는 거라면 if 문에 직접 사용하는게 어떠한가 했습니다.

좋은 코드 잘 보고 많이 배우고 갑니다!

Copy link
Author

Choose a reason for hiding this comment

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

역시 개발자에게 꼼꼼한 검토와 의심은 좋은 습관이라고 저도 확신합니다!
좋은 설계에 정답은 없지만, 정답에 가까운 답을 찾아가는게 개발자에게는 늘 숙제라고 생각됩니다.

같이 좋은 설계에 대해서 고민해주셔서 감사합니다!
저도 많이 배우고 갑니다 :)

randomNumbers.add(number);
}
}
return new Number(randomNumbers);
}

private static boolean hasDuplicatedNumber(List<Integer> randomNumbers, int number) {
return randomNumbers.contains(number);
}

private List<Integer> convertInputNumber(String input) {
return input.chars()
.mapToObj(Character::getNumericValue)
.toList();
}

public int countBallCount(final Number comparableNumber) {
return (int) IntStream.range(0, numbers.size())
.filter(i -> comparableNumber.isBall(numbers.get(i), i))
.count();
}

public int countStrikeCount(final Number comparableNumber) {
return (int) IntStream.range(0, numbers.size())
.filter(i -> comparableNumber.isStrike(numbers.get(i), i))
.count();
}
Comment on lines +59 to +69
Copy link

Choose a reason for hiding this comment

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

countBallCount와 countStrikeCount 이 두 부분을 stream을 써서 처리함으로써 일급 컬렉션의 불변이 지켜지는 것 같아 좋은 것 같습니다👍하지만 메서드 명 부분은 count가 중복으로 들어가서 좀 더 명확하면 좋지 않을까 생각해요!

Copy link
Author

Choose a reason for hiding this comment

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

오..제가 몇번을 코드를 복기하며 읽어봤지만 해당 함수가 읽었을 때 의미가 모호하다는 내용은 지금에서야 깨달았어요!
단순히 finalCount, finalStrike로 정리해도 훨씬 깔끔하겠군요!!

좋은 리뷰 감사드립니다!
즐거운 하루 되세요!

Choose a reason for hiding this comment

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

@noxknow 헉, 엄청 예리하십니다..!!!!
@h-beeen getBallCount, getStrikeCount 요런 이름은 어떨까요~?

Copy link
Author

Choose a reason for hiding this comment

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

getBallCount() 와 같은 getXXX 메소드 명을 의도적으로 회피해서 설계하긴 했습니다.
getter의 의미를 많이 담고 있기 때문에 자칫 Number라는 객체가 멤버변수로 ballCount와 strikeCount를 들고 있다고 오해할 소지가 있기 때문이었습니다.

다만 @noxknow 님께서 말씀 주셨듯

하지만 메서드 명 부분은 count가 중복으로 들어가서 좀 더 명확하면 좋지 않을까 생각해요!

와 같은 이슈가 있는 코드라서

countStrike(), countBall() 정도로 getter의 영역을 벗어나고 조금 더 계산의 의미를 부여한 메소드도 적절할 수 있다고 생각해요!
어떻게 생각하시는지 @wooteco-daram 님의 생각도 궁금합니다!!

Comment on lines +59 to +69

Choose a reason for hiding this comment

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

오.. 같은 클래스끼리면 private 메소드에 접근이 가능하다는 것 처음 알았네요!!!!
어떻게 접근해야하지 고민이었는데, 의외로 간단했네요.
좋은 인사이트를 주셔서 감사합니다!!
나중에 리팩터링할 때 참고하곘습니다. :)

Copy link
Author

Choose a reason for hiding this comment

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

코드에 DDD를 두 방울 정도 떨어뜨린 느낌으로 설계했습니다.
좋게 봐주셔서 감사합니다 :)
도움이 되었다니 뿌듯합니다!


Copy link

Choose a reason for hiding this comment

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

질문이 있습니다. 저는 스트라이크를 세면 자연스럽게 볼을 셀 필요가 없어진다고 생각해서 스트라이크와 볼을 함께 세도록 코드를 작성했었는데, 해빈님은 그것을 countBallCount 메서드와 countStrikeCount 메서드로 나누어 두셨습니다. 이것에 대한 이유가 '메서드 하나에 기능을 하나만 넣기 위해서' 이외에 다른 이유가 있을까요?

Copy link
Author

Choose a reason for hiding this comment

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

스트라이크를 세면 볼을 셀 필요가 없다라고 생각하는 부분이 어떤 말씀인지 잘 이해가 가지 않습니다.
예컨데, 볼 갯수에서 스트라이크 갯수를 빼면 볼 개수가 나오는 로직을 말씀하시는 것이 아닐까 싶습니다.

지금 현재 생성자를 통해서 strikeCount와, ballCount를 계산해 Result 객체를 생성하는 로직이기에,
각각의 계산을 전부 수행하는 것이 맞다고 판단했습니다.

isStrike 함수는 해당 함수가 같은 위치에 있는가를 판별하는 로직으로, 같은 자리가 아니면서 숫자를 포함하는 로직이 필요했기에 분리한 기능입니다.

말씀 듣고 머릿속으로 재구상해보았을때, 저는 스트라이크를 세면 자연스럽게 볼을 셀 필요가 없어진다고 생각해서 스트라이크와 볼을 함께 세도록 코드를 작성 했다는 말이 이해가 잘 가지 않는데, U-Keun님 코드 리뷰를 통해 더 자세히 알아보겠습니다!

Choose a reason for hiding this comment

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

저도 @U-Keun 님처럼 스트라이크를 판단하면 동시에 볼도 같이 판단할 수 있지 않을까라는 생각을 했습니다. 개인적으로 언급하신 "메서드의 세분화"에 더 초점을 둔 것 같아요! 스트라이크와 볼을 각각 구하고 결과값을 출력하는 과정에서 변환하는 방향으로 코드를 작성했던 것 같아요

Copy link

@U-Keun U-Keun Oct 25, 2023

Choose a reason for hiding this comment

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

조금 더 자세히 이야기하면, 어떤 숫자가 스트라이크임이 확인이 되면 그 숫자에 대해서 볼인지 여부를 판단하지 않아도 된다는 의미였습니다. 답변 감사합니다.

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.

이 부분은 아직 확신이 잘 서지 않네요!
설계 방식의 차이인 것 같습니다!

@cosyflower 님, @kunsanglee 께서 말씀주신대로, 저는 메소드 단일책임에 조금 더 입각해서 설계했던 것 같고,
@U-Keun 님께서 말씀주신대로, 최적화된 시스템 설계도 도움이 될 것 같습니다.

모두 좋은 의견을 들을 수 있는 기회였던 것 같습니다!
좋은 리뷰 감사해요!

// Ball : Contain their number at other position
private boolean isBall(int number, int digit) {
return !isStrike(number, digit) && numbers.contains(number);
}

// Strike : Contain their number at same digit
private boolean isStrike(int number, int digit) {
return number == numbers.get(digit);
}
}
Copy link

Choose a reason for hiding this comment

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

Number 라는 도메인이라는 이름에 좀 많은 메소드가 들어 있어서 Number 의 역할이 2개 이상이라고 느낄거 같아요
야구게임 관련 체크 유틸을 만들어서 ball, strike 를 판별 할 수 있는 네이밍을 가진 클래스가 있다면 Number는 Number 역할만 할 수 있지 않을가요?

Copy link
Author

Choose a reason for hiding this comment

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

맞습니다..! 사실 Number <-> Result 관계에 대해서 너무 고민을 많이 했던 것 같아요.
나름 DDD 구조를 모방한 패턴으로 적용해서, getter() 없는 설계를 하는데 너무 초점을 맞췄던 것 같기도 하구요!

일급 컬렉션에 담은 값들을, 외부에서 값 타입으로 꺼내서 사용하는 순간 그 의미가 퇴색된다고 보았기 때문에
해당 내용을 전부 담은 Number라는 객체가 생각보다 많은 의미와 책임을 담고 있었다고 생각이 드네요.

많은 분들이 리뷰해주셨던 내용 중 가장 기억에 남는 내용은,
Number를 추상클래스로 바꾸고, Number를 상속하는 ComputerNumber, PlayerNumber와 같은 방식으로 분기해서
작동하도록 설계한다면, 객체지향적 설계 + 일급 컬렉션의 값타입 참조하지 않기(getter 사용 안하기) + 책임 분리
모든 토끼를 잘 잡을 수 있을 것 같아요!

좋은 리뷰 감사드립니다 :)

Copy link

Choose a reason for hiding this comment

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

상속도 좋은 방식인거 같습니다.
각각의 역할을 확실하게 나누어서 자식의 기능은 별도로 구성한다.
상속도 좋지만 이러한 경우에는 인터페이스도 생각해 볼만도 한거같습니다.
클래스와 클래스간의 상속은 하나만 상속할 수 있다는 불편한 제약이 있어서 인터페이스도 고려할만 한거 같습니다.

Choose a reason for hiding this comment

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

저는 number, numbers 표기보다 구체적으로 네이밍을 하는게 로직을 파악하기 쉬울 것 같다는 생각이 들어요!
쭉 읽다보면 numbers가 뭐지? 하면서 다시 올려다보고 하는 경우가 발생할 수도 있다는 생각이 들어서 남겨봅니다:)

Loading