ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,55 @@
+package christmas.domain.customer;
+
+import christmas.constant.NumConstant;
+import christmas.validator.DateValidator;
+
+import java.time.DayOfWeek;
+import java.time.LocalDate;
+
+public class VisitDate {
+    private final int visitDate;
+
+    private VisitDate(int visitDate) {
+        DateValidator.validateVisitDateBound(visitDate);
+        this.visitDate = visitDate;
+    }
+
+    public static VisitDate from(int visitDate) {
+        return new VisitDate(visitDate);
+    }
+
+    public DayOfWeek getDayOfWeek() {
+        LocalDate date = LocalDate.of(
+                NumConstant.THIS_YEAR.getValue(),
+                NumConstant.THIS_MONTH.getValue(),
+                visitDate
+        );
+        return date.getDayOfWeek();
+    }
+
+    public boolean isVisitDateWeekend() {
+        DayOfWeek dayOfWeek = getDayOfWeek();
+        if (dayOfWeek == DayOfWeek.FRIDAY || dayOfWeek == DayOfWeek.SATURDAY) {
+            return true;
+        }
+        return false;
+    }
+
+    public boolean isOverChristmas() {
+        if(visitDate > NumConstant.CHRISTMAS_DAY.getValue()) {
+            return true;
+        }
+        return false;
+    }
+
+    public int calculateDdayEventApplyDay() {
+        return visitDate - 1;
+    }
+
+    public boolean isSpecialDay() {
+        if (visitDate == NumConstant.CHRISTMAS_DAY.getValue() || getDayOfWeek() == DayOfWeek.SUNDAY) {
+            return true;
+        }
+        return false;
+    }
+}

리뷰: Dday이벤트가 최소 1000원으로 시작하고 1일 이후부터 100원씩 붙게되는데 
100원씩 붙게되는 날의 개수를 구하는 메서드입니다!
1이 방문 날짜인 경우 0을 return하므로 100원 적용이 안되는 겁니다!
@@ -0,0 +1,68 @@
+package christmas.service;
+
+import christmas.constant.Badge;
+import christmas.domain.customer.CustomerInfo;
+import christmas.domain.event.EventStatus;
+import christmas.handler.EventHandler;
+
+import java.util.Collections;
+import java.util.Map;
+
+public class EventService {
+    private final EventHandler eventHandler;
+    private final CustomerInfo customerInfo;
+
+    private EventService(CustomerInfo customerInfo) {
+        eventHandler = EventHandler.from(EventStatus.createInstance());
+        this.customerInfo = customerInfo;
+    }
+
+    public static EventService of(CustomerInfo customerInfo) {
+        return new EventService(customerInfo);
+    }
+
+    public void applyDiscount() {
+        eventHandler.applyDiscount(customerInfo);
+    }
+
+    public int getTotalBenefitAmount() {
+        if (customerInfo.isOverMinAmount()) {
+            return eventHandler.getTotalBenefitAmount();
+        }
+        return 0;
+    }
+
+    public Map<String, Integer> getBenefits() {
+        if (customerInfo.isOverMinAmount()) {
+            return eventHandler.getBenefits();
+        }
+        return Collections.emptyMap();
+    }
+
+    public Map<String, Integer> getOrderList() {
+        return customerInfo.getOrderList();
+    }
+
+    public String getGiveaway() {
+        if (customerInfo.isApplyGiveawayEvent()) {
+            return "샴페인 1개";
+        }
+        return "없음";
+    }
+
+    public int getTotalOrderAmount() {
+        return customerInfo.getTotalOrderAmount();
+    }
+
+    public int getTotalDiscountAmount() {
+        if (customerInfo.isOverMinAmount()) {
+            return eventHandler.getTotalDiscountAmount();
+        }
+        return 0;
+    }
+
+    public String getBadge() {
+        return Badge.getBadge(eventHandler.getTotalBenefitAmount());
+    }
+
+}

리뷰: 리팩토링 대상이었는데 정신없이 개발하다보니 놓친 부분이네요 지적 감사합니다!
@@ -0,0 +1,31 @@
+package christmas.constant;
+
+public enum Badge {
+    MISS("없음"),
+    STAR("별"),
+    TREE("트리"),
+    SANTA("산타");
+    private String name;
+
+    Badge(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public static String getBadge(int totalBenefitAmount) {
+        totalBenefitAmount = Math.abs(totalBenefitAmount);
+        if(totalBenefitAmount >= NumConstant.STAR_BADGE_AMOUNT.getValue() && totalBenefitAmount < NumConstant.TREE_BADGE_AMOUNT.getValue()) {
+            return STAR.name;
+        }
+        if(totalBenefitAmount >= NumConstant.TREE_BADGE_AMOUNT.getValue() && totalBenefitAmount < NumConstant.SANTA_BADGE_AMOUNT.getValue()) {
+            return TREE.name;
+        }
+        if(totalBenefitAmount >= NumConstant.SANTA_BADGE_AMOUNT.getValue()) {
+            return SANTA.name;
+        }
+        return MISS.name;
+    }
+}

리뷰: 오 저도 이 코드를 보고나서 깔끔하다고 느꼈었는데.. 감사합니다!!
@@ -0,0 +1,38 @@
+package christmas.domain.order;
+
+import christmas.constant.MenuType;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public class Orders {
+    private final List<Order> orders;
+
+    private Orders(List<Order> orders) {
+        this.orders = orders;
+    }
+
+    public static Orders from(List<Order> orders) {
+        return new Orders(orders);
+    }
+
+    public int getTotalOrderAmount() {
+        return orders.stream()
+                .map(Order::getOrderAmount)
+                .reduce(0, Integer::sum);
+    }
+
+    public int getOrderNumByMenuType(MenuType menuType) {
+        return orders.stream()
+                .filter(order -> order.isMatchMenuType(menuType))
+                .mapToInt(Order::getMenuNum)
+                .sum();
+    }
+
+    public Map<String, Integer> getOrderList() {
+        return orders.stream()
+                .collect(Collectors.toMap(Order::getMenuName, Order::getMenuNum));
+    }
+}

리뷰: 메서드 네이밍에서 여러 주문 정보를 가져오는 좋은 이름이 뭐가 있을까 하다가 나온게 이건데 네이밍 어렵네요 ㅠㅠ
@@ -0,0 +1,48 @@
+package christmas.domain.menu;
+
+import christmas.constant.MenuType;
+
+import java.util.*;
+
+public class Menus {
+    private final List<Menu> menus;
+    private static Menus instance;
+
+    private Menus(List<Menu> menus) {
+        this.menus = menus;
+    }
+
+    public static Menus getInstance() {
+        if(instance == null) {
+            instance = new Menus(initialMenus());
+        }
+        return instance;
+    }
+
+    private static List<Menu> initialMenus() {
+        List<Menu> initialMenus = new ArrayList<>();
+
+        initialMenus.add(Menu.of("양송이수프", 6000, MenuType.APPETIZER));
+        initialMenus.add(Menu.of("타파스", 5500, MenuType.APPETIZER));
+        initialMenus.add(Menu.of("시저샐러드", 8000, MenuType.APPETIZER));
+        initialMenus.add(Menu.of("티본스테이크", 55000, MenuType.MAIN));
+        initialMenus.add(Menu.of("바비큐립", 54000, MenuType.MAIN));
+        initialMenus.add(Menu.of("해산물파스타", 35000, MenuType.MAIN));
+        initialMenus.add(Menu.of("크리스마스파스타", 25000, MenuType.MAIN));
+        initialMenus.add(Menu.of("초코케이크", 15000, MenuType.DESSERT));
+        initialMenus.add(Menu.of("아이스크림", 5000, MenuType.DESSERT));
+        initialMenus.add(Menu.of("제로콜라", 3000, MenuType.DRINK));
+        initialMenus.add(Menu.of("레드와인", 60000, MenuType.DRINK));
+        initialMenus.add(Menu.of("샴페인", 25000, MenuType.DRINK));
+
+        return initialMenus;
+    }
+
+    public Optional<Menu> getMenuByMenuName(String menuName) {
+        return menus.stream()
+                .filter(menu -> menu.getMenuName().equals(menuName))
+                .findFirst();
+
+    }
+
+}

리뷰: Menus는 쉽게말해 메뉴판으로 사용된 객체입니다! 런타임 시점에 변경될일이 없을듯하여 싱글톤 방식으로 선언하였습니다. 이에 대해 어떻게 생각하시나요?
@@ -0,0 +1,77 @@
+export const validInputDates = ["0", "1", "31", "32", "-1", "-31"];
+
+export const invalidInputDates = [
+  "-0",
+  "2.5",
+  "foo",
+  "bar",
+  "😊",
+  "12.0",
+  "12.000000000000004",
+  "12345678910987654321",
+];
+
+export const validDomainDates = [1, 2, 11, 15, 23, 31];
+
+export const invalidDomainDates = [32, 0];
+
+export const validInputMenus = [
+  "-1",
+  "티본스테이크-1",
+  "양송이수프-1,티본스테이크-0",
+  "티본스테이크-30",
+  "레드와인-1",
+  "레드와인-1,블루와인-1,퍼플와인-1,그린와인-1",
+  "foo-1",
+];
+
+export const invalidInputMenus = [
+  "티본스테이크1",
+  "양송이수프-1,티본-스테이크-1",
+  "시저샐러드-1,티본스테이크--1",
+  "티본스테이크-foo",
+  "티본스테이크-1.0",
+  "티본스테이크-1,티본스테이크-1",
+  "티본스테이크-1.000000000000004",
+  "티본스테이크-1.시저샐러드-1",
+  "티본스테이크-1시저샐러드-1",
+  "티본스테이크-1,,시저샐러드-1",
+  "티본스테이크-1,-,시저샐러드-1",
+];
+
+export const valiidDomainMenus = [
+  [["티본스테이크-1"]],
+  [
+    [
+      "양송이수프-3",
+      "타파스-2",
+      "시저샐러드-3",
+      "티본스테이크-1",
+      "해산물파스타-2",
+      "아이스크림-3",
+      "제로콜라-6",
+    ],
+  ],
+];
+
+export const invalidDomainMenus = [
+  [["-1"]],
+  [["티본스테이크-1", "-1", "시저샐러드-1"]],
+  [["foo-1"]],
+  [["티본스태이크-1"]],
+  [["티본스테이크-0"]],
+  [["샴페인-1", "제로콜라-2", "레드와인-1"]],
+  [["제로콜라-15"]],
+  [["티본스테이크-10", "타파스-10", "해산물파스타-1"]],
+  [
+    [
+      "양송이수프-3",
+      "타파스-2",
+      "시저샐러드-5",
+      "티본스테이크-3",
+      "해산물파스타-2",
+      "아이스크림-5",
+      "제로콜라-1",
+    ],
+  ],
+];

리뷰: 테스트에서 쓰이는 데이터들을 한 곳에 모아놓으셨네요. 테스트 케이스가 많아지면 많아질수록 테스트 코드 자체가 길어지는데, 이런 방법을 사용하면 방지할 수 있겠어요!

그런데 이 방식을 채택하면 현재 테스트하는 케이스가 무엇인지 테스트 코드에 직접적으로 드러나지 않아서, 어떤 값이 입력되고 그에 대한 결과 값은 무엇인지 알기가 어려운 것 같아요. 또 테스트 내용이 바뀐다면, 테스트 케이스 파일로 넘어가서 테스트 케이스를 변경해야 할 수도 있을 것은데, 관리하기 어렵지 않을까 싶어요.

어떤 이유에서 이 방법을 채택하셨는지가 궁금합니다!
@@ -0,0 +1,17 @@
+import { EOL as LINE_SEPARATOR } from "os";
+
+export const COMMON_STRINGS = {
+  LINE_SEPARATOR,
+  EMPTY: "",
+  WHITE_SPACE: " ",
+  COLON: ":",
+  DASH: "-",
+  COMMA: ",",
+};
+
+export const CATEGORY_DICT = {
+  appetizer: "appetizer",
+  main: "main",
+  dessert: "dessert",
+  drink: "drink",
+};

리뷰: 이 부분을 다른 common 파일에 작성하신 이유가 있나요? domain에서 사용하고 있는데, domain에 직접 작성한다거나, 'category'라는 상수 파일을 따로 만든다거나 하지 않고 여기에 작성하신 이유가 궁금해요. Enum같은 개념이라 따로 빼신 걸까요?
@@ -0,0 +1,5 @@
+export { default as DOMAIN } from "./domain.js";
+export { default as INPUT } from "./input.js";
+export { default as OUTPUT } from "./output.js";
+export { default as ERROR } from "./error.js";
+export { default as VALIDATION } from "./validation.js";

리뷰: 상수를 선언한 파일에서도 `export default`를 해주고 있는데, 그런 상수 파일들을 감싸는 파일을 사용하는 이유가 궁금해요. 어떤 이점 때문에 이런 방식을 채택하신 건가요?
@@ -0,0 +1,27 @@
+import { ItemCalc } from "../utils/Calc/index.js";
+import { DOMAIN } from "../Constants/index.js";
+
+class BasicCalculator {
+  /** @param {MainOrder} mainOrder  */
+  static originalTotal(mainOrder) {
+    const nestedMenus = mainOrder.getMenus();
+    const result = Object.values(nestedMenus)
+      .map((menus) => BasicCalculator.calcMenus(menus));
+    return ItemCalc.sumArray(result);
+  }
+
+  /** @param {{[menu: string]: number}} menus  */
+  static calcMenus(menus) {
+    const result = Object.entries(menus)
+      .map(([k, v]) => DOMAIN.MENU_DICT[k].price * v);
+    return ItemCalc.sumArray(result);
+  }
+
+  /** @param {MainOrder} mainOrder  */
+  static calcEventFlag(mainOrder) {
+    const originalTotal = BasicCalculator.originalTotal(mainOrder);
+    return Boolean(DOMAIN.EVENT_THRESHOLD_INCLUSIVE <= originalTotal);
+  }
+}
+
+export default BasicCalculator;

리뷰: `key`, `value`에도 이름을 붙이면 더 이해하기 쉬울 것 같아요!
@@ -0,0 +1,27 @@
+import { ItemCalc } from "../utils/Calc/index.js";
+import { DOMAIN } from "../Constants/index.js";
+
+class BasicCalculator {
+  /** @param {MainOrder} mainOrder  */
+  static originalTotal(mainOrder) {
+    const nestedMenus = mainOrder.getMenus();
+    const result = Object.values(nestedMenus)
+      .map((menus) => BasicCalculator.calcMenus(menus));
+    return ItemCalc.sumArray(result);
+  }
+
+  /** @param {{[menu: string]: number}} menus  */
+  static calcMenus(menus) {
+    const result = Object.entries(menus)
+      .map(([k, v]) => DOMAIN.MENU_DICT[k].price * v);
+    return ItemCalc.sumArray(result);
+  }
+
+  /** @param {MainOrder} mainOrder  */
+  static calcEventFlag(mainOrder) {
+    const originalTotal = BasicCalculator.originalTotal(mainOrder);
+    return Boolean(DOMAIN.EVENT_THRESHOLD_INCLUSIVE <= originalTotal);
+  }
+}
+
+export default BasicCalculator;

리뷰: `Boolean()`을 사용하지 않아도 결과가 같은데 제거해도 되지 않을까요?
@@ -0,0 +1,34 @@
+import { DomainValidator } from "../utils/Validator/index.js";
+import { DomainParser } from "../utils/Parser/index.js";
+
+class MainOrder {
+  /** @type {number}  */
+  #date;
+
+  /** @type {{appetizer: {}, main: {}, dessert:{}, drink: {}}}  */
+  #menus;
+
+  /** @param {number} date  */
+  constructor(date) {
+    DomainValidator.date(date);
+    this.#date = date;
+  }
+
+  getDate() {
+    return this.#date;
+  }
+
+  /** @param {[string]} rawOrder  */
+  setMenus(rawOrder) {
+    if (this.#menus) throw new Error();
+    DomainValidator.menus(rawOrder);
+    const parsedMenu = DomainParser.menus(rawOrder);
+    this.#menus = parsedMenu;
+  }
+
+  getMenus() {
+    return this.#menus;
+  }
+}
+
+export default MainOrder;

리뷰: 메뉴를 카테고리별로 나누어서 저장하는 게 편리하고 좋은 것 같아요 👍
@@ -0,0 +1,77 @@
+export const validInputDates = ["0", "1", "31", "32", "-1", "-31"];
+
+export const invalidInputDates = [
+  "-0",
+  "2.5",
+  "foo",
+  "bar",
+  "😊",
+  "12.0",
+  "12.000000000000004",
+  "12345678910987654321",
+];
+
+export const validDomainDates = [1, 2, 11, 15, 23, 31];
+
+export const invalidDomainDates = [32, 0];
+
+export const validInputMenus = [
+  "-1",
+  "티본스테이크-1",
+  "양송이수프-1,티본스테이크-0",
+  "티본스테이크-30",
+  "레드와인-1",
+  "레드와인-1,블루와인-1,퍼플와인-1,그린와인-1",
+  "foo-1",
+];
+
+export const invalidInputMenus = [
+  "티본스테이크1",
+  "양송이수프-1,티본-스테이크-1",
+  "시저샐러드-1,티본스테이크--1",
+  "티본스테이크-foo",
+  "티본스테이크-1.0",
+  "티본스테이크-1,티본스테이크-1",
+  "티본스테이크-1.000000000000004",
+  "티본스테이크-1.시저샐러드-1",
+  "티본스테이크-1시저샐러드-1",
+  "티본스테이크-1,,시저샐러드-1",
+  "티본스테이크-1,-,시저샐러드-1",
+];
+
+export const valiidDomainMenus = [
+  [["티본스테이크-1"]],
+  [
+    [
+      "양송이수프-3",
+      "타파스-2",
+      "시저샐러드-3",
+      "티본스테이크-1",
+      "해산물파스타-2",
+      "아이스크림-3",
+      "제로콜라-6",
+    ],
+  ],
+];
+
+export const invalidDomainMenus = [
+  [["-1"]],
+  [["티본스테이크-1", "-1", "시저샐러드-1"]],
+  [["foo-1"]],
+  [["티본스태이크-1"]],
+  [["티본스테이크-0"]],
+  [["샴페인-1", "제로콜라-2", "레드와인-1"]],
+  [["제로콜라-15"]],
+  [["티본스테이크-10", "타파스-10", "해산물파스타-1"]],
+  [
+    [
+      "양송이수프-3",
+      "타파스-2",
+      "시저샐러드-5",
+      "티본스테이크-3",
+      "해산물파스타-2",
+      "아이스크림-5",
+      "제로콜라-1",
+    ],
+  ],
+];

리뷰: 앗 특별히 리뷰를 막 받아야겠다는 마음으로 리뷰를 남긴 것은 아니지만 와주셔서 너무 감사합니다. 

우선 `static`만 쓰는 메서드에 대해서 말씀드리겠습니다. 사실 저는 이번 우테코가 클래스를 구현해서 프로그래밍하는 것이 처음인데요... 미션 유틸의 구조에서 `static`을 쓰는 것을 보고 일단은 `static` 메서드를 중심으로 쓰게 되었어요. 왜 객체보다 클래스를 선호하는지 물어보신다면... 일단은 메서드를 호출할 때 아무래도 대문자를 쓰고 싶기도 하고(클래스, 생성자가 아닌 경우 대문자를 쓰는 건 자바스크립트 코드 컨벤션에 위배되니까), 또 객체의 경우에 아무래도 런타임에서 조작될 수 있다는 생각이 들어요. 클래스라고 더 안전한 건 아니겠지만... 아무래도 그냥 우테코에서 쓰니까 이유가 있겠거니 한 부분이 큽니다.

덧붙여서 `static` 메서드를 쓰는 이유는.. 아무래도 인스턴스를 안 만들고 싶다는 생각이 큰 것 같네요. 인스턴스를 생성시키고 '결과'를 중간 상태로 관리하게 될 경우에, 주문이 바뀌거나 방문 일정을 바꾼다거나 하는 조작을 했을 경우에 중간 결과까지 바꾸어야 하는데 그게 조금 번거로울 것 같다는 생각에 인스턴스를 갖지 않는 클래스를 많이 만든 것 같습니다. 

충분한 대답은 아니겠지만 대충은 이렇네요 😊
@@ -0,0 +1,77 @@
+export const validInputDates = ["0", "1", "31", "32", "-1", "-31"];
+
+export const invalidInputDates = [
+  "-0",
+  "2.5",
+  "foo",
+  "bar",
+  "😊",
+  "12.0",
+  "12.000000000000004",
+  "12345678910987654321",
+];
+
+export const validDomainDates = [1, 2, 11, 15, 23, 31];
+
+export const invalidDomainDates = [32, 0];
+
+export const validInputMenus = [
+  "-1",
+  "티본스테이크-1",
+  "양송이수프-1,티본스테이크-0",
+  "티본스테이크-30",
+  "레드와인-1",
+  "레드와인-1,블루와인-1,퍼플와인-1,그린와인-1",
+  "foo-1",
+];
+
+export const invalidInputMenus = [
+  "티본스테이크1",
+  "양송이수프-1,티본-스테이크-1",
+  "시저샐러드-1,티본스테이크--1",
+  "티본스테이크-foo",
+  "티본스테이크-1.0",
+  "티본스테이크-1,티본스테이크-1",
+  "티본스테이크-1.000000000000004",
+  "티본스테이크-1.시저샐러드-1",
+  "티본스테이크-1시저샐러드-1",
+  "티본스테이크-1,,시저샐러드-1",
+  "티본스테이크-1,-,시저샐러드-1",
+];
+
+export const valiidDomainMenus = [
+  [["티본스테이크-1"]],
+  [
+    [
+      "양송이수프-3",
+      "타파스-2",
+      "시저샐러드-3",
+      "티본스테이크-1",
+      "해산물파스타-2",
+      "아이스크림-3",
+      "제로콜라-6",
+    ],
+  ],
+];
+
+export const invalidDomainMenus = [
+  [["-1"]],
+  [["티본스테이크-1", "-1", "시저샐러드-1"]],
+  [["foo-1"]],
+  [["티본스태이크-1"]],
+  [["티본스테이크-0"]],
+  [["샴페인-1", "제로콜라-2", "레드와인-1"]],
+  [["제로콜라-15"]],
+  [["티본스테이크-10", "타파스-10", "해산물파스타-1"]],
+  [
+    [
+      "양송이수프-3",
+      "타파스-2",
+      "시저샐러드-5",
+      "티본스테이크-3",
+      "해산물파스타-2",
+      "아이스크림-5",
+      "제로콜라-1",
+    ],
+  ],
+];

리뷰: > 테스트에서 쓰이는 데이터들을 한 곳에 모아놓으셨네요. 테스트 케이스가 많아지면 많아질수록 테스트 코드 자체가 길어지는데, 이런 방법을 사용하면 방지할 수 있겠어요!
> 
> 그런데 이 방식을 채택하면 현재 테스트하는 케이스가 무엇인지 테스트 코드에 직접적으로 드러나지 않아서, 어떤 값이 입력되고 그에 대한 결과 값은 무엇인지 알기가 어려운 것 같아요. 또 테스트 내용이 바뀐다면, 테스트 케이스 파일로 넘어가서 테스트 케이스를 변경해야 할 수도 있을 것은데, 관리하기 어렵지 않을까 싶어요.
> 
> 어떤 이유에서 이 방법을 채택하셨는지가 궁금합니다!

테스트 케이스의 경우에는 사실 저도 3주차까진 이런 방법을 쓰지는 않았는데요.. 4주차에는 하나의 케이스가 여러 시나리오(평일, 주말, 특별 등)에 적용되어야 할 경우가 많더라고요. 이런 경우에 굳이 메뉴를 매번 쓰고 예상되는 결과를 쓰는 게 참 불편했습니다. 또 결과값이어야 하는 것이 다음에는 중간 인자가 되기도 하고요.

그리고 사실 처음에 테스트 코드를 짜고 메서드를 구현하는 방식으로 개발을 해봤는데요. 예상했던 중간 인자들이 자꾸 바뀌더라고요. 그러니까 사실 처음 테스트 코드를 짤 당시에, `총 혜택 금액`을 구하는 테스트의 인자는 `총 할인 금액`과 `reward` 객체였는데요... 중간에 파라미터가 바뀌니까 테스트 코드 전체를 엎어야 하더라고요. 그래서 결국 테스트 되는 중간 인자를 생각하지 말고 입력과 그에 해당하는 중간, 최종 결과를 모두 객체에 몰아서 관리하고 테스트가 바뀔 때 중간 중간 인자를 바꾸자는 생각으로 이런 구현이 되게 되었어요.

만약 유지 보수의 관점에서 생각해본다면 중간 인자를 좀 수정하고, jest 옵션에 --verbose를 넣어서 테스트를 하는 중간에 무엇이 테스트되는지 볼 수 있을 것 같기도 하네요.

음... 사실 개발 중간까지는 저한테는 굉장히 유지 보수에 편리한 코드였는데요(테스트 인자가 바뀌어도 점표기법으로 접근하는 필드만 바꾸면 되니까요) 다 완성하고 보니 다음 사람에게는 상당히 불친절한 코드가 되었네요. 

어떻게 하는 게 좋을지 저도 고민이 됩니다. uraflower님께서는 어떻게 생각하시나요?
@@ -0,0 +1,17 @@
+import { EOL as LINE_SEPARATOR } from "os";
+
+export const COMMON_STRINGS = {
+  LINE_SEPARATOR,
+  EMPTY: "",
+  WHITE_SPACE: " ",
+  COLON: ":",
+  DASH: "-",
+  COMMA: ",",
+};
+
+export const CATEGORY_DICT = {
+  appetizer: "appetizer",
+  main: "main",
+  dessert: "dessert",
+  drink: "drink",
+};

리뷰: 사실 validation에서도 사용되기에 domain에서 쓰던 것을 따로 빼버렸습니다. 한편으로는 이 녀석이 도메인적 성격을 갖고 있는 것은 분명하지만 또 그 자체로는 도메인이 아니기도 해서(이름이 도메인 지식이라기에는 애매하니까요)  따로 분류하고 싶다는 생각이 더 들기도 했습니다. 물론 common으로 뺀 것이 좋은 일이었는지는 모르겠지만요.

Enum같은 경우는 제가 사실 잘 모르겠지만, 알아보고 덧붙여야 할 내용이 있다는 생각이 들면 더 남기도록 하겠습니다.
@@ -0,0 +1,5 @@
+export { default as DOMAIN } from "./domain.js";
+export { default as INPUT } from "./input.js";
+export { default as OUTPUT } from "./output.js";
+export { default as ERROR } from "./error.js";
+export { default as VALIDATION } from "./validation.js";

리뷰: 실제로 그럴 일은 거의 없겠지만 `VALIDATION`, `DOMAIN`, `INPUT`, 그리고 `OUTPUT`을 동시에 임포트해야 하는 상황을 고려한 부분입니다.

```javascript
import DOMAIN from "../Constants/domain.js";
import VALIDATION from "../Constants/validation.js";
import INPUT from "../Constants/input.js";
import OUTPUT from "../Constants/output.js";
```
위와 같이 상수 `import`에 4줄을 사용해야 하는 점이 별로 마음에 들지 않아서 `index.js`를 통해 다시 내보내는 구조를 취한 것이고, 특별히 어떤 이유나 이점이 크진 않았다고 생각합니다.
@@ -0,0 +1,27 @@
+import { ItemCalc } from "../utils/Calc/index.js";
+import { DOMAIN } from "../Constants/index.js";
+
+class BasicCalculator {
+  /** @param {MainOrder} mainOrder  */
+  static originalTotal(mainOrder) {
+    const nestedMenus = mainOrder.getMenus();
+    const result = Object.values(nestedMenus)
+      .map((menus) => BasicCalculator.calcMenus(menus));
+    return ItemCalc.sumArray(result);
+  }
+
+  /** @param {{[menu: string]: number}} menus  */
+  static calcMenus(menus) {
+    const result = Object.entries(menus)
+      .map(([k, v]) => DOMAIN.MENU_DICT[k].price * v);
+    return ItemCalc.sumArray(result);
+  }
+
+  /** @param {MainOrder} mainOrder  */
+  static calcEventFlag(mainOrder) {
+    const originalTotal = BasicCalculator.originalTotal(mainOrder);
+    return Boolean(DOMAIN.EVENT_THRESHOLD_INCLUSIVE <= originalTotal);
+  }
+}
+
+export default BasicCalculator;

리뷰: 사실 이 녀석은 사실 `ItemCalc`라고 따로 확실히 유틸적인 성격인 계산 함수들을 빼놓은 모듈에 있었는데, 메뉴의 가격을 본다는 점이 확실히 도메인이라 이쪽으로 이사를 오게 되었습니다. 옮기는 과정에서 조금 부주의했나봐요. 말씀해주셔서 감사합니다.
@@ -0,0 +1,27 @@
+import { ItemCalc } from "../utils/Calc/index.js";
+import { DOMAIN } from "../Constants/index.js";
+
+class BasicCalculator {
+  /** @param {MainOrder} mainOrder  */
+  static originalTotal(mainOrder) {
+    const nestedMenus = mainOrder.getMenus();
+    const result = Object.values(nestedMenus)
+      .map((menus) => BasicCalculator.calcMenus(menus));
+    return ItemCalc.sumArray(result);
+  }
+
+  /** @param {{[menu: string]: number}} menus  */
+  static calcMenus(menus) {
+    const result = Object.entries(menus)
+      .map(([k, v]) => DOMAIN.MENU_DICT[k].price * v);
+    return ItemCalc.sumArray(result);
+  }
+
+  /** @param {MainOrder} mainOrder  */
+  static calcEventFlag(mainOrder) {
+    const originalTotal = BasicCalculator.originalTotal(mainOrder);
+    return Boolean(DOMAIN.EVENT_THRESHOLD_INCLUSIVE <= originalTotal);
+  }
+}
+
+export default BasicCalculator;

리뷰: 사실 저는 이런 경우 그냥 개인적인 이유로 `Boolean()`을 쓰는 것을 좋아합니다. 생각해보면 중간에 안 넣은 것도 많이 있긴 할 텐데, 식으로 리턴하기보다는 값으로 리턴하는 것이 뭔가 직관적으로 맞는 것 같다는 느낌이 든달까요. 그냥 개인적으로 안정감이 드는 부분입니다. 자바스크립트 특성 상, 정의되지 않은 변수여도 어떻게든 비교해서 반환해줄 거니까 불안한 마음이 드는 걸까요? 아무래도 그렇게 하게 됩니다.
@@ -0,0 +1,75 @@
+// eslint-disable-next-line no-unused-vars
+import MainOrder from "./MainOrder.js";
+import BasicCalculator from "./BasicCalculator.js";
+import { DayCalc, ItemCalc } from "../utils/Calc/index.js";
+import { DOMAIN } from "../Constants/index.js";
+
+class DiscountCalculator {
+  // 상속 메서드(논리적)
+  /** @param {MainOrder} mainOrder  */
+  static #calcEventFlag(mainOrder) {
+    return BasicCalculator.calcEventFlag(mainOrder);
+  }
+
+  // 고유 메서드
+  /** @param {MainOrder} mainOrder  */
+  static discountInfo(mainOrder) {
+    const christmas = DiscountCalculator.christmasDiscount(mainOrder);
+    const weekday = DiscountCalculator.weekdayDiscount(mainOrder);
+    const weekend = DiscountCalculator.weekendDiscount(mainOrder);
+    const special = DiscountCalculator.specialDiscount(mainOrder);
+
+    return {
+      christmas, weekday, weekend, special,
+    };
+  }
+
+  /** @param {MainOrder} mainOrder  */
+  static weekdayDiscount(mainOrder) {
+    const eventFlag = DiscountCalculator.#calcEventFlag(mainOrder);
+    if (!eventFlag) return 0;
+
+    const day = mainOrder.getDate();
+    if (DayCalc.isWeekend(day)) return 0;
+    const { dessert } = mainOrder.getMenus();
+    const quantity = ItemCalc.sumObjValue(dessert);
+    return quantity * DOMAIN.WEEKDAY_DISCOUNT_PER_MENUS;
+  }
+
+  /** @param {MainOrder} mainOrder  */
+  static weekendDiscount(mainOrder) {
+    const eventFlag = DiscountCalculator.#calcEventFlag(mainOrder);
+    if (!eventFlag) return 0;
+
+    const day = mainOrder.getDate();
+    if (!DayCalc.isWeekend(day)) return 0;
+    const { main } = mainOrder.getMenus();
+    const quantity = ItemCalc.sumObjValue(main);
+    return quantity * DOMAIN.WEEKEND_DISCOUNT_PER_MENUS;
+  }
+
+  /** @param {MainOrder} mainOrder  */
+  static specialDiscount(mainOrder) {
+    const eventFlag = DiscountCalculator.#calcEventFlag(mainOrder);
+    if (!eventFlag) return 0;
+
+    const day = mainOrder.getDate();
+    if (!DayCalc.isSpecialDay(day)) return 0;
+    return DOMAIN.SPECIAL_DAY_DISCOUNT;
+  }
+
+  /** @param {MainOrder} mainOrder  */
+  static christmasDiscount(mainOrder) {
+    const eventFlag = DiscountCalculator.#calcEventFlag(mainOrder);
+    if (!eventFlag) return 0;
+
+    const day = mainOrder.getDate();
+    const [start, end] = [DOMAIN.D_DAY_START_INCLUSIVE, DOMAIN.D_DAY_END_INCLUSIVE];
+
+    if (end < day) return 0;
+    const multiplier = DayCalc.eventMoneyMultiplier(start, end, day);
+    return DOMAIN.D_DAY_DEFAULT_DISCOUNT + multiplier * DOMAIN.D_DAY_BONUS_DISCOUNT;
+  }
+}
+
+export default DiscountCalculator;

리뷰: 이 부분과 같은 경우는 원래 따로 분리되어있었는데요. 할인이 아니라 증정품 때문에 그냥 eventFlag로 빠지게 되었습니다. `reward`의 경우 반환값이 0이 아니라 {}인데 이럴 경우 호출하는 함수가 둘로 갈리게 되고, 이 경우 유지 보수에 있어서도 그렇고 불필요하게 메서드를 늘리고 싶지도 않다는 생각에 그냥 `eventFlag`를 유지하게 되었네요.

그런데 말씀을 듣고 나니... 여전히 분리가 되네요. 인자로 true일 경우 반환할 값을 넣어주면 되니까요. 아무래도.. 제가 덜 생각한 부분입니다.
@@ -0,0 +1,34 @@
+import { DomainValidator } from "../utils/Validator/index.js";
+import { DomainParser } from "../utils/Parser/index.js";
+
+class MainOrder {
+  /** @type {number}  */
+  #date;
+
+  /** @type {{appetizer: {}, main: {}, dessert:{}, drink: {}}}  */
+  #menus;
+
+  /** @param {number} date  */
+  constructor(date) {
+    DomainValidator.date(date);
+    this.#date = date;
+  }
+
+  getDate() {
+    return this.#date;
+  }
+
+  /** @param {[string]} rawOrder  */
+  setMenus(rawOrder) {
+    if (this.#menus) throw new Error();
+    DomainValidator.menus(rawOrder);
+    const parsedMenu = DomainParser.menus(rawOrder);
+    this.#menus = parsedMenu;
+  }
+
+  getMenus() {
+    return this.#menus;
+  }
+}
+
+export default MainOrder;

리뷰: 대신 중첩이 발생해서 그냥 순회해서는 합계를 구할 수 없다는 것이 단점이랍니다😞