ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,21 @@
+import { foodCost } from '../constants/index.js';
+
+// 문자열인 주문목록을 Map 객체로 변환
+export const createMapObj = (menuStr) => {
+  const menuArr = menuStr.split(',');
+  const menuObj = new Map();
+  menuArr.forEach((menu) => {
+    const tmp = menu.split('-');
+    menuObj.set(tmp[0], Number(tmp[1]));
+  });
+  return menuObj;
+};
+
+//  총 비용 계산
+export const createTotalCost = (menu) => {
+  let totalCost = 0;
+  menu.forEach((value, key) => {
+    totalCost += foodCost[key] * value;
+  });
+  return totalCost;
+};

리뷰: 혹시 tmp 는 어떤 단어의 약자일까요..?? 템플릿..?인가요?
@@ -0,0 +1,114 @@
+import {
+  error,
+  magicNumber,
+  uiConstants,
+  food,
+  menuBoard,
+} from './constants/index.js';
+import { createMapObj } from './utils/index.js';
+
+export default class Validate {
+  excute(obj, type) {
+    switch (type) {
+      case uiConstants.DATE:
+        this.#dateValidate(obj);
+        break;
+      case uiConstants.MENU:
+        this.#menuValidate(obj);
+        break;
+    }
+  }
+
+  #dateValidate(obj) {
+    this.#isEmpty(obj);
+    this.#isNumber(obj);
+    this.#isInRange(obj);
+  }
+
+  #menuValidate(obj) {
+    this.#isGoodForm(obj);
+    this.#isInMenuBoard(obj);
+    this.#isNotSameMenu(obj);
+    this.#isMenuCntNumber(obj);
+    this.#isOnlyBeverage(obj);
+    this.#upToTwenty(obj);
+  }
+
+  // --------------
+  // 유효성 검사 메서드모음
+
+  // 숫자인지 아닌지
+  #isNumber(obj) {
+    if (isNaN(obj)) throw new Error(error.WRONG_DATE);
+  }
+
+  // 공백인지 아닌지 체크
+  #isEmpty(obj) {
+    const regExp = /\s/;
+    if (!obj || regExp.test(obj)) throw new Error(error.WRONG_DATE);
+  }
+
+  // 숫자가 조건에 맞는지 (1~31), 0인지 아닌지도 검사가 된다.
+  #isInRange(num) {
+    if (num < magicNumber.FIRST_DAY || num > magicNumber.END_DAY)
+      throw new Error(error.WRONG_DATE);
+  }
+
+  // 음료만 주문 x
+  #isOnlyBeverage(menu) {
+    const menuObj = createMapObj(menu);
+    const menuLen = menuObj.size;
+    let beverageCnt = 0;
+    menuObj.forEach((value, key) => {
+      if (food.BEVERAGE.includes(key)) beverageCnt += 1;
+    });
+    if (menuLen === beverageCnt) throw new Error(error.NOT_ONLY_BEVERAGE);
+  }
+
+  // 메뉴 개수는 20개이하로
+  #upToTwenty(menu) {
+    const menuObj = createMapObj(menu);
+    let menuCnt = 0;
+    menuObj.forEach((value, key) => {
+      menuCnt += value;
+    });
+    if (menuCnt > magicNumber.MENU_MAX_CNT) throw new Error(error.UP_TO_TWENTY);
+  }
+
+  // 메뉴판에 없는 메뉴 x
+  #isInMenuBoard(menu) {
+    const menuObj = createMapObj(menu);
+    menuObj.forEach((value, key) => {
+      if (!menuBoard.includes(key)) throw new Error(error.WRONG_MENU);
+    });
+  }
+
+  // 메뉴 개수가 1이상의 숫자인지.
+  #isMenuCntNumber(menu) {
+    const menuObj = createMapObj(menu);
+    menuObj.forEach((value, key) => {
+      if (isNaN(value) || value < magicNumber.MENU_MIN_CNT)
+        throw new Error(error.WRONG_MENU);
+    });
+  }
+
+  // 입력이 올바른 형태로 받아지는지
+  #isGoodForm(menuStr) {
+    const menuArr = menuStr.split(',');
+    const regExp = /^[가-힣]{1,}[-]{1}[0-9]{1,}$/;
+    if (!menuArr.every((menu) => regExp.test(menu)))
+      throw new Error(error.WRONG_MENU);
+  }
+
+  // 중복 메뉴 x
+  #isNotSameMenu(menuStr) {
+    const menuArr = menuStr.split(',');
+    let before = [];
+    menuArr.forEach((menu) => {
+      const tmp = menu.split('-');
+      if (before.includes(tmp[magicNumber.BEFORE]))
+        throw new Error(error.WRONG_MENU);
+      before.push(tmp[magicNumber.BEFORE]);
+    });
+  }
+}

리뷰: 1.1 같은 소수점을 작성했을때 예기치 못한 문제가 발생할 수 있을 것 같아요!!
양의 정수인지 검증하는 로직이 있으면 좋을 것 같습니다!
@@ -0,0 +1,42 @@
+import { createTotalCost } from '../utils/index.js';
+import { EventDiscount, EventCheck, Planner } from './index.js';
+
+// 전체적인 이벤트를 출력해주는 플래너 클래스
+export default class PlannerPrint {
+  #date;
+
+  #menu;
+
+  constructor(date, menu) {
+    this.#date = Number(date);
+    this.#menu = menu;
+  }
+
+  preview() {
+    const event = new EventDiscount();
+    const discountTotal = event.discount(this.#date, this.#menu);
+    const [totalCost, eventList, menuObj] = this.#createObj(
+      this.#date,
+      this.#menu,
+    );
+    this.#print(new Planner(), [menuObj, totalCost, eventList, discountTotal]);
+  }
+
+  #print(obj, variable) {
+    const [menuObj, totalCost, eventList, discountTotal] = [...variable];
+    obj.outputOrderMenu(menuObj);
+    obj.outputTotalCost(totalCost);
+    obj.outputFreeGift(eventList);
+    obj.outputDiscountDetail(discountTotal, eventList);
+    obj.outputTotalEventCost(discountTotal);
+    obj.outputExpectationCost(discountTotal, totalCost);
+    obj.outputBadge(discountTotal);
+  }
+
+  #createObj(date, menu) {
+    const eventCheck = new EventCheck(date, menu);
+    const [eventList, menuObj] = eventCheck.get();
+    const totalCost = createTotalCost(menuObj);
+    return [totalCost, eventList, menuObj];
+  }
+}

리뷰: 플래너와 플래너프린트를 나눠 놓으신 이유가 있으실까요?? 궁금합니다 ㅎㅅㅎ!
@@ -0,0 +1,114 @@
+import {
+  error,
+  magicNumber,
+  uiConstants,
+  food,
+  menuBoard,
+} from './constants/index.js';
+import { createMapObj } from './utils/index.js';
+
+export default class Validate {
+  excute(obj, type) {
+    switch (type) {
+      case uiConstants.DATE:
+        this.#dateValidate(obj);
+        break;
+      case uiConstants.MENU:
+        this.#menuValidate(obj);
+        break;
+    }
+  }
+
+  #dateValidate(obj) {
+    this.#isEmpty(obj);
+    this.#isNumber(obj);
+    this.#isInRange(obj);
+  }
+
+  #menuValidate(obj) {
+    this.#isGoodForm(obj);
+    this.#isInMenuBoard(obj);
+    this.#isNotSameMenu(obj);
+    this.#isMenuCntNumber(obj);
+    this.#isOnlyBeverage(obj);
+    this.#upToTwenty(obj);
+  }
+
+  // --------------
+  // 유효성 검사 메서드모음
+
+  // 숫자인지 아닌지
+  #isNumber(obj) {
+    if (isNaN(obj)) throw new Error(error.WRONG_DATE);
+  }
+
+  // 공백인지 아닌지 체크
+  #isEmpty(obj) {
+    const regExp = /\s/;
+    if (!obj || regExp.test(obj)) throw new Error(error.WRONG_DATE);
+  }
+
+  // 숫자가 조건에 맞는지 (1~31), 0인지 아닌지도 검사가 된다.
+  #isInRange(num) {
+    if (num < magicNumber.FIRST_DAY || num > magicNumber.END_DAY)
+      throw new Error(error.WRONG_DATE);
+  }
+
+  // 음료만 주문 x
+  #isOnlyBeverage(menu) {
+    const menuObj = createMapObj(menu);
+    const menuLen = menuObj.size;
+    let beverageCnt = 0;
+    menuObj.forEach((value, key) => {
+      if (food.BEVERAGE.includes(key)) beverageCnt += 1;
+    });
+    if (menuLen === beverageCnt) throw new Error(error.NOT_ONLY_BEVERAGE);
+  }
+
+  // 메뉴 개수는 20개이하로
+  #upToTwenty(menu) {
+    const menuObj = createMapObj(menu);
+    let menuCnt = 0;
+    menuObj.forEach((value, key) => {
+      menuCnt += value;
+    });
+    if (menuCnt > magicNumber.MENU_MAX_CNT) throw new Error(error.UP_TO_TWENTY);
+  }
+
+  // 메뉴판에 없는 메뉴 x
+  #isInMenuBoard(menu) {
+    const menuObj = createMapObj(menu);
+    menuObj.forEach((value, key) => {
+      if (!menuBoard.includes(key)) throw new Error(error.WRONG_MENU);
+    });
+  }
+
+  // 메뉴 개수가 1이상의 숫자인지.
+  #isMenuCntNumber(menu) {
+    const menuObj = createMapObj(menu);
+    menuObj.forEach((value, key) => {
+      if (isNaN(value) || value < magicNumber.MENU_MIN_CNT)
+        throw new Error(error.WRONG_MENU);
+    });
+  }
+
+  // 입력이 올바른 형태로 받아지는지
+  #isGoodForm(menuStr) {
+    const menuArr = menuStr.split(',');
+    const regExp = /^[가-힣]{1,}[-]{1}[0-9]{1,}$/;
+    if (!menuArr.every((menu) => regExp.test(menu)))
+      throw new Error(error.WRONG_MENU);
+  }
+
+  // 중복 메뉴 x
+  #isNotSameMenu(menuStr) {
+    const menuArr = menuStr.split(',');
+    let before = [];
+    menuArr.forEach((menu) => {
+      const tmp = menu.split('-');
+      if (before.includes(tmp[magicNumber.BEFORE]))
+        throw new Error(error.WRONG_MENU);
+      before.push(tmp[magicNumber.BEFORE]);
+    });
+  }
+}

리뷰: 유효성 검사를 따로 모아두면 전 오히려 너무 정리가 안되어 보일거라고 생각했는데, 이렇게 switch 문과 함께 깔끔하게도 쓸 수 있군요..!
@@ -0,0 +1,21 @@
+import { foodCost } from '../constants/index.js';
+
+// 문자열인 주문목록을 Map 객체로 변환
+export const createMapObj = (menuStr) => {
+  const menuArr = menuStr.split(',');
+  const menuObj = new Map();
+  menuArr.forEach((menu) => {
+    const tmp = menu.split('-');
+    menuObj.set(tmp[0], Number(tmp[1]));
+  });
+  return menuObj;
+};
+
+//  총 비용 계산
+export const createTotalCost = (menu) => {
+  let totalCost = 0;
+  menu.forEach((value, key) => {
+    totalCost += foodCost[key] * value;
+  });
+  return totalCost;
+};

리뷰: 저는 로직에서 하나의 값을 도출하는 경우에는 `reduce` 메서드가 더 적합하다고 생각하는데 어떻게 생각하시나요??
@@ -0,0 +1,28 @@
+const magicNumber = {
+  ZERO: 0,
+  EVENT_START: 1,
+  EVENT_END: 25,
+  WEEK: 7,
+  GIFT_COST: 120000,
+  MIN_COST: 10000,
+  DDAY_DISCOUNT: 1000,
+  DISCOUNT_UNIT: 100,
+  DESSERT_DISCOUNT: 2023,
+  MAIN_DISCOUNT: 2023,
+  SPECIAL_DISCOUNT: 1000,
+  FREE_GIFT_CNT: 1,
+  CHAMPAGNE_COST: 25000,
+  STAR_COST: 5000,
+  TREE_COST: 10000,
+  SANTA_COST: 20000,
+  EVENT_COUNT: 5,
+  FIRST_DAY: 1,
+  END_DAY: 31,
+  MENU_MAX_CNT: 20,
+  MENU_MIN_CNT: 1,
+  BEFORE: 0,
+  NOTHING: 0,
+  INITIAL_NUM: 0,
+};
+
+export default magicNumber;

리뷰: 이번에 많은 숫자를 다루면서 `numeric seperator` 라는 개념을 처음 알고 적용해 보았는데 
숫자 단위 파악에 이점이 있었던 것 같아요!
한번 적용해보시면 좋을 것 같습니다
```
 GIFT_COST: 120_000,
 ```
@@ -0,0 +1,21 @@
+import { foodCost } from '../constants/index.js';
+
+// 문자열인 주문목록을 Map 객체로 변환
+export const createMapObj = (menuStr) => {
+  const menuArr = menuStr.split(',');
+  const menuObj = new Map();
+  menuArr.forEach((menu) => {
+    const tmp = menu.split('-');
+    menuObj.set(tmp[0], Number(tmp[1]));
+  });
+  return menuObj;
+};
+
+//  총 비용 계산
+export const createTotalCost = (menu) => {
+  let totalCost = 0;
+  menu.forEach((value, key) => {
+    totalCost += foodCost[key] * value;
+  });
+  return totalCost;
+};

리뷰: temporary - 임시라는 뜻이었는데

사실 저게 미리 아무 변수명이나 작성하고, 
나중에 고쳤어야 했는데, 미처 확인을 하지 못했네요..
@@ -0,0 +1,42 @@
+import { createTotalCost } from '../utils/index.js';
+import { EventDiscount, EventCheck, Planner } from './index.js';
+
+// 전체적인 이벤트를 출력해주는 플래너 클래스
+export default class PlannerPrint {
+  #date;
+
+  #menu;
+
+  constructor(date, menu) {
+    this.#date = Number(date);
+    this.#menu = menu;
+  }
+
+  preview() {
+    const event = new EventDiscount();
+    const discountTotal = event.discount(this.#date, this.#menu);
+    const [totalCost, eventList, menuObj] = this.#createObj(
+      this.#date,
+      this.#menu,
+    );
+    this.#print(new Planner(), [menuObj, totalCost, eventList, discountTotal]);
+  }
+
+  #print(obj, variable) {
+    const [menuObj, totalCost, eventList, discountTotal] = [...variable];
+    obj.outputOrderMenu(menuObj);
+    obj.outputTotalCost(totalCost);
+    obj.outputFreeGift(eventList);
+    obj.outputDiscountDetail(discountTotal, eventList);
+    obj.outputTotalEventCost(discountTotal);
+    obj.outputExpectationCost(discountTotal, totalCost);
+    obj.outputBadge(discountTotal);
+  }
+
+  #createObj(date, menu) {
+    const eventCheck = new EventCheck(date, menu);
+    const [eventList, menuObj] = eventCheck.get();
+    const totalCost = createTotalCost(menuObj);
+    return [totalCost, eventList, menuObj];
+  }
+}

리뷰: 이번에 UI로직과 비즈니스 로직을 확실히 분리시켜 보고 싶었습니다.
그래서 마지막 플래너 내용을 다 출력하는 역할만 하는 PlannerPrint와 
출력하기 위한 로직을 담은 Planner로 나눠봤습니다.

이렇게 나누는게 맞는지는 확실하지는 않네😂
@@ -0,0 +1,114 @@
+import {
+  error,
+  magicNumber,
+  uiConstants,
+  food,
+  menuBoard,
+} from './constants/index.js';
+import { createMapObj } from './utils/index.js';
+
+export default class Validate {
+  excute(obj, type) {
+    switch (type) {
+      case uiConstants.DATE:
+        this.#dateValidate(obj);
+        break;
+      case uiConstants.MENU:
+        this.#menuValidate(obj);
+        break;
+    }
+  }
+
+  #dateValidate(obj) {
+    this.#isEmpty(obj);
+    this.#isNumber(obj);
+    this.#isInRange(obj);
+  }
+
+  #menuValidate(obj) {
+    this.#isGoodForm(obj);
+    this.#isInMenuBoard(obj);
+    this.#isNotSameMenu(obj);
+    this.#isMenuCntNumber(obj);
+    this.#isOnlyBeverage(obj);
+    this.#upToTwenty(obj);
+  }
+
+  // --------------
+  // 유효성 검사 메서드모음
+
+  // 숫자인지 아닌지
+  #isNumber(obj) {
+    if (isNaN(obj)) throw new Error(error.WRONG_DATE);
+  }
+
+  // 공백인지 아닌지 체크
+  #isEmpty(obj) {
+    const regExp = /\s/;
+    if (!obj || regExp.test(obj)) throw new Error(error.WRONG_DATE);
+  }
+
+  // 숫자가 조건에 맞는지 (1~31), 0인지 아닌지도 검사가 된다.
+  #isInRange(num) {
+    if (num < magicNumber.FIRST_DAY || num > magicNumber.END_DAY)
+      throw new Error(error.WRONG_DATE);
+  }
+
+  // 음료만 주문 x
+  #isOnlyBeverage(menu) {
+    const menuObj = createMapObj(menu);
+    const menuLen = menuObj.size;
+    let beverageCnt = 0;
+    menuObj.forEach((value, key) => {
+      if (food.BEVERAGE.includes(key)) beverageCnt += 1;
+    });
+    if (menuLen === beverageCnt) throw new Error(error.NOT_ONLY_BEVERAGE);
+  }
+
+  // 메뉴 개수는 20개이하로
+  #upToTwenty(menu) {
+    const menuObj = createMapObj(menu);
+    let menuCnt = 0;
+    menuObj.forEach((value, key) => {
+      menuCnt += value;
+    });
+    if (menuCnt > magicNumber.MENU_MAX_CNT) throw new Error(error.UP_TO_TWENTY);
+  }
+
+  // 메뉴판에 없는 메뉴 x
+  #isInMenuBoard(menu) {
+    const menuObj = createMapObj(menu);
+    menuObj.forEach((value, key) => {
+      if (!menuBoard.includes(key)) throw new Error(error.WRONG_MENU);
+    });
+  }
+
+  // 메뉴 개수가 1이상의 숫자인지.
+  #isMenuCntNumber(menu) {
+    const menuObj = createMapObj(menu);
+    menuObj.forEach((value, key) => {
+      if (isNaN(value) || value < magicNumber.MENU_MIN_CNT)
+        throw new Error(error.WRONG_MENU);
+    });
+  }
+
+  // 입력이 올바른 형태로 받아지는지
+  #isGoodForm(menuStr) {
+    const menuArr = menuStr.split(',');
+    const regExp = /^[가-힣]{1,}[-]{1}[0-9]{1,}$/;
+    if (!menuArr.every((menu) => regExp.test(menu)))
+      throw new Error(error.WRONG_MENU);
+  }
+
+  // 중복 메뉴 x
+  #isNotSameMenu(menuStr) {
+    const menuArr = menuStr.split(',');
+    let before = [];
+    menuArr.forEach((menu) => {
+      const tmp = menu.split('-');
+      if (before.includes(tmp[magicNumber.BEFORE]))
+        throw new Error(error.WRONG_MENU);
+      before.push(tmp[magicNumber.BEFORE]);
+    });
+  }
+}

리뷰: 아이고.. 그 부분을 놓쳤네요 ㅜㅜ
@@ -0,0 +1,21 @@
+import { foodCost } from '../constants/index.js';
+
+// 문자열인 주문목록을 Map 객체로 변환
+export const createMapObj = (menuStr) => {
+  const menuArr = menuStr.split(',');
+  const menuObj = new Map();
+  menuArr.forEach((menu) => {
+    const tmp = menu.split('-');
+    menuObj.set(tmp[0], Number(tmp[1]));
+  });
+  return menuObj;
+};
+
+//  총 비용 계산
+export const createTotalCost = (menu) => {
+  let totalCost = 0;
+  menu.forEach((value, key) => {
+    totalCost += foodCost[key] * value;
+  });
+  return totalCost;
+};

리뷰: 간단한 로직이 필요한 경우에는 그냥 `forEach`를 쓴 것 같아요

대신에 더 복잡한 경우에는 `reduce`를 쓸 것 같아요.
`forEach` 보다 다양한 로직을 넣을(?)수 있는 `reduce`의 활용법이 더 많은 것 같아요.
@@ -0,0 +1,59 @@
+package christmas.utils
+
+object Constant {
+    /* Model */
+    const val FIRST_DAY_DISCOUNT_AMOUNT = 1_000
+    const val DAILY_DISCOUNT_AMOUNT = 100
+    const val ONE_DAY_DECREASE = 1
+    const val WEEK_DISCOUNT_AMOUNT = 2_023
+    const val CHRISTMAS_DAY_DISCOUNT_AMOUNT = 1_000
+    const val CHAMPAGNE_CONDITION_AMOUNT = 120_000
+    const val CHRISTMAS_DAY = 25
+    const val DISCOUNT_THRESHOLD = 10_000
+    const val EMPTY = 0
+    const val BADGE_STAR = "별"
+    const val BADGE_TREE = "트리"
+    const val BADGE_SANTA = "산타"
+    const val NONE = "없음"
+    const val STAR_MIN = 5_000
+    const val STAR_MAX = 9_999
+    const val TREE_MIN = 10_000
+    const val TREE_MAX = 19_999
+    const val SANTA_MIN = 20_000
+    const val FIRST_INDEX = 0
+    const val SECOND_INDEX = 1
+
+    /* Validate */
+    const val ORDER_ITEMS_SIZE = 2
+    const val VISIT_DATE_MIN = 1
+    const val VISIT_DATE_MAX = 31
+    const val QUANTITY_MINIMUM = 1
+    const val QUANTITY_MAXIMUM = 20
+    const val HYPHEN = "-"
+    const val COMMA = ","
+    const val NUMERIC_REGEX = "^\\d+$"
+    private const val ERROR_MESSAGE_PREFIX = "[ERROR] "
+    const val ERROR_MESSAGE_INPUT_VISIT_DATE = ERROR_MESSAGE_PREFIX + "유효하지 않은 날짜입니다. 다시 입력해 주세요."
+    const val ERROR_MESSAGE_INPUT_MENU = ERROR_MESSAGE_PREFIX + "유효하지 않은 주문입니다. 다시 입력해 주세요."
+
+    /* InputView */
+    const val VISIT_DATE_ANNOUNCE_MESSAGE = "12월 중 식당 예상 방문 날짜는 언제인가요? (숫자만 입력해 주세요!)"
+    const val MENU_ANNOUNCE_MESSAGE = "주문하실 메뉴를 메뉴와 개수를 알려 주세요. (e.g. 해산물파스타-2,레드와인-1,초코케이크-1)"
+
+    /* OutPutView */
+    private const val PRINT_ENTER = "\n"
+    const val START_PLANNER_MESSAGE = "안녕하세요! 우테코 식당 12월 이벤트 플래너입니다."
+    const val PREVIEW_ANNOUNCE_MESSAGE = "12월 %d일에 우테코 식당에서 받을 이벤트 혜택 미리 보기!"
+    const val ORDER_MENU_TITLE_MESSAGE = "$PRINT_ENTER<주문 메뉴>"
+    const val ITEMS_RESULT = "%s %d개"
+    const val TOTAL_ORDER_SUM_TITLE_MESSAGE = "$PRINT_ENTER<할인 전 총주문 금액>"
+    const val TOTAL_AMOUNT_RESULT = "%,d원"
+    const val PRESENT_MENU_TITLE_MESSAGE = "$PRINT_ENTER<증정 메뉴>"
+    const val PRESENT_AMOUNT = 1
+    const val BENEFIT_DETAILS_TITLE_MESSAGE = "$PRINT_ENTER<혜택 내역>"
+    const val FREEBIE_EVENT_NAME = "증정 이벤트"
+    const val BENEFIT_RESULT = "%s: -%,d원"
+    const val BENEFIT_AMOUNT_TITLE_MESSAGE = "$PRINT_ENTER<총혜택 금액>"
+    const val PAYMENT_AMOUNT_TITLE_MESSAGE = "$PRINT_ENTER<할인 후 예상 결제 금액>"
+    const val BADGE_TITLE_MESSAGE = "$PRINT_ENTER<12월 이벤트 배지>"
+}
\ No newline at end of file

리뷰: "ERROR"을 저렇게 따로 const val로 빼놓으니까 한결 가독성도 올라가고 혹시 모를 오타가 있어도 발견하기 쉬울 것 같네요(ex.ERROR을 Error로 썼다거나 하는 오류 들이요)
@@ -0,0 +1,79 @@
+package christmas.model
+
+import christmas.utils.Constant.BADGE_SANTA
+import christmas.utils.Constant.BADGE_STAR
+import christmas.utils.Constant.BADGE_TREE
+import christmas.utils.Constant.CHAMPAGNE_CONDITION_AMOUNT
+import christmas.utils.Constant.CHRISTMAS_DAY_DISCOUNT_AMOUNT
+import christmas.utils.Constant.DAILY_DISCOUNT_AMOUNT
+import christmas.utils.Constant.FIRST_DAY_DISCOUNT_AMOUNT
+import christmas.utils.Constant.EMPTY
+import christmas.utils.Constant.NONE
+import christmas.utils.Constant.ONE_DAY_DECREASE
+import christmas.utils.Constant.SANTA_MIN
+import christmas.utils.Constant.STAR_MAX
+import christmas.utils.Constant.STAR_MIN
+import christmas.utils.Constant.TREE_MAX
+import christmas.utils.Constant.TREE_MIN
+import christmas.utils.Constant.WEEK_DISCOUNT_AMOUNT
+
+class DecemberEvent {
+    fun applyTheDayDiscount(date: Int): Int {
+        val dailyDiscount = (date - ONE_DAY_DECREASE) * DAILY_DISCOUNT_AMOUNT
+
+        return FIRST_DAY_DISCOUNT_AMOUNT + dailyDiscount
+    }
+
+    fun applyWeekdayDiscount(orderMenu: List<OrderItems>): Int {
+        var dessertMenuCount = EMPTY
+
+        for (item in orderMenu) {
+            val increaseCount = increaseDessertMenuCount(item.getMenuName(), item.getQuantity())
+            dessertMenuCount += increaseCount
+        }
+
+        return dessertMenuCount * WEEK_DISCOUNT_AMOUNT
+    }
+
+    private fun increaseDessertMenuCount(menuName: String, quantity: Int): Int {
+        if (menuName == StoreMenu.CHOCO_CAKE.menuName || menuName == StoreMenu.ICE_CREAM.menuName)
+            return quantity
+
+        return EMPTY
+    }
+
+
+    fun applyWeekendDiscount(orderMenu: List<OrderItems>): Int {
+        var mainMenuCount = EMPTY
+
+        for (item in orderMenu) {
+            val increaseCount = increaseMainMenuCount(item.getMenuName(), item.getQuantity())
+            mainMenuCount += increaseCount
+        }
+
+        return mainMenuCount * WEEK_DISCOUNT_AMOUNT
+    }
+
+    private fun increaseMainMenuCount(menuName: String, quantity: Int): Int {
+        if (menuName == StoreMenu.T_BONE_STEAK.menuName || menuName == StoreMenu.BBQ_RIBS.menuName ||
+            menuName == StoreMenu.SEAFOOD_PASTA.menuName || menuName == StoreMenu.CHRISTMAS_PASTA.menuName
+        )
+            return quantity
+
+        return EMPTY
+    }
+
+    fun applySpecialDiscount(): Int = CHRISTMAS_DAY_DISCOUNT_AMOUNT
+
+    fun presentChampagne(totalOrderAmount: Int): Boolean = totalOrderAmount >= CHAMPAGNE_CONDITION_AMOUNT
+
+    fun assignBadge(benefitAmount: Int): String {
+        when {
+            benefitAmount in STAR_MIN..STAR_MAX -> return BADGE_STAR
+            benefitAmount in TREE_MIN..TREE_MAX -> return BADGE_TREE
+            benefitAmount >= SANTA_MIN -> return BADGE_SANTA
+        }
+
+        return NONE
+    }
+}
\ No newline at end of file

리뷰: 코틀린 코딩 컨벤션에서는 의무 사항은 아니지만, 3개 이상의 조건에 대해서는 when의 사용을 권장합니다. 만약 if를 사용하고 싶으시다면, 가독성 적인 측면을 위해서 각각 조건에 대해 여러 줄로 나누시는 등의 방법으로 가독성을 높이는 것이 좋을 것 같습니다.
@@ -0,0 +1,58 @@
+package christmas.controller
+
+import christmas.model.Customer
+import christmas.model.EventHelper
+import christmas.model.OrderItems
+import christmas.model.StoreMenu.Companion.sortOrderMenu
+import christmas.model.StoreMenu.Companion.splitOrderMenu
+import christmas.model.StoreMenu.Companion.splitOrderMenuForValidate
+import christmas.utils.Validate.validateOrderMenu
+import christmas.utils.Validate.validateVisitDate
+import christmas.view.InputView
+import christmas.view.OutputView
+
+class ChristmasController {
+    private val input = InputView()
+    private val output = OutputView()
+
+    fun run() {
+        output.printStartPlanner()
+        val customer = Customer(inputVisitDate(), inputOrderMenu())
+
+        customer.requestApplyDecemberEvent()
+        previewBenefit(customer, customer.getEventHelper())
+    }
+
+    private fun inputVisitDate(): Int {
+        return try {
+            val visitDateInput = input.readVisitDate()
+            validateVisitDate(visitDateInput)
+            visitDateInput.toInt()
+        } catch (e: IllegalArgumentException) {
+            println(e.message)
+            inputVisitDate()
+        }
+    }
+
+    private fun inputOrderMenu(): List<OrderItems> {
+        return try {
+            val orderMenuInput = input.readOrderMenu()
+            validateOrderMenu(splitOrderMenuForValidate(orderMenuInput))
+            splitOrderMenu(orderMenuInput)
+        } catch (e: IllegalArgumentException) {
+            println(e.message)
+            inputOrderMenu()
+        }
+    }
+
+    private fun previewBenefit(customer: Customer, eventHelper: EventHelper) {
+        output.printPreviewAnnounce(customer.getVisitDate())
+        output.printOrderMenu(sortOrderMenu(customer.getOrderMenu()))
+        output.printTotalOrderPrize(customer.calculateTotalOrderSum())
+        output.printFreebieMenu(eventHelper.getFreebie())
+        output.printBenefitDetails(eventHelper.getDiscountHistory(), eventHelper.getFreebie())
+        output.printBenefitAmount(eventHelper.calculateTotalBenefitAmount())
+        output.printPaymentAmount(customer.calculateTotalOrderSum(), eventHelper.getDiscountHistory())
+        output.printBadge(eventHelper.getBadge())
+    }
+}
\ No newline at end of file

리뷰: 이름에 맞게 검증이 완료되면 검증이 완료된 값을 뱉는건 어떨까요?