우테코 2주차 미션 숫자 야구 게임이 끝이 났다.

처음에 미션을 봤을 때는 생각보다 문제의 난이도가 쉬운 것 같았지만 패키지 구조, 메서드 분리, 네이밍 등을 신경쓰고 기능별로 커밋을 하면서 코드를 짜다보니 고민도 많이 하게 되고 문제를 어떻게 푸냐에 따라 스스로 난이도를 높여가면서 훈련하는 기분도 들었다.

 

규칙

숫자 야구 게임은 1 ~ 9 사이의 서로 다른 수로 이루어진 3자리 수를 맞추는 게임이다.

  1. 컴퓨터가 1 ~ 9 사이의 서로 다른 랜덤한 숫자 3자리를 생성한다.
  2. 플레이어는 1 ~ 9 사이의 서로 다른 숫자 3자리를 입력한다.
  3. 컴퓨터가 생성한 숫자와 플레이어의 숫자를 비교한다.
  4. 같은 수가 같은 자리에 있으면 스트라이크, 다른 자리에 있으면 볼, 같은 수가 전혀 없으면 낫싱이란 힌트를 얻는다.
  5. 힌트를 이용해서 컴퓨터의 숫자를 맞추면 승리한다.
    • 예) 상대방(컴퓨터)의 수가 425일 때
      • 123을 제시한 경우 : 1스트라이크
      • 456을 제시한 경우 : 1볼 1스트라이크
      • 789를 제시한 경우 : 낫싱

 

요구 사항

  • indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다.
  • 함수(또는 메서드)가 한 가지 일만 하도록 최대한 작게 만들어라.
  • JUnit 5와 AssertJ를 이용하여 본인이 정리한 기능 목록이 정상 동작함을 테스트 코드로 확인한다.
  • 사용자가 잘못된 값을 입력할 경우 IllegalArgumentException을 발생시킨 후 애플리케이션은 종료되어야 한다.

 

라이브러리

camp.nextstep.edu.missionutils에서 제공하는 Randoms 및 Console API를 사용하여 구현해야 한다.

  • Random 값 추출은 pickNumberInRange()를 활용한다.
  • 사용자 입력값은 readLine()을 활용한다.

 

먼저 가장 큰 고민은 게임의 핵심으로 1~9 사이의 서로 다른 숫자 세자리에 대한 검증 처리였다. 어떻게 하면 깔끔하고 확실하게 예외 처리를 할 수 있을까 고민을 하다가 우테코를 시작하기전에 한번 봤었던 일급 컬렉션이 생각나서 다시 찾아보았다.

 

 

 

일급 컬렉션 (First Class Collection)의 소개와 써야할 이유

최근 클린코드 & TDD 강의의 리뷰어로 참가하면서 많은 분들이 공통적으로 어려워 하는 개념 한가지를 발견하게 되었습니다. 바로 일급 컬렉션인데요. 왜 객체지향적으로, 리팩토링하기 쉬운 코

jojoldu.tistory.com

 

일급 컬렉션은 컬렉션을 Wrapping 하면서, 그 외 다른 멤버 변수가 없는 상태를 말하는데 이에 대한 이점은 비즈니스에 종속적인 자료구조, Collection의 불변성, 상태와 행위를 한 곳에서 관리 등이 있다.

위와 같이 야구 게임의 숫자를 Wrapping하고 생성할때 검증을 거치면 이후 서비스 로직에서는 생성만 하면 된다. 그리고 테스트 코드를 통해 검증을 테스트할 수 있었다.

이렇게 게임 숫자를 객체로 보고 클래스를 만듦으로써 코드가 흝어지지 않고 한 곳에서 관리가 되면서 책임을 분리할 수 있게 되었다.

처음에 미션을 봤을 때는 생각보다 문제의 난이도가 쉬운 것 같았지만 패키지 구조, 메서드 분리, 네이밍 등을 신경쓰고 기능별로 커밋을 하면서 코드를 짜다보니 고민도 많이 하게 되고 리펙토링을 하다보면 스스로 난이도를 높여가면서 훈련하는 기분도 들었다.

 

 

위와 같이 야구 게임의 숫자를 Wrapping하고 생성할때 검증을 거치면 이후 서비스 로직에서는 생성만 하면 된다. 그리고 테스트 코드를 통해 검증을 테스트할 수 있었다.

 

 

이렇게 게임 숫자를 객체로 만듦으로써 코드가 흝어지지 않고 한 곳에서 관리가 되면서 책임을 분리할 수 있었다. 

 

게임에 대한 판정은 Umpire 클래스로 따로 분리했는데 가독성을 높이기 위해 고민을 많이 했었다. 자연스럽게 코드를 읽으면서 이해가 되도록 하다보니 playerPeek과 computerPeek이라는 변수를 선언했는데 gameCount 네이밍을 잘 했다면 변수 선언을 따로 안 해도 됐을거 같기도 하고.. 역시 어려운 것 같다.

 

 

게임 결과도 처음에는 리스트로 반환을 했지만 어떤 인덱스가 ball인지 스트라이크인지 헷갈릴 것 같아서 ball과 strike 정보를 담은 GameResult라는 클래스를 생성하였다. 이렇게 역할에 따라 클래스로 세분화를 해서 서비스 로직에서는 간단하게 흐름을 연결하는 식으로 작성을 했다.

 

 

이번 미션은 예외 처리와 테스트 코드에 대해서 아쉬움이 많이 남는데 코로나에 걸려서 몸 상태가 너무 안 좋았다. 4일 가량을 거의 누워 지내다가 30분 코딩하고 1시간 눕고를 반복해서 겨우 제출을 했는데 그래도 미리 좀 열심히 해놓았던게 정말 다행이라 생각했다. 3주차 미션이 시작하고 이틀이 지나서야 몸 상태가 돌아왔는데 지난 미션들의 공통 피드백과 이번 미션에 집중해야 될 부분을 한번 보면서 정리하고 3주차 미션은 열심히 해봐야겠다.

 

Mockito를 사용해서 테스트를 할 때 ArgumentMatchers를 사용하면 유연한 검증을 할 수 있다.

 

ArgumentMatchers는 반환 값으로 사용할 수 없고 검증이나 스터빙 외에는 사용할 수 없다.

 

 

특정 값을 확인할 필요가 없는 경우 파라미터 타입에 맞게 anyLong(), anyString() 등을 대입하면 된다.

 

하지만 메서드에 파라미터가 둘 이상인 경우에 일부 파라미터에만 ArgumentMatchers를 사용할 수 없다. 

 

위와 같이 테스트를 실행하면 Invalid use of argument natchers 에러가 뜬다.

 

 

 

이럴 때는 특정 값을 입력하는 파라미터에 eq matcher를 사용해서 "test" 대신 eq("test") 식으로 입력한다.

 

 

 

 

 

 

 

String을 '+' 연산자로 붙이는 경우 계속 해서 새로운 인스턴스가 생겨서 성능 저하 문제로 문자열을 붙여야 되는 경우 StringBuilder, StringBuffer 를 사용하는데 조그만 부분에도 '+' 연산자를 쓰지 않는 것이 좋나 싶어서 찾아보니 '+' 연산자를 사용해도 내부적으로 StringBuilder를 사용한다고 한다.

 

 

다만, 문자열을 한번에 더하는 것이 아니라 계속해서 더하는 경우 '+' 는 StringBuilder 인스턴스를 새로 생성하기 때문에 성능 저하가 생기는 것이 맞다. for문을 돌리거나 계속해서 문자열을 더하는 경우가 아니면 '+' 연산자를 사용!

 

 

 

 

 

[참고]

 

String은 항상 StringBuilder로 변환될까?

JDK 1.5 버전 이후부터, String 의 + 연산은 컴파일시에 StringBuilder 를 사용하도록 자동변환되어 성능 최적화가 이뤄진다고 알려져 있습니다. 그러나 String은 항상 StringBuilder로 변환되지 않습니다. Stri

siyoon210.tistory.com

static import를 사용하면 static 메서드를 사용할 때나 이너 클래스를 사용할 때 클래스명을 생략할 수 있다. 잘 활용하면 테스트 코드를 작성할때 가독성을 올려준다거나 하는 이점도 있지만 패키지 구조를 다 아는 내 눈에만 예뻐 보일 수 있겠다는 생각이 들었다.

 

오히려 가독성을 떨어트리고 헷갈리게 할 수 있기 때문에 전역적으로 사용되고 static import를 해도 이해하기 쉬운 부분에 사용하는 것이 좋을 것 같다.

 

 

언제 사용하면 좋은지 해당 글에서 보았다.

 

Static Import

In order to access static members, it is necessary to qualify references with the class they came from. For example, one must say: double r = Math.cos(Math.PI * theta); In order to get around this, people sometimes put static members into an interface and

docs.oracle.com

 

when should you use static import? Very sparingly! 

use it when you require frequent access to static members from one or two classes.

 

아주 드물게 사용해야 한다고 강조하고 있다!

 

If you overuse the static import feature, it can make your program unreadable and unmaintainable, polluting its namespace with all the static members you import. Readers of your code (including you, a few months after you wrote it) will not know which class a static member comes from. Importing all of the static members from a class can be particularly harmful to readability

대충 남용하면 가독성 떨어지고.. 유지 보수 어렵고.. 네임스페이스 오염시키고.. 코드 읽는 사람이 헷갈리고.. 등등 엄청 안좋다는 말

 

if you need only one or two members, import them individually. Used appropriately, static import can make your program more readable, by removing the boilerplate of repetition of class names.

 

이전에 혼자 코드 짤 때 static import 하는게 깔끔한거 같아서 막 다 static import를 했었는데 다른 분이 짠 코드를 보다보니 static import가 안 되어있는 것이 보기에 이해가 더 잘 됐다. 그래서 이거 사용하는거 맞나 의문이 들었는데 찾아보길 잘한 것 같다.

 

 

 

 

[참고]

 

[Java] Static import에 대한 관찰

JDK5에서 Static import가 추가되었다. 먼저 static import를 적용하지 않은 일반 코드를 보자. 가장 기본적인 용법은 import문 뒤에 static을 붙이고, {패키지.클래스.\*} 혹은 {패키지.클래스.멤버} 를 적으면

velog.io

 

+ Recent posts