[우테코 프리코스] 2주차 미션 회고 (java)

3Beom's 개발 블로그·2022년 11월 8일

지난 1주차 미션에 이어 2주차 미션도 열심히 했다..!
-> 1주차 미션 회고

2주차 미션


-> 우테코 프리코스 2주차 java-baseball
-> 필자의 결과물
-> 새로 배운 내용 - JUnit5
-> 새로 배운 내용 - AssertJ

1주차 미션 때는 Java 언어에 익숙해지는 것이 나만의 목표였고, 2주차 미션은 함수 분리와 테스트 코드 작성에 익숙해 지는 것이 미션의 의도이자 나의 목표였다.

2주차 미션을 시작하기 전, 코수타(코치님과의 수다 타임..? 맞나..?) 시간에 코치님께서 TDD에 대해 언급하신 것을 보고 테스트 위주 개발 방식에 대해 알아봤다.
장점이 많고 중요한 개발 방식인 것을 확인한 후, 2주차 미션에서 직접 실천해보고자 했다.

그리고 함수 분리에 대해서도 알아보았고, 이와 더불어 가독성에 대해서도 이런 저런 자료들을 찾아보며 2주차 미션에서 실천해 볼 개발 방식을 정리해 보았다.

개발 방식 (개발 전 작성)


  • 프로그래밍 요구 사항을 준수하며 개발한다.
  • TDD 방식에 맞추어 개발을 진행한다.
    (1) 테스트 코드 먼저 작성 및 실패
    (2) 테스트 통과할 만큼만 구현
    (3) 리팩토링
  • 개발을 진행하며 TDD의 장단점에 대해 생각해 본다.
  • 각 기능은 본인의 역할에만 충실한다.
    • 다른 클래스 혹은 메소드에 영향을 주지 않도록 한다.
    • 다른 테스트 코드에 영향을 주지 않도록 한다. (수정 발생 시, 수정한 메소드 외 다른 테스트들은 문제가 없도록 개발)
  • 메소드 분리 과정에서 또 다른 클래스로 분리가 가능한지 고민해 본다.
  • 메소드 매개변수 개수를 최소화 한다.

이렇게 나만의 개발 방식을 한 번 만들어 보았고, 최대한 지키고자 했다.
그리고 개발하면서도 네이밍이나 깃 커밋 메세지 컨벤션 등 이런 저런게 떠올랐고, 해당 내용들은 회고에 적었다.

회고 내용이 좀 길어서 아래와 같이 정리해 보았다.

❗️회고 내용은 필자가 개발하면서 느꼈던 지극히 매우매우 개인적이고 주관적인 생각들을 끄적인 것들이며, 소감문 혹은 경험담에 가까우니 지식과 경험이 부족해 잘못된 내용이 다수 존재할 수 있다.❗️
(반박 시 내 말이 틀림ㅠ 피드백 부탁드립니다.)

개발 회고 (개발 후 작성)


1. 개발 전 설정했던 개발 방식 회고

1-1. 프로그래밍 요구 사항을 준수하며 개발한다.

  • 기존 요구 사항 적용
    • JDK 11 버전, Application.main(), build.gradle 수정 및 외부 라이브러리 사용 금지, System.exit() 호출 금지, ApplicationTest 성공, 파일 및 패키지 이름 수정 이동 금지
  • Java 컨벤션 적용
  • indent depth 3 이상 금지
  • 3항 연산자 금지
  • 하나의 함수는 한 가지 일만 수행하도록 최소화
    • 하단에 자세히 작성
  • JUnit 5, AssertJ 활용 테스트 코드 작성
    • 개발 시작 전, JUnit 5, AssertJ 및 TDD에 대한 학습 수행, test/java/study에서 실습 수행
  • 주어진 API (pickNumberInRange(), readLine()) 활용

1-2. TDD 방식에 맞추어 개발을 진행한다. + 장단점에 대한 생각

<TDD 방식 적용>

  • 기록을 남기기 위해 각 기능 별 RED, GREEN, REFACTOR 단위로 커밋하였다.
  • 개발 전 정리했던 기능 목록에 맞추어 먼저 Test 코드를 작성한 후, 테스트가 통과되도록 구현하고 리팩토링 하였다.
  • TDD 방식을 처음 접해보았는데, 개인적으로 기존에 세세하게 기능 목록을 설정한 후 개발을 진행하는 방식보다, TDD 방식이 좀 더 나에게 맞는 것 같았다.

<TDD 방식에서 느낀 장점 (본인 기준)>

  • 예외 사항에 대해 고려하는 폭이 보다 넓어졌다.
    • 1주차 미션 때 기능 구현 -> 테스트 코드 작성 순으로 진행했을 때는 내가 설정한 로직 안에 갇혀 예외 사항에 대해 떠오르는 폭이 좁은 느낌이었다.
    • 하지만 TDD 방식은 "내가 만든 기능"이 아닌, "프로그램 동작에 필요한 기능"에 대해 고민하다보니 좀 더 객관적으로 바라보고 다양한 예외 사항에 대해 떠올릴 수 있었다.
  • 구글링 할 때도 봤던 내용인데, 코드가 간결해지는 효과가 정말 있었다.
    • 기존 방식의 경우, 더 좋은 방법이 있는데도 이미 구현해 놓은 내용이 아까워 코드가 지저분해 지는 경험을 많이 했었다.
    • 하지만 "테스트를 통과할 정도로만" 구현을 먼저 수행한 후, 리팩토링을 진행해 보니 코드가 지저분해지는 확률을 많이 낮춰주는 느낌을 받았다.
    • 필요한 변수와 로직도 간결해지고, 리팩토링 과정에서 더 뺐으면 뺐지 더 넣진 않았으니, 확실히 효과적이라고 생각했다.
  • 코드를 작성하는 시간이 단축되었다.
    • 기존에는 기능을 구현하면서 리팩토링을 함께 진행하다보니 코드를 조금씩 추가하는데도 오래 고민하게 되고, 그만큼 시간이 더 오래 걸렸다.
    • 하지만 테스트 통과할 만큼만 우선 구현을 해두고 이후에 리팩토링을 진행하니 고민하는 시간을 효율적으로 줄일 수 있었다. 이는 TDD 방식이 아니라, "먼저 간단히 구현 후 리팩토링" 순서가 많이 도움되었다.
  • 테스트 코드 작성이 크게 부담스럽지 않았다.
    • 1주차 미션 때는 기능 구현에 많은 고민을 쏟고 테스트 코드도 또 고민할 생각을 하면 항상 조금 부담되었다.
    • 하지만 TDD 방식은 테스트 코드를 먼저 작성함으로써 테스트 해야할 경우에 대해 먼저 고민하게 되고, 이 과정에서 기능 구현에 대한 로직이 대략적으로 그려지니 부담이 훨씬 적었다.
    • 대략적으로 그려놓은 로직으로 테스트를 통과할 정도로만 구현하고, 그 이후에 리팩토링에 대한 고민만 하면 되니 좀 더 수월한 느낌이 들었다.

<TDD 방식에서 느낀 단점 (본인 기준)>

  • 조금 억지스러울 수 있지만.. 테스트 코드를 먼저 작성할 때 빨간줄이 계속 뜨는게 조금 거슬렸다.

    • 아무래도 아직 구현되지 않은 클래스와 메소드를 활용하려니 빨간줄이 뜨는건 어쩔 수 없지만, 그래도 마음 한 편이 조금 불편했다.ㅠ
  • 구현되지 않은 기능에 대한 테스트 코드를 작성하려니 모호한 면이 있었다.

    • 내가 지금 작성하고 있는 테스트 코드가 내 의도대로 동작될지 의심되었다.

      (JUnit 5와 AssertJ를 처음 활용해 봐서 더 그랬던 것 같다.)

    • 구현되지 않은 허구에 대해 테스트하는 과정 자체에서 모호한 면이 많았다.

    • 하지만 이러한 단점들은 테스트 코드 작성을 더 많이 해보며 지식을 쌓고, 기능 목록을 좀 더 자세하게 설정하면 보완될 수 있을 것이라 생각한다.

1-3. 각 기능은 본인의 역할에만 충실한다. + 역할 최소화

<클래스의 본인 역할 충실>

  • "게임 운영 클래스""문제 출제 클래스"로 나누었고, 각각은 본인의 역할에만 충실하도록 설정하였다.
    • 특정 클래스에 기존과 다른 클래스가 붙어도 동작할 수 있도록 구현하는 것을 고려했다.
    • 각자의 역할에만 충실하면 다른 클래스와 일해도 잘 동작할 것이라 생각했다.
  • 게임 운영 클래스는 안내 데스크처럼 사용자로부터 숫자를 입력받고, 안내해주는 역할만 수행하는 것으로 설정하였다.
  • 문제 출제 클래스는 문제를 출제하고 게임 운영 클래스로부터 사용자가 입력한 숫자를 전달받아 결과만 전달해주는 것으로 설정하였다.
  • 다음과 같이 클래스를 더 나누어 각각에게 역할을 더 작게 쥐어줄 수도 있었다.
    • 게임 운영 클래스
      • 사용자 입력 및 문제 출제부로 전달하는 역할 담당 클래스
      • 안내 문장 출력 클래스
    • 문제 출제 클래스
      • 랜덤 숫자 만드는 클래스
      • 사용자 입력 숫자와 비교하는 클래스
  • 하지만 이는 오히려 코드 가독성을 해칠 수 있을 것이라 생각했고, 불필요하게 자세하게 나누는 것 같아 더 나누지 않았다.

<메소드의 본인 역할 충실, 그리고 역할 최소화>

  • 해당 과정이 가장 어렵게 느껴졌다. 본인의 역할만 충실하도록 구현하는 것의 장점이 가독성도 있지만, 재사용성도 높일 수 있다고 생각하는데, 재사용성이 매우 어려웠고 잘 지켜지지 않았던 것 같다. 아직 좀 더 연습과 고민이 많이 필요하다고 생각한다.
  • 본 미션에서는 우선 재사용성보다는 본인의 역할에만 충실하도록 설정하고, 역할을 최대한 잘게 나누어 작은 함수들을 많이 만드는 것에 집중했다. 작은 공장들을 여러개 만드는 것처럼.
  • 한가지 확실했던건, 가독성이 상당히 좋아졌다. 함수 하나하나가 길지 않으니 부담스럽지 않았고, 함수의 이름에서 의도가 드러나니 전보다 확실히 읽기 편해지는 듯 했다.

1-4. 메소드 매개변수 개수를 최소화 한다.

  • 본 내용은 구글링 중 확인했던 내용이었는데, 메소드의 매개변수를 최소화하는 것이 가독성을 높이고 유지 보수에 도움이 된다고 하여 실천해보았다. 본 방식의 장단점이 있었다.

<장점>

  • 매개변수가 없는 함수는 확실히 가독성이 좋았다.
  • 다른 함수에 대한 의존도도 낮출 수 있었다. 이는 곧 유지 보수에 도움이 되는 것으로 이어질 수 있을 것이라 생각되었다.
    • Ex) 만약 A 함수에서 B 함수를 호출할 때, 매개변수가 여럿 있을 경우, A 함수에서는 매개변수 형식에 맞춰야 할 것이고, 이게 불가능하다면 B 로직이 바뀌어야 할 것이다. 하지만 매개변수가 없으면 이런 고민이 줄어든다.

<단점>

  • 매개변수를 없애려는 과정에서 로직이 수정되는 경우도 있었지만(가장 좋은 방법이라 생각한다.), 멤버 변수가 늘어나기도 했다. 이에 대해 조금 염려가 되기도 했다.

    • 멤버 변수는 객체 내에서 공동으로 쓰는 변수인데, 만약 여러 멤버 함수에서 본 변수를 활용하고 있다면, 나중에 로직을 수정하는 과정에서 또 고려해야할 거리가 늘어나는건 아닌가 싶은 생각이 들었다.
  • 멤버 변수가 늘어나는 것은 클래스를 분리하는 것으로 이어질 수 있어 오히려 좋은건가? 싶다가도 마냥 좋은 것은 아닌 것 같다는 생각이 들었다. 결국 상황에 맞추어 최선의 방안을 선택하는 것이 가장 중요한 것 같다.

2. 개발 중 고려했던 개발 방식 회고

2-1. 가독성을 위한 네이밍

  • 가독성을 위해 이름을 설정할 때 다양한 방법이 있겠지만 이번에는 특정 방식에 초점을 맞추어보았다.

    -> 로직에 초점을 맞춘 것이 아닌, 기능에 초점을 맞추어 이름을 결정한다.

  • 이는 이번에 AssertJ에 대해 알아보다가 생각하게 되었다.

    • AssertJ의 메소드는 하나의 문장을 만들듯 지어진다.

      • Ex1)
              assertThat(string)
                      .isNotNull()
                      .isNotEmpty()
                      .startsWith("Woowa")
                      .endsWith("Course")
                      .contains("Tech")
                      .isEqualTo("Woowa Tech Course")
                      .isNotEqualTo("abcd");

      ->Assert that 'string' is not null / is not empty / ...

      • Ex2)
              assertThatThrownBy(() -> {
                  GameExceptionHandler.handleNotNumberException(alphabetInput);
              }).isInstanceOf(IllegalArgumentException.class);

      -> Assert that thrown by 'Method' is instance of 'illegal argument exception'

  • 결국 코드의 가독성에 대해 중요하게 생각해야 하는 것 중 하나가 코드를 작성한 개발자의 의도를 파악하는 것인데, 이를 이름으로 표현할 때 "로직을 담는 것""기능을 담는 것"에 대해 고민한 적이 많았다.

    • 내가 작성한 코드를 분석하려는 개발자 입장에서는 "로직을 담는 것"이 더 편할 것이라 생각했다.
    • 하지만 내가 만든 기능을 활용하려는 개발자 입장에서는 "기능을 담는 것"이 더 편할 것이라 생각했다.
  • 이 전까지는 '로직을 담는 것'에 좀 더 초점을 맞추어 왔었는데, 본 미션에서는 "기능을 담는 것"에 한번 초점을 맞추어 보았다.

    • Ex1) pickUniqueRandomNumber
    • Ex2) getNumbersFromUser
  • 직접 실천해 본 결과, 다음과 같은 결론을 얻을 수 있었다.

    (1) 상위 메소드(타 메소드를 호출하며 기능을 이루는 메소드)는 "기능을 담는 것"이 더 도움될 수 있다.

    (2) 하위 메소드(타 메소드에 의해 호출되는 메소드)는 주로 로직을 담고 있으므로, "로직을 담는 것"이 더 도움될 수 있다.

    • Ex)
      • makeResultSentence() 내에서 convertResultMapToString() 을 호출한다.
      • makeResultSentence() : 본 함수의 기능은 결과 문장을 만드는 것이며, 로직을 직접 갖고 있진 않고 하위 메소드를 호출한다. 따라서 이름에 "기능을 담는다."
      • convertResultMapToString() : 본 함수의 기능은 결과 Map을 String으로 변환하는 것이며, 로직을 갖고 있다. 따라서 이름에 "로직을 담는다."
  • 물론 구현해야 하는 기능에 따라 달라지고, 사람마다 가독성을 받아들이는 것에 차이는 있겠지만, 상기 내용과 같이 이름을 설정하면 좀 더 도움이 될 수 있지 않을까 생각해 본다.

2-2. 깃 커밋 메세지 컨벤션 scope 설정

  • 본 미션에서 커밋 메세지 컨벤션을 고려하며 커밋 메세지를 작성했는데, 'scope' 내용에 대해 고민이 있었다.

  • 기능의 범주를 넣는거라 그래도 자세할 수록 좋을 것 같아 클래스 단위를 입력하였다.

    • Ex) feat(GameOeprator): ...
  • 하지만 클래스 단위로 입력해보니, 커밋 메세지 내용이 중복되기도 하고, subject line(첫번째 줄)이 너무 길어졌다ㅠ

    • Ex)

      feat(GameOperator):GREEN 게임 운영 기능 구현
      
      사용자 입력 및 게임 안내문, 결과 출력 기능 구현
      
      GameOperator.java 구현
      ...
  • 따라서 좀 더 큰 범주인 패키지 단위로 입력하는 것이 더 좋을 것 같다는 생각을 하게 되었다. (자세한 내용은 body나 footer를 열어보면 되니까!)

    • Ex)

      feat(domain): ...
      test(exception): ...
  • 만약 이 미션이 팀 단위 미션이었다면 본 방식을 의견으로 제시할 것 같다..!

  • 다음 주차부터 적용해 보면 좋을 것 같다.


profile
경험과 기록으로 성장하기

0개의 댓글