우아한테크코스 백엔드 4기 프리코스 1주차 미션 숫자 야구 게임을 진행하면서 고민했던 것을 기록으로 남깁니다.
미션 github : 우아한테크코스 - 숫자 야구 게임
내가 작성한 코드 : OzRagwort - 숫자 야구 게임
1주차 미션은 숫자 야구 게임이다. 게임의 규칙은 랜덤한 1~9로 이루어진 3자리 숫자를 유저가 맞추는 게임이다. 구현적으로는 생각보다 간단한 문제였지만 새롭게 알게 된 것들을 최대한 지키려고 구현하려고 노력했다.
지켜야 할 요구사항은 기능 요구사항, 프로그래밍 요구사항, 과제 진행 요구사항 세 가지로 구성되어있었다.
입력
IllegalArgumentException
를 발생입력한 수에 대한 볼, 스트라이크 개수 표시
실행 결과 예시와 동일한 입력, 출력이 이루어져야 함
숫자를 입력해주세요 : 123
1볼 1스트라이크
숫자를 입력해주세요 : 145
1볼
숫자를 입력해주세요 : 671
2볼
숫자를 입력해주세요 : 216
1스트라이크
숫자를 입력해주세요 : 713
3스트라이크
3개의 숫자를 모두 맞히셨습니다! 게임 종료
게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요.
1
숫자를 입력해주세요 : 123
1볼
…
기능 요구사항은 완벽하게 동작하도록 만들라는 이야기 같아서 어려웠던 요구사항은 아니었다. 그러나 나머지 요구사항을 지금까지 프로그래밍하면서 크게 생각해보지 않았던 요구사항이었다. 특히 자바 코드 컨벤션이라는 것은 프리코스를 준비하기 전까지 들어본 적이 없던 것이었다. 이런 것들을 신경 쓰고 지키기 위해서 시간을 많이 썼던 것 같다.
총 4개의 클래스로 구성하였다. 내가 4개의 클래스로 구성한 이유는 아래와 같은 식으로 프로그램이 흘러가도록 하기 위해서이다.
프로그램을 시작하는 Application, 게임을 시작/재시작/종료를 관리하는 Controller, 실제 게임이 진행되는 Game, 랜덤 값을 생성/저장하고 볼/스트라이크 결과를 알려주는 Computer로 이루어져 있다. 나름대로 각각의 객체가 역할을 나누고 동작하도록 구현하려 노력했었다.
처음에는 입력값을 검증하는 Valid라는 클래스가 있었다. Game과 Controller에서 입력받으면 Valid 클래스에서 입력값에 대한 검증을 하는 식이었다. 그런데 구현 도중 미션에서 주어진 API camp.nextstep.edu.missionutils.Random
를 보던 중 Random에서는 랜덤 값을 리턴하는 메소드와 검증하는 메소드가 한 클래스에 있었다. 이걸 본 뒤 "왜 이 둘이 같이 있을까?"라는 생각했다. 나도 입력받은 값을 입력받은 클래스에서 검증할지 참 많이 고민했었다.
결과적으로 Valid 클래스를 없애고 입력받은 곳에서 값을 검증하도록 했다. 그 이유는 프로그램이 커지면 커질수록 검증할 상황이 많아지고 검증하는 메소드가 늘어갈 텐데 그러면 그때마다 Valid 클래스에 추가하는 건 아닌 것 같았기 때문이다. 혹시 입력값의 형식이 바뀌거나 입력값을 검증하는 방식이 달라져야 할 때 나는 입력을 받는 클래스와 Valid 클래스를 모두 수정해야 한다. 또 서로 다른 C1, C2 클래스에서 같은 Valid의 V라는 메소드를 사용하게 된다면 C1에 의해 V가 수정될 때 의도치 않게 C2에 영향을 미칠 수 있다. 그렇다면 한 번에 하나의 검증 메소드를 사용해야 할 텐데 그러면 Valid로 따로 클래스를 만들 필요가 없다고 생각했다. 이런 이유로 Valid를 삭제했다.
해당 미션에서 가장 먼저 구현한 기능은 랜덤한 3자리 숫자를 생성하는 것이었다. 이것을 구현하기 위해 두 가지 방식이 떠올랐다. 하나는 3자리 숫자를 만들어주는 메소드를 만드는 것이고 하나는 클래스를 만들어 생성하는 방식이었다. 단순히 메소드를 만들어 숫자를 만들고 숫자도 입력받고 검증도 해버리면 아마 동작은 할 것이다. 하지만 이게 객체지향적으로 맞냐는 생각하게 된다. 프리코스를 준비하면서 객체지향에서 역할에 대한 내용을 본 적이 있다. 객체들이 서로 협력하는 사이에 각 객체는 자신만의 역할이 있다고 한다. 이것을 생각하고 전체적인 그림을 그렸다. 사용자에게 입력받는 역할, 값을 만들고 가지고 있는 역할, 값을 계산하는 역할 등 여러 가지로 나뉠 것 같다고 생각했다.
그래서 나는 Computer라는 객체가 값을 생성한 뒤 리턴하지 않고 가지고 있도록 만들었다. 이후 볼 카운트도 Computer 객체가 하도록 했다. 이후 Game가 Computer를 시켜서 값도 시키고 유저 입력값을 보내면 Computer가 결과만 리턴하는 방식이다. Computer 객체가 값을 생성하고 리턴하고 Game은 그 값을 입력받은 숫자와 비교하여 볼 카운트를 계산할 수도 있겠지만.... 뭔가 Game은 입력받아서 결과에 따라 힌트만 주고 계산은 Computer가 하도록 하는 방법이 더 좋아 보였다. (잘 한건지는 모르겠다)
camp.nextstep.edu.missionutils.Random
를 보면 파라미터에 전부 final이 붙어있다. 지금까지 파라미터에 final을 붙인걸 본 적이 없었다. 이 부분에 대해 java parameter should be final
라는 키워드로 검색해보면 많은 정보가 있다. 답은 내부 로직상 메소드에서 특정 파라미터값이 변하거나 재할당되면 안될 때 사용하는 것이다. 잉? 당연한 말이 아닌가? 싶긴 한데 그게 맞다. 시나리오를 하나 써본다.
뭐 말도 안 되는 시나리오지만 가능은 할 수도 있겠다. 이 변수는 지금도 앞으로도 재할당하면 안되는 값이다! 라는 것을 확실히 하고 다른 개발자에게도 알리고 싶어서 쓰는 것 같다.
이번 미션에는 프로그래밍을 하기 전 README.md에 구현할 기능 목록을 정리해 추가하라는 요구사항이 있다. 사실 무엇을 만들지 알고 만드는 것은 너무나 당연한 말이다. 그렇다면 나는 그동안 그렇게 프로그래밍을 해왔는지를 돌아봤다. 이전에도 나는 개발을 하기 전 간단한 순서도를 그려본 뒤 프로그래밍했었다. 그래야 프로그램의 흐름을 정리할 수 있었기 때문이다. 하지만 평소와 차이점이 있었다. 바로 글로 기능별로 정리해 프로그래밍의 방향을 잡아준다는 것이다. 이번 미션은 순서도가 간단해 나중에 다시 보더라도 금방 이해할 수 있었겠지만 복잡한 프로그램을 만들 때는 저렇게 간단하게 그린 순서도를 다시 보면 이해가 되지 않거나 더 헷갈리는 경우도 많았다.
실제로 미션을 받자마자 그려본 간단한 순서도와 그 순서도에 맞춰 작성한 구현할 기능 목록이다. 이번에는 평소처럼 간단한 순서도를 보면서 그대로 글로 옮겨 README.md에 작성했다. 그리고 실제로 저 순서대로 개발을 진행했다. 그리고 프로그래밍을 하다 보니 README.md를 수정하게 되기도 했는데 자꾸 기능 목록을 보다 보니 내가 지금 무엇을 하고 있는지 앞으로 무엇을 해야 하는지 이해가 쉬웠고 프로그래밍이 더 수월하다고 느꼈다. 사실 처음 해보다 보니 처음에는 README.md에 구현할 기능 목록이라는 이름으로 프로그램의 순서도를 적어둔 것 같다. 구현이 마무리되고 다시 README.md를 정리하면서 구현할 기능 목록, 프로그램 플로우, 요구사항 체크리스트 세 가지로 세분화하여 다시 정리하였다. 아마 처음부터 이렇게 정리하고 개발을 시작했다면 더 수월하게 구현할 수 있었을 것 같았다. 2주차 미션부터는 처음부터 구현할 기능 목록, 프로그램 플로우, 요구사항 체크리스트를 정리하고 시작해볼 생각이다. 그렇다면 미션의 README.md가 아닌 내가 작성한 README.md를 보면서 프로그래밍을 할 수 있을 것 같다.
나는 객체지향의 개념을 설명해보라고 하면 아직 자신 있게 설명하지 못할 것 같다. 뭔가 막연한 느낌이다. 특히 다른 사람의 코드를 보면 가끔 '왜 이렇게 구현한거지?' 와 같은 생각을 한적도 있었다. 그래서 이번 미션을 통해서도 최대한 객체지향적으로 개발을 해보겠다고 생각하며 진행했었다. 이번 미션을 만약 내가 과거에 진행했다면 아마 다음처럼 상황이 흘러갔을 것이다.
어쩌면 이 과정을 모두 main()에서 하도록 작성했을 것이다. 순서도에 맞게 동작하는 것만 생각하다 보니 내가 실제로 그렇게 프로그래밍을 해왔었던 것 같다. 그리고 끝나고 잘 짰다고 생각하고 있었을지도 모른다! 이번 미션에서는 사용되는 클래스는 4개이고 각각의 역할은 다음과 같다.
최대한 역할과 협력이라는 개념을 적용해보려 노력했지만 사실 이게 아닌 것 같다는 생각이 더 든다. 그래서 다른 사람들은 어떻게 작성하는지 궁금해서 훔쳐보기도 했고 직접 이야기하면서 서로 코드 리뷰도 해보고 싶은데 그런 건 우아한테크코스에 최종 합격하고 만나서 해야겠지...... 화이팅!
코드 가독성이 좋은 코드를 작성하는 건 너무 당연히 당연하지만 또 너무 어렵다. 이번 미션에서 indent depth를 줄이고 메소드가 하는 일을 줄이라는 요구조건은 코드 가독성을 향상시키는 참 좋은 방법인것 같다. 알고리즘 문제를 풀다 보면 for문과 if문이 너무 많이 쓰이는 경우가 많다. 그러다 보면 안 그래도 헷갈리는 알고리즘 문제에서 길을 잃어버리기도 한다. 그 이유가 indent depth가 너무 많다보면 코드를 이해하기 어렵다는 것을 나는 이미 알고 있었던 것이다. 이번에 indent depth를 줄이기 위해 노력하다 보니 자연스럽게 코드가 쉬워지는것을 느꼈다. 그리고 indent depth를 줄이려면 자연스럽게 메소드들이 쪼개졌다. 하나의 메소드가 하나의 일을 하니 그 어렵던 이름짓기도 쉬워지고 코드에서 잘 이름 지어진 메소드들을 쓰니 코드를 읽기 더 쉬워졌다. 좋은 건지는 모르겠지만 개인적으로 마치 책을 읽는 느낌이 들었다. 앞으로도 몸에 익도록 연습하면 좋을 것 같다.
애니멀봄이라는 프로젝트를 할 때 연습 삼아 변경사항을 나름대로 커밋을 하면서 서버를 만들어본 적이 있다. 처음에는 규칙 없이 커밋을 했었는데 커밋이 쌓일수록 이전 커밋이 무슨 커밋인지 이해가 되지 않는 문제가 있었다. 이런 문제를 해결하기 위해 나름의 규칙으로 커밋 메시지 규칙을 정해 커밋을 한 적이 있었는데 공식적인 컨벤션들이 있는줄은 몰랐었다.
컨벤션의 필요성을 이미 느껴봤기때문에 실제 적용했을 때 불편하거나 하지는 않았고 오히려 "그래 이런게 필요했어!" 라는 생각을 했던 것 같다. 그리고 자바 코드 컨벤션은 포맷터가 있기 때문에 적용이 어렵지도 않았다. 하지만 git 커밋 메시지 컨벤션은 아직 조금 헷갈리는 부분이 있어서 익숙해져야 할 것 같다.
앞으로도 열심히 해서 최종 합격했으면 좋겠다! 👍