본문 바로가기
기타

우아한 테크코스 프리코스 회고

by 민죠미 2023. 3. 23.
작년 10월, 우아한 테크코스 5기 백엔드 과정에 지원을 했다. 1400명의 백엔드 지원자와 함께한 프리코스 과정을 지나 약 200명의 최종 코딩테스트까지 갔지만 아쉽게 떨어져 본 코스 과정을 밟지는 못했다. 프리코스 과정에서도 배운 것이 많았기에 늦게나마 기억을 저장하고자 기록한다. 

 

1주차 onboarding

https://github.com/MinjeongKong/java-onboarding/tree/MinjeongKong

 

GitHub - MinjeongKong/java-onboarding: 온보딩 미션을 진행하는 저장소

온보딩 미션을 진행하는 저장소. Contribute to MinjeongKong/java-onboarding development by creating an account on GitHub.

github.com

4기까지는 프리코스 이전에 알고리즘 테스트를 보는 것으로 알고 있는데, 5기부터는 지원자라면 모두 프리코스 과정을 경험할 수 있게 1차 코딩테스트를 없앴다고 한다. 때문에 이전까지는 프리코스 과제가 3주에 걸쳐 3번 진행되었는데, 이번부터는 4주에 걸쳐 4개의 과제를 수행해야 했다.

그 중 1주차 과제인 onboarding 은 예상컨데 이전 기수의 1차 코딩테스트 문제를 가져온 것 같았다. 1차 코딩테스트가 없어지니 지원자 중에선 학부 2학년이나 비전공자도 꽤 보였었는데, Git 경험이 없는 사람들에겐 미션 제출 자체로도 어려움이 있었을 것 같다.

프리코스 과제 가이드

https://github.com/woowacourse/woowacourse-docs/tree/main/precourse

 

GitHub - woowacourse/woowacourse-docs: 우아한테크코스 문서를 관리하는 저장소

우아한테크코스 문서를 관리하는 저장소. Contribute to woowacourse/woowacourse-docs development by creating an account on GitHub.

github.com

모든 과제는 위의 방식으로 제출하여야 했다. 나의 경우 쓰기 권한이 있는 외부 레포에 직접 브랜치를 생성하고 PR을 한 적은 있었으나, fork 한 레포를 통해 PR 을 해본 것은 처음이었다.

1주차에서 신경쓴 것은

  • 기능 요구사항에 따라 필요한 메서드를 정의하는 것
  • 하나의 메서드가 하나의 기능만을 수행할 것
  • 메서드 주석 써보기

평소 알고리즘 문제를 풀 때는 생각 나는 대로 main 에 구현하기 급했다면, 본 과제를 수행할 때는 요구사항을 수행하기 위해서 어떤 기능을 하는 메서드가 필요한지부터 생각하고 정리하는 과정을 거쳤다.

 


 

2주차 숫자 야구 게임

https://github.com/MinjeongKong/java-baseball/tree/MinjeongKong

 

GitHub - MinjeongKong/java-baseball: 숫자 야구 게임 미션을 진행하는 저장소

숫자 야구 게임 미션을 진행하는 저장소. Contribute to MinjeongKong/java-baseball development by creating an account on GitHub.

github.com

2주 차 미션에서는 1주 차에서 학습한 것에 더해 함수를 분리하고, 각 함수별로 테스트를 작성하는 것을 목표로 요구 사항이 아래와 같이 추가되었다.

추가된 요구 사항

  • indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다.
    • 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다.
    • 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메서드)를 분리하면 된다.
  • 3항 연산자를 쓰지 않는다.
  • 함수(또는 메서드)가 한 가지 일만 하도록 최대한 작게 만들어라.
  • JUnit 5와 AssertJ를 이용하여 본인이 정리한 기능 목록이 정상 동작함을 테스트 코드로 확인한다.
    • 테스트 도구 사용법이 익숙하지 않다면 test/java/study를 참고하여 학습한 후 테스트를 구현한다.

2주차 과제에서 신경쓴 것은 커밋 메세지 컨벤션과 도메인/서비스/컨트롤러의 분리이다.
도메인/서비스/컨트롤러를 분리하라는 요구사항은 있지 않았지만... 스프링으로 프로젝트를 할 때 하던 것을 순수 자바에서도 적용하고 싶었다. 기존 코드에는 Application.java 파일만 존재해서 무엇을 도메인 클래스로 정의해야할지 기능 요구사항을 한참 쳐다보았던 기억이 난다... 또한 스프링에서는 컨트롤러에서 api와 관련된 코드를 짰지만 이 과제에서는 컨트롤러와 서비스 코드의 경계가 모호하다고 느껴졌다.

결과적으론 컨트롤러에서 게임의 설정과 초기 셋팅, 게임의 실행 순서를 조작하고 서비스 코드에서 게임 실행에 필요한 동작이 이루어지는 방향으로 구현했다. View와 관련된 출력문들은 utils 패키지에 Message 클래스를 생성하여 static 함수로 처리하였다.

지금보면 도메인 간 책임분배면에서 실패하여 깔끔하지 못한 코드이지만 나름 요구사항에도 없던 시퀀스 다이어그램을 그려가며 설계하고자 애쓴 모습이 가상하다... 책임 분리에 관한 고민은 3주차 과제에서 이어진다.

 

 


 

3주차 로또

https://github.com/MinjeongKong/java-lotto/tree/MinjeongKong

 

GitHub - MinjeongKong/java-lotto: 로또 미션을 진행하는 저장소

로또 미션을 진행하는 저장소. Contribute to MinjeongKong/java-lotto development by creating an account on GitHub.

github.com

3주차 미션에서는 1. 클래스(객체)를 분리하는 연습, 2. 도메인 로직에 대한 단위 테스트를 작성하는 연습을 목표로 요구사항이 추가되었다.

추가된 요구 사항

  • 함수(또는 메서드)의 길이가 15라인을 넘어가지 않도록 구현한다.
    • 함수(또는 메서드)가 한 가지 일만 잘 하도록 구현한다.
  • else 예약어를 쓰지 않는다.
    • 힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다.
    • else를 쓰지 말라고 하니 switch/case로 구현하는 경우가 있는데 switch/case도 허용하지 않는다.
  • Java Enum을 적용한다.
  • 도메인 로직에 단위 테스트를 구현해야 한다. 단, UI(System.out, System.in, Scanner) 로직은 제외한다.
    • 핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 분리해 구현한다.
    • 단위 테스트 작성이 익숙하지 않다면 test/java/lotto/LottoTest를 참고하여 학습한 후 테스트를 구현한다.

2주차 과제를 하면서 기능구현에 필요한 함수가 어느 클래스에 있어야 맞는 것일지에 대한 고민을 많이 하였다. 실제로 완성한 코드를 보면서도 어쩐지 깔끔하지 못하다는 생각이 들었다. 해답을 찾기위해 조영호 저자의 오브젝트 를 읽으며 3주차 과제를 진행하게 되었다.

책을 읽으면서 2주차 과제 코드에서 ‘깔끔하지 못하다’ 라는 느낌을 받게된 원인이 책임 분배의 실패에 있다고 생각하게되었다. 책 내용에 따르면 한 도메인이 기능의 책임을 많이 가지고 있기 보다는 객체마다 자신이 아는것(private 인스턴스)에 대해서 스스로 책임을 지고 자신이 필요한 정보를 가진 객체에 대해서는 최소한의 인터페이스로 의존하며 협력하는 구조를 가져야한다고 말한다.

이와 같이 최소한의 정보만 객체간의 인터페이스를 통해 접근 가능하게 하고, 이 외의 정보를 은닉하는 것이 캡슐화이며 이를 통해 객체간 의존성을 낮추고, 응집성을 높여 변경하기 쉬운 객체를 만드는 것이 객체지향적 프로그래밍임을 다시 파악하게 되었다.

2주차 과제에서는 GameService 클래스에 역할이 집중되어 Game 도메인을 통해 User, Computer 까지 get으로 접근하는 등 의존성이 높아졌다고 생각되었다. 3주차 과제에서는 도메인이 스스로의 역할을 책임지고, 이를 테스트코드로 작성하여 확인하는 것을 목표로 하였다.

도메인 객체에서 중심 기능을 담당하는 메서드는 public 으로 선언하여 윗 계층(Service, Controller) 객체에서 접근하는 인터페이스로 설계하였고 이 메서드가 동작하기 위해 필요한 작은 기능의 메서드들은 private 으로 외부에서 파악할 수 없게 캡슐화 하였다. 이 때 기능을 수행하기 위해 필요한 다른 객체의 정보는 인스턴스로 의존성을 가질지 파라미터로 의존성을 가질지 고민하며 구현하였다.

유효성 검사의 경우 Lotto, User 도메인 안에서 validate 메서드를 조건마다 세분화 하여 만든 뒤 테스트 코드를 작성하였다. 이후 Winning 도메인에서도 같은 조건으로 유효성 검사를 해야한다고 느껴 코드 재사용을 위해 Valid 클래스를 생성하고 유효성 검사를 하는 메서드들을 리팩터링 하였다. 이 때 리팩터링이 올바르게 되었는지 기존에 만든 User 테스트 코드를 돌려 확인하는 과정에서 ‘도메인 로직에 대한 단위 테스트를 작성’ 의 필요성을 알게 되었다.

Enum 타입 사용에 있어서는 간단하게 사용을 해본 경험은 있으나 Enum 생성자를 만들거나 클래스 내 메서드를 작성해본적은 없어서 남궁성 저자의 ‘JAVA의 정석’ 을 읽으며 Enum에 대해 학습하였다.

테스트 코드 작성에 있어서 assertThat 이나 assertThatThrownBy 등은 익숙하였지만 camp.nextstep.edu.missionutils.test 패키지에서 제공하는 함수에 대해선 제공된 ApplicationTest 코드를 보며 이해한 뒤 사용하였다. 패키지 내에 사용된 실제 JUnit 함수에 대한 이해는 추가 공부가 필요하다고 느꼈다. 평소 기능 목록을 작성하고 기능에 따라 클래스를 분리하는 데 있어서 막막함을 느끼는 편이었는데 본 과제를 수행하며 재미를 느끼게 되었다.

 


 

4주차 다리 건너기

https://github.com/MinjeongKong/java-bridge/tree/MinjeongKong

 

GitHub - MinjeongKong/java-bridge

Contribute to MinjeongKong/java-bridge development by creating an account on GitHub.

github.com

4주차 미션에서는 1. 클래스(객체)를 분리하는 연습, 2. 리팩터링을 목표로 요구사항이 추가되었다.

추가된 요구 사항

  • 함수(또는 메서드)의 길이가 10라인을 넘어가지 않도록 구현한다.
    • 함수(또는 메서드)가 한 가지 일만 잘하도록 구현한다.
  • 메서드의 파라미터 개수는 최대 3개까지만 허용한다.
  • 아래 있는 InputView, OutputView, BridgeGame, BridgeMaker, BridgeRandomNumberGenerator 클래스의 요구사항을 참고하여 구현한다.
    • 각 클래스의 제약 사항은 아래 클래스별 세부 설명을 참고한다.
    • 이외 필요한 클래스(또는 객체)와 메서드는 자유롭게 구현할 수 있다.
    • InputView 클래스에서만 camp.nextstep.edu.missionutils.Console  readLine() 메서드를 이용해 사용자의 입력을 받을 수 있다.
    • BridgeGame 클래스에서 InputView, OutputView 를 사용하지 않는다.

3주차 과제 코드 리뷰 중 불필요한 throws를 명시했음을 알게되었다. 큰 고민없이 IllegalArgumentException 을 컨트롤러 단까지 throws 하여 catch 할 생각이었는데 언체크 예외임을 뒤늦게 파악하였다. 기본적인 개념임에도 스스로 자바 문법이 약하구나 느끼게 되었고, ‘모든 코드에는 이유가 있어야 한다’ 는 오브젝트 책 내용이 생각나 반성하게 되었다. 이후 4주차 과제를 진행하면서는 최대한 확실하게 알고 있는 것이 아니면 구글링을 습관화 하도록 노력하였다.

기능 목록 정의 중 IllegalArgumentException 과 IllegalStateException 의 차이를 몰라 찾아보았다. 간단히 이해한 바로는 IllegalArgumentException 은 주로 입력을 받을 때 입력받은 데이터가 유효하지 않은 데이터일 경우에 발생시키며 IllegalStateException 는 코드상에서 적절하지 않은 때에 메서드를 호출했거나, 조회한 데이터가 없을 경우 등에서 발생시키는 것이었다. 따라서 InputView 클래스 내에 입력받을 시엔 유효성 검사를 통해 IllegalArgumentException 를 발생시켰으며, IllegalStateException 의 경우 Pos 클래스 내 0, 1 값에 따라 방향 문자를 조회하는 메서드에서 0, 1 외의 값을 파라미터로 받을 경우 발생시켰다.

스스로 MVC 패턴에 나름 익숙한 상태라고 생각했으나 과제에 제시된 도메인 클래스에 맞춰 기능을 짜는 것이 생각보다 쉽지 않았다. 평소라면 초반에 큰 고민없이 일단 기능을 짜놓고 리팩터링 하면서 역할에 대한 고민을 하였을텐데, 이미 역할의 분배가 틀이 짜여진 상태에서 객체들을 조합하는 것은 꽤나 낯설었다.

하지만 확실하게 먼저 대략적인 역할 정리를 해두고 나니, 테스트 코드를 짜는게 쉽게 느껴졌다. 뷰와 모델이 서로 의존관계를 맺지 않고 컨트롤러에서 둘을 협력시키다보니 코드 수정에도 용이하고 테스트 코드도 간결하다는 느낌을 받았다.


 

그 이후

https://github.com/MinjeongKong/java-menu/tree/MinjeongKong

 

GitHub - MinjeongKong/java-menu

Contribute to MinjeongKong/java-menu development by creating an account on GitHub.

github.com

최종 코딩 테스트까지 완료했지만 아쉽게 붙지는 못했다. (테스트 코드를 못쓰고 나와서 그런 것 같음..ㅎㅎ)

4주간의 과정을 통해 스스로 기능목록을 정의하고 그에 맞춰 설계하는 법, 객체스러운 코드, 클린 코드에 대해 실전적으로 배울 수 있어 좋은 경험이었다. 무엇보다 같은 과제를 여럿이서 함께 수행하면서 서로 지난 과제 코드를 피드백하고, 과제 중에 생긴 고민을 토론하고, 스터디를 통해 새로운 지식을 공유하는 한 달의 과정이 뜻깊었다.

 

 

댓글