ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,49 @@
+import InputView from '../views/InputView.js';
+import OutputView from '../views/OutputView.js';
+import PromotionResultService from '../service/PromotionResultService.js';
+import ErrorHandler from '../errors/ErrorHandler/module.js';
+
+/**
+ * @module ChristmasPromotionController
+ * 크리스마스 프로모션 이벤트 관련 애플리케이션의 흐름 제어를 담당
+ */
+const ChristmasPromotionController = {
+  /**
+   * @returns {Promise<void>}
+   */
+  async play() {
+    const { visitDate, orderMenuInfo } = await processUserInput();
+
+    processPromotionResult({ visitDate, orderMenuInfo });
+  },
+};
+
+/**
+ * @returns {Promise<{visitDate : number, orderMenuInfo : [string, number][]}>} 유저가 입력한 방문 일자 및 메뉴 정보를 반환할 Promise 객체
+ */
+async function processUserInput() {
+  OutputView.printStartGuideComments();
+
+  const visitDate = await ErrorHandler.retryOnErrors(InputView.readVisitDate.bind(InputView));
+  const orderMenuInfo = await ErrorHandler.retryOnErrors(
+    InputView.readOrderMenuInfo.bind(InputView),
+  );
+
+  return { visitDate, orderMenuInfo };
+}
+
+/**
+ * @param {{visitDate : number, orderMenuInfo : [string, number][]}} params - 유저가 입력한 방문 일자 및 메뉴 정보
+ * @returns {void}
+ */
+function processPromotionResult({ visitDate, orderMenuInfo }) {
+  const promotionResult = PromotionResultService.createPromotionResult({
+    visitDate,
+    orderMenuInfo,
+  });
+
+  OutputView.printEndGuideComments(visitDate);
+  OutputView.printPromotionResult({ orderMenuInfo, promotionResult });
+}
+
+export default ChristmasPromotionController;

리뷰: 이름을 통해 의미를 명확하게 파악할 수 있는 경우에는 주석이 없어도 괜찮지 않을까요?
@@ -0,0 +1,63 @@
+export const PROMOTION_DATE_INFO = Object.freeze({
+  year: 2023,
+  month: 12,
+});
+
+export const PROMOTION_MENU_TABLE = Object.freeze({
+  애피타이저: [
+    {
+      name: '양송이수프',
+      price: 6_000,
+    },
+    {
+      name: '타파스',
+      price: 5_500,
+    },
+    {
+      name: '시저샐러드',
+      price: 8_000,
+    },
+  ],
+  메인: [
+    {
+      name: '티본스테이크',
+      price: 55_000,
+    },
+    {
+      name: '바비큐립',
+      price: 54_000,
+    },
+    {
+      name: '해산물파스타',
+      price: 35_000,
+    },
+    {
+      name: '크리스마스파스타',
+      price: 25_000,
+    },
+  ],
+  디저트: [
+    {
+      name: '초코케이크',
+      price: 15_000,
+    },
+    {
+      name: '아이스크림',
+      price: 5_000,
+    },
+  ],
+  음료: [
+    {
+      name: '제로콜라',
+      price: 3_000,
+    },
+    {
+      name: '레드와인',
+      price: 60_000,
+    },
+    {
+      name: '샴페인',
+      price: 25_000,
+    },
+  ],
+});

리뷰: `Badge` 같은 경우에는 `EventBadgeMaker` 모듈과 같은 디렉토리에 존재하는데 메뉴도 같은 모듈에 있다면 어떨까요?
@@ -0,0 +1,53 @@
+import { PROMOTION_DATE_INFO } from '../../constants/promotionSystem.js';
+
+// constant.js는 테스트 코드 및 module.js에서만 사용
+export const INITIAL_PROMOTION_BENEFIT_RESULT = Object.freeze({
+  xmasBenefitAmount: 0,
+  weekDayBenefitAmount: 0,
+  weekendBenefitAmount: 0,
+  specialBenefitAmount: 0,
+  giftAmount: 0,
+});
+
+export const MINIMUM_TOTAL_ORDER_AMOUNT = 10000;
+
+export const BENEFIT_DATE_INFO = Object.freeze({
+  startDate: new Date(`${PROMOTION_DATE_INFO.year}-${PROMOTION_DATE_INFO.month}-01`),
+  endDate: new Date(`${PROMOTION_DATE_INFO.year}-${PROMOTION_DATE_INFO.month}-31`),
+  christmas: new Date(`${PROMOTION_DATE_INFO.year}-${PROMOTION_DATE_INFO.month}-25`),
+});
+
+export const DAY_OF_BENEFIT_CONDITION = Object.freeze({
+  weekday: Object.freeze({
+    menuCategory: '디저트',
+    days: [0, 1, 2, 3, 4],
+  }),
+
+  weekend: Object.freeze({
+    menuCategory: '메인',
+    days: [5, 6],
+  }),
+});
+
+export const BENEFIT_AMOUNT_INFO = Object.freeze({
+  everyDay: 100,
+  christmas: 1000,
+  dayOfWeek: 2023,
+  special: 1000,
+});
+
+export const MINIMUM_ORDER_AMOUNT_FOR_GIFT = 120000;
+
+export const GIFT_INFO = {
+  menuCategory: '음료',
+  menuName: '샴페인',
+};
+
+export const SPECIAL_DATES = new Set([
+  `${PROMOTION_DATE_INFO.year}-${PROMOTION_DATE_INFO.month}-03`,
+  `${PROMOTION_DATE_INFO.year}-${PROMOTION_DATE_INFO.month}-10`,
+  `${PROMOTION_DATE_INFO.year}-${PROMOTION_DATE_INFO.month}-17`,
+  `${PROMOTION_DATE_INFO.year}-${PROMOTION_DATE_INFO.month}-24`,
+  `${PROMOTION_DATE_INFO.year}-${PROMOTION_DATE_INFO.month}-25`,
+  `${PROMOTION_DATE_INFO.year}-${PROMOTION_DATE_INFO.month}-31`,
+]);

리뷰: P5- 파싱 로직이 유틸리티 함수로 빠질 수 있을 것 같아요!
@@ -0,0 +1,48 @@
+import { addCalculation } from '../../utils/number.js';
+
+/**
+ * @module PromotionReceipt
+ * 총 혜택 금액 및 예상 지출 금액을 묶어 프로모션 결과에 대한 영수증을 표현한 모듈
+ */
+const PromotionReceipt = Object.freeze({
+  /**
+   *
+   * @param {{promotionBenefitResult : import('../../utils/jsDoc.js').PromotionBenefitResult, totalOrderAmount : number}} params - 혜택 정보 및 총 주문 금액이 들어있는 객체
+   * @returns {import('../../utils/jsDoc.js').PromotionReceipt} 총 혜택 금액 및 예상 지출 금액
+   */
+  issue({ promotionBenefitResult, totalOrderAmount }) {
+    const totalBenefitAmount = calculateBenefitAmount(promotionBenefitResult);
+    const expectPaymentAmount = calculateExpectedPayment({
+      promotionBenefitResult,
+      totalBenefitAmount,
+      totalOrderAmount,
+    });
+
+    return { totalBenefitAmount, expectPaymentAmount };
+  },
+});
+
+export default PromotionReceipt;
+
+/**
+ * @param {import('../../utils/jsDoc.js').PromotionBenefitResult} promotionBenefitResult - 혜택 결과
+ * @returns {number} 총 혜택 금액
+ */
+function calculateBenefitAmount(promotionBenefitResult) {
+  return Object.values(promotionBenefitResult).reduce(addCalculation, 0);
+}
+
+/**
+ *
+ * @param {{totalOrderAmount : number, promotionBenefitResult : import('../../utils/jsDoc.js').PromotionBenefitResult, totalBenefitAmount : number}} params - 함수 실행에 필요한 매개변수들
+ * @returns {number} 예상 지출 금액
+ */
+function calculateExpectedPayment({
+  totalOrderAmount,
+  totalBenefitAmount,
+  promotionBenefitResult,
+}) {
+  const benefitAmount = totalBenefitAmount - promotionBenefitResult.giftAmount;
+
+  return totalOrderAmount - benefitAmount;
+}

리뷰: 오 이거 신박하네요ㅋㅋㅋ `sort` 같은 메서드에서도 변형해서 쓸 수 있겠어요!
@@ -0,0 +1,47 @@
+import MenuSearcher from './module.js';
+
+describe('메뉴 찾기 테스트', () => {
+  describe('findByMenuName 테스트', () => {
+    test.each([
+      {
+        menuName: '티본스테이크',
+        category: '스테이크',
+        expectedMenu: { name: '티본스테이크', price: 55000 },
+      },
+      {
+        menuName: '초코케이크',
+        category: '디저트',
+        expectedMenu: { name: '초코케이크', price: 15000 },
+      },
+      { menuName: '제로콜라', category: '음료', expectedMenu: { name: '제로콜라', price: 3000 } },
+      { menuName: '초코케이크', category: '음료', expectedMenu: undefined },
+    ])(
+      '메뉴 이름이 $menuName이고, 카테고리가 $category일 경우 검색 결과는 $expectedMenu 이다.',
+      ({ menuName, category, expectedMenu }) => {
+        // given - when
+        const menu = MenuSearcher.findByMenuName(menuName, category);
+
+        // then
+        expect(menu).toEqual(expectedMenu);
+      },
+    );
+  });
+
+  describe('isMenuInCategory 테스트', () => {
+    test.each([
+      { menuName: '티본스테이크', category: '메인', expectedResult: true },
+      { menuName: '초코케이크', category: '디저트', expectedResult: true },
+      { menuName: '제로콜라', category: '음료', expectedResult: true },
+      { menuName: '초코케이크', category: '음료', expectedResult: false },
+    ])(
+      '메뉴 이름이 $menuName이고, 카테고리가 $category일 때, 실행 결과는 $expectedResult 이다.',
+      ({ menuName, category, expectedResult }) => {
+        // given - when
+        const result = MenuSearcher.isMenuInCategory(menuName, category);
+
+        // then
+        expect(result).toBe(expectedResult);
+      },
+    );
+  });
+});

리뷰: 요즘 들어 테스트코드를 짤때 `boolean 반환 함수`에 대한 test suite 분리에 대해 생각을 하게 되더라구요. 
_`toBeTruthy`와 `toBeFalsy` matcher도 존재하는데 반환값이 `boolean`인 경우에는 test suite를 분리하는게 맞나?_ 라는 생각이 들어서요! matcher도 더 명시적으로 읽히고 파라미터 테스트의 파라미터도 좀 더 가볍게 다룰 수도 있을것 같구요.
진영님 의견은 어떠신가요?
@@ -0,0 +1,48 @@
+import { addCalculation } from '../../utils/number.js';
+
+/**
+ * @module PromotionReceipt
+ * 총 혜택 금액 및 예상 지출 금액을 묶어 프로모션 결과에 대한 영수증을 표현한 모듈
+ */
+const PromotionReceipt = Object.freeze({
+  /**
+   *
+   * @param {{promotionBenefitResult : import('../../utils/jsDoc.js').PromotionBenefitResult, totalOrderAmount : number}} params - 혜택 정보 및 총 주문 금액이 들어있는 객체
+   * @returns {import('../../utils/jsDoc.js').PromotionReceipt} 총 혜택 금액 및 예상 지출 금액
+   */
+  issue({ promotionBenefitResult, totalOrderAmount }) {
+    const totalBenefitAmount = calculateBenefitAmount(promotionBenefitResult);
+    const expectPaymentAmount = calculateExpectedPayment({
+      promotionBenefitResult,
+      totalBenefitAmount,
+      totalOrderAmount,
+    });
+
+    return { totalBenefitAmount, expectPaymentAmount };
+  },
+});
+
+export default PromotionReceipt;
+
+/**
+ * @param {import('../../utils/jsDoc.js').PromotionBenefitResult} promotionBenefitResult - 혜택 결과
+ * @returns {number} 총 혜택 금액
+ */
+function calculateBenefitAmount(promotionBenefitResult) {
+  return Object.values(promotionBenefitResult).reduce(addCalculation, 0);
+}
+
+/**
+ *
+ * @param {{totalOrderAmount : number, promotionBenefitResult : import('../../utils/jsDoc.js').PromotionBenefitResult, totalBenefitAmount : number}} params - 함수 실행에 필요한 매개변수들
+ * @returns {number} 예상 지출 금액
+ */
+function calculateExpectedPayment({
+  totalOrderAmount,
+  totalBenefitAmount,
+  promotionBenefitResult,
+}) {
+  const benefitAmount = totalBenefitAmount - promotionBenefitResult.giftAmount;
+
+  return totalOrderAmount - benefitAmount;
+}

리뷰: 이 애플리케이션이 출력하는 데이터를 모르는 사람이 본다면, `benefitAmount`와 `totalBenefitAmount`를 헷갈려할 것 같아요. 총 혜택 금액에서 증정 메뉴의 금액을 뺀 값은 총 할인 받은 금액이니까 `discountedAmount`라고 표현할 수 있지 않을까요?
@@ -0,0 +1,53 @@
+import { PROMOTION_DATE_INFO } from '../../constants/promotionSystem.js';
+
+// constant.js는 테스트 코드 및 module.js에서만 사용
+export const INITIAL_PROMOTION_BENEFIT_RESULT = Object.freeze({
+  xmasBenefitAmount: 0,
+  weekDayBenefitAmount: 0,
+  weekendBenefitAmount: 0,
+  specialBenefitAmount: 0,
+  giftAmount: 0,
+});
+
+export const MINIMUM_TOTAL_ORDER_AMOUNT = 10000;
+
+export const BENEFIT_DATE_INFO = Object.freeze({
+  startDate: new Date(`${PROMOTION_DATE_INFO.year}-${PROMOTION_DATE_INFO.month}-01`),
+  endDate: new Date(`${PROMOTION_DATE_INFO.year}-${PROMOTION_DATE_INFO.month}-31`),
+  christmas: new Date(`${PROMOTION_DATE_INFO.year}-${PROMOTION_DATE_INFO.month}-25`),
+});
+
+export const DAY_OF_BENEFIT_CONDITION = Object.freeze({
+  weekday: Object.freeze({
+    menuCategory: '디저트',
+    days: [0, 1, 2, 3, 4],
+  }),
+
+  weekend: Object.freeze({
+    menuCategory: '메인',
+    days: [5, 6],
+  }),
+});
+
+export const BENEFIT_AMOUNT_INFO = Object.freeze({
+  everyDay: 100,
+  christmas: 1000,
+  dayOfWeek: 2023,
+  special: 1000,
+});
+
+export const MINIMUM_ORDER_AMOUNT_FOR_GIFT = 120000;
+
+export const GIFT_INFO = {
+  menuCategory: '음료',
+  menuName: '샴페인',
+};
+
+export const SPECIAL_DATES = new Set([
+  `${PROMOTION_DATE_INFO.year}-${PROMOTION_DATE_INFO.month}-03`,
+  `${PROMOTION_DATE_INFO.year}-${PROMOTION_DATE_INFO.month}-10`,
+  `${PROMOTION_DATE_INFO.year}-${PROMOTION_DATE_INFO.month}-17`,
+  `${PROMOTION_DATE_INFO.year}-${PROMOTION_DATE_INFO.month}-24`,
+  `${PROMOTION_DATE_INFO.year}-${PROMOTION_DATE_INFO.month}-25`,
+  `${PROMOTION_DATE_INFO.year}-${PROMOTION_DATE_INFO.month}-31`,
+]);

리뷰: 새로운 개발자가 들어오면 그 분이 스스로 판단할게 아니라 프로젝트에 대한 설명을 충분히 인수인계 받는게 먼저라고 생각합니다.

제가 따로 관리한 이유는 상수를 기존에는 테스트코드와 소스 코드에서 재 사용되는 값들을 관리하고자 상수와 관련된 도메인에서 관리를 하여 코드 응집성을 강화 했었습니다.

이번 미션의 경우 상수 값이 너무 많이 발생해서 로직 내부를 파악하기 어려웠었고(물론 이 부분은 더 분리를 했다면 개선 가능했을거라고 생각해요.) 인스턴스의 생명주기가 대체로 짧은 탓에 함수형 모듈로 개선을 했습니다. 

그 과정에서 도메인 static 상수를 어떻게 관리할까 고민했고, constants에 있는 상수는 공통적으로 사용되는 상수로써 관리 되고 있었기 때문에, 여러 라이브러리를 찾아보다 toss/slash와 three.js에서 상수를 디렉토리 내부의 constant.js로 관리해서 저도 디렉토리 내부에 있는 constant.js는 공통적으로 사용하는 것이 아닌 소스코드와 도메인의 테스트 코드에서 재 사용하기 위해 추가했습니다!

그렇게 함으로써 모듈 내부엔 로직에 집중할 수 있고, 관심사가 분리된다고 생각했어요.
@@ -0,0 +1,49 @@
+import InputView from '../views/InputView.js';
+import OutputView from '../views/OutputView.js';
+import PromotionResultService from '../service/PromotionResultService.js';
+import ErrorHandler from '../errors/ErrorHandler/module.js';
+
+/**
+ * @module ChristmasPromotionController
+ * 크리스마스 프로모션 이벤트 관련 애플리케이션의 흐름 제어를 담당
+ */
+const ChristmasPromotionController = {
+  /**
+   * @returns {Promise<void>}
+   */
+  async play() {
+    const { visitDate, orderMenuInfo } = await processUserInput();
+
+    processPromotionResult({ visitDate, orderMenuInfo });
+  },
+};
+
+/**
+ * @returns {Promise<{visitDate : number, orderMenuInfo : [string, number][]}>} 유저가 입력한 방문 일자 및 메뉴 정보를 반환할 Promise 객체
+ */
+async function processUserInput() {
+  OutputView.printStartGuideComments();
+
+  const visitDate = await ErrorHandler.retryOnErrors(InputView.readVisitDate.bind(InputView));
+  const orderMenuInfo = await ErrorHandler.retryOnErrors(
+    InputView.readOrderMenuInfo.bind(InputView),
+  );
+
+  return { visitDate, orderMenuInfo };
+}
+
+/**
+ * @param {{visitDate : number, orderMenuInfo : [string, number][]}} params - 유저가 입력한 방문 일자 및 메뉴 정보
+ * @returns {void}
+ */
+function processPromotionResult({ visitDate, orderMenuInfo }) {
+  const promotionResult = PromotionResultService.createPromotionResult({
+    visitDate,
+    orderMenuInfo,
+  });
+
+  OutputView.printEndGuideComments(visitDate);
+  OutputView.printPromotionResult({ orderMenuInfo, promotionResult });
+}
+
+export default ChristmasPromotionController;

리뷰: mvc를 아시는 분들은 쉽게 이해하시겠지만 controller의 역할을 이해하시지 못하는 분들도 있을거라 생각해서 주석을 남겼습니다. 

또한, 코드의 일관성의 이유도 있었던거 같습니다!
@@ -0,0 +1,110 @@
+import {
+  priceFilter,
+  PRICE_REFINER,
+  merageFoodInfo,
+  BENEFITS_REFINER,
+  isNotApplicableBenefits
+} from "../src/constants/ViewRefiner.js";
+
+test('숫자를 천단위로 잘라서 문자로 변환되는지 테스트', () => {
+  const NUMBER = 1234567890;
+  const PRICE_STRING = '1,234,567,890';
+  
+  expect(priceFilter(NUMBER)).toEqual(PRICE_STRING);
+});
+
+test('숫자가 0일경우 문자로 0원을 반환한다', () => {
+  const PRICE = 0;
+  const RESULT = '0원';
+
+  expect(PRICE_REFINER.negative(PRICE)).toEqual(RESULT);
+});
+
+test('숫자가 0이 아니라면 천단위로 잘라 마이너스와 원을 붙여 문자로 반환한다', () => {
+  const PRICE = 1500000;
+  const RESULT = '-1,500,000원';
+
+  expect(PRICE_REFINER.negative(PRICE)).toEqual(RESULT);
+});
+
+test('음식 이름과 수량을 잘 짝지어 주는지 테스트', () => {
+  const FOOD_NAME = '시골된장찌개';
+  const FOOD_AMOUNT = '7';
+  const FOOD_NAME_WITH_AMOUNT = '시골된장찌개 7개';
+
+  expect(merageFoodInfo(FOOD_NAME, FOOD_AMOUNT)).toEqual(FOOD_NAME_WITH_AMOUNT);
+});
+
+test('혜택 내역이 하나도 없을때(키 condition의 value가 false) 없음이라는 문자만 담긴 배열을 반환한다', () => {
+  const map = new Map();
+  map
+    .set('christmas', 1000)
+    .set('weekday', 2023 * 1)
+    .set('weekend', 0)
+    .set('special', 0)
+    .set('freebie', 0)
+    .set('condition', false);
+
+  const BENEFITS_LIST = ['없음'];
+
+  expect(BENEFITS_REFINER.benfitList(map)).toEqual(BENEFITS_LIST);
+});
+
+test('키 condition의 value가 true라면 map에 담긴 정보를 최종 출력형식에 맞춰 문자 배열로 반환한다', () => {
+  const map = new Map();
+  map
+    .set('christmas', 1000 + (24 -1) * 100)
+    .set('weekday', 2023 * 10)
+    .set('weekend', 0)
+    .set('special', 1000)
+    .set('freebie', 25000)
+    .set('condition', true);
+
+  const BENEFITS_ARRAY = [
+    '크리스마스 디데이 할인: -3,300원',
+    '평일 할인: -20,230원',
+    '특별 할인: -1,000원',
+    '증정 이벤트: -25,000원'
+  ];
+
+  expect(BENEFITS_REFINER.benfitList(map)).toEqual(BENEFITS_ARRAY);
+});
+
+test('모든 혜택 할인금액이 0원이면 true를 반환한다', () => {
+  const map = new Map();
+  map
+    .set('christmas', 0)
+    .set('weekday', 0)
+    .set('weekend', 0)
+    .set('special', 0)
+    .set('freebie', 0)
+    .set('condition', true);
+
+  expect(isNotApplicableBenefits(map)).toEqual(true);
+});
+
+test('map 속의 conditon 의 value 가 false라면 true를 반환한다', () => {
+  const map = new Map();
+  map
+    .set('christmas', 2000)
+    .set('weekday', 2023)
+    .set('weekend', 0)
+    .set('special', 1000)
+    .set('freebie', 25000)
+    .set('condition', false);
+  
+  expect(isNotApplicableBenefits(map)).toEqual(true);
+});
+
+test('혜택 금액이 모두 0원이 아니면서 conditon의 value가 true라면 false를 반환한다', () => {
+  const map = new Map();
+  map
+    .set('christmas', 0)
+    .set('weekday', 0)
+    .set('weekend', 0)
+    .set('special', 0)
+    .set('freebie', 25000)
+    .set('condition', true);
+  
+  expect(isNotApplicableBenefits(map)).toEqual(false);
+});

리뷰: 도메인 로직이란 현실 문제에 대한 의사결정을 하는 코드로 알고 있는데, 혹시 해당 테스트가 도메인 로직에 대한 테스트인지 여쭙고 싶습니당!
@@ -1,7 +1,67 @@
-export default OutputView = {
-    printMenu() {
-        Console.print("<주문 메뉴>");
-        // ...
-    }
-    // ...
-}
+import { Console } from "@woowacourse/mission-utils";
+import { ANSWER } from "./constants/PlannerMesseage.js";
+import { PRICE_REFINER, BENEFITS_REFINER } from "./constants/ViewRefiner.js";
+
+const OutputView = {
+  printIntroduce() {
+    Console.print(ANSWER.introduce);
+  },
+  
+  printProlog(date) {
+    Console.print(`${ANSWER.prolog(date)}\n`);
+  },
+
+  printOrderMenuResult(foodsArray) {
+    Console.print(ANSWER.orderMenu);
+
+    foodsArray.forEach((food) => Console.print(food));
+    Console.print('');
+  },
+
+  printTotalOrderAmountResult(amount) {
+    Console.print(ANSWER.totalOrderAmount); 
+    
+    Console.print(PRICE_REFINER.positive(amount));
+    Console.print('');
+  },
+
+  printFreebieMenuResult(freebieMenu) {
+    Console.print(ANSWER.freebieMenu);
+
+    Console.print(freebieMenu);
+    Console.print('');
+  },
+
+  printBenefitListResult(benefitArray) {
+    Console.print(ANSWER.benefitList);
+
+    BENEFITS_REFINER.benfitList(benefitArray).forEach((benefit) => Console.print(benefit));
+    Console.print('');
+  },
+
+  printTotalBenefitAmountResult(totalBenefitAmount) {
+    Console.print(ANSWER.totalBenefitAmount);
+
+    Console.print(PRICE_REFINER.negative(totalBenefitAmount));
+    Console.print('');
+  },
+
+  printFinalPaymentAmountResult(finalPaymentAmount) {
+    Console.print(ANSWER.finalPaymentAmount);
+
+    Console.print(PRICE_REFINER.positive(finalPaymentAmount));
+    Console.print('');
+  },
+
+  printBadgeResult(badge) {
+    Console.print(ANSWER.DecemberEventBadge);
+
+    Console.print(badge);
+  },
+  
+  printErrorMesseage(messeage) {
+    Console.print(`${messeage}`);
+  }
+};
+
+export default OutputView;

리뷰: **OutputView**가 정말 깔끔하게 잘 정리되어 있는 것 같습니다 ! 👍👍
@@ -0,0 +1,81 @@
+import Supervisor from "../domain/Supervisor.js";
+import InputView from "../InputView.js";
+import OutputView from "../OutputView.js";
+
+class EventPlanner {
+  #supervisor;
+
+  constructor() {
+    this.#supervisor = new Supervisor();
+  }
+  
+  async start() {
+    OutputView.printIntroduce();
+    
+    await this.getDate();
+    await this.getMenu();
+
+    this.plannerResult(); 
+  }
+
+  async getDate() {
+    try {
+      this.#supervisor.dateUpload(await InputView.readDate());
+
+    } catch (error) {
+      OutputView.printErrorMesseage(error);
+      await this.getDate();
+    }  
+  }
+
+  async getMenu() {
+    try {
+      this.#supervisor.menuUpload(await InputView.readMenu());
+      
+    } catch (error) {
+      OutputView.printErrorMesseage(error);
+      await this.getMenu();
+    }
+  }
+
+  plannerResult() {
+    OutputView.printProlog(this.#supervisor.event().date());
+    this.orderMenu();
+    this.totalOrderAmount();
+    this.freebieMenu();
+    this.benefitList();
+    this.totalBenefitAmount();
+    this.finalPaymentAmount();
+    this.badge();
+  }
+  
+  orderMenu() {
+    OutputView.printOrderMenuResult(this.#supervisor.cashier().foodsListWithQuantity());
+  }
+
+  totalOrderAmount() {
+    OutputView.printTotalOrderAmountResult(this.#supervisor.cashier().totalFoodsPrice());
+  }
+
+  freebieMenu() {
+    OutputView.printFreebieMenuResult(this.#supervisor.cashier().freebieMenu());
+  }
+
+  benefitList() {
+    OutputView.printBenefitListResult(this.#supervisor.benefitList());
+  }
+
+  totalBenefitAmount() {
+    OutputView.printTotalBenefitAmountResult(this.#supervisor.totalBenefitAmount());
+  }
+
+  finalPaymentAmount() {
+    OutputView.printFinalPaymentAmountResult(this.#supervisor.finalPaymentAmount());
+  }
+
+  badge() {
+    OutputView.printBadgeResult(this.#supervisor.giveBadge());
+  }
+}
+
+export default EventPlanner;

리뷰: `getDate`와 `getMenu` 메서드에서 재입력 요청에 대한 `try catch` 구문이 중복되어서 사용되는 것 같아요! 재입력 요청에 대한 `try catch` 구문을 함수로 분리하는 방법에 대해 고려해보시면 좋을 것 같아요!
@@ -0,0 +1,84 @@
+import { dateValidator } from "../util/Validator.js";
+import {
+  DISCOUNT,
+  BADGE, 
+  BENEFIT_MESSEAGE
+} from "../constants/BenefitStorage.js";
+
+class Event {
+  #date;
+
+  constructor(date) {
+    dateValidator(date);    
+    this.#date = Number(date);
+  }
+
+  #week() {
+    const WEEK = ['SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'];
+    
+    return WEEK[new Date(2023, 11, this.#date).getDay()];
+  }
+
+  date() {
+    return this.#date;
+  }
+
+  christmasDDayDiscount() {
+    if (this.#date <= DISCOUNT.christmasDate) {
+      return DISCOUNT.christmasBasic + (this.#date - 1) * DISCOUNT.christmasAccumulation;
+    }
+    return DISCOUNT.zero;
+  }
+
+  weekdayDiscount(desertQuantity) {
+    if (this.#week() !== 'FR' && this.#week() !== 'SA') {
+      return desertQuantity * DISCOUNT.weekday;
+    }
+    return DISCOUNT.zero;
+  }
+
+  weekendDiscount(mainQuantity) {
+    if (this.#week() === 'FR' || this.#week() === 'SA') {
+      return mainQuantity * DISCOUNT.weekend;
+    }
+    return DISCOUNT.zero;
+  }
+
+  specialDiscount() {
+    if (this.#week() === 'SU' || this.#date === DISCOUNT.christmasDate) {
+      return DISCOUNT.special;
+    }
+    return DISCOUNT.zero;
+  }
+
+  freebieDiscount(totalFoodsPriceBeforeDiscount) {
+    if (totalFoodsPriceBeforeDiscount >= DISCOUNT.freebieStandard) {
+      return DISCOUNT.freebiePrice;
+    }
+    return DISCOUNT.zero;
+  }
+
+  condition(totalFoodsPriceBeforeDiscount) {
+    if (totalFoodsPriceBeforeDiscount >= DISCOUNT.eventStandardAmount) {
+      return true;
+    }
+    return false;
+  }
+  
+  getBadge(totalBenefitAmount) { 
+    if (totalBenefitAmount < BADGE.star) {
+      return BENEFIT_MESSEAGE.non;
+    }
+    if (totalBenefitAmount >= BADGE.santa) {
+      return BADGE.santaName;
+    }
+    if (totalBenefitAmount >= BADGE.tree) {
+      return BADGE.treeName;
+    }
+    if (totalBenefitAmount >= BADGE.star) {
+      return BADGE.starName;
+    }
+  }
+}
+
+export default Event;

리뷰: 요일에 대한 값들을 상수로 선언하고, 상수를 활용하면 가독성 향상에 도움될 것 같아요!
@@ -0,0 +1,49 @@
+import { foodValidator } from "../util/Validator.js";
+import { 
+  MENU,
+  NAME,
+  KNIFE
+} from "../constants/FoodStorage.js";
+
+class Food {
+  #name;
+
+  #quantity;
+
+  constructor(food) {
+    foodValidator(this.#enrollFood(food));
+  }
+   
+  #enrollFood(food) {
+    const foodInfoArray = food.split(KNIFE.bladePosition); 
+    this.#name = foodInfoArray[KNIFE.namePosition];
+    this.#quantity = Number(foodInfoArray[KNIFE.quantityPosition]);
+
+    return foodInfoArray;
+  }
+
+  #category() {
+    const [foodTypeInfo] = MENU
+      .filter((foodType) => Object.hasOwn(foodType, this.#name));
+  
+    return foodTypeInfo;
+  }
+
+  type() {
+    return this.#category()[NAME.type];
+  }
+
+  name() {
+    return this.#name;
+  }
+  
+  quantity() {
+    return this.#quantity;
+  }
+
+  totalPrice() {
+    return this.#category()[this.#name] * this.quantity();
+  }
+}
+
+export default Food;

리뷰: 타입, 이름, 퀀티티 등등 반환해주는 함수명에 대해 `get`을 붙여주는게 가독성 향상에 더 도움이 되지 않을까요??