ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,96 @@
+import OutputView from '../views/OutputView.js';
+import InputView from '../views/InputView.js';
+import PlannerData from '../model/PlannerData.js';
+import PlannerUtils from '../utils/PlannerUtills.js';
+import { STATUS_MSG } from '../constants/PlannerMsg.js';
+
+class PlannerController {
+  constructor() {
+    this.PLANNER_DATA = new PlannerData();
+  }
+
+  async plannerStart() {
+    OutputView.printStatusMsg(STATUS_MSG.welcomeMsg);
+    await this.#inputVisitDay();
+  }
+
+  async #inputVisitDay() {
+    try {
+      const visitDate = await InputView.readDate();
+      this.PLANNER_DATA.updateDate(visitDate);
+      return this.#orderMenu();
+    } catch (error) {
+      OutputView.printError(error.message);
+      return this.#inputVisitDay();
+    }
+  }
+
+  async #orderMenu() {
+    try {
+      const foodAndAmount = await InputView.readMenu();
+      this.PLANNER_DATA.updateFood(foodAndAmount);
+      return this.#showPlanner();
+    } catch (error) {
+      OutputView.printError(error.message);
+      return this.#orderMenu();
+    }
+  }
+
+  #showPlanner() {
+    OutputView.printStatusMsg(
+      STATUS_MSG.showPlanner(this.PLANNER_DATA.getDate()),
+    );
+    this.#showOrderMenu();
+  }
+
+  #showOrderMenu() {
+    OutputView.printStatusMsg(STATUS_MSG.orderMenu);
+    OutputView.printOrderMenu(this.PLANNER_DATA.getUserOrder());
+    this.#totalOrderAmount();
+  }
+
+  #totalOrderAmount() {
+    OutputView.printStatusMsg(STATUS_MSG.totalAmount);
+    const plannerUtils = new PlannerUtils(
+      this.PLANNER_DATA.getUserOrder(),
+      this.PLANNER_DATA.getDate(),
+    );
+    OutputView.printTotalAmount(plannerUtils.getTotalAmount());
+    this.#giftCheck(plannerUtils);
+  }
+
+  #giftCheck(plannerUtils) {
+    OutputView.printStatusMsg(STATUS_MSG.giftMenu);
+    OutputView.printGift(plannerUtils.getTotalAmount());
+    this.#benefitCheck(plannerUtils);
+  }
+
+  #benefitCheck(plannerUtils) {
+    OutputView.printStatusMsg(STATUS_MSG.userBenefit);
+    this.#showBenefits(plannerUtils.benefitCheck(), plannerUtils);
+  }
+
+  #showBenefits(benefitObject, plannerUtils) {
+    OutputView.printBenefits(benefitObject);
+    this.#showTotalBenefit(plannerUtils);
+  }
+
+  #showTotalBenefit(plannerUtils) {
+    OutputView.printStatusMsg(STATUS_MSG.totalBenefit);
+    OutputView.printTotalBenefit(plannerUtils.calcBenefitAmount());
+    this.#showPayment(plannerUtils);
+  }
+
+  #showPayment(plannerUtils) {
+    OutputView.printStatusMsg(STATUS_MSG.payment);
+    OutputView.printPayment(plannerUtils.calcTotalPayment());
+    this.#userEventBadge(plannerUtils);
+  }
+
+  #userEventBadge(plannerUtils) {
+    OutputView.printStatusMsg(STATUS_MSG.eventBadge);
+    OutputView.printEventBadge(plannerUtils.calcBenefitAmount());
+  }
+}
+
+export default PlannerController;

리뷰: 인풋을 받는 구조가 유사하니, 인풋 핸들링과 트라이 캐치 사용의 중복을 하나의 메소드로 따로 선언하고 활용하는 방식도 좋은 방법이더라구요 추천드리구 갑니다 🙌🏻
@@ -0,0 +1,21 @@
+import { Console } from '@woowacourse/mission-utils';
+import { INPUT_MSG } from '../constants/PlannerMsg.js';
+import InputValidator from '../utils/InputValidator.js';
+
+const InputView = {
+  INPUT_VAL: new InputValidator(),
+
+  async readDate() {
+    const userDate = await Console.readLineAsync(INPUT_MSG.inputVisitDay);
+    await this.INPUT_VAL.dateValidate(userDate);
+    return userDate;
+  },
+
+  async readMenu() {
+    const userMenu = await Console.readLineAsync(INPUT_MSG.orderMenu);
+    await this.INPUT_VAL.menuValidate(userMenu.split(','));
+    return userMenu.split(',');
+  },
+};
+
+export default InputView;

리뷰: 인풋 유효성 검사에 대한 클래스를 따로 선언하셔서 뷰에서 호출하는 방식이 또 새롭네요 !! 저는 인풋 받는 내부에서 구현하고, 분리해야 한다는 생각만 했었는데 이 방식도 효율적인거 같아요 ☺️
@@ -0,0 +1,96 @@
+import OutputView from '../views/OutputView.js';
+import InputView from '../views/InputView.js';
+import PlannerData from '../model/PlannerData.js';
+import PlannerUtils from '../utils/PlannerUtills.js';
+import { STATUS_MSG } from '../constants/PlannerMsg.js';
+
+class PlannerController {
+  constructor() {
+    this.PLANNER_DATA = new PlannerData();
+  }
+
+  async plannerStart() {
+    OutputView.printStatusMsg(STATUS_MSG.welcomeMsg);
+    await this.#inputVisitDay();
+  }
+
+  async #inputVisitDay() {
+    try {
+      const visitDate = await InputView.readDate();
+      this.PLANNER_DATA.updateDate(visitDate);
+      return this.#orderMenu();
+    } catch (error) {
+      OutputView.printError(error.message);
+      return this.#inputVisitDay();
+    }
+  }
+
+  async #orderMenu() {
+    try {
+      const foodAndAmount = await InputView.readMenu();
+      this.PLANNER_DATA.updateFood(foodAndAmount);
+      return this.#showPlanner();
+    } catch (error) {
+      OutputView.printError(error.message);
+      return this.#orderMenu();
+    }
+  }
+
+  #showPlanner() {
+    OutputView.printStatusMsg(
+      STATUS_MSG.showPlanner(this.PLANNER_DATA.getDate()),
+    );
+    this.#showOrderMenu();
+  }
+
+  #showOrderMenu() {
+    OutputView.printStatusMsg(STATUS_MSG.orderMenu);
+    OutputView.printOrderMenu(this.PLANNER_DATA.getUserOrder());
+    this.#totalOrderAmount();
+  }
+
+  #totalOrderAmount() {
+    OutputView.printStatusMsg(STATUS_MSG.totalAmount);
+    const plannerUtils = new PlannerUtils(
+      this.PLANNER_DATA.getUserOrder(),
+      this.PLANNER_DATA.getDate(),
+    );
+    OutputView.printTotalAmount(plannerUtils.getTotalAmount());
+    this.#giftCheck(plannerUtils);
+  }
+
+  #giftCheck(plannerUtils) {
+    OutputView.printStatusMsg(STATUS_MSG.giftMenu);
+    OutputView.printGift(plannerUtils.getTotalAmount());
+    this.#benefitCheck(plannerUtils);
+  }
+
+  #benefitCheck(plannerUtils) {
+    OutputView.printStatusMsg(STATUS_MSG.userBenefit);
+    this.#showBenefits(plannerUtils.benefitCheck(), plannerUtils);
+  }
+
+  #showBenefits(benefitObject, plannerUtils) {
+    OutputView.printBenefits(benefitObject);
+    this.#showTotalBenefit(plannerUtils);
+  }
+
+  #showTotalBenefit(plannerUtils) {
+    OutputView.printStatusMsg(STATUS_MSG.totalBenefit);
+    OutputView.printTotalBenefit(plannerUtils.calcBenefitAmount());
+    this.#showPayment(plannerUtils);
+  }
+
+  #showPayment(plannerUtils) {
+    OutputView.printStatusMsg(STATUS_MSG.payment);
+    OutputView.printPayment(plannerUtils.calcTotalPayment());
+    this.#userEventBadge(plannerUtils);
+  }
+
+  #userEventBadge(plannerUtils) {
+    OutputView.printStatusMsg(STATUS_MSG.eventBadge);
+    OutputView.printEventBadge(plannerUtils.calcBenefitAmount());
+  }
+}
+
+export default PlannerController;

리뷰: 여경님 코드보고 저도 역시 줄여야겠다 생각했습니다!
좋은 리뷰 감사합니다!
@@ -0,0 +1,96 @@
+import OutputView from '../views/OutputView.js';
+import InputView from '../views/InputView.js';
+import PlannerData from '../model/PlannerData.js';
+import PlannerUtils from '../utils/PlannerUtills.js';
+import { STATUS_MSG } from '../constants/PlannerMsg.js';
+
+class PlannerController {
+  constructor() {
+    this.PLANNER_DATA = new PlannerData();
+  }
+
+  async plannerStart() {
+    OutputView.printStatusMsg(STATUS_MSG.welcomeMsg);
+    await this.#inputVisitDay();
+  }
+
+  async #inputVisitDay() {
+    try {
+      const visitDate = await InputView.readDate();
+      this.PLANNER_DATA.updateDate(visitDate);
+      return this.#orderMenu();
+    } catch (error) {
+      OutputView.printError(error.message);
+      return this.#inputVisitDay();
+    }
+  }
+
+  async #orderMenu() {
+    try {
+      const foodAndAmount = await InputView.readMenu();
+      this.PLANNER_DATA.updateFood(foodAndAmount);
+      return this.#showPlanner();
+    } catch (error) {
+      OutputView.printError(error.message);
+      return this.#orderMenu();
+    }
+  }
+
+  #showPlanner() {
+    OutputView.printStatusMsg(
+      STATUS_MSG.showPlanner(this.PLANNER_DATA.getDate()),
+    );
+    this.#showOrderMenu();
+  }
+
+  #showOrderMenu() {
+    OutputView.printStatusMsg(STATUS_MSG.orderMenu);
+    OutputView.printOrderMenu(this.PLANNER_DATA.getUserOrder());
+    this.#totalOrderAmount();
+  }
+
+  #totalOrderAmount() {
+    OutputView.printStatusMsg(STATUS_MSG.totalAmount);
+    const plannerUtils = new PlannerUtils(
+      this.PLANNER_DATA.getUserOrder(),
+      this.PLANNER_DATA.getDate(),
+    );
+    OutputView.printTotalAmount(plannerUtils.getTotalAmount());
+    this.#giftCheck(plannerUtils);
+  }
+
+  #giftCheck(plannerUtils) {
+    OutputView.printStatusMsg(STATUS_MSG.giftMenu);
+    OutputView.printGift(plannerUtils.getTotalAmount());
+    this.#benefitCheck(plannerUtils);
+  }
+
+  #benefitCheck(plannerUtils) {
+    OutputView.printStatusMsg(STATUS_MSG.userBenefit);
+    this.#showBenefits(plannerUtils.benefitCheck(), plannerUtils);
+  }
+
+  #showBenefits(benefitObject, plannerUtils) {
+    OutputView.printBenefits(benefitObject);
+    this.#showTotalBenefit(plannerUtils);
+  }
+
+  #showTotalBenefit(plannerUtils) {
+    OutputView.printStatusMsg(STATUS_MSG.totalBenefit);
+    OutputView.printTotalBenefit(plannerUtils.calcBenefitAmount());
+    this.#showPayment(plannerUtils);
+  }
+
+  #showPayment(plannerUtils) {
+    OutputView.printStatusMsg(STATUS_MSG.payment);
+    OutputView.printPayment(plannerUtils.calcTotalPayment());
+    this.#userEventBadge(plannerUtils);
+  }
+
+  #userEventBadge(plannerUtils) {
+    OutputView.printStatusMsg(STATUS_MSG.eventBadge);
+    OutputView.printEventBadge(plannerUtils.calcBenefitAmount());
+  }
+}
+
+export default PlannerController;

리뷰: 저도 저번에는 catch부문에 다시 inputVisitDay를 실행하면 재귀적으로 처리되어 테스트가 통과가 안 됐는데 @JonghyunLEE12  님은  inputVisitDay를 바로 return하는걸로 해결하셨네요. 이번에 저는 while(true) 구문 안에서 valid한 입력값을 받으면 while을 나가는 것으로 작성해봤는데 개인적으로 더 깔끔한 처리라고 생각이 들더라고요 한번 참고해보셔도 좋을 것 같아요
@@ -0,0 +1,60 @@
+/* eslint-disable max-lines-per-function */
+import InputValidator from '../../src/domain/utils/InputValidator.js';
+
+describe('날짜 테스트', () => {
+  const userInput = new InputValidator();
+  const testCases = [
+    { date: 'a', expectedError: '[ERROR]' },
+    { date: ' ', expectedError: '[ERROR]' },
+    { date: '이십오일', expectedError: '[ERROR]' },
+    { date: '32', expectedError: '[ERROR]' },
+    { date: '0', expectedError: '[ERROR]' },
+    { date: '25.5', expectedError: '[ERROR]' },
+    { date: '-1', expectedError: '[ERROR]' },
+    { date: ' 12', expectedError: '[ERROR]' },
+    { date: '1 2', expectedError: '[ERROR]' },
+    { date: '12  ', expectedError: '[ERROR]' },
+  ];
+
+  testCases.forEach((testCase) => {
+    test(`날짜 테스트, 날짜: ${testCase.date}`, async () => {
+      await expect(userInput.dateValidate(testCase.date)).rejects.toThrow(
+        testCases.expectedError,
+      );
+    });
+  });
+});
+
+describe('메뉴 테스트', () => {
+  const menuValidate = new InputValidator();
+  const testCases = [
+    { menu: ['짬뽕-1'], expectedError: '[ERROR]' },
+    { menu: ['티본스테이크-0'], expectedError: '[ERROR]' },
+    { menu: ['티본스테이크-1.5'], expectedError: '[ERROR]' },
+    { menu: ['티본스테이크-a'], expectedError: '[ERROR]' },
+    { menu: ['제로콜라1', '레드와인1'], expectedError: '[ERROR]' },
+    { menu: ['김밥-1', '떡볶이-1'], expectedError: '[ERROR]' },
+    { menu: ['제로콜라-일', '레드와인-이'], expectedError: '[ERROR]' },
+    { menu: ['제로콜라-a', '레드와인-b'], expectedError: '[ERROR]' },
+    { menu: ['해산물파스타-2', '해산물파스타-1'], expectedError: '[ERROR]' },
+    { menu: ['제로콜라-1'], expectedError: '[ERROR]' },
+    { menu: ['레드와인-1', '제로콜라-1'], expectedError: '[ERROR]' },
+    { menu: ['시저샐러드- 1', '제로콜라- 1'], expectedError: '[ERROR]' },
+    {
+      menu: ['레드와인-2', '제로콜라-2', '샴페인-2'],
+      expectedError: '[ERROR]',
+    },
+    {
+      menu: ['티본스테이크-10', '레드와인-20', '제로콜라-20'],
+      expectedError: '[ERROR]',
+    },
+  ];
+
+  testCases.forEach((testCase) => {
+    test(`메뉴 테스트, 메뉴: ${testCase.menu}`, async () => {
+      await expect(menuValidate.menuValidate(testCase.menu)).rejects.toThrow(
+        testCase.expectedError,
+      );
+    });
+  });
+});

리뷰: 이십오일ㅋㅋ 테스트코드를 정말 자세하게 짜셨네요 많이 배우고 갑니다!
@@ -0,0 +1,32 @@
+import { ERROR_MSG } from '../../constants/PlannerMsg.js';
+
+class DateValidate {
+  #regax;
+
+  #date;
+
+  constructor(regax, date) {
+    this.#regax = regax;
+    this.#date = date;
+  }
+
+  regaxCheck() {
+    if (this.#regax.test(this.#date)) {
+      throw new Error(ERROR_MSG.dateError);
+    }
+  }
+
+  safeCheck() {
+    if (!Number.isSafeInteger(Number(this.#date))) {
+      throw new Error(ERROR_MSG.dateError);
+    }
+  }
+
+  rangeCheck() {
+    if (Number(this.#date) <= 0 || Number(this.#date) > 31) {
+      throw new Error(ERROR_MSG.dateError);
+    }
+  }
+}
+
+export default DateValidate;

리뷰: 개인적으로 this.#regax를 필드로 두신 이유가 궁금합니다. 상수화해서 바로 가져와 쓰시지않고 new DateValidate()할때  regex를 전달하신 다른이유가 있을까요?
@@ -0,0 +1,125 @@
+import { ERROR_MSG } from '../../constants/PlannerMsg.js';
+import { FOOD_MENU } from '../../constants/FoodMenu.js';
+
+class MenuValidate {
+  #canOrder;
+
+  #userMenu;
+
+  #menu;
+
+  constructor(menu, canOrder = [], userMenu = []) {
+    this.#menu = menu;
+    this.#canOrder = canOrder;
+    this.#userMenu = userMenu;
+  }
+
+  menuCheck() {
+    this.#findingMenu(this.#canOrder, this.#userMenu, this.#menu);
+    this.#userMenu.forEach((order) => {
+      if (!this.#canOrder.includes(order)) {
+        throw new Error(ERROR_MSG.notInMenu);
+      }
+    });
+  }
+
+  #findingMenu() {
+    Object.keys(FOOD_MENU).map((course) =>
+      this.#makeCanOrder(course, this.#canOrder),
+    );
+
+    this.#menu.forEach((order) => this.#userMenu.push(order.split('-')[0]));
+  }
+
+  #makeCanOrder(course) {
+    Object.values(FOOD_MENU[`${course}`]).map((food) =>
+      this.#canOrder.push(food[0]),
+    );
+  }
+
+  menuAmountCheck(regax) {
+    const totalMenuAmount = [];
+    this.#menu.forEach((order) => {
+      this.#amountCheck(order.split('-')[1], regax);
+      totalMenuAmount.push(Number(order.split('-')[1]));
+    });
+
+    this.#totalAmountCheck(totalMenuAmount);
+  }
+
+  #amountCheck(amount, regax) {
+    if (regax.test(Number(amount))) {
+      throw new Error(ERROR_MSG.notInMenu);
+    }
+    if (Number(amount) <= 0) {
+      throw new Error(ERROR_MSG.notInMenu);
+    }
+
+    if (!Number.isSafeInteger(Number(amount))) {
+      throw new Error(ERROR_MSG.notInMenu);
+    }
+  }
+
+  #totalAmountCheck(total) {
+    const checkTotal = total.reduce((sum, amount) => sum + amount);
+    if (checkTotal > 20) {
+      throw new Error(ERROR_MSG.totalError);
+    }
+  }
+
+  formCheck() {
+    const form = /^[가-힣]+-[0-9]+$/;
+    this.#menu.forEach((order) => this.#formRegaxCheck(order, form));
+  }
+
+  #formRegaxCheck(order, form) {
+    if (!form.test(order)) {
+      throw new Error(ERROR_MSG.notInMenu);
+    }
+  }
+
+  duplicateCheck() {
+    if (this.#userMenu.length !== new Set(this.#userMenu).size) {
+      throw new Error(ERROR_MSG.notInMenu);
+    }
+  }
+
+  drinkCheck() {
+    const menuObject = this.#makingMenuObject();
+    if (this.#objectCheck(menuObject)) {
+      throw new Error(ERROR_MSG.drinkError);
+    }
+  }
+
+  #makingMenuObject() {
+    const menuObject = {};
+    Object.keys(FOOD_MENU).forEach((course) => {
+      menuObject[course] = this.#userMenuCourses(course);
+    });
+    return menuObject;
+  }
+
+  #userMenuCourses(course) {
+    const includeCourse = [];
+    Object.values(FOOD_MENU[course]).forEach((eachMenu) => {
+      if (this.#menuIncludes(eachMenu)) {
+        includeCourse.push(eachMenu[0]);
+      }
+    });
+    return includeCourse.length;
+  }
+
+  #menuIncludes(eachMenu) {
+    return this.#userMenu.includes(eachMenu[0]);
+  }
+
+  #objectCheck(menuObject) {
+    const { appetizer, main, dessert, drink } = menuObject;
+    if (appetizer === 0 && main === 0 && dessert === 0 && drink !== 0) {
+      return true;
+    }
+    return false;
+  }
+}
+
+export default MenuValidate;

리뷰: 앗 이부분은 제가 제출하고나서 깨달았네요ㅜㅜ
저도 참 아쉽습니다. 좋은 의견 감사합니다!
@@ -0,0 +1,32 @@
+import { ERROR_MSG } from '../../constants/PlannerMsg.js';
+
+class DateValidate {
+  #regax;
+
+  #date;
+
+  constructor(regax, date) {
+    this.#regax = regax;
+    this.#date = date;
+  }
+
+  regaxCheck() {
+    if (this.#regax.test(this.#date)) {
+      throw new Error(ERROR_MSG.dateError);
+    }
+  }
+
+  safeCheck() {
+    if (!Number.isSafeInteger(Number(this.#date))) {
+      throw new Error(ERROR_MSG.dateError);
+    }
+  }
+
+  rangeCheck() {
+    if (Number(this.#date) <= 0 || Number(this.#date) > 31) {
+      throw new Error(ERROR_MSG.dateError);
+    }
+  }
+}
+
+export default DateValidate;

리뷰: InputValidate 클래스안에 Date 와 Menu 에 대해 두가지 검사를 정의해줬습니다!
우선 전체적인 정규표현식을 상위에서 정의하고 하위 객체들에게 전달해주는 방식으로 사용했는데요.
MenuValidate 안에는 form 이라는 정규표현식이 또 존재하기 때문에, 한 객체 안에 다른 정규표현식이 존재하는것 보다는
따로 정의를 해주는게 더 예뻐보여서 다음과 같이 사용했습니다!
@@ -0,0 +1,96 @@
+import OutputView from '../views/OutputView.js';
+import InputView from '../views/InputView.js';
+import PlannerData from '../model/PlannerData.js';
+import PlannerUtils from '../utils/PlannerUtills.js';
+import { STATUS_MSG } from '../constants/PlannerMsg.js';
+
+class PlannerController {
+  constructor() {
+    this.PLANNER_DATA = new PlannerData();
+  }
+
+  async plannerStart() {
+    OutputView.printStatusMsg(STATUS_MSG.welcomeMsg);
+    await this.#inputVisitDay();
+  }
+
+  async #inputVisitDay() {
+    try {
+      const visitDate = await InputView.readDate();
+      this.PLANNER_DATA.updateDate(visitDate);
+      return this.#orderMenu();
+    } catch (error) {
+      OutputView.printError(error.message);
+      return this.#inputVisitDay();
+    }
+  }
+
+  async #orderMenu() {
+    try {
+      const foodAndAmount = await InputView.readMenu();
+      this.PLANNER_DATA.updateFood(foodAndAmount);
+      return this.#showPlanner();
+    } catch (error) {
+      OutputView.printError(error.message);
+      return this.#orderMenu();
+    }
+  }
+
+  #showPlanner() {
+    OutputView.printStatusMsg(
+      STATUS_MSG.showPlanner(this.PLANNER_DATA.getDate()),
+    );
+    this.#showOrderMenu();
+  }
+
+  #showOrderMenu() {
+    OutputView.printStatusMsg(STATUS_MSG.orderMenu);
+    OutputView.printOrderMenu(this.PLANNER_DATA.getUserOrder());
+    this.#totalOrderAmount();
+  }
+
+  #totalOrderAmount() {
+    OutputView.printStatusMsg(STATUS_MSG.totalAmount);
+    const plannerUtils = new PlannerUtils(
+      this.PLANNER_DATA.getUserOrder(),
+      this.PLANNER_DATA.getDate(),
+    );
+    OutputView.printTotalAmount(plannerUtils.getTotalAmount());
+    this.#giftCheck(plannerUtils);
+  }
+
+  #giftCheck(plannerUtils) {
+    OutputView.printStatusMsg(STATUS_MSG.giftMenu);
+    OutputView.printGift(plannerUtils.getTotalAmount());
+    this.#benefitCheck(plannerUtils);
+  }
+
+  #benefitCheck(plannerUtils) {
+    OutputView.printStatusMsg(STATUS_MSG.userBenefit);
+    this.#showBenefits(plannerUtils.benefitCheck(), plannerUtils);
+  }
+
+  #showBenefits(benefitObject, plannerUtils) {
+    OutputView.printBenefits(benefitObject);
+    this.#showTotalBenefit(plannerUtils);
+  }
+
+  #showTotalBenefit(plannerUtils) {
+    OutputView.printStatusMsg(STATUS_MSG.totalBenefit);
+    OutputView.printTotalBenefit(plannerUtils.calcBenefitAmount());
+    this.#showPayment(plannerUtils);
+  }
+
+  #showPayment(plannerUtils) {
+    OutputView.printStatusMsg(STATUS_MSG.payment);
+    OutputView.printPayment(plannerUtils.calcTotalPayment());
+    this.#userEventBadge(plannerUtils);
+  }
+
+  #userEventBadge(plannerUtils) {
+    OutputView.printStatusMsg(STATUS_MSG.eventBadge);
+    OutputView.printEventBadge(plannerUtils.calcBenefitAmount());
+  }
+}
+
+export default PlannerController;

리뷰: 처음 시작에서부터 하나의 메서드가 끝나면 다음 메서드를 호출하는 식으로 마무리를 하여 controller쪽의 모든 메서드가 연결되어 있는데 그렇게 모든 메서드를 연결을 지으신 이유가 있으실까요?
@@ -0,0 +1,21 @@
+import { Console } from '@woowacourse/mission-utils';
+import { INPUT_MSG } from '../constants/PlannerMsg.js';
+import InputValidator from '../utils/InputValidator.js';
+
+const InputView = {
+  INPUT_VAL: new InputValidator(),
+
+  async readDate() {
+    const userDate = await Console.readLineAsync(INPUT_MSG.inputVisitDay);
+    await this.INPUT_VAL.dateValidate(userDate);
+    return userDate;
+  },
+
+  async readMenu() {
+    const userMenu = await Console.readLineAsync(INPUT_MSG.orderMenu);
+    await this.INPUT_VAL.menuValidate(userMenu.split(','));
+    return userMenu.split(',');
+  },
+};
+
+export default InputView;

리뷰: 날짜를 받아오자마자 바로 validate를 해주는 것이 괜찮다고 생각했습니다. controller로 받아오는 것보다 이 편이 훨씬 보기 좋은 것 같네요
@@ -0,0 +1,28 @@
+import DateValidate from './validator/DateValidate.js';
+import MenuValidate from './validator/MenuValidate.js';
+
+class InputValidator {
+  #REGAX;
+
+  constructor() {
+    this.#REGAX = /\s|[!@#$%^&*(),?":{}|<>]|[a-zA-Z]/;
+  }
+
+  async dateValidate(date) {
+    const dateValidator = new DateValidate(this.#REGAX, date);
+    dateValidator.regaxCheck();
+    dateValidator.safeCheck();
+    dateValidator.rangeCheck();
+  }
+
+  async menuValidate(menu) {
+    const menuValidator = new MenuValidate(menu);
+    menuValidator.menuCheck();
+    menuValidator.menuAmountCheck(this.#REGAX);
+    menuValidator.formCheck();
+    menuValidator.duplicateCheck();
+    menuValidator.drinkCheck();
+  }
+}
+
+export default InputValidator;

리뷰: 받아온 값을 검사할 때 한번 보내서 정규식 해서 그것을 다시 검사하는 식으로 하신것 같은데 이렇게 정규식을 한 단계로 따로 빼신 이유가 있을 지 궁금합니다!
@@ -0,0 +1,96 @@
+import OutputView from '../views/OutputView.js';
+import InputView from '../views/InputView.js';
+import PlannerData from '../model/PlannerData.js';
+import PlannerUtils from '../utils/PlannerUtills.js';
+import { STATUS_MSG } from '../constants/PlannerMsg.js';
+
+class PlannerController {
+  constructor() {
+    this.PLANNER_DATA = new PlannerData();
+  }
+
+  async plannerStart() {
+    OutputView.printStatusMsg(STATUS_MSG.welcomeMsg);
+    await this.#inputVisitDay();
+  }
+
+  async #inputVisitDay() {
+    try {
+      const visitDate = await InputView.readDate();
+      this.PLANNER_DATA.updateDate(visitDate);
+      return this.#orderMenu();
+    } catch (error) {
+      OutputView.printError(error.message);
+      return this.#inputVisitDay();
+    }
+  }
+
+  async #orderMenu() {
+    try {
+      const foodAndAmount = await InputView.readMenu();
+      this.PLANNER_DATA.updateFood(foodAndAmount);
+      return this.#showPlanner();
+    } catch (error) {
+      OutputView.printError(error.message);
+      return this.#orderMenu();
+    }
+  }
+
+  #showPlanner() {
+    OutputView.printStatusMsg(
+      STATUS_MSG.showPlanner(this.PLANNER_DATA.getDate()),
+    );
+    this.#showOrderMenu();
+  }
+
+  #showOrderMenu() {
+    OutputView.printStatusMsg(STATUS_MSG.orderMenu);
+    OutputView.printOrderMenu(this.PLANNER_DATA.getUserOrder());
+    this.#totalOrderAmount();
+  }
+
+  #totalOrderAmount() {
+    OutputView.printStatusMsg(STATUS_MSG.totalAmount);
+    const plannerUtils = new PlannerUtils(
+      this.PLANNER_DATA.getUserOrder(),
+      this.PLANNER_DATA.getDate(),
+    );
+    OutputView.printTotalAmount(plannerUtils.getTotalAmount());
+    this.#giftCheck(plannerUtils);
+  }
+
+  #giftCheck(plannerUtils) {
+    OutputView.printStatusMsg(STATUS_MSG.giftMenu);
+    OutputView.printGift(plannerUtils.getTotalAmount());
+    this.#benefitCheck(plannerUtils);
+  }
+
+  #benefitCheck(plannerUtils) {
+    OutputView.printStatusMsg(STATUS_MSG.userBenefit);
+    this.#showBenefits(plannerUtils.benefitCheck(), plannerUtils);
+  }
+
+  #showBenefits(benefitObject, plannerUtils) {
+    OutputView.printBenefits(benefitObject);
+    this.#showTotalBenefit(plannerUtils);
+  }
+
+  #showTotalBenefit(plannerUtils) {
+    OutputView.printStatusMsg(STATUS_MSG.totalBenefit);
+    OutputView.printTotalBenefit(plannerUtils.calcBenefitAmount());
+    this.#showPayment(plannerUtils);
+  }
+
+  #showPayment(plannerUtils) {
+    OutputView.printStatusMsg(STATUS_MSG.payment);
+    OutputView.printPayment(plannerUtils.calcTotalPayment());
+    this.#userEventBadge(plannerUtils);
+  }
+
+  #userEventBadge(plannerUtils) {
+    OutputView.printStatusMsg(STATUS_MSG.eventBadge);
+    OutputView.printEventBadge(plannerUtils.calcBenefitAmount());
+  }
+}
+
+export default PlannerController;

리뷰: Controller에는 이벤트 플레너의 입력,출력등의 순서를 정의했습니다.
각각의 순서들을 하나의 메소드로 정의했고, 이를 연결시켜주는 방식으로 사용했습니다