[우아한 테크 코스 프리코스 5기] 2주차

seheo·2022년 11월 9일
0

우테코

목록 보기
2/3
post-thumbnail

🚀 기능 요구 사항

기본적으로 1부터 9까지 서로 다른 수로 이루어진 3자리의 수를 맞추는 게임이다.

  • 같은 수가 같은 자리에 있으면 스트라이크, 다른 자리에 있으면 볼, 같은 수가 전혀 없으면 낫싱이란 힌트를 얻고, 그 힌트를 이용해서 먼저 상대방(컴퓨터)의 수를 맞추면 승리한다.
    • 예) 상대방(컴퓨터)의 수가 425일 때
      • 123을 제시한 경우 : 1스트라이크
      • 456을 제시한 경우 : 1볼 1스트라이크
      • 789를 제시한 경우 : 낫싱
  • 위 숫자 야구 게임에서 상대방의 역할을 컴퓨터가 한다. 컴퓨터는 1에서 9까지 서로 다른 임의의 수 3개를 선택한다. 게임 플레이어는 컴퓨터가 생각하고 있는 서로 다른 3개의 숫자를 입력하고, 컴퓨터는 입력한 숫자에 대한
    결과를 출력한다.
  • 이 같은 과정을 반복해 컴퓨터가 선택한 3개의 숫자를 모두 맞히면 게임이 종료된다.
  • 게임을 종료한 후 게임을 다시 시작하거나 완전히 종료할 수 있다.
  • 사용자가 잘못된 값을 입력할 경우 IllegalArgumentException을 발생시킨 후 애플리케이션은 종료되어야 한다.

추가된 요구 사항

  • indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다.
    • 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다.
    • 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메서드)를 분리하면 된다.
  • 3항 연산자를 쓰지 않는다.
  • 함수(또는 메서드)가 한 가지 일만 하도록 최대한 작게 만들어라.
  • JUnit 5와 AssertJ를 이용하여 본인이 정리한 기능 목록이 정상 동작함을 테스트 코드로 확인한다.
    • 테스트 도구 사용법이 익숙하지 않다면 test/java/study를 참고하여 학습한 후 테스트를 구현한다.

✏️ 과제 진행 요구 사항

  • 미션은 java-baseball 저장소를 Fork & Clone해 시작한다.
  • 기능을 구현하기 전 docs/README.md에 구현할 기능 목록을 정리해 추가한다.
  • Git의 커밋 단위는 앞 단계에서 docs/README.md에 정리한 기능 목록 단위로 추가한다.
  • 과제 진행 및 제출 방법은 프리코스 과제 제출 문서를 참고한다.

기능분석

기능 목록

[번호]. [기능] - [클래스명]

1. 입력값 검증 - Exception

  1. 숫자인가 (양수)?
  2. 3자리 숫자인가?
  3. 서로 다른 숫자로 이루어져있나?

2. 컴퓨터 랜덤값 생성 - Computer

3. 사용자 랜덤값 입력받기 - Console

4. 야구 게임 구현 - BaseballUmpire, BaseballGame, Console, Restart

  1. 컴퓨터의 숫자와 입력 숫자 비교 (공 던지기, 볼 판독기)
    • 스트라이크 - 위치, 숫자 모두 일치
    • 볼 - 숫자 일치, 위치 불일치
    • 낫싱 - 모두 불일치
  2. 게임 결과 출력 (힌트 출력 기능)
    • 3스트라이크 게임 종료
    • 3스트라이크가 아니면 재입력
  3. 재시작 기능 (재시작 여부 확인)
    • 입력값 검증 1 or 2
    • 1입력 시 재시작
    • 2입력 시 종료

After

   public void gameStart() {
        Computer computerBalls = new Computer();
        this.strike = 0;
        this.ball = 0;
        System.out.print("숫자를 입력하세요 : ");
        String userBalls = Console.readLine();
        Exception.Check(userBalls);

        while (throwBall(userBalls, computerBalls.getComputerBalls())) {
            this.strike = 0;
            this.ball = 0;
            System.out.print("숫자를 입력하세요 : ");
            userBalls = Console.readLine();
            Exception.Check(userBalls);
        }
    }

애매한 부분

반복문 안에서만 사용되는 지역변수

클린코드 5장: 형식 맞추기 - 수직 거리

서로 밀접한 개념은 같은 파일에 속하는 것이 좋습니다.
무엇을 하는지 이해하기 위해 이 조각 저 조각이 어디에 있는지 찾고 기억해야 한다.
함수나 변수가 정의된 코드를 찾으러 상속 관계를 거슬러 올라간 경험이 있다면 공감할 것이다.
같은 파일에 속할 정도로 밀접한 두 개념은 세로 거리로 연관성을 표현합니다.
연관성이 깊은 두 개념이 멀리 떨어져 있으면 코드를 읽는 사람이 여기저기 뒤져야 한다.

리팩토링 전

	private void compareBalls(int userBall, ArrayList<Integer> computerBalls, int ballCount)
        int computerBall;
		boolean isStrike;
        boolean isBall;

		for (int computerBallCount = 0; computerBallCount < computerBalls.size();
			computerBallCount++) {
			computerBall = computerBalls.get(computerBallCount);
			isStrike = (ballCount == computerBallCount);
            isBall = (userBall == computerBall);
			refereeBall(userBall, computerBall, isStrike);
		}
        
	}

수정

	private void compareBalls(int userBall, ArrayList<Integer> computerBalls, int ballCount)
        int computerBall;
		 isStrike;

		for (int computerBallCount = 0; computerBallCount < computerBalls.size();
			computerBallCount++) {
			int computerBall = computerBalls.get(computerBallCount);
			boolean isStrike = (ballCount == computerBallCount);
			boolean isBall = (userBall == computerBall);
			refereeBall(isBall, isStrike);
		}
        
	}

사실 loop문 안에 있든, 메서드 지역변수로 있던 "무엇을 하는지 이해하기 위해 이 조각 저 조각이 어디에 있는지 찾고 기억해야 한다. 함수나 변수가 정의된 코드를 찾으러 상속 관계를 거슬러 올라간 경험이 있다면 공감할 것이다." 이런 일이 일어날것 같지는 않지만 수정했다.

private method TestCase

TestCase 작성 시

구현 전에 어떻게 돌아가는지 기능에 대해서 정의만하고 TestCase를 만들다 보니 Access Modifier를 고려하지 않았다.
그러다 보니 막상 구현했을때 필요없는 getter를 만들어야하고, 나중에 기능을 완성하고 Access Modifier를 public에서 private로 수정하고 나니 사용 할 수 없는 테스트가 되었다.

이 경우에 어떻게 처리를 해야되는지 찾아보았다.
[JUnit] private 메서드, 변수 테스트 방법, Unit Test Private Methods in Java

private method Test

  1. 변수나 함수를 접근가능한 함수를 만들어서 테스트
  2. java.lang.reflect 라이브러리 사용

느낀점

저번 주 단체 피드백 부분 중 잘 못지켰다고 생각하는 의미 있는 커밋 메시지 작성, , git을 통한 자원 관리, space와 tab 혼용을 중점적으로 체크했고 이 부분은 이번에는 잘 지킨거 같다.

"메서드에 오직 한 단계의 들여쓰기(indent)만 허용했는가?" 이 부분이 이번에는 반드시 지켜야 할 추가된 요구사항으로 나왔지만 저번 주 부터 지킬려고 애쓴덕인지 이번에는 그렇게까지 이 요구사항을 준수하는게 어렵지는 않았다.
처음 작성한 코드들은 들여쓰가가 2 단계인 소스들도 있었지만, 정말 작은 단위의 기능으로 나누니 금방 들여쓰기를 없앨 수 있었다.

우아한테크코스 클린코드 원칙에 있는 부분들이 결국 앞으로 요구사항으로 추가될 것 같아 이 부분에 있는 것들을 다 지켜며
"메소드의 인자 수를 제한했는가?" 최대 3개 (3개도 가능한 줄이는게 좋다)
1개의 method 빼고 전부 이러한 점을 준수하고 있는데, 매개변수가 3개인 compareBalls method인데, 매개변수를 줄이기 위해 새로운 클래스를 만들려다가, 매개변수의 특징을 공통적으로 가지는 네이밍을 못하겠어서, 그대로 사용하는게 가독성 괜찮은 것 같아 이 method는 매개변수가 3개인 상태로 두기로 했다.

compareBalls 매개변수 3개

private void compareBalls(int userBall, ArrayList<Integer> computerBalls, int ballCount) {
		for (int computerBallCount = 0; computerBallCount < computerBalls.size();
			computerBallCount++) {
			int computerBall = computerBalls.get(computerBallCount);
			boolean isStrike = (ballCount == computerBallCount);
			boolean isBall = (userBall == computerBall);
			refereeBall(isBall, isStrike);
		}
	}

compareBalls 매개변수 2개

private void compareBalls(UserComBall userComball, int ballCount) {
		for (int computerBallCount = 0; computerBallCount < computerBalls.size();
			computerBallCount++) {
			int computerBall = userComBall.getComputerBalls(computerBallCount);
            int userBall = userComBall.getUserBalls(ballCount);
			boolean isStrike = (ballCount == computerBallCount);
			boolean isBall = (userBall == computerBall);
			refereeBall(isBall, isStrike);
		}
	}

"콜렉션에 대해 일급 콜렉션을 적용했는가?" 일급 콜렉션이라는 말을 들어본적이 없어 공부했는다.

Collection을 Wrapping하면서, 그 외 다른 멤버 변수가 없는 상태를 일급 컬렉션이라 합니다.
Wrapping 함으로써 다음과 같은 이점을 가지게 됩니다.
1. 비지니스에 종속적인 자료구조
2. Collection의 불변성을 보장
3. 상태와 행위를 한 곳에서 관리
4. 이름이 있는 컬렉션

나의 코드 중에 Computer가 일급 콜렉션을 통해 구현했다.
내가 이해한 일급 콜렉션 처럼 Collection 멤버 변수 하나만을 Wrapping하고, 중복이 없도록 생성되록 상태와 행위를 한 곳에서 관리한다.

public class Computer {

	private ArrayList<Integer> computerBalls;

	public Computer() {
		this.computerBalls = new ArrayList<>();

		while (computerBalls.size() < BALL_MAX_SIZE) {
			int randomNumber = Randoms.pickNumberInRange(1, 9);
			duplicationCheck(randomNumber);
		}
	}

	public ArrayList<Integer> getComputerBalls() {
		return computerBalls;
	}

	private void duplicationCheck(int randomNumber) {
		if (!this.computerBalls.contains(randomNumber)) {
			this.computerBalls.add(randomNumber);
		}
	}

}

네이밍과 클래스 분리를 많이 고민했는데, 다른 사람들도 읽기 편한 코드 인지 많이 궁금해서 바로 피어리뷰를 받아보고 싶다.

2개의 댓글

comment-user-thumbnail
2022년 11월 10일

잘보고갑니다!
저도 2주차하면서 클린코드 5장의 내용이 도움이 많이됐는데 공감되네요 ㅎㅎ😊

1개의 답글