1. 서론
안녕하세요! 팡일입니다.
1주차 미션을 마무리 짓고, 팀원들과 스터디를 진행한 게 얼마 전 같은데, 벌써 2주차 프리코스 회고를 작성하게 되었습니다.
2주차 프리코스는 어떤 미션이 출제 되었고, 미션을 수행하면서 마주했던 새로운 스토리들을 기록하고 공유해보려고 합니다.
| 자동차 경주 게임 - 문제 설명
이번 과제에서도 안내 사항은 기능 요구 사항, 입출력 요구 사항, 프로그래밍 요구사항 크게 3가지로 분류되었습니다.
1) 기능 요구 사항
(1) 문제 설명
: 초간단 자동차 경주 게임을 구현한다.
- 사용자는 자동차 이름을 쉼표(,)를 기준으로 입력한다.
- 이름은 5글자 이하만 가능하다.
- 사용자는 몇 번의 이동을 할 것인지 입력한다.
- 주어진 횟수 동안 n대의 자동차는 전진 또는 멈출 수 있다.
- 전진하는 조건은 0에서 9 사이에서 무작위 값을 구한 후, 무작위 값이 4이상일 경우이다.
- 전진하는 자동차를 출력할 때 자동차 이름을 같이 출력한다.
- 우승자를 출력한다.
- 우승자가 여러 명일 경우 쉼표(,)를 이용하여 구분한다.
- 사용자가 잘못된 값을 입력할 경우, '[ERROR]'로 시작하는 메세지와 함께 Error를 발생시킨 후, 애플리케이션은 종료되어야 한다.
2) 입출력 요구 사항
(1) 입력
// 경주할 자동차 이름(이름은 쉼표(,) 기준으로 구분)
pobi,woni, jun// 시도할 횟수
5
(2) 출력
// 차수별 실행 결과
pobi : --
woni : ----
jun : ---// 단독 우승자 안내 문구
최종 우승자 : pobi
// 공동 우승자 안내 문구
최종 우승자 : pobi, jun
(3) 실행 결과 예시
경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)
pobi,woni,jun
시도할 횟수는 몇 회인가요?
5
실행 결과
pobi : -
woni :
jun : -
pobi : --
woni : -
jun : --
pobi : ---
woni : --
jun : ---
pobi : ----
woni : ---
jun : ----
pobi : -----
woni : ----
jun : -----
최종 우승자 : pobi, jun
3) 프로그래밍 요구 사항
(1) 기본
- Node.js 22.19.0 버전에서 실행 가능해야 한다.
- 프로그램 실행의 시작점은 App.js의 run()이다.
- package.json 파일은 변경할 수 없으며, 제공된 라이브러리와 스타일 라이브러리 이외의 외부 라이브러리는 사용하지 않는다.
- 프로그램 종료 시 process.exit()를 호출하지 않는다.
- 프로그래밍 요구 사항에서 달리 명시하지 않는 한 파일, 패키지 등의 이름을 바꾸거나 이동하지 않는다.
- 자바스크립트 코드 컨벤션을 지키면서 프로그래밍한다.
- 기본적으로 JavaScript Style Guide를 원칙으로 한다.
(2) 추가
- indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다.
- 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다.
- 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메서드)를 분리하면 된다.
- 3항 연산자를 쓰지 않는다.
- 함수(또는 메서드)가 한 가지 일만 하도록 최대한 작게 만들어라.
- Jest를 이용하여 정리한 기능 목록이 정상적으로 작동하는지 테스트 코드로 확인한다.
- 테스트 도구 사용법이 익숙하지 않다면 아래 문서를 참고하여 학습한 후 테스트를 구현한다.
(3) 라이브러리
- @woowacourse/mission-utils에서 제공하는 Random 및 Console API를 사용하여 구현해야 한다.
- Random 값 추출은 Random.pickNumberInRange()를 활용한다.
- 사용자의 값을 입력 및 출력하려면 Console.readLineAsync()와 Console.print()를 활용한다.
| 1주차를 통해 배운 점 Review
아래의 네 가지는 지난 1주차 미션 회고를 통해서 개선하고자 했던 목록입니다.
1) 상수값만 대문자로
: 프로그램 전역에서 절대 바뀌지 않는 상수값만 UPPER_CASE 사용
2) 내부 로직 캡슐화
: 외부에서 호출할 필요 없는 내부 메서드는 # 기호를 사용하여 private 메서드로 선언
3) 상수 파일 구체화
: 코드 내에서 자주 반복되거나 여러 파일에서 공유되는 상수들은 한 파일로 분리
4) 폴더 단위 모듈화
: import를 통해 사용되는 상수나 유틸 함수들을 index.js에서 한 번에 모아 export
실제로 2주차 미션을 하면서 이 점들을 유의하면서 진행하였고, 그 결과 전보다는 클린 코드를 더욱 잘 작성할 수 있게 되었습니다.
2. 고민의 순간 / 개선의 순간 모음
이번 2주차 미션을 수행하면서도, 1주차 미션 방식보다 더 개선이 되었던 점들, 고민이 되었던 점들이 존재했습니다.
단순히 기능을 완성시키는 것이 아니라, 고민했던 점들과 개선시켰던 점들을 잘 담아냈던 포인트들이 있었고, 몇 가지를 공유해보려고 합니다.
| Commit 방식 개선
1) 기존 방식
: 기존에는 다음과 같은 단순한 커밋 절차를 사용하였습니다.
- git add .
- git commit -m "커밋명"
- 필요 시 브랜치로 push
이 방식은 커밋 메시지가 한 줄로만 작성되기 때문에 아래와 같은 점들을 한눈에 파악하기 어려웠습니다..
- 어떤 파일이 어떤 이유로 수정되었는지
- 수정의 목적이 단순한 버그 수정인지, 리팩토링인지, 기능 추가인지
즉, 커밋 로그만 보고 변경 의도나 작업 범위를 정확히 이해하기 힘들다는 한계가 있었습니다.

2) 개선 후 방식
: 개선된 방식에서는 명확한 커밋 구조와 메시지 가이드라인을 적용하였습니다.
- git add .
- git commit 실행 후,
- 타이틀(Title) : 변경의 핵심을 한 줄로 요약 (fix: correct typos and remove duplicate code)
- 본문(Body) : 변경된 주요 내용, 원인, 추가 설명 등을 항목별로 구체적으로 작성
- 마지막으로 git push
// 실제 예시
fix: correct typos and remove duplicate code
1) OUTPUT_START (src/RacingGame.js)
: 제공된 결과물의 포맷과 동일하게 하기 위해, 실행결과 앞에 \n를 추가했습니다.
2) printStart 메서드 중복 제거
: printStart 메서드가 중복으로 선언되어 있는 점을 발견하여, 삭제하였습니다.
3) printWinners로 메서드 이름 변경
: 타이포가 있는 점을 발견하여, 올바른 메서드 이름으로 변경하였습니다.
4) printRound forEach 개선
: 기존의 경우 불필요하게 중괄호로 forEach 구문이 구성되어 있어서, 이 부분을 제거하여 간단하게 구성했습니다.
위와 같은 형태의 메시지는 “무엇을”, “왜” 수정했는지가 명확히 드러나며, 코드의 품질 관리를 향상시켰습니다.

| 에러 검증의 우선순위: 이름 길이 vs 중복 검사
RacingGame의 참가자 이름 검증 로직을 구현하면서, 두 가지 에러 기준을 어느 순서로 처리할지 고민이 생겼습니다.
- [ERROR] 이름이 5자 초과일 경우
- [ERROR] 동일한 이름이 중복되는 경우
처음에는 단순히 두 조건을 나란히 검사하면 된다고 생각했지만,
코드를 실제로 작성하다 보니 에러 발생 순서에 따라 사용자 경험(UX)과 성능이 달라질 수 있었습니다.
1) “입력값 유효성”이 항상 먼저다
: 검증 로직은 기본적으로 입력값 자체의 형식(Validity) 을 먼저 확인하고,
그다음에 값들 간의 관계(Relationship) 를 검증하는 것이 원칙이라고 생각했습니다.
즉, 두 가지로 나눠보면
- 개별 입력값이 올바른지 확인하고 (이름 길이 ≤ 5)
- 유효한 값들끼리의 관계를 비교한다 (중복 여부)
이 순서를 지키면 코드의 의미가 명확해지고, 잘못된 값으로 인해 불필요한 비교 연산이 수행되는 일도 방지할 수 있었습니다.
2) 잘못된 값은 애초에 비교할 필요가 없다
: 예를 들어 ["pangil", "pangil"]이라는 입력을 받았다고 해봅시다.
첫 번째 "pangil"은 이미 6자로, 형식적으로 잘못된 값인데요?
이 상황에서 중복 검사를 먼저 하면 결국 의미 없는 비교 연산을 수행하게 될 것입니다.
반면, 이름 길이 검사를 먼저 수행하면
“이 입력은 게임 규칙에 맞지 않는다”는 사실을 즉시 알려줄 수 있습니다.
이는 사용자 입장에서도 더 직관적인 피드백이 된다고 생각했습니다.
3) 정리된 검증 구조
// 1. 이름 길이 검사
nameList.forEach((name) => {
if (name.length > 5) {
throw new Error(`${ERROR_MESSAGES.INVALID_NAME_LENGTH} (target: ${name})`);
}
});
// 2. 중복 검사
if (new Set(nameList).size !== nameList.length) {
throw new Error(ERROR_MESSAGES.DUPLICATE_NAME);
}
이 구조는 단순하면서도 명확하다고 생각했습니다. 입력 → 유효성 → 관계 검증의 자연스러운 흐름을 유지하고, 사용자에게는 일관된 에러 메시지를, 개발자에게는 유지보수성을 제공할 수 있다고 생각했습니다.
| 단일 책임 원칙(Single Responsibility Principle) 적용
1) 기존 방식
: 기존의 readGameSettings() 함수는 한 함수 안에서
두 가지 입력(이름, 라운드 수)을 받고, 각 입력을 검증한 뒤 반환하는 모든 과정을 한꺼번에 처리하고 있었습니다.
import { InputValidators } from '../utils/InputValidators.js';
import { InputView } from './InputView.js';
export async function readGameSettings() {
let names = await InputView.readNames();
names = InputValidators.validateNames(names);
let round = await InputView.readRound();
round = InputValidators.validateRound(round);
return { names, round };
}
이 구조는 처음 볼 때는 단순해 보이지만, 실제로는 여러 책임이 하나의 함수에 뒤섞여 있는 형태였습니다.
(입력 처리, 검증, 데이터 반환이라는 서로 다른 역할을 모두 수행함)
2) 개선 후 방식
: 단일 책임 원칙에 따라, 각 입력(names, round)을 처리하는 부분을 별도의 함수로 분리했습니다.
async #readPlayerNames() {
const names = await InputView.readNames();
return InputValidators.validateNames(names);
}
async #readRoundCount() {
const round = await InputView.readRound();
return InputValidators.validateRound(round);
}
async #readGameSettings() {
const names = await this.#readPlayerNames();
const round = await this.#readRoundCount();
return { names, round };
}
이제 각 함수는 명확한 역할을 가지게 되었습니다.
- #readPlayerNames() → 이름 입력 및 검증
- #readRoundCount() → 라운드 입력 및 검증
- #readGameSettings() → 전체 게임 설정을 조합
| MVC 패턴으로 변경하기
0) 초기 구조
// 기존 폴더 및 파일 구조
├── App.js
├── RacingGame.js
├── constants
│ ├── ErrorMessages.js
│ ├── GameOptions.js
│ ├── IOMessages.js
│ ├── StringConstants.js
│ └── index.js
├── index.js
├── model
│ └── Car.js
├── utils
│ ├── InputValidators.js
│ └── validators
│ ├── NameValidator.js
│ └── RoundValidator.js
└── view
├── InputView.js
├── OutputView.js
└── Reader.js
초기 구조는 위와 같았습니다.
겉보기에 model, utils, view 폴더로 나뉘어 있었지만, 각 폴더를 왜 이렇게 구분했는지 명확한 근거가 부족했습니다.
즉, 이 구조가 어떤 아키텍처 패턴을 따르고 있는지 설명하기 어려웠습니다.
그래서 이번 리팩터링의 목표는 MVC(Model–View–Controller) 패턴의 개념에 맞춰 구조를 재정립하는 것이었습니다.
1) MVC 패턴 이해하기
: 먼저 “MVC 패턴을 적용한다”는 말이 구체적으로 어떤 의미인지 정의해야 했습니다.
- Model: 자동차 경주 게임의 핵심 데이터(자동차 상태, 진행 로직 등)를 관리
- View: 사용자 입력과 결과 출력을 담당
- Controller: Model과 View를 연결하여 전체 실행 흐름을 제어
사용자는 App.js를 통해 프로그램을 실행하기 때문에,
결국 자동차 경주라는 비즈니스 로직(모델)과
이를 조작하는 컨트롤러,
그리고 사용자 인터페이스를 담당하는 뷰로 역할을 구분짓는 것이 자연스러웠습니다.
2) RacingGame.js 분리 (Model + Controller 역할 명확화)
: 기존의 RacingGame.js는 사실상 모델이자 컨트롤러 역할을 동시에 하고 있었습니다. 자동차의 상태를 관리하는 로직과, 게임을 실행하고 결과를 계산하는 로직이 한 파일에 섞여 있었죠.
이를 해결하기 위해, 아래와 같이 완전히 분리했습니다.
- RacingGame.js → 순수한 모델(Model) 로
- GameController.js → 실행 및 흐름 제어를 담당하는 컨트롤러(Controller) 로
또한, 모델의 역할이 명확해짐에 따라, 기존의 #read, #start, #getWinners, #result 등 메서드를 private → public 메서드로 전환하고, 모델 내부에서 속성(state) 을 다루는 로직만 남겼습니다.
즉, 모델은 오직 “데이터와 그 데이터를 조작하는 메서드”만 포함하도록 재구성했습니다.
3) Reader.js 분리
: 기존의 Reader.js는 사용자 입력을 받아 실행 흐름을 제어하는 등, 본질적으로 컨트롤러의 역할을 수행하고 있었습니다.
하지만 구조상 view 폴더에 위치해 있었고, 이에 따라 파일의 역할이 모호해졌습니다.
따라서 Reader.js의 기능을 전부 GameController.js로 옮겨, 컨트롤러 내에서 입력 처리와 실행 제어를 일관된 흐름으로 관리하도록 변경했습니다.
이로써 View는 순수하게 입출력만 담당하고, Controller는 실행 및 계산 로직을 총괄하게 되었습니다.
4) MVC 패턴 재정의 / 폴더 및 파일 구조
: 리팩터링을 통해 MVC 패턴을 단순히 개념적으로 이해하는 것을 넘어, 실제 코드 수준에서 명확히 구분되는 구조를 만들 수 있었습니다.
제가 이번에 내린 정의는 다음과 같습니다.
| 구성 요소 | 역할 정의 |
| Model | 속성과 그 속성을 제어하는 메서드들의 집합 |
| View | 사용자와 직접 맞닿는 입출력 담당 |
| Controller | Model과 View를 연결하고, 전체 흐름·실행·계산을 담당 |
// 리팩토링 후 폴더 및 파일 구조
src
├── App.js
├── constants
│ ├── ErrorMessages.js
│ ├── GameOptions.js
│ ├── IOMessages.js
│ ├── StringConstants.js
│ └── index.js
├── controller
│ └── GameController.js
├── index.js
├── model
│ ├── Car.js
│ └── RacingGame.js
├── validators
│ ├── InputValidators.js
│ ├── NameValidator.js
│ └── RoundValidator.js
└── view
├── InputView.js
└── OutputView.js
3. '2주차' 전반적인 회고
| 회고록
이번 2주차 미션 제출 시에 첨부했던 실제 회고록을 이번 포스팅에서도 공유해보려고 합니다.
“첫인상과 목표 설정”
이번 2주차 과제를 처음 마주했을 때, 저는 1주차 미션을 통해 느꼈던 부족한 점들을 보완하고, 실제로 개선된 결과를 만들어내고 싶다는 목표를 세웠습니다. 그래서 본격적으로 과제를 시작하기 전에 먼저 1주차 회고를 다시 읽으며, 어떤 부분에서 배움을 얻었고, 어떤 점을 개선할 수 있을지를 스스로 돌아보았습니다.
특히 “상수값만 대문자로 선언하기”, “내부 로직 캡슐화”, “상수 파일 구체화”, “폴더 단위 모듈화” 등과 같은 구체적인 기준을 세우면서, 점점 더 클린 코드의 방향성을 체감할 수 있었습니다. 이러한 기준들이 단순히 형식적인 규칙이 아니라, 코드의 일관성과 유지보수성을 높이는 근거임을 실감하게 되었습니다.
또한 테스트 기능을 지원하는 Jest 라이브러리를 본격적으로 공부하면서 익숙해지고자 노력했습니다. 단순히 테스트를 통과하는 수준이 아니라, 각 기능 단위마다 작동을 검증할 수 있는 단위 테스트(Unit Test)의 개념을 배우고 싶었습니다. 처음에는 Jest의 테스트 구조나 다양한 Matchers 사용법이 낯설었지만, 직접 예제를 따라 해보며 점차 테스트의 본질적인 목적을 이해하게 되었습니다.
비록 아직 완벽히 익숙하다고 할 수는 없었지만, 테스트를 먼저 작성하고 기능을 구현하는 “테스트 주도적 사고방식”을 조금씩 체득해가는 과정이 인상 깊었습니다.
“요구사항에서 리팩토링까지의 흐름”
이번 주차는 1주차에 비해 전체적인 과제 진행 흐름이 훨씬 익숙하게 느껴졌습니다.
우선 기능 요구사항을 분석하며 “이 프로그램이 어떤 일을 수행해야 하는가”를 명확히 이해했고, 입출력 요구사항을 통해 사용자와의 상호작용 방식을 구체적으로 파악했습니다. 그리고 프로그래밍 요구사항을 보며 “어떤 라이브러리를 사용해야 하는가”, “어떤 규칙을 따라야 하는가”를 명확히 짚을 수 있었습니다.
이전과 달리, 구현 단계에서는 큰 어려움 없이 진행할 수 있었습니다. 다만, 진짜 재미는 “리팩토링” 과정에서 느껴졌습니다. 단순히 작동하는 코드를 만드는 것을 넘어, “더 나은 구조를 위해 무엇을 바꿔야 할까?”를 고민하는 시간이었습니다. 작지만 의미 있는 수정들을 하나하나 더해가며 코드가 점점 단정해지고, 구조적으로 단단해지는 것을 보는 과정이 즐거웠습니다.
“기억에 남는 순간들”
가장 인상 깊었던 순간은 세 가지가 있었습니다.
첫 번째는 커밋 관리 방식의 변화입니다.
이전에는 단순히 ’git add .’와 ’git commit -m "메시지”’로만 커밋을 남겼습니다. 하지만 이런 방식은 “어떤 부분이 왜, 어떻게 수정되었는가”를 명확히 담지 못한다는 한계를 느꼈습니다.
그래서 AngularJS 커밋 컨벤션을 적용해보았습니다. ‘commit -m’ 대신 ’commit’만 입력하면 에디터가 열리고, 그 안에서 제목, 본문, 변경 이유 등을 구조적으로 작성하도록 변경했습니다.
그 결과 커밋 히스토리만 봐도 수정의 목적과 내용을 명확히 파악할 수 있게 되었고, 코드 품질 관리 측면에서도 훨씬 향상된 결과를 얻을 수 있었습니다.
두 번째는 예외 처리 순서에 대한 고민이었습니다.
RacingGame의 참가자 이름 검증 로직을 구현하면서 “이름이 다섯 글자를 초과하는 경우”와 “이름이 중복되는 경우” 중 어떤 예외를 먼저 처리해야 하는지 고민했습니다.
단순히 두 조건을 나열하는 것으로 충분하다고 생각했지만, 실제로 코드를 작성하다 보니 에러 발생 순서가 사용자 경험(UX)에 영향을 줄 수 있다는 점을 깨닫게 되었습니다.
결국 입력값의 형식을 먼저 확인하고, 이후 관계를 검증하는 것이 더 자연스럽다는 결론을 내렸습니다. 즉, 이름의 길이를 먼저 검사한 뒤 중복 여부를 확인하도록 구조를 변경했습니다.
이렇게 입력 → 유효성 → 관계 검증의 순서를 확립하면서, 비록 사소하지만 사용자의 피드백 흐름이 훨씬 매끄러워졌습니다.
세 번째는 단일 책임 원칙(Single Responsibility Principle)의 적용입니다.
처음에는 자동차 이름과 진행 횟수를 모두 처리하는 하나의 메서드 안에서 입력, 검증, 반환을 전부 수행했습니다. 하지만 이는 명확히 단일 책임 원칙에 어긋나는 구조였습니다. 그래서 입력 처리, 검증, 데이터 반환을 각각의 함수로 분리하고, 이 함수들을 통합적으로 제어하는 상위 함수에서 호출하도록 개선했습니다.
그 결과 각 함수의 역할이 명확해졌고, 유지보수나 테스트 시에도 훨씬 효율적인 구조를 만들 수 있었습니다.
“가장 도전적이었던 리팩토링”
이번 과제에서 가장 큰 도전은 MVC 디자인 패턴을 기반으로 한 구조 리팩토링이었습니다.
처음에는 MVC 패턴이 생소했기 때문에, 단순히 model, view, controller 폴더를 만들어 구조를 흉내 내는 데 그쳤습니다. 그러다 보니 실제로는 Model과 Controller가 섞이거나, View가 Controller의 역할을 수행하는 등 역할이 혼재된 상태가 되었습니다. 그래서 MVC 패턴의 개념을 다시 학습하고, 이번 프로젝트에 맞게 구조를 재설계했습니다.
예를 들어, ’RacingGame’ 클래스 안에 섞여 있던 자동차 상태 관리 로직과 게임 실행 및 결과 계산 로직을 분리해 Model과 Controller로 명확히 나누었습니다. 또한 입력과 실행 흐름을 제어하던 ’Reader.js’ 파일을 Controller 폴더로 옮겨, 프로그램의 제어 흐름을 한곳에서 일관되게 관리할 수 있도록 변경했습니다.
이 과정은 단순한 폴더 이동 이상의 경험이었습니다. “왜 이렇게 구분해야 하는가?”라는 원리를 체득하며, 디자인 패턴의 본질적인 의도를 이해하게 된 계기가 되었습니다.
“좋았던 점과 아쉬웠던 점”
이번 경험에서 가장 좋았던 점은 기록의 중요성을 깨닫고 이를 실천했다는 점입니다.
1주차에는 문제를 해결해도 기록으로 남기지 못해, 시간이 지나면 다시 같은 고민을 반복하곤 했습니다. 하지만 이번 2주차에는 문제를 마주할 때마다 간단하게라도 기록을 남기며, “무엇이 문제였는가”와 “어떤 선택을 했는가”를 글로 정리했습니다. 이렇게 남긴 기록들은 단순한 로그가 아니라, 다음 과제의 지침서가 되어주었습니다.
반면, 아쉬운 점도 있었습니다.
아직 테스트 코드 작성이 완전히 자연스럽지는 않았습니다. mock 함수(’mockQuestions’, ’mockRandoms’, ’getLogSpy’)와 같은 고급 테스트 도구들을 좀 더 깊이 이해했다면, 더 탄탄한 테스트를 설계할 수 있었을 것이라는 아쉬움이 남습니다.
그럼에도 불구하고, 자동차 이름과 실행 횟수를 입력받는 과정에서 직접 단위 테스트를 작성하고 그 효과를 체감할 수 있었던 것은 큰 수확이었습니다. 이 경험을 바탕으로, 다음 미션에서는 테스트 코드를 더욱 완성도 있게 다듬어보고자 합니다.
“정리하며”
이번 2주차 과제는 단순히 코드를 작성하는 미션이 아니라, 개발자로서의 사고방식과 습관을 형성하는 과정이었다고 생각합니다. 기능 구현에서 리팩토링, 그리고 구조적 개선까지의 모든 단계에서 “왜?”라는 질문을 놓지 않았던 경험이었습니다. 앞으로도 이런 태도를 잃지 않고, 더 나은 코드를 고민하며 성장해 나가고 싶습니다.
| 결론
이번 2주차는 1주차보다 더욱 성장한 시간이었습니다. 과제 자체의 난이도는 낮았지만, 전보다 더 탄탄한 구조와 고민하면서 마주한 점들을 해결하고 기록하며 성장 포인트를 인지할 수 있었던 점이 가장 인상적으로 남습니다.
다음 주차에서도 이번에 배운 점들을 적극적으로 활용하여 적용하며 더 나은 겨롸물을 만들어나가고 싶습니다.
과연 3주차 미션은 어떤 미션일까요?

'✍🏻 회고 > 우테코' 카테고리의 다른 글
| [우테코] 3편 : 1주차 프리코스 회고 (0) | 2025.10.23 |
|---|---|
| [우테코] 2편 : 우아한테크코스 8기 '지원 과정' (1) | 2025.10.11 |
| [우테코] 1편 : 우아한테크코스 2026 입학 설명회 (0) | 2025.10.11 |
