[프론트엔드 7기] 최종코딩테스트 준비하기, 루틴을 적용한 점심메뉴
본 포스팅은 우아한테크코스 7기 최종코딩테스트 준비 회고록입니다.
프로젝트 배경 및 목적
점심 메뉴 추천 프로젝트는 코치별로 못 먹는 메뉴와 이미 먹은 메뉴를 고려해, 5일간 중복되지 않는 카테고리와 메뉴를 추천하는 콘솔 기반 프로그램입니다. 이를 통해 제한된 조건 속에서 효율적인 메뉴 추천 로직을 구현하고, 콘솔 환경에서의 문제 해결 능력을 강화하는 데 초점을 맞췄습니다.
이 프로젝트는 우아한테크코스 5기 최종 코딩테스트 문제를 기반으로 진행했습니다.
GitHub - hafnium1923/javascript-menu
Contribute to hafnium1923/javascript-menu development by creating an account on GitHub.
github.com
7기 코딩테스트를 준비하는 과정에서 저는 기존의 학습 방식, 즉 검색과 주변의 도움에 의존하는 방식만으로는 5시간이라는 제한된 시간 안에 프로그램을 완성하기 어려움을 느꼈습니다.
따라서, 문제 해결 루틴을 정형화하고 설계 과정을 체계화하여, 주어진 문제를 빠르고 정확하게 해결할 수 있는 능력을 기르기 위해 본 과제를 선택했습니다.
특히, 5기부터는 코딩테스트 형식이 브라우저 기반에서 콘솔 기반으로 변경되었기에 적합하다고 판단했습니다. 이 프로젝트를 통해 효율적인 문제 풀이 루틴화에 집중했습니다.
모듈 설계
src/
├── infrastructure/
│ ├── ConsoleInput.js
│ ├── ConsoleOutput.js
├── validation/
│ ├── Validator.js
│ ├── ErrorHandler.js
├── domain/
│ ├── Coach.js
│ ├── Menu.js
│ ├── CategoryPicker.js
│ ├── MenuPlanner.js
├── service/
│ ├── LunchManager.js
├── App.js
├── index.js
instructure | 콘솔 입력/출력과 같은 I/O 처리를 담당 |
validation | 입력값 검증 및 에러 처리를 담당 |
domain | 주요 비즈니스 로직을 구현하는 핵심 모듈 |
Coach: 코치별 데이터를 관리 | |
CategoryPicker: 중복되지 않는 카테고리 추천 | |
MenuPlanner: 메뉴 추천 로직을 담당 | |
Menu: 메뉴와 카테고리 간의 매핑 및 관리 로직을 처리 | |
service | 프로그램의 전체 흐름과 상태를 관리(LunchManager) |
점심 메뉴 추천 프로젝트는 모듈화를 통해 명확한 책임 분리를 이루고, 확장성과 유지보수성을 높이고자 설계되었습니다. 초기 설계에서는 Coach(코치) 인스턴스가 못 먹는 메뉴와 먹은 메뉴를 각각 관리하도록 설계하였지만, 이후 구현 과정에서 메뉴 데이터를 효율적으로 다루기 위해 Menu 클래스를 추가했습니다.
Menu 클래스
기존 설계에서는 카테고리와 메뉴 간의 관계를 단순히 문자열 배열로 관리했지만, 데이터 처리의 효율성을 높이기 위해 Menu 클래스를 추가했습니다.
Menu 클래스는 메뉴 데이터를 초기화하고, 카테고리별 메뉴와 특정 메뉴의 카테고리를 관리합니다. 역매핑을 활용하기 때문에 못 먹는 음식의 카테고리를 찾는 과정에서 효율적으로 탐색할 수 있습니다.
class Menu {
#menuCategoryMap;
constructor(sampleData) {
this.#menuCategoryMap = Object.entries(sampleData).reduce(
(map, [category, menus]) => {
menus.split(', ').forEach((menu) => {
map[menu] = category;
});
return map;
},
{}
);
}
getCategoryByMenu(menu) {
return this.#menuCategoryMap[menu];
}
getMenusByCategory(category) {
return Object.keys(this.#menuCategoryMap).filter(
(menu) => this.#menuCategoryMap[menu] === category
);
}
}
문제 해결
코치의 상태 관리
코치별로 갖고 있는 상태가 다르기 때문에, 각 코치는 자신이 못 먹는 음식(dislikedMenus)과 먹은 음식(eatenMenus)을 관리합니다. Coach 클래스는 메뉴를 추천받을 때마다 자신의 상태를 업데이트할 수 있습니다.
class Coach {
constructor(name) {
this.name = name;
this.dislikedMenus = {};
this.eatenMenus = {};
}
// ... 생략
}
카테고리 선택과 메뉴 추천
CategoryPicker는 랜덤으로 카테고리를 선택하며, 각 카테고리는 최대 2회까지만 선택 가능합니다. MenuPlanner는 코치의 상태를 참고해 추천 가능한 메뉴를 반환합니다.
class MenuPlanner {
planMenu(category) {
const availableMenus = this.coach.getAvailableMenusByCategory(category);
return Random.shuffle(availableMenus)[0];
}
}
디버깅 과정을 통한 Random.shuffle 이슈 해결
단위 테스트는 모두 통과하였으나, App.js에서 프로그램이 중단되는 문제가 발생했습니다. 원인은 바로 Random.shuffle이 배열의 요소가 숫자일 때만 동작했기 때문입니다(...)
코드에서 문제를 찾지 못해 고민하던 와중, API 문서를 읽으면서 깨달았습니다💦 이를 해결하기 위해 문자열 배열에서도 동작하는 새로운 shuffle 메서드를 구현했습니다.
API 구현이 바뀐건지 모르겠지만, 이 때문에 ApplicationTest 파일의 테스트코드가 동작하지 않아 약간 아쉬움이 남은 프로젝트입니다.
shuffle(array) {
return array.sort(() => Math.random() - 0.5);
}
하루 메뉴 추천과 기록 관리
LunchManager는 하루 메뉴 추천(recommendDailyMenu)을 진행하고 이를 기록(result)으로 관리합니다. startRecommendation 메서드가 이 과정을 5일간 반복해주며, 한 주간의 메뉴 추천을 마칩니다.
class LunchManager {
recommendDailyMenu() {
const category = this.categoryPicker.pickCategory();
this.coaches.forEach((coach) => {
const recommendedMenu = new MenuPlanner(coach).planMenu(category);
coach.removeEatenMenu(category, recommendedMenu);
});
}
}
다음에는···.
이번 프로젝트를 통해 시간을 단축하고, 코드를 정리하는 루틴을 익혔습니다. 테스트 코드를 작성하는 순서와 시점에 따라 결과가 달라질 수 있으며 경우에 따라 디버깅 모드보다 훨씬 더 빠르게 코드 에러를 잡아낼 수 있음을 학습했습니다.
또한, 설계를 엎거나 협력 관계를 바꾸는 순간 시간이 크게 소모된다는 점을 경험하며, 초기 설계 단계에서 유즈케이스 기반으로 로직을 구체화해야 한다는 교훈을 얻었습니다. 다음 프로젝트에서는 유즈케이스를 철저히 정의하고, 설계 변경이 최소화될 수 있도록 협력 구조를 명확히 하는 데 집중할 계획입니다.
이를 통해 협업과 유지보수를 고려한 문서화, 자동화된 테스트 환경 구축, 그리고 복잡한 추천 로직을 다룰 확장성을 확보하고자 합니다💪🏻
GitHub - mindaaaa/javascript-menu: 한 주의 점심 메뉴를 추천해주는 <점심 메뉴>를 구현합니다.
한 주의 점심 메뉴를 추천해주는 <점심 메뉴>를 구현합니다. Contribute to mindaaaa/javascript-menu development by creating an account on GitHub.
github.com