ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,30 @@
+package christmas.domain.event.present;
+
+import christmas.domain.order.Menu;
+import christmas.domain.order.OrderSheet;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public enum PresentEventManager {
+    CHAMPAGNE {
+        @Override
+        protected PresentEvent create(OrderSheet orderSheet) {
+            return new ChampagnePresentEvent(orderSheet);
+        }
+    };
+
+    abstract protected PresentEvent create(OrderSheet orderSheet);
+
+    public static PresentResult getPresentResult(OrderSheet orderSheet) {
+        Map<Menu, Integer> presents = new HashMap<>();
+        for (PresentEventManager value : values()) {
+            PresentEvent event = value.create(orderSheet);
+            if (event.isPresentable()) {
+                int presentCount = presents.getOrDefault(event.getPresent(), 1);
+                presents.put(event.getPresent(), presentCount);
+            }
+        }
+        return new PresentResult(presents);
+    }
+}

리뷰: 맞습니다. Present와 Discount 서로 이벤트 적용의 성질이 달라 따로 구분하기로 결정했습니다.
그리고 여러 유형의 선물이 있을 것이라 생각해 Present의 확장성을 고려한 것도 맞습니다.
제가 오랜 시간 고려했던 부분을 캐치해주셨습니다.
@@ -0,0 +1,73 @@
+package christmas.domain.order;
+
+import christmas.constants.ErrorMessage;
+import christmas.domain.Price;
+import christmas.dto.MenuDTO;
+
+import java.util.*;
+
+public class OrderSheet {
+    private static final int TOTAL_COUNT_LIMIT = 20;
+
+    private final Map<Menu, Integer> orderSheet;
+    private int totalPrice;
+
+    public OrderSheet(Map<String, Integer> menus) {
+        orderSheet = new HashMap<>();
+        validateExceedMenuCount(menus);
+        countMenu(menus);
+        calculateTotalPrice();
+        validateOnlyDrink();
+    }
+
+    public int getMenuCountByMenuType(MenuType menuType) {
+        int menuCount = orderSheet.keySet().stream()
+                .filter(menu -> menu.compareType(menuType))
+                .map(menu -> orderSheet.get(menu))
+                .reduce(0, Integer::sum);
+        return menuCount;
+    }
+
+    public int calculateTotalPrice() {
+        this.totalPrice = orderSheet.keySet().stream()
+                .map(menu -> menu.getPrice() * orderSheet.get(menu))
+                .reduce(0, Integer::sum);
+        return totalPrice;
+    }
+
+    private void countMenu(Map<String, Integer> menus) {
+        for (String menuName : menus.keySet()) {
+            Menu menu = Menu.getMenuByName(menuName);
+            orderSheet.put(menu, menus.get(menuName));
+        }
+    }
+
+    public Price getTotalPrice() {
+        return new Price(totalPrice);
+    }
+
+    public List<MenuDTO> getOrderSheet() {
+        return orderSheet.keySet().stream()
+                .map(menu -> new MenuDTO(menu.getName(), orderSheet.get(menu)))
+                .toList();
+    }
+
+    public boolean isMoreThanTotal(int amount) {
+        return totalPrice >= amount;
+    }
+
+    private void validateExceedMenuCount(Map<String, Integer> menus) {
+        int totalMenuCount = menus.values().stream()
+                .reduce(0, Integer::sum);
+        if (totalMenuCount <= TOTAL_COUNT_LIMIT) return;
+        throw new IllegalArgumentException(ErrorMessage.EXCEED_MENU_COUNT.getErrorMessage());
+    }
+
+    private void validateOnlyDrink() {
+        int drinkCount = (int) orderSheet.keySet().stream()
+                .filter(menu -> menu.compareType(MenuType.DRINK))
+                .count();
+        if (drinkCount == orderSheet.size())
+            throw new IllegalArgumentException(ErrorMessage.ONLY_DRINK.getErrorMessage());
+    }
+}

리뷰: 다른 분들이 볼 때는 이 메서드 이름이 "Menu의 개수를 반환하는 함수"라고 보일 수 있겠네요.
저는 "메뉴 이름으로 메뉴 객체를 찾고 그 객체를 카운트하는 함수"라고 생각했었습니다.
만약 바꾼다면 `countMenu`에서 `convertToMenu`라고 바꿀 수 있겠네요
@@ -0,0 +1,73 @@
+package christmas.domain.order;
+
+import christmas.constants.ErrorMessage;
+import christmas.domain.Price;
+import christmas.dto.MenuDTO;
+
+import java.util.*;
+
+public class OrderSheet {
+    private static final int TOTAL_COUNT_LIMIT = 20;
+
+    private final Map<Menu, Integer> orderSheet;
+    private int totalPrice;
+
+    public OrderSheet(Map<String, Integer> menus) {
+        orderSheet = new HashMap<>();
+        validateExceedMenuCount(menus);
+        countMenu(menus);
+        calculateTotalPrice();
+        validateOnlyDrink();
+    }
+
+    public int getMenuCountByMenuType(MenuType menuType) {
+        int menuCount = orderSheet.keySet().stream()
+                .filter(menu -> menu.compareType(menuType))
+                .map(menu -> orderSheet.get(menu))
+                .reduce(0, Integer::sum);
+        return menuCount;
+    }
+
+    public int calculateTotalPrice() {
+        this.totalPrice = orderSheet.keySet().stream()
+                .map(menu -> menu.getPrice() * orderSheet.get(menu))
+                .reduce(0, Integer::sum);
+        return totalPrice;
+    }
+
+    private void countMenu(Map<String, Integer> menus) {
+        for (String menuName : menus.keySet()) {
+            Menu menu = Menu.getMenuByName(menuName);
+            orderSheet.put(menu, menus.get(menuName));
+        }
+    }
+
+    public Price getTotalPrice() {
+        return new Price(totalPrice);
+    }
+
+    public List<MenuDTO> getOrderSheet() {
+        return orderSheet.keySet().stream()
+                .map(menu -> new MenuDTO(menu.getName(), orderSheet.get(menu)))
+                .toList();
+    }
+
+    public boolean isMoreThanTotal(int amount) {
+        return totalPrice >= amount;
+    }
+
+    private void validateExceedMenuCount(Map<String, Integer> menus) {
+        int totalMenuCount = menus.values().stream()
+                .reduce(0, Integer::sum);
+        if (totalMenuCount <= TOTAL_COUNT_LIMIT) return;
+        throw new IllegalArgumentException(ErrorMessage.EXCEED_MENU_COUNT.getErrorMessage());
+    }
+
+    private void validateOnlyDrink() {
+        int drinkCount = (int) orderSheet.keySet().stream()
+                .filter(menu -> menu.compareType(MenuType.DRINK))
+                .count();
+        if (drinkCount == orderSheet.size())
+            throw new IllegalArgumentException(ErrorMessage.ONLY_DRINK.getErrorMessage());
+    }
+}

리뷰: Stream에 allMatch()라는 좋은 기능이 있는 줄 몰랐습니다! 확실히 우리가 원하는 요구사항(모든 메뉴가 음료수인지 확인)을 구현해줄 수 있는 자바 API인 것 같습니다. 
4주차 미션을 리팩토링할 때 적용하겠습니다.
@@ -0,0 +1,49 @@
+package christmas.domain.event.discount;
+
+import christmas.constants.AmountConstants;
+import christmas.domain.day.Day;
+import christmas.domain.order.OrderSheet;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public enum DiscountEventManager {
+    CHRISTMAS {
+        @Override
+        protected DiscountEvent create(Day day, OrderSheet orderSheet) {
+            return new ChristmasDiscountEvent(day);
+        }
+    },
+    HOLIDAY {
+        @Override
+        protected DiscountEvent create(Day day, OrderSheet orderSheet) {
+            return new HolidayDiscountEvent(day, orderSheet);
+        }
+    },
+    WEEKDAY {
+        @Override
+        protected DiscountEvent create(Day day, OrderSheet orderSheet) {
+            return new WeekDayDiscountEvent(day, orderSheet);
+        }
+    },
+    STAR {
+        @Override
+        protected DiscountEvent create(Day day, OrderSheet orderSheet) {
+            return new StarDiscountEvent(day);
+        }
+    };
+
+    abstract protected DiscountEvent create(Day day, OrderSheet orderSheet);
+
+    public static DiscountResult getDiscountResult(Day day, OrderSheet orderSheet) {
+        if (orderSheet.isMoreThanTotal(AmountConstants.DISCOUNT_THRESHOLD.getAmount())) {
+            List<DiscountEvent> discountEvents = Arrays.stream(values())
+                    .map(event -> event.create(day, orderSheet))
+                    .filter(DiscountEvent::isDiscountable)
+                    .toList();
+            return new DiscountResult(discountEvents);
+        }
+        return new DiscountResult(new ArrayList<>());
+    }
+}

리뷰: Enum 추상 메서드를 사용한 생각의 과정 잘 들었습니다!

Enum이 상수 타입이니 각 요소들은 싱글턴으로 생성이 되겠군요.

말씀하신대로 따로 팩터리 클래스를 생성할 필요 없이 Enum 클래스 안에서 쉽게 생성할 수 있겠네요.  👍
@@ -0,0 +1,17 @@
+export const RESULT_ITEM_TITLES = Object.freeze({
+  ORDERED_MENUS: '<주문 메뉴>',
+  TOTAL_ORDER_AMOUNT: '<할인 전 총주문 금액>',
+  GIFT_MENU: '<증정 메뉴>',
+  BENEFIT_DETAILS: '<혜택 내역>',
+  TOTAL_BENEFIT_AMOUNT: '<총혜택 금액>',
+  PAYMENT_AMOUNT: '<할인 후 예상 결제 금액>',
+  EVENT_BADGE: '<12월 이벤트 배지>',
+});
+
+export const RESULT_BENEFIT_TITLES = Object.freeze({
+  CDD: '크리스마스 디데이 할인',
+  WDD: '평일 할인',
+  WED: '주말 할인',
+  SD: '특별 할인',
+  G: '증정 이벤트',
+});

리뷰: 여기 조금은 풀어 쓰는게 좋을 것 같아요! 저도 긴거 싫어해서 가능한 한 간단하게 하려고는 하는데
너무 줄이면 이해를 못하겠더라구요...
아래에서 이게 뭐지? 하고 올라왔어요ㅎ...
@@ -0,0 +1,17 @@
+export const RESULT_ITEM_TITLES = Object.freeze({
+  ORDERED_MENUS: '<주문 메뉴>',
+  TOTAL_ORDER_AMOUNT: '<할인 전 총주문 금액>',
+  GIFT_MENU: '<증정 메뉴>',
+  BENEFIT_DETAILS: '<혜택 내역>',
+  TOTAL_BENEFIT_AMOUNT: '<총혜택 금액>',
+  PAYMENT_AMOUNT: '<할인 후 예상 결제 금액>',
+  EVENT_BADGE: '<12월 이벤트 배지>',
+});
+
+export const RESULT_BENEFIT_TITLES = Object.freeze({
+  CDD: '크리스마스 디데이 할인',
+  WDD: '평일 할인',
+  WED: '주말 할인',
+  SD: '특별 할인',
+  G: '증정 이벤트',
+});

리뷰: 사실 원래는 길게 썼었는데 메서드 내에서 15줄이상 쓰면 안된다는 제약때문에 어쩔 수 없이 변수명을 줄이다보니 이렇게 됐네요 ㅠㅠ
@@ -0,0 +1,17 @@
+export const RESULT_ITEM_TITLES = Object.freeze({
+  ORDERED_MENUS: '<주문 메뉴>',
+  TOTAL_ORDER_AMOUNT: '<할인 전 총주문 금액>',
+  GIFT_MENU: '<증정 메뉴>',
+  BENEFIT_DETAILS: '<혜택 내역>',
+  TOTAL_BENEFIT_AMOUNT: '<총혜택 금액>',
+  PAYMENT_AMOUNT: '<할인 후 예상 결제 금액>',
+  EVENT_BADGE: '<12월 이벤트 배지>',
+});
+
+export const RESULT_BENEFIT_TITLES = Object.freeze({
+  CDD: '크리스마스 디데이 할인',
+  WDD: '평일 할인',
+  WED: '주말 할인',
+  SD: '특별 할인',
+  G: '증정 이벤트',
+});

리뷰: 아 프리티어 때문에 넘어가서ㅋㅋㅋ 알아요 저도 그래서 프리티어 설정을 바꿔버렸어요...
@@ -0,0 +1,75 @@
+import InputView from '../views/InputView.js';
+import InputValidator from '../validators/InputValidator.js';
+import OutputView from '../views/OutputView.js';
+import {
+  removeWhiteSpaceFromBothEndsOfString,
+  getOrderedMenusObject,
+} from '../utils/general.js';
+import OrderSheet from '../models/OrderSheet.js';
+import { generateResultFormat } from '../utils/formatting.js';
+
+class EventPlannerController {
+  #inputView;
+  #outputView;
+  #orderSheet;
+
+  constructor() {
+    this.#inputView = new InputView();
+    this.#outputView = new OutputView();
+    this.#orderSheet = new OrderSheet();
+  }
+
+  async start() {
+    this.printEventPlannerStart();
+    await this.getVisitDate();
+    await this.getOrderedMenus();
+    this.printResult();
+  }
+
+  async getVisitDate() {
+    const inputValidator = new InputValidator();
+    while (true) {
+      const visitDateInput = removeWhiteSpaceFromBothEndsOfString(
+        await this.#inputView.readVisitDate()
+      );
+      try {
+        inputValidator.validateVisitDate(visitDateInput);
+        this.#orderSheet.setVisitDate(Number(visitDateInput));
+        break;
+      } catch (error) {
+        this.#outputView.printErrorMessage(error.message);
+      }
+    }
+  }
+
+  async getOrderedMenus() {
+    const inputValidator = new InputValidator();
+    while (true) {
+      const orderedMenusInput = removeWhiteSpaceFromBothEndsOfString(
+        await this.#inputView.readMenus()
+      );
+      try {
+        inputValidator.validateOrderedMenus(orderedMenusInput);
+        this.#orderSheet.setOrderedMenus(
+          getOrderedMenusObject(orderedMenusInput)
+        );
+        break;
+      } catch (error) {
+        this.#outputView.printErrorMessage(error.message);
+      }
+    }
+  }
+
+  printEventPlannerStart() {
+    this.#outputView.printStartMessage();
+  }
+
+  printResult() {
+    const resultOutputFormat = generateResultFormat({
+      ...this.#orderSheet.getResult(),
+    });
+    this.#outputView.printResult(resultOutputFormat);
+  }
+}
+
+export default EventPlannerController;

리뷰: 전체 흐름을 함수화하여 start에서 호출하는 방식으로 작성하여 코드의 전반적인 흐름이 한 눈에 보이는 것 같습니다!

클래스 내부에서만 사용되는 메서드는 private처리를 해주면 더 의도가 드러나고 코드를 읽을 때 한결 편할 것 같아요!
@@ -0,0 +1,75 @@
+import InputView from '../views/InputView.js';
+import InputValidator from '../validators/InputValidator.js';
+import OutputView from '../views/OutputView.js';
+import {
+  removeWhiteSpaceFromBothEndsOfString,
+  getOrderedMenusObject,
+} from '../utils/general.js';
+import OrderSheet from '../models/OrderSheet.js';
+import { generateResultFormat } from '../utils/formatting.js';
+
+class EventPlannerController {
+  #inputView;
+  #outputView;
+  #orderSheet;
+
+  constructor() {
+    this.#inputView = new InputView();
+    this.#outputView = new OutputView();
+    this.#orderSheet = new OrderSheet();
+  }
+
+  async start() {
+    this.printEventPlannerStart();
+    await this.getVisitDate();
+    await this.getOrderedMenus();
+    this.printResult();
+  }
+
+  async getVisitDate() {
+    const inputValidator = new InputValidator();
+    while (true) {
+      const visitDateInput = removeWhiteSpaceFromBothEndsOfString(
+        await this.#inputView.readVisitDate()
+      );
+      try {
+        inputValidator.validateVisitDate(visitDateInput);
+        this.#orderSheet.setVisitDate(Number(visitDateInput));
+        break;
+      } catch (error) {
+        this.#outputView.printErrorMessage(error.message);
+      }
+    }
+  }
+
+  async getOrderedMenus() {
+    const inputValidator = new InputValidator();
+    while (true) {
+      const orderedMenusInput = removeWhiteSpaceFromBothEndsOfString(
+        await this.#inputView.readMenus()
+      );
+      try {
+        inputValidator.validateOrderedMenus(orderedMenusInput);
+        this.#orderSheet.setOrderedMenus(
+          getOrderedMenusObject(orderedMenusInput)
+        );
+        break;
+      } catch (error) {
+        this.#outputView.printErrorMessage(error.message);
+      }
+    }
+  }
+
+  printEventPlannerStart() {
+    this.#outputView.printStartMessage();
+  }
+
+  printResult() {
+    const resultOutputFormat = generateResultFormat({
+      ...this.#orderSheet.getResult(),
+    });
+    this.#outputView.printResult(resultOutputFormat);
+  }
+}
+
+export default EventPlannerController;

리뷰: 무한루프를 통해서 에러 처리 후 재입력을 받는 요구사항을 해결하신 것 같네요! 무한 루프가 위험성이 조금 있다고 생각하는데, 다른 해결방식도 고려해보신게 있으신가요?
@@ -0,0 +1,75 @@
+import InputView from '../views/InputView.js';
+import InputValidator from '../validators/InputValidator.js';
+import OutputView from '../views/OutputView.js';
+import {
+  removeWhiteSpaceFromBothEndsOfString,
+  getOrderedMenusObject,
+} from '../utils/general.js';
+import OrderSheet from '../models/OrderSheet.js';
+import { generateResultFormat } from '../utils/formatting.js';
+
+class EventPlannerController {
+  #inputView;
+  #outputView;
+  #orderSheet;
+
+  constructor() {
+    this.#inputView = new InputView();
+    this.#outputView = new OutputView();
+    this.#orderSheet = new OrderSheet();
+  }
+
+  async start() {
+    this.printEventPlannerStart();
+    await this.getVisitDate();
+    await this.getOrderedMenus();
+    this.printResult();
+  }
+
+  async getVisitDate() {
+    const inputValidator = new InputValidator();
+    while (true) {
+      const visitDateInput = removeWhiteSpaceFromBothEndsOfString(
+        await this.#inputView.readVisitDate()
+      );
+      try {
+        inputValidator.validateVisitDate(visitDateInput);
+        this.#orderSheet.setVisitDate(Number(visitDateInput));
+        break;
+      } catch (error) {
+        this.#outputView.printErrorMessage(error.message);
+      }
+    }
+  }
+
+  async getOrderedMenus() {
+    const inputValidator = new InputValidator();
+    while (true) {
+      const orderedMenusInput = removeWhiteSpaceFromBothEndsOfString(
+        await this.#inputView.readMenus()
+      );
+      try {
+        inputValidator.validateOrderedMenus(orderedMenusInput);
+        this.#orderSheet.setOrderedMenus(
+          getOrderedMenusObject(orderedMenusInput)
+        );
+        break;
+      } catch (error) {
+        this.#outputView.printErrorMessage(error.message);
+      }
+    }
+  }
+
+  printEventPlannerStart() {
+    this.#outputView.printStartMessage();
+  }
+
+  printResult() {
+    const resultOutputFormat = generateResultFormat({
+      ...this.#orderSheet.getResult(),
+    });
+    this.#outputView.printResult(resultOutputFormat);
+  }
+}
+
+export default EventPlannerController;

리뷰: 15라인이 넘는 것 같습니다...!
@@ -0,0 +1,75 @@
+import InputView from '../views/InputView.js';
+import InputValidator from '../validators/InputValidator.js';
+import OutputView from '../views/OutputView.js';
+import {
+  removeWhiteSpaceFromBothEndsOfString,
+  getOrderedMenusObject,
+} from '../utils/general.js';
+import OrderSheet from '../models/OrderSheet.js';
+import { generateResultFormat } from '../utils/formatting.js';
+
+class EventPlannerController {
+  #inputView;
+  #outputView;
+  #orderSheet;
+
+  constructor() {
+    this.#inputView = new InputView();
+    this.#outputView = new OutputView();
+    this.#orderSheet = new OrderSheet();
+  }
+
+  async start() {
+    this.printEventPlannerStart();
+    await this.getVisitDate();
+    await this.getOrderedMenus();
+    this.printResult();
+  }
+
+  async getVisitDate() {
+    const inputValidator = new InputValidator();
+    while (true) {
+      const visitDateInput = removeWhiteSpaceFromBothEndsOfString(
+        await this.#inputView.readVisitDate()
+      );
+      try {
+        inputValidator.validateVisitDate(visitDateInput);
+        this.#orderSheet.setVisitDate(Number(visitDateInput));
+        break;
+      } catch (error) {
+        this.#outputView.printErrorMessage(error.message);
+      }
+    }
+  }
+
+  async getOrderedMenus() {
+    const inputValidator = new InputValidator();
+    while (true) {
+      const orderedMenusInput = removeWhiteSpaceFromBothEndsOfString(
+        await this.#inputView.readMenus()
+      );
+      try {
+        inputValidator.validateOrderedMenus(orderedMenusInput);
+        this.#orderSheet.setOrderedMenus(
+          getOrderedMenusObject(orderedMenusInput)
+        );
+        break;
+      } catch (error) {
+        this.#outputView.printErrorMessage(error.message);
+      }
+    }
+  }
+
+  printEventPlannerStart() {
+    this.#outputView.printStartMessage();
+  }
+
+  printResult() {
+    const resultOutputFormat = generateResultFormat({
+      ...this.#orderSheet.getResult(),
+    });
+    this.#outputView.printResult(resultOutputFormat);
+  }
+}
+
+export default EventPlannerController;

리뷰: ```javascript
const visitDateInput = await this.#inputView.readVisitDate(); 
``` 
해당 위치에서는 이런식으로 작성하고, 입력을 받는 곳에서 removeWhiteSpaceFromBothEndsOfString 유틸리티 사용, 유효성 검사 후 가공된 값을 반환하는 방식으로 구현했다면 어땠을까요?

개인적으로 상위 로직은 큰 단위로 작성하고, 디테일한 처리는 하위 로직에서 담당하는게 좋다고 생각합니다! SungHyun627님은 어떻게 생각하시나요?
@@ -0,0 +1,75 @@
+import InputView from '../views/InputView.js';
+import InputValidator from '../validators/InputValidator.js';
+import OutputView from '../views/OutputView.js';
+import {
+  removeWhiteSpaceFromBothEndsOfString,
+  getOrderedMenusObject,
+} from '../utils/general.js';
+import OrderSheet from '../models/OrderSheet.js';
+import { generateResultFormat } from '../utils/formatting.js';
+
+class EventPlannerController {
+  #inputView;
+  #outputView;
+  #orderSheet;
+
+  constructor() {
+    this.#inputView = new InputView();
+    this.#outputView = new OutputView();
+    this.#orderSheet = new OrderSheet();
+  }
+
+  async start() {
+    this.printEventPlannerStart();
+    await this.getVisitDate();
+    await this.getOrderedMenus();
+    this.printResult();
+  }
+
+  async getVisitDate() {
+    const inputValidator = new InputValidator();
+    while (true) {
+      const visitDateInput = removeWhiteSpaceFromBothEndsOfString(
+        await this.#inputView.readVisitDate()
+      );
+      try {
+        inputValidator.validateVisitDate(visitDateInput);
+        this.#orderSheet.setVisitDate(Number(visitDateInput));
+        break;
+      } catch (error) {
+        this.#outputView.printErrorMessage(error.message);
+      }
+    }
+  }
+
+  async getOrderedMenus() {
+    const inputValidator = new InputValidator();
+    while (true) {
+      const orderedMenusInput = removeWhiteSpaceFromBothEndsOfString(
+        await this.#inputView.readMenus()
+      );
+      try {
+        inputValidator.validateOrderedMenus(orderedMenusInput);
+        this.#orderSheet.setOrderedMenus(
+          getOrderedMenusObject(orderedMenusInput)
+        );
+        break;
+      } catch (error) {
+        this.#outputView.printErrorMessage(error.message);
+      }
+    }
+  }
+
+  printEventPlannerStart() {
+    this.#outputView.printStartMessage();
+  }
+
+  printResult() {
+    const resultOutputFormat = generateResultFormat({
+      ...this.#orderSheet.getResult(),
+    });
+    this.#outputView.printResult(resultOutputFormat);
+  }
+}
+
+export default EventPlannerController;

리뷰: 결과 계산, 계산한 결과 포맷팅, 포맷팅한 결과 출력 역할 분리를 잘하셨네요!
@@ -0,0 +1,15 @@
+export const DISCOUNT_UNITS = Object.freeze({
+  CHRISTMAS_DDAY_DISCOUNT_UNIT: 100,
+  WEEKDAY_DISCOUNT_UNIT: 2023,
+  WEEKEND_DISCOUNT_UNIT: 2023,
+});
+
+export const DISCOUNT_AMOUNTS = Object.freeze({
+  ZERO_DISCOUNT_AMOUNT: 0,
+  INITIAL_CHRISTMAS_DDAY_DISCOUNT_AMOUNT: 1000,
+  SPECIAL_DISCOUNT_AMOUNT: 1000,
+});
+
+export const DISCOUNT_EVENT_CRITERIAS = Object.freeze({
+  MIN_TOTAL_ORDER_AMOUNT_FOR_DISCOUNT_EVENT: 10000,
+});

리뷰: ```javascript
export const DISCOUNT_UNIT = Object.freeze({
  christmasDDay: 100,
  weekday: 2023,
  weekend: 2023,
});
```
airbnb convention에 의하면, 객체 상수를 만들 때 key는 camelCase로 한다고 합니다! 또, discount unit이라는 의미가 중복되기 때문에 없애도 될 것 같아요! 해당 객체는 예시를 위해 특정한 것이고, 다른 상수 객체들에도 동일한 리뷰 드리고 싶습니다!
@@ -0,0 +1,31 @@
+package com.codesquad.datastructurestudy.basic;
+
+public class BinarySearch {
+
+  private static int binarySearch(int[] numbers, int number, int first, int last) {
+    while (first <= last) {
+      int mid = (first + last) / 2;
+
+      if (numbers[mid] == number) {
+        return mid;
+      } else if (numbers[mid] < number) {
+        first = mid + 1;
+      } else {
+        last = mid - 1;
+      }
+    }
+    return -1;
+  }
+
+  public static void main(String[] args) {
+    int[] numbers = {1,2,3,4,5,6,7,8,9};
+    int number = 2;
+    int index = binarySearch(numbers, number, 0, numbers.length - 1);
+
+    if (index == -1) {
+      System.out.println("못찾았다");
+    } else {
+      System.out.println(index + "번째 인덱스에서 찾았다");
+    }
+  }
+}

리뷰: 이 부분은 int overflow를 조심하셔야해요.
https://endorphin0710.tistory.com/112
여길 참고해보세요.
@@ -0,0 +1,44 @@
+package com.codesquad.datastructurestudy.basic;
+
+import java.util.Arrays;
+
+public class BinarySearchOperationCount {
+
+  private static int binarySearch(int[] numbers, int number, int first, int last) {
+    int mid;
+    int opCount = 0;
+
+    while (first <= last) {
+      mid = (first + last) / 2;
+
+      if (numbers[mid] == number) {
+        return mid;
+      } else if (numbers[mid] < number) {
+        first = mid + 1;
+      } else {
+        last = mid - 1;
+      }
+      opCount++;
+    }
+    return opCount;
+  }
+
+  public static void main(String[] args) {
+    int[] arr1 = new int[500];
+    int[] arr2 = new int[5000];
+    int[] arr3 = new int[50000];
+    int number = 5000;
+
+    Arrays.fill(arr1, 0);
+    Arrays.fill(arr2, 0);
+    Arrays.fill(arr3, 0);
+
+    int opCount1 = binarySearch(arr1, number, 0, arr1.length - 1);
+    int opCount2 = binarySearch(arr2, number, 0, arr2.length - 1);
+    int opCount3 = binarySearch(arr3, number, 0, arr3.length - 1);
+
+    System.out.println("비교연산횟수 : " + opCount1);
+    System.out.println("비교연산횟수 : " + opCount2);
+    System.out.println("비교연산횟수 : " + opCount3);
+  }
+}

리뷰: 이건 약간 윤성우님 책 스타일로 선언을 했네요 ㅋㅋ
저라면 초기화와 동시에 선언하는 스타일을 사용했을 것 같습니다.