ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,30 @@
+package christmas.constants.event;
+
+public enum BadgeType {
+    NONE("없음", 0),
+    STAR("별", 5_000),
+    TREE("트리", 10_000),
+    SANTA("산타", 20_000);
+
+    private final String name;
+    private final int threshold;
+
+    BadgeType(String name, int threshold) {
+        this.name = name;
+        this.threshold = threshold;
+    }
+
+    public static BadgeType from(int benefitPrice) {
+        BadgeType result = NONE;
+        for (BadgeType badgeType : values()) {
+            if (benefitPrice >= badgeType.threshold) {
+                result = badgeType;
+            }
+        }
+        return result;
+    }
+    
+    public String getName() {
+        return name;
+    }
+}

리뷰: 배지 없음을 NONE으로 관리하신 부분도 좋은 전략이라고 생각합니다 :)

Optional을 고려해보시는 것도 좋을 것 같아요! 이번에 Optional을 처음 활용해봤는데요, **없을 수도 있다 라는 비즈니스 요구사항**을 **코드로 명시**할 수 있는 점이 정말 큰 장점인 것 같습니다 :)

만약 Optional을 도입한다면 `from` 메서드를 이렇게 리팩터링해볼 수 있을 것 같아요!
```java
public static Optional<BadgeType> from(int benefitPrice) {
        Optional<Badge> badge = Arrays.stream(values())
                .filter(badge -> badge.minTotalBenefitAmount <= totalBenefitAmount)
                .findFirst();
        
        if (badge.isPresent()) {
            return badge.get();
        }
        return badge.empty();
    }
}
```

저도 아직 Optional을 언제 사용해야 남용이 아닐까? 라는 확실한 기준은 없어서, 참고 정도만 하시면 좋을 것 같습니다 :)
@@ -0,0 +1,54 @@
+package christmas.constants.menu;
+
+import static christmas.constants.menu.MenuType.APPETIZER;
+import static christmas.constants.menu.MenuType.DESSERT;
+import static christmas.constants.menu.MenuType.DRINKS;
+import static christmas.constants.menu.MenuType.MAIN;
+import static christmas.exception.ErrorCode.INVALID_MENU_ORDER;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+public enum Menu {
+    MUSHROOM_SOUP(APPETIZER, "양송이수프", 6000),
+    TAPAS(APPETIZER, "타파스", 5500),
+    SALAD(APPETIZER, "시저샐러드", 8000),
+    STAKE(MAIN, "티본스테이크", 55_000),
+    LIB(MAIN, "바비큐립", 54_000),
+    PASTA_(MAIN, "해산물파스타", 35_000),
+    PASTA(MAIN, "크리스마스파스타", 25_000),
+    CAKE(DESSERT, "초코케이크", 15_000),
+    ICE_CREAM(DESSERT, "아이스크림", 5000),
+    ZERO_COLA(DRINKS, "제로콜라", 3000),
+    WINE(DRINKS, "레드와인", 60_000),
+    CHAMPAGNE(DRINKS, "샴페인", 25_000);
+
+    private final MenuType menuType;
+    private final String name;
+    private final int price;
+
+    Menu(MenuType menuType, String name, int price) {
+        this.menuType = menuType;
+        this.name = name;
+        this.price = price;
+    }
+
+    public static Menu from(String input) {
+        return Arrays.stream(values())
+                .filter(menu -> Objects.equals(menu.name, input))
+                .findFirst()
+                .orElseThrow(() -> new IllegalArgumentException(INVALID_MENU_ORDER.getMessage()));
+    }
+
+    public MenuType getMenuType() {
+        return menuType;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public int getPrice() {
+        return price;
+    }
+}

리뷰: `이름`에 대한 `메뉴`를 HashMap으로 캐싱해두는 전략도 추천드립니다.

Enum에선 static 블록을 추가할 수 있는데요, 요런식으로 구현할 수 있습니다!

```java
public enum Menu {
    // 열거형 상수들 ...

    private static final Map<String, Menu> cachedMenu = new HashMap<>();

    static {
        for (Menu menu : values()) {
            cachedMenu.put(menu.getName(), menu);
        }
    }
}
```
이렇게 Map으로 캐싱해두면, 매 번 열거형 상수들을 순회하지 않아도 되는 장점이 있습니다 :)

하지만 NPE가 발생할 위험이 있기 때문에, Optional을 사용해보시는 것도 고려해보시면 좋을 것 같습니다.
그렇게 하면

```java
public static Optional<Menu> from(String menuName) {
        return Optional.ofNullalbe(cachedMenu.get(menuName);
}
```
위와 같이 리팩터링 해볼 수 있을 것 같아요 :)
@@ -0,0 +1,32 @@
+package christmas.util;
+
+import static christmas.exception.ErrorCode.INVALID_DATE;
+import static christmas.exception.ErrorCode.INVALID_MENU_ORDER;
+
+import java.util.List;
+
+public class Parser {
+    public static int parseToDate(String input) {
+        try {
+            return Integer.parseInt(input);
+        } catch (NumberFormatException e) {
+            throw new IllegalArgumentException(INVALID_DATE.getMessage());
+        }
+    }
+
+    public static int parseToAmount(String input) {
+        try {
+            return Integer.parseInt(input);
+        } catch (NumberFormatException e) {
+            throw new IllegalArgumentException(INVALID_MENU_ORDER.getMessage());
+        }
+    }
+
+    public static List<String> parseToMenu(String input, String delimiter) {
+        try {
+            return List.of(input.split(delimiter));
+        } catch (IndexOutOfBoundsException e) {
+            throw new IllegalArgumentException(INVALID_MENU_ORDER.getMessage());
+        }
+    }
+}

리뷰: catch 한 어떠한 Exception을 다른 Exception으로 래핑하여 다시 던져줄 땐, catch 했던 Exception도 넣어서 넘겨주는 것을 추천드립니다.

```java
public static int parseToDate(String input) {
        try {
            return Integer.parseInt(input);
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException(INVALID_DATE.getMessage(), e);
        }
    }
```

그렇지 않으면 기존에 catch 했던 예외가 날아가버리는 상황이 되어, 세부 예외를 스택 트레이스 할 수 없게됩니당!
@@ -0,0 +1,25 @@
+package christmas.constants.event;
+
+public enum EventRule {
+    EVENT_THRESHOLD(10_000),
+    PRESENT_THRESHOLD(120_000),
+    EVENT_START(1),
+    EVENT_END(31),
+    MAX_MENU_AMOUNT(20),
+    CHRISTMAS_EVENT_END(25),
+    CHRISTMAS_INIT_PRICE(1_000),
+    CHRISTMAS_EXTRA_DISCOUNT(100),
+    MENU_DISCOUNT(2_023),
+    SPECIAL_DISCOUNT(1_000);
+
+
+    private final int value;
+
+    EventRule(int value) {
+        this.value = value;
+    }
+
+    public int getValue() {
+        return value;
+    }
+}

리뷰: 이벤트 룰을 한 곳에 모아놓으니 코드가 쉽게 읽히지 않는거 같아요! 이벤트 종류에 따라 분리하거나, EventType의 멤버변수로 룰을 정의하시는 것은 어떤가요?
@@ -0,0 +1,38 @@
+package christmas.exception;
+
+import christmas.view.output.OutputView;
+import java.util.Arrays;
+import java.util.function.Supplier;
+
+public class RetryHandler implements ExceptionHandler {
+    private final OutputView outputView;
+
+    public RetryHandler(OutputView outputView) {
+        this.outputView = outputView;
+    }
+
+    @Override
+    @SafeVarargs
+    public final <T> T execute(Supplier<T> action, Class<? extends Exception>... exceptions) {
+        while (true) {
+            try {
+                return action.get();
+            } catch (IllegalArgumentException e) {
+                printException(e, exceptions);
+            }
+        }
+    }
+
+    private void printException(Exception actual, Class<? extends Exception>... exceptions) {
+        if (isExpectedException(actual, exceptions)) {
+            outputView.printError(actual.getMessage());
+            return;
+        }
+        throw new RuntimeException(actual);
+    }
+
+    private boolean isExpectedException(Exception actual, Class<? extends Exception>... exceptions) {
+        return Arrays.stream(exceptions)
+                .anyMatch(exception -> exception.isInstance(actual));
+    }
+}

리뷰: 실제 exception과 인자로 받은 exception을 비교하는 로직이 필요한 이유가 궁금해요!
@@ -0,0 +1,19 @@
+package christmas.constants.event;
+
+public enum EventType {
+    CHRISTMAS("크리스마스 디데이 할인"),
+    WEEKDAY("평일 할인"),
+    WEEKEND("주말 할인"),
+    SPECIAL("특별 할인"),
+    PRESENT("증정 이벤트");
+
+    private final String description;
+
+    EventType(String description) {
+        this.description = description;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+}

리뷰: `Eventable` 인터페이스와 `각각의 이벤트 구현체 클래스`에서 할인 / 증정 상품 계산 처리하신 방법도 굉장히 직관적으로 읽히고 정말 좋은 것 같습니다! 👍

저는 `크리스마스 디데이/ 평일/ 주말 /특별 할인 이벤트 유형`과 `할인 이벤트 유형에 해당하는 계산 처리`는 **서로 밀접한 연관이 있는 상수**라는 생각이 들었습니다! 그래서 `EventType Enum`에서 할인 계산 처리도 함께 관리하도록 했습니다.

이렇게 처리를 하니 **할인 이벤트 유형이라는 상태**와 **이벤트 유형에 대한 계산처리라는 행위**가 한 곳에 밀집되어 있어 유지보수하기 편할 것 같다는 생각이 들었습니다! 이 부분에 대해서 HEY님 의견이 궁금합니다 😊
@@ -0,0 +1,7 @@
+package christmas.model;
+
+public interface Eventable<T> {
+    boolean canJoinEvent(T condition);
+
+    int getDiscountPrice();
+}

리뷰: Eventable이라는 객체명이 조금 헷갈리는거 같아요. 현재 Eventable은 이벤트 여부를 판단하는 메소드와 할인 금액을 계산하는 메소드가 존재해요. 하지만 Eventable 네이밍은 할인 여부만 판단하는 역할이라고 오해할 수 있을거 같습니다 🔥
@@ -0,0 +1,33 @@
+package christmas.model;
+
+import static christmas.constants.event.EventRule.PRESENT_THRESHOLD;
+import static christmas.constants.menu.Menu.CHAMPAGNE;
+
+public class PresentEvent implements Eventable<Integer> {
+    private final int amount;
+
+    private PresentEvent(int orderPrice) {
+        if (canJoinEvent(orderPrice)) {
+            this.amount = 1;
+            return;
+        }
+        this.amount = 0;
+    }
+
+    public static PresentEvent create(int orderPrice) {
+        return new PresentEvent(orderPrice);
+    }
+
+    @Override
+    public boolean canJoinEvent(Integer orderPrice) {
+        if (orderPrice < PRESENT_THRESHOLD.getValue()) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public int getDiscountPrice() {
+        return CHAMPAGNE.getPrice() * amount;
+    }
+}

리뷰: 1 이라는 숫자를 상수로 선언하는 것은 어떤가요? 요구사항을 이해하지 못한 사람이 코드를 보는 경우 1이라는 숫자의 의미를 파악하기 어려울거 같아요 :)
@@ -0,0 +1,80 @@
+package christmas.service;
+
+import static christmas.constants.event.EventRule.EVENT_THRESHOLD;
+import static christmas.constants.event.EventType.CHRISTMAS;
+import static christmas.constants.event.EventType.PRESENT;
+import static christmas.constants.event.EventType.SPECIAL;
+import static christmas.constants.event.EventType.WEEKDAY;
+import static christmas.constants.event.EventType.WEEKEND;
+
+import christmas.constants.event.EventType;
+import christmas.dto.EventDetail;
+import christmas.dto.UserOrder;
+import christmas.model.ChristmasEvent;
+import christmas.model.Eventable;
+import christmas.model.PresentEvent;
+import christmas.model.SpecialEvent;
+import christmas.model.WeekdayEvent;
+import christmas.model.WeekendEvent;
+import java.util.EnumMap;
+import java.util.List;
+import java.util.Map;
+
+public class EventService {
+    private final Map<EventType, Eventable> eventResult;
+    private final int INVALID_VALUE = 0;
+
+    public EventService() {
+        eventResult = new EnumMap<>(EventType.class);
+    }
+
+    public void applyEvent(UserOrder userOrder) {
+        if (!canJoinEvent(userOrder.orderPrice())) {
+            userOrder = new UserOrder(INVALID_VALUE, INVALID_VALUE, INVALID_VALUE, INVALID_VALUE);
+        }
+
+        eventResult.put(PRESENT, PresentEvent.create(userOrder.orderPrice()));
+        eventResult.put(CHRISTMAS, ChristmasEvent.create(userOrder.date()));
+        eventResult.put(WEEKDAY, WeekdayEvent.create(userOrder));
+        eventResult.put(WEEKEND, WeekendEvent.create(userOrder));
+        eventResult.put(SPECIAL, SpecialEvent.create(userOrder.date()));
+    }
+
+    private boolean canJoinEvent(int orderPrice) {
+        if (orderPrice < EVENT_THRESHOLD.getValue()) {
+            return false;
+        }
+        return true;
+    }
+
+    public int getDiscountPriceByEvent(EventType eventType) {
+        return eventResult.get(eventType).getDiscountPrice();
+    }
+
+    public int getExpectedPrice(UserOrder userOrder) {
+        return userOrder.orderPrice() - getTotalDiscountPrice();
+    }
+
+    public int getTotalDiscountPrice() {
+        return eventResult.entrySet()
+                .stream()
+                .filter(entry -> entry.getKey() != PRESENT)
+                .mapToInt(value -> value.getValue().getDiscountPrice())
+                .sum();
+    }
+
+    public int getTotalBenefitPrice() {
+        return eventResult.values()
+                .stream()
+                .mapToInt(Eventable::getDiscountPrice)
+                .sum();
+    }
+    
+    public List<EventDetail> convertToEventDetails() {
+        return eventResult.entrySet()
+                .stream()
+                .filter(entry -> entry.getValue().getDiscountPrice() != 0)
+                .map(entry -> new EventDetail(entry.getKey(), entry.getValue().getDiscountPrice()))
+                .toList();
+    }
+}

리뷰: EventService도 충분히 좋은 코드라고 생각됩니다!

이벤트는 `할인`에 대한 이벤트, `증정상품`에 대한 이벤트로 나뉘기 때문에 `DiscountEventService`, `GiftEventService`로 나눈다면, 각 서비스 클래스가 어떤 이벤트에 대해 처리하는지 보다 명확해지고 유지보수하기도 좋아질 것 같습니다 :)
@@ -0,0 +1,31 @@
+package christmas.model;
+
+import static christmas.constants.event.EventRule.SPECIAL_DISCOUNT;
+
+import christmas.util.DayAnalyzer;
+
+public class SpecialEvent implements Eventable<Integer> {
+    private final int discountPrice;
+
+    private SpecialEvent(Integer date) {
+        if (canJoinEvent(date)) {
+            discountPrice = SPECIAL_DISCOUNT.getValue();
+            return;
+        }
+        discountPrice = 0;
+    }
+
+    public static SpecialEvent create(Integer date) {
+        return new SpecialEvent(date);
+    }
+
+    @Override
+    public boolean canJoinEvent(Integer date) {
+        return DayAnalyzer.isSpecialDay(date);
+    }
+
+    @Override
+    public int getDiscountPrice() {
+        return this.discountPrice;
+    }
+}

리뷰: 해당 로직은 정적 메소드에 들어가는 것이 적합할거 같은데 어떻게 생각하시나요? 생성자는 객체를 생성하는 책임만 갖고, 정적 메소드에서 조건에 따른 discountPrice값을 생성자로 넘겨줄 수 있을거 같습니다.
@@ -0,0 +1,80 @@
+package christmas.service;
+
+import static christmas.constants.event.EventRule.EVENT_THRESHOLD;
+import static christmas.constants.event.EventType.CHRISTMAS;
+import static christmas.constants.event.EventType.PRESENT;
+import static christmas.constants.event.EventType.SPECIAL;
+import static christmas.constants.event.EventType.WEEKDAY;
+import static christmas.constants.event.EventType.WEEKEND;
+
+import christmas.constants.event.EventType;
+import christmas.dto.EventDetail;
+import christmas.dto.UserOrder;
+import christmas.model.ChristmasEvent;
+import christmas.model.Eventable;
+import christmas.model.PresentEvent;
+import christmas.model.SpecialEvent;
+import christmas.model.WeekdayEvent;
+import christmas.model.WeekendEvent;
+import java.util.EnumMap;
+import java.util.List;
+import java.util.Map;
+
+public class EventService {
+    private final Map<EventType, Eventable> eventResult;
+    private final int INVALID_VALUE = 0;
+
+    public EventService() {
+        eventResult = new EnumMap<>(EventType.class);
+    }
+
+    public void applyEvent(UserOrder userOrder) {
+        if (!canJoinEvent(userOrder.orderPrice())) {
+            userOrder = new UserOrder(INVALID_VALUE, INVALID_VALUE, INVALID_VALUE, INVALID_VALUE);
+        }
+
+        eventResult.put(PRESENT, PresentEvent.create(userOrder.orderPrice()));
+        eventResult.put(CHRISTMAS, ChristmasEvent.create(userOrder.date()));
+        eventResult.put(WEEKDAY, WeekdayEvent.create(userOrder));
+        eventResult.put(WEEKEND, WeekendEvent.create(userOrder));
+        eventResult.put(SPECIAL, SpecialEvent.create(userOrder.date()));
+    }
+
+    private boolean canJoinEvent(int orderPrice) {
+        if (orderPrice < EVENT_THRESHOLD.getValue()) {
+            return false;
+        }
+        return true;
+    }
+
+    public int getDiscountPriceByEvent(EventType eventType) {
+        return eventResult.get(eventType).getDiscountPrice();
+    }
+
+    public int getExpectedPrice(UserOrder userOrder) {
+        return userOrder.orderPrice() - getTotalDiscountPrice();
+    }
+
+    public int getTotalDiscountPrice() {
+        return eventResult.entrySet()
+                .stream()
+                .filter(entry -> entry.getKey() != PRESENT)
+                .mapToInt(value -> value.getValue().getDiscountPrice())
+                .sum();
+    }
+
+    public int getTotalBenefitPrice() {
+        return eventResult.values()
+                .stream()
+                .mapToInt(Eventable::getDiscountPrice)
+                .sum();
+    }
+    
+    public List<EventDetail> convertToEventDetails() {
+        return eventResult.entrySet()
+                .stream()
+                .filter(entry -> entry.getValue().getDiscountPrice() != 0)
+                .map(entry -> new EventDetail(entry.getKey(), entry.getValue().getDiscountPrice()))
+                .toList();
+    }
+}

리뷰: 해당 로직은 EnumMap을 초기화하는 로직인거 같아요. 그럼 생성자가 아닌 init 이라는 이름의 정적 메소드를 통해 내부 로직을 좀 더 명확히 파악할 수 있도록 하는 것은 어떤가요?
@@ -0,0 +1,80 @@
+package christmas.service;
+
+import static christmas.constants.event.EventRule.EVENT_THRESHOLD;
+import static christmas.constants.event.EventType.CHRISTMAS;
+import static christmas.constants.event.EventType.PRESENT;
+import static christmas.constants.event.EventType.SPECIAL;
+import static christmas.constants.event.EventType.WEEKDAY;
+import static christmas.constants.event.EventType.WEEKEND;
+
+import christmas.constants.event.EventType;
+import christmas.dto.EventDetail;
+import christmas.dto.UserOrder;
+import christmas.model.ChristmasEvent;
+import christmas.model.Eventable;
+import christmas.model.PresentEvent;
+import christmas.model.SpecialEvent;
+import christmas.model.WeekdayEvent;
+import christmas.model.WeekendEvent;
+import java.util.EnumMap;
+import java.util.List;
+import java.util.Map;
+
+public class EventService {
+    private final Map<EventType, Eventable> eventResult;
+    private final int INVALID_VALUE = 0;
+
+    public EventService() {
+        eventResult = new EnumMap<>(EventType.class);
+    }
+
+    public void applyEvent(UserOrder userOrder) {
+        if (!canJoinEvent(userOrder.orderPrice())) {
+            userOrder = new UserOrder(INVALID_VALUE, INVALID_VALUE, INVALID_VALUE, INVALID_VALUE);
+        }
+
+        eventResult.put(PRESENT, PresentEvent.create(userOrder.orderPrice()));
+        eventResult.put(CHRISTMAS, ChristmasEvent.create(userOrder.date()));
+        eventResult.put(WEEKDAY, WeekdayEvent.create(userOrder));
+        eventResult.put(WEEKEND, WeekendEvent.create(userOrder));
+        eventResult.put(SPECIAL, SpecialEvent.create(userOrder.date()));
+    }
+
+    private boolean canJoinEvent(int orderPrice) {
+        if (orderPrice < EVENT_THRESHOLD.getValue()) {
+            return false;
+        }
+        return true;
+    }
+
+    public int getDiscountPriceByEvent(EventType eventType) {
+        return eventResult.get(eventType).getDiscountPrice();
+    }
+
+    public int getExpectedPrice(UserOrder userOrder) {
+        return userOrder.orderPrice() - getTotalDiscountPrice();
+    }
+
+    public int getTotalDiscountPrice() {
+        return eventResult.entrySet()
+                .stream()
+                .filter(entry -> entry.getKey() != PRESENT)
+                .mapToInt(value -> value.getValue().getDiscountPrice())
+                .sum();
+    }
+
+    public int getTotalBenefitPrice() {
+        return eventResult.values()
+                .stream()
+                .mapToInt(Eventable::getDiscountPrice)
+                .sum();
+    }
+    
+    public List<EventDetail> convertToEventDetails() {
+        return eventResult.entrySet()
+                .stream()
+                .filter(entry -> entry.getValue().getDiscountPrice() != 0)
+                .map(entry -> new EventDetail(entry.getKey(), entry.getValue().getDiscountPrice()))
+                .toList();
+    }
+}

리뷰: 이벤트에 대한 정책들을 외부에서 주입받도록 하는 것은 어떤가요? 현재 코드는 의존 역전 원칙(DIP)을 준수하지 않은 코드라고 생각합니다. 할인 종류에 대한 정책이 변하는 경우 애플리케이션 코드를 수정해야하는 문제가 발생합니다. 따라서, 애플리케이션 내부에서는 추상 객체에 의존하고, service 생성자에 EnumMap을 넘겨주는 것은 어떤가요?
@@ -0,0 +1,45 @@
+package christmas.util;
+
+import static christmas.constants.Day.FRIDAY;
+import static christmas.constants.Day.SATURDAY;
+import static christmas.constants.Day.SUNDAY;
+import static christmas.constants.Day.THURSDAY;
+
+import christmas.constants.Day;
+import java.util.Arrays;
+
+public class DayAnalyzer {
+    private static final int DAY_OF_WEEK = 7;
+    private static final int CHRISTMAS_DAY = 25;
+
+    private static Day getDay(int date) {
+        return Arrays.stream(Day.values())
+                .filter(day -> day.getIndex() == date % DAY_OF_WEEK)
+                .findFirst()
+                .orElseThrow();
+    }
+
+    public static boolean isWeekday(int date) {
+        Day targetDay = getDay(date);
+        if (targetDay.compareTo(THURSDAY) <= 0 && targetDay.compareTo(SUNDAY) >= 0) {
+            return true;
+        }
+        return false;
+    }
+
+    public static boolean isWeekend(int date) {
+        Day targetDay = getDay(date);
+        if (targetDay == FRIDAY || targetDay == SATURDAY) {
+            return true;
+        }
+        return false;
+    }
+
+    public static boolean isSpecialDay(int date) {
+        Day targetDay = getDay(date);
+        if (targetDay == SUNDAY || date == CHRISTMAS_DAY) {
+            return true;
+        }
+        return false;
+    }
+}

리뷰: 저는 날짜를 하드코딩해버렸는데,,, DayAnalyzer 클래스 정말 직관적이고 좋은 접근 방식인 것 같습니다 👍👍
@@ -0,0 +1,7 @@
+package christmas.exception;
+
+import java.util.function.Supplier;
+
+public interface ExceptionHandler {
+    <T> T execute(Supplier<T> action, Class<? extends Exception>... exceptions);
+}

리뷰: `Class<? extends Exception>... exceptions`는 어떤 역할의 파라미터인지, 어떻게 활용되는지 여쭤보고 싶습니다!
@@ -0,0 +1,30 @@
+package christmas.constants.event;
+
+public enum BadgeType {
+    NONE("없음", 0),
+    STAR("별", 5_000),
+    TREE("트리", 10_000),
+    SANTA("산타", 20_000);
+
+    private final String name;
+    private final int threshold;
+
+    BadgeType(String name, int threshold) {
+        this.name = name;
+        this.threshold = threshold;
+    }
+
+    public static BadgeType from(int benefitPrice) {
+        BadgeType result = NONE;
+        for (BadgeType badgeType : values()) {
+            if (benefitPrice >= badgeType.threshold) {
+                result = badgeType;
+            }
+        }
+        return result;
+    }
+    
+    public String getName() {
+        return name;
+    }
+}

리뷰: stream으로 최적화 할 수 있을 것 같습니다.
@@ -0,0 +1,13 @@
+package christmas.dto;
+
+import christmas.util.Parser;
+import java.util.List;
+
+public record SingleMenu(String menu, int amount) {
+    private static final String DELIMITER = "-";
+
+    public static SingleMenu create(String singleOrder) {
+        List<String> parsed = Parser.parseToMenu(singleOrder, DELIMITER);
+        return new SingleMenu(parsed.get(0), Parser.parseToAmount(parsed.get(1)));
+    }
+}

리뷰: SingleMenu를 record로 하신 이유가 무엇인가요?
@@ -0,0 +1,38 @@
+package christmas.exception;
+
+import christmas.view.output.OutputView;
+import java.util.Arrays;
+import java.util.function.Supplier;
+
+public class RetryHandler implements ExceptionHandler {
+    private final OutputView outputView;
+
+    public RetryHandler(OutputView outputView) {
+        this.outputView = outputView;
+    }
+
+    @Override
+    @SafeVarargs
+    public final <T> T execute(Supplier<T> action, Class<? extends Exception>... exceptions) {
+        while (true) {
+            try {
+                return action.get();
+            } catch (IllegalArgumentException e) {
+                printException(e, exceptions);
+            }
+        }
+    }
+
+    private void printException(Exception actual, Class<? extends Exception>... exceptions) {
+        if (isExpectedException(actual, exceptions)) {
+            outputView.printError(actual.getMessage());
+            return;
+        }
+        throw new RuntimeException(actual);
+    }
+
+    private boolean isExpectedException(Exception actual, Class<? extends Exception>... exceptions) {
+        return Arrays.stream(exceptions)
+                .anyMatch(exception -> exception.isInstance(actual));
+    }
+}

리뷰: @safevarargs는 왜 사용하신건가요?