이 포스트는 우아한테크코스 6기를 준비하며 풀어본 우아한테크코스 5기
문제입니다.
작년 우아한테크코스 5기의 최종 코딩테스트 문제는 점심 메뉴 추천
이었다. 그동안의 프리코스 문제들은 일주일의 시간동안 해결하면 됐었는데 다른 블로그의 후기들을 살펴보니 현장에서 5시간 안에 해결해야만 한다고 했다. 실제 현장의 압박감 등을 고려하면 이렇게 연습차원에서 문제를 해결하는 것도 빠른 시간안에 가능해야할 것 같다. 해당 문제는 기능구현을 최대한 빠르게 해보고자 노력을 했고 이후에 클린코드를 접목시켰다.
프리코스 문제를 처음 풀 때 나를 제일 힘들게 했던 것은 기능구현 목록 작성이다. 한달동안 우아한테크코스 문제를 풀면서 찾은 꿀팁 아닌 꿀팁이 있는데 내가 최종적으로 코드를 작성하고 실행이 되는 console 창을 이용하면 기능구현 목록을 작성하는 데 조금이나마 도움을 받을 수 있다.
이번 문제를 예를 들어 설명을 해보자.
점심 메뉴 추천을 시작합니다.
코치의 이름을 입력해 주세요. (, 로 구분)
토미,제임스,포코
토미(이)가 못 먹는 메뉴를 입력해 주세요.
우동,스시
제임스(이)가 못 먹는 메뉴를 입력해 주세요.
뇨끼,월남쌈
포코(이)가 못 먹는 메뉴를 입력해 주세요.
마파두부,고추잡채
메뉴 추천 결과입니다.
[ 구분 | 월요일 | 화요일 | 수요일 | 목요일 | 금요일 ]
[ 카테고리 | 한식 | 한식 | 일식 | 중식 | 아시안 ]
[ 토미 | 쌈밥 | 김치찌개 | 미소시루 | 짜장면 | 팟타이 ]
[ 제임스 | 된장찌개 | 비빔밥 | 가츠동 | 토마토 달걀볶음 | 파인애플 볶음밥 ]
[ 포코 | 된장찌개 | 불고기 | 하이라이스 | 탕수육 | 나시고렝 ]
추천을 완료했습니다.
위 마크다운 파일은 우아한테크코스_메뉴추천에서 가져왔다.
매 줄마다 해당 출력값을 얻기 위해서는 값이 어떠한 형태로 저장이 되어야 하는지, 해당 기능을 하기위해서 별도의 데이터를 관리하는 곳이 필요한지 (MVC 패턴에 적용하기 위함) 등을 파악하며 큰 흐름을 살펴보는 것이 중요하다. 직접 끄적거리는 과정에서 기능구현 목록과 MVC 패턴 구현을 모두 만족시킬 수 있다. 이 과정은 실제로 아래 이미지와 같다.
예외처리와 구현할 기능 목록을 나누어 별도로 작성하였는데 데이터(값)를 중심으로 작성하는 것이 중요하다. 쓸데없이 많은 기능을 세세하게 적을 필요가 없다. 쓸데없이 많은 파일만 생성될 뿐이다...
앞서 우아한테크코스 프리코스 3주차에서도 말했지만 이 기능 목록을 작성하는 것이 제일 중요하다. 모든 로직의 흐름이 여기서 결정되기 때문에 이 큰 흐름이 코드를 작성하다가 무너진다면 회복하기에 꽤 많은 시간이 잡아먹힐 것이고 코드도 많이 꼬일 것이다.
본 문제에서는 App.js
파일에 메뉴 카테고리와 해당 카테고리에 해당하는 메뉴들이 배열로 담겨져 있었다. 처음에는 App.js에 적혀있는 파일들을 이동시키면 안되는건가 싶었지만 문제 요구사항에 별 말이 없었기에 static.js
파일로 이동시켰다. 객체로 구성하여 value값을 배열의 형태로 저장하여 필요할 때마다 뽑아서 쓸 예정이었다.
그리고 앞에서 진행한 프리코스와 마찬가지로 Model과 View에서 숫자 사용을 최대한 지양하기 위해서 숫자를 모두 static.js
파일에 상수로 저장하였다. 중간중간 코드를 작성할 때에도 예상치 못한 숫자가 등장할 때가 있어서 바로바로 상수화 해주었다.
예외처리와 출력메시지는 이제 앞에서 너무 많이 설명한 바 있어 여기서는 생략하겠다.
Util 클래스도 앞에 프리코스에서 많이 설명한 바 있어 구체적인 설명은 생략하고자 한다. RandomGenerator.js
와 InputValidators.js
를 적용하였고 추가적으로 메뉴가 중복되는 경우 false를 반환하는 기능을 여기서 구현하여 써먹어보려 했지만 실제로 코드에서 사용되는 부분은 한번밖에 없어서 굳이 Util 클래스에서 구현하지는 않았다.
// 카테코리의 번호 (1 ~ 5) 중 하나를 반환
makeRandomCategory() {
const pickedCategory = Random.pickNumberInRange(
StaticString.CATEGORY_START_NUMBER,
StaticString.CATEGORY_END_NUMBER
);
return pickedCategory;
},
}
처음 MVC 패턴을 적용할 때 정말 갑갑했었다. View
와 Contoller
까지는 이해가 쉽게 됐었지만 대체 어떤 값들을 Model에 분류를 해야할 지가 정말 막막했다. 하지만 위에서 기능구현 목록을 살펴보며 필요한 데이터를 정리하는 과정에서 Model
을 쉽게 구분할 수 있었다.
Model
은 View와 Controller에 대해 몰라야만 한다. 조금더 쉽게 이해를 하자면 Model
에는 다른 곳에서는 담을 수 없는 데이터들이 담겨야만 하고 이는 필요할 때 수정이 가능한 데이터여야 한다.
또한 Model 안에는 get
과 validate
가 주로 사용된다. 그리고 나머지 계산적인 기능은 controller에서 이루어지는 것이라고 생각하면 편하다. 그렇다면 점심 메뉴 추천 기능에서 Model 기능은 어떠한 것이 있을까?
class Users {
#users;
// 입력값에 대한 예외처리 작업
constructor(name) {
InputValidators.validateNotProperName(name);
InputValidators.validateOverRangeName(name);
this.#users = name.split(",");
InputValidators.validateOverRangeCount(this.#users);
}
// 이름 입력값이 ","를 기준으로 들어왔을 때 자동적으로 배열로 변환해주고 이를 리턴해주는 작업
getUsersName() {
return this.#users;
}
}
여기서는 역시 콜백함수
를 이용하여 각 함수에 대한 가독성을 좋게 하고자 하였다. 자세한 설명은 앞에 프리코스 2주차, 3주차를 참고하길 바란다.
// 못 먹는 음식을 받아오기 => callback은 ,로 구분된 문자열(메뉴)을 인수로 가진 함수
readNotEatingMenu(user, callback) {
this.getUserInput(
`\n${user}${GuideMessage.PERSON_NOT_EATING_MENU}`,
callback
);
},
getUserInput(guide, callback) {
Console.readLine(guide, (input) => {
callback(input);
});
},
이번 OutputView
는 신경을 많이 썼다. 사용자의 입력에 따라 최종적인 출력 메뉴 배열이 달라지기 때문이다. 앞에는 사용자의 이름도 추가해서 가독성있는 배열을 출력해야 하기 때문에 OutputView
에서 인자를 받아 최종 배열 문자열을 출력하고자 하였다.
// 추천메뉴 출력
printRecommendMenus(user, menus) {
menus.unshift(`${user}`);
Console.print(`[ ${menus.join(" | ")} ]`);
},
역시 시간이 은근 많이 쓰이는 곳이 Controller
인 것 같다. 함수화를 하여 프로그램을 구현하는 것이 아직은 적응이 안된 것 같다. 많은 함수로 쪼개고 그 함수를 꼬리물기 하듯이 엮는 과정이 아직 복잡하게 느껴지는 것 같다. 특히 이번 Controller
에 연산 기능을 몰아 넣었기 때문에 더욱 그랬다.
그나마 꿀팁은 앞에서 설명한 입출력 화면을 보면서 시간 순서대로 함수를 꼬리물기 하는 것과 같이 작성하면 된다.
// 카테고리의 종류에 따라 랜덤으로 메뉴를 배열에 넣는 코드 : array = 카테고리 배열, NotArray = 못 먹는 음식 배열
setMenuCategory(array, NotArray) {
this.#recommendMenu = [];
for (let i = 0; i <= StaticString.DATE_LENGTH; i++) {
if (MenuCategory.includes(array[i]))
this.#recommendMenu.push(
this.isMenuDuplicate(AllMenus[array[i]], NotArray)
);
}
return this.#recommendMenu;
}
// 인수는 카테고리별 메뉴를 담은 배열이다. -> output: 카테고리별 랜덤 메뉴 하나 (재귀 함수)
isMenuDuplicate(menuArray, NotEatingArray) {
let Random = RandomNumberGenerators.makeRandomMenus(menuArray);
if (this.#recommendMenu.includes(Random) || NotEatingArray.includes(Random))
return this.isMenuDuplicate(menuArray, NotEatingArray);
return Random;
}
클래스 private에 관해서는 바로 전 주차에서 설명하였지만 이번 주에 이것 때문에 여러모로 애를 먹었다. 최종 출력과정에서 분명히 에러가 발생할 만한 곳을 찾기 어려웠는데 최종 메뉴추천 배열이 아예 출력되지 않는 문제가 발생했디 때문이었다. 에러라도 발생하면 어디서 문제가 발생하였는지라도 알 수 있었을텐데 그러지도 못해서 정말 1시간 반정도를 이곳에 투자했던 것 같다.
결과적으로는 private 필드선언 그리고 Model에서 정의해놓은 함수에서 오류가 발생한 것이었다.
class Users {
#users;
// 입력값에 대한 예외처리 작업
constructor(name) {
InputValidators.validateNotProperName(name);
InputValidators.validateOverRangeName(name);
this.#users = name.split(",");
InputValidators.validateOverRangeCount(this.#users);
}
// 이름 입력값이 ","를 기준으로 들어왔을 때 자동적으로 배열로 변환해주고 이를 리턴해주는 작업
getUsersName() {
return this.#users;
}
}
const MenuController {
#users
.
.
.
this.#users = new Users(input);
.
.
.
// 최종적인 결과를 출력하는 함수
resultCommend() {
OutputView.printResultRecommend();
OutputView.printDays();
OutputView.printCategory(this.getCategoryMenus());
for (let i = 0; i < this.#users.getUsersName().length; i++) {
this.getPrintRecommendMenus(i);
}
OutputView.printCompleteRecommend();
}
최종 Controller
에서 private
필드로 #users
를 생성하였고 생성자 함수를 이용하여 선언도 해주었다. 그리고 해당 클래스는 기존에는 getUsersname()
이라는 함수를 이용해야만 최종적인 배열도 출력하고 이용이 가능해지는 것이다. 하지만 내가 이 #users
가 배열 그 자체라고 착각을 하였고 이것의 길이를 구하고자 length
프로퍼티를 이용하니 원하는 값이 나올 수가 없었던 것이다. 따라서 기존 클래스에서 정의한 this.#users.getUsersName()
까지 코드를 작성하여야 원하는 배열이 나올 수 있는 것이다.
앞으로는 Clean Code 책을 읽으면서, 그리고 Jest에 대해 조금 더 공부를 하면서 지금까지 작성하였던 코드를 수정하고자 한다.
한달이라는 짧은 기간동안 우아한테크코스의 문제들을 풀어보았지만 이 짧은 시간 안에 자바스크립트라는 문법에 대해 코드로 직접 작성하면서 머리에 많이 중요한 개념들을 각인 시킬 수 있었던 것 같다. 이렇게 짧은 기간동안 많은 것을 배울 수가 있었는데 1년이라는 시간동안 이 코스에 합류해서 노력하여 배운다면 1년 뒤 나의 모습이 상상이 되지 않을 정도로 성장해있을 것 같아 설렌다. 부디 그 꿈을 이룰 수 있게끔 지금 이 순간에 노력이 헛되지 않게 공부하고자 한다.
자료출처:
잘 보고 갑니다 ㅎㅎ 공부하는데 큰 도움이 됐어요