ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,58 @@
+import { DATE, DISCOUNT, INFO, MENU, MENU_KIND } from '../Util/constants';
+
+class Discount {
+  calculateDiscount(date, menu) {
+    const discount = {};
+    const addDisCount = (key, value) => {
+      if (value !== 0) discount[key] = value;
+    };
+
+    addDisCount(DISCOUNT.CHRISTMAS, this.isPeriod(date));
+    addDisCount(DISCOUNT.WEEK, this.isWeekday(date, menu));
+    addDisCount(DISCOUNT.WEEKEND, this.isWeekend(date, menu));
+    addDisCount(DISCOUNT.SPECIAL, this.isSpecialDay(date));
+    return discount;
+  }
+
+  isPeriod(date) {
+    let periodDiscount = 0;
+    if (date >= DATE.EVENT_START && date <= DATE.EVENT_END) {
+      periodDiscount +=
+        -DATE.PERIOD_DISCOUNT + (date - 1) * -DATE.PER_DAY_DISCOUNT;
+    }
+    return periodDiscount;
+  }
+
+  isWeekday(date, menu) {
+    let weekDisCount = 0;
+    if (!(date % 7 === 1 || date % 7 === 2)) {
+      let count = 0;
+      Object.keys(menu).forEach(name => {
+        if (MENU[name].kind === MENU_KIND.DESSERT) count += menu[name];
+      });
+      weekDisCount += -INFO.WEEK_DISCOUNT * count;
+    }
+    return weekDisCount;
+  }
+
+  isWeekend(date, menu) {
+    let weekendDisCount = 0;
+    if (date % 7 === 1 || date % 7 === 2) {
+      let count = 0;
+      Object.keys(menu).forEach(name => {
+        if (MENU[name].kind === MENU_KIND.MAIN) count += menu[name];
+      });
+      weekendDisCount += -INFO.WEEK_DISCOUNT * count;
+    }
+    return weekendDisCount;
+  }
+
+  isSpecialDay(date) {
+    let specialDayDiscount = 0;
+    if (DATE.SPECIAL_DATE.includes(date))
+      specialDayDiscount += -DATE.SPECIAL_DISCOUNT;
+    return specialDayDiscount;
+  }
+}
+
+export default Discount;

리뷰: 각 case마다 따로 구분을 지어 주신 거라면,
```javascript
let specialDayDiscount = 0;
specialDayDiscount += -DATE.SPECIAL_DISCOUNT;
```
와 같은 방식도 좋지만, 아래와 같은 방법으로 한번에 선언해 버려도 괜찮지 않을까 싶어요!
```javascript
const specialDayDiscount = -DATE.SPECIAL_DISCOUNT;
```
@@ -0,0 +1,90 @@
+const MENU_KIND = Object.freeze({
+  APPETIZER: '애피타이저',
+  MAIN: '메인',
+  DESSERT: '디저트',
+  DRINK: '음료',
+});
+
+const MENU = Object.freeze({
+  양송이수프: { price: 6000, kind: MENU_KIND.APPETIZER },
+  타파스: { price: 5500, kind: MENU_KIND.APPETIZER },
+  시저샐러드: { price: 8000, kind: MENU_KIND.APPETIZER },
+  티본스테이크: { price: 55000, kind: MENU_KIND.MAIN },
+  바비큐립: { price: 54000, kind: MENU_KIND.MAIN },
+  해산물파스타: { price: 35000, kind: MENU_KIND.MAIN },
+  크리스마스파스타: { price: 25000, kind: MENU_KIND.MAIN },
+  초코케이크: { price: 15000, kind: MENU_KIND.DESSERT },
+  아이스크림: { price: 5000, kind: MENU_KIND.DESSERT },
+  제로콜라: { price: 3000, kind: MENU_KIND.DRINK },
+  레드와인: { price: 60000, kind: MENU_KIND.DRINK },
+  샴페인: { price: 25000, kind: MENU_KIND.DRINK },
+});
+
+const DATE = Object.freeze({
+  EVENT_START: 1,
+  EVENT_END: 25,
+  PERIOD_DISCOUNT: 1000,
+  SPECIAL_DISCOUNT: 1000,
+  SPECIAL_DATE: [3, 10, 17, 24, 25, 31],
+  PER_DAY_DISCOUNT: 100,
+});
+
+const INFO = Object.freeze({
+  UNIT: '원',
+  COUNT: '개',
+  WEEK_DISCOUNT: 2023,
+  ORDER_MINIMUM: 10000,
+  GIFT_CONDITION: 120000,
+  GIFT_PRICE: 25000,
+  GIFT: '샴페인 1개',
+  NONE: '없음',
+  BADGE: {
+    SANTA: { PRICE: 20000, NAME: '산타' },
+    TREE: { PRICE: 10000, NAME: '트리' },
+    STAR: { PRICE: 5000, NAME: '별' },
+  },
+});
+
+const DISCOUNT = Object.freeze({
+  CHRISTMAS: '크리스마스 디데이 할인',
+  WEEK: '평일 할인',
+  WEEKEND: '주말 할인',
+  SPECIAL: '특별 할인',
+  GIFTS: '증정 이벤트',
+});
+
+const INPUT_MESSAGE = Object.freeze({
+  DATE: '12월 중 식당 예상 방문 날짜는 언제인가요? (숫자만 입력해 주세요!)\n',
+  ORDER:
+    '주문하실 메뉴와 개수를 알려 주세요. (e.g. 해산물파스타-2,레드와인-1,초코케이크-1)\n',
+});
+
+const OUTPUT_MESSAGE = Object.freeze({
+  INTRO: '안녕하세요! 우테코 식당 12월 이벤트 플래너입니다.\n',
+  TITLE: {
+    PREVIEW: (date) => `12월 ${date}일에 우테코 식당에서 받을 이벤트 혜택 미리보기!\n`,
+    ORDER_MENU: '<주문 메뉴>',
+    BEFORE_DISCOUNT: '<할인 전 총주문 금액>',
+    AFTER_DISCOUNT: '<할인 후 예상 결제 금액>',
+    GIFT: '<증정 메뉴>',
+    BENEFIT: '<혜택 내역>',
+    BENEFIT_AMOUNT: '<총혜택 금액>',
+    BADGE: '<12월 이벤트 배지>',
+  },
+});
+
+const ERROR_MESSAGE = Object.freeze({
+  INVALID_DATE: '[ERROR] 유효하지 않은 날짜입니다. 다시 입력해 주세요.',
+  INVALID_ORDER: '[ERROR] 유효하지 않은 주문입니다. 다시 입력해 주세요.',
+});
+
+export {
+  MENU_KIND,
+  MENU,
+  DATE,
+  INFO,
+  DISCOUNT,
+  INPUT_MESSAGE,
+  OUTPUT_MESSAGE,
+  ERROR_MESSAGE,
+};

리뷰: 저는 이런 방법을 몰라서 두개로 나눠서 썼었는데, 좋은 방법이 있었군요!
좋은 정보 감사합니다 :)
@@ -0,0 +1,90 @@
+const MENU_KIND = Object.freeze({
+  APPETIZER: '애피타이저',
+  MAIN: '메인',
+  DESSERT: '디저트',
+  DRINK: '음료',
+});
+
+const MENU = Object.freeze({
+  양송이수프: { price: 6000, kind: MENU_KIND.APPETIZER },
+  타파스: { price: 5500, kind: MENU_KIND.APPETIZER },
+  시저샐러드: { price: 8000, kind: MENU_KIND.APPETIZER },
+  티본스테이크: { price: 55000, kind: MENU_KIND.MAIN },
+  바비큐립: { price: 54000, kind: MENU_KIND.MAIN },
+  해산물파스타: { price: 35000, kind: MENU_KIND.MAIN },
+  크리스마스파스타: { price: 25000, kind: MENU_KIND.MAIN },
+  초코케이크: { price: 15000, kind: MENU_KIND.DESSERT },
+  아이스크림: { price: 5000, kind: MENU_KIND.DESSERT },
+  제로콜라: { price: 3000, kind: MENU_KIND.DRINK },
+  레드와인: { price: 60000, kind: MENU_KIND.DRINK },
+  샴페인: { price: 25000, kind: MENU_KIND.DRINK },
+});
+
+const DATE = Object.freeze({
+  EVENT_START: 1,
+  EVENT_END: 25,
+  PERIOD_DISCOUNT: 1000,
+  SPECIAL_DISCOUNT: 1000,
+  SPECIAL_DATE: [3, 10, 17, 24, 25, 31],
+  PER_DAY_DISCOUNT: 100,
+});
+
+const INFO = Object.freeze({
+  UNIT: '원',
+  COUNT: '개',
+  WEEK_DISCOUNT: 2023,
+  ORDER_MINIMUM: 10000,
+  GIFT_CONDITION: 120000,
+  GIFT_PRICE: 25000,
+  GIFT: '샴페인 1개',
+  NONE: '없음',
+  BADGE: {
+    SANTA: { PRICE: 20000, NAME: '산타' },
+    TREE: { PRICE: 10000, NAME: '트리' },
+    STAR: { PRICE: 5000, NAME: '별' },
+  },
+});
+
+const DISCOUNT = Object.freeze({
+  CHRISTMAS: '크리스마스 디데이 할인',
+  WEEK: '평일 할인',
+  WEEKEND: '주말 할인',
+  SPECIAL: '특별 할인',
+  GIFTS: '증정 이벤트',
+});
+
+const INPUT_MESSAGE = Object.freeze({
+  DATE: '12월 중 식당 예상 방문 날짜는 언제인가요? (숫자만 입력해 주세요!)\n',
+  ORDER:
+    '주문하실 메뉴와 개수를 알려 주세요. (e.g. 해산물파스타-2,레드와인-1,초코케이크-1)\n',
+});
+
+const OUTPUT_MESSAGE = Object.freeze({
+  INTRO: '안녕하세요! 우테코 식당 12월 이벤트 플래너입니다.\n',
+  TITLE: {
+    PREVIEW: (date) => `12월 ${date}일에 우테코 식당에서 받을 이벤트 혜택 미리보기!\n`,
+    ORDER_MENU: '<주문 메뉴>',
+    BEFORE_DISCOUNT: '<할인 전 총주문 금액>',
+    AFTER_DISCOUNT: '<할인 후 예상 결제 금액>',
+    GIFT: '<증정 메뉴>',
+    BENEFIT: '<혜택 내역>',
+    BENEFIT_AMOUNT: '<총혜택 금액>',
+    BADGE: '<12월 이벤트 배지>',
+  },
+});
+
+const ERROR_MESSAGE = Object.freeze({
+  INVALID_DATE: '[ERROR] 유효하지 않은 날짜입니다. 다시 입력해 주세요.',
+  INVALID_ORDER: '[ERROR] 유효하지 않은 주문입니다. 다시 입력해 주세요.',
+});
+
+export {
+  MENU_KIND,
+  MENU,
+  DATE,
+  INFO,
+  DISCOUNT,
+  INPUT_MESSAGE,
+  OUTPUT_MESSAGE,
+  ERROR_MESSAGE,
+};

리뷰: 정보 감사합니다 ㅎㅎ ☺️
@@ -0,0 +1,90 @@
+const MENU_KIND = Object.freeze({
+  APPETIZER: '애피타이저',
+  MAIN: '메인',
+  DESSERT: '디저트',
+  DRINK: '음료',
+});
+
+const MENU = Object.freeze({
+  양송이수프: { price: 6000, kind: MENU_KIND.APPETIZER },
+  타파스: { price: 5500, kind: MENU_KIND.APPETIZER },
+  시저샐러드: { price: 8000, kind: MENU_KIND.APPETIZER },
+  티본스테이크: { price: 55000, kind: MENU_KIND.MAIN },
+  바비큐립: { price: 54000, kind: MENU_KIND.MAIN },
+  해산물파스타: { price: 35000, kind: MENU_KIND.MAIN },
+  크리스마스파스타: { price: 25000, kind: MENU_KIND.MAIN },
+  초코케이크: { price: 15000, kind: MENU_KIND.DESSERT },
+  아이스크림: { price: 5000, kind: MENU_KIND.DESSERT },
+  제로콜라: { price: 3000, kind: MENU_KIND.DRINK },
+  레드와인: { price: 60000, kind: MENU_KIND.DRINK },
+  샴페인: { price: 25000, kind: MENU_KIND.DRINK },
+});
+
+const DATE = Object.freeze({
+  EVENT_START: 1,
+  EVENT_END: 25,
+  PERIOD_DISCOUNT: 1000,
+  SPECIAL_DISCOUNT: 1000,
+  SPECIAL_DATE: [3, 10, 17, 24, 25, 31],
+  PER_DAY_DISCOUNT: 100,
+});
+
+const INFO = Object.freeze({
+  UNIT: '원',
+  COUNT: '개',
+  WEEK_DISCOUNT: 2023,
+  ORDER_MINIMUM: 10000,
+  GIFT_CONDITION: 120000,
+  GIFT_PRICE: 25000,
+  GIFT: '샴페인 1개',
+  NONE: '없음',
+  BADGE: {
+    SANTA: { PRICE: 20000, NAME: '산타' },
+    TREE: { PRICE: 10000, NAME: '트리' },
+    STAR: { PRICE: 5000, NAME: '별' },
+  },
+});
+
+const DISCOUNT = Object.freeze({
+  CHRISTMAS: '크리스마스 디데이 할인',
+  WEEK: '평일 할인',
+  WEEKEND: '주말 할인',
+  SPECIAL: '특별 할인',
+  GIFTS: '증정 이벤트',
+});
+
+const INPUT_MESSAGE = Object.freeze({
+  DATE: '12월 중 식당 예상 방문 날짜는 언제인가요? (숫자만 입력해 주세요!)\n',
+  ORDER:
+    '주문하실 메뉴와 개수를 알려 주세요. (e.g. 해산물파스타-2,레드와인-1,초코케이크-1)\n',
+});
+
+const OUTPUT_MESSAGE = Object.freeze({
+  INTRO: '안녕하세요! 우테코 식당 12월 이벤트 플래너입니다.\n',
+  TITLE: {
+    PREVIEW: (date) => `12월 ${date}일에 우테코 식당에서 받을 이벤트 혜택 미리보기!\n`,
+    ORDER_MENU: '<주문 메뉴>',
+    BEFORE_DISCOUNT: '<할인 전 총주문 금액>',
+    AFTER_DISCOUNT: '<할인 후 예상 결제 금액>',
+    GIFT: '<증정 메뉴>',
+    BENEFIT: '<혜택 내역>',
+    BENEFIT_AMOUNT: '<총혜택 금액>',
+    BADGE: '<12월 이벤트 배지>',
+  },
+});
+
+const ERROR_MESSAGE = Object.freeze({
+  INVALID_DATE: '[ERROR] 유효하지 않은 날짜입니다. 다시 입력해 주세요.',
+  INVALID_ORDER: '[ERROR] 유효하지 않은 주문입니다. 다시 입력해 주세요.',
+});
+
+export {
+  MENU_KIND,
+  MENU,
+  DATE,
+  INFO,
+  DISCOUNT,
+  INPUT_MESSAGE,
+  OUTPUT_MESSAGE,
+  ERROR_MESSAGE,
+};

리뷰: 오 금액이 큰 경우 더 유용하겠네요 정보 감사합니다 !
@@ -0,0 +1,60 @@
+import { ERROR_MESSAGE, MENU, MENU_KIND } from './constants';
+
+const validateDate = date => {
+  if (!(date >= 1 && date <= 31)) {
+    throw new Error(ERROR_MESSAGE.INVALID_DATE);
+  }
+  if (typeof date !== 'number' || !Number.isInteger(date)) {
+    throw new Error(ERROR_MESSAGE.INVALID_DATE);
+  }
+};
+
+const validateName = menu => {
+  const checkedName = new Set();
+  menu.forEach(([name]) => {
+    if (!Object.keys(MENU).includes(name)) {
+      throw new Error(ERROR_MESSAGE.INVALID_ORDER);
+    }
+    checkedName.add(name);
+  });
+};
+
+const validateDuplicate = menu => {
+  const checkedName = new Set();
+  menu.forEach(([name]) => {
+    if (checkedName.has(name)) {
+      throw new Error(ERROR_MESSAGE.INVALID_ORDER);
+    }
+    checkedName.add(name);
+  });
+};
+
+const validateOnlyDrink = menu => {
+  const types = new Set(menu.map(([name]) => MENU[name].type));
+  if (types.size === 1 && types.has(MENU_KIND.DRINK)) {
+    throw new Error(ERROR_MESSAGE.INVALID_ORDER);
+  }
+};
+
+const validateCount = menu => {
+  if (!menu.every(([, count]) => count >= 1)) {
+    throw new Error(ERROR_MESSAGE.INVALID_ORDER);
+  }
+};
+
+const validateTotalCount = menu => {
+  const total = menu.reduce((acc, [, count]) => acc + count, 0);
+  if (total > 20) {
+    throw new Error(ERROR_MESSAGE.INVALID_ORDER);
+  }
+};
+
+const validateOrder = order => {
+  validateName(order);
+  validateDuplicate(order);
+  validateOnlyDrink(order);
+  validateCount(order);
+  validateTotalCount(order);
+};
+
+export { validateDate, validateOrder };

리뷰: 에어비앤비 컨벤션에서 지양하는 거 같더라구요.. 그래서 혹시나 해서 뻈는데 사용하는게 좋았던 거 같네요 🤔
@@ -0,0 +1,10 @@
+const parseInput = userInput =>
+  userInput
+    .trim()
+    .split(',')
+    .map(item => {
+      const [name, quantity] = item.trim().split('-');
+      return [name, Number(quantity)];
+    });
+
+export default parseInput;

리뷰: 아마 저런 입력이 들어온다면 유효성 검사에서 걸러내줄 수 있을 거 같습니다 ! 그래도 파싱하는 과정에서 발생할 수 있는 에러 경우를 알려주셔서 좀 더 고민해볼 수 있는 계기가 될 거 같아요 감사합니다 ☺️
@@ -0,0 +1,96 @@
+import { DISCOUNT, INFO, MENU } from '../Util/constants';
+import Discount from './Discount';
+
+class Client {
+  #date;
+  #order;
+
+  constructor(date, order) {
+    this.#date = date;
+    this.#order = order;
+  }
+
+  calculateBenefits() {
+    const discount = new Discount();
+    return discount.calculateDiscount(this.#date, this.#order);
+  }
+
+  getDate() {
+    return this.#date;
+  }
+
+  getOrderList() {
+    let output = '';
+    Object.entries(this.#order).forEach(([name, count]) => {
+      output += `${name} ${count}${INFO.COUNT}\n`;
+    });
+    return output;
+  }
+
+  getBeforeDiscount() {
+    let cost = 0;
+    Object.entries(this.#order).forEach(([name, count]) => {
+      cost += MENU[name].price * count;
+    });
+    return cost;
+  }
+
+  getGift() {
+    const isGift = this.getBeforeDiscount() >= INFO.GIFT_CONDITION;
+    return (isGift && INFO.GIFT) || INFO.NONE;
+  }
+
+  getBenefitList() {
+    const benefit = this.calculateBenefits();
+    if (!benefit || Object.keys(benefit).length === 0) {
+      return INFO.NONE;
+    }
+    let output = '';
+    Object.entries(benefit).forEach(([discount, cost]) => {
+      output += `${discount}: ${cost.toLocaleString()}${INFO.UNIT}\n`;
+    });
+    if (this.getGift() !== INFO.NONE)
+      output += `${DISCOUNT.GIFTS}: -${INFO.GIFT_PRICE.toLocaleString()}${
+        INFO.UNIT
+      }\n`;
+    return output;
+  }
+
+  getDiscountAmount() {
+    const benefit = this.calculateBenefits();
+    if (!benefit || Object.keys(benefit).length === 0) return 0;
+
+    let totalAmount = Object.values(benefit).reduce((acc, cur) => acc + cur);
+    if (this.getGift() !== INFO.NONE) totalAmount += -INFO.GIFT_PRICE;
+    return totalAmount;
+  }
+
+  getAfterDiscount() {
+    const beforeDiscount = this.getBeforeDiscount();
+    const discountAmount = this.getDiscountAmount();
+    let gift = 0;
+
+    if (this.getGift() !== INFO.NONE) {
+      gift = INFO.GIFT_PRICE;
+    }
+
+    const total = beforeDiscount + discountAmount + gift;
+    return total;
+  }
+
+  getEventBadge() {
+    const benefit = this.getDiscountAmount();
+
+    switch (true) {
+      case benefit <= -INFO.BADGE.SANTA.PRICE:
+        return INFO.BADGE.SANTA.NAME;
+      case benefit <= -INFO.BADGE.TREE.PRICE:
+        return INFO.BADGE.TREE.NAME;
+      case benefit <= -INFO.BADGE.STAR.PRICE:
+        return INFO.BADGE.STAR.NAME;
+      default:
+        return INFO.NONE;
+    }
+  }
+}
+export default Client;

리뷰: 오 훨씬 깔끔한 거 같아 좋네요 피드백 감사합니다 ☺️
@@ -0,0 +1,96 @@
+import { DISCOUNT, INFO, MENU } from '../Util/constants';
+import Discount from './Discount';
+
+class Client {
+  #date;
+  #order;
+
+  constructor(date, order) {
+    this.#date = date;
+    this.#order = order;
+  }
+
+  calculateBenefits() {
+    const discount = new Discount();
+    return discount.calculateDiscount(this.#date, this.#order);
+  }
+
+  getDate() {
+    return this.#date;
+  }
+
+  getOrderList() {
+    let output = '';
+    Object.entries(this.#order).forEach(([name, count]) => {
+      output += `${name} ${count}${INFO.COUNT}\n`;
+    });
+    return output;
+  }
+
+  getBeforeDiscount() {
+    let cost = 0;
+    Object.entries(this.#order).forEach(([name, count]) => {
+      cost += MENU[name].price * count;
+    });
+    return cost;
+  }
+
+  getGift() {
+    const isGift = this.getBeforeDiscount() >= INFO.GIFT_CONDITION;
+    return (isGift && INFO.GIFT) || INFO.NONE;
+  }
+
+  getBenefitList() {
+    const benefit = this.calculateBenefits();
+    if (!benefit || Object.keys(benefit).length === 0) {
+      return INFO.NONE;
+    }
+    let output = '';
+    Object.entries(benefit).forEach(([discount, cost]) => {
+      output += `${discount}: ${cost.toLocaleString()}${INFO.UNIT}\n`;
+    });
+    if (this.getGift() !== INFO.NONE)
+      output += `${DISCOUNT.GIFTS}: -${INFO.GIFT_PRICE.toLocaleString()}${
+        INFO.UNIT
+      }\n`;
+    return output;
+  }
+
+  getDiscountAmount() {
+    const benefit = this.calculateBenefits();
+    if (!benefit || Object.keys(benefit).length === 0) return 0;
+
+    let totalAmount = Object.values(benefit).reduce((acc, cur) => acc + cur);
+    if (this.getGift() !== INFO.NONE) totalAmount += -INFO.GIFT_PRICE;
+    return totalAmount;
+  }
+
+  getAfterDiscount() {
+    const beforeDiscount = this.getBeforeDiscount();
+    const discountAmount = this.getDiscountAmount();
+    let gift = 0;
+
+    if (this.getGift() !== INFO.NONE) {
+      gift = INFO.GIFT_PRICE;
+    }
+
+    const total = beforeDiscount + discountAmount + gift;
+    return total;
+  }
+
+  getEventBadge() {
+    const benefit = this.getDiscountAmount();
+
+    switch (true) {
+      case benefit <= -INFO.BADGE.SANTA.PRICE:
+        return INFO.BADGE.SANTA.NAME;
+      case benefit <= -INFO.BADGE.TREE.PRICE:
+        return INFO.BADGE.TREE.NAME;
+      case benefit <= -INFO.BADGE.STAR.PRICE:
+        return INFO.BADGE.STAR.NAME;
+      default:
+        return INFO.NONE;
+    }
+  }
+}
+export default Client;

리뷰: 좋은 의견 감사합니다. 말씀하신 거처럼 모델이 뷰의 세부사항을 과도하게 알고 있는 형태라면 표현방식이 혼합된 느낌이 들어 유연성이 떨어질 거 같네요 ㅜㅜ 사실 mvc 패턴에 집착하지 않고 클래스 자체가 가질 수 있는 역할을 생각하고, 관련된 기능을 구현하는 방식을 고려하다보니 패턴에서 오는 처리기준과는 어긋나는 상황이 발생하는 거 같네요 ...  베리에이션이 다양하게 존재하는 상황에서도 유연하게 사용할 수 있는 구조가 무엇인지 좀더 고민해봐야겠네요 조언 감사합니다 ☺️
@@ -0,0 +1,10 @@
+const parseInput = userInput =>
+  userInput
+    .trim()
+    .split(',')
+    .map(item => {
+      const [name, quantity] = item.trim().split('-');
+      return [name, Number(quantity)];
+    });
+
+export default parseInput;

리뷰: 핸들 인풋에서 받아서 바로 파싱하시는 것 같은데 유효성 검사에서 누락되지 않을까요?
@@ -0,0 +1,58 @@
+import { DATE, DISCOUNT, INFO, MENU, MENU_KIND } from '../Util/constants';
+
+class Discount {
+  calculateDiscount(date, menu) {
+    const discount = {};
+    const addDisCount = (key, value) => {
+      if (value !== 0) discount[key] = value;
+    };
+
+    addDisCount(DISCOUNT.CHRISTMAS, this.isPeriod(date));
+    addDisCount(DISCOUNT.WEEK, this.isWeekday(date, menu));
+    addDisCount(DISCOUNT.WEEKEND, this.isWeekend(date, menu));
+    addDisCount(DISCOUNT.SPECIAL, this.isSpecialDay(date));
+    return discount;
+  }
+
+  isPeriod(date) {
+    let periodDiscount = 0;
+    if (date >= DATE.EVENT_START && date <= DATE.EVENT_END) {
+      periodDiscount +=
+        -DATE.PERIOD_DISCOUNT + (date - 1) * -DATE.PER_DAY_DISCOUNT;
+    }
+    return periodDiscount;
+  }
+
+  isWeekday(date, menu) {
+    let weekDisCount = 0;
+    if (!(date % 7 === 1 || date % 7 === 2)) {
+      let count = 0;
+      Object.keys(menu).forEach(name => {
+        if (MENU[name].kind === MENU_KIND.DESSERT) count += menu[name];
+      });
+      weekDisCount += -INFO.WEEK_DISCOUNT * count;
+    }
+    return weekDisCount;
+  }
+
+  isWeekend(date, menu) {
+    let weekendDisCount = 0;
+    if (date % 7 === 1 || date % 7 === 2) {
+      let count = 0;
+      Object.keys(menu).forEach(name => {
+        if (MENU[name].kind === MENU_KIND.MAIN) count += menu[name];
+      });
+      weekendDisCount += -INFO.WEEK_DISCOUNT * count;
+    }
+    return weekendDisCount;
+  }
+
+  isSpecialDay(date) {
+    let specialDayDiscount = 0;
+    if (DATE.SPECIAL_DATE.includes(date))
+      specialDayDiscount += -DATE.SPECIAL_DISCOUNT;
+    return specialDayDiscount;
+  }
+}
+
+export default Discount;

리뷰: 날짜 객체를 사용하거나 관련 상수를 정의해두신 분들이 많이 보이더라구요 ! 저도 관련된 변수는 정의하는게 좋은 거 같다고 생각이 드네요 ㅎㅎ 추천 감사합니다 ~ ☺️
@@ -0,0 +1,58 @@
+import { DATE, DISCOUNT, INFO, MENU, MENU_KIND } from '../Util/constants';
+
+class Discount {
+  calculateDiscount(date, menu) {
+    const discount = {};
+    const addDisCount = (key, value) => {
+      if (value !== 0) discount[key] = value;
+    };
+
+    addDisCount(DISCOUNT.CHRISTMAS, this.isPeriod(date));
+    addDisCount(DISCOUNT.WEEK, this.isWeekday(date, menu));
+    addDisCount(DISCOUNT.WEEKEND, this.isWeekend(date, menu));
+    addDisCount(DISCOUNT.SPECIAL, this.isSpecialDay(date));
+    return discount;
+  }
+
+  isPeriod(date) {
+    let periodDiscount = 0;
+    if (date >= DATE.EVENT_START && date <= DATE.EVENT_END) {
+      periodDiscount +=
+        -DATE.PERIOD_DISCOUNT + (date - 1) * -DATE.PER_DAY_DISCOUNT;
+    }
+    return periodDiscount;
+  }
+
+  isWeekday(date, menu) {
+    let weekDisCount = 0;
+    if (!(date % 7 === 1 || date % 7 === 2)) {
+      let count = 0;
+      Object.keys(menu).forEach(name => {
+        if (MENU[name].kind === MENU_KIND.DESSERT) count += menu[name];
+      });
+      weekDisCount += -INFO.WEEK_DISCOUNT * count;
+    }
+    return weekDisCount;
+  }
+
+  isWeekend(date, menu) {
+    let weekendDisCount = 0;
+    if (date % 7 === 1 || date % 7 === 2) {
+      let count = 0;
+      Object.keys(menu).forEach(name => {
+        if (MENU[name].kind === MENU_KIND.MAIN) count += menu[name];
+      });
+      weekendDisCount += -INFO.WEEK_DISCOUNT * count;
+    }
+    return weekendDisCount;
+  }
+
+  isSpecialDay(date) {
+    let specialDayDiscount = 0;
+    if (DATE.SPECIAL_DATE.includes(date))
+      specialDayDiscount += -DATE.SPECIAL_DISCOUNT;
+    return specialDayDiscount;
+  }
+}
+
+export default Discount;

리뷰: 오 변수 선언을 간결하게 할 수 있어서 좋은 방법인거 같아요 감사합니다 ㅎㅎ!!
@@ -0,0 +1,26 @@
+package christmas.config;
+
+import java.util.Arrays;
+
+public enum EventBadge {
+    SANTA("산타", 20000),
+    TREE("트리", 10000),
+    STAR("별", 5000),
+    NONE("없음", 0);
+
+    private final String name;
+    private final int limitPrice;
+
+    EventBadge(String name, int limitPrice) {
+        this.name = name;
+        this.limitPrice = limitPrice;
+    }
+
+    public static String findBadgeByTotalDiscount(int totalDiscount) {
+        return Arrays.stream(values())
+                .filter(badge -> totalDiscount >= badge.limitPrice)
+                .findFirst()
+                .orElse(NONE)
+                .name;
+    }
+}

리뷰: 이 메서드는 EvenBadge의 멤버의 선언 순서에 의존적인 것 같아요. 더 좋게 개선할 방법은 없을까요?
@@ -0,0 +1,25 @@
+package christmas.config;
+
+public class Message {
+    public static final String INPUT_DATE = "12월 중 식당 예상 방문 날짜는 언제인가요? (숫자만 입력해 주세요!)";
+    public static final String INPUT_ORDER = "주문하실 메뉴를 메뉴와 개수를 알려 주세요. (e.g. 해산물파스타-2,레드와인-1,초코케이크-1)";
+
+    public static final String OUTPUT_VISIT_DATE = "12월 %d일에 우테코 식당에서 받을 이벤트 혜택 미리 보기!\n";
+    public static final String OUTPUT_MENU_TITLE = "\n<주문 메뉴>";
+    public static final String OUTPUT_ITEM = "%s %d개%n";
+    public static final String OUTPUT_WON = "%s원%n";
+    public static final String OUTPUT_MINUS_WON = "-" + OUTPUT_WON;
+    public static final String OUTPUT_TOTAL_PRICE = "%n<할인 전 총주문 금액>%n" + OUTPUT_WON;
+    public static final String OUTPUT_GIFT_TITLE = "\n<증정 메뉴>";
+    public static final String OUTPUT_PROMOTION_TITLE = "\n<혜택 내역>";
+    public static final String OUTPUT_PROMOTION_ITEM = "%s: " + OUTPUT_MINUS_WON;
+    public static final String OUTPUT_TOTAL_DISCOUNT = "%n<총혜택 금액>%n" + OUTPUT_WON;
+    public static final String OUTPUT_PAYMENT_PRICE = "%n<할인 후 예상 결제 금액>%n" + OUTPUT_WON;
+    public static final String OUTPUT_BADGE = "%n<12월 이벤트 배지>%n%s%n";
+    public static final String OUTPUT_NONE = "없음";
+
+
+    public static final String ERROR_MESSAGE_PREFIX = "[ERROR]";
+    public static final String ERROR_INPUT_DATE = ERROR_MESSAGE_PREFIX + " 유효하지 않은 날짜입니다. 다시 입력해 주세요.";
+    public static final String ERROR_INPUT_ORDER = ERROR_MESSAGE_PREFIX + " 유효하지 않은 주문입니다. 다시 입력해 주세요.";
+}

리뷰: 프로그램 전역에서 사용하지 않고 Output에서만 사용하는 변수는 따로 클래스로 만들지 않고 Output객체 안에 상수 필드로 선언해도 좋을 것 같아요:smile:
추가적으로 Message 안에 에러메시지와 Output에서 사용하는 상수가 혼합되어 있어서 분리하면 더 좋을 것 같은데 어떻게 생각하시나요?
@@ -0,0 +1,25 @@
+package christmas.controller;
+
+import christmas.model.Order;
+import christmas.model.VisitDate;
+import christmas.view.InputView;
+
+public class InputController {
+    public static VisitDate setVisitDate() {
+        try {
+            return new VisitDate(InputView.readDate());
+        } catch (IllegalArgumentException illegalArgumentException) {
+            System.out.println(illegalArgumentException.getMessage());
+            return setVisitDate();
+        }
+    }
+
+    public static Order setOrder() {
+        try {
+            return new Order(InputView.readOrder());
+        } catch (IllegalArgumentException illegalArgumentException) {
+            System.out.println(illegalArgumentException.getMessage());
+            return setOrder();
+        }
+    }
+}

리뷰: MVC 패턴에서 Controller끼리 서로 의존하는 것은 별로 좋지 않다고 알고 있는데 InputController를 따로 만들고 PromotionController에서 사용한 이유가 따로 있을까요?
@@ -0,0 +1,25 @@
+package christmas.controller;
+
+import christmas.model.Order;
+import christmas.model.VisitDate;
+import christmas.view.InputView;
+
+public class InputController {
+    public static VisitDate setVisitDate() {
+        try {
+            return new VisitDate(InputView.readDate());
+        } catch (IllegalArgumentException illegalArgumentException) {
+            System.out.println(illegalArgumentException.getMessage());
+            return setVisitDate();
+        }
+    }
+
+    public static Order setOrder() {
+        try {
+            return new Order(InputView.readOrder());
+        } catch (IllegalArgumentException illegalArgumentException) {
+            System.out.println(illegalArgumentException.getMessage());
+            return setOrder();
+        }
+    }
+}

리뷰: setOrder, setVisitDate 각각 VisitDate, Order 객체를 생성하고 있는 것 같아요.
set이라는 접두어보다 generate가 좀 더 어울린다고 생각하는데 어떠신가요?