우테코 5기 프리코스 4주차 회고

우테코 프리코스 4주차 회고

마지막 미션(ㅠㅠ)이기 때문에 지금까지 배웠던 것을 잘 적용하기 위해 코드 구현 시간과 비슷한 정도로 리팩터링에 시간을 많이 쓴 거 같다. 똑같은 걸 보고 또 보고.. 또 봤기 때문에 머리가 터질뻔 했다ㅋㅋ 하지만 그 반복된 시간 속에서 클린 코드라는 좋은 친구를 한 명 얻게 되어 너무 보람찬 시간이었다. 😄 (나만 친구라 생각하는 거 아니지..?)

🧑‍🤝‍🧑 앞으로의 피어 리뷰?

몇 주간 계속된 피드백과 리뷰로 이제 피어 리뷰가 큰 의미가 있을까라는 나의 오만한 생각을 부숴준 우리 스터디원들.. 너무 감사합니다!

이번 미션이 마지막으로 프리코스 마지막 후기가 될 것 같아 이번에는 어떤 피드백을 받았다는 단편적인 피드백을 적기보다는 피어 리뷰를 하면서 직접 느낀 앞으로도 꾸준히 피어 리뷰가 필요한 이유를 적어보겠다.

잘못된 습관 Catch 혹은 실수 리마인드

어떠한 잘못된 습관이나 실수 같은 경우는 내가 아무리 코드를 계속 보더라도 발견하지 못할 확률이 높다. 이러한 것을 리뷰어 입장에서는 쉽게 발견할 수 있으며 리뷰를 남겨줄 수 있다. 그리고 내가 또 어느 곳에서 자주 실수하는지도 리마인드 해주는 역할도 할 수 있다.

코드나 혹은 로직의 더 좋은 개선 방법 제공

내가 짠 코드나 로직의 경우 이미 설계 단계에서부터 머리에 각인이 되어버리기 때문에 더 좋은 개선 방법이 있더라도 잘 발견하지 못한다. 그래서 코드에 대해 열린 마음을 가지고 있는 리뷰어가 더 좋은 방법을 제공하는 경우도 많다.

다른 분들의 좋은 코드 흡수

다른 분들에게 리뷰를 받는 것 말고도 다른 분들의 코드를 읽고 리뷰를 하는 것만으로 상당한 경험치가 쌓인다. 각자마다 설계한 방법, 로직, 코드들이 다 다르기 때문에 내가 생각지도 못한 방법들이 있을 수 있다. 커비가 한번 되어보자 흡!

📄 3주차 공통 피드백

2, 3주 차 공통 피드백에 테스트 코드에 대한 이야기가 많았다. 지금까지 테스트 코드를 짜면서 진행하긴 했지만 뭔가 빈약하게 짠 거 같은 느낌이 들어 이번에는 테스트 코드도 더 신경을 써보자 생각하고 미션을 진행했다.

테스트 코드 커버리지

위 화면처럼 테스트 패키지를 오른쪽 클릭 -> More Run/Debug -> Run Test with Coverage를 클릭하면 아래 화면처럼 자기가 작성한 코드에 대한 테스트 커버리지가 나오게 된다.

이번 미션에서는 테스트 커버리지를 100%로 맞춰보자고 마음먹었었는데 달성하여 제출하게 되어 기뻤다! 하지만 문뜩 이런 생각이 들었다. 실제로도 테스트 커버리지가 100%가 될 수 있을까 그리고 그렇게 되면 완벽한 건가?

실제로 토스에서 테스트 커버리지 100%를 1년 6개월 동안 유지해본 경험이 공유된 영상을 보게 되었고 궁금증을 시원하게 해결해주었다.

높은 테스트 커버리지의 이점

  • 자신있게 누를 수 있는 배포 버튼
  • 거침없는 리팩토링
  • 불필요한 프로덕션 코드 제거
  • 프로덕션 코드에 대한 이해도 상승
  • 점점 쉬워지는 테스트 작성

높은 테스트 커버리지를 유지할 때 단점

  • 느려지는 테스트 -> 생산성이 떨어짐
    • 느려지는 원인 찾아 해결 가능 ( ex)스프링 애플리케이션 컨텍스트 로딩, 불필요한 로깅 설정 제거.. 등)
  • 진짜 어려운 테스트

그리고 가장 중요한 건 커버리지가 100%라 하더라도 버그는 존재할 수 있다.

  • 테스트를 잘못 작성
  • 요구사항에 오해
  • 컴포넌트간 협업 실패

테스트 코드 중복 제거

테스트 코드 같은 경우 같은 테스트에 여러 개의 값을 넣어보고 싶을 때 아래 처럼 @ValueSource를 사용해서 한번에 테스트 해볼 수 있다.

// @Test
// @DisplayName("숫자가 아닌 경우 예외 처리")
// void validateBridgeDigit() {
//   assertThatThrownBy(() -> validator.validateBridgeSize("1a"))
//         .isInstanceOf(IllegalArgumentException.class)
//         .hasMessage(ErrorMessage.INCORRECT_BRIDGE_SIZE);
// }

// @Test
// @DisplayName("숫자가 아닌 경우 예외 처리")
// void validateBridgeDigit() {
//   assertThatThrownBy(() -> validator.validateBridgeSize(";"))
//         .isInstanceOf(IllegalArgumentException.class)
//         .hasMessage(ErrorMessage.INCORRECT_BRIDGE_SIZE);
// }

@ParameterizedTest
@ValueSource(strings = {"1a", ";"})
@DisplayName("숫자가 아닌 경우 예외 처리")
void validateBridgeDigit(String input) {
  assertThatThrownBy(() -> validator.validateBridgeSize(input))
        .isInstanceOf(IllegalArgumentException.class)
        .hasMessage(ErrorMessage.INCORRECT_BRIDGE_SIZE);
}

회고 작성 피드백

코수타(코치들의 수다 타임)에서 코치님께서 회고 같은 경우도 한 번에 작성하는 것보다 그날 그날 거를 작성해놓는 게 좋다고 하셨다. 그때 정말 공감되었던 게 그전 주 차까지 거의 한 번에 작성하면서 뭔가 아님을 느끼고 있었다. 이렇게 하면 힘들기도 하고 내용도 상세하게 기억이 나지 않는 걸 깨닫고 그 주 차에 딱 그렇게 적용해 보고 있던 도중에 들었기 때문에 약간 소름 돋았다.. 피드백 너무 맛있어ㅠㅠ

☝ 지난주 목표

지금까지 1, 2, 3주 차 동안 많은 리뷰, 피드백들을 머리속에 입력했지만 짧은 시간 내에 입력했기 때문에 깊게 소화할 시간이 없어서 이번 주 차에 모든 것들을 잘 정리해 보고 적용해 보자고 최종 목표를 잡았었다. 그래서 지금까지의 피어 리뷰 피드백 + 최종 요구사항 Check List들을 계속 곱씹어가며 미션을 풀었다.


최종 요구사항 Check List

최종 요구사항 Check List

  • 프로그램 실행의 시작점은 Application의 main()이다.
  • build.gradle 파일을 변경할 수 없고, 외부 라이브러리를 사용하지 않는다.
  • Java 코드 컨벤션 가이드를 준수하며 프로그래밍한다.
  • 프로그램 종료 시 System.exit()를 호출하지 않는다.
  • 프로그램 구현이 완료되면 ApplicationTest의 모든 테스트가 성공해야 한다. 테스트가 실패할 경우 0점 처리한다
  • 프로그래밍 요구 사항에서 달리 명시하지 않는 한 파일, 패키지 이름을 수정하거나 이동하지 않는다.
  • indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다.
  • 3항 연산자를 쓰지 않는다.
  • 함수(또는 메서드)가 한 가지 일만 하도록 최대한 작게 만들어라.
  • JUnit 5와 AssertJ를 이용하여 본인이 정리한 기능 목록이 정상 동작함을 테스트 코드로 확인한다.
  • else 예약어를 쓰지 않는다.
  • 도메인 로직에 단위 테스트를 구현해야 한다. 단, UI(System.out, System.in, Scanner) 로직은 제외한다.
  • 함수(또는 메서드)의 길이가 10라인을 넘어가지 않도록 구현한다.
  • 메서드의 파라미터 개수는 최대 3개까지만 허용한다.
  • InputView, OutputView, BridgeGame, BridgeMaker, BridgeRandomNumberGenerator 클래스의 요구사항을 참고하여 구현한다.

3주차 피드백

  • 비즈니스 로직과 UI 로직을 분리한다
  • 연관성이 있는 상수는 static final 대신 enum을 활용한다
  • final 키워드를 사용해 값의 변경을 막는다
  • 객체의 상태 접근을 제한한다
  • 객체는 객체스럽게 사용한다
  • 필드(인스턴스 변수)의 수를 줄이기 위해 노력한다
  • 발생할 수 있는 예외 상황에 대해 고민한다
  • 성공하는 케이스 뿐만 아니라 예외에 대한 케이스도 테스트한다
  • 테스트 코드도 코드다
  • 테스트를 위한 코드는 구현 코드에서 분리되어야 한다

2주차 피드백

  • README.md를 상세히 작성한다
  • 기능 목록을 재검토한다
  • 기능 목록을 업데이트한다
  • 값을 하드 코딩하지 않는다
  • 구현 순서도 코딩 컨벤션이다
  • 변수 이름에 자료형은 사용하지 않는다
  • 한 함수가 한 가지 기능만 담당하게 한다
  • 처음부터 큰 단위의 테스트를 만들지 않는다

1주차 피드백

  • 요구사항을 정확히 준수한다
  • 커밋 메시지를 의미 있게 작성한다
  • Pull Request를 보내기 전 브랜치를 확인한다
  • PR을 한 번 작성했다면 닫지 말고 추가 커밋을 한다
  • 이름을 통해 의도를 드러낸다
  • 축약하지 않는다
  • 공백도 코딩 컨벤션이다
  • 공백 라인을 의미 있게 사용한다
  • space와 tab을 혼용하지 않는다
  • 의미 없는 주석을 달지 않는다
  • IDE의 코드 자동 정렬 기능을 활용한다
  • Java에서 제공하는 API를 적극 활용한다
  • 배열 대신 Java Collection을 사용한다


⏯ 4주차 진행 과정

  1. 흐름도 작성
  2. 구현 기능 작성
  3. 코드 구현
  4. 리팩토링 및 제출

1. 흐름도 작성

이번에도 역시 저번과 같이 코드를 구현하기 전 흐름도를 작성하여 핵심 부분을 주축으로 설계하려고 했다.

2. 구현 기능 작성


구현 기능 목록

🚀 구현할 기능 목록


  • 다리 생성
    • 다리를 생성할 때 위, 아래 칸 중 건널 수 있는 칸은 0과 1중 무작위 값 이용
    • 무작위 값이 0인 경우 아래 칸 - D 저장, 1인 경우 위 칸 - U 저장
  • 이동할 칸이 유효한 칸인지 확인

입력

  • 다리 길이 입력
    • 3이상 20이하 숫자 입력
    • 그 외의 경우 예외 처리
      • 입력 크기가 1보다 작거나 2보다 큰 경우 예외 처리
      • 숫자가 아닌 경우 예외 처리
      • 3보다 작거나 20보다 큰 경우 예외 처리
  • 이동할 칸 입력
    • 라운드 마다 위(U), 아래(D) 중 입력
    • 그 외의 경우 예외 처리
      • U(위) 혹은 D(아래)가 아닌 경우 예외 처리
    • 다리를 건널 때까지 혹은 실패할 때 까지 매 라운드 입력
      • 성공한 경우 -> 게임 결과 출력 -> 게임 종료
      • 실패한 경우 -> 다시 시도할지 여부 입력
  • 다시 시도할지 여부 입력
    • 재시작 시 처음에 만든 다리로 게임 재시작
    • 종료 입력 시 결과 출력 후 게임 종료
    • R(재시작)과 Q(종료)중 하나의 문자를 입력
    • 그 외의 경우 예외 처리

출력

  • 게임 시작 문구
  • 다리 출력
    • 이동할 칸을 건널 수 있다면 O, 건널 수 없다면 X로 표시
    • 선택하지 않은 칸은 공백 한 칸으로 표시
    • 다리 칸의 구분은문자열로 구분
    • 현재까지 건넌 다리를 모두 출력
  • 최종 게임 결과 출력
  • 게임 성공 여부 출력
  • 시도 횟수 출력

게임 종료

  • 다리를 끝까지 건너면 종료된다.
  • 다리를 건너다 실패하고 추가 게임 여부에서 종료를 입력한 경우

사용자가 잘못된 값을 입력한 경우(예외 처리)

  • IllegalArgumentException을 발생
  • “[ERROR]”로 시작하는 메시지를 출력 후 그 부분부터 입력을 다시 받는다.
  • Exception이 아닌 IllegalArgumentException, IllegalStateException 등과 같은 명확한 유형을 처리한다.


3. 코드 구현

이번 미션에서는 경우의 수가 상당히 나눠져 있었는데

  • 다리 건너기
    • 이동 중 막힌 경우
      • 재입력 -> 다리 처음부터 시작(다리는 그대로)
      • 종료 -> 최종 출력 후 종료
    • 다리를 다 통과한 경우 -> 성공 -> 최종 출력 후 종료

이 부분이 서로 엮여 있었기 때문에 처음에 설계를 할 때 상당히 골치 아팠는데 한 번에 설계하려던 것이 문제였다. 동시에 고려하다 보니깐 이쪽도 터지고 저쪽도 터졌다. 그래서 생각을 하던 중 한 번에 다 하려 하지 말고 작은 거 부터 하나하나씩 구현하라는 피드백이 떠올랐고 그 생각을 다시 리마인드하며 끝까지 구현했다!

이번에는 지금까지와 달리 클래스들을 다 제공해 주시고 안에 메소드까지 제공해 주셔서 요구사항을 지키며 틀안에 코드를 작성하여 조립을 해야 됐다. 근데 그 과정이 마치 게임에서의 퀘스트를 하나하나 해결해나가는 것 같아서 매우 재미있게 구현했던 것 같다. 👍

https://github.com/woowacourse-precourse/java-bridge/pull/283

thumbnail

4. 리팩토링 및 제출

처음에도 말했듯이 이번에는 리팩토링 하는데 시간을 상당히 많이 사용했는데 커밋을 보면 코드 구현 관련 커밋 17개, 리팩터링 관련 커밋 17개로 커밋 양이 같다. 실수 없이 잘 적용해 보고 싶어 끊임없이 돌려 봤는데 봐도 봐도 계속 고칠게 보여서 미쳐버릴 뻔 했다.

  • 패키지 분리
  • 테스트 추가
  • import 정리
  • enum 분리
  • Converter 분리
  • 파라미터 줄이기
  • ..등등

🙇 정리 및 후기

4주 동안 너무 재밌게 몰두하였기 때문에 이 글을 적기 전까지는 마지막이라는 실감이 안 났었는데 마지막 후기 적는 곳에 오니깐 프리코스가 끝난다는 실감이 나기 시작한다… 😭 (분명 내일 미션 메일이 와야 되는데..?)

정기적으로 전문가이신 분들에게 피드백을 받고 그 피드백을 적용하는 이 라이프 사이클이 너무 즐거운 나날들이었다. 이 과정에서 클린 코드라는 좋은 친구를 사길 수 있었고 테스트 코드 작성, 프레임워크 구조 설계, 자바 언어를 더 응용해 보는 등 매주 성장하는 내 모습을 볼 수 있었다.

하지만 다음과 같은 말이 있다.

중요한 것은 어떻게 시작했는가가 아니라 어떻게 끝내는가이다. -앤드류 매튜스

우선 프리코스 과정은 이번 미션으로 마지막이 되지만 앞으로 성장을 향한 나의 성장일기는 계속된다! 앞의 과정 중에 아쉬웠던 것이나 적용하지 못한 부분들을 추가 리팩터링해볼 예정이고 그러고 나서 이 과정 그대로 다시 tdd를 적용해 보면서 재진행할 예정이다.

이런 좋은 기회를 주셔서 너무 감사하고 다시 우테코 크루와 만나서 함께 성장할 수 있었으면! 💪


*틀린 부분이 있으면 언제든지 말씀해 주시면 공부해서 수정하겠습니다.


© 2022. All rights reserved.

Powered by 애송이