ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,52 @@
+package christmas.domain;
+
+import christmas.error.ErrorMessage;
+import java.time.DayOfWeek;
+import java.time.LocalDate;
+import java.util.List;
+
+public class VisitDay {
+
+    private static final int MIN_VISIT_DAY = 1;
+    private static final int MAX_VISIT_DAY = 31;
+    private static final int EVENT_YEAR = 2023;
+    private static final int EVENT_MONTH = 12;
+
+    private final int visitDay;
+
+    public VisitDay(int visitDay) {
+        validate(visitDay);
+        this.visitDay = visitDay;
+    }
+
+    private void validate(int visitDay) {
+        if (invalidDay(visitDay)) {
+            throw new IllegalArgumentException(ErrorMessage.INVALID_VISIT_DAY.getMessage());
+        }
+    }
+
+    private boolean invalidDay(int visitDay) {
+        return visitDay < MIN_VISIT_DAY || visitDay > MAX_VISIT_DAY;
+    }
+
+    public boolean isNotWithinChristmasPeriod(int christmasDDay) {
+        return visitDay < MIN_VISIT_DAY || visitDay > christmasDDay;
+    }
+
+    public int decreaseOneDay() {
+        return visitDay - 1;
+    }
+
+    public boolean isWeekend() {
+        DayOfWeek visitDayOfWeek = LocalDate.of(EVENT_YEAR, EVENT_MONTH, visitDay).getDayOfWeek();
+        return visitDayOfWeek == DayOfWeek.FRIDAY || visitDayOfWeek == DayOfWeek.SATURDAY;
+    }
+
+    public boolean isNotSpecialDay(List<Integer> specialDays) {
+        return !specialDays.contains(visitDay);
+    }
+
+    public int getVisitDay() {
+        return visitDay;
+    }
+}

리뷰: 요청할 때마다 `DayOfWeek`를 만드는데 아니라 객체 생성시에 미리 만들어 놓은 다음에 쓰는건 어떨까요? 어짜피 visitDay는 안바뀌니까요.
@@ -0,0 +1,57 @@
+package christmas.domain.menu;
+
+import christmas.error.ErrorMessage;
+import java.util.Arrays;
+
+public enum Menu {
+
+    MUSHROOM_SOUP("양송이 수프", 6_000, MenuCategory.APPETIZER),
+    TAPAS("타파스", 5_500, MenuCategory.APPETIZER),
+    CAESAR_SALAD("시저샐러드", 8_000, MenuCategory.APPETIZER),
+    T_BONE_STEAK("티본스테이크", 55_000, MenuCategory.MAIN),
+    BARBECUE_RIBS("바비큐립", 54_000, MenuCategory.MAIN),
+    SEAFOOD_PASTA("해산물파스타", 35_000, MenuCategory.MAIN),
+    CHRISTMAS_PASTA("크리스마스파스타", 25_000, MenuCategory.MAIN),
+    CHOCOLATE_CAKE("초코케이크", 15_000, MenuCategory.DESSERT),
+    ICE_CREAM("아이스크림", 5_000, MenuCategory.DESSERT),
+    COKE_ZERO("제로콜라", 3_000, MenuCategory.BEVERAGE),
+    RED_WINE("레드와인", 60_000, MenuCategory.BEVERAGE),
+    CHAMPAGNE("샴페인", 25_000, MenuCategory.BEVERAGE);
+
+    private final String name;
+    private final int price;
+    private final MenuCategory category;
+
+    Menu(String name, int price, MenuCategory category) {
+        this.name = name;
+        this.price = price;
+        this.category = category;
+    }
+
+    public static Menu from(String menuName) {
+        return Arrays.stream(Menu.values())
+                .filter(menu -> menu.name.equals(menuName))
+                .findFirst()
+                .orElseThrow(() -> new IllegalArgumentException(ErrorMessage.INVALID_ORDER.getMessage()));
+    }
+
+    public boolean isMain() {
+        return this.category == MenuCategory.MAIN;
+    }
+
+    public boolean isDessert() {
+        return this.category == MenuCategory.DESSERT;
+    }
+
+    public boolean isNotBeverage() {
+        return this.category != MenuCategory.BEVERAGE;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public int getPrice() {
+        return price;
+    }
+}

리뷰: 객체를 객체답게 사용하신 코드네요!
@@ -0,0 +1,59 @@
+package christmas.domain.order;
+
+import christmas.domain.menu.Menu;
+import christmas.error.ErrorMessage;
+import java.util.Objects;
+
+public class OrderMenu {
+
+    private static final int MIN_QUANTITY = 1;
+    private static final int MAX_QUANTITY = 20;
+
+    private final Menu menu;
+    private final int quantity;
+
+    public OrderMenu(Menu menu, int quantity) {
+        this.menu = menu;
+        if (quantity < MIN_QUANTITY || quantity > MAX_QUANTITY) {
+            throw new IllegalArgumentException(ErrorMessage.INVALID_ORDER.getMessage());
+        }
+        this.quantity = quantity;
+    }
+
+    public boolean isMenuMain() {
+        return menu.isMain();
+    }
+
+    public boolean isMenuDessert() {
+        return menu.isDessert();
+    }
+
+    public int calculatePrice() {
+        return menu.getPrice() * quantity;
+    }
+
+    public boolean isNotBeverage() {
+        return menu.isNotBeverage();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        OrderMenu orderMenu = (OrderMenu) o;
+        return menu == orderMenu.menu;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(menu);
+    }
+
+    public Menu getMenu() {
+        return menu;
+    }
+
+    public int getQuantity() {
+        return quantity;
+    }
+}

리뷰: eqauls()에 대해 공부를 많이 하셨네요! hashCode까지 다시 구현하신거 인상깊습니다!
@@ -0,0 +1,51 @@
+package christmas.service;
+
+import christmas.domain.BenefitDetails;
+import christmas.domain.ExpectedPaymentAmountCalculator;
+import christmas.domain.event.EventBadge;
+import christmas.domain.event.EventBadgeAssigner;
+import christmas.domain.menu.Menu;
+import christmas.domain.order.OrderMenu;
+import christmas.domain.order.OrderMenus;
+import christmas.dto.OrderMenusDto;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class Service {
+
+    private final ExpectedPaymentAmountCalculator expectedPaymentAmountCalculator;
+    private final EventBadgeAssigner eventBadgeAssigner;
+
+    public Service(
+            ExpectedPaymentAmountCalculator expectedPaymentAmountCalculator,
+            EventBadgeAssigner eventBadgeAssigner) {
+        this.expectedPaymentAmountCalculator = expectedPaymentAmountCalculator;
+        this.eventBadgeAssigner = eventBadgeAssigner;
+    }
+
+    public OrderMenus order(OrderMenusDto orderMenusDto) {
+        return new OrderMenus(convertToOrderMenuList(orderMenusDto));
+    }
+
+    private List<OrderMenu> convertToOrderMenuList(OrderMenusDto orderMenusDto) {
+        return orderMenusDto.getMenus().entrySet()
+                .stream()
+                .map(orderMenus -> new OrderMenu(Menu.from(orderMenus.getKey()), orderMenus.getValue()))
+                .collect(Collectors.toList());
+    }
+
+    public int calculateTotalOrderAmount(OrderMenus orderMenus) {
+        return orderMenus.calculateTotalOrderAmount();
+    }
+
+    public int calculateExpectedPaymentAmount(OrderMenus orderMenus, BenefitDetails benefitDetails) {
+        return expectedPaymentAmountCalculator.calculateExpectedPaymentAmount(
+                orderMenus.calculateTotalOrderAmount(),
+                benefitDetails.calculateTotalBenefitAmount()
+        );
+    }
+
+    public EventBadge assignEventBadge(BenefitDetails benefitDetails) {
+        return eventBadgeAssigner.assignEventBadge(benefitDetails.calculateTotalBenefitAmount());
+    }
+}

리뷰: 전반적으로 여러 곳에서 stream을 잘 쓰셔서 드릴 말씀이 없네요;; 추가적으로 이야기만 드리면 stream에서 `( )` 안이 길다면 메서드로 분리하는 것을 추천드립니다.

```java
    private List<OrderMenu> convertToOrderMenuList(OrderMenusDto orderMenusDto) {
        return orderMenusDto.getMenus().entrySet()
                .stream()
                .map(this::convertToOrderMenu)
                .collect(Collectors.toList());
    }

    private OrderMenu convertToOrderMenu(OrderMenuDto orderMenu) {
        return  new OrderMenu(Menu.from(orderMenu.getKey()), orderMenu.getValue())
    }
```
@@ -0,0 +1,35 @@
+package christmas.dto;
+
+import christmas.domain.order.OrderMenu;
+import christmas.domain.order.OrderMenus;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public class OrderMenusDto {
+
+    private final Map<String, Integer> menus;
+
+    private OrderMenusDto(OrderMenus orderMenus) {
+        this.menus = orderMenus.getOrderMenus()
+                .stream()
+                .collect(Collectors.toMap(
+                        orderMenu -> orderMenu.getMenu().getName(),
+                        OrderMenu::getQuantity));
+    }
+
+    private OrderMenusDto(Map<String, Integer> inputOrderMenus) {
+        this.menus = inputOrderMenus;
+    }
+
+    public static OrderMenusDto from(OrderMenus orderMenus) {
+        return new OrderMenusDto(orderMenus);
+    }
+
+    public static OrderMenusDto from(Map<String, Integer> inputOrderMenus) {
+        return new OrderMenusDto(inputOrderMenus);
+    }
+
+    public Map<String, Integer> getMenus() {
+        return menus;
+    }
+}

리뷰: OrderMenuDto 객체를 생성하는 메서드를 해당 클래스 내부에 정적으로 선언하셨군요! 참신한 방법이네요 🫢
저는 재모님 코드로 치면 OrderMenus 객체 내부에 toDto() 메서드로 OrderMenusDto 객체를 반환하게 했는데, 재모님은 어떤 이점을 생각하셔서 이 방법으로 구현하셨는지 궁금합니다! 😊 (저도 다음에 써먹어 보려구요 :-))
@@ -0,0 +1,92 @@
+package constants;
+
+public class Menus {
+
+	public enum Appetizer {
+		
+		BUTTON_MUSHROOM_SOUP("양송이수프",6000),
+		TAPAS("타파스",5500),
+		CAESAR_SALAD("시저샐러드",8000);
+		
+		private final String dish;
+		private final int price;
+
+		Appetizer(String dish,int price) {
+			this.dish=dish;
+			this.price=price;
+		}
+		
+		public String appetizer() {
+			return dish;
+		}
+		public int price() {
+			return price;
+		}
+	}
+	
+	public enum MainMenu {
+		
+		TBONE_STEAK("티본스테이크",55000),
+		BARBECUE_RIP("바비큐립",54000),
+		SEAFOOD_PASTA("해산물파스타",35000),
+		CHRISTRMAS_PASTA("크리스마스파스타",25000);
+		
+		private final String dish;
+		private final int price;
+
+		MainMenu(String dish,int price) {
+			this.dish=dish;
+			this.price=price;
+		}
+		
+		public String main() {
+			return dish;
+		}
+		public int price() {
+			return price;
+		}
+	}
+	
+	public enum Dessert {
+		
+		CHOCOLATE_CAKE("초코케이크",15000),
+		ICECREAM("아이스크림",5000);
+		
+		private final String dish;
+		private final int price;
+
+		Dessert(String dish,int price) {
+			this.dish=dish;
+			this.price=price;
+		}
+		
+		public String dessert() {
+			return dish;
+		}
+		public int price() {
+			return price;
+		}
+	}
+	
+	public enum Beverage {
+		
+		ZERO_COLA("제로콜라",3000),
+		RED_WINE("레드와인",60000),
+		CHAMPAGNE("샴페인",25000);
+		
+		private final String dish;
+		private final int price;
+		
+		Beverage(String dish,int price) {
+			this.dish=dish;
+			this.price=price;
+		}
+		
+		public String beverage() {
+			return dish;
+		}
+		public int price() {
+			return price;
+		}
+	}
+}

리뷰: 열거체마다 중복 코드가 있는 것 같아요! 차라리 열거체에 `MenuType`같은 필드를 추가하여 하나로 통일하는건 어떨까요?
@@ -0,0 +1,25 @@
+package constants;
+
+import creator.ObjectCreator;
+
+public class Constants {
+
+	//input 오류
+	public final static String INVALID_DATE_OUTPUT=ErrorMessages.INVALID_DATE_OUTPUT.detected();
+	public final static String INVALID_MENU_OUTPUT=ErrorMessages.INVALID_MENU_OUTPUT.detected();
+	
+	//할인가정보
+	public final static int D_DAY_DISCOUNT_BASIC=-1000;
+	public final static int D_DAY_DISCOUNT_PER_DAY=-100;
+	public final static int WEEK_DISCOUNT=-2023;
+	public final static int SPECIAL_DISCOUNT=-1000;
+	public final static int DISCOUNT_TYPES_CNT=4;
+	
+	public final static int DAYS_OF_A_WEEK=7;
+	public final static int SUNDAY_METER=3;
+	public final static int SATURDAY_METER=2;
+	public final static int FRIDAY_METER=1;
+	public final static int DATE_OF_CHRISTMAS=25;
+
+	public final static ObjectCreator CREATE=new ObjectCreator();
+}

리뷰: 이 값을 `Constants`에 포함시킨 이유가 있으실까요? 외부에서 `ErrorMessages`를 통해 사용해도 충분할 것 같습니다!
@@ -0,0 +1,40 @@
+package creator;
+
+import data.EventData;
+import features.Controller;
+import features.Input;
+import features.Statistics;
+import features.Validator;
+import view.InputView;
+import view.OutputView;
+
+public class ObjectCreator {
+	
+	public EventData dto() {
+		return new EventData();
+	}
+
+	public Input input() {
+		return new Input();
+	}
+	
+	public Validator valid() {
+		return new Validator();
+	}
+	
+	public InputView reader() {
+		return new InputView();
+	}
+	
+	public OutputView announcer() {
+		return new OutputView();
+	}
+	
+	public Statistics stat(EventData dto) {
+		return new Statistics(dto);
+	}
+	
+	public Controller player() {
+		return new Controller();
+	}
+}

리뷰: 메서드명이 너무 축약된 것 같아요! `create` + 객체명 과 같이 의미가 명확한 네이밍은 어떨까요? (e.g. `cretaeOutputView`)
@@ -0,0 +1,17 @@
+package christmas.domain.menu
+
+import christmas.exception.OrdersException
+
+data class MenuCount(private val count: Int) {
+    init {
+        require(count >= 1) { OrdersException.INVALID.message }
+    }
+
+    fun getCount() = count
+
+    override fun toString(): String = "%,d$UNIT_SUFFIX".format(count)
+
+    companion object {
+        private const val UNIT_SUFFIX = "개"
+    }
+}
\ No newline at end of file

리뷰: val 선언 때문에 따로 private로 제한하지 않아도 값 수정이 불가능할텐데, 혹시 private로 선언한 이유가 따로 있을까요..?!
@@ -0,0 +1,29 @@
+package christmas.domain
+
+import christmas.exception.DecemberDayException
+
+data class DecemberDay(private val day: Int) {
+    init {
+        require(day in DAY_START..DAY_END) { DecemberDayException.INVALID.message }
+    }
+
+    fun hasPassedChristmas() = day > DAY_CHRISTMAS
+    fun getDaySinceStartOfMonth() = day - DAY_START
+    fun isWeekEnd() = day in WEEKENDS
+    fun isSpecialDay() = day in SPECIAL_DAYS
+    override fun toString(): String = "%d${UNIT_SUFFIX}".format(day)
+
+    companion object {
+        private const val DAY_START = 1
+        private const val DAY_END = 31
+        private const val DAY_CHRISTMAS = 25
+        private const val UNIT_SUFFIX = "일"
+        private val WEEKENDS = listOf(1, 2, 8, 9, 15, 16, 22, 23, 29, 30)
+        private val SPECIAL_DAYS = listOf(3, 10, 17, 24, 25, 31)
+
+        fun fromString(input: String): DecemberDay {
+            val day = requireNotNull(input.toIntOrNull()) { DecemberDayException.INVALID.message }
+            return DecemberDay(day)
+        }
+    }
+}
\ No newline at end of file

리뷰: 이부분은 fromString 보다는 checkDay가 좀더 자연스러워 보입니다..!! 개인적으로 fromString을 보면 toString처럼 String 포맷 함수처럼 보여요..!!
@@ -0,0 +1,94 @@
+package christmas.domain
+
+import org.assertj.core.api.Assertions
+import org.junit.jupiter.api.Assertions.*
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.ValueSource
+
+class DecemberDayTest {
+    @ParameterizedTest
+    @ValueSource(ints = [1, 31, 20])
+    fun `객체 생성 - 정상 입력`(input: Int) {
+        // when & then
+        assertDoesNotThrow { DecemberDay(input) }
+    }
+
+    @ParameterizedTest
+    @ValueSource(ints = [-1, 32, 50])
+    fun `객체 생성 - 범위 초과 예외 처리`(input: Int) {
+        // when & then
+        Assertions.assertThatExceptionOfType(IllegalArgumentException::class.java)
+            .isThrownBy { DecemberDay(input) }
+            .withMessage("[ERROR] 유효하지 않은 날짜입니다. 다시 입력해 주세요.")
+    }
+
+    @Test
+    fun `문자열로 객체 생성 - 정상 입력`() {
+        // given
+        val input = "30"
+        // when
+        val result = DecemberDay.fromString(input)
+        // then
+        assertEquals(DecemberDay(30), result)
+    }
+
+    @Test
+    fun `문자열로 객체 생성 - 빈 값 입력 예외 처리`() {
+        // given
+        val input = ""
+        // when & then
+        Assertions.assertThatExceptionOfType(IllegalArgumentException::class.java)
+            .isThrownBy { DecemberDay.fromString(input) }
+            .withMessage("[ERROR] 유효하지 않은 날짜입니다. 다시 입력해 주세요.")
+    }
+
+    @ParameterizedTest
+    @ValueSource(strings = ["", "abcd", "2147483648", "-2147483649"])
+    fun `문자열로 객체 생성 - Int 범위 초과 입력 예외 처리`(input: String) {
+        // when & then
+        Assertions.assertThatExceptionOfType(IllegalArgumentException::class.java)
+            .isThrownBy { DecemberDay.fromString(input) }
+            .withMessage("[ERROR] 유효하지 않은 날짜입니다. 다시 입력해 주세요.")
+    }
+
+    @Test
+    fun `크리스마스를 지났는지 확인`() {
+        // given
+        val input = DecemberDay(26)
+        // when
+        val result = input.hasPassedChristmas()
+        // then
+        assertEquals(true, result)
+    }
+
+    @Test
+    fun `달의 시작일로부터 몇일이 지났는지 계산`() {
+        // given
+        val input = DecemberDay(17)
+        // when
+        val result = input.getDaySinceStartOfMonth()
+        // then
+        assertEquals(16, result)
+    }
+
+    @Test
+    fun `주말인지 확인`() {
+        // given
+        val input = DecemberDay(17)
+        // when
+        val result = input.isWeekEnd()
+        // then
+        assertEquals(false, result)
+    }
+
+    @Test
+    fun `별이 있는 날인지 확인`() {
+        // given
+        val input = DecemberDay(17)
+        // when
+        val result = input.isSpecialDay()
+        // then
+        assertEquals(true, result)
+    }
+}
\ No newline at end of file

리뷰: @ValueSource 보니깐 빈값 예외처리도 같이 테스트하는거 같은데, 따로 함수를 둔 이유가 혹시 있을까요..?
@@ -0,0 +1,14 @@
+package christmas.domain
+
+data class Money(private val amount: Int) {
+
+    fun toNegative() = Money(-amount)
+    fun isGreaterOrEqual(other: Money): Boolean = amount >= other.amount
+    fun plus(other: Money): Money = Money(amount + other.amount)
+    fun multiplyBy(input: Int): Money = Money(amount * input)
+    override fun toString(): String = "%,d$UNIT_SUFFIX".format(amount)
+
+    companion object {
+        private const val UNIT_SUFFIX = "원"
+    }
+}
\ No newline at end of file

리뷰: 사실 저는 이번 미션을 하면서 가장 헷갈렸던 점은 **어디까지를 객체로** 봐야하는가 였습니다!
코드리뷰를 하면서 확장성과 유지보수를 위한 관심사 분리는 필수지만, 오히려 과도한 객체 분리는 오히려 코드 가독성을 해친다는 생각도 많이 들었습니다. multiplyBy의 경우, 함수의 뜻만 봤을 때는 곱셈 연산인 것을 알 수 있지만 혹시나 따로 함수로 빼둔 이유가 있을까봐 코드를 찾는데 시간을 소요했었습니다! 개인적으로는 객체를 조금 줄여도 되지 않을까 싶어요...!!!
너무 잘하셔서 이런 리뷰밖에 못드리네요 죄송합니다😂😂
@@ -0,0 +1,17 @@
+package christmas.domain.menu
+
+import christmas.exception.OrdersException
+
+data class MenuCount(private val count: Int) {
+    init {
+        require(count >= 1) { OrdersException.INVALID.message }
+    }
+
+    fun getCount() = count
+
+    override fun toString(): String = "%,d$UNIT_SUFFIX".format(count)
+
+    companion object {
+        private const val UNIT_SUFFIX = "개"
+    }
+}
\ No newline at end of file

리뷰: 사실 저도 이 부분이 많이 고민됐어요 
자동으로 생성되는 getter 메서드를 이용할지, 혹은 직접 이렇게 함수로 구현할지 고민이 됐는데 일단 private로 통일을 하고 메서드를 제공하는게 가독성이 좋을것 같아서 시도해봤어요!
@@ -0,0 +1,29 @@
+package christmas.domain
+
+import christmas.exception.DecemberDayException
+
+data class DecemberDay(private val day: Int) {
+    init {
+        require(day in DAY_START..DAY_END) { DecemberDayException.INVALID.message }
+    }
+
+    fun hasPassedChristmas() = day > DAY_CHRISTMAS
+    fun getDaySinceStartOfMonth() = day - DAY_START
+    fun isWeekEnd() = day in WEEKENDS
+    fun isSpecialDay() = day in SPECIAL_DAYS
+    override fun toString(): String = "%d${UNIT_SUFFIX}".format(day)
+
+    companion object {
+        private const val DAY_START = 1
+        private const val DAY_END = 31
+        private const val DAY_CHRISTMAS = 25
+        private const val UNIT_SUFFIX = "일"
+        private val WEEKENDS = listOf(1, 2, 8, 9, 15, 16, 22, 23, 29, 30)
+        private val SPECIAL_DAYS = listOf(3, 10, 17, 24, 25, 31)
+
+        fun fromString(input: String): DecemberDay {
+            val day = requireNotNull(input.toIntOrNull()) { DecemberDayException.INVALID.message }
+            return DecemberDay(day)
+        }
+    }
+}
\ No newline at end of file

리뷰: 아하 언뜻 보면 String 포맷 함수랑 헷갈릴 수 있겠네요! 좋은 의견 감사합니다
원래는 객체를 생성할 때 문자열로부터 생성한다는 의미를 줄려고 했는데 다소 애매한 표현이 될 수 있겠네요🙈
@@ -0,0 +1,94 @@
+package christmas.domain
+
+import org.assertj.core.api.Assertions
+import org.junit.jupiter.api.Assertions.*
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.ValueSource
+
+class DecemberDayTest {
+    @ParameterizedTest
+    @ValueSource(ints = [1, 31, 20])
+    fun `객체 생성 - 정상 입력`(input: Int) {
+        // when & then
+        assertDoesNotThrow { DecemberDay(input) }
+    }
+
+    @ParameterizedTest
+    @ValueSource(ints = [-1, 32, 50])
+    fun `객체 생성 - 범위 초과 예외 처리`(input: Int) {
+        // when & then
+        Assertions.assertThatExceptionOfType(IllegalArgumentException::class.java)
+            .isThrownBy { DecemberDay(input) }
+            .withMessage("[ERROR] 유효하지 않은 날짜입니다. 다시 입력해 주세요.")
+    }
+
+    @Test
+    fun `문자열로 객체 생성 - 정상 입력`() {
+        // given
+        val input = "30"
+        // when
+        val result = DecemberDay.fromString(input)
+        // then
+        assertEquals(DecemberDay(30), result)
+    }
+
+    @Test
+    fun `문자열로 객체 생성 - 빈 값 입력 예외 처리`() {
+        // given
+        val input = ""
+        // when & then
+        Assertions.assertThatExceptionOfType(IllegalArgumentException::class.java)
+            .isThrownBy { DecemberDay.fromString(input) }
+            .withMessage("[ERROR] 유효하지 않은 날짜입니다. 다시 입력해 주세요.")
+    }
+
+    @ParameterizedTest
+    @ValueSource(strings = ["", "abcd", "2147483648", "-2147483649"])
+    fun `문자열로 객체 생성 - Int 범위 초과 입력 예외 처리`(input: String) {
+        // when & then
+        Assertions.assertThatExceptionOfType(IllegalArgumentException::class.java)
+            .isThrownBy { DecemberDay.fromString(input) }
+            .withMessage("[ERROR] 유효하지 않은 날짜입니다. 다시 입력해 주세요.")
+    }
+
+    @Test
+    fun `크리스마스를 지났는지 확인`() {
+        // given
+        val input = DecemberDay(26)
+        // when
+        val result = input.hasPassedChristmas()
+        // then
+        assertEquals(true, result)
+    }
+
+    @Test
+    fun `달의 시작일로부터 몇일이 지났는지 계산`() {
+        // given
+        val input = DecemberDay(17)
+        // when
+        val result = input.getDaySinceStartOfMonth()
+        // then
+        assertEquals(16, result)
+    }
+
+    @Test
+    fun `주말인지 확인`() {
+        // given
+        val input = DecemberDay(17)
+        // when
+        val result = input.isWeekEnd()
+        // then
+        assertEquals(false, result)
+    }
+
+    @Test
+    fun `별이 있는 날인지 확인`() {
+        // given
+        val input = DecemberDay(17)
+        // when
+        val result = input.isSpecialDay()
+        // then
+        assertEquals(true, result)
+    }
+}
\ No newline at end of file

리뷰: 이 부분은 놓치고 있었네요..!! 아래는 실수입니다!
@@ -0,0 +1,14 @@
+package christmas.domain
+
+data class Money(private val amount: Int) {
+
+    fun toNegative() = Money(-amount)
+    fun isGreaterOrEqual(other: Money): Boolean = amount >= other.amount
+    fun plus(other: Money): Money = Money(amount + other.amount)
+    fun multiplyBy(input: Int): Money = Money(amount * input)
+    override fun toString(): String = "%,d$UNIT_SUFFIX".format(amount)
+
+    companion object {
+        private const val UNIT_SUFFIX = "원"
+    }
+}
\ No newline at end of file

리뷰: 아니에요!! 혼자 볼 때는 익숙해져 보이지 않았던 부분들도 보이게 되고 다양한 시점에서 고민할 부분이 많아졌어요😄
소중한 리뷰 남겨주셔서 감사합니다!!
@@ -0,0 +1,40 @@
+const food = {
+  APPETIZER: ['양송이수프', '타파스', '시저샐러드'],
+  MAIN: ['티본스테이크', '바비큐립', '해산물파스타', '크리스마스파스타'],
+  DESSERT: ['초코케이크', '아이스크림'],
+  BEVERAGE: ['제로콜라', '레드와인', '샴페인'],
+};
+
+const foodCost = {
+  양송이수프: 6000,
+  타파스: 5500,
+  시저샐러드: 8000,
+  티본스테이크: 55000,
+  바비큐립: 54000,
+  해산물파스타: 35000,
+  크리스마스파스타: 25000,
+  초코케이크: 15000,
+  아이스크림: 5000,
+  제로콜라: 3000,
+  레드와인: 60000,
+  샴페인: 25000,
+};
+
+const menuBoard = [
+  '양송이수프',
+  '타파스',
+  '시저샐러드',
+  '티본스테이크',
+  '바비큐립',
+  '해산물파스타',
+  '크리스마스파스타',
+  '초코케이크',
+  '아이스크림',
+  '제로콜라',
+  '레드와인',
+  '샴페인',
+];
+
+const specialDay = [3, 10, 17, 24, 25, 31];
+
+export { food, foodCost, menuBoard, specialDay };

리뷰: 코스 따로 가격 따로 데이터를 만드셨군요! 
저도 처음엔 이렇게 각각 만들려고 했는데, 이렇게 하면 나중에 수정해야 할 때 같은 음식을 여러번 여러곳에서 수정해줘야 할 것 같다는 생각이 들어서, 
전 한 음식에 코스, 가격 정보를 담아서 음식들을 Map 형태로 데이터했습니다! 
이 외에도 다른 더 좋은 방법이 있으시다면 공유 부탁드려요! ㅎㅎ
@@ -0,0 +1,32 @@
+const uiConstants = {
+  DATE: 'DATE',
+  MENU: 'MENU',
+  GREETING_MESSAGE: '안녕하세요! 우테코 식당 12월 이벤트 플래너입니다.',
+  WHEN_VISIT_MESSAGE:
+    '12월 중 식당 예상 방문 날짜는 언제인가요? (숫자만 입력해 주세요!)\n',
+  ORDER_MENU_MESSAGE:
+    '주문하실 메뉴를 메뉴와 개수를 알려 주세요. (e.g. 해산물파스타-2,레드와인-1,초코케이크-1)\n',
+  PREVIEW_EVENT_MESSAGE: '일에 우테코 식당에서 받을 이벤트 혜택 미리 보기!',
+  DECEMBER: '12월 ',
+  ORDER_MENU: '<주문 메뉴>',
+  COUNT: '개',
+  TOTAL_COST: '<할인 전 총주문 금액>',
+  MONEY_UNIT: '원',
+  FREE_GIFT_MENU: '<증정 메뉴>',
+  FREE_GIFT: '샴페인',
+  NOTING: '없음',
+  EVENT_DETAIL: '<혜택 내역>',
+  DDAY_DISCOUNT: '크리스마스 디데이 할인: ',
+  WEEKDAY_DISCOUNT: '평일 할인: ',
+  WEEKEND_DISCOUNT: '주말 할인: ',
+  SPECIAL_DISCOUNT: '특별 할인: ',
+  FREE_GIFT_EVENT_DISCOUNT: '증정 이벤트: ',
+  TOTAL_EVENT_COST: '<총혜택 금액>',
+  EXPECTATION_COST: '<할인 후 예상 결제 금액>',
+  BADGE: '<12월 이벤트 배지>',
+  STAR: '스타',
+  TREE: '트리',
+  SANTA: '산타',
+};
+
+export default uiConstants;

리뷰: 전 상수들은 모두 constants 라는 파일에 몰아 넣었는데, 
이렇게 구분해놓으니까 훨씬 깔끔하고 가독성도 좋아보이는군요!!