Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[코드 리뷰용 PR] - 재구현 스터디 #250

Open
wants to merge 25 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
b380ee1
docs(README): 핵심 기능과 기능 요구사항을 정리한다
sh1mj1 Nov 28, 2023
bc9d9dd
feat(Referee): 사용자가 입력한 답과 정답을 비교해서 볼, 스트라이크 개수를 알려준다
sh1mj1 Nov 28, 2023
8224533
feat(BaseBallNumbers): 사용자가 입력한 세자리 수의 유효성을 검증한다
sh1mj1 Nov 28, 2023
f096adc
feat(BaseBallNumber): 사용자가 입력한 각 숫자의 유효성을 검증한다
sh1mj1 Nov 28, 2023
bc5992d
test(BaseBallNumbers): 정상적인 숫자 셋인 경우 테스트를 추가한다
sh1mj1 Nov 28, 2023
b3fbddb
feat(Answer): 랜덤으로 정답을 생성해주는 클래스를 추가한다
sh1mj1 Nov 28, 2023
a22be16
feat(InputView.readNumbers): 사용자의 입력을 숫자로 가져온다
sh1mj1 Nov 28, 2023
b37ca8e
feat(NumbersConverter): 사용자의 입력을 숫자 야구 게임의 숫자 셋으로 변환한다
sh1mj1 Nov 28, 2023
a3aa77c
feat(InputView): 게임 종료시의 사용자의 입력을 커맨드로 변환한다
sh1mj1 Nov 28, 2023
e6badf7
feat(OutputView): 게임의 한 턴의 결과를 보여준다
sh1mj1 Nov 28, 2023
8df6aa9
feat(OutputView): 게임 시 프롬프트 메시지를 출력하는 메서드를 구현한다
sh1mj1 Nov 28, 2023
b9701ec
fix(Referee): compare 메서드의 동작의 버그를 고친다
sh1mj1 Nov 28, 2023
21434dc
feat(GameController): 구현한 동작들을 통합하여 시스템을 완성한다
sh1mj1 Nov 28, 2023
e68ee10
feat(NumbersConverter): 숫자형을 숫자 야구 게임의 숫자 셋으로 변환하는 함수를 최상위 확장 함수로 변경한다
sh1mj1 Nov 28, 2023
40fdfa5
style(GameController): 심판 역할과 정답을 생성해내는 역할을 생성주 주입을 하도록 변경한다.
sh1mj1 Nov 28, 2023
c484f78
test(NumbersConverterTest): 테스트 대상 파일이 최상위 함수로 변경된 것을 반영하여 테스트 코드를 수정한다
sh1mj1 Nov 28, 2023
3cadfe2
style(AnswerGenerator): 기존 `Answer` 클래스의 이름을 `AnswerGenerator` 로 변경한다
sh1mj1 Nov 28, 2023
3ed274c
refactor(InputView): 잘못된 커맨드를 입력하면 예외를 던진다
sh1mj1 Nov 28, 2023
796f037
refactor(InputView): 입력 커맨드에 대한 유효성 검증의 위치를 옮긴다
sh1mj1 Nov 28, 2023
f81942e
test(NumbersConverter): 입력을 커맨드로 변환하고 유효성 검증하는 테스트를 추가한다
sh1mj1 Nov 28, 2023
7c75a95
refactor(RefereeTest): 입력값과 정답을 비교해서 결과를 알려주는 테스트에서 여러 케이스를 한번에 테스트한다
sh1mj1 Nov 28, 2023
905a9ba
refactor(BaseballNumbersTest): 게임에서 사용되는 숫자 셋을 테스트할 때 여러 케이스를 한번에 테스트한다
sh1mj1 Nov 28, 2023
1dedcee
docs(README): 최종 시스템 UML 을 추가한다
sh1mj1 Nov 28, 2023
cd16128
style(Referee): 코드의 가독성을 개선한다
sh1mj1 Nov 28, 2023
765dc92
refactor(RandomAnswerGenerator): 랜덤 수를 생성하는 로직을 개선한다
sh1mj1 Nov 28, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# 핵심 기능

세 자리 수를 정답과 비교해서 결과를 알려준다.

# 기능 요구사항

* 세 자리 수(입력수)를 정답과 비교한다.

* 정답을 생성한다.

* 입력수를 입력받는다.

* 비교 결과를 출력한다.

* 재시작 여부를 입력받는다.

# 최종 UML

![img.png](img.png)

Binary file added docs/img.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 9 additions & 1 deletion src/main/kotlin/baseball/Application.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
package baseball

import baseball.controller.GameController
import baseball.model.RandomAnswerGenerator
import baseball.model.Referee

fun main() {
TODO("프로그램 구현")
val gameController = GameController(
referee = Referee(),
answerGenerator = RandomAnswerGenerator()
)
gameController.start()
}
33 changes: 33 additions & 0 deletions src/main/kotlin/baseball/controller/GameController.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package baseball.controller

import baseball.model.AnswerGenerator
import baseball.model.BaseballNumbers
import baseball.model.Referee
import baseball.view.Command
import baseball.view.InputView
import baseball.view.OutputView

class GameController(
val referee: Referee,
val answerGenerator: AnswerGenerator
) {
private val inputView = InputView()
private val outputView = OutputView()

fun start() {
outputView.showStartPrompt()
do {
val answer = answerGenerator.generate()
oneCycleGame(answer)
outputView.showSuccessPrompt()
} while (inputView.readCommand().toCommand() == Command.RESTART)
}

private fun oneCycleGame(answer: BaseballNumbers) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

코틀린 코딩 컨벤션에 함수, 메서드의 이름은 동사 또는 동사구로 짓는 것으로 설명하고 있는데 이 메서드 이름은 의도하신 걸까요?
https://kotlinlang.org/docs/coding-conventions.html#choose-good-names

do {
val userBaseballNumbers = (inputView.readNumbers()).toBaseballNumbers()
val ballAndStrike = referee.compare(userBaseballNumbers, answer)
outputView.showTurnResult(ballAndStrike)
} while (!ballAndStrike.isSuccess())
}
}
35 changes: 35 additions & 0 deletions src/main/kotlin/baseball/controller/NumbersConverter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package baseball.controller

import baseball.model.BaseballNumber
import baseball.model.BaseballNumber.Companion.START_NUMBER
import baseball.model.BaseballNumbers
import baseball.model.BaseballNumbers.Companion.NUMBERS_DIGIT
import baseball.view.Command
import baseball.view.InputView.Companion.COMMAND_PROMPT


fun Int.toBaseballNumbers(): BaseballNumbers {
require(this >= START_NUMBER) {
INVALID_NUMBERS
}

var tempNumber = this
val numbers = mutableListOf<BaseballNumber>()
while (tempNumber > 0) {
val digit = tempNumber % 10
numbers.add(0, BaseballNumber(digit))
tempNumber /= 10
}
return BaseballNumbers(numbers)
}

fun Int.toCommand(): Command {
val command = Command.entries.firstOrNull { it.command == this }
require(command != null) {
INVALID_COMMAND
}
return command
}

const val INVALID_NUMBERS = "0 이상의 $NUMBERS_DIGIT 자리 수를 입력해주세요."
val INVALID_COMMAND = "잘못된 입력입니다.\n $COMMAND_PROMPT"
5 changes: 5 additions & 0 deletions src/main/kotlin/baseball/model/AnswerGenerator.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package baseball.model

interface AnswerGenerator {
fun generate(): BaseballNumbers
}
28 changes: 28 additions & 0 deletions src/main/kotlin/baseball/model/BallAndStrike.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package baseball.model

import baseball.model.BaseballNumbers.Companion.NUMBERS_DIGIT

data class BallAndStrike(
val strikeCount: Int,
val ballCount: Int
) {

fun isSuccess() = strikeCount == NUMBERS_DIGIT

override fun toString(): String {
return when {
(strikeCount > 0 && ballCount > 0) ->
"$ballCount$BALL_SUFFIX $strikeCount$STRIKE_SUFFIX"

(strikeCount > 0) -> "$strikeCount$STRIKE_SUFFIX"
(ballCount > 0) -> "$ballCount$BALL_SUFFIX"
else -> NOTHING
}
}

companion object {
const val BALL_SUFFIX = "볼"
const val STRIKE_SUFFIX = "스트라이크"
const val NOTHING = "낫싱"
}
}
15 changes: 15 additions & 0 deletions src/main/kotlin/baseball/model/BaseballNumber.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package baseball.model

data class BaseballNumber(val number: Int) {
init {
require(number in START_NUMBER..END_NUMBER) {
INVALID_NUMBER_RANGE
}
}

companion object {
const val START_NUMBER = 1
const val END_NUMBER = 9
const val INVALID_NUMBER_RANGE = "숫자는 $START_NUMBER 와 $END_NUMBER 사이의 수여야 합니다."
}
}
19 changes: 19 additions & 0 deletions src/main/kotlin/baseball/model/BaseballNumbers.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package baseball.model

data class BaseballNumbers(val numbers: List<BaseballNumber>) {

init {
require(numbers.size == 3) {
INVALID_NUMBERS_SIZE
}
require(numbers.distinct().size == 3) {
NUMBERS_DUPLICATED
}
}

companion object {
const val NUMBERS_DIGIT = 3
const val INVALID_NUMBERS_SIZE = "숫자는 $NUMBERS_DIGIT 자리 수가 되어야 합니다."
const val NUMBERS_DUPLICATED = "숫자에 중복된 수가 있습니다."
}
}
16 changes: 16 additions & 0 deletions src/main/kotlin/baseball/model/RandomAnswerGenerator.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package baseball.model

import baseball.model.BaseballNumber.Companion.END_NUMBER
import baseball.model.BaseballNumber.Companion.START_NUMBER
import baseball.model.BaseballNumbers.Companion.NUMBERS_DIGIT
import camp.nextstep.edu.missionutils.Randoms.pickNumberInRange

class RandomAnswerGenerator : AnswerGenerator {
override fun generate(): BaseballNumbers {
val numbers = mutableSetOf<BaseballNumber>()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Set으로 depth를 줄이셨군요
저는 예시 코드를 수정할 생각을 안해서 depth를 줄일 고민을 하지 않았었는데 좋은 것 같습니다!

while (numbers.size < NUMBERS_DIGIT) {
numbers.add(BaseballNumber(pickNumberInRange(START_NUMBER, END_NUMBER)))
}
return BaseballNumbers(numbers.toList())
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저도 Generator 인터페이스를 만들어 상속 받는 방식으로 구현할지 고민하다 object로 NumberGenerator를 구현했는데 어떤 과정을 거쳐 이렇게 구현하게 되셨는지 궁금합니다!

21 changes: 21 additions & 0 deletions src/main/kotlin/baseball/model/Referee.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package baseball.model

class Referee {
fun compare(inputNumbers: BaseballNumbers, answer: BaseballNumbers): BallAndStrike {
var strikeCount = 0
var ballCount = 0
for (i in 0 until inputNumbers.numbers.size) {
val inputNumber = inputNumbers.numbers[i]
val answerNumber = answer.numbers[i]

if (inputNumber == answerNumber) {
strikeCount++
} else if (answer.numbers.any { it == inputNumber }) {
ballCount++
}
}

return BallAndStrike(strikeCount, ballCount)
}

}
6 changes: 6 additions & 0 deletions src/main/kotlin/baseball/view/Command.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package baseball.view

enum class Command(val commandName: String, val command: Int) {
RESTART("새로 시작", 1),
EXIT("종료", 2),
}
26 changes: 26 additions & 0 deletions src/main/kotlin/baseball/view/InputView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package baseball.view


import camp.nextstep.edu.missionutils.Console.readLine

class InputView {
fun readNumbers(): Int {
print(INPUT_NUMBERS_PROMPT)
return readLine().toIntOrNull() ?: -1
}

fun readCommand(): Int {
println(COMMAND_PROMPT)
return readLine().toIntOrNull() ?: -1
}

companion object {
private const val DELIMITER = " : "
const val INPUT_NUMBERS_PROMPT = "숫자를 입력해주세요$DELIMITER"

val COMMAND_PROMPT =
"게임을 ${Command.RESTART.commandName}하려면 ${Command.RESTART.command}, " +
"${Command.EXIT.commandName}하려면 ${Command.EXIT.command}를 입력하세요."

}
}
15 changes: 15 additions & 0 deletions src/main/kotlin/baseball/view/OutputView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package baseball.view

import baseball.model.BallAndStrike

class OutputView {
fun showStartPrompt() = println(START_PROMPT)
fun showSuccessPrompt() = println(SUCCESS_PROMPT)

fun showTurnResult(ballAndStrike: BallAndStrike) = println(ballAndStrike)

companion object {
const val START_PROMPT = "숫자 야구 게임을 시작합니다."
const val SUCCESS_PROMPT = "3개의 숫자를 모두 맞히셨습니다! 게임 종료"
}
}
64 changes: 64 additions & 0 deletions src/test/kotlin/baseball/controller/NumbersConverterTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package baseball.controller

import baseball.model.BaseballNumber
import baseball.model.BaseballNumbers
import baseball.view.Command
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.assertThrows
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.Arguments
import org.junit.jupiter.params.provider.CsvSource
import org.junit.jupiter.params.provider.MethodSource
import org.junit.jupiter.params.provider.ValueSource
import java.util.stream.Stream

class NumbersConverterTest {

@ParameterizedTest
@CsvSource("123, 1, 2, 3", "324, 3, 2, 4", "281, 2, 8, 1")
fun `입력받은 수를 게임의 숫자 셋으로 변환한다`(inputNumber: Int, firstDigit: Int, secondDigit: Int, thirdDigit: Int) {
val expected = BaseballNumbers(
listOf(BaseballNumber(firstDigit), BaseballNumber(secondDigit), BaseballNumber(thirdDigit))
)

val result = inputNumber.toBaseballNumbers()
assertThat(result).isEqualTo(expected)
}

@ParameterizedTest
@ValueSource(ints = [-10, -9, -1, 0])
fun `입력받은 수가 0 이하이면, 예외를 던진다`(inputNumber: Int) {
val exception = assertThrows<IllegalArgumentException> {
inputNumber.toBaseballNumbers()
}
assertThat(exception.message).isEqualTo(INVALID_NUMBERS)
}

@ParameterizedTest
@MethodSource("provideInputToCommand")
fun `입력받은 수를 커맨드로 변환한다`(inputNumber: Int, expectedCommand: Command) {
val result = inputNumber.toCommand()
assertThat(result).isEqualTo(expectedCommand)
}

@ParameterizedTest
@ValueSource(ints = [0, 3, 4, -1, -2, 100])
fun `입력받은 수가 커맨드가 아닌 수라면 예외를 던진다`(inputNumber: Int) {
val exception = assertThrows<IllegalArgumentException> {
inputNumber.toCommand()
}
assertThat(exception.message).isEqualTo(INVALID_COMMAND)
}

companion object {
@JvmStatic
fun provideInputToCommand(): Stream<Arguments> = Stream.of(
Arguments.of(
1, Command.RESTART
),
Arguments.of(
2, Command.EXIT
),
)
}
}
27 changes: 27 additions & 0 deletions src/test/kotlin/baseball/model/BaseballNumberTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package baseball.model

import baseball.model.BaseballNumber.Companion.INVALID_NUMBER_RANGE
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.assertDoesNotThrow
import org.junit.jupiter.api.assertThrows
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ValueSource

class BaseballNumberTest {
@ParameterizedTest
@ValueSource(ints = [0, -1, 10, 100])
fun `사용자가 입력한 숫자가 1~9 가 아니면 예외를 던진다`(inputNumber: Int) {
val exception = assertThrows<IllegalArgumentException> {
BaseballNumber(inputNumber)
}
assertThat(exception.message).isEqualTo(INVALID_NUMBER_RANGE)
}

@ParameterizedTest
@ValueSource(ints = [1, 2, 3, 4, 5, 6, 7, 8, 9])
fun `사용자가 입력한 숫자가 1~9 가 이면 정상이다`(inputNumber: Int) {
assertDoesNotThrow {
BaseballNumber(inputNumber)
}
}
}
Loading