ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,61 @@
+import { MENU_NAMES, MENUS } from '../../constant/menu.js';
+
+class OrderMenuError {
+  #orderNotInput(orderMenu) {
+    if (
+      orderMenu.length === 1 &&
+      orderMenu[0].menu.trim() === ''
+    ) {
+      throw new Error('[ERROR] 주문하실 메뉴와 개수를 입력해 주세요.');
+    }
+  }
+
+  #orderWrongInput(orderMenu) {
+    if (!orderMenu
+      .every((order) => Number.isNaN(order.cnt) === false &&
+                        Number.isInteger(order.cnt) &&
+                        order.cnt > 0)
+    ) {
+      throw new Error('[ERROR] 유효하지 않은 주문입니다. 다시 입력해 주세요.');
+    }
+  }
+
+  #orderNotExistMenu(orderMenu) {
+    if (!orderMenu.every((order) => MENU_NAMES.includes(order.menu))) {
+      throw new Error('[ERROR] 유효하지 않은 주문입니다. 다시 입력해 주세요.');
+    }
+  }
+
+  #orderDuplication(orderMenu) {
+    const originOrderMenu = orderMenu.map((order) => order.menu);
+    const setOrderMenu = new Set(originOrderMenu);
+    if (originOrderMenu.length !== setOrderMenu.size) {
+      throw new Error('[ERROR] 유효하지 않은 주문입니다. 다시 입력해 주세요.');
+    }
+  }
+
+  #orderTypeOnlyDrink(orderMenu) {
+    const DRINK = Object.keys(MENUS.Drink);
+    if (orderMenu.every((order) => DRINK.includes(order.menu))) {
+      throw new Error('[ERROR] 음료만 주문하실 수 없습니다. 다시 입력해 주세요.');
+    }
+  }
+
+  #totalOrderCntOverRange(orderMenu) {
+    const totalMenuCnt = orderMenu.reduce((acc, order) => acc + order.cnt, 0);
+    if (totalMenuCnt > 20) {
+      throw new Error('[ERROR] 메뉴는 최대 20개 까지 주문 가능합니다. 다시 입력해 주세요.');
+    }
+  }
+
+  valid(orderMenu) {
+    this.#orderNotInput(orderMenu);
+    this.#orderWrongInput(orderMenu);
+    this.#orderNotExistMenu(orderMenu);
+    this.#orderTypeOnlyDrink(orderMenu);
+    this.#orderDuplication(orderMenu);
+    this.#totalOrderCntOverRange(orderMenu);
+  }
+}
+
+export default OrderMenuError;

리뷰: 저도 실수로 놓친 부분이지만 1.0을 포함할 지 안할지 고민하셨다고 하셨는데 그 부분 이해합니다...
미션이 끝나고 생각난거지만 1.0도 포함을 한다면 1.000000000000000... 도 1로 되는거라 0을 무한대로 입력하면 문제가 발생하지 않을까 하는 우려가 되는데 어떻게 생각하시나요?
@@ -0,0 +1,28 @@
+const EVENT_NAMES = ['크리스마스 디데이 할인', '평일 할인', '주말 할인', '특별 할인', '증정 이벤트'];
+
+const WEEKDAY_DISCOUNT_DATE = [
+  3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 17,
+  18, 19, 20, 21, 24, 25, 26, 27, 28, 31,
+];
+
+const WEEKEND_DISCOUNT_DATE = [
+  1, 2, 8, 9, 15, 16, 22, 23, 29, 30,
+];
+
+const SPECIAL_DISCOUNT_DATE = [
+  3, 10, 17, 24, 25, 31,
+];
+
+Object.freeze(
+  EVENT_NAMES,
+  WEEKDAY_DISCOUNT_DATE,
+  WEEKEND_DISCOUNT_DATE,
+  SPECIAL_DISCOUNT_DATE,
+);
+
+export {
+  EVENT_NAMES,
+  WEEKDAY_DISCOUNT_DATE,
+  WEEKEND_DISCOUNT_DATE,
+  SPECIAL_DISCOUNT_DATE,
+};

리뷰: 해당 부분은 `new Date()` 메서드로 표현이 가능할 것 같습니다 !
`new Date()`를 사용하시면 0-일요일 ~ 6-토요일로 0~6까지의 number로 출력됩니다 !
@@ -0,0 +1,66 @@
+import OUTPUT from '../../utils/constant/output.js';
+import OutputView from '../../utils/view_type/OutputView.js';
+import { EVENT_NAMES } from '../../utils/constant/event.js';
+
+class PrintEventResult {
+  #totalOrderAmount(TOTAL_AMOUNT) {
+    OutputView.printOutput(`${OUTPUT.TOTAL_AMOUNT.BEFORE_DISCOUNT}`);
+    OutputView.printOutput(`${TOTAL_AMOUNT.toLocaleString()}원`);
+    OutputView.printOutput('');
+  }
+
+  #giveawayMenu(GIVEAWAY_MENU) {
+    OutputView.printOutput(`${OUTPUT.MENU.GIVEAWAY_MENU}`);
+    OutputView.printOutput(`${GIVEAWAY_MENU}`);
+    OutputView.printOutput('');
+  }
+
+  #eventDetails(EVENT_DETAILS_RESULT) {
+    if (EVENT_DETAILS_RESULT === '없음') {
+      OutputView.printOutput(`${OUTPUT.BENEFIT.LIST}`);
+      OutputView.printOutput(`${EVENT_DETAILS_RESULT}`);
+      OutputView.printOutput('');
+      return;
+    }
+    this.#validEventDetails(EVENT_DETAILS_RESULT);
+  }
+
+  #validEventDetails(EVENT_DETAILS_RESULT) {
+    OutputView.printOutput(OUTPUT.BENEFIT.LIST);
+    Object.keys(EVENT_DETAILS_RESULT).forEach((event, idx) => {
+      if (EVENT_DETAILS_RESULT[event] > 0) {
+        OutputView.printOutput(`${EVENT_NAMES[idx]}: -${EVENT_DETAILS_RESULT[event].toLocaleString()}원`);
+      }
+    });
+    OutputView.printOutput('');
+  }
+
+  #totalBenefitAmount(BENEFIT_AMOUNT_RESULT) {
+    OutputView.printOutput(`${OUTPUT.BENEFIT.TOTAL_BENEFIT_AMOUNT}`);
+    OutputView.printOutput(`-${BENEFIT_AMOUNT_RESULT.toLocaleString()}원`);
+    OutputView.printOutput('');
+  }
+
+  #expectPaymentAmount(EXPEXT_PAYMENT_AMOUNT) {
+    OutputView.printOutput(`${OUTPUT.TOTAL_AMOUNT.AFTER_DISCOUNT}`);
+    OutputView.printOutput(`${EXPEXT_PAYMENT_AMOUNT.toLocaleString()}원`);
+    OutputView.printOutput('');
+  }
+
+  #evnetBadge(BADGE) {
+    OutputView.printOutput(`${OUTPUT.EVENT.BADGE}`);
+    OutputView.printOutput(`${BADGE}`);
+    OutputView.printOutput('');
+  }
+
+  print(AMOUNT_RESULT, BENEFIT_RESULT) {
+    this.#totalOrderAmount(AMOUNT_RESULT.TOTAL_AMOUNT);
+    this.#giveawayMenu(BENEFIT_RESULT.GIVEAWAY_MENU);
+    this.#eventDetails(BENEFIT_RESULT.EVENT_DETAILS_RESULT);
+    this.#totalBenefitAmount(AMOUNT_RESULT.BENEFIT_AMOUNT_RESULT);
+    this.#expectPaymentAmount(AMOUNT_RESULT.EXPECT_PAYMENT_AMOUNT);
+    this.#evnetBadge(BENEFIT_RESULT.BADGE);
+  }
+}
+
+export default PrintEventResult;

리뷰: ```jsx
OutputView.printOutput('');
```
이 문구 개행때문에 입력하신 것 같은데 `원`도 변수화 하셔서 `원\n`을 하셔도 되고 
그 다음 문구에 `OutputView.printOutput(${OUTPUT.EVENT.BADGE});`가 나오니
`BADGE: '\n<12월 이벤트 배지>'` 이런식으로 추가하면 코드가 조금 더 깔끔해 질 것 같아요 !
@@ -0,0 +1,56 @@
+import { WEEKDAY_DISCOUNT_DATE, WEEKEND_DISCOUNT_DATE, SPECIAL_DISCOUNT_DATE } from '../../utils/constant/event.js';
+import { MENUS } from '../../utils/constant/menu.js';
+
+class CalculateEventBenefit {
+  #event = {
+    christmas: 0,
+    weekday: 0,
+    weekend: 0,
+    special: 0,
+    giveaway: 0,
+  };
+
+  #christmasEvent(visitDate) {
+    if (visitDate > 0 && visitDate < 26) {
+      this.#event.christmas = 1000 + (visitDate - 1) * 100;
+    }
+  }
+
+  #specialEvent(visitDate) {
+    if (SPECIAL_DISCOUNT_DATE.includes(visitDate)) {
+      this.#event.special = 1000;
+    }
+  }
+
+  #weekdayEvent(order, DESSERT, visitDate) {
+    if (
+      WEEKDAY_DISCOUNT_DATE.includes(visitDate) &&
+      DESSERT.includes(order.menu)
+    ) {
+      this.#event.weekday += 2023 * order.cnt;
+    }
+  }
+
+  #weekendEvent(order, MAIN, visitDate) {
+    if (
+      WEEKEND_DISCOUNT_DATE.includes(visitDate) &&
+      MAIN.includes(order.menu)
+    ) {
+      this.#event.weekend += 2023 * order.cnt;
+    }
+  }
+
+  result(visitDate, orderMenu) {
+    const DESSERT = Object.keys(MENUS.Dessert);
+    const MAIN = Object.keys(MENUS.Main);
+    this.#christmasEvent(visitDate);
+    this.#specialEvent(visitDate);
+    orderMenu.forEach((order) => {
+      this.#weekdayEvent(order, DESSERT, visitDate);
+      this.#weekendEvent(order, MAIN, visitDate);
+    });
+    return this.#event;
+  }
+}
+
+export default CalculateEventBenefit;

리뷰: ```jsx
#event = {
    christmas: 0,
    weekday: 0,
    weekend: 0,
    special: 0,
    giveaway: 0,
  };
``` 
이렇게 작성하신 것 처럼 해당 상수 값도 고정값이니 변수화해서 의미를 부여하는 것도 괜찮을 것 같습니다 !
@@ -0,0 +1,41 @@
+import VisitDateError from '../../../utils/error/type/visit_date_error.js';
+import printError from '../../../utils/error/print_error.js';
+import INPUT_QUESTION from '../../../utils/constant/input_question.js';
+import OUTPUT from '../../../utils/constant/output.js';
+import InputView from '../../../utils/view_type/InputView.js';
+import OutputView from '../../../utils/view_type/OutputView.js';
+
+class VisitDateManage {
+  #visitDate = null;
+
+  get getVisitDate() {
+    return this.#visitDate;
+  }
+
+  async firstGreeting() {
+    OutputView.printOutput(OUTPUT.FIRST_GREETING);
+    await this.#inputVisitDate();
+  }
+
+  async #inputVisitDate() {
+    while (true) {
+      this.#visitDate = await InputView.userInput(INPUT_QUESTION.VISIT_DATE);
+      if (this.#isValidDate()) {
+        break;
+      }
+    }
+  }
+
+  #isValidDate() {
+    const ERROR = new VisitDateError();
+    try {
+      ERROR.valid(this.#visitDate);
+      return true;
+    } catch (error) {
+      printError(error);
+      return false;
+    }
+  }
+}
+
+export default VisitDateManage;

리뷰: 요구사항에 있는 만약 형식에 맞지 않으면 새로운 값을 받아오는 과정이 없는것 같습니다. 
(혹시 제가 못 찾는 것이라면 제시해주시기 바랍니다)
해당 구문에서는 return false를 하고 그냥 break써서 빠져 나온것 같은데 return false 대신 return this.#inputVisitDate() 하면 다시 값을 받아 올수 있을 것 같습니다.
@@ -0,0 +1,48 @@
+import CalculateOrderMenu from '../../model/calculate_order_menu.js';
+import PrintOrderMenu from '../../view/print_order_menu.js';
+import OrderMenuError from '../../../utils/error/type/order_menu_error.js';
+import printError from '../../../utils/error/print_error.js';
+import INPUT_QUESTION from '../../../utils/constant/input_question.js';
+import InputView from '../../../utils/view_type/InputView.js';
+
+class OrderMenuManage {
+  #orderMenu = null;
+
+  get getOrderMenu() {
+    return this.#orderMenu;
+  }
+
+  async inputOrderMenu(visitDate) {
+    while (true) {
+      this.#orderMenu = await InputView.userInput(INPUT_QUESTION.ORDER_MENU);
+      this.#orderMenu = this.#calculateOrderMenu();
+      if (this.#isValidOrderMenu()) {
+        break;
+      }
+    }
+    this.#print(visitDate);
+  }
+
+  #calculateOrderMenu() {
+    const calculateOrderMenu = new CalculateOrderMenu();
+    return calculateOrderMenu.calculate(this.#orderMenu);
+  }
+
+  #isValidOrderMenu() {
+    const ERROR = new OrderMenuError();
+    try {
+      ERROR.valid(this.#orderMenu);
+      return true;
+    } catch (error) {
+      printError(error);
+      return false;
+    }
+  }
+
+  #print(visitDate) {
+    const printOrderMenu = new PrintOrderMenu();
+    printOrderMenu.print(visitDate, this.#orderMenu);
+  }
+}
+
+export default OrderMenuManage;

리뷰: 이 부분도 날짜 받아올때와 마찬가지로 잘못된 경우 새 값을 가져오는 과정이 없어보입니다
@@ -0,0 +1,67 @@
+import CalculateTotalOrderAmount from '../../model/calculate_total_order_amount.js';
+import CalculateEventDetails from '../../model/calculate_event_details.js';
+import PrintEventResult from '../../view/print_event_result.js';
+
+class EventManage {
+  #visitDate;
+  #orderMenu;
+
+  constructor(visitDate, orderMenu) {
+    this.#visitDate = Number(visitDate);
+    this.#orderMenu = orderMenu;
+  }
+
+  totalOrderAmount() {
+    const TOTAL_AMOUNT = this.#calculateTotalOrderAmount();
+    if (TOTAL_AMOUNT < 10000) {
+      return this.#printEventResult(TOTAL_AMOUNT, 0, null);
+    }
+    return this.#eventBenefit(TOTAL_AMOUNT);
+  }
+
+  #eventBenefit(TOTAL_AMOUNT) {
+    const EVENT_DETAILS = this.#calculateEventDetails();
+    EVENT_DETAILS.giveaway = TOTAL_AMOUNT >= 120000 ? 25000 : 0;
+    const BENEFIT_AMOUNT = Object.values(EVENT_DETAILS).reduce((acc, cur) => acc + cur, 0);
+    this.#printEventResult(TOTAL_AMOUNT, BENEFIT_AMOUNT, EVENT_DETAILS);
+  }
+
+  #calculateTotalOrderAmount() {
+    const calculateTotalOrderAmount = new CalculateTotalOrderAmount();
+    return calculateTotalOrderAmount.result(this.#orderMenu);
+  }
+
+  #calculateEventDetails() {
+    const calculateEventDetails = new CalculateEventDetails();
+    return calculateEventDetails.result(this.#visitDate, this.#orderMenu);
+  }
+
+  #eventBadgeResult(BENEFIT_AMOUNT) {
+    if (BENEFIT_AMOUNT < 5000) {
+      return '없음';
+    }
+    if (BENEFIT_AMOUNT < 10000) {
+      return '별';
+    }
+    if (BENEFIT_AMOUNT < 20000) {
+      return '트리';
+    }
+    return '산타';
+  }
+
+  #printEventResult(TOTAL_AMOUNT, BENEFIT_AMOUNT, EVENT_DETAILS) {
+    const GIVEAWAY_MENU = TOTAL_AMOUNT >= 120000 ? '샴페인 1개' : '없음';
+    const EVENT_DETAILS_RESULT = EVENT_DETAILS === null ? '없음' : EVENT_DETAILS;
+    const BENEFIT_AMOUNT_RESULT = BENEFIT_AMOUNT === 0 ? '없음' : BENEFIT_AMOUNT;
+    const DISCOUNT_AMOUNT = TOTAL_AMOUNT >= 120000 ? BENEFIT_AMOUNT - 25000 : BENEFIT_AMOUNT;
+    const EXPECT_PAYMENT_AMOUNT = TOTAL_AMOUNT - DISCOUNT_AMOUNT;
+    const BADGE = this.#eventBadgeResult(BENEFIT_AMOUNT);
+
+    const AMOUNT_RESULT = { TOTAL_AMOUNT, BENEFIT_AMOUNT_RESULT, EXPECT_PAYMENT_AMOUNT };
+    const BENEFIT_RESULT = { GIVEAWAY_MENU, EVENT_DETAILS_RESULT, BADGE };
+    const printEventResult = new PrintEventResult();
+    printEventResult.print(AMOUNT_RESULT, BENEFIT_RESULT);
+  }
+}
+
+export default EventManage;

리뷰: 이 부분에서 10000이 어떤 의미를 갖고 있는지 판단하기 힘듭니다. 이 후로도 숫자가 하드코딩 된 경우가 있는데 의미를 가질 수 있도록 상수쪽으로 빼주는 것은 어떨까요?
@@ -0,0 +1,67 @@
+import CalculateTotalOrderAmount from '../../model/calculate_total_order_amount.js';
+import CalculateEventDetails from '../../model/calculate_event_details.js';
+import PrintEventResult from '../../view/print_event_result.js';
+
+class EventManage {
+  #visitDate;
+  #orderMenu;
+
+  constructor(visitDate, orderMenu) {
+    this.#visitDate = Number(visitDate);
+    this.#orderMenu = orderMenu;
+  }
+
+  totalOrderAmount() {
+    const TOTAL_AMOUNT = this.#calculateTotalOrderAmount();
+    if (TOTAL_AMOUNT < 10000) {
+      return this.#printEventResult(TOTAL_AMOUNT, 0, null);
+    }
+    return this.#eventBenefit(TOTAL_AMOUNT);
+  }
+
+  #eventBenefit(TOTAL_AMOUNT) {
+    const EVENT_DETAILS = this.#calculateEventDetails();
+    EVENT_DETAILS.giveaway = TOTAL_AMOUNT >= 120000 ? 25000 : 0;
+    const BENEFIT_AMOUNT = Object.values(EVENT_DETAILS).reduce((acc, cur) => acc + cur, 0);
+    this.#printEventResult(TOTAL_AMOUNT, BENEFIT_AMOUNT, EVENT_DETAILS);
+  }
+
+  #calculateTotalOrderAmount() {
+    const calculateTotalOrderAmount = new CalculateTotalOrderAmount();
+    return calculateTotalOrderAmount.result(this.#orderMenu);
+  }
+
+  #calculateEventDetails() {
+    const calculateEventDetails = new CalculateEventDetails();
+    return calculateEventDetails.result(this.#visitDate, this.#orderMenu);
+  }
+
+  #eventBadgeResult(BENEFIT_AMOUNT) {
+    if (BENEFIT_AMOUNT < 5000) {
+      return '없음';
+    }
+    if (BENEFIT_AMOUNT < 10000) {
+      return '별';
+    }
+    if (BENEFIT_AMOUNT < 20000) {
+      return '트리';
+    }
+    return '산타';
+  }
+
+  #printEventResult(TOTAL_AMOUNT, BENEFIT_AMOUNT, EVENT_DETAILS) {
+    const GIVEAWAY_MENU = TOTAL_AMOUNT >= 120000 ? '샴페인 1개' : '없음';
+    const EVENT_DETAILS_RESULT = EVENT_DETAILS === null ? '없음' : EVENT_DETAILS;
+    const BENEFIT_AMOUNT_RESULT = BENEFIT_AMOUNT === 0 ? '없음' : BENEFIT_AMOUNT;
+    const DISCOUNT_AMOUNT = TOTAL_AMOUNT >= 120000 ? BENEFIT_AMOUNT - 25000 : BENEFIT_AMOUNT;
+    const EXPECT_PAYMENT_AMOUNT = TOTAL_AMOUNT - DISCOUNT_AMOUNT;
+    const BADGE = this.#eventBadgeResult(BENEFIT_AMOUNT);
+
+    const AMOUNT_RESULT = { TOTAL_AMOUNT, BENEFIT_AMOUNT_RESULT, EXPECT_PAYMENT_AMOUNT };
+    const BENEFIT_RESULT = { GIVEAWAY_MENU, EVENT_DETAILS_RESULT, BADGE };
+    const printEventResult = new PrintEventResult();
+    printEventResult.print(AMOUNT_RESULT, BENEFIT_RESULT);
+  }
+}
+
+export default EventManage;

리뷰: else를 지양하라고 한 것의 의미가 거의 비슷한 의미를 갖고 있는 삼항 연산자를 쓰라고 한 것은 아니라고 생각하는데, 삼항 연산자를 쓴 이유가 따로 있을까요?
@@ -0,0 +1,67 @@
+import CalculateTotalOrderAmount from '../../model/calculate_total_order_amount.js';
+import CalculateEventDetails from '../../model/calculate_event_details.js';
+import PrintEventResult from '../../view/print_event_result.js';
+
+class EventManage {
+  #visitDate;
+  #orderMenu;
+
+  constructor(visitDate, orderMenu) {
+    this.#visitDate = Number(visitDate);
+    this.#orderMenu = orderMenu;
+  }
+
+  totalOrderAmount() {
+    const TOTAL_AMOUNT = this.#calculateTotalOrderAmount();
+    if (TOTAL_AMOUNT < 10000) {
+      return this.#printEventResult(TOTAL_AMOUNT, 0, null);
+    }
+    return this.#eventBenefit(TOTAL_AMOUNT);
+  }
+
+  #eventBenefit(TOTAL_AMOUNT) {
+    const EVENT_DETAILS = this.#calculateEventDetails();
+    EVENT_DETAILS.giveaway = TOTAL_AMOUNT >= 120000 ? 25000 : 0;
+    const BENEFIT_AMOUNT = Object.values(EVENT_DETAILS).reduce((acc, cur) => acc + cur, 0);
+    this.#printEventResult(TOTAL_AMOUNT, BENEFIT_AMOUNT, EVENT_DETAILS);
+  }
+
+  #calculateTotalOrderAmount() {
+    const calculateTotalOrderAmount = new CalculateTotalOrderAmount();
+    return calculateTotalOrderAmount.result(this.#orderMenu);
+  }
+
+  #calculateEventDetails() {
+    const calculateEventDetails = new CalculateEventDetails();
+    return calculateEventDetails.result(this.#visitDate, this.#orderMenu);
+  }
+
+  #eventBadgeResult(BENEFIT_AMOUNT) {
+    if (BENEFIT_AMOUNT < 5000) {
+      return '없음';
+    }
+    if (BENEFIT_AMOUNT < 10000) {
+      return '별';
+    }
+    if (BENEFIT_AMOUNT < 20000) {
+      return '트리';
+    }
+    return '산타';
+  }
+
+  #printEventResult(TOTAL_AMOUNT, BENEFIT_AMOUNT, EVENT_DETAILS) {
+    const GIVEAWAY_MENU = TOTAL_AMOUNT >= 120000 ? '샴페인 1개' : '없음';
+    const EVENT_DETAILS_RESULT = EVENT_DETAILS === null ? '없음' : EVENT_DETAILS;
+    const BENEFIT_AMOUNT_RESULT = BENEFIT_AMOUNT === 0 ? '없음' : BENEFIT_AMOUNT;
+    const DISCOUNT_AMOUNT = TOTAL_AMOUNT >= 120000 ? BENEFIT_AMOUNT - 25000 : BENEFIT_AMOUNT;
+    const EXPECT_PAYMENT_AMOUNT = TOTAL_AMOUNT - DISCOUNT_AMOUNT;
+    const BADGE = this.#eventBadgeResult(BENEFIT_AMOUNT);
+
+    const AMOUNT_RESULT = { TOTAL_AMOUNT, BENEFIT_AMOUNT_RESULT, EXPECT_PAYMENT_AMOUNT };
+    const BENEFIT_RESULT = { GIVEAWAY_MENU, EVENT_DETAILS_RESULT, BADGE };
+    const printEventResult = new PrintEventResult();
+    printEventResult.print(AMOUNT_RESULT, BENEFIT_RESULT);
+  }
+}
+
+export default EventManage;

리뷰: 입력 2개와 이벤트로 클래스를 나누셨던데 배지는 나머지 쪽(할인이나 혜택 등 계산파트)와 기능이 달라보이는데 다른 클래스로 빼는것은 어떨까요
@@ -0,0 +1,41 @@
+import VisitDateError from '../../../utils/error/type/visit_date_error.js';
+import printError from '../../../utils/error/print_error.js';
+import INPUT_QUESTION from '../../../utils/constant/input_question.js';
+import OUTPUT from '../../../utils/constant/output.js';
+import InputView from '../../../utils/view_type/InputView.js';
+import OutputView from '../../../utils/view_type/OutputView.js';
+
+class VisitDateManage {
+  #visitDate = null;
+
+  get getVisitDate() {
+    return this.#visitDate;
+  }
+
+  async firstGreeting() {
+    OutputView.printOutput(OUTPUT.FIRST_GREETING);
+    await this.#inputVisitDate();
+  }
+
+  async #inputVisitDate() {
+    while (true) {
+      this.#visitDate = await InputView.userInput(INPUT_QUESTION.VISIT_DATE);
+      if (this.#isValidDate()) {
+        break;
+      }
+    }
+  }
+
+  #isValidDate() {
+    const ERROR = new VisitDateError();
+    try {
+      ERROR.valid(this.#visitDate);
+      return true;
+    } catch (error) {
+      printError(error);
+      return false;
+    }
+  }
+}
+
+export default VisitDateManage;

리뷰: 3주차 공통 피드백에 "객체로부터 데이터를 꺼내는 것(get)이 아니라, 메시지를 던지도록 구조를 바꿔 데이터를 가지는 개체가 일하도록 해야한다"고 했는데, getter를 사용하지 않고 해결할 방법을 고민해보는 것도 좋을 것 같습니다.
@@ -0,0 +1,41 @@
+import VisitDateError from '../../../utils/error/type/visit_date_error.js';
+import printError from '../../../utils/error/print_error.js';
+import INPUT_QUESTION from '../../../utils/constant/input_question.js';
+import OUTPUT from '../../../utils/constant/output.js';
+import InputView from '../../../utils/view_type/InputView.js';
+import OutputView from '../../../utils/view_type/OutputView.js';
+
+class VisitDateManage {
+  #visitDate = null;
+
+  get getVisitDate() {
+    return this.#visitDate;
+  }
+
+  async firstGreeting() {
+    OutputView.printOutput(OUTPUT.FIRST_GREETING);
+    await this.#inputVisitDate();
+  }
+
+  async #inputVisitDate() {
+    while (true) {
+      this.#visitDate = await InputView.userInput(INPUT_QUESTION.VISIT_DATE);
+      if (this.#isValidDate()) {
+        break;
+      }
+    }
+  }
+
+  #isValidDate() {
+    const ERROR = new VisitDateError();
+    try {
+      ERROR.valid(this.#visitDate);
+      return true;
+    } catch (error) {
+      printError(error);
+      return false;
+    }
+  }
+}
+
+export default VisitDateManage;

리뷰: 변수명을 `ERROR`로 하신 이유가 궁금합니다.
@@ -0,0 +1,41 @@
+import VisitDateError from '../../../utils/error/type/visit_date_error.js';
+import printError from '../../../utils/error/print_error.js';
+import INPUT_QUESTION from '../../../utils/constant/input_question.js';
+import OUTPUT from '../../../utils/constant/output.js';
+import InputView from '../../../utils/view_type/InputView.js';
+import OutputView from '../../../utils/view_type/OutputView.js';
+
+class VisitDateManage {
+  #visitDate = null;
+
+  get getVisitDate() {
+    return this.#visitDate;
+  }
+
+  async firstGreeting() {
+    OutputView.printOutput(OUTPUT.FIRST_GREETING);
+    await this.#inputVisitDate();
+  }
+
+  async #inputVisitDate() {
+    while (true) {
+      this.#visitDate = await InputView.userInput(INPUT_QUESTION.VISIT_DATE);
+      if (this.#isValidDate()) {
+        break;
+      }
+    }
+  }
+
+  #isValidDate() {
+    const ERROR = new VisitDateError();
+    try {
+      ERROR.valid(this.#visitDate);
+      return true;
+    } catch (error) {
+      printError(error);
+      return false;
+    }
+  }
+}
+
+export default VisitDateManage;

리뷰: (지나가다) 예외가 발생하여 `return false`를 하면 `#inputVisitDate()` 메서드에서 조건식에 걸리지 않을 것이고, 무한 루프에 의해 재입력을 받게될 것 같네요!

무한 루프가 아닌 다른 방식의 해결방법도 고민해보면 좋을 것 같습니다.
@@ -0,0 +1,29 @@
+import VisitDateManage from '../child/visit_date_manage.js';
+import OrderMenuManage from '../child/order_menu_manage.js';
+import EventManage from '../child/event_manage.js';
+
+class PlannerManage {
+  #VISIT_DATE = null;
+  #ORDER_MENU = null;
+
+  async visitDate() {
+    const visitDateManage = new VisitDateManage();
+    await visitDateManage.firstGreeting();
+    this.#VISIT_DATE = visitDateManage.getVisitDate;
+    await this.#orderMenu();
+  }
+
+  async #orderMenu() {
+    const orderMenuManage = new OrderMenuManage();
+    await orderMenuManage.inputOrderMenu(this.#VISIT_DATE);
+    this.#ORDER_MENU = orderMenuManage.getOrderMenu;
+    await this.#eventDetails();
+  }
+
+  async #eventDetails() {
+    const eventManage = new EventManage(this.#VISIT_DATE, this.#ORDER_MENU);
+    eventManage.totalOrderAmount();
+  }
+}
+
+export default PlannerManage;

리뷰: 3주차 공통 피드백에 필드의 수를 최소화한다는 코멘트가 있는데, 필드를 사용하지 않고 인자로 전해주어 사용했다면 어땠을까요?
@@ -0,0 +1,29 @@
+import VisitDateManage from '../child/visit_date_manage.js';
+import OrderMenuManage from '../child/order_menu_manage.js';
+import EventManage from '../child/event_manage.js';
+
+class PlannerManage {
+  #VISIT_DATE = null;
+  #ORDER_MENU = null;
+
+  async visitDate() {
+    const visitDateManage = new VisitDateManage();
+    await visitDateManage.firstGreeting();
+    this.#VISIT_DATE = visitDateManage.getVisitDate;
+    await this.#orderMenu();
+  }
+
+  async #orderMenu() {
+    const orderMenuManage = new OrderMenuManage();
+    await orderMenuManage.inputOrderMenu(this.#VISIT_DATE);
+    this.#ORDER_MENU = orderMenuManage.getOrderMenu;
+    await this.#eventDetails();
+  }
+
+  async #eventDetails() {
+    const eventManage = new EventManage(this.#VISIT_DATE, this.#ORDER_MENU);
+    eventManage.totalOrderAmount();
+  }
+}
+
+export default PlannerManage;

리뷰: 도메인 로직과 UI 로직이 분리되어 있지않은 채 로직이 전개되다 보니 큰 흐름을 파악하기 어려워보입니다. 
PlannerManage에서는 페이즈를 나누고 네이밍을 통해 큰 프로세스를 표현하고, 내부 동작은 도메인, UI 로직을 분리하여 담당하도록 하면 더욱 의미가 드러나고 프로세스를 이해하기 수월할 것 같습니다.