Skip to main content

우테코 1주차 후기

·5 mins· loading · loading ·
Soeun Uhm
Author
Soeun Uhm
problem-solving engineer, talented in grit.
Table of Contents

우테코의 첫번째 미션(숫자야구) 가 끝났다. 이번에는 구현, 리팩토링 과정, 아쉬운 점, 1주차 동안 공부한 것의 포스팅까지 작성해보았다.

첫번째 구현
#

숫자 야구 구현 기능 목록
#

  1. 게임 시작

    • 게임 시작 메세지 출력
  2. 컴퓨터가 1~9 사이 랜덤한 서로 다른 숫자 3개 생성

  3. 사용자 입력 받기

    • 조건 : 세 자리 서로 다른 숫자
    • 조건에 맞지 않는 입력이 들어오면 IllegalArgumentException 발생 후 프로그램 종료
      • 오류1. 숫자가 아닌 경우
      • 오류2. 3자리가 아닌 경우
      • 오류3. 서로 다른 세 숫자가 아닌 경우
      • 오류4. 1이상 9이하의 숫자가 아닌 경우 (=0이 포함된 경우)
  4. 숫자 야구 게임 실행

    • 실제 숫자 야구 게임 로직
      • 사용자 input, 컴퓨터가 생성한 숫자 모두 문자열로 변환
      • 문자열에서 자리 & 숫자 일치하면 strike++
      • 컴퓨터가 생성한 숫자 Set 이 사용자의 input 을 contain 하면 ball++
    • 게임 결과 출력
      • 결과1. ball == 0 && strike != 0 이면 “스트라이크” 의 개수만 출력
      • 결과2. strike == 0 && ball != 0 이면 “볼” 의 개수만 출력
      • 결과3. ball == 0 && strike == 0 이면 “낫싱” 출력
      • 결과4. ball > 0 && strike > 0 이면 “볼” , “스트라이크” 개수 둘 다 출력
    • 게임 결과를 바탕으로 정답 여부 판단
      • “3스트라이크” 메세지 출력시 게임 종료 메세지 출력
  5. 사용자가 게임을 계속할지 종료할지 결정하는 input 받기

    • input이 1 또는 2 가 아니면 IllegalArgumentException 발생 후 게임 종료
  6. Application 에서 게임 흐름 구현

    • startGame()
    • do-while 문을 사용해 userDecision 이 1이면 계속 playGame()
    • userDecision 이 1이 아니면 게임 종료

첫번째로 구현 시, 나는 흐름 별로 클래스를 나누었다. MVC 패턴은 생각하지 않고, 게임의 흐름 대로 구현했다. 아래 Application 코드를 보면 알 수 있듯이 난 ‘순서’ 위주로 프로그래밍을 했다. 절차 지향적 프로그래밍을 한 셈이다.

public class Application {  
  
  public static void main(String[] args) {  
    Scanner sharedScanner = new Scanner(System.in);  
  
    startGame();  
    String userDecision;  
  
    do {  
      playGame(sharedScanner);  
      userDecision = userDecision(sharedScanner);  
    } while (userDecision.equals("1"));  
  
    endGame();  
  }  
}

intellij 로 그려본 다이어그램은 아래와 같았다 .. 기능별로 나누긴 했다만, 여전히 하나의 메서드 안에 기능이 너무 많았다. 그리고 메세지를 출력하는 부분, 게임로직을 담당하는 부분도 전혀 분리하지 않았다.

image

1차 구현 완료하고, 테스트 코드를 작성했다. 테스트 코드는 ApplicationTest, InputTest, GameTest 3가지로 나누었다.

  • ApplicationTest
    • 게임 종료 후 재시작 작동 테스트
  • InputTest
    • 자리수 예외 테스트
    • 1이상 9이하 예외 테스트
    • 숫자가 아닌 문자 입력시 예외 테스트
    • 게임 종료 후 사용자 입력 예외 테스트
  • GameTest
    • 3스트라이크 테스트
    • 1볼2스트라이크 테스트
    • 1볼 테스트

리팩토링
#

리팩토링 때는 아래 사항들을 지키려고 노력했다.

  • 자바 코드 컨벤션을 지키면서 프로그래밍한다.
    • 기본적으로  Google Java Style Guide을 원칙으로 한다.
    • 단, 들여쓰기는 ‘2 spaces’가 아닌 ‘4 spaces’로 한다.
  • indent(인덴트, 들여쓰기) depth를 2가 넘지 않도록 구현한다. 1까지만 허용한다.
    • 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다.
    • 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메소드)를 분리하면 된다.
  • else 예약어를 쓰지 않는다.
    • 힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다.
    • else를 쓰지 말라고 하니 switch/case로 구현하는 경우가 있는데 switch/case도 허용하지 않는다.
  • 모든 로직에 단위 테스트를 구현한다. 단, UI(System.out, System.in) 로직은 제외
    • 핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 구분한다.
    • UI 로직을 InputView, ResultView와 같은 클래스를 추가해 분리한다.
  • 3항 연산자를 쓰지 않는다.
  • 함수(또는 메소드)가 한 가지 일만 하도록 최대한 작게 만들어라.

1. 함수는 하나의 기능만 하도록 !
#

이전까지 나의 메서드는 매우 길었다. 복잡도가 9인 것도 있었다. 그래서 메서드 하나가 딱 하나의 기능만 하도록 최대한 쪼개보았다. 예외 처리만 담당하는 메서드, 하나의 오류를 검증하는 메서드 ,메세지만 출력하는 메서드 등 딱 하나의 기능만 담당하도록 나누었다.

2. 메세지를 출력하는 View 클래스 분리
#

메세지 출력을 담당하는 View 클래스를 분리했다. 여기서 게임 메세지를 상수값으로 저장하고, 메세지를 출력했다.

3. stream 의 도입
#

처음에는 stream 에 익숙하지 않아 모두 for 문을 썼다. 그러나 데이터의 흐름을 제어할 수 있는 stream 이 훨씬 더 직관적이고, 코드 복잡도를 줄이면서 원하는 결과를 얻을 수 있었다. 따라서 strike 와 ball 을 계산 하는 부분에 모두 stream 을 썼다.

4. Enum 클래스 사용
#

GameMessage 들을 담아두는 Enum 클래스를 사용했다. 게임 메세지들은 모두 상수값이라고 생각했고, 추후에 게임 메세지 변경 시 Enum 클래스만 변경할 수 있으므로 리팩토링시 변경 범위가 최소화 된다. Enum 에 대한 내용은 따로 하나의 포스팅으로 공부하며 정리했다.

리팩토링 결과
#

리팩토링 후 다이어그램

image

아쉬운 점
#

이번 주는 시험 기간과 겹쳐서 일주일을 온전히 다 쓰지는 못했다. 그래서 제출 후에 아쉬움이 많이 남는다. 하지만 우테코가 무엇인가 ! 결국 나의 발전이 있으면 되는 것이다. 1주차 때는 비록 ‘정답’ 에서 많이 벗어난 것 같지만, 꾸덕꾸덕 열심히 하다보면 언젠가 해냈을 나를 믿는다.

1. java 의 객체지향을 활용하지 못함
#

우선적으로, java 의 객체지향을 제대로 활용하지 못했다. 모든 클래스를 public static 으로 썼다. 생성자를 만들지 않고 그냥 클래스이름.메서드 로 접근해서 사용했다. 클래스 메서드 / 인스턴스 메서드를 공부하고 다시 보니 정말 말도 안되는 구현이었다. 이 참에 java 의 객체 지향을 좀 더 단단하게 공부할 기회가 되어 좋았다.

2. MVC 패턴을 사용하지 못함
#

나는 View 만 분리했는데, 게임 로직을 담은 Model 과 View 를 합친 Controller 부분까지 구현한다면 훨씬 좋았을 것 같다. 1주차는 우선 내 생각으로 구현해보고 싶었기 때문에 디자인패턴을 안 쓰고 구현해보았다. 4주차 미션이 다 끝나고 디자인패턴, MVC 같은 개념에 좀 더 익숙해지면 다시 구현해볼 계획이다.

3. 모든 로직에 대한 Test case 를 쓰지 못함
#

TDD 라는 개념을 1주차 마지막에 처음 접했다. Test case 를 먼저 작성하고, Test case 에 따라서 프로그램을 구현하는 방식이다. 이렇게 하면 논리적 오류가 없게끔 보장하면서 리팩토링을 할 수 있다. 2주차 미션부터 TDD 방식으로 구현해볼 계획이다.

1주차 동안 공부한 포스팅
#