JAVA/우아한테크코스 4기

[220225][JAVA] 크리스와 페어 협업미션을 통해 배운 점 (로또(자동) 미션)

kth990303 2022. 2. 25. 13:55
반응형

로또 1단계 미션을 크리스와 함께 진행하였다.

크리스는 보이는 라디오에서도 나와 같이 활동했고, 회식도 함께 진행한 덕에 서로 편하게 소통할 수 있었으며,

거주지도 서로 가까워 페어 미션동안 오프라인으로 만나면서 진행하였다.

 

이번 시간에도 페어에게 정말 많은 걸 배울 수 있었고,

크리스에게 java 개념들과 특정 구조에 대한 이점을 배울 수 있었다 :)


정규식 성능 개선

그 동안은 정규식 String만 따로 위에 빼준 다음에, String.matches()로 정규식을 비교해주었다.

그러나, String.matches()를 이용할 경우 아래와 같은 작업이 발생한다.

출처: stackoverflow

matches 과정마다 Pattern.compile이 발생하는데, 이 때 Pattern.compile이 컴파일 과정에서 성능을 좀 많이 잡아먹는다고 한다.

public static final Pattern NATURAL_NUMBER_REGEX = Pattern.compile("^[1-9][0-9]*$");

// ... 다른 메소드

if (!NATURAL_NUMBER_REGEX.matcher(value).matches()) {
    // 로직 처리
}

따라서 이렇게 static final 키워드로 compile을 미리 만들어준 다음에,

regex.matcher().matches()로 진행해주어 성능을 개선해주는 방법이 좀 더 좋다고 한다.

 

다음부터는 regex만 static final 키워드로 올려주는 게 아닌, Pattern.compile도 같이 위로 올려주는 걸로.

 

도움된 글: https://stackoverflow.com/questions/2469244/whats-the-difference-between-string-matches-and-matcher-matches


Junit 문법_@CsvSource, @MethodSource

이전 협업미션 때 @ParameterizedTest 키워드를 배워서 @ValueSource로 여러 값들을 동시에 테스트한 적이 있다.

그런데, @ValueSource를 이용하려면 결과가 동일한 값이어야 했기 때문에, 다양한 결과를 나타내는 값들은 여러 assert문으로 테스트를 진행했던 기억이 있다.

 

@CsvSource, @MethodSource를 이용하면 다양한 값들을 넣었을 때 결과가 다른 경우들도 하나의 assert문으로 테스트할 수 있다.

@ParameterizedTest
@DisplayName("매치 개수를 받아 개수에 따른 당첨 종류 객체를 반환한다.")
@CsvSource({"3, false, THREE", "4, false, FOUR", "5, false ,FIVE", "5, true, FIVE_BONUS", "6, false ,SIX"})
void from(final int matchCount, final boolean bonus, final LottoMatchKind expected) {
    //when
    final LottoMatchKind actual = LottoMatchKind.from(matchCount, bonus);
    //then
    assertThat(actual).isEqualTo(expected);
}

위 코드는 @CsvSource 키워드를 이용해 결과값이 THREE, FOUR, FIVE, FIVE_BONUS, SIX를 기대할 때의 테스트이다.

LottoMatchKind가 enum 객체이고, 이를 matchCount, bonus를 입력값으로 받아 정적 팩토리 메소드로 생성할 수 있게 해놓았기 때문에 위와 같은 코드가 나왔다.

 

private static Stream<Arguments> provideOtherNumbersAndExpected() {
    return Stream.of(
            Arguments.of(Arrays.asList("1", "2", "3", "4", "5", "6"), 6),
            Arguments.of(Arrays.asList("1", "2", "3", "4", "5", "7"), 5),
            Arguments.of(Arrays.asList("1", "2", "3", "4", "7", "8"), 4),
            Arguments.of(Arrays.asList("1", "2", "3", "7", "8", "9"), 3),
            Arguments.of(Arrays.asList("1", "2", "7", "8", "9", "10"), 2),
            Arguments.of(Arrays.asList("1", "7", "8", "9", "10", "11"), 1),
            Arguments.of(Arrays.asList("7", "8", "9", "10", "11", "12"), 0)
    );
}

@ParameterizedTest
@DisplayName("보너스 숫자를 제외하고, 당첨된 숫자의 개수를 반환한다.")
@MethodSource("provideOtherNumbersAndExpected")
void getMatchNumbersCount(final List<String> otherNumbers, final int expected) {
    //given
    final LottoNumbers target = new LottoNumbers(Arrays.asList("1", "2", "3", "4", "5", "6"));
    final LottoNumbers otherLottoNumbers = new LottoNumbers(otherNumbers);
    //when
    final int actual = target.getMatchCount(otherLottoNumbers);
    //then
    assertThat(actual).isEqualTo(expected);
}

위 테스트는 입력값으로 List<String>이 들어올 때의 테스트인데, @CsvSource로 하기엔 컬렉션 객체인데다가, 너무 복잡하기 때문에 입력값을 따로 static 메소드로 분리해준 후 @MethodSource로 테스트한 코드이다.

 

provideOtherNumbersAndExpected에서 입력값과 기대값을 입력해놓은 후, Stream.of(Arguments.of()) 값들을 반환하게 해주었다. 따라서 반환형은 Stream<Arguments> 가 되고, @MethodSource에는 "provideOtherNumbersAndExpected"를 적어주면 Stream<Arguments>에 있는 값들이 기대값과 일치하는지 테스트해준다.

 

도움된 글: https://zzang9ha.tistory.com/344


given-when-then tdd 패턴

그동안 나는 테스트 메소드를 굉장히 짧게 assertThat으로 바로 검증하는 것을 좋아했는데,

크리스는 assertThat 안에 모두 넣는 것보단,

given에 기대값, 입력값들을 넣고,

when에 실제값,

then에 expect와 actual값을 기대하는 assert문 테스트를 넣어

가독성을 높이는 방법을 선호하였다.

@Test
@DisplayName("로또 가격을 받아 구매 개수를 반환한다.")
void getPurchaseCount() {
    //given
    final int expected = 2;
    //when
    final int actual = purchaseAmount.getPurchaseCount(lottoPrice);
    //then
    assertThat(actual).isEqualTo(expected);
}

크리스는 when에 존재하는 변수명은 actual,

given에 존재하는 기대값의 변수명은 expected로 쓰는 것을 지키려 한다고 한다.

 

이번 시간에는 이 방법을 사용해보았고, 실제로 가독성이 꽤 괜찮아보여 이 방법을 자주 사용해볼 듯하다.


Dto 생성

지난 미션에는 도메인 객체를 생성하는 것에 그쳤었는데,

이번 협업미션을 통해 DTO를 만들어보았다.

dto 클래스

이는 getter를 지양하기 위해 사용되는 방법 중 하나인데,

지난 시간에는 getter를 만들되, 비즈니스 로직에선 사용하지 않고 view에서만 사용하도록 암묵적인 약속을 지키는 방법으로 했던 반면, 이번 시간에는 view에서는 도메인 객체에 접근하지 않고 dto를 의존하도록 dto를 만들어보았다.

 

이 방법도 괜찮다고 생각한 것이,

view에서 도메인에 의존해버리는 것 자체가 좋지 않기도 하고,

다른 개발자가 내 코드를 볼 때, dto만 접근하게 해야겠다는 생각을 하게 해줄 수 있겠다 싶어 괜찮아보였다.


ui (View) 인터페이스 생성

지난 미션에선 ui 패키지 생성 후, InputView, OutputView 클래스로 바로 생성했었는데,

이번 미션에선 InputView, OutputView를 각각 인터페이스를 생성 후에,

각각의 구현체인 ConsoleInputView, ConsoleOutputView를 생성해주는 방법을 이용했다.

이 방법 역시 크리스의 아이디어였는데,

입출력을 console로 할 때도 있지만, 다른 방법으로 입출력을 받는 경우도 존재하기 때문에 view는 인터페이스로 만들어두는 방법을 선호한다고 한다.


이번 페어미션에선 더 좋은 구조와 설계에 대한 토론,

변수명과 메소드명에 대한 토론을 굉장히 많이 진행하였다.

 

스타일이 서로 다른 점이 많았어서 더 좋은 구조와 설계에 대한 얘기를 하면서 많은 생각을 하며 장단점을 돌이켜보는 좋은 시간이 됐었다!

우테코에서 페어 프로그래밍을 내준 이유가 이러한 이유가 아닐까 싶다.

 

구조와 설계 면에서 더 많이 이 포스팅에 적고 싶었지만,

리뷰어 던에게 피드백을 받는 과정을 거친 후에, 더 많이 작성해보려 한다 :)

반응형