ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,79 @@
+package christmas.model
+
+import christmas.utils.Constant.BADGE_SANTA
+import christmas.utils.Constant.BADGE_STAR
+import christmas.utils.Constant.BADGE_TREE
+import christmas.utils.Constant.CHAMPAGNE_CONDITION_AMOUNT
+import christmas.utils.Constant.CHRISTMAS_DAY_DISCOUNT_AMOUNT
+import christmas.utils.Constant.DAILY_DISCOUNT_AMOUNT
+import christmas.utils.Constant.FIRST_DAY_DISCOUNT_AMOUNT
+import christmas.utils.Constant.EMPTY
+import christmas.utils.Constant.NONE
+import christmas.utils.Constant.ONE_DAY_DECREASE
+import christmas.utils.Constant.SANTA_MIN
+import christmas.utils.Constant.STAR_MAX
+import christmas.utils.Constant.STAR_MIN
+import christmas.utils.Constant.TREE_MAX
+import christmas.utils.Constant.TREE_MIN
+import christmas.utils.Constant.WEEK_DISCOUNT_AMOUNT
+
+class DecemberEvent {
+    fun applyTheDayDiscount(date: Int): Int {
+        val dailyDiscount = (date - ONE_DAY_DECREASE) * DAILY_DISCOUNT_AMOUNT
+
+        return FIRST_DAY_DISCOUNT_AMOUNT + dailyDiscount
+    }
+
+    fun applyWeekdayDiscount(orderMenu: List<OrderItems>): Int {
+        var dessertMenuCount = EMPTY
+
+        for (item in orderMenu) {
+            val increaseCount = increaseDessertMenuCount(item.getMenuName(), item.getQuantity())
+            dessertMenuCount += increaseCount
+        }
+
+        return dessertMenuCount * WEEK_DISCOUNT_AMOUNT
+    }
+
+    private fun increaseDessertMenuCount(menuName: String, quantity: Int): Int {
+        if (menuName == StoreMenu.CHOCO_CAKE.menuName || menuName == StoreMenu.ICE_CREAM.menuName)
+            return quantity
+
+        return EMPTY
+    }
+
+
+    fun applyWeekendDiscount(orderMenu: List<OrderItems>): Int {
+        var mainMenuCount = EMPTY
+
+        for (item in orderMenu) {
+            val increaseCount = increaseMainMenuCount(item.getMenuName(), item.getQuantity())
+            mainMenuCount += increaseCount
+        }
+
+        return mainMenuCount * WEEK_DISCOUNT_AMOUNT
+    }
+
+    private fun increaseMainMenuCount(menuName: String, quantity: Int): Int {
+        if (menuName == StoreMenu.T_BONE_STEAK.menuName || menuName == StoreMenu.BBQ_RIBS.menuName ||
+            menuName == StoreMenu.SEAFOOD_PASTA.menuName || menuName == StoreMenu.CHRISTMAS_PASTA.menuName
+        )
+            return quantity
+
+        return EMPTY
+    }
+
+    fun applySpecialDiscount(): Int = CHRISTMAS_DAY_DISCOUNT_AMOUNT
+
+    fun presentChampagne(totalOrderAmount: Int): Boolean = totalOrderAmount >= CHAMPAGNE_CONDITION_AMOUNT
+
+    fun assignBadge(benefitAmount: Int): String {
+        when {
+            benefitAmount in STAR_MIN..STAR_MAX -> return BADGE_STAR
+            benefitAmount in TREE_MIN..TREE_MAX -> return BADGE_TREE
+            benefitAmount >= SANTA_MIN -> return BADGE_SANTA
+        }
+
+        return NONE
+    }
+}
\ No newline at end of file

리뷰: 아 동작 순서에 따라서 또 다르게 작성될 수 있네요. 제시 해주신 코드처럼 낮은 금액보다는 높은 금액부터 검사하는 것이 처리 과정을  확실히 줄일 수 있겠다는 생각이 들어요! 많은 부분에서 도움을 받게 된 것 같아요.. 정말 감사합니다!
@@ -0,0 +1,56 @@
+package christmas
+
+import christmas.model.DecemberEvent
+import christmas.model.OrderItems
+import org.junit.jupiter.api.Assertions
+import org.junit.jupiter.api.Test
+
+class DecemberEventTest {
+    private val decemberEvent: DecemberEvent = DecemberEvent()
+
+    @Test
+    fun `디데이 할인 금액 테스트`() {
+        val discountAmount = decemberEvent.applyTheDayDiscount(25)
+
+        Assertions.assertTrue(discountAmount == 3400)
+    }
+
+    @Test
+    fun `평일 할인 금액 테스트`() {
+        val discountAmount = decemberEvent.applyWeekdayDiscount(listOf(OrderItems("초코케이크", 2), OrderItems("해산물파스타", 3)))
+
+        Assertions.assertTrue(discountAmount == 4046)
+    }
+
+    @Test
+    fun `주말 할인 금액 테스트`() {
+        val discountAmount = decemberEvent.applyWeekendDiscount(listOf(OrderItems("초코케이크", 2), OrderItems("해산물파스타", 3)))
+
+        Assertions.assertTrue(discountAmount == 6069)
+    }
+
+    @Test
+    fun `특별 할인 금액 테스트`() {
+        val discountAmount = decemberEvent.applySpecialDiscount()
+
+        Assertions.assertTrue(discountAmount == 1000)
+    }
+
+    @Test
+    fun `샴페인 증정 테스트`() {
+        Assertions.assertTrue(decemberEvent.presentChampagne(130_000))
+    }
+
+    @Test
+    fun `배지 부여 테스트`() {
+        val noneBadge = decemberEvent.assignBadge(0)
+        val starBadge = decemberEvent.assignBadge(5000)
+        val treeBadge = decemberEvent.assignBadge(10000)
+        val santaBadge = decemberEvent.assignBadge(200000)
+
+        Assertions.assertTrue(noneBadge == "없음")
+        Assertions.assertTrue(starBadge == "별")
+        Assertions.assertTrue(treeBadge == "트리")
+        Assertions.assertTrue(santaBadge == "산타")
+    }
+}
\ No newline at end of file

리뷰: 테스트 코드에 대해서는 아직 어려움을 느끼고 있기 때문에 많은 학습이 필요한 것 같아요. 자세한 설명과 링크까지..리뷰 정말 감사합니다!!
@@ -0,0 +1,79 @@
+package christmas.model
+
+import christmas.utils.Constant.BADGE_SANTA
+import christmas.utils.Constant.BADGE_STAR
+import christmas.utils.Constant.BADGE_TREE
+import christmas.utils.Constant.CHAMPAGNE_CONDITION_AMOUNT
+import christmas.utils.Constant.CHRISTMAS_DAY_DISCOUNT_AMOUNT
+import christmas.utils.Constant.DAILY_DISCOUNT_AMOUNT
+import christmas.utils.Constant.FIRST_DAY_DISCOUNT_AMOUNT
+import christmas.utils.Constant.EMPTY
+import christmas.utils.Constant.NONE
+import christmas.utils.Constant.ONE_DAY_DECREASE
+import christmas.utils.Constant.SANTA_MIN
+import christmas.utils.Constant.STAR_MAX
+import christmas.utils.Constant.STAR_MIN
+import christmas.utils.Constant.TREE_MAX
+import christmas.utils.Constant.TREE_MIN
+import christmas.utils.Constant.WEEK_DISCOUNT_AMOUNT
+
+class DecemberEvent {
+    fun applyTheDayDiscount(date: Int): Int {
+        val dailyDiscount = (date - ONE_DAY_DECREASE) * DAILY_DISCOUNT_AMOUNT
+
+        return FIRST_DAY_DISCOUNT_AMOUNT + dailyDiscount
+    }
+
+    fun applyWeekdayDiscount(orderMenu: List<OrderItems>): Int {
+        var dessertMenuCount = EMPTY
+
+        for (item in orderMenu) {
+            val increaseCount = increaseDessertMenuCount(item.getMenuName(), item.getQuantity())
+            dessertMenuCount += increaseCount
+        }
+
+        return dessertMenuCount * WEEK_DISCOUNT_AMOUNT
+    }
+
+    private fun increaseDessertMenuCount(menuName: String, quantity: Int): Int {
+        if (menuName == StoreMenu.CHOCO_CAKE.menuName || menuName == StoreMenu.ICE_CREAM.menuName)
+            return quantity
+
+        return EMPTY
+    }
+
+
+    fun applyWeekendDiscount(orderMenu: List<OrderItems>): Int {
+        var mainMenuCount = EMPTY
+
+        for (item in orderMenu) {
+            val increaseCount = increaseMainMenuCount(item.getMenuName(), item.getQuantity())
+            mainMenuCount += increaseCount
+        }
+
+        return mainMenuCount * WEEK_DISCOUNT_AMOUNT
+    }
+
+    private fun increaseMainMenuCount(menuName: String, quantity: Int): Int {
+        if (menuName == StoreMenu.T_BONE_STEAK.menuName || menuName == StoreMenu.BBQ_RIBS.menuName ||
+            menuName == StoreMenu.SEAFOOD_PASTA.menuName || menuName == StoreMenu.CHRISTMAS_PASTA.menuName
+        )
+            return quantity
+
+        return EMPTY
+    }
+
+    fun applySpecialDiscount(): Int = CHRISTMAS_DAY_DISCOUNT_AMOUNT
+
+    fun presentChampagne(totalOrderAmount: Int): Boolean = totalOrderAmount >= CHAMPAGNE_CONDITION_AMOUNT
+
+    fun assignBadge(benefitAmount: Int): String {
+        when {
+            benefitAmount in STAR_MIN..STAR_MAX -> return BADGE_STAR
+            benefitAmount in TREE_MIN..TREE_MAX -> return BADGE_TREE
+            benefitAmount >= SANTA_MIN -> return BADGE_SANTA
+        }
+
+        return NONE
+    }
+}
\ No newline at end of file

리뷰: 헉 저도모르게 when문 안에 들어있는 else는 if-else랑 다르다고 생각을 하고 있었습니다..! 제가 놓친 부분을 짚어주셔서 감사하네요!
@@ -0,0 +1,39 @@
+import { getProductListProps } from './product';
+
+interface Props extends getProductListProps {
+  baseUrl: string;
+}
+
+const buildGetProductListURL = ({
+  baseUrl,
+  category,
+  page,
+  size,
+  order,
+}: Props) => {
+  const params = [];
+
+  if (category) {
+    params.push(`category=${encodeURIComponent(category)}`);
+  }
+  if (page !== undefined) {
+    params.push(`page=${page}`);
+  }
+  if (size !== undefined) {
+    params.push(`size=${size}`);
+  }
+
+  if (order === 'desc') {
+    params.push(`sort=price%2C${order}&sort=name`);
+  } else {
+    params.push('sort=price&sort=name');
+  }
+
+  if (params.length > 0) {
+    baseUrl += `?${params.join('&')}`;
+  }
+
+  return baseUrl;
+};
+
+export default buildGetProductListURL;

리뷰: api 관리한거 멋있어요... 👍
step2 때 저도 이렇게해보고싶네요!!
@@ -0,0 +1,47 @@
+import React from 'react';
+import styled from '@emotion/styled';
+import { theme } from '@/style/theme.style';
+
+type ButtonThemeType = 'dark' | 'light' | 'disabled';
+
+const BUTTON_THEME = {
+  dark: {
+    backgroundColor: theme.color.black,
+    color: theme.color.white,
+  },
+  light: {
+    backgroundColor: theme.color.lightGray,
+    color: theme.color.black,
+  },
+  disabled: {
+    backgroundColor: theme.color.gray,
+    color: theme.color.white,
+  },
+};
+
+interface Props extends React.ButtonHTMLAttributes<HTMLButtonElement> {
+  children?: React.ReactNode;
+  $theme?: ButtonThemeType;
+  $width?: string;
+  $height?: string;
+  $borderRadius?: string;
+}
+
+const BaseButton = ({ children, ...rest }: Props) => {
+  return <StyledButton {...rest}>{children}</StyledButton>;
+};
+
+export default BaseButton;
+
+const StyledButton = styled.button<Props>`
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  background-color: ${({ $theme }) =>
+    $theme ? BUTTON_THEME[$theme].backgroundColor : 'transparent'};
+  color: ${({ $theme }) => ($theme ? BUTTON_THEME[$theme].color : 'inherit')};
+  width: ${({ $width }) => ($width ? $width : 'fit-content')};
+  height: ${({ $height }) => ($height ? $height : 'fit-content')};
+  border-radius: ${({ $borderRadius }) =>
+    $borderRadius ? $borderRadius : '4px'};
+`;

리뷰: styled 이렇게 한 파일에 적는것도 가독성 좋네요
저도 emotion 썼는데 ui 작업하면서 계속 와리가리했던 경험이있어서..
@@ -0,0 +1,49 @@
+package com.codesquad.rocket.config;
+
+import java.io.IOException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpHeaders;
+import org.springframework.stereotype.Component;
+
+@Component
+public class SimpleCorsFilter implements Filter {
+
+    private final Logger logger = LoggerFactory.getLogger(SimpleCorsFilter.class);
+
+    public SimpleCorsFilter() {
+        logger.info("SimpleCORSFilter init");
+    }
+
+    @Override
+    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws
+        IOException, ServletException {
+
+        HttpServletRequest request = (HttpServletRequest) req;
+        HttpServletResponse response = (HttpServletResponse) res;
+        response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, request.getHeader("Origin"));
+        response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
+        response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "POST, GET, OPTIONS, DELETE, PUT, PATCH");
+        response.setHeader(HttpHeaders.ACCESS_CONTROL_MAX_AGE, "3600");
+        response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "Content-Type, Accept, X-Requested-With, remember-me, Authorization");
+        chain.doFilter(req, res);
+    }
+
+    @Override
+    public void init(FilterConfig filterConfig) {
+    }
+
+    @Override
+    public void destroy() {
+    }
+}

리뷰: HttpHeaders에 static final한 형태로 헤더에 지정하는 텍스트가 이미 존재한다. 이를 사용하여 리팩토링한다.
@@ -0,0 +1,35 @@
+package christmas.domain.discount.policy;
+
+import christmas.domain.common.Date;
+import christmas.domain.common.Money;
+import christmas.domain.discount.DateDiscountPolicy;
+import christmas.domain.discount.DiscountResult;
+
+public class ChristmasDDayDiscountPolicy extends DateDiscountPolicy {
+
+    private static final Date DISCOUNTABLE_STANDARD_DATE = Date.from(25);
+    private static final Money BASE_DISCOUNT_AMOUNT = Money.from(-1_000);
+    private static final Money ADD_AMOUNT = Money.from(-100);
+    private static final String POLICY_NAME = "크리스마스 디데이 할인";
+
+    @Override
+    protected boolean isSatisfied(Date date) {
+        return !date.isLaterThan(DISCOUNTABLE_STANDARD_DATE);
+    }
+
+    @Override
+    protected DiscountResult bulidDiscountResult(Date date) {
+        Money discountAmount = calculateDiscountAmount(date);
+
+        return new DiscountResult(POLICY_NAME, discountAmount);
+    }
+
+    private Money calculateDiscountAmount(Date date) {
+        Money discountAmount = BASE_DISCOUNT_AMOUNT;
+        for (int day = 1; day < date.getDayOfMonth(); day++) {
+            discountAmount = discountAmount.add(ADD_AMOUNT);
+        }
+
+        return discountAmount;
+    }
+}

리뷰: 이렇게 하면, 매 반복마다 getDayOfMonth 메소드를 호출하기 때문에 성능면에서 좋지 않을거 같아요! 지금은 getDayOfMonth에 복잡한 로직이 없어 크게 차이는 없을거 같지만, 위와 같은 코드는 성능 이슈가 발생할 수 있다고 생각합니다. 해당 메소드의 값을 변수로 할당해 사용하는 것은 어떤가요? 😀
@@ -0,0 +1,55 @@
+package christmas.domain.menu;
+
+import christmas.domain.common.Date;
+import christmas.domain.common.Money;
+import christmas.domain.discount.DateDiscountPolicy;
+import christmas.domain.discount.DiscountResult;
+import java.util.Objects;
+
+public abstract class AbstractMenu implements Menu {
+
+    protected final MenuName name;
+    protected final Money price;
+    protected final DateDiscountPolicy policy;
+
+    protected AbstractMenu(MenuName name, Money price, DateDiscountPolicy policy) {
+        this.name = name;
+        this.price = price;
+        this.policy = policy;
+    }
+
+    @Override
+    public Money calculatePriceWith(int quantity) {
+        return price.multiply(quantity);
+    }
+
+    @Override
+    public DiscountResult calculateDiscountBenefitWith(Date date, int quantity) {
+        DiscountResult menuDiscountResult = policy.discount(date);
+
+        return new DiscountResult(menuDiscountResult.policyName(), menuDiscountResult.amount().multiply(quantity));
+    }
+
+    @Override
+    public boolean isDiscountable(Date date) {
+        return policy.isDiscountable(date);
+    }
+
+    @Override
+    public String getName() {
+        return name.getName();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        AbstractMenu that = (AbstractMenu) o;
+        return Objects.equals(name, that.name) && Objects.equals(price, that.price);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(name, price);
+    }
+}

리뷰: 오잉 추상 클래스로 선언한 이유가 궁금해요. 제가 봤을 땐 추상 메소드가 없는거 같은데 맞을까요? 추상 메소드가 없는데 추상 클래스로 선언한 이유가 궁금합니다!
@@ -0,0 +1,35 @@
+package christmas.domain.discount.policy;
+
+import christmas.domain.common.Date;
+import christmas.domain.common.Money;
+import christmas.domain.discount.DateDiscountPolicy;
+import christmas.domain.discount.DiscountResult;
+
+public class ChristmasDDayDiscountPolicy extends DateDiscountPolicy {
+
+    private static final Date DISCOUNTABLE_STANDARD_DATE = Date.from(25);
+    private static final Money BASE_DISCOUNT_AMOUNT = Money.from(-1_000);
+    private static final Money ADD_AMOUNT = Money.from(-100);
+    private static final String POLICY_NAME = "크리스마스 디데이 할인";
+
+    @Override
+    protected boolean isSatisfied(Date date) {
+        return !date.isLaterThan(DISCOUNTABLE_STANDARD_DATE);
+    }
+
+    @Override
+    protected DiscountResult bulidDiscountResult(Date date) {
+        Money discountAmount = calculateDiscountAmount(date);
+
+        return new DiscountResult(POLICY_NAME, discountAmount);
+    }
+
+    private Money calculateDiscountAmount(Date date) {
+        Money discountAmount = BASE_DISCOUNT_AMOUNT;
+        for (int day = 1; day < date.getDayOfMonth(); day++) {
+            discountAmount = discountAmount.add(ADD_AMOUNT);
+        }
+
+        return discountAmount;
+    }
+}

리뷰: 불과 몇달전에 책에서 봤던 내용이네요.. 이제 절대 까먹을리 없을 것 같네요! 감사합니다 🙇🏻‍♂️
@@ -0,0 +1,55 @@
+package christmas.domain.menu;
+
+import christmas.domain.common.Date;
+import christmas.domain.common.Money;
+import christmas.domain.discount.DateDiscountPolicy;
+import christmas.domain.discount.DiscountResult;
+import java.util.Objects;
+
+public abstract class AbstractMenu implements Menu {
+
+    protected final MenuName name;
+    protected final Money price;
+    protected final DateDiscountPolicy policy;
+
+    protected AbstractMenu(MenuName name, Money price, DateDiscountPolicy policy) {
+        this.name = name;
+        this.price = price;
+        this.policy = policy;
+    }
+
+    @Override
+    public Money calculatePriceWith(int quantity) {
+        return price.multiply(quantity);
+    }
+
+    @Override
+    public DiscountResult calculateDiscountBenefitWith(Date date, int quantity) {
+        DiscountResult menuDiscountResult = policy.discount(date);
+
+        return new DiscountResult(menuDiscountResult.policyName(), menuDiscountResult.amount().multiply(quantity));
+    }
+
+    @Override
+    public boolean isDiscountable(Date date) {
+        return policy.isDiscountable(date);
+    }
+
+    @Override
+    public String getName() {
+        return name.getName();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        AbstractMenu that = (AbstractMenu) o;
+        return Objects.equals(name, that.name) && Objects.equals(price, that.price);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(name, price);
+    }
+}

리뷰: 직접 생성하는 것은 막고, 공통 로직을 재사용하기 위해서 abstract로 만들었어요! 하지만, 하늘님처럼 타입 기반으로 메뉴를 분류하면 이런 코드는 불필요할 것 같네요!
@@ -0,0 +1,77 @@
+package christmas.domain.common;
+
+import static christmas.global.util.ObjectUtil.requireNonNull;
+
+import christmas.global.util.DateUtil;
+import java.util.Objects;
+
+public class Date {
+
+    public static final int BASE_MONTH = 12;
+    private static final int BASE_YEAR = 2023;
+    private static final int DAY_OF_MONTH_MIN_RANGE = 1;
+    private static final int DAY_OF_MONTH_MAX_RANGE = 31;
+    private static final String INVALID_DAY_OF_MONTH_RANGE_MESSAGE =
+            "날짜는 " + DAY_OF_MONTH_MIN_RANGE + "과 " + DAY_OF_MONTH_MAX_RANGE + " 사이의 수를 입력해주세요.";
+    private static final String UNKNOWN_DATE_MESSAGE = "알 수 없는 날짜입니다.";
+
+    private final int dayOfMonth;
+
+    private Date(int dayOfMonth) {
+        this.dayOfMonth = dayOfMonth;
+    }
+
+    public static Date from(int dayOfMonth) {
+        checkDayOfMonthRange(dayOfMonth);
+
+        return new Date(dayOfMonth);
+    }
+
+    private static void checkDayOfMonthRange(int dayOfMonth) {
+        checkDayOfMonthMinRange(dayOfMonth);
+        checkDayOfMonthMaxRange(dayOfMonth);
+    }
+
+    private static void checkDayOfMonthMinRange(int dayOfMonth) {
+        if (dayOfMonth < DAY_OF_MONTH_MIN_RANGE) {
+            throw new IllegalArgumentException(INVALID_DAY_OF_MONTH_RANGE_MESSAGE);
+        }
+    }
+
+    private static void checkDayOfMonthMaxRange(int dayOfMonth) {
+        if (dayOfMonth > DAY_OF_MONTH_MAX_RANGE) {
+            throw new IllegalArgumentException(INVALID_DAY_OF_MONTH_RANGE_MESSAGE);
+        }
+    }
+
+    public boolean isLaterThan(Date target) {
+        requireNonNull(target, UNKNOWN_DATE_MESSAGE);
+
+        return this.dayOfMonth > target.dayOfMonth;
+    }
+
+    public boolean isWeekDay() {
+        return DateUtil.isWeekday(BASE_YEAR, BASE_MONTH, dayOfMonth);
+    }
+
+    public boolean isWeekend() {
+        return DateUtil.isWeekend(BASE_YEAR, BASE_MONTH, dayOfMonth);
+    }
+
+    public int getDayOfMonth() {
+        return dayOfMonth;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        Date date = (Date) o;
+        return dayOfMonth == date.dayOfMonth;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(dayOfMonth);
+    }
+}

리뷰: > 방문할 날짜는 1 이상 31 이하의 숫자로만 입력받아 주세요.
> 1 이상 31 이하의 숫자가 아닌 경우, "[ERROR] 유효하지 않은 날짜입니다. 다시 입력해 주세요."라는 에러 메시지를 보여 주세요.

요구사항에서 명시한 예외 메시지 말고 이러한 예외 메시지를 선택한 이유는 구체성을 높이기 위함이였나요 ? 😄
@@ -0,0 +1,63 @@
+package christmas.domain.common;
+
+import static christmas.global.util.ObjectUtil.requireNonNull;
+
+import java.util.Objects;
+
+public class Money {
+
+    private static final int ZERO_MONEY_AMOUNT = 0;
+    private static final String UNKNOWN_MONEY_MESSAGE = "알 수 없는 돈과 해당 연산을 수행할 수 없습니다.";
+    public static final String CURRENCY = "원";
+
+    private final long amount;
+
+    private Money(long amount) {
+        this.amount = amount;
+    }
+
+    public static Money from(long amount) {
+        return new Money(amount);
+    }
+
+    public static Money zero() {
+        return new Money(ZERO_MONEY_AMOUNT);
+    }
+
+    public Money add(Money target) {
+        requireNonNull(target, UNKNOWN_MONEY_MESSAGE);
+
+        return new Money(this.amount + target.amount);
+    }
+
+    public Money subtract(Money target) {
+        requireNonNull(target, UNKNOWN_MONEY_MESSAGE);
+
+        return new Money(this.amount - target.amount);
+    }
+
+    public Money multiply(int multiplier) {
+        return new Money(this.amount * multiplier);
+    }
+
+    public boolean isGreaterThanEqual(int amount) {
+        return this.amount >= amount;
+    }
+
+    public long getAmount() {
+        return amount;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        Money money = (Money) o;
+        return amount == money.amount;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(amount);
+    }
+}

리뷰: 불변 객체로 활용한 점이 좋네요 ~ 😄
@@ -0,0 +1,30 @@
+package christmas.domain.discount;
+
+import static christmas.global.util.ObjectUtil.requireNonNull;
+
+import christmas.domain.common.Date;
+
+public abstract class DateDiscountPolicy {
+
+    private static final String UNKNOWN_DATE_MESSAGE = "알 수 없는 날짜입니다.";
+    private static final String CANT_APPLY_DISCOUNT_MESSAGE = "할인을 적용할 수 없습니다.";
+
+    public DiscountResult discount(Date date) {
+        requireNonNull(date, UNKNOWN_DATE_MESSAGE);
+        if (!isDiscountable(date)) {
+            throw new IllegalStateException(CANT_APPLY_DISCOUNT_MESSAGE);
+        }
+
+        return bulidDiscountResult(date);
+    }
+
+    public boolean isDiscountable(Date date) {
+        requireNonNull(date, UNKNOWN_DATE_MESSAGE);
+
+        return isSatisfied(date);
+    }
+
+    protected abstract boolean isSatisfied(Date date);
+
+    protected abstract DiscountResult bulidDiscountResult(Date date);
+}

리뷰: 하위 자식 구현체가 상위 부모 객체의 로직에 영향을 주기 위해서 protected abstract로 선언한 것이 인상적이네요~ 😄
@@ -0,0 +1,18 @@
+package christmas.domain.discount.policy;
+
+import christmas.domain.common.Date;
+import christmas.domain.discount.DateDiscountPolicy;
+import christmas.domain.discount.DiscountResult;
+
+public class NoneDiscountPolicy extends DateDiscountPolicy {
+
+    @Override
+    protected boolean isSatisfied(Date date) {
+        return false;
+    }
+
+    @Override
+    protected DiscountResult bulidDiscountResult(Date date) {
+        return null;
+    }
+}

리뷰: 해당 Policy는 어떤 이유 때문에 구성하셨나요 ~ ? 😄