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

Open
wants to merge 29 commits into
base: main
Choose a base branch
from
Open

[조윤호] 야구 게임 미션 #2

wants to merge 29 commits into from

Conversation

ray-yhc
Copy link

@ray-yhc ray-yhc commented Mar 25, 2023

주관적인 의견이라도 편하게 리뷰 남겨주시면 감사하겠습니다!
잘 부탁드립니다 :)

Copy link
Member

@seong-wooo seong-wooo left a comment

Choose a reason for hiding this comment

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

고생하셨습니다!

전반적으로 느낀 바를 얘기해보자면,
핵심 로직이 여러군데 분산되어있다는 느낌이 들었어요.

객체 지향 = 데이터를 관리하는 곳에서만 데이터를 이용함
저는 객체 지향을 이렇게 생각하는데, 그게 잘 안지켜지고있다는 느낌이 듭니다~

Comment on lines 5 to 17
public static boolean isThreeDigit (String test) {
String pattern = "\\d{3}";
return test.matches(pattern);
}
public static boolean isBaseball (String test) {
String pattern = "^(?!.*(.).*\\1)\\d{3}$";
return test.matches(pattern);
}

public static boolean isCommand (String test) {
String pattern = "[12]";
return test.matches(pattern);
}
Copy link
Member

Choose a reason for hiding this comment

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

전체적으로 String.matches를 사용하고 계신데요!
matches 내부로 들어가면 Pattern 을 사용하는 것을 알 수 있습니다.

입력이 들어올때마다 Pattern 객체를 생성하고 버리는 과정이 진행되는데요
이 부분을 캐싱하면 어떨까요?

자세한 내용은 이펙티브 자바 아이템 6에서 확인하실 수 있습니다~

}

public GameStatus singleTurn(List<Integer> player){
int strike = 0, ball = 0;
Copy link
Member

Choose a reason for hiding this comment

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

한 줄에 두 개의 변수를 선언하면,, 가독성이 떨어지는 것 같습니다!

Comment on lines 5 to 17
public static boolean isThreeDigit (String test) {
String pattern = "\\d{3}";
return test.matches(pattern);
}
public static boolean isBaseball (String test) {
String pattern = "^(?!.*(.).*\\1)\\d{3}$";
return test.matches(pattern);
}

public static boolean isCommand (String test) {
String pattern = "[12]";
return test.matches(pattern);
}
Copy link
Member

Choose a reason for hiding this comment

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

추가로 만약에 야구 게임의 숫자가 4자리로 늘어난다면?
이 클래스 뿐만 아니라 다른 클래스도 변경해야하는데,
이런식으로 변경해야할 부분이 여러 군데라면 변경하기 굉장히 힘들겠죠..?

Comment on lines 11 to 17
public SingleGame() {
while(computer.size() < 3) {
int randomNumber = Randoms.pickNumberInRange(1, 9);
if (!computer.contains(randomNumber))
computer.add(randomNumber);
}
}
Copy link
Member

Choose a reason for hiding this comment

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

이런 방법으로 초기 숫자를 생성할 예정이라면
LinkedHashSet 자료구조를 썻어도 좋았을 것 같네요.
순서를 보장하면서 중복이 없도록 만들어줘서 좋습니다 :)

Comment on lines 26 to 32
List<List<Integer>> inputs = new ArrayList<>(List.of(
List.of(4, 5, 6), // n
List.of(4, 5, 1), // 1b
List.of(1, 5, 6), // 1s
List.of(1, 5, 2), // 1b1s
List.of(1, 2, 3) // 3s
));
Copy link
Member

Choose a reason for hiding this comment

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

사용자가 0 을 입력해도 정상적으로 돌아가네요
예를 들어 012 같은거 ?

Copy link

@jeongyuneo jeongyuneo left a comment

Choose a reason for hiding this comment

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

전체적으로 네이밍의 어색함이 많이 느껴진 것 같습니다.
특히 메소드명이 명사로 시작하는 경우가 종종 보여 더욱 그렇게 느껴졌습니다!
코멘트는 제 주관적인 의견을 담았으니, 생각이 다르시면 코멘트 남겨주세용😄


startGame();
while (isContinue)
isContinue = playSingleGame();

Choose a reason for hiding this comment

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

playSingleGame()이 boolean을 반환하는게 좀 어색하다고 느껴집니다!

Comment on lines 11 to 12
private static UserInput userInput;
private static UserOutput userOutput;

Choose a reason for hiding this comment

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

전역변수로 선언하는 것보다 파라미터로 넘겨주는게 더 좋지 않을까요?

@@ -0,0 +1,18 @@
package baseball.inout;

public class CheckRegex {

Choose a reason for hiding this comment

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

해당 클래스는 유틸성 클래스같은데, 이런 경우 기본 생성자를 private로 막아주어 외부에서 객체 생성을 하지 못하도록 막아주어야 합니다!

Comment on lines 19 to 21
public void setComputer (List<Integer> list) {
computer = list;
}

Choose a reason for hiding this comment

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

테스트를 위한 메소드 작성은 좋지 않은 것 같습니다.
그리고 setter 사용보다는 생성자를 이용해서 생성해주면 좋을 것 같아요!

Comment on lines 26 to 27
if (computer.get(i) == player.get(i)) strike ++;
else if (computer.contains(player.get(i))) ball ++;

Choose a reason for hiding this comment

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

단항연산자 앞에 공백은 삭제해주시면 가독성이 더 좋아질 것 같습니다!

Suggested change
if (computer.get(i) == player.get(i)) strike ++;
else if (computer.contains(player.get(i))) ball ++;
if (computer.get(i) == player.get(i)) strike++;
else if (computer.contains(player.get(i))) ball++;

Comment on lines 7 to 23
public void initMessage() {
System.out.println("숫자 야구 게임을 시작합니다.");
}

public void statusMessage(GameStatus status) {
if (status.ball == 0 && status.strike == 0)
System.out.println("낫싱");
else if (status.ball == 0)
System.out.println(status.strike + "스트라이크");
else if (status.strike == 0)
System.out.println(status.ball + "볼");
else
System.out.println(status.ball + "볼 " + status.strike + "스트라이크");

}

public void endMessage() {

Choose a reason for hiding this comment

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

메소드 네이밍이 어색한 것 같습니다.
초기 메시지를 출력하다., 상태 메시지를 출력하다., 종료 메시지를 출력하다.가 메소드의 행위를 더 명확하게 나타낼 수 있을 것 같습니다!

// then
assertThat(status).isEqualTo(expects.get(i));
}
}

Choose a reason for hiding this comment

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

클래스 마지막에 빈 줄 삽입은 컨벤션입니다!

Comment on lines 18 to 20
if (!CheckRegex.isThreeDigit(input))
throw new IllegalArgumentException("3자리의 숫자를 입력해야 합니다.");
if (!CheckRegex.isBaseball(input))

Choose a reason for hiding this comment

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

검증 로직이 모두 '어떤 조건을 만족한가'와 '그렇지 않은가' 즉, !(not)을 사용해서 이루어지는데, 저는 이런 방식보다는 조건문에 '어떤 조건을 만족하지 않은가'를 넣는게 코드가 의미하는 바를 더 명확하게 나타낼 것 같은데 어떻게 생각하시나용

List<Integer> playNum = userInput.getNum();
GameStatus gameStatus = singleGame.singleTurn(playNum);
userOutput.statusMessage(gameStatus);
isCorrect = gameStatus.isCorrect();

Choose a reason for hiding this comment

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

게임상태가 올바르면 반복한다는게 어떤 의미일까요?

Comment on lines 32 to 39
boolean isCorrect = false;

while (!isCorrect) {
List<Integer> playNum = userInput.getNum();
GameStatus gameStatus = singleGame.singleTurn(playNum);
userOutput.statusMessage(gameStatus);
isCorrect = gameStatus.isCorrect();
}

Choose a reason for hiding this comment

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

반복문을 실행하기 위해 boolean 변수를 선언한 후 반복문에서 상태를 바꾸게 되면 처음에는 게임 상태가 올바르지 않아서(종료가 아니어서) 시작하게 되는데, 이런 동작이 어색하다고 느껴집니다.
게임을 실행하면 해당 과정을 반복하는데 게임 상태가 올바르면(종료이면) 반복을 종료한다. 이런 로직이 좀 더 자연스럽지 않을까요??

Copy link

@jeongyuneo jeongyuneo left a comment

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 +9
import baseball.game.model.GameCommand;
import baseball.game.model.GameStatus;
import baseball.game.GameService;
import baseball.inout.UserInput;
import baseball.inout.UserOutput;

import java.util.List;

Choose a reason for hiding this comment

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

안쓰는 임포트는 지워주세요!
인텔리제이를 사용하신다면, 맥 기준 ctrl+option+o(영어 오) / 윈도우 기준 ctrl+alt+o 임포트 정리 단축키를 사용해서 간편하게 정리할 수 있습니다ㅎㅎ

Copy link
Author

Choose a reason for hiding this comment

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

오오 꿀팁이네요


public class GameService {

GameBall computer;

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.

앗 놓쳤네요! 꼼꼼하게 봐주셔서 감사합니다~

throw new IllegalArgumentException("숫자의 범위가 올바르지 않습니다.");

List<Integer> ball = new ArrayList<>();
for (int i = 0; i < 3; i++)

Choose a reason for hiding this comment

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

Suggested change
for (int i = 0; i < 3; i++)
for (int i = 0; i < BALL_NUM; i++)

Comment on lines +20 to +23
if (isInvalidLength(userInput))
throw new IllegalArgumentException("3자리의 숫자를 입력해야 합니다.");
if (isInvalidRange(userInput))
throw new IllegalArgumentException("숫자의 범위가 올바르지 않습니다.");

Choose a reason for hiding this comment

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

볼을 만드는 메소드에서 검증 책임을 같이 수행하고 있네요. 검증하는 로직을 메소드로 분리하면 좋을 것 같아요!

Comment on lines +16 to +21
int commandNumber;
try {
commandNumber = Integer.parseInt(userInput);
} catch (NumberFormatException ex) {
throw new IllegalArgumentException("숫자를 입력해야 합니다.");
}

Choose a reason for hiding this comment

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

파싱하는 로직도 분리하면 좋을 것 같아요!

/**
* 게임 진행여부 입력
*/
public static String isContinue() {

Choose a reason for hiding this comment

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

메소드 네이밍이 어색합니다. isContinue()(계속되는가)라고 물어보면 예/아니오(boolean값)이 반환될 것이 예상되는데 String이 반환되네요.

Comment on lines +15 to +22
if (status.ball == 0 && status.strike == 0)
System.out.println("낫싱");
else if (status.ball == 0)
System.out.println(status.strike + "스트라이크");
else if (status.strike == 0)
System.out.println(status.ball + "볼");
else
System.out.println(status.ball + "볼 " + status.strike + "스트라이크");

Choose a reason for hiding this comment

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

view에서 출력 외 로직이 동작하는 것이 어색합니다!

Copy link
Author

Choose a reason for hiding this comment

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

그렇군요..!! GameStatus로 옮기는 것을 고려해봐야겠네요
view에서는 단순히 문자열을 받아서 출력하는 것이 좋은가요?

Choose a reason for hiding this comment

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

저는 입출력 클래스는 메인 서비스 로직의 영향을 받지 않아야된다고 생각합니다.
그런데 지금처럼 출력이 아닌 로직을 포함할 경우, 볼과 스트라이크를 구하는 방식이 변경된다면 '게임 결과'가 변경되는 것인데 '게임 결과'가 아닌 '출력'의 로직을 수정해야 합니다. 이는 굉장히 어색한 책임 같습니다.
'출력'을 위한 객체가 출력이 아닌 책임까지 갖게 되면 책임이 모호해져서, 입출력에는 최대한 로직을 넣지 않는 것을 선호하는 편입니다!

Comment on lines +14 to +20
@Test
void getCommand() {
}

@Test
void isContinue() {
}

Choose a reason for hiding this comment

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

사용하지 않는 메소드는 삭제해주세요!

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.

3 participants