빙응의 공부 블로그

[우테코]우아한 테크코스 1~3주차 회고하기 본문

후기

[우테코]우아한 테크코스 1~3주차 회고하기

빙응이 2025. 11. 7. 00:21

 

 

이번에 우아한 테크코스 1~3주차를 하고 오픈미션을 받게 되면서 작성하는 회고록입니다.

 

 

우테코를 신청한 계기?

저는 지난 8월에 대학교를 졸업했습니다. 학교를 다니는 동안 백엔드 개발자가 되고 싶다는 꿈을 꾸며, 여러 대회에 나가고 팀 프로젝트도 하면서 나름 열심히 달려왔어요.

그런데 이상하게도, 항상 마음 한켠엔 ‘내가 잘하고 있는 걸까?’, ‘지금 이 정도면 충분할까?’ 하는 불안함이 있었습니다.
코드를 짜고 프로젝트를 만들긴 했지만, 막상 제 실력이 어느 정도인지, 어떤 부분이 부족한지 스스로 판단하기가 참 어려웠어요.

그러다 졸업하고 한 달쯤 지났을 때, 우아한테크코스 모집 공고를 보게 됐습니다.
“아, 이거다.” 싶었어요.
단순히 코드를 잘 짜는 것보다 같이 성장하는 법을 배우고, 제 한계를 정확히 마주할 수 있는 곳이라는 생각이 들었거든요.

그래서 지원했습니다.

이번 기회를 통해 저 스스로를 더 깊이 들여다보고, 부족함을 인정하면서도 성장해 나가는 개발자가 되고 싶습니다.

 


1주차. 계산기 만들기

quddaz/java-calculator-8 at quddaz

1주차 미션은 비교적 쉬운 주제인 계산기 만들기였습니다.
겉보기에는 단순한 구현 문제처럼 보였지만, 막상 해보니 몇 가지 예외 상황과 요구사항에 빈틈이 있었습니다.

결국 문제를 그대로 따르기보다, 직접 상황을 설계하고 예외를 정의하면서 만들어야 하는 과제였어요.
“그냥 돌아가는 코드”보다는 “의도적으로 구조를 설계하는 연습”이 된 것 같습니다.

이번 주차에서 제가 특히 중요하게 생각한 것은 세 가지입니다.

 

1. 객체지향적 사고와 가독성

  • 단순히 기능을 나누는 게 아니라, 책임을 명확히 분리하려고 노력했습니다.
  • 메서드 하나하나가 ‘왜 존재하는가’를 설명할 수 있도록 고민했어요.

2. 코드 컨벤션과 깃 컨벤션 지키기

  • 협업이 아니더라도, 일관된 규칙을 지키는 습관이 중요하다고 생각했습니다.
  • 깃 커밋 메시지를 의미 있게 작성하면서 스스로의 개발 흐름을 정리할 수 있었어요.

3. 예외는 철저히, 테스트는 편하게

  • 잘못된 입력이나 엣지 케이스를 꼼꼼히 다루는 것이 생각보다 중요했습니다.
  • 테스트가 용이하지 않으면 그것은 좋은 설계가 아니다라는 생각을 하며 구현했습니다.

부족했던 점

이번 미션에서는 오랜만에 순수 자바로 구현을 진행했습니다.
평소에는 스프링 기반으로 프로젝트를 진행했기 때문에, 제 실력이 ‘Java’보다는 ‘Spring’에 더 집중되어 있었다는 걸 새삼 느꼈습니다.

물론 스프링을 잘 다루는 것도 중요한 역량이지만, 프레임워크가 많은 걸 대신해주는 만큼 기초적인 설계 감각이나 객체지향 원칙을 직접 체감할 기회가 적었던 것 같습니다.
이번 미션을 하면서 오히려 ‘스프링이 왜 이렇게 설계되었는가’ 를 다시 돌아볼 수 있었어요.

특히 계층화 구조나 단일 책임 원칙(SRP) 같은 개념의 필요성을 몸으로 느꼈습니다.
스프링에서는 이미 잘 분리되어 있어서 당연하게 넘겼던 부분들이,
막상 순수 자바로 구현하려니 “아, 이래서 구조가 중요하구나” 하고 다시 깨닫게 되었어요.

 

결론적으로 1주차에서 제일 많이 느꼈던 건
기초적인 감각의 필요성이였습니다.

 

2주차. 레이싱 게임 만들기

quddaz/java-racingcar-8 at quddaz

2주차 미션은 1주차보다 한 단계 난이도가 높아진 레이싱 게임 만들기였습니다.
1주차에서는 도메인이 하나뿐이라 비교적 단순했지만, 이번에는 여러 개의 도메인이 유기적으로 연결되는 구조를 설계해야 했습니다.
그만큼 더 복잡하고 세밀한 설계가 필요했어요.

이번 미션을 진행하면서 제가 특히 중요하게 생각한 것은 다음과 같습니다.

1. 도메인은 한 가지 책임만 가진다.

  • 각 도메인이 어떤 역할을 하는지 명확히 구분하려고 했습니다.
  • 기능을 나누는 것보다 “이 객체가 존재하는 이유”를 먼저 고민했습니다.

2. 도메인의 속성은 private으로 감추기.

  • 외부에서 함부로 접근하지 못하게 하여, 객체 내부의 상태를 보호하려 했습니다.
  • “다른 객체가 이 도메인을 몰라도 돌아가야 한다”는 원칙을 지키는 게 생각보다 중요했습니다.

3. 테스트하기 쉬운 코드를 작성하자.

  • 이번 미션을 통해 테스트 가능한 코드가 결국 좋은 코드라는 걸 다시 느꼈습니다.
  • 설계 단계부터 테스트를 염두에 두니, 자연스럽게 코드의 결합도가 낮아졌어요.

부족했던 점(DIP VS 전략)

이번 과제에서 저는 난수 생성 로직을 전략 패턴 형태로 분리하여 테스트 용이성을 확보했습니다.

// 난수 생성 전략 패턴을 위한 인터페이스 구현체
public class RandomNumberGenerator implements NumberGenerator {
    private final int min;
    private final int max;
    public RandomNumberGenerator(int minValue, int maxValue) {
        this.min = minValue;
        this.max = maxValue;
    }
    @Override
    public int generate() {
        return Randoms.pickNumberInRange(min, max);
    }
}

하지만 코드 리뷰 피드백에서 다음과 같은 의문이 제기되었습니다.

 

“이 구현이 정말 전략 패턴인가, 아니면 DIP(의존 역전 원칙)에 더 가깝지 않은가?”

저의 의도는 전략 패턴을 적용한 것이었지만, 리뷰를 보면서 DIP의 관점에서도 충분히 해석될 수 있겠구나 하는 생각을 하게 되었습니다.
그래서 제 설계 의도를 다음과 같이 정리했습니다.

 외부 랜덤 메서드를 직접 수정할 수 없는 상황에서 테스트하기 쉬운 구조를 만들기 위해 전략 패턴처럼 인터페이스를 분리했고, 추후 난수 생성 방식이 바뀌거나 여러 생성 전략이 필요해질 때 전략을 교체할 수 있도록 준비한 것입니다. 코드 관점에서는 의존 역전 원칙(DIP)을 적용한 형태이지만, 제 의도는 분명히 전략 패턴에 가깝습니다.

제가 디자인 패턴에 접근할 때 중요하게 생각하는 것은 개발자의 의도입니다. 디자인 패턴은 단순한 문법이나 기법이 아니라 문제에 접근하는 사고 방식이라고 생각합니다. 같은 구조를 두고도 누군가는 이를 DIP의 적용으로 보고, 다른 누군가는 정책 주입이나 템플릿 메서드의 변형으로 볼 수 있습니다. 결국 중요한 것은 왜 그렇게 설계했는가, 그리고 그 의도가 코드와 문서로 명확히 드러나느냐입니다.

 

코드 리뷰를 하며 다음과 같이 생각하게 되었습니다.

 

결국 협업에서는 코드 그 자체보다 의도를 얼마나 명확히 전달하느냐가 더 중요하다.
아무리 구조가 좋아도 팀원이 그 의도를 이해하지 못한다면, 그건 좋은 설계가 아니다.

 

부족했던 점( MVC 구조에 대한 고민 )

두번째는 디렉토리 계층에 대한 이야기입니다. 

이전 과제에서는 계층 구조를 명확히 나누지 못했던 점이 아쉬워, 이번에는 MVC 패턴을 적용해 도메인과 UI, 컨트롤러를 구분하려고 했습니다.

하지만 미션을 마치고 제출한 뒤, 문득 이런 생각이 들었습니다.

“굳이 MVC여야 했을까?”

 

그래서 다음 문제부터 mvc 말고 역할에 따라 계층을 정하기로 했어요.

이번 과제는 Spring처럼 요청과 응답이 오가는 웹 애플리케이션이 아닌, 하나의 프로그램 안에서 모든 로직이 동작하는 콘솔 기반 구조였습니다.
그렇다 보니 MVC를 억지로 끼워 넣은 듯한 느낌이 들었고,
“컨트롤러가 정말 필요한가?”, “서비스가 단순히 객체의 메서드를 실행하기만 하는데 계층을 따로 둬야 할까?”
같은 의문이 자연스럽게 생겼습니다.

이 경험을 통해, 패턴을 적용하는 목적이 분리 자체에 있는 것이 아니라, 문제 해결에 얼마나 적합하냐에 달려 있다는 것을 깨달았습니다.
그래서 3주차 미션에는 MVC라는 틀에 얽매이기보다, 역할과 책임에 따라 자연스럽게 계층을 나누는 방식으로 접근 했습니다.


3주차. 로또 만들기

3주차 미션은 2주차보다 한층 더 복잡해진 로또 게임 구현이었습니다.
이번 주차에서는 단순히 기능 구현보다, “어떻게 하면 유지보수하기 좋은 구조를 만들 수 있을까?”를 중심으로 고민했습니다.

또한 몇 가지 새로운 코드 컨벤션이 추가되었죠.

  • JUnit 5 + AssertJ로 기능별 테스트 작성
  • Java Enum을 적극적으로 활용해 프로그램의 구조를 단순화

그래서 이번 주차의 핵심 목표를 다음과 같이 정했습니다.

  1. 2주차 때 고민했던 “자연스러운 계층 나누기”를 실천해보자.
  2. Enum을 적극적으로 활용해 의미 있는 상태 표현을 해보자.
  3. 테스트 코드를 더 견고하게 만들어보자.

1. Enum을 활용한 설계

이번 미션의 핵심은 Enum의 적극적인 활용이었습니다. 이전까지 Enum을 단순히 “상수 모음” 정도로만 썼다면,

이번엔 상태와 로직을 함께 가지는 객체로 설계했습니다.

 

예시 1. 로또 등수 관리 (LottoRank)

public enum LottoRank {
    MISS(0, 0L, false, ""),
    FIFTH(3, 5_000L, false, "3개 일치 (5,000원) - %d개"),
    ...
    FIRST(6, 2_000_000_000L, false, "6개 일치 (2,000,000,000원) - %d개");

    public static LottoRank findRank(int matchCount, boolean isBonusMatch) {
        return Arrays.stream(values())
            .filter(rank -> rank.isMatched(matchCount, isBonusMatch))
            .findFirst()
            .orElse(MISS);
    }
}

이전에는 등수 계산을 if-else로 처리했지만,
지금은 각 Enum이 스스로 비교 로직을 갖도록 했습니다.

즉, LottoRank가 단순 데이터가 아닌 “행동하는 상태”가 되었습니다.
이 덕분에 코드 가독성이 높아졌고, 테스트도 훨씬 간결해졌습니다.

 

예시 2: 상수 관리 (LottoBuyConfig)

public enum LottoBuyConfig {
    LOTTO_PRICE(1000);

    private final int value;

    public static boolean isValidLottoBuyAmount(int amount) {
        return amount > 0 && amount % LOTTO_PRICE.getValue() == 0;
    }
}

이전에는 상수를 static final 변수로 관리했지만, Enum으로 옮기니 코드 의미가 더 명확해졌습니다.
특히 상수에 검증 로직을 함께 넣을 수 있어, “상수를 관리하는 객체”라는 성격이 분명해졌습니다.

또한 상수 값 변경 과정을 더 직관적으로 확인하여 유지보수성을 높일 수 있었습니다.

 

2. 구조 설계 — 계층보다 ‘역할 중심’으로

이전 주차에서는 MVC 패턴을 억지로 끼워 맞추려다 어색했던 경험이 있었습니다.
그래서 이번에는 ‘계층 구조를 지키는 것’보다,
각 객체가 왜 존재하는가, 어떤 책임을 가져야 하는가에 더 집중했습니다.

  • Lotto: 한 장의 로또를 표현하는 핵심 도메인
  • Lottos: 여러 장의 로또를 관리하는 컬렉션 객체
  • WinningLotto: 당첨 번호와 보너스 번호를 관리하는 객체
  • LottoRank: 일치 개수에 따라 등수를 판별하는 Enum
  • LottoResult: 당첨 결과 집계와 수익률 계산을 담당하는 객체

이번 미션에서는 WinningLotto, Lotto, LottoResult 등 여러 도메인이 서로 긴밀하게 연결되는 구조를 설계했습니다.
그 과정에서 “이 객체는 어떤 책임을 가져야 하는가?”라는 질문을 기준으로 의사결정을 내렸고,각 도메인 간 불필요한 의존을 최소화하는 것에 초점을 맞췄습니다.

그 결과, 객체들이 서로를 강하게 의존하지 않고도 협력할 수 있는 구조가 완성되었습니다. 이는 테스트 작성 시에도 큰 도움이 되었고, 코드의 확장성과 유지보수성 모두 개선되었습니다.

 

느낀 점

이번 미션을 하며 가장 크게 느낀 점은 Enum의 진짜 가치입니다.
단순히 상수를 모으는 게 아니라, 도메인 로직을 캡슐화하는 수단이 될 수 있다는 걸 배웠습니다. 또한, 코드의 “형태”보다 “의도”가 더 중요하다는 걸 다시 한번 느꼈습니다.

Enum이든 클래스든, 어떤 구조를 쓰더라도 “왜 이렇게 설계했는가”가 명확해야 팀원들이 이해할 수 있습니다. 앞으로는 이런 의도를 드러내는 설계에 더 집중하려 합니다.

 


 

4주차. 오픈 미션

오픈 미션은 아직 비밀입니다.

 

저는 프로젝트 경험이 몇 번 있어, 어떤 도전이 좋을지는 아직 잘 모르겠습니다.
하지만 이번에는 코틀린을 배워서 적용해보려고 합니다.
학창 시절에는 Java 하나만으로도 충분히 벅찼지만, 이번 도전 기회를 통해 밀고 미뤄두었던 코틀린을 활용해 제가 해보고 싶은 것을 만들어보고 싶습니다.
시간은 부족하겠지만, 최선을 다해 임하겠습니다.

 

[Kotlin] 코틀린(Kotlin) 기본 문법 (Java 개발자 관점)

 

[Kotlin] 코틀린(Kotlin) 기본 문법 (Java 개발자 관점)

저는 평소 자바를 중심으로 개발해왔습니다. 이번에 코틀린을 배우고 활용할 기회가 생겨, 새로운 언어로의 도전을 통해 개발 역량을 넓혀가고자 합니다.1. 변수 1-1 변수 선언Kotlin은 변수 선언

quddnd.tistory.com

[Kotlin] 코틀린(Kotlin) 심화 문법 (Java 개발자 관점)

 

[Kotlin] 코틀린(Kotlin) 심화 문법 (Java 개발자 관점)

5. 람다5-1 람다Kotlin의 람다식은 자바보다 더 간결합니다. 파라미터가 하나일 경우 it 키워드로 암시적 참조가 가능하며, 람다가 마지막 인자이면 괄호 밖으로 뺄 수 있습니다.(Trailing Lambda)val list

quddnd.tistory.com