티스토리 뷰

우테코 서류 합격과 시작

우아한 형제들에서 지원하는 우아한 테크코스 교육과정의 4기에 서류합격이 되어 프리코스를 진행하게 되었습니다. 항상 테코톡이나 테코블을 통해서 "와... 교육이라도 이런 식으로 개발 교육을 받으면 얼마나 좋을까?"했던 우테코에 꿈인지 생시인지 서류합격을 하게 되었네요 :)

우테코의 심사 일정은 그냥 코테나 면접만 단시간 내로 진행하고 끝나는 것이 아닌 3주간의 미션이 있는데요. 원래도 코테나 면접은 개인피드백이 오지 않는 편이지만 3주동안 제가 이 프리코스를 통해서 얻고자하는 바가 있고, 또한 동일하게 미션을 진행하는 다른 지원자분들이 이 글을 보면서 제 회고록에서 배울점, 피드백할 부분을 공유할 수 있다고 생각해서 매주 회고를 남길 생각입니다.

서류를 쓸 때에 대한 회고와 첫 시작은 현재 글인 1주차 미션과는 무관하기 때문에 서론은 여기까지하고 최종 미션 후 전체 회고를 진행할 때 쓸까해요. 지금은 프리코스의 1주차인 [숫자 야구 게임]에 대해서 회고를 하려 합니다.

 

숫자 야구 게임

메일과 함께 시작된 첫 미션인 숫자 야구 게임이었습니다.

미션 저장소

미션이 담긴 저장소에서 git fork를 한 후 미션을 진행하고 pr을 날리는 방식으로 진행되었습니다.

흔히 알고 있는 숫자 야구게임, 숫자 3자리 정답을 맞추기 위해서 숫자 3자리를 계속 물어보고 동일한 숫자인지, 동일한 위치인지에 따라 스트라이크 볼을 지정하는 게임입니다.

 

기본적으로 1부터 9까지 서로 다른 수로 이루어진 3자리의 수를 맞추는 게임이다.

- 같은 수가 같은 자리에 있으면 스트라이크, 다른 자리에 있으면 볼, 같은 수가 전혀 없으면 포볼 또는 낫싱이란 힌트를 얻고, 그 힌트를 이용해서 먼저 상대방(컴퓨터)의 수를 맞추면 승리한다.
	예) 상대방(컴퓨터)의 수가 425일 때
		- 123을 제시한 경우 : 1스트라이크
		- 456을 제시한 경우 : 1볼 1스트라이크
		- 789를 제시한 경우 : 낫싱
- 위 숫자 야구 게임에서 상대방의 역할을 컴퓨터가 한다. 컴퓨터는 1에서 9까지 서로 다른 임의의 수 3개를 선택한다. 게임 플레이어는 컴퓨터가 생각하고 있는 3개의 숫자를 입력하고, 컴퓨터는 입력한 숫자에 대한 결과를 출력한다.
- 이 같은 과정을 반복해 컴퓨터가 선택한 3개의 숫자를 모두 맞히면 게임이 종료된다.
- 게임을 종료한 후 게임을 다시 시작하거나 완전히 종료할 수 있다.
- 사용자가 잘못된 값을 입력할 경우 IllegalArgumentException을 발생시킨 후 애플리케이션은 종료되어야 한다.
- 아래의 프로그래밍 실행 결과 예시와 동일하게 입력과 출력이 이루어져야 한다.

따로 프레임워크를 사용해서 개발하는 흔히 이야기하는 과제와 같은 형태라기보다는 최대한 자바를 활용해서 자바를 잘 사용하는 것이 주 목적인 과제라고 생각합니다.

 

미션에서의 프로그래밍 요구사항

1. 주어진 테스트코드 통과하기

  • 터미널에서 java -version을 실행해 자바 8인지 확인한다. 또는 Eclipse, IntelliJ IDEA와 같은 IDE의 자바 8로 실행하는지 확인한다.
  • 터미널에서 맥 또는 리눅스 사용자의 경우 ./gradlew clean test, 윈도우 사용자의 경우 gradlew.bat clean test 명령을 실행했을 때 모든 테스트가 통과하는지 확인한다.

마치 코딩테스트를 보는 것처럼 구현한 내용이 정확하게 요구사항을 맞추었는지 테스트 코드가 이미 작성되어있고 그걸 통과하여야 했습니다.

이를 위해서 랜덤한 값을 생성할 때, 입력을 받을 때 우테코측에서 제공되는 API를 사용해야했습니다 :)

 

2. indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현

클린코드를 관심있게 공부했었기 때문에 개인 연습용 프로젝트를 진행했을 때도 연습했던 내용이라 크게 어렵지는 않았어요.

indent가 3이상이 넘어가게 된다면 해당 메소드가 가져야할 역할이 여러가지가 된다라고도 볼 수 있기 때문에 indent가 넘어가는 경우, 즉 메소드가 여러가지 일을 하게 될 경우 해당 부분을 메소드로 다시 빼 한가지 일만 할 수 있도록 조정하였어요.

 

3. 3항 연산자를 쓰지 않는다.

3항 연산자라는 단어를 듣고 순간적으로 뭐지 할 수도 있었던 것 같아요. 아래 예시를 보면 바로 이거구나 할 수 있을 것 같습니다.

return result > 0 ? result : ( result * -1 );

이런 3항 연산자는 좋아하시는 분들도 있을 수는 있지만 보통 코드의 가독성을 떨어뜨리기 때문에 코드를 읽고 이해하는 시간을 추가적으로 소요하게 됩니다. 따라서 아래와 같이 if문을 직접 사용하는게 좋습니다.

if (result >0) {
	return result;
}
return result * -1;

코드는 길어졌지만 "가독성"이 올라간걸 볼 수 있어요.

 

4. 함수 (또는 메소드)가 한가지 일만 하도록 최대한 작게 만든다.

메소드와 함수를 쪼개다보면 항상 드는 생각이 있습니다.

아 이거 객체에서 몇몇 메소드만 사용해야하는데 굳이 다른 메소드를 가진 이 객체를 사용해야하나?

공부를 하다보면 메소드를 잘게 쪼개고 최대한 의미를 가지는 메소드를 만들게 되었을 때 새로운 객체로 쪼개야하는 신호라고 생각합니다.

잘개 쪼갠 메소드에서 얻는 이점은 해당 객체가 가져야할 책임이 너무 많지는 않은지, 몇몇 소수의 메소드를 따로 사용하는 경우가 생겼을 때 객체를 분리하기 쉽도록 하는 것에 있다고 생각합니다. 개인적으로는 이부분이 제가 잘 이해하고 사용하고 있는지 궁금해요 :)

 

미션을 진행하면서 개인적으로 고려했던 부분들

미션을 진행하기 전 특히나 3주간의 짧지만은 않은 기간 동안 합격과 불합격을 떠나서 무언가 얻어가고 싶었기 때문에 몇가지 제 습관들, 미션 요구사항에서 얻을 수 있는 개발 지식들을 최대한 얻어가려고 했습니다.

pr에도 남겨둔 내용이지만 조금 더 구체적으로 미션에서 이야기를 할까해요.

 

1. 요구사항 "잘" 분석하기

프리코스를 진행하기로하자마자 제일 먼저 고치고 싶었던 부분입니다.

마냥 코테 뿐만 아니라 실제 리얼월드 프로젝트를 개발하면서 분명히 담긴 요구사항이지만 여기서 좀 더 고려할 수 있지 않을까?, 이건 불분명한 값이니까 여기에서 또 검증해야하지않을까?를 너무 떠올리다보니 개발이 힘들었던 경험이 있거든요.

하지 않아도 되는 도메인에서 해당 작업을 고려하게 되어서 굳이 시간을 쓰지 않아도되는 부분에서 검증하느라 며칠을 버린다거나, 검증에 검증을 하다보니 복잡한 로직이 되어버려 며칠을 버린다던가 하는것처럼요. 그렇다고 테스트를 다 돌리고 실제 상황처럼 api를 돌렸을 때 다시 고쳤던 때가 적지 않아서 더 고민이었어요.


프리코스는 구현을 해야할 기능이 주어져있고 그 기능을 구현하는 건 온전히 지원자의 몫이기 때문에 오버프로그래밍하거나, 부족한 요구사항을 만들지 않는 연습을 할 수 있다고 생각했어요.

그래서 사실 실제 코드를 짜는 시간도 많은 시간이 소요되기는 했지만 첫 미션부터 빡빡하게 습관을 고치면서 연습을 해보자 해서 미션 진행 시간의 거의 3-40%가량을 요구사항을 어떻게하면 더 뺄까를 생각했던 것 같아요.

그나마 깨끗하게 정리했던 요구사항 생각정리

 

제출하였을 때 TODO List로 제출한 목록은 다음과 같습니다.

# 미션 - 숫자 야구 게임⚾️

## ⌨️ 입력

#### 1. Number

- [예외] 0~9의 범위를 벗어난 수가 들어올 경우 IllegalArgumentException이 발생해야 한다.

#### 2. Numbers

- [예외] 4개이상의 수가 입력될 경우 IllegalArgumentException이 발생해야 한다.
- [예외] 입력된 숫자 중 중복된 숫자가 존재할 경우 IllegalArgumentException이 발생해야 한다.

#### 2-1. Numbers Matching

- 숫자가 위치한 곳에 해당 수가 존재하는 만큼 더해서 Strike수를 반환할 수 있다.
- 숫자가 위치한 곳에는 없지만 다른 곳에 해당 수가 존재하는 만큼 더해서 Ball수를 반환할 수 있다.

<br>

#### 3. Computer

- `camp.nextstep.edu.missionutils`에서 제공하는 `Randoms`API를 사용해 값을 저장한다.
- `Randoms` API에서 이전에 입력한 숫자를 입력할 경우 다시 입력하도록 요청한다.
- 입력된 Numbers를 가지고 Answer와 매칭하여 GameResult를 반환할 수 있다.
- GameResult는 3 스트라이크일 경우 게임이 종료됨을 판단한다.
- [예외] GameResult의 strike count와 ball count의 합이 3을 넘는다면 IllegalArgumentException이 발생해야 한다.
- GameResult가 종료되었다면 게임 재시작에 대한 명령을 시작해야 한다.
  - 게임을 종료하였을 때 1을 누르면 게임이 재시작되어야 한다.
  - 게임을 종료하였을 때 2를 누르면 Application이 종료되어야 한다.

<br>

## 🖥 출력

#### 5. InputView

- `camp.nextstep.edu.missionutils`에서 제공하는 `Console` API를 사용해 값을 입력받는다.
-  숫자를 입력받을 때 다음과 같은 입력창을 출력한다.

```
숫자를 입력해주세요 :
```

- 게임이 종료되면 재시작, 종료를 묻는 입력창을 출력하다.

```
게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요.
```

#### 6. OutputView

- 맞은 개수에 따라 스트라이크, 볼, 낫싱 등과 같이 출력한다.

```
1볼 1스트라이크
3스트라이크 
낫싱
```

- 3개의 숫자를 모두 맞출 경우 다음과 같이 출력한다.

```
3개의 숫자를 모두 맞히셨습니다! 게임 종료
```

사실 아직까지도 요구사항에 욕심을 많이 빼지 못했고, 조금 더 고려했어야하는 부분들, 놓친 부분들이 있지만 첫 미션에서부터 완벽하게 습관을 끊을 수 없다라고 생각하고 있어요. 차근차근 3주동안 잘 만들어나가게 되었으면 좋을 것 같아요 :)

 

2. 객체 분리하기

요구사항이 너무 과하면 너무 많은 고려점이 생겨서 개발할 때 오히려 이상한 방향으로 흘러가게 되고, 기능 요구사항에서 각각의 도메인 객체가 어떤 것들이 생성되어 어떤 책임을 가져야할 지를 많이 생각하면서 설계했어요.

첫 미션에서는 함수(메소드) 분리가 메인이었지만 이 부분을 객체가 가져야할 메소드까지 생각하면서 고려하려고 했어요. 결국 메소드를 가지는건 각 객체가 가지는 부분이고 메소드 또한 내부에서 한가지의 일만 하도록하는, 객체와 조금 비슷하다고 생각했기 때문인 것 같아요.

 

하나의 요청사항에서 숫자를 3개가 필요한데, 이 숫자가 항상 1~9사이의 수가 되어야하기 때문에 Number라는 객체, 이 Number라는 객체가 3개가 모여 앞서 이야기한 요청에 필요한 Numbers 일급 컬랙션을 가지게하자. 라는 흐름대로 설계를 진행했던 것 같아요.

최대한 의미가 명확한 책임이 있는 경우 객체가 충분히 분리되어야 하나의 클래스가 모든 책임을 맡아 너무 뚱뚱한 설계가 되는 것을 막을 수 있다고 생각했기 때문이에요. (Application.java 안에 모든 책임이 다 들어가 모든 코드가 있으면 유지보수하기 힘들어지겠죠.)

Number같은 경우 1~9 사이의 수가 들어오지 않으면 예외처리, Numbers같은 경우 중복된 수 혹은 3자리가 아닌 수가 들어올 경우 예외처리를 해야하는 분명한 책임이 있어 사실 어렵지는 않았던 것 같아요. 다만 문제는 Computer에서 발생했었습니다.

 

게임을 실행하고, 정답 숫자를 생성하고 이를 비교해서 정답확인, 게임 종료시 재시작 혹은 완전 종료를 담당하는 역할을 처음에는 Computer가 담당하도록 했었어요. 더 책임을 분리할 수 있을까를 계속 고민해서 ComputerGame을 생성하는 역할만 가지고 Game에서 정답 저장, 정답확인을 하고 Computer는 게임 생성, 게임 종료, 게임 재시작의 역할만 가지면 어떨까?하고 말이죠.

그러다보니 거의 몇시간동안 코드 고민만 하게되고, 정작 구현도 중요한데 중요한걸 놓치는 것 같아 너무 과하게 분리를 하기보다는 먼저 구현을 하고 책임이 많이 있다 싶으면 분리하기로 했던 것 같아요.

그래서 현재와 같이 Computer 객체가 정답을 알고, 정답 확인을 해주고, 게임 상태(진행, 종료, 재시작)을 가져 게임 상태에 따라서 재시작 종료 여부를 알게끔 설계를 진행했어요.

    ├── main
    │   └── java
    │       └── baseball
    │           ├── Application.java
    │           ├── domain
    │           │   ├── computer
    │           │   │   ├── Computer.java
    │           │   │   ├── GameResult.java
    │           │   │   └── GameState.java
    │           │   └── number
    │           │       ├── BaseBallNumber.java
    │           │       └── BaseBallNumbers.java
    │           ├── exception
    │           │   ├── computer
    │           │   │   ├── ComputerEndStateRefreshException.java
    │           │   │   ├── GameResultNegativeCountException.java
    │           │   │   ├── GameResultTotalCountException.java
    │           │   │   └── GameStateNotFoundException.java
    │           │   └── number
    │           │       ├── BaseBallNumberRangeException.java
    │           │       ├── BaseBallNumbersDuplicateException.java
    │           │       └── BaseBallNumbersInputSizeException.java
    │           └── view
    │               ├── InputView.java
    │               └── ResultView.java
    └── test
        └── java
            └── baseball
                ├── ApplicationTest.java
                └── domain
                    ├── computer
                    │   ├── ComputerTest.java
                    │   ├── GameResultTest.java
                    │   └── GameStateTest.java
                    └── number
                        ├── BaseBallNumberTest.java
                        └── BaseBallNumbersTest.java

Computer 도메인에서는 게임 실행을, Number 도메인에서는 숫자를 저장하고 들어온 숫자를 비교하는 객체들이 담겨있어요. 굳이 지금 생각해본다면 Computer에 Number도 들어가있어도 되지 않을까 라고 생각이 듭니다. (오버프로그래밍한 부분을 한가지 찾았네요..)

더불어 각각의 exception을 커스텀하게 만들어서 각 exception 클래스 네임만 보더라도 어떤 역할을 가진 exception인지 알수 있도록 했어요. Exception 자체를 각각의 객체에서 생성하게 되었을 때 어떤 exception인지 알아보기가 힘들때가 많아서 지금과 같은 방법을 찾아보고 사용하였습니다 :)

 

3. 커밋 단위 세분화하기

과제 요구사항에서는 Git 커밋 단위라는 이야기가 나왔었어요.

사실 커밋 단위를 세분화할수록 개발과정에서 문제가 생기는 지점에 대해서 수정이 너무 편했던 기억이 있어 이번에도 이걸 더 연습해보자해서 신경썼던 부분이었어요.

특별하게 다양한 커밋 단위를 사용하기보다는 컨벤션을 보면서 제가 사용할 주요 커밋 컨벤션을 정리해서 어떤 상황에 어떤걸 사용해야하는지를 고려했던 것 같아요.

feat : 새로운 기능 추가
fix : 에러 수정
docs : 문서, 요구사항 수정
style : 코드 포멧팅, 로직은 변경되지 않았지만 코드 띄워쓰기, 세미콜론 누락과 같은 변경점
refactor :코드 리팩토링
test : 테스트 추가

refactor에 대한 부분이 어느 정도까지 적용해야할까?에 대한 고민이 아직까지 남아있기는 해요. 단순히 "리팩토링"이라는 단어만으로는 형용할수있는 범위가 너무 넓어서 로직은 그대로지만 코드 내부 변경점이 있을 때 refactor를 사용하였어요.

 

생각 정리

미션을 진행하고 메일로 친절히 1주차 내용에 대한 피드백을 주셨었어요 :) 사실 대외비인지의 여부를 판단하기 힘들어 피드백 내용을 모두 공개하지는 않았지만 제가 지금까지 고민하고 생각했던 부분들에 대한 몇가지 정답과 같이 나와있어서 사이다를 마신 느낌이 들어요.

그 중 가장 인상 깊었던 내용 중 하나는 "기능 목록을 업데이트하라" 였어요.

사실 기능 목록을 무조건 처음에 100% 구현해내려고 했지만 사실 알고보면 완벽하게 정리해야한다는 부담을 가지기보다는 기능을 구현하면서 계속 문서를 업데이트해서 "죽은 문서"가 아닌 "살아있는 문서"를 만들어라 라는 내용이 너무 가슴속에 와닿았기 때문인 것 같아요.

README.md 파일에 작성하는 기능 목록은 기능 구현을 하면서 변경될 수 있다. 시작할 때 모든 기능 목록을 완벽하게 정리해야 한다는 부담을 가지기보다 기능을 구현하면서 문서를 계속 업데이트한다. 죽은 문서가 아니라 살아있는 문서를 만들기 위해 노력한다.

미션을 진행하면서 수정된 기능 목록도 분명히 있지만 100%를 좋아하는 제 성격상 완벽하게 요구사항을 구현해내야한다라는 부담감이 없지않아있었고 3주동안 해낼 수 있을까를 조금 두려워했는데 잠시 내려놓고 "살아있는문서!"를 만들어야 한다는 생각을 할 수 있었어요.

다른 피드백들도 모든게 도움이 되는 이야기들이었지만 특히나 미션기간 내내 고민했던 내용이 담긴 피드백이어서 한눈에 기억에 담긴 것 같아요 :)

 

1주차 미션도 재미있는 고민거리와 개발 기능들로 마무리를 지었는데 2주차 미션도 또다른 제 고민거리를 만들어 줄 무언가가 있는지 기대가 돼요. 다음 글은 2주차 미션을 진행하고 난 후 다시 회고록으로 돌아오겠습니다 :)

 
댓글
최근에 올라온 글
Total
Today
Yesterday