8기 프리코스 여정의 끝

이프·2026년 1월 13일

woowacourse

목록 보기
9/9

⚠️ 주의: TL;DR

최종 코딩 테스트 준비

때는 2025년 12월 29일, 프리코스에서 합격해 최종 코딩 테스트에 임하게 되었다.
7기 최종 코딩 테스트에서 TDD + 확장성 등 짧은 시간 내에 모든 것을 보여주려고 욕심 냈었다 😂 그래서 이번에는 오만함을 버리기로 결정했다.

스터디 진행

합격한 스터디원과 함께 최종 코딩 테스트 스터디를 통해 준비했다.

스터디 주요 진행 방식

  1. 난이도가 어려운 7기부터 문제들을 풀어나간다.
  2. 스터디 시간에 5시간 내에 문제들을 해결한다. (Application Test 통과 위주)
  3. 각 문제에 대해 코드 조각으로 사용할 수 있는 유틸성 코드는 코드 조각 모음에 작성한다.

이렇게 하루 두 문제를 해결 할 때도 있고, 한 문제를 해결 할 때도 있었다.
그리고 최종 코딩 테스트에 가까워질수록 문제 풀이 시간을 3시간, 2시간 30분으로 줄여갔다.

결과적으로 가장 어려웠던 출석부 문제도 3시간 내에 해결할 수 있게 됐고, 4기의 자판기 같은 경우 1시간만에 해결 할 수 있었다.

깨달은 점

사실 스터디를 진행하면서 가장 큰 부분은 시간 단축이 아닌, 설계 관점의 전환이었다.
항상 도메인에 대해 생각하다보니 설계를 도메인 중심으로 했는데, 스터디에서 빠르게 문제를 해결하는 것에 집중하다 보니 행동 중심으로 바라보게 된 것이다.

예시로 그림으로 표현해보면 아래와 같다.

도메인 중심

도메인 중심으로 설계하게 되면 도메인에 해당 하는 모델을 설계하고 Client, 즉 콘솔 프로그램에선 Controller에서 조합을 한다.

해당 방식의 장점은 무엇이고 단점은 무엇일까? 내가 생각 할 때는 아래와 같다.

  • 장점: 견고한 도메인 모델, 도메인 이해도
  • 단점: 도메인 이해 비용 (생산성 저하), 잘못된 설계시 전체 수정 필요

그럼 반대로 행동 중심은 어떨까?

행동 중심

이 방식은 Client가 필요한 기능이 있을 때, 관련 도메인 모델에서 기능을 구현하는 방식이다. 그리고 요구사항에서 추가 기능이 필요한 시점에 관련 도메인 모델에서 추가 기능을 구현한다.

이 때 추가되는 기능은 도메인 모델 A에 생성될 지, 도메인 모델 B에 생성 될 지는 요구사항에 따라 다르다. 나는 이 시점을 미션의 입・출력 시점만 바라봤다.

예를 들어 금액 입력 -> 금액 검증 기능, 로또 구매 후 출력 -> 로또 구매 기능로 볼 수 있다. 해당 방식의 장단점은 무엇일까?

  • 장점: 유즈케이스(행동 기반) 기반으로 필요한 기능에 집중, 생산성 향상
  • 단점: 도메인 중심에 비해 비교적 가벼운 도메인 모델: 변경이 잦을 수 있음

둘의 비교

도메인 중심은 SW 공학에서 소개되는 워터풀 방법론과 밀접한 방식으로 느껴졌다.
반면, 행동 중심은 애자일 방법론과 밀접한 방식으로 느껴졌다.

행동 중심은 BDD(Behavior Driven Development)라고도 불리는데, 이는 TDD와도 밀접하게 연관되어있다. 기본적으로 테스트 코드를 작성하는 예시를 보자.

@Test
void test() {
	// given: 로또 금액(500)이 주어졌을 때
    // when: 유효한 금액인지 검증한다
    // then: 500원 단위이므로 true를 반환한다.
}

이렇게 요구사항의 각 행동에 대해서 필요한 기능만 구현하면, 생산성을 매우 높일 수 있었기 때문에 행동 중심으로 최종 코딩 테스트를 준비했다.

참고: 스터디 자료 ( 링크 )


출발 전 시련

1월 9일 금요일, 최종 코딩 테스트까지 하루가 남은 시점이었다.
부모님이 서울에 눈이 많이 온다는데 괜찮겠냐고 해서 검색을 해보니 강설이라고 한다

경향신문 기사

다음날 오전 6시 30분 출발행 시외버스를 예매가 되어있었는데 뭔가 쎄함이 느껴졌다... ㅋㅋㅋ
서울에는 "눈이 오면 차가 많이 막히나..?"
나는 경상도에 살고 있으니까 눈으로 인한 교통 부재를 경험한 적이 없었다.

중앙일보 기사

그래서 검색을 해보니 서울에는 눈으로 인해 3시간 정도 차가 막히는 일도 있었다고 한다... 6시 30분에 버스를 타면 11시 도착 예정이었는데, 2시간 정도의 여유가 있으면 충분할까 걱정이 되기 시작했다.

그래서 카카오톡 오픈 채팅방을 이리저리 검색해서 사람들에게 경험을 물어보고, 교통 공사에 전화해서도 2시간 이상 밀릴 수 있는지 물어봤다.
교통 공사: "고객님 그건 당연히 생각해볼 수 있어요. 두 시간 이상 밀리는 것은 당연합니다."

3번의 도전인만큼 억울하게 기회를 놓치고 싶지 않았다. 그래서 여러 교통편을 알아봤고 우리집에서 기차를 타기 위해 경유하는 것은 비용, 시간, 나의 길치 이슈(?) 때문에 00시 30분 출발행 버스로 예매를 변경했다.

불과 몇 시간 전에 계획했던 모든 일정이 뒤틀리면서 불안감이 엄습해왔다... 00시 30분 버스를 타면 코테까지 약 7시간 이상을 대기해야 했던 것이다..

서울 도착

새벽이라 길이 안막혀서 그런지 오전 4시 반에 동서울 터미널에 도착했다.
(🤓 촌놈 서울 구경왔습니다 ~)

나는 버스 같은 곳에서 못잔다... 그래서 조금이라도 수면을 취하자는 마인드로 냅다 찜질방에 들어갔다. "빠르게 씼고 5시부터 수면을 취하면 5시간 이상은 잘 수 있겠지?"라는 생각으로 봤는데... 늦게 와서 그런지 남은 매트는 없고, 사람들은 꽉 차고, 코 골고... 망했다! 🤣

안그래도 사람 많은 곳은 조금 부담감을 느끼는데, 생각보다 사람이 너무 많았다. 그나마 다행인 것은 독서실이 있었는데, 거기서 공부를 좀 하다가... 웹툰 보며 그냥 밤을 샜다..!

스터디원과의 카톡

사실 이 때부터 "아 컨디션은 그냥 개망했구나"라는 생각으로, 이때까지 준비해 온 나를 믿기로 했다.

그렇게 오전 10시에 잠실역 스타벅스에 도착해서, 쌓여버린 스타벅스 기프티콘으로 커피 한잔 마시고 대기했다. 태희님 덕분에 커피 잘마셨습니다!

근데 예보와 달리 눈이 안와서 조금 당황했다...
나는 지금까지 뭔 쇼를 한거지..?

시험 장소로 이동

심심하기도 하고 오전 12시가 되기 20분~30분 전, 삼성 생명 건물 7층으로 미리 이동했는데 내가 첫 번째로 도착했다 👏👏

정확히는 나를 포함해서 총 세 명이 같은 시각에 엘리베이터를 타고 도착했다. 기다리면서 간단하게 대화도 나눴고 덕분에 앞에서 고생했던 기억들을 잊게됐다.

지금와서 생각하니 내부 모습좀 찍어둘 걸 후회되기도 한다. 평소에 사진을 안찍어서... ㅠ
그래도 이건 찍었다.

ㅋㅋㅋㅋㅋㅋㅋㅋㅋ 지금 생각하도 웃긴데 화장실에 가글이 있었는데, 저런게 붙어있어서 이건 못참고 찍어버렸다...

이제 착석 후 기본적인 노트북 세팅을 마무리하고 대기하고 있는데, "혹시 이프님 맞으시죠??" 라고 옆에서 누가 물었다. 인생한접시라는 닉네임을 사용하시는 분이셨는데, 나를 알고 계셔서 너무 놀랬고 감사했다..!

그래서 함께 준비 과정, 우테코에 대한 마음가짐, 개발자로서 생각 등을 나누다 접시님께서 건물 구경좀 해보자고 하셔서 Ok하고 구경좀 하러 다녔다. 이 때, 시험용 A4 용지를 보고 접시님께 장난 삼아 "포비님 사인 받아볼까요?"라고 말했는데... 접시님께서 좋다고 바로 포비에게 사인해주실 수 있냐고 물으러 갔다. 행동력 👍👍👍

감사합니다. 포비, 인생한접시님 😊


시험 시작

이렇게 시간을 보내다 13시가 되어 최종 코딩테스트가 진행됐다.
7기 때도 그랬는지 기억이 안나는데 최종 코딩테스트가 시작되고 안내를 하는데... 컨디션 때문인지 집중하기가 너무 힘들었다.

안내 내용은 문제를 읽어보면 알 수 있는 내용이었다.

안내 내용 요약

  • 코딩 테스트 4시간
  • 소감문 작성 1시간
  • AI 사용 불가

구현부터 먼저!

먼저 이번 문제는 행성 로또로 그냥 로또 문제와 거의 유사했다.

스터디에서 진행했던 방식으로 행동 중심으로 간단하게 문서를 정리했다.

1. 구입 금액 입력
    * 로또 1장은 500원
    * 로또 구매
        * 번호 출력 (오름차순)
        * 로또 번호는 1~30
        * 중복되지 않는 5개
2. 당첨 번호 입력
    * `, `로 구분
    * 중복되지 않는 5개
3. 보너스 번호 입력
    * 1 ~ 30
4. 당첨 통계 출력
    * 일치한 번호 개수별 금액과 당첨 개수 출력
5. 예외
    * 사용자가 잘못된 값을 입력할 경우 IllegalArgumentException을 발생시키고,
    * "[ERROR]"로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시 받는다.

이렇게 구현부터 먼저하는 방식으로 문제 해결 자체는 약 1시간 정도가 소요됐다.

도전 과제 선택

도전 과제로는 추가 기능 구현과 리팩토링이 있었는데 리팩토링을 선택했다. 이유는 세가지 였다.

  • 관리할 수 없는 코드는 기능을 추가하지 않는다.
  • 미래에 대한 기능을 생각하는 것은 YAGNI를 위반한다.
  • 개발 사이클은 기능 구현과 리팩토링이다.

스터디 중 읽었던 클린 코드가 떠올랐고, 깨끗하게 유지되지 않은 코드는 추가 기능에 따라오는 리스크가 너무 크다는 내용이 있었다. 그리고 SW 설계 3대 원칙에 부합하다는 것이다.

만약 추가 기능을 구현했을 때, 기존 코드에 변경이 발생한다면?

이 경우 기존 코드가 제대로 동작하는지 또 확인해야 된다.
즉, 추가 비용인 것이다.

만약 이 기능이 필요하겠지? 라고 생각하고 추가 기능을 구현한다면?

기획에서 요구하지 않은 것을 추가해서 예상치 못한 동작이 될 수 있다.
즉, 먼 미래에 대한 생각은 '나만의 생각'인 것이다.

만약 요구사항 A개발 사이클이 끝나고 곧바로 B개발 요구사항이 생긴다면?

개발 사이클이 기능 개발이 기준인 경우, "기획자님 기능 A개발을 완료했습니다." -> "어 그럼 이제 바로 B개발을 진행해볼까요?" -> "B개발"
이 경우 요구사항에 의한 기능 변경이 발생하고, 이것을 다시 재검증하지 못한다면 기술 부채가 될 것이고, 결국 클린 코드에서 말하는 패망의 길로 가게 된다.

만약 개발 사이클을 기능 개발 + 리팩토링을 기준으로 잡는다면, 기획자에게 기능 B 개발 요구를 받더라도 유지보수의 이점 때문에 기술 부채를 사전에 차단할 수 있다.

그래서 이런 나만의 철학으로 리팩토링을 선택했다.

도전 과제 진행

  1. 우선 안정적인 코드를 위해 커버리지를 끌어 올려야 한다.
    각 도메인 모델에 대해 테스트를 작성해서 커버리지를 100%로 유지했다.

  2. 견고한 예외 처리를 해야 된다.
    만약 사용자 A가 로또를 구매했을 때 700원을 입력한다면, 사용자는 2개의 기댓값을 가질 수 있다.

    • 500원 단위로 입력하라는 예외
    • 200원 잔돈 반환

    여기서 200원의 잔돈을 반환하는 것은 추가 기능이다. 나의 목표와는 맞지 않기 때문에 더 상세한 예외를 던져주는 방식을 선택하게 됐다.

  3. 클린 코드로 깔끔히 리팩토링
    클린 코드에서 과거에 본인이 작성한 코드를 봤을 때도, 이해하기 힘들다는 내용이 포함되어 있다. 그래서 매직 넘버 제거, 단일 진실 공급원 원칙, Tell Don't Ask 원칙, 짧은 메서드로 가독성 향상(메서드 10줄 이하 유지) 등의 리팩토링을 진행했다.

아쉬운 점

이 도전에서 내가 생각한대로 진행하지 못한 것도 있었다.

public class WinningNumber {

    private final Lotto winningLotto;
    private final LottoNumber bonusNumber;

    public WinningNumber(Lotto winningLotto, LottoNumber bonusNumber) {
        if (winningLotto.hasNumber(bonusNumber)) {
            throw new IllegalArgumentException("당첨 번호와 보너스 번호는 중복될 수 없습니다.");
        }
        this.winningLotto = winningLotto;
        this.bonusNumber = bonusNumber;
    }

    public Map<Integer, Integer> analyzeStatistic(Lottos purchaseLottos) {
        Map<Integer, Integer> winningStatistic = new HashMap<>();
        purchaseLottos.forEach(purchaseLotto -> {
            int matchCount = purchaseLotto.countSameNumber(winningLotto);
            boolean bonus = purchaseLotto.hasNumber(bonusNumber);

            LottoRank lottoRank = LottoRank.match(matchCount, bonus);
            winningStatistic.put(lottoRank.rank(), winningStatistic.getOrDefault(lottoRank.rank(), 0) + 1);
        });
        return winningStatistic;
    }
}

당첨 번호에서 당첨 통계를 분석하는 로직이다.
나는 구매한 로또 목록을 Lottos라는 일급 컬렉션을 활용해 당첨 통계를 생성했다.

public void forEach(Consumer<Lotto> action) {
    lottos.forEach(action);
}

처음에는 단순히 getter를 활용해 Lottos.lottos()로 당첨 통계를 처리하도록 했는데, 반복처리가 아닌 Stream.map()을 활용하고 싶었다.

하지만 영어를 못해서 ㅠ 이것만 보고 어떻게 처리해야 할 지 감이 안왔다. 서칭을 통해 해볼까 했지만, 시간이 너무 부족해서 우선은 forEach를 활용했고 여유가 되면 map으로 개선해보려했지만... 실패했다. 😭😭😭

// Lottos
    public <R> Stream<R> map(Function<? super Lotto, ? extends R> mapper) {
        return lottos.stream().map(mapper);
    }
    
// WinningNumber
    public Map<Integer, Integer> analyzeStatistic(Lottos purchaseLottos) {
        return purchaseLottos.map(lotto -> LottoRank.match(
                        lotto.countSameNumber(winningLotto),
                        lotto.hasNumber(bonusNumber)
                ))
                .collect(Collectors.groupingBy(
                        LottoRank::rank,
                        Collectors.summingInt(e -> 1)
                ));
    }

사실 그냥 T만 Lotto로 바꿨으면 됐는데 컨디션부터 시간이 부족해 급해지는 마음에 생각이 흐트러졌던 것 같다. 그리고 map을 반환하지 않고 간단히 stream만 반환했어도 됐는데... 당시에 생각하지 못했다 ㅋㅋㅋ

소감문(회고문) 작성

어쨌든 이렇게 마치고 남은 1시간 동안 소감문을 작성했다.
소감문에는 해당 포스트에 작성한 내용들을 축약해서 포함했는데, 소감문 작성 폼이 markdown이 아닌 단순한 텍스트 기반이라 내가 쓰면서도 가독성이 부족해... 의식의 흐름대로 작성했다.

그렇게 소감문 작성 중간이었나? 감독관님께서 "먼 지역에서 오신 분들도 계시기 때문에, 다 작성하시면 나가도 됩니다."라는 말을 듣고 호다닥 마무리했다.

1월 9일 아침 7시쯤 기상해서 약 34시간동안 깨어있는 상태에 한끼도 안먹은 상태였어서 정말 죽을 것 같았다...(하루에 큰 그릇 5끼 정도를 먹음)

그런데 만약 6시까지 모든 시간을 다 써버린다면 집으로 돌아가는 마지막 시외버스가 6시 30분이었는데, 이거 놓쳐버리면 서울 한복판에서 죽을 수도 있겠다는 생각때문에 호다닥 마무리 했다 ㅋㅋㅋ

그럼에도 소감문에는 내가 생각하는 모든 핵심적인 내용들을 다 담았다고 느껴서 후회는 없었다.


마치며

프리코스(10월)부터 최종 코테까지 길다면 길고, 짧다면 짧을 수 있는 3개월동안 몰입 할 수 있었다. 이 과정 속에서 당일날 엄청난 우여곡절들이 있었지만, 말도 안되는 최악의 상태에서 좋은 결과로 최종 코딩테스트를 마무리 할 수 있었다?

이건 내가 정말 열심히 준비했다는 근거라고 생각한다🔥

과거에 나였다면 핑계부터 댔을 것이다.
"아 컨디션이 안좋아서 망했다", "아 공부를 못해서 망했다"
이번에도 34시간 이상 무숙취, 공복 상태, 장시간 이동 등 핑계를 댈 것은 정말 많다. 하지만 이런 외부적인 요인들이 있었음에도 불구하고 나는 좋은 결과를 냈다고 생각한다.

결국 이번 8기를 준비하는 과정에서 깨달은 것은 후회하지 않을 정도로 노력했으면, 외부적인 요인은 방해물이 되지 않는다는 것이다.

6기에 이어 8기까지 많은 실패가 있었지만 실패들을 거듭하면서, 개발 역량은 물론이고 점점 더 단단해지는 마인드를 얻게된 것 같다 :)

결과까지 약 10일... 나에게 휴식이란 없다. 스타트업 유지보수 기회가 주어졌으니, 우테코 전까지 여기에 집중할 것이다! (물론 무보수다 😂😂)

profile
if (이런 시나리오는 어떨까?) then(테스트로 검증하고 해결) else(다음 시나리오 고민)

0개의 댓글