지금까지 살면서 병행이라는 것이 좋은것이라고 생각했다. 전자과에 다닐때도 내가 하고 싶은 프로그래밍 공부를 병행했고 소프트웨어학과를 와서도 내가 하고 싶은 프로젝트를 병행했다. 당연히 바쁘면서도 이 부족한 시간을 내가 잘 활용해 결과를 만들면 좋아했다. 나름 열심히 공부한 덕분에 내 스스로 많이 성장한 것 같다. 그점에 뿌듯함을 느낀다.
그러나 최근 병행에 대해서 회의감을 느꼈다. 다양한 경험을 할 수 있지만 깊은 경험을 하지 못한다는 생각이 들었다. 인생을 돌아봤을때 내인생에서 성장했다고 느낀 시점은 어느 하나를 잡고 밤낮없이 그것만 집중하고 생각했던 순간들이다. 그래서 우아한 테크코스 프리코스를 진행하는 중에는 다른 모든 것을 멈추고 우아한 테크코스에만 집중하고 싶었다. 제대로 해보고 싶었다.
10월 19일 우테코 프리코스가 시작되었다. 오후 2시에 유튜브 라이브로 오리엔테이션을 진행하고 3시에 과제가 메일로 왔다.
이 시간이 고통이 아니라 즐거운 시간이기를 기대해 봅니다.
https://github.com/woowacourse-precourse/java-baseball-6
1주차의 과제는 작년 5기 2주차 과제인 숫자 야구 게임이 나왔다. 첫날인 목요일은 우아한 테크코스 Java 코드 컨벤션을 정독하고 과제의 요구사항을 꼼꼼하게 읽었다. 그리고 우아한 테크코스 독스에서 제출전 주의사항과 문서들을 확인했다.
자바 스타일 가이드 : https://github.com/HaiSeong/google-java-styleguide
Pull Request 전 체크리스트 : https://github.com/woowacourse/woowacourse-docs/blob/main/cleancode/pr_checklist.md
가장 충격적이였던 규칙이었다.
우선 과제 문서에 기능 요구사항들을 정리했다.
지인의 추천을 받아 책 한권을 읽기 시작했다. '객체지향의 사실과 오해'라는 책이다. 이 책에서는 객체지향의 본질과 무엇에 집중해야 하는지를 말한다. 좋은 개발 도서는 찾기 어려운것 같다. 너무 쉽게 쓴 프로그래밍 입문서적, 또는 실무자들이나 읽을 수 있을법한 정서 둘중하나이다. 이러한 이유에서 대학생 고학년들은 책보다는 강의에 눈길이 간다. 하지만 이 책은 정말 적절한 책이었다. 쉬우면서도 재밌고 좋은 내용이 많았다.
'객체의 행동에 초점을 맞춰야 한다. 객체는 다른 객체와 협력하기 위해 존재하기 때문이다.'
책을 읽으면서 가장 마음에 들었던 문장이다. 이 문장이 이번주차 과제를 하는데 많은 도움을 주었다.
프로그래밍을 처음 배울때 우리는 모두 프로그램을 어떻게 만들지 설계도를 만들고 코딩을 하라고 배운다. 수도코드를 짜는법, 플로우 차트를 그리는 방법을 배운다. 그러나 실제로 이런 설계도를 완벽하게 만들고 프로그래밍을 해본적은 손에 꼽는다. 시험을 잘보기 위해서는 문제를 읽으며 손이 음직여야 한다. 플로우 차트를 그리고 있다면 5문제중 2문제를 맞추기 어려울것이다.
이번 과제도 바로 키보드로 손이 갈 뻔했다. 그러나 글의 초반에서 말한것 처럼 나는 '제대로' 해보고 싶었기에 차분히 설계부터 시작했다. 그러나 바로 객체를 설계하는것은 어려웠다.
객체의 존재이유는 다른 객체와 협력을 위해서 이며 따라서 객체의 행동에 초점을 맞춰야 한다는 책의 내용이 떠올랐다. 그래서 플로우차트를 그려야 겠다고 생각했다. 프로그램에 필요한 모든 동작을 정리하면 그 동작에 따라 역할을 부여하고 객체를 설계할 수 있지 않을까 라는 생각이였다.
draw.io를 이용해 플로우 차트를 그렸다. 플로우 차트를 그리고 나니 구현 해야할것이 명확해 졌다는 생각이 들었다.
우선 전체 프로그램이 제공해야 하는 기능, 즉 책임이 정리 되었으니 이 책임을 더 작은 책임으로 분할 하면 됐다. 그 책임마다 하나의 객체를 만들었다. 많은 객체가 만들어 졌는데 이들을 어떻게 패키지화 할까 고민했다.
사실 우테코에 지원하기전 프리코스 기출문제들을 보다가 이 문제를 본 기억이 있다. 의도치 않게 스포를 당해버렸는데 코드의 자세한 내용은 기억이 나지 않았지만 많은 사람들이 이 과제를 MVC 패턴으로 구현한것이 기억났다. 스포당했다... 나도 이 패턴을 적용하기로 결정했다.
그러나 이것은 실수였다. 나는 MVC 패턴을 겉 핥기로 알고 있었다. 그래도 View에 무엇을 넣어야 할지는 명확했다. 사용자의 입출력을 담당하는 객체들을 분리했다. 하지만 Model과 Controller를 어떻게 나눌지는 헷갈렸다. 결국 내 느낌대로 분리했다. 그결과...
대표적으로 잘못 분류했던 객체를 몇개 소개해보겠다.
내 설계에 따르면 BaseballGame은 반복문을 돌며 사용자의 입력 받고 Model에 넘겨주며 정답일시 게임을 끝내는 역할을 한다. 누가 봐도 Game은 Controller에 가야했지만 이러한 역할을 부여하고도 그 당시 내 생각은
'게임 ?? 이거 이름이 약간 명사형인거 보니까 Model이 제일 잘어울리지 않을까?'
이런 말도안되는 기준을 가지고 Model 패키지에 넣었다. Model 패키지에서도 마치 컨트롤러 같은 역할을 하고있었다.
AnswerGenerator는 게임의 정답을 만드는 객체이다. 이 경우도 말도 안되게
'정답에 따라 Game이 Control 되니 컨트롤러가 아닐까?'
이런 삽질을 하고 있었다.
잘못 설계된 쓰레기 였지만 돌아가기는 했다. 주어진 테스트가 돌아가는것을 확인한 나는 리펙토링을 시작하며 코드를 다시 뜯어보았다. 그러던중 MVC 패턴에 위배되는 점을 찾았다.
'모델은 뷰나 컨트롤러에 대해서 어떤 정보도 알지 말아야 합니다.'
내 코드는 모델과 컨트롤러가 뒤엉켜 있었다. MVC 패턴에 이해도가 없는 상태로 패턴을 사용했기 때문이었다.
MVC 패턴 공부부터 다시 시작했다. 우선 MVC 패턴을 사용하는 이유부터 알아야 했다. 어쩌면 MVC 패턴이 정답이 아닐지도 몰랐으니...
- MVC 패턴을 가진 시스템의 각 컴포넌트는 자신이 맡은 역할만 수행한 후 다른 컴포넌트로 결과만 넘겨주면 되기 때문에 시스템 결합도를 낮출 수 있습니다.
- 유지보수 시에도 특정 컴포넌트만 수정하면 되기 때문에 보다 쉽게 시스템 변경이 가능합니다.
- 기능별로 코드를 분리하여 하나의 파일에 코드가 모이는 것을 방지하여 코드의 가독성, 확장성, 재사용성이 증가합니다.
참고 블로그
결론적으로 MVC패턴을 사용하면 각 컴포넌트가 자신의 역할에 집중할수 있게 되며, 각 컴포넌트 간의 연관도가 줄어들어 확장성과 재사용성이 증가한다. 이 과제를 수행하는데 적절해 보였다. 사용자의 입력과 게임의 로직을 분리할 필요가 있었고, 게임과 게임 컨트롤러가 여러 객체에게 메시지를 보내 프로그램이 동작하는 구조와 일치했다.
처음 만들었던 generateAnswer 메서드의 코드이다. 중복되지 않는 3개의 숫자를 만들어야 한다. Set은 중복을 허용하지 않는다. 그래서 나는 이 메서드를 Set을 이용해 만들면 되겠다고 생각했다. 그런데...
테스트를 통과하지 못한다. 근데 또 이게 웃긴게 3번을 하면 1번은 통과한다. 처음에 이게 뭔가 싶었다. 그래서 테스트 케이스를 뜯어봤다.
테스트 케이스에서는 246이면 낫싱, 135면 3스트라이크 라고한다. 정답을 무작위로 만드는데 이런 말도안되는 케이스가 있나? 생각했지만 곧 어떤 구조로 동작하는지 알아버렸다.
'이래서 camp.nextstep.edu.missionutils.Randoms 을 쓰라 했구나...'
그럼 뭐가 문제지 고민하던중, Set을 사용한게 테스트를 통과하지 못하는 이유라는것을 알게 되었다. Set은 순서를 보장하지 않는다. pickNumberInRange 메서드에서 만들어진 숫자가 Set에 저장되면 중복된 값이 저장되지는 않지만 순서가 저장되지 않는다. 실제 게임을 만들 때는 문제가 없다. 어차피 정답은 랜덤으로 만들어 지는게 맞으니. 그러나 테스트 케이스를 맞추기 위해서 List를 이용했다.
getRandomValueNotInList에서 리스트에 없는 숫자를 만들고 generateAnswer에서 List에 넣어줬더니 테스트 케이스가 잘 동작했다.
프로그래머는 확장성을 고려해야 한다. 이 미션도 혹시 나중에 바뀔수 있는 부분이 있을까 라는 생각을 가지고 찾아보았다.
1. 더 많은 숫자를 입력하고 맞추게 하기
2. 정답을 랜덤으로 만드는게 아니라 한명이 문제를 내주고 다른사람이 문제 맞추게 하기
3. 숫자 대신 알파벳 야구
1번은 쉽게 바꿀 수 있다고 생각되었다. GameNumbers의 GAME_NUMBERS_SIZE 값을 조정하면 되었다.
3번은 게임의 이름이 숫자야구인만큼 이런식으로 바뀔 확률은 적다고 생각이 되었다.
2번은 가능성이 있다고 생각되었다. (테스트 케이스에서도 정답 바꿔치기를 하니...)
그래서 기존에 클래스로 구현되었던 AnswerGenerator를 인터페이스로 설계하기로 했다.
그리고 이 인터페이스를 구현하는 RandomAnswerGenerator를 구현했다.
만약 2번 같은 기능을 추가한다고 하면 AnswerGenerator를 구현하는 CustomAnswerGenerator를 만들면 된다.
게임 컨트롤러에서는 RandomAnswerGenerator 클래스를 의존하는게 아니라 AnswerGenerator라는 역할을 의존한다.
그리고 GameController에서 적절한 구현체를 넣어주었다.
처음에 구현할때 게임 진행에 필요한 메시지를 모두 상수로 관리하려니 클래스가 지져분하다고 생각되었다. 그래서 enums라는 패키지를 만들어 각 메시지들을 관리했다. 하지만 이렇게 하는게 맞나라는 생각이 들어서 다시 상수로 변경했다. 이 부분은 이번 과제가 끝나고 코드리뷰를 받을때 여러 사람의 의견을 들어보고 싶다.
한 주동안 몰입하며 나름 즐겁게 진행한 것 같다. 설계를 하고 한단계 한단계 넘어가는게 재미있었다. 이번주는 첫주로 프리코스에 온보딩 하는 시간이었다. 내일 나올 2주차 과제는 더 이쁘게 설계하고 좋은 코드를 짜보고 싶다.