우테코 7기/레벨 1

[우테코 7기] 로또 미션 회고

nourzoo 2025. 3. 1. 17:31

 

첫 미션이었던 로또 미션🎫

우테코에서의 첫 미션은 로또 미션이었다.

첫 페어프로그래밍 경험이어서 많은 것이 서툴렀고, 심지어는 미션 제출 방법과 순서도 많이 헷갈렸다...

step1과 step2로 이루어져 있고, step1은 페어와 함께 짝 프로그래밍으로 개발한다. 이후 리뷰어의 피드백을 받고, step2에서는 혼자 리팩토링을 진행한다. PR과 리뷰받기를 반복하고 리뷰어가 merge해주면 해당 미션이 끝나게 된다.

나의 첫 페어프로그래밍은 헤일러와 함께 진행했다!

 

Step1️⃣

인터페이스의 사용

주어진 요구사항을 분석하여 리드미부터 작성했다. TDD로 구현할지도 잠깐 고민했지만 TDD는 다음 미션때 확실히 공부하기로 헤일러와 합의를 보고 원래 하던 방식대로 도메인 설계를 해나갔다.

 

고민이 되었던 부분은 로또 머신이 여섯 개의 랜덤한 숫자를 생성해주는데, 테스트코드 작성시 이 랜덤한 값들에 대해 어떻게 테스트를 진행할까에 대한 것이었다.

여기서 인터페이스를 도입해서 테스트 값을 고정시켜주었다.

IntegerGenerator 인터페이스를 상속받는 클래스 RandomIntegerGenerator, FixedIntegerGenerator 를 생성하고, 테스트코드에서는 항상 일정한 로또 번호 6개를 반환하도록 구현했다.

 

프리코스 때는 인터페이스를 어떤 상황에서 작성해야 하는지, 어떻게 작성해야 하는지 몰라서 사용을 못했는데, 요번에 사용법을 배운 것 같다.

테스트하기 어려운 코드가 생기면 인터페이스 추출을 생각하자!

 

스트림(boxed) 사용

for문을 의식적으로 지양하려다 보니까 스트림을 많이 사용하게 되었다. 헤일러가 스트림을 굉장히 잘 다뤄서 옆에서 보고 많이 배웠다.

    public static List<Integer> inputWinningLottoTicket() {
        System.out.println("지난 주 당첨 번호를 입력해 주세요.");
        String winningLottoTicket = Console.readLine();
        validateLottoTicket(winningLottoTicket);
        return Arrays.stream(winningLottoTicket.split(","))
                .mapToInt(Integer::parseInt)
                .boxed()
                .toList();
    }

 

여기서 boxed() 메서드는 IntStream 같이 원시 타입에 대한 스트림 지원을 Stream<Integer> 같은 클래스 타입으로 전환해준다.

 

IntStream : 기본형 int, 박싱/언박싱 발생X

Stream<Integer> : 객체 Integer, 박싱/언박싱 발생O, 이게 일반적인 스트림!

 

위의 예시로 보면 mapToInt() 메서드로는 IntStream으로 변환되므로 이를 boxed() 해주어야 각 정수가 Ineger 객체로 변환이 된다.

 

여러 단축키들

남이 개발하는 것을 실시간으로 보면서 어떤 단축키들을 유용하게 쓰는지 보는 것이 페어프로그래밍의 큰 장점인 것 같기도 하다.

(헤일러한테 많이 배웠다...)

  • Ctrl + Alt + A → 현재 보고 있는 파일 git add
  • Alt + 9 → git log
  • Ctrl + Alt + F → 필드 추출(인스턴스로 추출)
  • Ctrl + Alt + M → 특정 라인블록을 메소드로 추출

 

Step2️⃣

서비스의 비즈니스 로직과 도메인의 비즈니스 로직

네오의 강의에서 다룬 내용이다.

지금까지 나는 하나의 사람에 대해 Crew 이런식으로 클래스를 정의하고, 여러 사람에 대해서는 CrewRepository 이런식으로 클래스를 정의했는데, Repository 와 같은 애플리케이션 로직이 필요하느냐에 대한 얘기였다.

 

처음에는 레이어드 아키텍처가 범주를 분리하는 효과적인 방법이라고 생각했다. 특히 Repository 패턴을 적용하면 애플리케이션 로직을 분리할 수 있어서 좋다고 생각했다. 

 

이와 관련해서 크루들과 얘기를 나누어보았는데, 밍곰이 밍곰 리뷰어께서 남겨주신 코멘트를 공유해주었다.

레이어드 아키텍처에 대해 조금 길게 글을 쓰겠습니다. 마틴 파울러의 POEAA(엔터프라이즈 애플리케이션 아키텍처 패턴)를 살펴보면, 비즈니스 로직 = 도메인 로직 + 애플리케이션 로직으로 정의합니다.
도메인 로직은 익숙할 겁니다. 지금 미션에서 출석 도메인을 구현하고 있는 로직이죠. 애플리케이션 로직은 순수 도메인 로직이 아니지만, 도메인을 구현하기 위해 필요한 코드입니다. 예를 들어 출석 관련 데이터 DB 저장, 출석 후 알림 발송, 출석 데이터 DB 저장 실패 시 롤백 등 예외처리, 에러 분석을 위한 로그 저장, 외부 시스템과 통합하기 위한 메시지 발송 등등이 되겠죠.
기본적으로 애플리케이션 로직은 복잡할 수 밖에 없습니다. 여기다 복잡한 도메인 로직까지 넣게 되면 더 어렵겠죠. 그래서 계층을 분리해 관심사를 쪼개는 아키텍처가 레이어드 아키텍처입니다. Repository에는 DB와 직접적으로 연동하는 관심사(데이터 영속화)를 가진다면, Service에서는 애플리케이션 로직에 관심사를 두게 되는 것이죠.
레벨 1 미션에서는 애플리케이션 로직이라는 것이 없습니다. 그렇기에 레이어드 아키텍처를 도입하지 않고도 미션들을 진행할 수 있습니다. 오히려 이러한 아키텍처를 도입함으로서 레벨 1에서 집중할 수 있는 부분들을 놓칠 수 있다고 생각합니다 ㅠㅠ

 

일단 레벨 1 미션에서는 복잡한 애플리케이션 로직이 없었다. 그래서 도메인 로직을 구현하는 데에 있어서 Repository 패턴을 적용하는 것이 오히려 불필요한 추상화가 될 수 있었다.

예외가 발생하지 않는 것을 테스트

    @DisplayName("로또 번호의 개수가 1 이상 45 이하일 때 정상적으로 로또를 발행하는지 테스트")
    @ParameterizedTest
    @MethodSource("randomLottoNumbers")
    void 로또_테스트(List<Integer> numbers) {
        Assertions.assertThatCode(() -> new LottoTicket(numbers))
                .doesNotThrowAnyException();
    }

 

이번 로또 미션 강의자료에 AssertJ 테스트가 있었는데, 진짜 웬만한 테스트는 다 커버할 수 있는 강의자료이다! 넘 좋았다.

사실 전부 꼼꼼하게 공부하진 못했지만... 그래도 내가 원하는 테스트코드를 찾을 수 있을 만큼은 본 것 같다.

암튼. 보통은 예외가 발생하는 테스트를 assertThrownBy를 던져서 잡게 된다. 반대로 예외가 발생하지 않는 것을 테스트하는 코드를 작성해보고 싶어서 이 테스트 메서드를 공부하게 되었다. assertThatCode도 강의자료에서 발견했다.

 

AssertJ vs JUnit5

원래는 무슨 차이인지 사실 잘 몰랐다. 그리고 그냥 인텔리제이가 자동완성 시켜주는 프레임워크를 사용했었다...

차이점을 쉽게 말하자면 AssertJ는 쩜(.)을 사용한다! 고급지게 말하면 메서드 체이닝을 사용하여 가독성을 높인 방식...

JUnit5는 다양한 검증 메서드가 있지만 메서드마다 다른 형태로 사용해야 한다.

 

assertEquals("이승연", name);

assertTrue(age > 20);

assertNotNull(name);

 

하지만 AssertJ를 보자

 

assertThat(name).isEqualTo("이승연);

assertThat(age).isGreaterThan(20);

assertThat(name).isNotNull;

 

이런 식으로 직관적으로? 더 쉽게 볼 수 있다!

 

미션을 딱 하나 진행했지만, 내가 뇌빼고 쓰던 것들에 숨겨진 의미들을 알게 되는 것 같아서 재밌었다. 개발 지식이 쌓이는 것에 대해 재미를 붙이게 되는 계기가 된 듯. (+ 이펙티브 자바 스터디)

 

아쉬웠던 점😢

기능 단위의 페어프로그래밍

너무 오랜만에 개발을 하는거라 쉽지 않았다. 또 이번 페어와는 시간 단위가 아니라 기능 단위로 네비게이터/드라이버를 나누어 진행했는데 서로 맡은 기능만 개발하다 보니까 네비게이터와 드라이버 역할을 의도된대로 수행하지 못했다는 생각이 들어서 아쉬웠다. (네오도 수업 피드백에서 시간 단위로 15분 혹은 20분씩 바꾸는걸 더 추천한다고 하셨다!)

 

개발 역량

막상 내 앞에 키보드가 오면 머릿속이 하얘지고 뭐부터 건드려야 할지 멘붕상태가 찾아온다... 

다행이 이번 미션에서는 헤일러가 네비게이터 역할을 톡톡히 수행해주어서 나름? 수월하게? 개발했는데, 나 같은 크루 두 명 앉혀놓으면 정말 큰일날 것 같다는 생각을 했다.

이번에 내 개발역량이 최악이라는 사실을 (이미 알고있었지만 더)깨달았고... 앞으로 공부에 시간을 많이 투자해야 할 것 같다.

네오가 '객체지향에는 완벽한 답이 없다. 내가 할 수 있는 한 최대한 객체지향으로 짜자' 라는 말을 강의에서 하셨는데, 앞으로는 개발에 들어가기 전에 설계에 더 시간을 써야할 것 같다.

 

리뷰어와의 티키타카

이번 리뷰어분께서 처음 step1 pr을 날렸을 때 피드백이 많이 오지 않았다... 질문을 많이 받으면서 생각할만한 것을 찾고 싶었는데, 내가 먼저 질문을 드리지 않아서 그런지 거의 피드백 없이 머지를 시켜주셨다... (코드가 고칠 부분이 없었던 건 아닐 것 같다.) 다음 출석 미션 때는 애초에 개발을 진행할 때 생기는 의문점들을 메모장에 적어두면서 하려고 한다. 일부러라도 질문을 만들라고...