1주차 미션을 진행하면서 그 때 그 때 궁금한 점을 해결하는 과정을 정리해보려고 한다.
git 기본 사용법에 대해 디스코드에서 이야기가 나와서 평소에 궁금했던 걸 질문해보았다.
그리고 답변을 봤는데, CLI와 GUI가 뭐지...?
CLI(Command-Line Interface)
입출력만을 이용해서 컴퓨터와 소통
GUI(Graphic User Interface)
사용자가 눈에보이는 그래픽(아이콘)으로 컴퓨터와 소통
라고 한다!ㅎㅎ
https://velog.io/@jellyjw/CLI-GUI의-차이-및-CLI-기본-명령어-정리
공들여서 READ.ME를 작성하고 나서, 순간 멍해졌다. 뭐부터 구현해야하지?
일단 생각이 들었던 건 아래의 두가지 방향이었다.
즉, 프로그램 작동 흐름 순서상 가장 앞에 일어나는 기능을 먼저 구현한다.
객체를 중심으로 가는 게 아니라 절차적 프로그래밍 방식이라는 느낌이 든다. 테스트가 쉬운 코드 먼저 구현해본다. 라는 TDD의 내용도 걸리기도 하고!
TDD 강의에서 입력과 출력이 명확한 게 테스트하기 편하므로, validate와 같은 것들을 먼저 구현하라고 했던대로 입력에 대한 validate 먼저 작성한다.
숫자야구의 숫자라고 치면 보통 입력받은 숫자를 바로 객체로 만들지 않는다.
예를들어,
InputValidate(전반적인 입력 형태 검증) → InputConvertor (→ 때에따라 Dto도)
이런 과정을 거칠 때가 있는데, 그렇다면 이 validate가 어디에 위치해야하는지 확정하지 않은채로 일단 기능별 validate를 만들게 된다.
생각해보면 사실 validate를 일단 쭉 만들어놓고 나중에 책임별로 객체들에 나눠도 되겠다 싶기도 하다.
그 과정에서 convert 마찬가지로 일단 만들어 놓고 나중에 나눠볼까 한다!
물론 상황에 따라 다를 수도 있고, 뭐가 더 맞는지 모르겠지만 그나마 덜 걸리는 2번으로 먼저 해보려고 한다! 과연 좋은 선택이었을지... to be continue...
내부 로직을 이후에 구현하고, 내부 로직 없이 입, 출력만 반환하도록 정의한다는 답변이 뒷받침될 수 있을 것 같다.
아직 스트림 사용이 능숙하지 않아서 필요한 사용법이 있을 때마다 찾아서 사용해보고 정리해보고 있다!
이런 의도로 쓰고 싶었는데 찾아보니 이 때에는 anyMatch를 쓰면 된다고 한다.
filter에 의해 걸러진 객체가 하나라도 존재하면 true를 반환하고, 그렇지 않으면 false를 반환한다!
세자리 숫자 중에 중복된 숫자가 있는지 검증하는 과정인데 너무 단계별로 메소드가 많다고 느껴졌다. 스트림을 활용해 더 줄일 수 있을 것 같은데... 하고 찾아보던 중
생각의 전환!
리스트 내 중복된 숫자를 찾으려고 하기보다, 반대로 distinct를 활용해 리스트 내 unique한 숫자가 3개인지 확인하면 간결해질 수 있었다!
그래 복잡할 때는 반대로 생각해보기!
원래 이런 형태를
이렇게 고쳐보았다.
get을 최대한 지양하려고 하다보니 메시지를 보내는 방식으로 진행하게 되고,
그러다 보니 메시지를 보내는 과정에서 필요한 메소드들을 만들다보니 이렇게 메소드가 늘어나게 되었다.
예를들어, 아래는 정답 Balls 중에서 숫자가 일치하는 경우인 BALL 갯수를 알아내는 기능을 위해 구현 메소드들이다.
Balls 클래스
가장 바깥층은, enum으로 관리하는 TryResult로 비교 결과를 받아서 ball의 숫자를 받는 형태이다.
각각 스트라이크, 볼인지 확인하고 enum인 TryResult로 결과를 반환해준다
확인하는 과정이 이 안을 거쳐서 일어난다.
answerBalls에 get을 쓰지 않기 위해서 answerBall을 객체로 하고 playerBall을 메소드의 변수로 하여 처리해주는 방식으로 연결해주었다.
balls(정답Balls)를 돌면서 정답ball이 playerBall과 숫자가 같은 게 하나라도 있는지 여부를 반환한다.
Ball 클래스
answerBall의 숫자를 get으로 가져와서 playerBall의 숫자를 get한 것과 비교하면 쉽겠으나... 그렇게 쓰이는 get을 쓰지 말자는 것이 목적이기 때문에!
Ball(answerBall) 객체에서 정답Ball의 number를 갖고
플레이어Ball객체에 같은 숫자인지 비교하는 메시지를 보냄으로써
이 역시도 get 없이 구현을 해보았다.
일단 get을 쓰지 않겠다는 소기의 목적은 달성했으나,
get을 피하기 위해 구상한 메소드안 3F, 4F 부분이 조금 복잡하지 않나라는 고민이 든다.
뒤의 🔎 Balls 클래스가 너무 큰데...? 를 해결하면서 3F, 4F 부분의 복잡도 문제를 해결하였다!
처음에는 random 번호를 받아 Ball을 생성하는 클래스 하나로 만들었다가 문득 책임에 대한 생각이 들었다.
지금 이 클래스는 2개의 역할을 하고 있는 것 아닌가?
1. 랜덤 넘버를 생성하는 것과, 2. 그 결과 숫자로 공을 생성하는 것
그러면서 각각의 역할이 해야하는 추가 작업들이 있었기에 자연스레 객체 분리의 신호라고 느껴지며 분리를 하게 되었던 것 같다!
더불어 객체 분리를 하니 훨씬 테스트 하기도 좋은 코드가 되어 객체 분리의 적절한 시점이었다고 느꼈다!
RandomNumberGenerator
RandomBallsGenerator
private 메소드일 수 있는데 테스트를 위해서 default로 만드는 게 맞을까?
테스트라는 목적이 접근 제어자를 private에서 default로 바꿀 수 있는 이유가 될까?라는 의문이 든다.
로직에 실제 동작과는 상관 없는 테스트만을 위한 코드가 추가되게 된다.
나중에 pr리뷰 때 의견을 들어보고 싶다!
현재 스트라이크, 볼 등의 비교결과 반환까지 구현한 Balls 클래스인데
Balls에 대한 valide부터 시작해서, 스트라이크, 볼인지 확인하고 갯수를 구하는 메소드까지 이 안에 구현이 되어있다.
흠... 일단 Balls를 갖고 계산을 해야하기 때문에 여기에 구현을 했는데,
과연 스트라이크, 볼 결과 확인이 Balls의 책임인가 하는 고민이 들었다.
더불어 클래스가 너무 커진다는 것은 객체 분리의 신호이기 때문에!
그렇다면, 어떤 역할의 객체가 분리되어야할까?
일단, 문제점으로 보였던 스트라이크, 볼 결과 확인 관련 메소드를 책임져줄 객체가 필요할 것 같다.
스트라이크, 볼을 따로 구하지 않고 스트림을 적용한 메소드 2개로 구할 수 있게 리팩토링하니 Balls가 판단에 관여하지 않게 책임을 줄여줄 수 있었다!
이렇게 아래부분이 다 필요없어졌다ㅎㅎ
3개의 스트라이크, 볼 등의 결과를 담은 리스트에서 3스트라이크인지 확인하는 메소드를 작성하다 고민이 들었다.
처음에 스트림의 allMatch를 사용하면 간단하겠다는 생각이 들어 작성을 해보았는데, 문득 스트림을 사용하는 것이 성능에 더 안 좋다는 이야기가 떠올랐다.
길이가 3인 리스트를 도는 것이라 간단한 연산인데 스트림을 사용하는 게 과한 걸까
라는 질문이 들었고, 그래서 스트림을 사용하지 않고 forEach문을 사용한 버전으로도 작성해보았다.
확실히 스트림을 사용한 것이 가독성이 좋긴한데 성능면에서 차이는 어떨까?
GPT의 의견이 궁금했다!
길이가 3인 리스트를 도는 것이라 간단한 연산인데 스트림을 사용하는 게 과한 걸까
라는 나의 질문과는 반대로, 오히려 작은 크기의 연산에서는 성능 차이가 미미하다는 답변을 받았다. 오호! 그러면 내 경우에는 스트림을 사용하는 것이 더 좋은 방법이겠군!🤔
추가로 답변의 스트림 사용으로 오버헤드
가 있을 수 있다는 게 무슨 말인지 모르겠어서 추가질문!
컨트롤러에 3스트라이크인지 확인하는checkGameWin
메소드를 구현하고 이에 대한 commit 메시지를 적으면서 생각이 들었다.
checkGameWin
밖에 할 일이 없는데 그래도 만드는 게 맞을까?라는 생각의 흐름끝에 우선은 컨트롤러에 놔두고, 마지막에 메소드들을 살펴보며 게임 판단 객체에 들어갈만한 다른 메소드들이 더 생기면 분리를 고민해보려고 한다!
List<TryResult>
의 일급 컬렉션으로 변환이 필요한 시점이 두가지 사항의 반영이 필요했는데,
이걸 해결할 수 있는 방법이 아까 말했던 게임 판단에 해당하는 클래스다! 바로 생성했다!
이렇게 고민되었던 checkWin도 새로 만든 GameResult 클래스에서 처리해주는 것으로 분리했다!
우테코 디스코드에서 클린코드, 객체지향적 코드라면 신문기사처럼 읽히는지
를 보면 좋다고 한 이야기를 듣고 코드를 훑어보는데 눈에 걸리는 곳이 있었다.
게임을 새로 시작할 때마다 정답공과 게임상태를 새로 설정해 play메소드에 넣어주고 있었는데,
이렇게 여러 요구들을 곰곰히 생각해보니 공통으로 가리키는 방향이, 정답공과 게임상태를 관리하는 게임진행객체
를 생성하는 것이었다!
BaseballGame 객체로 묶어주면서 여러 인자를 전달할 필요가 없어졌고, 어떤 목적의 인자를 전달하는지도 명확해졌다!
while 조건인 게임진행상태도 BaseballGame에게 isPlaying 메시지를 보내 확인할 수 있게 바뀌었다!
원래 이렇게 직접 비교했던 부분을 아래처럼 enum에 메시지를 보내는 형식으로 바꿀 수 있었다!
기존에는 볼, 스트라이크 등의 메시지를 outputView에서 관리했다.
볼, 스트라이크 등의 메시지를 해당 enum 객체인 TryResult에서 enum의 필드로 관리한다.
바꿨지만 여전히 고민이 든다.