ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,61 @@
+package baseball;
+
+import java.util.List;
+
+public class Referee {
+
+    public int countBall(List<Integer> list1, List<Integer> list2) {
+
+        int count = 0;
+        int strike = countStrike(list1, list2);
+
+        for (Integer number : list1) {
+            count = getCount(list2, count, number);
+        }
+        return count - strike;
+    }
+
+    public int countStrike(List<Integer> list1, List<Integer> list2) {
+
+        int count = 0;
+        for (int index = 0; index < 3; index++) {
+            count = getCountStrike(list1, list2, count, index);
+        }
+        return count;
+    }
+
+    public String informStrikeBall(List<Integer> list1, List<Integer> list2) {
+
+        int ball = countBall(list1, list2);
+        int strike = countStrike(list1, list2);
+
+        if (strike == 3) {
+            return strike + "스트라이크";
+        }
+
+        if (strike == 0 && ball != 0) {
+            return ball + "볼";
+        }
+
+        if (strike != 0 && ball != 0) {
+            return ball + "볼 " + strike + "스트라이크";
+        }
+
+        return "낫싱";
+    }
+
+    private int getCountStrike(List<Integer> list1, List<Integer> list2, int count, int index) {
+        if (list1.get(index) == list2.get(index)) {
+            count++;
+        }
+        return count;
+    }
+
+    private int getCount(List<Integer> list2, int count, Integer number) {
+        if (list2.contains(number)) {
+            count++;
+        }
+        return count;
+    }
+
+}

리뷰: 핵심 로직에 화면(콘솔 출력)에 대한 요구사항이 섞여있네요!
만약 화면 요구사항이 변하게 된다면 어떻게 될까요?
화면의 요구사항이 바뀌었지만 개발자가 수정해야하는 부분은 도메인의 핵심 로직입니다.
아래 요구사항을 반영해야하는 이유중 하나가 될 수 있겠네요 :)

> 핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 구분한다.
@@ -0,0 +1,56 @@
+package baseball;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Scanner;
+
+public class ScannerUtil {
+
+    public List<Integer> makeScannerNumbers() {
+        System.out.print("숫자를 입력하세요: ");
+
+        ListUtil listUtil = new ListUtil();
+
+        List<Integer> integers = stringListToIntegerList();
+
+        while (!listUtil.checkListSize(integers)) {
+            System.out.println("잘못된 숫자입니다.");
+            System.out.print("숫자를 입력하세요: ");
+
+            integers = stringListToIntegerList();
+        }
+        return integers;
+    }
+
+    public String insertString() {
+
+        Scanner scanner = new Scanner(System.in);
+        return scanner.next();
+    }
+
+    public int insertInt() {
+
+        Scanner scanner = new Scanner(System.in);
+        return scanner.nextInt();
+    }
+
+    private String[] splitStringList() {
+        String string = insertString();
+
+        return string.split("");
+    }
+
+    private List<Integer> stringListToIntegerList() {
+        String[] strings = splitStringList();
+
+        List<Integer> temp = new ArrayList<>();
+        ListUtil listUtil = new ListUtil();
+
+        for (String string : strings) {
+            int number = Integer.parseInt(string);
+            listUtil.distinctNumberAdd(temp, number);
+        }
+
+        return temp;
+    }
+}

리뷰: Scanner는 매우 비싼 자원입니다. 미리 생성해두고 재활용하도록 해보면 어떨까요 ?

static 메서드를 활용해보면 좋을 것 같아요.
@@ -0,0 +1,62 @@
+import InputView from '../view/InputView.js';
+import Benefit from './Benefit.js';
+import Giveaway from './Giveaway.js';
+import OrderAmount from './OrderAmount.js';
+import OrderManager from './OrderManager.js';
+
+class OrderProcessor {
+  static async processOrder() {
+    const { visitDate, orderMenus } = await this.#inputOrder();
+    const orderManager = this.#createOrderManager(visitDate, orderMenus);
+    const orderAmount = this.#createOrderAmount(visitDate, orderManager);
+    const giveaway = this.#createGiveaway(orderAmount);
+    const benefit = this.#createBenefit(visitDate, orderManager, orderAmount, giveaway);
+    return { visitDate, orderMenus, orderManager, orderAmount, giveaway, benefit };
+  }
+
+  static async #inputOrder() {
+    return {
+      visitDate: await InputView.readVisitDate(),
+      orderMenus: await InputView.readOrderMenu(),
+    };
+  }
+
+  static #createOrderManager(visitDate, orderMenus) {
+    const orderManager = new OrderManager(visitDate, orderMenus);
+    const menusInfo = orderManager.getMenusInfo();
+    const dayOfWeek = orderManager.getDayOfWeek();
+    return { menusInfo, dayOfWeek };
+  }
+
+  static #createOrderAmount(visitDate, orderManager) {
+    const { menusInfo, dayOfWeek } = orderManager;
+    const beforeDiscount = OrderAmount.calculateAmountBeforeDiscount(menusInfo);
+    const totalDiscount = OrderAmount.calculateDiscountTotalAmount(visitDate, menusInfo, dayOfWeek);
+    const afterDiscount = OrderAmount.calculateOrderAmountAfterDiscount(
+      beforeDiscount,
+      totalDiscount,
+    );
+    return { beforeDiscount, totalDiscount, afterDiscount };
+  }
+
+  static #createGiveaway(orderAmount) {
+    const { beforeDiscount } = orderAmount;
+    const isAddGiveaway = Giveaway.checkAddGiveaway(beforeDiscount);
+    const giveawayDiscount = Giveaway.calculateGiveawyDiscountAmount(beforeDiscount);
+    return { isAddGiveaway, giveawayDiscount };
+  }
+
+  static #createBenefit(visitDate, orderManager, orderAmount, giveaway) {
+    const { menusInfo, dayOfWeek } = orderManager;
+    const { totalDiscount } = orderAmount;
+    const { giveawayDiscount } = giveaway;
+    const dDay = Benefit.calculateDDayDiscount(visitDate);
+    const weekDay = Benefit.calculateWeekDayDiscount(menusInfo, dayOfWeek);
+    const weekEnd = Benefit.calculateWeekEndDiscount(menusInfo, dayOfWeek);
+    const special = Benefit.calculateSpecialDiscount(visitDate, dayOfWeek);
+    const eventBadge = Benefit.getEventBadge(totalDiscount + giveawayDiscount);
+    return { dDay, weekDay, weekEnd, special, eventBadge };
+  }
+}
+
+export default OrderProcessor;

리뷰: 해당 클래스의 함수들에서 static을 모두 붙이셨는데, static을 안 붙여도 괜찮지 않을까요?  
모두 static을 붙이신 이유가 궁금합니다!
@@ -0,0 +1,24 @@
+import Benefit from './Benefit.js';
+
+class OrderAmount {
+  static calculateAmountBeforeDiscount(menusInfo) {
+    return menusInfo.reduce((acc, cur) => acc + cur[1] * cur[2], 0);
+  }
+
+  static calculateDiscountTotalAmount(visitDate, orderMenusInfo, dayOfWeek) {
+    return [
+      Benefit.calculateDDayDiscount(visitDate),
+      Benefit.calculateWeekDayDiscount(orderMenusInfo, dayOfWeek),
+      Benefit.calculateWeekEndDiscount(orderMenusInfo, dayOfWeek),
+      Benefit.calculateSpecialDiscount(visitDate, dayOfWeek),
+    ]
+      .filter(benefitDiscount => benefitDiscount !== false)
+      .reduce((acc, cur) => acc + cur, 0);
+  }
+
+  static calculateOrderAmountAfterDiscount(orderAmountBeforeDiscount, discountTotalAmount) {
+    return orderAmountBeforeDiscount + discountTotalAmount;
+  }
+}
+
+export default OrderAmount;

리뷰: 고차함수를 굉장히 잘 쓰시네요! 활용법을 많이 배워갑니다. 👍
@@ -0,0 +1,34 @@
+import OutputView from '../view/OutputView.js';
+import OrderProcessor from '../domains/OrderProcessor.js';
+
+class ChristmasEventController {
+  static async start() {
+    OutputView.printGreeting();
+    const processedOrder = await OrderProcessor.processOrder();
+    this.#printOutput(processedOrder);
+  }
+
+  static #printOutput(processedOrder) {
+    const { visitDate, orderMenus, orderAmount, giveaway, benefit } = processedOrder;
+    OutputView.printNotification(visitDate);
+    OutputView.printOrderMenu(orderMenus);
+    OutputView.printOrderAmountBeforeDiscount(orderAmount.beforeDiscount);
+    OutputView.printGiveawayMenu(giveaway.isAddGiveaway);
+    this.#printBenefitHistory(benefit, giveaway);
+    OutputView.printBenefitTotalAmount(orderAmount.totalDiscount, giveaway.giveawayDiscount);
+    OutputView.printOrderAmountAfterDiscount(orderAmount.afterDiscount);
+    OutputView.printEventBadge(benefit.eventBadge);
+  }
+
+  static #printBenefitHistory(benefit, giveaway) {
+    OutputView.printBenefitHistory(
+      benefit.dDay,
+      benefit.weekDay,
+      benefit.weekEnd,
+      benefit.special,
+      giveaway.giveawayDiscount,
+    );
+  }
+}
+
+export default ChristmasEventController;

리뷰: controller와 OrderProcessor를 분리하신게 인상 깊었습니다. 
저도 이 부분을 많이 고민했는데요.
이번 미션 같은 작은 코드에서 너무 나누면 오히려 가독성이 안좋아질까 하여 controller에 OrderProcessor 같은 함수들을 모두 넣었는데, 제가 잘못 생각한건 아닌가 싶은 코드였습니다.
controller의 역할에 대한 고민이 많네요..!
병현님께서는 controller의 역할을 Processor를 실행하고, 해당 데이터를 받아와 view로 보내는 중간자로 보신게 맞을까요?
@@ -0,0 +1,48 @@
+import CONSTANTS from '../constants/constants.js';
+import MENU from '../constants/menu.js';
+import OrderMenuValidator from '../validators/OrderMenuValidator.js';
+import VisitDateValidator from '../validators/VisitDateValidator.js';
+
+class OrderManager {
+  #visitDate;
+  
+  #orderMenus;
+
+  constructor(visitDate, orderMenus) {
+    this.#validate(visitDate, orderMenus);
+    this.#visitDate = visitDate;
+    this.#orderMenus = orderMenus;
+  }
+
+  #validate(visitDate, orderMenus) {
+    VisitDateValidator.validateVisitDate(visitDate);
+    OrderMenuValidator.validateOrderMenu(orderMenus);
+  }
+
+  getMenusInfo() {
+    const menuNames = this.#setMenuNames();
+    const menuPrices = this.#setMenuPrices(menuNames);
+    const menuCount = this.#setMenuCount();
+    return menuNames.map((element, index) => [element, menuPrices[index], menuCount[index]]);
+  }
+
+  getDayOfWeek() {
+    return new Date(new Date().getFullYear(), CONSTANTS.month.december, this.#visitDate).getDay();
+  }
+
+  #setMenuNames() {
+    return this.#orderMenus.map(orderMenu =>
+      Object.keys(MENU.menuName).find(key => MENU.menuName[key] === orderMenu[0]),
+    );
+  }
+
+  #setMenuPrices(menuNames) {
+    return menuNames.map(menuName => MENU.menu[MENU.menuName[menuName]].price);
+  }
+
+  #setMenuCount() {
+    return this.#orderMenus.map(orderMenu => Number(orderMenu[1]));
+  }
+}
+
+export default OrderManager;

리뷰: get함수에서 단순히 get만 하면 어떨지요? 
OrderProcessor에서 get함수를 할때마다 set함수들을 실행하니 비효율적이라 생각했습니다. 물론 지금은 한번만 실행이 되지만요!
return문에서 데이터 가공을 모두 한 후 Processor로 보내는 것은 3주차 피드백에서 나온 "getter를 getter로만" 내용을 많이 고민하신 것 같아 인상깊었습니다!
@@ -0,0 +1,31 @@
+import { Console } from '@woowacourse/mission-utils';
+import MESSAGE from '../constants/message.js';
+import VisitDateValidator from '../validators/VisitDateValidator.js';
+import OrderMenuValidator from '../validators/OrderMenuValidator.js';
+import reTry from '../utils/reTry.js';
+
+const InputView = {
+  async readVisitDate() {
+    return reTry(async () => {
+      const input = await Console.readLineAsync(MESSAGE.read.visitDate);
+      const visitDate = Number(input);
+      VisitDateValidator.validateVisitDate(visitDate);
+      return Number(visitDate);
+    });
+  },
+
+  async readOrderMenu() {
+    return reTry(async () => {
+      const input = await Console.readLineAsync(MESSAGE.read.orderMenu);
+      const orderMenus = input
+        .split(',')
+        .map(menu => menu.trim())
+        .map(menu => menu.split('-'))
+        .map(menuAndCount => menuAndCount.map(str => str.trim()));
+      OrderMenuValidator.validateOrderMenu(orderMenus);
+      return orderMenus;
+    });
+  },
+};
+
+export default InputView;

리뷰: 이것도 한번 함께 고민해보면 좋을 것 같은데, 저는 InputView에서는 정말 input만 return을 하고 다른 전처리는 데이터를 다루는 파일에서 처리를 해주었는데요.
혹시 이 부분은 어떻게 생각하시는지 궁금합니다.
데이터 관련 로직이 아닌 input 받은 데이터의 전처리는 inputView에서 처리하고자 하신건가요?!
@@ -0,0 +1,28 @@
+package racingcar.domain;
+
+import racingcar.constants.ErrorMsg;
+
+public class CarName {
+    private static final int CAR_NAME_LENGTH_MIN = 1;
+    private static final int CAR_NAME_LENGTH_MAX = 5;
+    private final String name;
+
+    public CarName(String name) {
+        validateLength(name);
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    private void validateLength(String name) {
+        if (name.length() < CAR_NAME_LENGTH_MIN) {
+            throw new IllegalArgumentException(ErrorMsg.CAR_NAME_LENGTH_MIN_ERROR.getMsg());
+        }
+
+        if (name.length() > CAR_NAME_LENGTH_MAX) {
+            throw new IllegalArgumentException(ErrorMsg.CAR_NAME_LENGTH_MAX_ERROR.getMsg());
+        }
+    }
+}

리뷰: 원시값 포장 👍
@@ -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++;
+    }
+
+}

리뷰: 랜덤 숫자에 의해 자동차의 이동 판단역할이
RacinCar에 있는게 맞는지 한번 생각해 보시면 좋을거 같아요
😢
@@ -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());
+        }
+    }
+}

리뷰: 해당 controller에서 의존성 주입에 대해서 한번 고민 해볼 필요가 있어요!
controller 계층을 사용하기위해서 만든것으로 보이는데
접근제어자가 없는 객체들을 사용하신 이유가 있을까요
@@ -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;
+    }
+
+}

리뷰: default 접근 제어자를 사용하신 이유가 있을까요?
@@ -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;
+    }
+}

리뷰: 여기도 젭근제어자가!@?
@@ -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;
+    }
+}

리뷰: 해당 클래스 역할이 모호하지 않나요??
클래스 이름은 게임전체의 상수를 관리하는 것으로 보여지는데
멤버 변수나 기능은 total_count나 number 세팅도 들어 있어서요
@@ -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();
+    }
+}

리뷰: 테스트 메소드 이름이 충분히 명시적이라서
해당 given/when/then 주석은 없어도 될거 같아요
조건이 바뀔때마다 
주석을 수정해주지 않으면 실제 테스트 코드와 주석이 불일치 하는 경우가 발생 할 수 있어요
@@ -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;
+    }
+
+}

리뷰: 일급컬렉션 활용 💯
@@ -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;
+    }
+
+}

리뷰: 매게변수를 줄일 수 있는 방법이 있을거 같은데 한번 고민해봐주세요
@@ -0,0 +1,46 @@
+package racingcar.domain;
+
+import racingcar.constants.ErrorMsg;
+
+public class TrialCount {
+    private final int count;
+
+    public TrialCount(String userInput) {
+        validateEmptyInput(userInput);
+        validateInputType(userInput);
+        count = convertToInt(userInput);
+        validateTrialCountZero(count);
+    }
+
+    public int getCount() {
+        return count;
+    }
+
+    private int convertToInt(String userInput) {
+        return Integer.parseInt(userInput);
+    }
+
+    private void validateEmptyInput(String userInput) {
+        if (userInput.isEmpty()) {
+            throw new IllegalArgumentException(ErrorMsg.TRIAL_COUNT_EMPTY_INPUT_ERROR.getMsg());
+        }
+    }
+
+    private void validateInputType(String userInput) {
+        for (char c : userInput.toCharArray()) {
+            validateIsDigit(c);
+        }
+    }
+
+    private void validateIsDigit(char c) {
+        if (!Character.isDigit(c)) {
+            throw new IllegalArgumentException(ErrorMsg.TRIAL_COUNT_INPUT_TYPE_ERROR.getMsg());
+        }
+    }
+
+    private void validateTrialCountZero(int trialNumber) {
+        if (trialNumber <= 0) {
+            throw new IllegalArgumentException(ErrorMsg.TRIAL_COUNT_ZERO_ERROR.getMsg());
+        }
+    }
+}

리뷰: 오 이부분 구조가 좋아보여요
이동 숫자를 객체로 포장해서
validation을 하니 응집력이 상당이 좋아보입니다.
@@ -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 도메인을 추가하여 전진여부 판단 로직을 분리하였습니다!