ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,71 @@
+package racingcar.domain;
+
+import racingcar.exception.CarsDuplicatedNameException;
+import racingcar.exception.CarsMaxScoreBlankException;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+public class Cars {
+
+    private static final String DELIMITER = ",";
+    private static final String DUPLICATED_DELIMITER_REGEX = ",+";
+    private final List<Car> cars;
+
+    private Cars(List<Car> cars) {
+        this.cars = Collections.unmodifiableList(cars);
+    }
+
+    public static Cars createCarNameByWord(String input) {
+        List<Car> cars = new ArrayList<>();
+        String[] words = divideWord(input);
+        validate(words);
+
+        for (String carName : words) {
+            cars.add(new Car(carName));
+        }
+        return new Cars(cars);
+    }
+
+    private static void validate(String[] words) {
+        validateDuplicatedWord(words);
+    }
+
+    private static void validateDuplicatedWord(String[] words) {
+        Set<String> uniqueWord = new HashSet<>();
+        for (String word : words) {
+            if (!uniqueWord.add(word)) {
+                throw new CarsDuplicatedNameException();
+            }
+        }
+    }
+
+    private static String[] divideWord(String word) {
+        return word.replaceAll(DUPLICATED_DELIMITER_REGEX, DELIMITER).split(DELIMITER);
+    }
+
+    public void move(CarMoveNumberGenerator carMoveNumberGenerator) {
+        for (Car car : cars) {
+            car.move(carMoveNumberGenerator);
+        }
+    }
+
+    public Winner findWinner() {
+        int maxScore = findMaxScore();
+        return new Winner(cars.stream()
+                .filter(car -> (car.getPosition() == maxScore))
+                .map(Car::getName)
+                .collect(Collectors.toList()));
+    }
+
+    private int findMaxScore() {
+        return cars.stream()
+                .max(Comparator.comparingInt(Car::getPosition))
+                .map(Car::getPosition)
+                .orElseThrow(CarsMaxScoreBlankException::new);
+    }
+
+    public List<Car> getCars() {
+        return cars;
+    }
+}

리뷰: `CarMoveNumberGenerator` 를 `Car` 객체까지 넘겨주지 않아도 될 것 같습니다!
그러면 `car.move()` 메서드를 테스트하기도 훨씬 간편해질 것 같아요!
@@ -0,0 +1,22 @@
+package racingcar.domain;
+
+public class RacingCarGame {
+
+    private final Cars cars;
+
+    public RacingCarGame(Cars cars) {
+        this.cars = cars;
+    }
+
+    public void move() {
+        cars.move(new CarRandomMoveNumberGenerator());
+    }
+
+    public Winner findWinner() {
+        return cars.findWinner();
+    }
+
+    public Cars getCars() {
+        return cars;
+    }
+}

리뷰: `RacingCarGame` 클래스의 메서드들이 대부분 `Cars` 클래스의 메서드를 호출하는 용도로만 사용되는 것 같아요!
`RacingCarGame` 클래스가 정말 필요한지, 아니면 어떤 식으로 `RacingCarGame` 만의 새로운 책임을 부여할 수 있을 지
생각해보시면 좋을 것 같아요!!
@@ -0,0 +1,47 @@
+package racingcar.domain;
+
+import racingcar.exception.TryCommandNumberException;
+import racingcar.exception.TryCommandRangeException;
+
+public class TryCommand {
+
+    private static final int MIN_TRY = 1;
+    private static final int MAX_TRY = 100000;
+    private int tryCount;
+
+    private TryCommand(int tryCount) {
+        this.tryCount = tryCount;
+    }
+
+    public static TryCommand createTryCommandByString(String input) {
+        int number = convertInt(input);
+        validate(number);
+        return new TryCommand(number);
+    }
+
+    private static void validate(int number) {
+        validateRange(number);
+    }
+
+    private static void validateRange(int number) {
+        if(number < MIN_TRY || number > MAX_TRY) {
+            throw new TryCommandRangeException(MIN_TRY, MAX_TRY);
+        }
+    }
+
+    private static int convertInt(String input) {
+        try{
+            return Integer.parseInt(input);
+        }catch (NumberFormatException exception) {
+            throw new TryCommandNumberException();
+        }
+    }
+
+    public boolean tryMove() {
+        if(tryCount > 0) {
+            tryCount--;
+            return true;
+        }
+        return false;
+    }
+}

리뷰: 값 객체의 내부 상태를 `final` 로 선언하는 방법도 있더라구요! 참고해보시면 좋을 것 같습니다!

- [Value Object, Reference Obejct](http://aeternum.egloos.com/v/1111257)
@@ -0,0 +1,71 @@
+package racingcar.domain;
+
+import racingcar.exception.CarsDuplicatedNameException;
+import racingcar.exception.CarsMaxScoreBlankException;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+public class Cars {
+
+    private static final String DELIMITER = ",";
+    private static final String DUPLICATED_DELIMITER_REGEX = ",+";
+    private final List<Car> cars;
+
+    private Cars(List<Car> cars) {
+        this.cars = Collections.unmodifiableList(cars);
+    }
+
+    public static Cars createCarNameByWord(String input) {
+        List<Car> cars = new ArrayList<>();
+        String[] words = divideWord(input);
+        validate(words);
+
+        for (String carName : words) {
+            cars.add(new Car(carName));
+        }
+        return new Cars(cars);
+    }
+
+    private static void validate(String[] words) {
+        validateDuplicatedWord(words);
+    }
+
+    private static void validateDuplicatedWord(String[] words) {
+        Set<String> uniqueWord = new HashSet<>();
+        for (String word : words) {
+            if (!uniqueWord.add(word)) {
+                throw new CarsDuplicatedNameException();
+            }
+        }
+    }
+
+    private static String[] divideWord(String word) {
+        return word.replaceAll(DUPLICATED_DELIMITER_REGEX, DELIMITER).split(DELIMITER);
+    }
+
+    public void move(CarMoveNumberGenerator carMoveNumberGenerator) {
+        for (Car car : cars) {
+            car.move(carMoveNumberGenerator);
+        }
+    }
+
+    public Winner findWinner() {
+        int maxScore = findMaxScore();
+        return new Winner(cars.stream()
+                .filter(car -> (car.getPosition() == maxScore))
+                .map(Car::getName)
+                .collect(Collectors.toList()));
+    }
+
+    private int findMaxScore() {
+        return cars.stream()
+                .max(Comparator.comparingInt(Car::getPosition))
+                .map(Car::getPosition)
+                .orElseThrow(CarsMaxScoreBlankException::new);
+    }
+
+    public List<Car> getCars() {
+        return cars;
+    }
+}

리뷰: 스트림으로 로직을 바꾸시면 빈 리스트를 선언하지 않고 더 깔끔하게 구현할 수 있을 것 같아요!
@@ -0,0 +1,10 @@
+package racingcar.exception;
+
+public class CarNameLengthException extends IllegalArgumentException{
+
+    private static final String EXCEPTION_MESSAGE_CAR_NAME_LENGTH = "[ERROR] 자동차이름은 5글자 이하입니다";
+
+    public CarNameLengthException() {
+        super(EXCEPTION_MESSAGE_CAR_NAME_LENGTH);
+    }
+}

리뷰: 특별히 커스텀 예외를 사용하신 이유가 있을까요?? 궁금합니다!
@@ -0,0 +1,42 @@
+package racingcar.domain;
+
+import racingcar.exception.CarNameLengthException;
+
+public class Car {
+
+    private static final int CAR_NAME_LENGTH = 5;
+    private static final int MOVABLE_MIN_NUMBER = 4;
+    private final String name;
+    private int position = 0;
+
+    public Car(String name) {
+        validate(name);
+        this.name = name;
+    }
+
+    private void validate(String name) {
+        validateLength(name);
+    }
+
+    private void validateLength(String name) {
+        if (name.length() > CAR_NAME_LENGTH) {
+            throw new CarNameLengthException();
+        }
+    }
+
+    public void move(CarMoveNumberGenerator carMoveNumberGenerator) {
+        final int number = carMoveNumberGenerator.generate();
+
+        if (number >= MOVABLE_MIN_NUMBER) {
+            position++;
+        }
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public int getPosition() {
+        return position;
+    }
+}

리뷰: 감사합니다! 놓치고있던 부분이네요..
@@ -0,0 +1,71 @@
+package racingcar.domain;
+
+import racingcar.exception.CarsDuplicatedNameException;
+import racingcar.exception.CarsMaxScoreBlankException;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+public class Cars {
+
+    private static final String DELIMITER = ",";
+    private static final String DUPLICATED_DELIMITER_REGEX = ",+";
+    private final List<Car> cars;
+
+    private Cars(List<Car> cars) {
+        this.cars = Collections.unmodifiableList(cars);
+    }
+
+    public static Cars createCarNameByWord(String input) {
+        List<Car> cars = new ArrayList<>();
+        String[] words = divideWord(input);
+        validate(words);
+
+        for (String carName : words) {
+            cars.add(new Car(carName));
+        }
+        return new Cars(cars);
+    }
+
+    private static void validate(String[] words) {
+        validateDuplicatedWord(words);
+    }
+
+    private static void validateDuplicatedWord(String[] words) {
+        Set<String> uniqueWord = new HashSet<>();
+        for (String word : words) {
+            if (!uniqueWord.add(word)) {
+                throw new CarsDuplicatedNameException();
+            }
+        }
+    }
+
+    private static String[] divideWord(String word) {
+        return word.replaceAll(DUPLICATED_DELIMITER_REGEX, DELIMITER).split(DELIMITER);
+    }
+
+    public void move(CarMoveNumberGenerator carMoveNumberGenerator) {
+        for (Car car : cars) {
+            car.move(carMoveNumberGenerator);
+        }
+    }
+
+    public Winner findWinner() {
+        int maxScore = findMaxScore();
+        return new Winner(cars.stream()
+                .filter(car -> (car.getPosition() == maxScore))
+                .map(Car::getName)
+                .collect(Collectors.toList()));
+    }
+
+    private int findMaxScore() {
+        return cars.stream()
+                .max(Comparator.comparingInt(Car::getPosition))
+                .map(Car::getPosition)
+                .orElseThrow(CarsMaxScoreBlankException::new);
+    }
+
+    public List<Car> getCars() {
+        return cars;
+    }
+}

리뷰: 오... 좋은글 감사합니다. 방어적복사는 처음 접해보는 개념이네요!
@@ -0,0 +1,54 @@
+package racingcar.view;
+
+import racingcar.domain.Car;
+import racingcar.domain.Cars;
+import racingcar.domain.Winner;
+
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+public class OutputView {
+
+    private static final String MESSAGE_INPUT_CAR_NAME = "경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)";
+    private static final String MESSAGE_INPUT_TRY = "시도할 회수는 몇회인가요?";
+    private static final String MESSAGE_RESULT = "실행 결과";
+    private static final String MOVE_MARK = "-";
+    private static final String JOIN_NAME_AND_POSITION = " : ";
+    private static final String WINNER_DELIMITER = ",";
+    private static final String MESSAGE_WINNER_PREFIX = "최종 우승자 : ";
+    private static final String MESSAGE_WINNER_SUFFIX = "";
+
+    public void printInputCarName() {
+        System.out.println(MESSAGE_INPUT_CAR_NAME);
+    }
+
+    public void printExceptionMessage(IllegalArgumentException exceptionMessage) {
+        System.out.println(exceptionMessage.getMessage());
+    }
+
+    public void printInputTry() {
+        System.out.println(MESSAGE_INPUT_TRY);
+    }
+
+    public void printBlank() {
+        System.out.println();
+    }
+
+    public void printResult(Cars cars) {
+        System.out.println(MESSAGE_RESULT);
+        for(Car car : cars.getCars()){
+            System.out.println(car.getName() + JOIN_NAME_AND_POSITION + convertMoveMark(car.getPosition()));
+        }
+        printBlank();
+    }
+
+    private String convertMoveMark(int position) {
+        return Stream.generate(() -> MOVE_MARK).limit(position).collect(Collectors.joining());
+    }
+
+    public void printWinner(Winner winner) {
+        String message = winner.getWinner().stream()
+                .collect(Collectors.joining(WINNER_DELIMITER, MESSAGE_WINNER_PREFIX, MESSAGE_WINNER_SUFFIX));
+        System.out.println(message);
+    }
+}

리뷰: 이런,, 스트림을 너무 남발했네요.. `repeat()` 라는 좋은 함수가 있는데 말이죠 ㅠ
@@ -0,0 +1,22 @@
+package racingcar.domain;
+
+public class RacingCarGame {
+
+    private final Cars cars;
+
+    public RacingCarGame(Cars cars) {
+        this.cars = cars;
+    }
+
+    public void move() {
+        cars.move(new CarRandomMoveNumberGenerator());
+    }
+
+    public Winner findWinner() {
+        return cars.findWinner();
+    }
+
+    public Cars getCars() {
+        return cars;
+    }
+}

리뷰: 원래 `RacingCarGame` 에서 시도횟수를 가지고있어서 `Cars` 를 시도횟수만큼 움직이는 로직이있었는데 controller 쪽으로 해당 로직을 옮기는바람에 쓸모없는 클래스가 되었네요.. 좋은 지적 감사합니다..!
@@ -0,0 +1,47 @@
+package racingcar.domain;
+
+import racingcar.exception.TryCommandNumberException;
+import racingcar.exception.TryCommandRangeException;
+
+public class TryCommand {
+
+    private static final int MIN_TRY = 1;
+    private static final int MAX_TRY = 100000;
+    private int tryCount;
+
+    private TryCommand(int tryCount) {
+        this.tryCount = tryCount;
+    }
+
+    public static TryCommand createTryCommandByString(String input) {
+        int number = convertInt(input);
+        validate(number);
+        return new TryCommand(number);
+    }
+
+    private static void validate(int number) {
+        validateRange(number);
+    }
+
+    private static void validateRange(int number) {
+        if(number < MIN_TRY || number > MAX_TRY) {
+            throw new TryCommandRangeException(MIN_TRY, MAX_TRY);
+        }
+    }
+
+    private static int convertInt(String input) {
+        try{
+            return Integer.parseInt(input);
+        }catch (NumberFormatException exception) {
+            throw new TryCommandNumberException();
+        }
+    }
+
+    public boolean tryMove() {
+        if(tryCount > 0) {
+            tryCount--;
+            return true;
+        }
+        return false;
+    }
+}

리뷰: 불변으로 해도 값을 변경하는 방법이 있었군요..
DDD 에서 나오는 개념이라 MVC 에는 어떻게 적용할지는 고민을 해봐야겠네요..
좋은글 감사합니다!
@@ -0,0 +1,10 @@
+package racingcar.exception;
+
+public class CarNameLengthException extends IllegalArgumentException{
+
+    private static final String EXCEPTION_MESSAGE_CAR_NAME_LENGTH = "[ERROR] 자동차이름은 5글자 이하입니다";
+
+    public CarNameLengthException() {
+        super(EXCEPTION_MESSAGE_CAR_NAME_LENGTH);
+    }
+}

리뷰: [커스텀예외](https://tecoble.techcourse.co.kr/post/2020-08-17-custom-exception/)

커스텀 예외들은 장단점이 있습니다! 그래서 정답은 없지만 제가 사용한 이유는 
첫째로클래스 이름으로 어떤 예외인지 바로 알 수 있고
둘째로 보통 도메인 코드를 볼때 중요한것은 그 도메인 역할과 관련된 로직이지, 예외처리를 어떻게 하는지가 중요한게 아니기때문에 예외를 따로빼내어 좀 더 보기 편한게 만들기 위함입니다!
셋째로 대부분 Spring MVC 프로젝트에서는 커스텀 예외를 사용하기에 연습차원에서 커스텀 예외를 사용하고 있습니다.
@@ -0,0 +1,32 @@
+package baseball.util
+
+// Message
+const val PRINT_NOTHING_MESSAGE = "낫싱"
+const val PRINT_STRIKE_MESSAGE = "%d스트라이크"
+const val PRINT_BALL_MESSAGE = "%d볼"
+const val PRINT_STRIKE_BALL_MESSAGE = "%d볼 %d스트라이크"
+const val START_GAME_MASSAGE = "숫자 야구 게임을 시작합니다."
+const val QUIT_GAME_MASSAGE = "3개의 숫자를 모두 맞히셨습니다! 게임 종료"
+const val SELECT_COMMAND_MESSAGE = "게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요."
+const val USER_INPUT_NUMBER_MESSAGE = "숫자를 입력해주세요: "
+// Error
+const val ERROR_INVALID_INPUT_MESSAGE = "잘못된 값을 입력했습니다"
+const val ERROR_INVALID_COMMAND_MESSAGE = "1과 2만 입력해주세요."
+
+// Command
+const val RETRY_COMMAND = "1"
+const val QUIT_COMMAND = "2"
+
+// Code
+const val END_CODE = 2
+const val PLAYING_CODE = 1
+
+// Size, Number
+const val MAX_SIZE = 3
+const val MAX_NUMBER = 9
+const val MIN_NUMBER = 1
+
+// String
+const val EMPTY_STRING = ""
+
+

리뷰: util 폴더를 사용해 EMPTY_STRING과 같이 사소한 값들까지 상수화를 하신 덕분에 가독성이 좋네요. 배워갑니다!
@@ -0,0 +1,59 @@
+package baseball.game
+
+import baseball.game.service.Game
+import baseball.model.AnswerBoard
+import baseball.model.Computer
+import baseball.util.*
+import baseball.view.User
+import baseball.view.validator.InputValidator
+
+class Baseball(
+    private val user: User,
+    private val computer: Computer,
+    private val answerBoard: AnswerBoard,
+) : Game {
+    private var gameState = PLAYING_CODE
+    private var answer = computer.createAnswer()
+
+    override fun play() {
+        println(START_GAME_MASSAGE)
+        process()
+    }
+
+    // 게임 진행
+    override fun process() {
+        do {
+            val userNumber = user.createNumber()
+
+            answerBoard.createResult(answer, userNumber)
+            println(answerBoard.printResult())
+
+            finish()
+        } while (isPlaying())
+    }
+
+    private fun isPlaying(): Boolean = gameState != END_CODE
+
+    override fun quit() {
+        gameState = END_CODE
+    }
+
+    override fun retry() {
+        answer = computer.createAnswer()
+        answerBoard.clearState()
+    }
+
+    private fun printFinishMessage() = println(QUIT_GAME_MASSAGE + "\n" + SELECT_COMMAND_MESSAGE)
+
+    private fun finish() {
+        if (answerBoard.isThreeStrike()) {
+            printFinishMessage()
+            when (InputValidator.validateUserCommand(readLine()!!)) {
+                RETRY_COMMAND -> retry()
+                QUIT_COMMAND -> quit()
+            }
+        }
+    }
+
+}
+

리뷰: MVC 모델을 사용하신 것 같습니다. 
Baseball Class는 Controller의 역할을 하는 것 같은데, Controller에서 직접 입력을 받는 것보다는
사용자와 직접적으로 맞닿는 View에서 입력을 받아와 Controller에서 사용하는 것이 좋을 것 같습니다.
@@ -0,0 +1,12 @@
+package baseball.view
+
+import baseball.util.USER_INPUT_NUMBER_MESSAGE
+import baseball.view.validator.InputValidator
+
+class User {
+    fun createNumber(): String {
+        print(USER_INPUT_NUMBER_MESSAGE)
+        val userNumber = readLine()!!
+        return InputValidator.validateUserNumber(userNumber)
+    }
+}
\ No newline at end of file

리뷰: 사용자로부터 입력을 받아와 검사된 값을 돌려보내주는 함수인 것 같습니다.
create라는 함수명을 보았을 때 저는 "사용자가 번호를 만드나? 사용자가 기계의 역할을 하는 것인가?"라는 생각이 들었습니다.
input과 어울리는 함수명이면 좋을 것 같습니다.

또한, 저도 이 부분은 조금 고민되는 부분이지만, 
MVC 패턴의 View에서 다른 클래스를 가져와 분기가 발생할 수 있는 로직을 사용하는 것은 패턴의 규칙에 맞지 않는다고 생각합니다.
@@ -0,0 +1,17 @@
+package baseball.view.validator
+
+import baseball.util.*
+
+object InputValidator {
+
+    fun validateUserNumber(userNumber: String): String {
+        require(userNumber.length == MAX_SIZE) { ERROR_INVALID_INPUT_MESSAGE }
+        return userNumber
+    }
+
+    fun validateUserCommand(command: String): String {
+        require(command == RETRY_COMMAND || command == QUIT_COMMAND) { ERROR_INVALID_COMMAND_MESSAGE }
+        return command
+    }
+}
+

리뷰: 민재님께서 저에게 require에 대해 알려주셨죠..! 잘 쓰고 있습니다! 감사합니다!
@@ -0,0 +1,59 @@
+package baseball.model
+
+import baseball.util.*
+
+
+class AnswerBoard {
+    // 상태를 모두 아웃으로 초기화
+    private val stateList = MutableList(MAX_SIZE) { BaseballState.OUT }
+
+
+    private fun createCount(): Triple<Int, Int, Int> {
+        var strikeCount = 0
+        var ballCount = 0
+        var outCount = 0
+
+        stateList.forEach { state ->
+            when (state) {
+                BaseballState.STRIKE -> strikeCount++
+                BaseballState.BALL -> ballCount++
+                BaseballState.OUT -> outCount++
+            }
+        }
+        return Triple(strikeCount, ballCount, outCount)
+    }
+
+    fun createResult(answer: String, userNumber: String) {
+        answer.forEachIndexed { answerIndex, answerNumber ->
+            if (answerNumber == userNumber[answerIndex]) {
+                stateList[answerIndex] = BaseballState.STRIKE
+            }
+            if (answerNumber != userNumber[answerIndex] && answer.contains(userNumber[answerIndex])) {
+                stateList[answerIndex] = BaseballState.BALL
+            }
+            if (answerNumber != userNumber[answerIndex] && !answer.contains(userNumber[answerIndex])) {
+                stateList[answerIndex] = BaseballState.OUT
+            }
+        }
+    }
+
+    fun printResult(): String {
+        val (strikeCount, ballCount, outCount) = createCount()
+        var message = EMPTY_STRING
+
+        // 아웃일 때 출력
+        if (outCount == stateList.size) message = PRINT_NOTHING_MESSAGE
+        // 스트라이크일때 출력
+        if (ballCount == 0 && strikeCount != 0) message = PRINT_STRIKE_MESSAGE.format(strikeCount)
+        // 볼일때 출력
+        if (ballCount != 0 && strikeCount == 0) message = PRINT_BALL_MESSAGE.format(ballCount)
+        // 스트라이크 볼일 때
+        if (ballCount != 0 && strikeCount != 0) message = PRINT_STRIKE_BALL_MESSAGE.format(ballCount, strikeCount)
+
+        return message
+    }
+
+    fun isThreeStrike(): Boolean = stateList.all { state -> state == BaseballState.STRIKE }
+
+    fun clearState() = stateList.replaceAll { BaseballState.OUT }
+}

리뷰: createCount라는 함수명은 "개수를 **_만든다_**"라는 의미인 것 같습니다. 
하지만 함수의 역할은 "스트라이크, 볼, 아웃의 개수를 **_센다_**"인 것 같습니다.
함수명과 세가지의 기능을 나누는 것은 어떨까요?
countStrike, countBall, countOut과 같이요!
@@ -0,0 +1,59 @@
+package baseball.model
+
+import baseball.util.*
+
+
+class AnswerBoard {
+    // 상태를 모두 아웃으로 초기화
+    private val stateList = MutableList(MAX_SIZE) { BaseballState.OUT }
+
+
+    private fun createCount(): Triple<Int, Int, Int> {
+        var strikeCount = 0
+        var ballCount = 0
+        var outCount = 0
+
+        stateList.forEach { state ->
+            when (state) {
+                BaseballState.STRIKE -> strikeCount++
+                BaseballState.BALL -> ballCount++
+                BaseballState.OUT -> outCount++
+            }
+        }
+        return Triple(strikeCount, ballCount, outCount)
+    }
+
+    fun createResult(answer: String, userNumber: String) {
+        answer.forEachIndexed { answerIndex, answerNumber ->
+            if (answerNumber == userNumber[answerIndex]) {
+                stateList[answerIndex] = BaseballState.STRIKE
+            }
+            if (answerNumber != userNumber[answerIndex] && answer.contains(userNumber[answerIndex])) {
+                stateList[answerIndex] = BaseballState.BALL
+            }
+            if (answerNumber != userNumber[answerIndex] && !answer.contains(userNumber[answerIndex])) {
+                stateList[answerIndex] = BaseballState.OUT
+            }
+        }
+    }
+
+    fun printResult(): String {
+        val (strikeCount, ballCount, outCount) = createCount()
+        var message = EMPTY_STRING
+
+        // 아웃일 때 출력
+        if (outCount == stateList.size) message = PRINT_NOTHING_MESSAGE
+        // 스트라이크일때 출력
+        if (ballCount == 0 && strikeCount != 0) message = PRINT_STRIKE_MESSAGE.format(strikeCount)
+        // 볼일때 출력
+        if (ballCount != 0 && strikeCount == 0) message = PRINT_BALL_MESSAGE.format(ballCount)
+        // 스트라이크 볼일 때
+        if (ballCount != 0 && strikeCount != 0) message = PRINT_STRIKE_BALL_MESSAGE.format(ballCount, strikeCount)
+
+        return message
+    }
+
+    fun isThreeStrike(): Boolean = stateList.all { state -> state == BaseballState.STRIKE }
+
+    fun clearState() = stateList.replaceAll { BaseballState.OUT }
+}

리뷰: 변수명이 동사 + 명사로 한다면 함수명과 불일치 하지 않을까요 ?!?
이름 짓기에서 중요한 명사는 앞에 쓰는 것이 좋닫고 합니다 !! ex) countStrike -> strikeCount
@@ -0,0 +1,12 @@
+package baseball.view
+
+import baseball.util.USER_INPUT_NUMBER_MESSAGE
+import baseball.view.validator.InputValidator
+
+class User {
+    fun createNumber(): String {
+        print(USER_INPUT_NUMBER_MESSAGE)
+        val userNumber = readLine()!!
+        return InputValidator.validateUserNumber(userNumber)
+    }
+}
\ No newline at end of file

리뷰: 이 부분도 지금까지도 고민이였는데 예외처리를 따로 해주는 것이 좋다고 합니다 !!