ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,45 @@
+package racingcar.controller;
+
+import racingcar.domain.*;
+import racingcar.view.UIManager;
+
+public class RacingGameController {
+    private UIManager uiManager;
+
+    public void run() {
+        uiManager = new UIManager();
+
+        uiManager.printGameStartMsg();
+        RacingCars racingCars = createRacingCars(uiManager.inputCarNames());
+
+        TrialCount trialCount = createTrialCount(uiManager.inputTrialCount());
+        RandomNumber randomNumber = new GameRandomNumber();
+        GameRule gameRule = new GameRule(trialCount, randomNumber);
+
+        uiManager.printGameResultMsg();
+        for (int i = 0; i < gameRule.getTrialCount(); i++) {
+            racingCars.moveCars(gameRule);
+            uiManager.printCarPositions(racingCars.getCarPositions());
+        }
+
+        uiManager.printWinners(racingCars.pickWinners());
+    }
+
+    public RacingCars createRacingCars(String userInput) {
+        try {
+            return new RacingCars(userInput);
+        } catch (IllegalArgumentException e) {
+            System.out.println(e.getMessage());
+            return createRacingCars(uiManager.inputCarNames());
+        }
+    }
+
+    public TrialCount createTrialCount(String userInput) {
+        try {
+            return new TrialCount(userInput);
+        } catch (IllegalArgumentException e) {
+            System.out.println(e.getMessage());
+            return createTrialCount(uiManager.inputTrialCount());
+        }
+    }
+}

리뷰: 접근제어자 private으로 수정하였습니다.
@@ -0,0 +1,22 @@
+package racingcar.constants;
+
+public enum ErrorMsg {
+    BLANK_IN_NAME_ERROR("이름에는 공백이 없어야 하며 쉼표(,) 앞뒤에도 공백이 없어야 합니다."),
+    CAR_NAME_LENGTH_MIN_ERROR("자동차 이름은 " + GameConstants.CAR_NAME_LENGTH_MIN.getNumber() + "자 이상이어야 합니다."),
+    CAR_NAME_LENGTH_MAX_ERROR("자동차 이름은 " + GameConstants.CAR_NAME_LENGTH_MAX.getNumber() + "자 이하여야 합니다."),
+    TRIAL_COUNT_INPUT_TYPE_ERROR("시도 횟수는 숫자로 입력해야 합니다."),
+    TRIAL_COUNT_EMPTY_INPUT_ERROR("시도 횟수를 입력하지 않았습니다."),
+    TRIAL_COUNT_ZERO_ERROR("시도 횟수는 1 이상이어야 합니다.");
+
+    private static final String ERROR_MSG_TAG = "[ERROR]";
+    private final String msg;
+
+    ErrorMsg(String msg) {
+        this.msg = ERROR_MSG_TAG + " " + msg + "\n";
+    }
+
+    public String getMsg() {
+        return msg;
+    }
+
+}

리뷰: Enum클래스의 생성자는 private만 허용하므로 
접근제어자를 지정하지 않으면 private이 기본으로 지정되기 때문에 따로 지정을 안했습니다!
명시적으로 private 키워드를 붙이는 것이 좋을까요?
@@ -0,0 +1,16 @@
+package racingcar.constants;
+
+public enum GameConstants {
+    CAR_NAME_LENGTH_MIN(1),
+    CAR_NAME_LENGTH_MAX(5);
+
+    private final int number;
+
+    GameConstants(int number) {
+        this.number = number;
+    }
+
+    public int getNumber() {
+        return number;
+    }
+}

리뷰: 위의 ErrorMsg와 같은 이유로 접근제어자를 지정하지 않았습니다.
@@ -0,0 +1,16 @@
+package racingcar.constants;
+
+public enum GameConstants {
+    CAR_NAME_LENGTH_MIN(1),
+    CAR_NAME_LENGTH_MAX(5);
+
+    private final int number;
+
+    GameConstants(int number) {
+        this.number = number;
+    }
+
+    public int getNumber() {
+        return number;
+    }
+}

리뷰: TRIAL_COUNT(시도횟수)도 게임전체 상수 중 하나라고 생각했었는데, 
게임 시작 전부터 결정되는 상수와
게임 시작 후 결정되는 TRIAL_COUNT는 차이점이 있는 것 같습니다.

TRIAL_COUNT는 GameConstants에서는 삭제하고, TrialCount 객체를 통해서 값을 받아오도록 수정하였습니다.
@@ -0,0 +1,40 @@
+package racingcar.domain;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import racingcar.constants.ErrorMsg;
+
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+public class CarNameTest {
+    @Test
+    void 자동차이름_길이범위최소_벗어났을_때() {
+        // Given
+        String carName = "";
+
+        // When & Then
+        assertThatThrownBy(() -> new CarName(carName))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessage(ErrorMsg.CAR_NAME_LENGTH_MIN_ERROR.getMsg());
+    }
+
+    @Test
+    void 자동차이름_길이범위최대_벗어났을_때() {
+        // Given
+        String carName = "ferrari";
+
+        // When & Then
+        assertThatThrownBy(() -> new CarName(carName))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessage(ErrorMsg.CAR_NAME_LENGTH_MAX_ERROR.getMsg());
+    }
+
+    @ParameterizedTest
+    @ValueSource(strings = {"a", "abc", "abcde"})
+    void 자동차이름_길이범위_정상(String carName) {
+        assertThatCode(() -> new CarName(carName))
+                .doesNotThrowAnyException();
+    }
+}

리뷰: 불필요한 주석 제거했습니다!
@@ -0,0 +1,67 @@
+package racingcar.domain;
+
+import racingcar.constants.ErrorMsg;
+
+import java.util.*;
+
+public class RacingCars {
+    private final List<RacingCar> racingCars;
+
+    public RacingCars(String userInput) {
+        racingCars = new ArrayList<>();
+        createRacingCars(userInput);
+    }
+
+    private void createRacingCars(String userInput) {
+        if (validateBlank(userInput)) {
+            throw new IllegalArgumentException(ErrorMsg.BLANK_IN_NAME_ERROR.getMsg());
+        }
+
+        addRacingCars(userInput);
+    }
+
+    private void addRacingCars(String userInput) {
+        for (String carName : userInput.split(",", -1)) {
+            racingCars.add(new RacingCar(carName));
+        }
+    }
+
+    private boolean validateBlank(String userInput) {
+        return userInput.contains(" ");
+    }
+
+    public void moveCars(GameRule gameRule) {
+        for (RacingCar car : racingCars) {
+            car.attemptToMove(gameRule);
+        }
+    }
+
+    public Map<String, Integer> getCarPositions() {
+        Map<String, Integer> carPosition = new LinkedHashMap<>();
+        for (RacingCar car : racingCars) {
+            carPosition.put(car.getCarName(), car.getMoveCount());
+        }
+        return carPosition;
+    }
+
+    public List<String> pickWinners() {
+        Winners winners = new Winners();
+        int maxStep = getMaxStep();
+
+        for (RacingCar car : racingCars) {
+            winners.pickWinner(car, maxStep);
+        }
+
+        return winners.getWinners();
+    }
+
+    private int getMaxStep() {
+        int maxStep = 0;
+        for (RacingCar car : racingCars) {
+            maxStep = Integer.max(maxStep, car.getMoveCount());
+        }
+
+        return maxStep;
+    }
+
+}

리뷰: Winners 도메인을 추가하여 우승자 추출하는 로직을 분리하였습니다!
@@ -0,0 +1,30 @@
+package racingcar.domain;
+
+public class RacingCar {
+    private final CarName carName;
+    private int moveCount;
+
+    public RacingCar(String name) {
+        this.carName = new CarName(name);
+        moveCount = 0;
+    }
+
+    public int getMoveCount() {
+        return moveCount;
+    }
+
+    public String getCarName() {
+        return carName.getName();
+    }
+
+    public void attemptToMove(GameRule gameRule) {
+        if (gameRule.canMove()) {
+            move();
+        }
+    }
+
+    private void move() {
+        moveCount++;
+    }
+
+}

리뷰: 1. 기본생성자를 활용한 도메인 활용은 기본적으로 좋은 방법이 아니에요. -> 해당 도메인은 도메인이라기 보단 static method를 활용 하기 위한 vo 로 생각 되요 ㅠㅜ
2. GameRule을 도메인으로 간주 한다면, racingcar ->gamerule 으로 의존성이 생기는데 합리적인 구조로 보이지는 않아요ㅠ

---------------
힌트준 내용을 좀더 고민해보면 좋았을거 같아요. 단순히 해당 도메인을 추가해서 처리하는것이 아니라
각 도메인을 어떤식으로 호출하고 의존성 관리할지 고민을 좀더해서 수정해봐요
@@ -0,0 +1,22 @@
+package racingcar.constants;
+
+public enum ErrorMsg {
+    BLANK_IN_NAME_ERROR("이름에는 공백이 없어야 하며 쉼표(,) 앞뒤에도 공백이 없어야 합니다."),
+    CAR_NAME_LENGTH_MIN_ERROR("자동차 이름은 " + GameConstants.CAR_NAME_LENGTH_MIN.getNumber() + "자 이상이어야 합니다."),
+    CAR_NAME_LENGTH_MAX_ERROR("자동차 이름은 " + GameConstants.CAR_NAME_LENGTH_MAX.getNumber() + "자 이하여야 합니다."),
+    TRIAL_COUNT_INPUT_TYPE_ERROR("시도 횟수는 숫자로 입력해야 합니다."),
+    TRIAL_COUNT_EMPTY_INPUT_ERROR("시도 횟수를 입력하지 않았습니다."),
+    TRIAL_COUNT_ZERO_ERROR("시도 횟수는 1 이상이어야 합니다.");
+
+    private static final String ERROR_MSG_TAG = "[ERROR]";
+    private final String msg;
+
+    ErrorMsg(String msg) {
+        this.msg = ERROR_MSG_TAG + " " + msg + "\n";
+    }
+
+    public String getMsg() {
+        return msg;
+    }
+
+}

리뷰: 오 이건 제가 실수했네요. 대리님 말씀이 맞아요!
@@ -0,0 +1,16 @@
+package racingcar.constants;
+
+public enum GameConstants {
+    CAR_NAME_LENGTH_MIN(1),
+    CAR_NAME_LENGTH_MAX(5);
+
+    private final int number;
+
+    GameConstants(int number) {
+        this.number = number;
+    }
+
+    public int getNumber() {
+        return number;
+    }
+}

리뷰: GameRule 도메인이 만들어 져서, GameConstants와 중복된 의미를 담는거 같은데
해당 클래스에 대해 고민이 좀더 필요해보여용
@@ -0,0 +1,30 @@
+package racingcar.domain;
+
+public class RacingCar {
+    private final CarName carName;
+    private int moveCount;
+
+    public RacingCar(String name) {
+        this.carName = new CarName(name);
+        moveCount = 0;
+    }
+
+    public int getMoveCount() {
+        return moveCount;
+    }
+
+    public String getCarName() {
+        return carName.getName();
+    }
+
+    public void attemptToMove(GameRule gameRule) {
+        if (gameRule.canMove()) {
+            move();
+        }
+    }
+
+    private void move() {
+        moveCount++;
+    }
+
+}

리뷰: ### 현재 수정사항
- GameRule 도메인의 멤버변수로 RandomNumber와 TrialCount 추가
   - RandomNumber는 자동차의 전진여부를 판단할 때만 필요한데, 전진여부 판단로직인 GameRule에 있으므로 RandomNumber를 GameRule의 멤버변수로 추가함
- GameRule 객체를 RacingCar의 메소드 안에서 생성하지 않고 Controller에서 생성하도록 수정 
   - Controller에서 RacingCars에 GameRule객체를 매개변수로 전달
- GameRandomNumber의 getRandomNumber() 메소드 안에서 바로 랜덤숫자를 반환하도록 수정
   - 랜덤숫자가 필요할 때마다 새로운 RandomNumber 객체를 생성하지 않고, GameRule이 가지고 있는 RandomNumber 객체를 통해 랜덤숫자 값만 반환받도록 함

### Issue 1 : GameRule 도메인과 다른 도메인 간의 의존관계 설정의 어려움
- RacingCars의 메소드 안에서 각 자동차 객체마다 전진여부판단이 필요한데, GameRule 객체를 RacingCar에게 넘겨주지 않고 외부에서 판단 후 RacingCar를 전진시킬 방법을 모르겠습니다..!

### Issue 2 : GameRandomNumber에서 randomNumber라는 멤버변수의 필요성
- getRandomNumber()에서 바로 랜덤숫자를 반환해주는데, 그럼 randomNumber 멤버변수가 없어도 되는 것일까요?

### Issue 3 : GameConstants 클래스
- 게임상수 5개 중, 3개는 한 곳에서만 호출하고 나머지 2개는 두 군데에서 호출하는데, 이 상수들을 어떻게 관리하는 것이 효과적일지 궁금합니다! 아래는 제가 생각해 본 방법들입니다.
   - 모든 상수를 Enum 클래스로 모아서 관리
   - 한 곳에서 호출하는 상수는 각각 사용되는 도메인의 멤버변수로 선언, 나머지는 Enum클래스로 관리
   - 모든 상수를 각각 사용되는 도메인의 멤버변수로 선언
   - 여러 곳에서 사용되는 상수의 경우, 특정 도메인에 정적 상수로 선언하고 다른 도메인에서 호출

---
각 도메인의 역할을 구분하는 것과 도메인 간 의존관계를 설정하는 부분이 어렵고 개인적으로 약한 것 같습니다. 
전체적으로 제가 혼란스러운 부분이 많아서 코드는 달라진 점이 많이 없고, 이슈들 위에 적어두었습니다. 리뷰 부탁드립니다!
@@ -0,0 +1,24 @@
+package com.example.chatgptcodereviewtest.controller;
+
+import com.example.chatgptcodereviewtest.service.TestService2;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 설명:
+ *
+ * @author 최현범(Jayce) / hb.choi@dreamus.io
+ * @since 2023/03/23
+ */
+@RestController
+@RequestMapping("/test2")
+@RequiredArgsConstructor
+public class TestController2 {
+
+    private final TestService2 testService2;
+
+    @GetMapping
+    public String getText(@RequestParam(value = "label", required = false) String label) {
+        return testService2.getUppercaseTestLabel(label);
+    }
+}
\ No newline at end of file

리뷰: 위 코드는 `TestController2`라는 REST API 컨트롤러 클래스입니다.

개선 제안:
1. Javadoc(주석) 작성이 필요합니다. 메소드와 클래스의 설명을 구체적으로 달아주어 가독성을 높일 수 있습니다.
2. 마지막 줄에 개행 문자열이 없습니다. 소스코드를 수정 및 편집할 때 깃에러가 발생하지 않도록 개행 문자열을 추가하는 것이 좋습니다.
3. `@RequestParam` 어노테이션의 required 속성값은 false로 되어 있어, 필수로 들어올 값이 아닙니다. 이에 대한 처리를 하고 싶다면, 해당 파라미터 변수에 널 체크나 디폴트값 등을 지정해야 합니다.

버그 위험이 없어 보입니다.
@@ -0,0 +1,17 @@
+package com.example.chatgptcodereviewtest.service;
+
+import org.springframework.stereotype.Service;
+
+/**
+ * 설명:
+ *
+ * @author 최현범(Jayce) / hb.choi@dreamus.io
+ * @since 2023/03/23
+ */
+@Service
+public class TestService2 {
+
+    public String getUppercaseTestLabel(String label) {
+        return label.toUpperCase();
+    }
+}

리뷰: 이 코드는 봉쇄적인 문제나 버그가 없는 것 같습니다. 

단순하게 문자열을 대문자로 변경하는 메서드를 가지고 있으며, Spring의 Service 어노테이션을 사용하여 Bean으로 등록되어 있습니다.

변수명과 메서드명의 직관성 측면에서 개선할 점이 있는 것 같습니다. 'label'은 구체적인 의미를 알기 어렵습니다. 더 구체적인 명칭을 사용한다면 코드의 가독성이 향상될 것입니다.
@@ -0,0 +1,35 @@
+package nextstep.subway.applicaion;
+
+import java.util.List;
+import nextstep.subway.applicaion.dto.PathResponse;
+import nextstep.subway.domain.Line;
+import nextstep.subway.domain.LineRepository;
+import nextstep.subway.domain.Path;
+import nextstep.subway.domain.PathFinder;
+import nextstep.subway.domain.Station;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@Transactional
+public class PathService {
+    private final LineRepository lineRepository;
+    private final StationService stationService;
+
+    public PathService(LineRepository lineRepository,
+        StationService stationService) {
+        this.lineRepository = lineRepository;
+        this.stationService = stationService;
+    }
+
+    public PathResponse searchPath(Long sourceId, Long targetId) {
+        Station source = stationService.findById(sourceId);
+        Station target = stationService.findById(targetId);
+        List<Line> lines = lineRepository.findAll();
+
+        PathFinder pathFinder = PathFinder.from(lines);
+        Path shortestPath = pathFinder.searchPath(source, target);
+        return PathResponse.from(shortestPath);
+    }
+
+}

리뷰: 경로를 찾기위해 lines라는 정보가 있고, 이 정보를 이용해 경로를 찾는데 이 경로를 찾는 책임을 PathFinder가 가지고 있고 그 결과 정보는 Path가 가지고 있네요!
객체지향에서 객체는 상태와 행위로 이루어져있다고 하는데요
경로를 찾기 위한 상태인 lines와 행위인 PathFinder를 하나의 객체로 도출해볼 수는 없을까요?

만약에 이렇게 리팩터링 할 경우 기존에 만들어 두었던 테스트를 활용해서 테스트의 검증이라는 보호 속에 리팩터링을 해보시는 것을 권해드립니다!
@@ -63,19 +63,19 @@ public void update(Station newUpStation, int minusDistance) {
     }
 
     public Section merge(Section section) {
-        if (!isDownStation(section.upStation)) {
+        if (!hasDownStationAs(section.upStation)) {
             throw new IllegalArgumentException("합치려는 구간의 상행역이 하행역과 같아야 합니다.");
         }
 
         return Section.of(line, upStation, section.downStation, distance + section.distance);
     }
 
 
-    public boolean isUpStation(Station station) {
+    public boolean hasUpStationAs(Station station) {
         return upStation.equals(station);
     }
 
-    public boolean isDownStation(Station station) {
+    public boolean hasDownStationAs(Station station) {
         return downStation.equals(station);
     }
 

리뷰: 변경된 네이밍이 의미를 조금 더 잘 전달하는 것 같아요 👍
@@ -1,6 +1,7 @@
 package nextstep.subway.domain;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
@@ -34,23 +35,30 @@ public void add(Section section) {
     public void remove(Station station) {
         validationRemoveStation(station);
 
-        Optional<Section> firstSection = sections.stream()
-            .filter(s -> s.isDownStation(station))
-            .findAny();
-
-        Optional<Section> secondSection = sections.stream()
-            .filter(s -> s.isUpStation(station))
-            .findAny();
+        Optional<Section> sectionHasUpStation = findSectionHasUpStationAs(station);
+        Optional<Section> sectionHasDownStation = findSectionHasDownStationAs(station);
 
-        firstSection.ifPresent(section -> sections.remove(section));
-        secondSection.ifPresent(section -> sections.remove(section));
+        sectionHasUpStation.ifPresent(section -> sections.remove(section));
+        sectionHasDownStation.ifPresent(section -> sections.remove(section));
 
-        if (firstSection.isPresent() && secondSection.isPresent()) {
-            mergeExistingSections(firstSection.get(), secondSection.get());
+        if (sectionHasUpStation.isPresent() && sectionHasDownStation.isPresent()) {
+            mergeExistingSections(sectionHasUpStation.get(), sectionHasDownStation.get());
         }
 
     }
 
+    private Optional<Section> findSectionHasDownStationAs(Station station) {
+        return sections.stream()
+            .filter(s -> s.hasUpStationAs(station))
+            .findAny();
+    }
+
+    private Optional<Section> findSectionHasUpStationAs(Station station) {
+        return sections.stream()
+            .filter(s -> s.hasDownStationAs(station))
+            .findAny();
+    }
+
     private List<Station> getStations() {
         List<Station> stations = new ArrayList<>();
         stations.add(sections.get(0).getUpStation());
@@ -152,4 +160,7 @@ private void mergeExistingSections(Section firstSection, Section secondSection)
         sections.add(firstSection.merge(secondSection));
     }
 
+    public List<Section> getSectionList() {
+        return Collections.unmodifiableList(sections);
+    }
 }

리뷰: 적절한 사이즈로 메서드를 나누어 주셨네요 👍
@@ -0,0 +1,48 @@
+package nextstep.subway.unit;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import nextstep.subway.domain.Line;
+import nextstep.subway.domain.Station;
+import org.springframework.test.util.ReflectionTestUtils;
+
+public class PathFixture {
+    /**              (10)
+     * 교대역    --- *2호선* ---   강남역
+     * |                        |
+     * *3호선* (2)               *신분당선* (5)
+     * |                        |
+     * 남부터미널역  --- *3호선* ---   양재
+                    (3)          **/
+
+
+    public static Station 강남역 = new Station("강남역");
+    public static Station 교대역 = new Station("교대역");
+    public static Station 양재역 = new Station("양재역");
+    public static Station 남부터미널역 = new Station("남부터미널역");
+
+    public static int 교대역_강남역_거리 = 10;
+    public static int 강남역_양재역_거리 = 5;
+    public static int 교대역_남부터미널역_거리 = 2;
+    public static int 남부터미널역_양재역_거리 = 3;
+
+    public static Line 이호선 = Line.of("2호선", "bg-green-600", 교대역, 강남역, 교대역_강남역_거리);
+    public static Line 삼호선 = Line.of("3호선", "bg-orange-500", 교대역, 남부터미널역, 교대역_남부터미널역_거리);
+    public static Line 신분당선 = Line.of("신분당선", "bg-red-500", 강남역, 양재역, 강남역_양재역_거리);
+
+    public static List<Line> 노선_목록 = new ArrayList<>();
+
+    static {
+        삼호선.addSection(남부터미널역, 양재역, 남부터미널역_양재역_거리);
+
+        ReflectionTestUtils.setField(강남역, "id", 1L);
+        ReflectionTestUtils.setField(교대역, "id", 2L);
+        ReflectionTestUtils.setField(양재역, "id", 3L);
+        ReflectionTestUtils.setField(남부터미널역, "id", 4L);
+
+        노선_목록.addAll(Arrays.asList(이호선, 삼호선, 신분당선));
+    }
+
+
+}

리뷰: 픽스쳐 분리를 통해 테스트 코드 내 효과적인 중복 제거 👍
@@ -1,7 +1,10 @@
 package christmas;
 
+import christmas.controller.MainController;
+
 public class Application {
     public static void main(String[] args) {
-        // TODO: 프로그램 구현
+        MainController mainController = new MainController();
+        mainController.run();
     }
 }

리뷰: 객체를 생성할 때 new 예약어보다 정적 팩토리 메서드를 사용해보시면 어떨까요. 가독성이나 객체 생성 수 제한 같은 여러 가지 이점이 있어요.

https://tecoble.techcourse.co.kr/post/2020-05-26-static-factory-method/
@@ -0,0 +1,47 @@
+package christmas.controller;
+
+import java.util.Map;
+
+import christmas.view.*;
+import christmas.model.*;
+
+public class MainController {
+    private OrderMenu orderMenu;
+
+    public MainController() {
+        orderMenu = new OrderMenu();
+    }
+
+    public void run() {
+        orderMenu.setOrderDate(InputView.readDate());
+        setReadOrder();
+
+        OutputView.printPreview(orderMenu.getOrderDate());
+        OutputView.printOrder(orderMenu.getUserOrder());
+        int totalPrice = orderMenu.getTotalPrice();
+        OutputView.printTotalPrice(totalPrice);
+        OutputView.printPresent(totalPrice);
+        benefitControl(totalPrice);
+    }
+
+    private void benefitControl(int totalPrice) {
+        Benefit benefit = new Benefit(orderMenu.getOrderDate(), orderMenu.getCategoryCount(), totalPrice);
+        OutputView.printBenefits(benefit.getBenefits());
+
+        int totalBenefitPrice = benefit.getTotalBenefitPrice();
+        OutputView.printTotalBenefitPrice(totalBenefitPrice);
+        OutputView.printTotalPayment(totalPrice - totalBenefitPrice + benefit.getPresentPrice());
+        OutputView.printEventBadge(totalBenefitPrice);
+    }
+
+    private void setReadOrder() {
+        Map<String, Integer> orderMenu;
+        try {
+            orderMenu = InputView.readOrder();
+            this.orderMenu.setUserOrder(orderMenu);
+        } catch (IllegalArgumentException e) {
+            System.out.println(e.getMessage());
+            setReadOrder();
+        }
+    }
+}

리뷰: OutputView나 InputView의 입출력 기능들을 모두 스태틱 메서드로 구현하셨네요. 보통은 해당 객체를 생성 후에 일반 메서드를 호출하는데 스태틱 메서드를 사용하신 이유가 있으실까요?
@@ -1,7 +1,10 @@
 package christmas;
 
+import christmas.controller.MainController;
+
 public class Application {
     public static void main(String[] args) {
-        // TODO: 프로그램 구현
+        MainController mainController = new MainController();
+        mainController.run();
     }
 }

리뷰: 생각하지 못했던 개념인데 감사합니다! 리팩토링할 때 적용하면 좋을 것 같네요
@@ -0,0 +1,39 @@
+package christmas.util;
+
+public enum Constant {
+    NOT_IN_MENU(-1),
+    EPPETIZER(0),
+    MAINMENU(1),
+    DESSERT(2),
+    BEVERAGE(3),
+    CATEGORY_COUNT(4),
+    ZERO(0),
+    ONE(1),
+    THOUSAND(1000),
+    HUNDRED(100),
+    CHRISTMAS_DAY(25),
+    MIN_BENEFIT_PRICE(10000),
+    PRESENT_COST(120000),
+    CHAMPAIGN_PRICE(25000),
+    WEEKDAYS(7),
+    FRIDAY(1),
+    SATURDAY(2),
+    SUNDAY(3),
+    EVENT_PRICE(2023),
+    SANTA(20000),
+    TREE(10000),
+    STAR(5000),
+    MAX_MENU_COUNT(20),
+    KEY_INDEX(0),
+    VALUE_INDEX(1);
+
+    private final int value;
+
+    Constant(int value) {
+        this.value = value;
+    }
+
+    public int get() {
+        return this.value;
+    }
+}

리뷰: Enum을 메뉴정보 저장하는 자료구조로 사용하면 용처가 잘 맞을 것 같습니다.
메뉴에 해당하는 한글이름(String)과 가격(Integer)가 존재하기 때문이지요.
@@ -0,0 +1,68 @@
+package christmas.model;
+
+import christmas.util.Constant;
+import christmas.util.ErrorMessage;
+import christmas.util.Utility;
+import christmas.util.Validator;
+
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.List;
+
+public class OrderMenu {
+    private final List<Map<String, Integer>> menu;
+
+    private int orderDate;
+    private Map<String, Integer> userOrder;
+
+    public OrderMenu() {
+        menu = new ArrayList<>(List.of(
+                // EPPETIZER 0
+                Map.of("양송이수프", 6000, "타파스", 5500, "시저샐러드", 8000),
+                // MAINMENU 1
+                Map.of("티본스테이크", 55000, "바비큐립", 54000, "해산물파스타", 35000, "크리스마스파스타", 25000),
+                // DESSERT 2
+                Map.of("초코케이크", 15000, "아이스크림", 5000),
+                // BEVERAGE 3
+                Map.of("제로콜라", 3000, "레드와인", 60000, "샴페인", 25000)
+        ));
+    }
+
+    public int getOrderDate() {
+        return this.orderDate;
+    }
+
+    public Map<String, Integer> getUserOrder() {
+        return this.userOrder;
+    }
+
+    public int getTotalPrice() {
+        int totalPrice = Constant.ZERO.get();
+        for (Map.Entry<String, Integer> entry : this.userOrder.entrySet()) {
+            int category = Utility.getCategory(entry.getKey(), menu);
+            int price = menu.get(category).get(entry.getKey());
+            totalPrice += price * entry.getValue();
+        }
+        return totalPrice;
+    }
+
+    public int[] getCategoryCount() {
+        int[] categoryCount = new int[Constant.CATEGORY_COUNT.get()];
+        for (Map.Entry<String, Integer> entry : this.userOrder.entrySet()) {
+            int category = Utility.getCategory(entry.getKey(), menu);
+            categoryCount[category] += entry.getValue();
+        }
+        return categoryCount;
+    }
+
+    public void setOrderDate(int date) {
+        this.orderDate = date;
+    }
+
+    public void setUserOrder(Map<String, Integer> userOrder) {
+        if (Validator.validateMenu(userOrder, this.menu)) {
+            throw new IllegalArgumentException(ErrorMessage.ORDER_ERROR.get());
+        }
+        this.userOrder = userOrder;
+    }
+}

리뷰: Map.of로 초기화하나 편해보이네요! 배웠습니다! 어디가서 써먹어 봐야겠어요!