지난 10월 19일 ~ 10월 25일까지 프리코스 1주차가 진행되었다.
어떤 과제가 나올까 궁금했는데, 작년 프리코스 과제였던 숫자 야구 게임이 나왔다.
https://github.com/woowacourse-precourse/java-baseball-6
GitHub - woowacourse-precourse/java-baseball-6
Contribute to woowacourse-precourse/java-baseball-6 development by creating an account on GitHub.
github.com
이 글에선, 1주차 과제를 하며 내가 했던 고민들과 느낀점을 기록해보고자 한다.
구현부터 설계부터?
사실 소프트웨어 공학을 배웠다면, 설계의 중요성은 누구나 알 것이다. 이번 과제 역시 요구사항에 기능을 구현하기전, 구현할 기능 목록을 정리하라고 명시되어 있었다.
하지만 또 실력이 그리 좋지 않은 개발자라면 설계,, 역시 상당히 막막하다. 일단 구현을 해보고 부딪히면서 아 이 설계가 좋았구나!! 느끼는 부분도 있다.
그래서 나는 이 과정을 반복했다.
설계 -> 구현 -> 아 이거 아니구나 -> 다시 설계 -> 구현
제대로 되지 않은 설계로 구현부터 하게 될 경우, 객체지향적인 코드를 짜기 어렵다는 것을 느꼈다. 요구사항엔 게임이 종료된 후 게임을 진행하는데 게임 시작 메시지인 "숫자 야구 게임을 시작합니다!"가 존재하는데, 재시작을 해도 해당 메시지는 전체 출력 예시에서 한 번 밖에 출력되지 않는다.
최대한 한 객체와 메서드가 하나의 역할만 하도록 코드를 짜고 싶었으나 이를 만족하는 것이 어려웠다.
최종적으론 MVC 패턴과 유사하게설계를 했다.
domain에는 객체와 객체에 필요한 메서드를 정의했고, Controller를 통해 메인 Game을 진행하고, Domain의 역할을 보조하는 클래스를 Utils과 Service에 분리했다.
전반적으로 Util에는 도메인의 로직이 아닌, 입출력 기능을 Service에는 도메인이 로직을 수행하는데 보조하는 기능을 넣었다.
그래서 Computer의 기능을 보조하는 strike와 ball의 개수를 분리하는 ComputerService를 따로 만들었다.
ComputerService를 따로 분리해야했을까?
하지만, 이렇게 설계를하고 나니 애매했다. Domain에 어떤 로직까지 담아야하고, 어디부턴 Service로 분리해야하는지가 애매했다.
메인 Game의 메서드에는 GameService를 작성하지 않았는데, Computer에는 ComputerService를 따로 작성했다. Service로 나누는 기준이 불명확하다.
어떤 기준으로 나누면 좋을까. 아직 답을 모르겠다.
Java 언어에 대한 고민
1. Stream을 쓸 수 있지 않을까?
모던 자바 인 액션 스터디(회고록 링크)에선, Stream 사용법 깊이 배움. 이 경우에 Stream과 for문 중에 무엇이 더 좋을까, Stream 사용이 가능하다면 적극적으로 사용해볼까 했었다.
ball과 Strike의 개수를 세는 메서드에서 이 고민을 많이했다.
필요한 로직은 사용자 입력값에서 아래 두가지를 고민하면 되었다.
1. computerAnswer에 존재하는 값이 있는가
2. computerAnswer에 존재하는 위치가 같은가
public static int countStrike(List<Integer> playerInput, List<Integer> computerAnswer) {
int strike = 0;
for (int i = 0; i < computerAnswer.size(); i++) {
int cur = playerInput.get(i);
if (computerAnswer.contains(cur) && computerAnswer.indexOf(cur) == i) {
strike++;
}
}
return strike;
}
나는 결국 고민 끝에 for문을 사용했다. 그 이유는 filter로 요소를 거를 적당한 메서드가 생각나지 않았기 때문이었다.
하지만 필요하면 만들면 되었는데, 도현님의 코드를 리뷰하다가 아래와 같이 직접 if문에 해당하는 boolean함수를 만드는 방법을 배웠다. 도현님은 위의 2가지에 대한 boolean 메서드를 직접 만들었는데 코드 흐름은 다음과 같았다.
1. 0~2 인덱스에 있는 값을 확인하기 위해 -> IntStream 사용
2. 각 인덱스에 있는 값이 answer에 존재하는 것만 filter
3. 동일한 인덱스에 동일한 값이 있는 것을 filter
4. 남아있는 index를 count
이렇게 strike를 구했다.
직접 필요한 메서드가 있다면 만들어서 사용하자.
또한 이렇게 직접 filter에 들어갈 Predicate 메서드를 만들면 명시적으로 프로그래밍이 코드가 어떤일을 하는지 알 수 있기 때문에 더 도움이 되는 것 같다.
2. 무엇을 static 변수, 함수로 쓸지, 무엇을 일반 객체, 메서드로 선언할지
객체의 메서드를 선언할때 static을 사용해 선언하면 Class가 생성될때 static 영역에 선언되어 객체를 따로 생성하지 않고도 호출할 수 있다.
클래스의 메서드를 정의하고 이를 다른 클래스에서 활용을 하는 구조에서 어떤 메서드는 static으로 선언하고 어떤 메서드는 static으로 선언하지 않아야할까에 대한 고민이 되었다.
static으로 선언하게 되면 따로 객체를 생성하지 않고 사용할 수 있다는 장점은 있지만, GC가 이를 따로 관리하지 않기에 프로그램이 종료될때까지 남아 있다는 특징을 고려해야했다.
따라서, Utils과 Service, Controller에 대한 내용은 도메인의 기능을 보조하는 역할을 하기에 언제든 누구나 접근할 수 있는 public static으로 선언을 해 사용을 했고
도메인 내에서 돌아가는 것 또는 객체 자체의 기능 -> static을 사용하지 않았다.
하지만, 과제를 마치고 지금 생각해보니 `ComputerService` 같은 경우 computer만 사용할 수 있는 고유한 메서드들이 정의되어있는데 이를 static으로 사용하는건 적절하지 않았다고 생각한다.
3. final을 어디까지 써야할까?
최근에 동시성과 불변객체에 관한 공부를 해서 일까, 변수를 최대한 final로 선언하고 싶다는 생각이 들었다.
final을 사용하면 객체의 불필요한 재할당, 상태 변화를 방어할 수 있는데 이 과제의 flow는
정답이 맞으면 -> 게임 재시작 여부 결정 : 이 경우엔 새로운 정답이 등장해야함.
정답이 틀리면 -> 다시 정답을 맞추라고 안내 : 이 경우엔 정답이 이전과 다르게 유지되어야했다.
요구사항을 기준으로 게임에 `상대방인 컴퓨터`가 정답을 생성한다고 했기에 정답은 Computer객체의 상태 변수라 생각했고 이를 변하지 않게 하기 위한 방어장치가 필요하다고 생각했다.
따라서, answer를 final로 선언했다. 이 경우 아래와 같이 `setAnswer()`를 두 번 실행해도 첫번째의 경우에만 초기화되고 두 번째에는 다시 초기화가 되지 않는다.
public void setAnswer() {
while (answer.size() < RANDOM_NUMBERS) {
int randomNumber = Randoms.pickNumberInRange(1, 9);
if (!answer.contains(randomNumber)) {
answer.add(randomNumber);
}
}
}
하지만, 지나고보니 아래와 List를 ArrayList로 생성했기에 add는 가능하다. 따라서 이 방법도 완벽하게 정답 수정을 방어하는 방법은 아니다.
사실 불변 객체인 List.of()를 사용하고 싶었으나 Random 값을 String으로 받고 -> ParseInt -> List에 추가하는 과정을 선언과 동시에 초기화하기가 어려웠다.
따라서, setAnswer외에 다른 add를 사용하는 메서드를 만들지 않는 것과 Computer객체를 final로 사용하는 것 두 가지 방법으로 변형을 방지하고자했다.
이러한 고민을 코드리뷰를 하며 도현님께 말하니 도현님께서 final은 이렇게까지 쓸 수 있다는 이런 유튜브 영상을 보내주셨다.
https://www.youtube.com/watch?v=lcPfxmn0otA
간단하게, 정리하면, for, catch, 메서드의 파라미터에도 쓸 수 있다는 내용이고 final을 사용해서 적절하게 오류를 컴파일 단에서 발견할 수 있다는 내용이다.
느낀점?
4-1 소프트웨어 공학 수업을 들으며 코드 `구현`에 대한 내용은 책에 단 두 줄 적혀있다고 교수님께선 구현보다 설계를 잘하는 것이 중요하다고 말씀하셨는데 이번 과제를 하며 더 절실하게 느꼈다.
전반적으론, Java에 대한 이해와 설계를 어떻게 더 잘할까?에 대한 고민, 객체지향적인 설계에 대한 고민을 많이해야겠다고 느꼈다.
다음 과제 다짐을 아래와 같이 정하며 글을 마무리하겠다.
1. 설계 잘하자 -> 객체가 해야할일을 먼저 명시하자.
2. final을 더 잘 활용해보자.
기록
그냥 하루하루 기록하려고 쓴 (정리되지 않은) 러프한 글
'BE > 우아한테크코스' 카테고리의 다른 글
[우아한테크코스 6기] 프리코스 - 4주차 (1) | 2023.12.06 |
---|---|
[우아한테크코스 6기] 프리코스 - 3주차 (0) | 2023.11.15 |
[우아한테크코스 6기] 프리코스 - 2주차 (0) | 2023.11.06 |
[우아한테크코스 6기] 프리코스 0주차 (1) | 2023.10.26 |