본문 바로가기
우아한테크코스 프리코스

우테코 6기 프리코스 3주 차 로또 회고

by 준형코딩 2023. 11. 9.

들어가면서

3주 차에 무엇을 공부했나?

 

1. 클래스 분리와 설계에 대해서 공부하였습니다.

3주 차에 클래스를 분리하는 연습이 목표로 추가가 되면서 조금 더 클래스의 분리와 객체 지향적 설계를 하기 위해서 많은 노력을 기울였습니다. 2주 차때는 설계의 미흡함으로 인해서 기능을 완성한 후에 객체 분리 리팩토링에 많은 시간을 투자하였습니다. 이번에는 설계를 탄탄하게 하여 개발을 진행하기 위해서 프리코스 기간 중에 읽기 시작한 “오브젝트”라는 객체지향 책을 공부하며 배운 내용을 설계에 담아보고자 노력했습니다. 특히 책 내용 중 초반에 나오는 책임 주도 설계 방법과 GRASP패턴이 도움이 되었습니다. 이전에는 그저 막연하게 설계를 진행했다면 체계적인 방법을 통해서 로또 게임의 시스템 책임을 파악하고 그 책임을 더 작은 책임으로 분할하고 분할된 책임을 수행할 수 있는 적절한 객체 또는 역할을 찾아서 책임을 할당하는 연습을 하였고 GRASP 패턴의 내용을 설계에 녹이려고 노력하였습니다. 아무래도 과제를 진행하면서 틈틈이 서적을 읽고 코드에 반영하려고 하다 보니 처음에 설계하였던 내용이 중간에 많이 바뀌긴 하였지만 점점 제 코드가 데이터 중심 설계에서 올바른 책임 주도 설계로 바뀌어가는 과정을 경험을 하게 되었고 오히려 나쁜 설계를 직접 경험하고 좋은 설계로 변경시켜 나갔기 때문에 객체지향 설계에 대한 이해도를 더 높일 수 있었습니다. 또한 이번 3주 차에서는 서적에 나오는 영화관 예매 시스템의 예제 코드를 참고하면서 최대한 객체들 스스로 판단하고 행동할 수 있는 자율적인 객체를 만들고자 노력했고 객체들 간 응집도를 높이고 결합도를 낮추려고 노력했습니다. 워낙 책의 내용이 방대해서 모든 내용을 코드에 반영하지는 못했지만 앞으로 개발자로 살아가면서 두고두고 읽어야 할 책이라는 것을 깨닫게 되었고 4주 차에도 최대한 오브젝트 책을 읽으면서 설계에 반영하고 프리코스가 끝난 후에도 몇 번 정독을 해야겠다는 다짐을 하게 되었습니다.

 

 

초기 설계
마지막 제출 시점 설계 (새롭게 알게 된 IntelliJ 다이어그램)

2. 정적팩토리 메소드에 대해서 공부하였습니다.

리뷰를 진행하며 도메인에 of나 from 같이 생성자와 비슷한 메서드들을 여러 코드에서 발견하게 되었고 이에 대한 궁금증이 일어서 찾아보니 정적 팩토리 메서드라는 것을 알게 되었습니다. 기존에는 정적 팩토리 메서드를 몰라서 생성자를 통해서만 객체를 생성하였었는데 정적 팩토리 메서드는 여러 이름을 가질 수 있고 호출할 때마다 새로운 객체를 생성할 필요가 없는 등 여러 장점이 있어 정적 팩토리 메서드를 기존 생성자 대신에 사용하는 것이 좋다는 것을 알게 되었습니다. 그래서 3주차 과제에서 이를 활용하기 위해 로또의 Enum 결과를 담은 List를 EnumMap으로 변환하는 LottoRankInfo라는 클래스에서 정적 팩토리 메서드를 사용하게 되었습니다. 이 과정에서 기존의 일급 컬렉션을 활용하게 되면 final로 지정된 인스턴스 변수 때문에 List에 들어있는 Enum key값별로 EnumMap을 세팅해 주었어야 하는데 세팅을 한번 진행하고 나면 EnumMap의 값을 변경할 수 없다는 문제점이 있었습니다. 이런 문제점을 해결하기 위해서 정적 팩토리 메서드를 도입하였고 기본 key 세팅과 로또 결과에 대한 값들을 정적 팩토리 메서드 내의 EnumMap에 주입한 후 이를 다시 클래스의 생성자를 통해 일급 컬렉션으로 래핑 하여 EnumMap 생성과 동시에 불변의 값을 가질 수 있게끔 코드를 작성하게 되었습니다. 아직 정적 팩토리 메서드에 대한 이해도가 높진 않지만 코드를 직접 짜보면서 정적 팩토리 메서드의 활용 방법과 장점을 체감할 수 있었습니다.

 

3. DI에 대해서 공부하였습니다.

마찬가지로 2주 차 리뷰를 진행하면서 다른 분들의 코드를 보는데 많은 분들이 controller 내부에서 직접 객체를 생성하는 것이 아니라 application에서 객체를 생성하고 controller에 주입하여서 사용하는것을 보았습니다. 그래서 굳이 바깥에서 객체를 주입하는 이유가 궁금하여 깃허브 리뷰에 질문을 남겼는데 한 분이 DI 의존성 주입에 대해서 공부해 보라고 조언을 해 주셨고 이 내용에 대해서 찾아보니 외부에서 객체를 주입함으로써 코드의 재사용성과 유연성이 높아지고 유지보수가 쉬워지며 테스트가 용이해진다는것을 알게 되었습니다. 다른분들의 코드를 통해서 새로운것을 알게되는 경험을 하면서 다시 한번 함께 공부하면서 얻을 수 있는 장점에 대해서 체감할 수 있었고 기존에 몰랐던 DI에 대한 필요성을 느끼게 되었습니다.

 

4. Collections.unmodifiableList에 대해서 공부하였습니다.

3주 차 과제를 수행하면서 일급 컬렉션에 대해서 더 공부를 하게 되었고 기존 제가 짠 일급 컬렉션에서는 컬렉션을 일급 컬렉션으로 래핑 하여 불변으로 만들어주긴 하지만 getter를 통해서 데이터를 내보낼 때 List.add 같은 명령어를 통해서 데이터의 불변을 깨뜨릴 수 있게 getter를 설계하고 있었다는 것을 깨닫게 되었습니다. 이를 방지하기 위해서 새로운 복사 컬렉션을 만들어서 바깥으로 내보내거나 unmodifiableList 같은 명령어를 통해서 데이터를 수정 불가능하게 해야 한다는 것을 공부를 통해서 알게 되었고 코드에 적용하게 되었습니다.

 

    public List<Lotto> getLottos() {
        return Collections.unmodifiableList(lottos);
    }

5. Tdd와 테스트에 대해서 더 공부하였습니다.

3주 차에는 켄트 백의 테스트 주도 개발과 실용 주의 단위 테스트라는 서적을 구매를 해서 읽으면서 과제를 진행했습니다. 3주 차에는 조금 더 주먹구구식으로 TDD 테스트를 짜는 방식이 아니라 체계적인 방법들을 도입해서 TDD와 테스트 코드에 익숙해지려고 노력했습니다. 클래스 설계와 분리에 많은 시간을 쓰느라 목표했던 만큼 TDD 서적을 읽는데 시간을 투자하지 못했지만 켄트 백의 테스트 주도 개발 서적에서 보았던 내용들을 참고하여 TDD로 과제를 진행하면서 견고하게 쌓인 테스트들이 코드의 변경이 생길 때마다 든든하게 에러를 잡아주는 경험을 하였고 2주 차보다 숙련도가 올라서 조금 더 수월하게 TDD를 진행할 수 있게 되었습니다. 그리고 실용 주의 단위 테스트라는 책을 통해서 좋은 테스트 조건을 외우기 쉽게 축약한 FIRST나 예외 처리를 할 때 필요한 경계조건을 축약한 CORRECT 같은 내용들을 배웠고 테스트 코드 네이밍 방법과 테스트 일관성을 유지할 수 있는 AAA나 given when then 패턴들을 배워서 과제에 적용시켰습니다. 마지막으로는 테스트코드를 조금 더 깔끔하게 리팩토링을 하는 방법에 대해서 배우게 되었고 Before이나 After 애너테이션이나 ParameterizedTest를 활용해서 테스트 코드를 깔끔하게 정리하려고 노력하였습니다.

 

 

(왼) 새롭게 구매한 테스트 서적, 실용주의 단위 테스트, 테스트 주도 개발 (오) 테스트 커버리지가 컨트롤러를 제외하고 모두 100%인 모습! 뿌듯..!!

 

6. enum에 대해서 공부하였습니다.

2주 차를 마치고 리뷰를 진행하면서 다른 분들의 코드에서 Enum을 발견할 수 있었습니다. 그래서 Enum을 과제에 꼭 도입하고 싶다는 생각을 가지고 있었는데 마침 3주 차 요구사항에 Enum에 대한 조건이 있었고 새로운 것을 배워서 과제에 도입해야 한다는 두려움 반 설렘 반을 안고 Enum에 대해서 공부를 진행하였습니다. 그렇게 공부를 하고 코드에 적용을 시켜 보기 시작하면서 처음에 Enum 없이 구현한 로또 코드에서는 당첨 결과를 분기하는 과정에서 관련 메소드들이 여기저기 흩어져있고 if 문을 사용해서 조건을 처리하다 보니 코드가 굉장히 길고 지저분한 상태였습니다. 하지만 Enum을 공부해서 과제에 도입을 하게 되니 한 곳에서 로또의 당첨 결과를 생성하고 Enum을 통해 생성된 결과를 EnumMap에서 관리를 하게 되니 if 문을 사용할 때보다 훨씬 간결하고 응집도가 높은 검사 로직을 만들 수 있었습니다. 새롭게 배우는 지식을 통해서 이렇게 간결하게 코드가 바뀔 수 있다는 것에 대해서 또 한 번 놀라게 되었고 이전과 비교할 수 없이 많이 성장을 하고 있는듯하여 뿌듯함을 많이 느낄 수 있었습니다.

 

package lotto.model;

import java.util.Arrays;
import java.util.NoSuchElementException;

public enum LottoRank {
    NONE(0, 0),
    FIFTH(3, 5000),
    FOURTH(4, 50000),
    THIRD(5, 1500000),
    SECOND(5, 30000000),
    FIRST(6, 2000000000);

    private int matchCount;
    private int prize;

    LottoRank(int matchCount, int prize) {
        this.matchCount = matchCount;
        this.prize = prize;
    }


    public static LottoRank valueOf(long matchCount, boolean bonusBall) {

        if (matchCount == LottoRank.SECOND.matchCount && bonusBall) {
            return LottoRank.SECOND;
        }

        if (matchCount < LottoRank.FIFTH.matchCount) {
            return LottoRank.NONE;
        }

        return Arrays.stream(LottoRank.values()).filter(rank -> rank.matchCount == matchCount).findAny()
                .orElseThrow(NoSuchElementException::new);
    }


}

 

7. mock 테스트에 대해서 공부하였습니다.

2주 차 과제를 진행하면서 메소드 테스트를 하는데 메소드 내부에서 랜덤 값을 반환하는 메서드가 존재하여 정확하게 결괏값을 예측하기 어려웠던 경험을 하게 되었습니다. 이 경험을 통해서 내부의 랜덤 값을 가짜 값으로 대체하여서 테스트를 할 수 있는 Mock 테스트가 있다는 것을 알게 되었고 2주 차에는 이 Mock 테스트를 도입하지 못하여서 3주 차에는 꼭 Mock 테스트에 대해서 공부하고 적용시켜 보고 싶다는 목표를 세웠었습니다. 이런 목표를 달성하기 위해서 실용주의 단위 테스트라는 책을 열심히 읽었고 그 책의 Mock 챕터에서 발견한 코드들을 과제에 적용시켜 보았습니다. 그러나 서적에서 나오는 내용 중에 Mock 객체를 초기화하는 코드로 initMocks라는 코드를 알려주었는데 이 코드가 deprecated된 코드라는 것을 알게 되었고 결국 인터넷에서 따로 Mock 테스트를 검색해 가면서 Mock에 대해서 공부하였습니다. 그렇게 Mock에 대해서 공부해서 진행하고 있던 3주 차 과제의 내용 중에 lottoChecker.createLottoRanks()를 하게 되면 로또 당첨 결과를 반환하는 메소드가 있었는데 내부의 반환값들을 Mock의 when thenReturn을 활용하여 가짜 값을 던지게 하였고 실제 데이터를 불편하게 세팅해서 테스트를 하는 것이 아니라 가짜 값을 적극 활용하여 필요한 로직만 빠르게 검사할 수 있는 테스트 코드를 작성할 수 있었습니다. 이렇게 Mock을 이용해서 테스트를 진행하면서 아직 기존의 단위 테스트만큼 숙련도가 높지 않아서 테스트 제작 속도가 많이 느리지만 숙련도를 높이게 된다면 복잡한 로직에서 더욱 깔끔하고 빠르게 Mock을 이용해서 테스트를 진행할 수 있겠다는 생각이 들었고 더 깊이 있게 Mock에 대해서 공부해 보아야겠다는 생각을 하게 되었습니다.

 

   @DisplayName("로또 랭크 생성 함수 반환값 테스트 with Mock")
    @Test
    void createLottoRanks_EqualResult_Success() {

        //given
        lottos = mock(Lottos.class);
        winningNumbers = mock(WinningNumbers.class);
        bonusNumber = mock(BonusNumber.class);
        lottoChecker = new LottoChecker(winningNumbers, bonusNumber);
        List<Lotto> mockLottos = Arrays.asList(mock(Lotto.class), mock(Lotto.class));
        MockedStatic<LottoRank> lottoRank = mockStatic(LottoRank.class);

        when(winningNumbers.compareWinningNumbers(anyList())).thenReturn(5L);
        when(bonusNumber.compareBonusNumber(anyList())).thenReturn(true);
        when(lottos.getLottos()).thenReturn(mockLottos);
        lottoRank.when(() -> LottoRank.valueOf(5L, true)).thenReturn(LottoRank.SECOND);
        //when
        List<LottoRank> result = lottoChecker.createLottoRanks(lottos);

        //then
        assertThat(result.contains(LottoRank.SECOND)).isTrue();
        lottoRank.close();

    }

3주 차 아쉬웠던 점

1. TDD를 위해 구매한 테스트 주도 개발 서적을 많이 읽지 못하였습니다.
2. 3주 차 과제를 마무리한 후 알게 된 IntelliJ의 다이어그램 기능을 통해서 설계된 클래스들을 전체적으로 살펴보았는데 처음에 의도한 대로 클래스들의 관계가 맺어지지 않은 곳들이 있다는 것을 발견하게 되었습니다.
3. 과제 수행 시간이 굉장히 오래 걸렸습니다. 기존 과제들은 목요일에 시작하면 토요일이나 일요일에 끝내고 리팩토링을 진행했던 반면에 이번 과제는 월요일까지 모든 기능들을 완성하지 못하고 시간의 압박을 느꼈습니다.
4. 정적 팩토리 메서드를 도입하긴 하였으나 숙련도가 높지 않아서 객체를 생성하는 과정의 코드가 복잡하게 완성되었습니다.

 

 

4주 차의 목표

1. TDD 개발 서적을 하루에 정해진 분량 동안 읽고 과제를 수행하는 방식으로 과제와 TDD 서적을 병행해가면서 TDD에 대한 이해도를 높일 것입니다.
2. 정적 팩토리 메서드에 대한 이해도를 높이고 조금 더 깔끔한 메서드를 만들 것입니다.
3. IntelliJ의 다이어그램 기능을 중간중간 활용하여서 내가 설계한 대로 객체들의 관계가 설정되고 있는지 확인하여 더 깔끔한 객체지향 설계를 진행할 것입니다.
4. 점점 더 객체에 대한 이해도가 높아지면서 분리된 객체들이 많아지고 있습니다. 그럼에 따라서 컨트롤러가 비대해짐을 느껴서 비즈니스 로직을 분리한 5layered 아키텍처의 service를 공부하고 도입해 볼 것입니다.

 

 

 

끝으로

새롭게 배울 것이 1주차 2주차에 비해서 굉장히 많았던 탓인지 유독 힘들게 느껴졌던 3주 차였습니다. 그러나 매 주차가 지날 때마다 과제가 쉽게 느껴지는 게 아니라 어렵게 느껴진다는 것은 그만큼 제가 성장곡선의 오르막을 걸으면서 가파르게 성장을 하고 있다는 증거일 것입니다. 이렇게 3주 차가 어려웠던 만큼 실력에 있어서 많은 성장이 있었음을 스스로 느끼고 있고 마지막 주차가 남은 것에 대해서 기쁜 것이 아니라 마지막 1주 차동안 어떻게 더 공부해야 내가 더 많은것을 프리코스에서 얻어갈 수 있을까라는 고민과 아쉬움을 느끼게 되었습니다. 하지만 이런 고민과 아쉬움도 잠시이고 프리코스를 참여하면서 앞으로 어떻게 공부를 해야 할지에 대한 확신이 생겼기 때문에 남은 1주차도 그동안 그랬던 것처럼 최대한 즐기면서 열심히 공부해나갈 예정입니다. 감사합니다.

마지막 주차도 힘내자

 

+ 3주 차 로또 풀리퀘스트

https://github.com/woowacourse-precourse/java-lotto-6/pull/672