이전 게시물은 [개발생각] 5기 1주차 온보딩 후기 를 보시면 됩니다!!
https://github.com/JangAJang/WoowaCourse_Preview/tree/main/1주차_숫자%20야구%20게임
1주차 한지 얼마나 됐다고 벌써 2주차까지 왔다...
간단하게 저번 주차와 비교해서 이번주차에 할 말은 생각 이상으로 많은 것을 공부하며 계획과 코딩을 해야하는 것이다.
사실 이번 우아한 테크코스를 하기 전 까진 테스트코드도 써본적이 없었다.
항상 만드는 것은 쇼핑몰, 지하철 노선 추천 프로젝트, 인스타그램같은 결국은 CRUD만을 다루는 프로젝트들이었기 때문이다.
어떤 결과인지 알고, 그걸 테스트하면서 그냥 넘어갔던게 대부분이었다.
이번 우아한테크코스에서는 그런 습관들을 많이 버리고 싶어서, TDD에 대한 공부를 조금 했었다.
사실 공부라고 하기도 조금 뭐하다, 나만의 정의를 내렸다.
- 기능 목록을 최대한 자세하게 쓴다 : 메서드가 다른 메서드를 불러와 사용해야 할 것 같은 경우, 그 메서드도 기능목록에 다 적었다.
- 기능 목록의 모든 메서드의 테스트코드를 작성한다 : 지금 보면 사실 잘 한 행동은 아니지만, 테스트를 어떻게 다루어야 하는지 배우긴 했다.
- MVC 패턴을 조금 더 잘 생각하고, 거기에 맞게 데이터를 옮기자 : 객체 자체를 옮기면 아무래도 메모리를 많이 쓸 거 같아 최대한 단순한 자료 구조 형태로 이동시키려고 노력했다.
이런 생각을 가지고 기획부터 하기 시작했다. 사실 코딩보다 기능목록을 마음에 들 때 까지 작성하는데 대략 3~4일 걸린 것 같다.
이걸 기행(?) 이라고 부를 수도 있지만, 기능 목록을 최대한 확실하게, 그리고 필요한 것들만 최대한 집어낼 수 있게 쓰려고 그런 것 같다.
프로젝트를 수요일에 받았는데, 실질적으로 코드를 쓴 것은 토요일 부터였다.
기능 목록을 이렇게 쓴 이유는 계속해서 머리속에 든 생각 때문이다.
"기능 목록 하나만 보고 이 프로젝트가 무슨 일을 하는지
머리속으로 바로 그릴 수 있을 정도로 체계화 시켜야한다."
내가 다시 봐도 기능 목록이 마음에 들지 않는 순간들이 많았다.
그럴 때 마다 컴퓨터 메모장이나 종이에 어떻게 기능을 만들어 쓰고, 사용해야 하는 변수들을 어떻게 할당받을지를 계속 고민했다.
그렇게 머리를 식히고 다시 보니 이게 무슨 문제가 있는지, 어떻게 데이터를 써먹어야 더 간편할지를 생각해낼 수 있었다.
# 기능목록
## 클래스별 구현목록
## 모델
### Player
1. getNumbers() : 정수 리스트를 리턴한다.
2. setNumbers(String input) : 문자열을 입력받아 리스트로 모델에 저장한다.
### Computer
컴퓨터에 저장된 수가 3개가 될 때 까지 반복한다.
1. insertNumber() : 랜덤값 1~9가 이미 있지 않으면 저장시킨다.
1. createRandomNumber : 랜덤값 1~9 출력 함수
2. isAlreadyInNumbers : 이미 있으면 true 리턴 함수
3. putNumber : 값을 저장시키는 함수
2. makeThreeDigitNumber() : 1~9의 세자리 랜덤 값을 생성한다.
1. insertNumber() 3번 반복
3. getNumbers() : 정수형 리스트를 반환한다.
## 예외처리
### PlayerException
checkException(String input) : 입력한 문자열로 에러가 발생하는지 체크한다.
1. isNotNumber(String input) : 입력한 문자열에 숫자가 아닌게 있을 떄 참을 반환한다.
2. isNotThreeDigit(String input) : 문자열의 길이가 3이 아니면 참을 반환한다.
3. containsZero(String input) : 문자열에 0이 있으면 참을 반환한다.
4. containsSameNumber(String input) : 문자열에 같은 숫자가 존재하면 참을 반환한다.
5. wrongNumberException() : "입력은 1부터 9중 서로 다른 3개의 숫자여야 합니다."로 예외처리한다.
## 뷰
### PlayerView
1. requestInput() : 입력 요청문을 출력한다.
2. readInput() : 입력문을 받아 문자열로 저장한다.
3. PlayerException을 통해 예외처리한다.
4. getInput() : 저장 문자열을 반환한다.
### ScoreResultView
전역변수 ballCount, strikeCount를 가진다.
1. setBallCount(int ballCount) : 볼을 개수만큼 입력받아 전역변수를 초기화한다. .
2. setStrikeCount(int strikeCount) : 스트라이크를 개수만큼 입력받아 전역변수를 초기화한다.
3. getResultOfScores(String result) : 점수들에 따른 결과를 문자열로 출력해준다.
4. makeResultString() : 스트라이크와 볼로, 문자열을 반환한다.
1. isNothing() : strikeCount == 0 && ballCount == 0일 때 참을 반환한다.
2. printNothing() : “낫싱”을 반환한다.
3. isOnlyBall() : strikeCount == 0 && ballCount != 0일때 참을 반환한다.
4. printOnlyBall() : “a 볼”을 반환한다.
5. isOnlyStrike() : strikeCount != 0 && ballCount == 0일때 참을 반환한다.
6. printOnlyStrike() : “b 스트라이크”를 반환한다.
7. printBothBallAndStrike() : “a볼 b스트라이크” 를 반환한다.
### GameStatusView
1. askOpnion() : 게임 재실행에 대한 요청문을 출력한다.
2. readOpinion() : 콘솔 입력을 문자열로 저장한다.
3. isNotCorrectAnswer() : "1", "2"가 아니면 참을 반환
4. alertWrongAnswer() : 잘못된 문자열임을 출력한다.
5. getOpinion() : 저장한 문자열을 리턴한다.
## 서비스
### GameService
initializeScore() : 전역변수 strikeCount, ballCount = 0으로 초기화한다.
countScore() : 플레이어의 숫자들과 컴퓨터의 숫자들을 비교해 점수를 만든다.
1. checkComputerWithPlayerNumber() : 반복문으로 0부터 2까지 인덱스를 이용해 컴퓨터와 사람을 비교한다.
2. scoreGameWithIndex(int index) : 인덱스를 이용해 플레이어와 컴퓨터를 비교한다.
3. isStrike(int index) : 플레이어 인덱스의 숫자와 컴퓨터 인덱스 숫자가 같다면 참을 반환
4. isBall(int index) : 플레이어 인덱스의 숫자가 컴퓨터 인덱스의 숫자와는 다르지만 컴퓨터가 해당 값을 가지고 있다면 참을 반환
5. increaseStrike() : 전역변수 strikeCount를 1증가 시켜준다.
6. increaseBall() : 전역변수 ballCount를 1증가시킨다.
7. setPlayer(String input) : 뷰에서 받은 입력을 컨트롤러를 통해 받아 플레이어 객체에 저장시킨다.
8. setComputer() : 컴퓨터 객체를 새로 만들고, makeThreeDigitNumbers()해준다.
### GameStatusService
전역 변수 gameStart
1. 생성자 : gameStatus = true로 초기화
2. setGameStatus(String opinion)
1. isEndOfGame(String opinion) : 문자열이 2이면 참을 반환
2. stopGame() : gameStart = false로 초기화
3. getGameStatus() : gameStatus를 반환
## 컨트롤러
### GameController
위의 클래스들을 참조한다.
1. initializeGame() : GameStatusService를 생성해 gameStatusService를 참으로 초기화
2. setGame() : GameService에서 setComputer()를 해서 컴퓨터 난수 생성
3. startGame()
1. clearScore() : GameService의 strikeCount, ballCount를 0으로 초기화해준다.
2. getPlayerNumbers() : PlayerView에서 입력 요청을 하고, 입력받은 문자열을 GameService에 넣어준다.
3. matchGame() : GameService에 setPlayer, setComputer을 해서 점수를 계산한다.
4. showPlayersScore() : GameService의 strikeCount, ballCount를 가져와 ScoreResultView로 보내 점수를 출력시킨다.
5. isThreeStrike() : GameService에서 스트라이크 개수를 가져와 3개면 참을 반환한다.
6. whatToDoNext()
1. getPlayersOpinion() : GameStatusView를 통해 입력을 받아온다.
2. setGameStatus(String opinion) : GameStatusService에 문자열 입력변수를 넣어 "2"이면 서비스의 gameStatus를 거짓으로 바꾼다.
진짜 머리속으로 '코딩'을 했다. 입력 변수가 딱 필요한게 뭐일까를 생각해서 딱 그것만 받으려 했고, 클래스를 참조해 사용할 땐 어디서 사용하는게 좋을까, 예외처리를 어디서 할까 등등 계속 고민했다.
이렇게 기능 목록을 나누고 해보니 좋은 것은, 클래스 단위로 보았을 때 이게 어디서 일이 끝나고, 이제 무슨 클래스에 일을 시켜야 할지를 딱 구분할 정도로 보인다는 것이다.
이런 구조로 MVC 패턴을 만들었다. 스스로 돌아봤을 때 잘한 점과 못한 점을 생각해보자면,
잘한점
- 기능 목록을 세분화 한 것 : 프로젝트를 만들다가 중간에 무엇을 만들까를 생각할 필요가 없었다. 머리 속에 모든 시퀀스를 정리하고 하기에 기능 목록을 보며 코드를 짜고, 그 코드에 대한 검수를 한다는 생각으로 다시 보는 일을 하면 되었다.
- 메서드에 인덴트를 1로 맞춘 것 : 이게 제일 긴 메서드 였다. 개인적으로 저번 주차에 했던 다짐을 지킬 수 있어서 좋았다.
private void startGame(){
while(!isThreeStrike()) {
clearScore();
putPlayerInService(getPlayerNumbers());
matchGame();
showPlayersScore();
}
}
아쉬운 점
- 모델에서 겹치는 부분을 빼내지 않은 것 : 프로젝트를 끝내고 TDD에 대해 마저 찾아보는 도중, 오타로(?) DDD를 찾아보았다. 도메인 주도 설계에서 겹치는 도메인에 대한 이야기가 있었는데, 컴퓨터와 플레이어가 모두 가지는 3자리 랜덤 숫자에 대해서 하나의 동일 도메인을 참조시켜 만들었으면 중복을 줄이면서 조금 더 객체 중심적으로 할 수 있지 않을까 후회된다(는 사실 DDD도 공부하고 있다. 공부가 제대로 되면 야구 코드도 리펙토링 해야지)
- 테스트 코드를 기능이 아닌 메서드 단위로 모두 테스트하려 한 것 : 처음에 모든 메서드에 대한 테스트를 만들었다. 하지만, 외부 클래스에서 호출 할 필요가 없는 메서드들을 default상태로 만들어서 가능했던 것이다. 후에 이것을 리펙토링하면서 테스트코드들을 한번 더 솎아내야 했다.
공부를 할 수록, 다음 주차에 대한 욕심이 커져간다.
도메인 주도 설계를 해보고 싶다는 욕심이 생겼다.
누군가 내 코드를 보고 리뷰를 해주고, 어떤 순서로 배워야 한다고 이야기해주는 것은 인생에 처음 있는 일이어서 인지,
계속해서 배우면서 배우는 동안 어떻게든 더 많은 것을 얻어내야 겠다는 욕심이 생긴다.
'장's 개발생각' 카테고리의 다른 글
[개발생각] 우아한테크코스 5기 4주차 다리게임 회고 (0) | 2022.11.23 |
---|---|
[개발생각] 우아한테크코스 5기 3주차 로또 후기 (0) | 2022.11.18 |
[개발생각] 우아한 테크코스 5기 1주차 온보딩 후기 (0) | 2022.11.03 |
[개발생각] 클린 코드, TDD를 위한 공부 (0) | 2022.10.20 |
[개발생각] (2022.10) 설명회를 다녀와서 (0) | 2022.10.13 |