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입니다!] - 재구현 스터디 #236

Open
wants to merge 51 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
b615aea
docs: write feature list into README.md
getupminaaa Oct 28, 2023
d0350f4
docs: update README.md
Oct 28, 2023
0f1a768
docs: update README.MD
Oct 28, 2023
18ae353
feat: 경주할 자동차 이름을 입력받는 기능 구현
Oct 28, 2023
45e1172
feat: 경주할 자동차 이름이 유효한 지 검사하는 기능 구현
Oct 28, 2023
0587a28
feat: “경주할 자동차 이름을 입력하세요."를 출력하는 기능 구현
Oct 29, 2023
f7d197c
feat: 몇 번의 이동(시도)를 할 지 입력받는 기능 구현
Oct 29, 2023
a07cc7c
feat: 입력받는 시도 횟수의 값이 유효한 지 검사하는 기능 구현
Oct 29, 2023
87bbdd6
feat: “시도할 횟수는 몇 회인가요?”를 출력하는 기능 구현
Oct 29, 2023
b1fb645
docs: Update README.md
getupminaaa Oct 29, 2023
24a689a
feat: 무작위 수를 생성하는 기능 구현
Oct 29, 2023
8838fe9
feat: 자동차가 전진하는지 정지하는지 판단하는 기능 구현
Oct 29, 2023
1a39005
docs: Update README.md
getupminaaa Oct 29, 2023
6885c1a
fix: regex가 한자리 숫자 이외의 여러 자리 숫자로 구성될 수 있도록 수정
Oct 29, 2023
830e7a0
feat: Car 데이터 클래스 생성
Oct 29, 2023
f9389e3
feat: Car 데이터 클래스 초기화
Oct 29, 2023
a373b2c
feat: 각 자동차마다 움직인 거리 저장하는 기능 구현
Oct 29, 2023
5ddaae7
feat: 각 차수별 실행 결과를 출력하는 기능 구현
Oct 29, 2023
fa304a9
fix: 시도 횟수 입력 값이 1 이상의 정수인지 판단하는 기능 수정
Oct 29, 2023
2226495
feat: 우승자를 판별하는 기능 구현
Oct 29, 2023
e1e5331
feat: 우승자를 출력하는 기능 구현
Oct 29, 2023
55d31c6
refactor: getRacingDistances 함수의 depth를 낮추기 위해 함수를 분리
Oct 29, 2023
68543a9
refactor: 예외처리와 예외처리 여부를 판별하는 함수를 합침
Oct 29, 2023
26dff7b
test: 이름 중복에 대한 예외처리 테스트 코드 작성
Oct 29, 2023
e5107a3
test: 이름 길이에 대한 예외처리 테스트 코드 작성
Oct 29, 2023
0e47d26
test: 이름 공백에 대한 예외 처리 테스트 코드 작성
Oct 29, 2023
c620702
test: 시도 횟수 입력에 대한 예외 처리
Oct 29, 2023
1b72d7f
test: 시도 횟수 입력에 대한 예외 처리 테스트 케이스에 공백 추가
Oct 29, 2023
f15e1d2
test: 전진 정지를 판단하는 기능에 대한 테스트 코드 작성
Oct 31, 2023
8b7ee71
test: 우승자를 판별하는 기능에 대해 테스트코드 작성
Oct 31, 2023
388d7f4
refactor: Racing 클래스 파라미터 제거 및 Racing 관련 함수들에 파라미터 추가
Oct 31, 2023
ffbdf01
test: 우승자가 여러 명일 경우에 대해 테스트 코드 작성
Oct 31, 2023
c97b43e
test: 자동차가 랜덤값에 따라 움직인 거리를 정상적으로 저장하는 지에 대한 테스트 코드 작성
Oct 31, 2023
ae6ac32
test: 최종 우승자를 출력 형식에 맞게 출력하는 지에 대해 테스트 코드 작성
Oct 31, 2023
397aa67
test: 경기 과정을 출력형식에 맞게 출력하는 지에 대해 테스트 코드 작성
Oct 31, 2023
6bc811a
refector: print문을 메서드로 리팩터링
Oct 31, 2023
3babfca
test: 경기 시작 출력이 형식에 맞는 지에 대해 테스트 코드 작성
Oct 31, 2023
08fb80d
test: 시도할 횟수 입력을 안내하는 문구가 출력 형식에 맞는 지에 대해 테스트 코드 작성
Oct 31, 2023
50e8ace
Merge branch 'develop'
Oct 31, 2023
9bbe173
docs: 0이 아닌 정수를 0보다 큰 정수로 정정
Oct 31, 2023
4a61b98
refactor: Constant 클래스 생성 및 상수 분리
Oct 31, 2023
f358cec
fix: test 클래스에 import 추가
Oct 31, 2023
4a0784d
refactor: racing 함수의 출력 부분과 로직 부분 분리
Oct 31, 2023
9456a40
refactor: 검증 함수를 Validator 클래스로 분리
Oct 31, 2023
beea07d
refactor: 에러메세지 분리
Oct 31, 2023
05ee616
refactor: main 함수 메서드 분리
Oct 31, 2023
2a3ea1e
refactor: main함수에 열거된 함수들을 확장함수로 분리
Oct 31, 2023
3c50f41
refactor: 게임 시작 시 출력 문구에 "이름은 쉼표(,) 기준으로 구분)" 추가
Oct 31, 2023
f492c8f
refactor: 하드코딩한 텍스트를 Const 상수로 분리
Nov 2, 2023
c8a1010
refactor: 디렉터리 구조를 MVC에 맞게 재구현
Nov 2, 2023
76e39ea
refactor: MVC구조에 맞게 코드 분리
Nov 8, 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
28 changes: 28 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# 기능 목록

### 입력

- 경주할 자동차 이름을 입력하는 기능
- 몇 번의 이동을 할 지 입력하는 기능

### 출력

- `“경주할 자동차 이름을 입력하세요."`를 출력하는 기능
- `“시도할 횟수는 몇 회인가요?”`를 출력하는 기능
- `“최종 우승자 : "`를 출력하는 기능
- 각 차수별 실행 결과를 출력하는 기능

### 경주

- 무작위 수를 생성하는 기능
- 자동차가 전진하는지 정지하는지 판단하는 기능 (무작위 수가 4 이상, 미만)
- 우승자를 판별하는 기능
- 각 자동차마다 움직인 거리 저장 기능

### 검사 및 예외처리

- 자동차 이름이 올바른 값인 지 검사하는 기능
- 올바른값: Null X, 5자리 이하, 중복 X
- 사용자가 몇 번 이동할 지 입력 할 때 숫자를 올바른 값을 입력하는 지 검사하는 기능
- 올바른값: Null X, 0 보다 큰 정수
Comment on lines +24 to +27

Choose a reason for hiding this comment

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

예외 처리에 대해 꼼꼼하게 정리해주시고 README를 두분 모두 깔끔하게 정리해주셔서 여러모로 많이 배우네요:)

- 사용자 입력 값에 대해 올바르지 않을 경우 `IllegalArgumentException` 를 발생시키는 기능
7 changes: 6 additions & 1 deletion src/main/kotlin/racingcar/Application.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package racingcar

import racingcar.controller.RacingController


fun main() {
// TODO: 프로그램 구현
val racingController = RacingController()
racingController.run()
}

41 changes: 41 additions & 0 deletions src/main/kotlin/racingcar/controller/RacingController.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package racingcar.controller

import racingcar.model.AttemptsNumber
import racingcar.model.Car
import racingcar.model.Cars
import racingcar.model.Racing
import racingcar.util.Constants
import racingcar.view.InputView
import racingcar.view.OutputView

class RacingController() {
private var attemptsNum = 0
private lateinit var carList: List<Car>
private val inputView = InputView()
private val outputView = OutputView()

fun run() {
printStartAndGetCarList()
requestAttemptsNumber()
doRacingAndPrintResult()
}

private fun doRacingAndPrintResult() {
val racing = Racing(carList)
repeat(attemptsNum) { racing.runRaceOnce().apply { outputView.printMatchProgress(carList) } }
racing.getWinner()
Copy link

Choose a reason for hiding this comment

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

getWinner() 라는 이름은 '우승자를 가져오다' 라는 의미로 사용돼 우승자를 반환하는 의미 해설될 수 있을 것 같아요.
차라리 determineWinner() 같은 이름은 어떨까요?

outputView.printWinner(racing.winner)
}

private fun requestAttemptsNumber() {
outputView.requestAttemptsNumber(Constants.NUMBER_ATTEMPTS_MSG)
val attemptsNumber = AttemptsNumber(inputView.getAttemptsNumber())
attemptsNum = attemptsNumber.validAttemptsNum
}

private fun printStartAndGetCarList() {
outputView.printRaceStart(Constants.RACE_START_MSG)
val cars = Cars(inputView.getCarList())
carList = cars.carList
}
}
15 changes: 15 additions & 0 deletions src/main/kotlin/racingcar/model/AttemptsNumber.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package racingcar.model

import racingcar.util.Validator.isNumberAttemptsValid

class AttemptsNumber(private val attempts: String) {

val validAttemptsNum: Int
get() = getValidAttemptsNum().toInt()
Comment on lines +7 to +8
Copy link

Choose a reason for hiding this comment

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

validAttemptsNum 라는 변수를 호출할 때 마다
getValidAttemptsNum().toInt() 의 연산을 계속 하게 됩니다.
아예 init 키워드로 한번의 계산만 하고 계산된 값만 가져오게 수정하면 더 좋을 것 같습니다.


private fun getValidAttemptsNum(): String {
isNumberAttemptsValid(attempts)
return attempts
Comment on lines +10 to +12
Copy link

Choose a reason for hiding this comment

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

함수로 만들어 검증하는 것 보다는 init 키워드를 만들어 검증만 하는 식으로 만드는 것도 좋을 것 같습니다

}

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

data class Car(
var name:String = "",
var distance:Int = 0
)
31 changes: 31 additions & 0 deletions src/main/kotlin/racingcar/model/Cars.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package racingcar.model

import racingcar.util.Validator.isCarNameLengthValid
import racingcar.util.Validator.isCarNameNotEmpty
import racingcar.util.Validator.isCarNameUnique

class Cars(private val carNameList: List<String>) {

private var _carNames = getValidCarName()

val carList: List<Car>
get() = _carList
private var _carList = initializeCars()

private fun getValidCarName(): List<String> {
isCarNameUnique(carNameList)
carNameList.forEach { name ->
isCarNameLengthValid(name)
isCarNameNotEmpty(name)
}
_carNames = carNameList
return _carNames
Comment on lines +21 to +22
Copy link

Choose a reason for hiding this comment

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

이 부분에서 이미 _carNames = carNameList를 통하여 값을 넘겨준 것 같은데
한번 더 return으로 값을 넘겨줄 필요가 없어보입니다.
return carNameList 만 하던가 아니면 void 형으로 만드는 것이 좋아 보여요

}

private fun initializeCars(): List<Car> {
val initializedCarList: List<Car> = _carNames.indices.map { Car(name = "", distance = 0) }
_carNames.forEachIndexed { idx, name -> initializedCarList[idx].name = name }
return initializedCarList
Comment on lines +26 to +28
Copy link

Choose a reason for hiding this comment

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

_carNames.map { Car(name = it, distance = 0) }
으로 한줄로 간결하게 만들 수 있을 것 같아요

}

}
30 changes: 30 additions & 0 deletions src/main/kotlin/racingcar/model/Racing.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package racingcar.model

import camp.nextstep.edu.missionutils.Randoms
import racingcar.util.Constants

class Racing(private val cars: List<Car>) {
val winner: String
get() = _winner

private var _winner = ""


private fun makeRandomNumber() = Randoms.pickNumberInRange(0, 9)
Copy link

Choose a reason for hiding this comment

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

0, 9 값 전부 상수로 만들면 가독성이 좋아질 것 같습니다.


private fun determineMoveOrStop(randomNumber: Int) = randomNumber >= Constants.BASE_NUMBER

fun runRaceOnce() {
cars.forEach { car ->
val randomNumber = makeRandomNumber()
if (determineMoveOrStop(randomNumber)) car.distance++
println(car.distance)
Copy link

Choose a reason for hiding this comment

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

model에서는 view와 의존하면 안돼요.
모델은 뷰와 컨트롤러와 의존하지 않아야 하며 데이터 관련해서만 처리해야 합니다.

}
}

fun getWinner() {
val maxDistance = cars.maxOfOrNull { it.distance }
val winnerList = cars.filter { it.distance == maxDistance }
_winner = winnerList.joinToString { it.name }
}
}
10 changes: 10 additions & 0 deletions src/main/kotlin/racingcar/util/Constants.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package racingcar.util

object Constants {
const val CAR_NAME_MAX_LENGTH = 5
const val RACE_START_MSG = "경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)"
const val NUMBER_ATTEMPTS_MSG = "시도할 횟수는 몇 회인가요?"
const val BASE_NUMBER = 4
const val WINNER_MSG = "최종 우승자 : "
const val DISTANCE_MSG = "-"
}
28 changes: 28 additions & 0 deletions src/main/kotlin/racingcar/util/Validator.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package racingcar.util

object Validator {
Copy link

Choose a reason for hiding this comment

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

require 문을 사용해보시는 것도 좋을 것 같습니다

private const val DUPLICATE_NAME_ERROR_MSG = "Duplicate car names are not allowed."
private const val NAME_OVER_LENGTH_MSG =
"Car name exceeds the maximum allowed length of ${Constants.CAR_NAME_MAX_LENGTH}."
private const val NAME_EMPTY_MSG = "Car name cannot be empty."
private const val INVALID_NUMBER_FORMAT_MSG =
"Invalid format for number of attempts. Please use the specified format."

fun isCarNameUnique(carList: List<String>) {
if (carList.size != carList.toSet().size) throw IllegalArgumentException(DUPLICATE_NAME_ERROR_MSG)
}

fun isCarNameLengthValid(carName: String) {
if (carName.length > Constants.CAR_NAME_MAX_LENGTH) throw IllegalArgumentException(NAME_OVER_LENGTH_MSG)
}
Comment on lines +16 to +17

Choose a reason for hiding this comment

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

이부분에서 혹시 Constants를 import하지 않고 사용하신 이유가 있을까요?만약 특별한 이유가 없다면 Constants를 import하는 쪽이 좀 더 간결하게 보일 것 같아서요


fun isCarNameNotEmpty(carName: String) {
if (carName.trim().isEmpty()) throw IllegalArgumentException(NAME_EMPTY_MSG)
}

fun isNumberAttemptsValid(numberAttempts: String) {
if (!(numberAttempts.all { it.isDigit() }) || numberAttempts.toInt() < 1) throw IllegalArgumentException(
INVALID_NUMBER_FORMAT_MSG
)
}
}
8 changes: 8 additions & 0 deletions src/main/kotlin/racingcar/view/InputView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package racingcar.view

import camp.nextstep.edu.missionutils.Console

class InputView {
fun getCarList() = Console.readLine().split(",")
fun getAttemptsNumber(): String = Console.readLine().trim()
}
23 changes: 23 additions & 0 deletions src/main/kotlin/racingcar/view/OutputView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package racingcar.view

import racingcar.model.Car
import racingcar.util.Constants

class OutputView {
fun printRaceStart(raceStartMsg: String) {
println(raceStartMsg)
}

fun requestAttemptsNumber(numberAttemptsMsg: String) {
println(numberAttemptsMsg)
}

fun printMatchProgress(cars: List<Car>) {
cars.forEach { car -> println("${car.name} : ${Constants.DISTANCE_MSG.repeat(car.distance)}") }
println()
}

fun printWinner(winnerNames: String) {
println("${Constants.WINNER_MSG}$winnerNames")
}
}
128 changes: 128 additions & 0 deletions src/test/kotlin/racingcar/ApplicationTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,18 @@ import camp.nextstep.edu.missionutils.test.NsTest
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import racingcar.controller.RacingController
import racingcar.model.Car
import racingcar.util.Constants.NUMBER_ATTEMPTS_MSG
import racingcar.util.Constants.RACE_START_MSG
import racingcar.util.Validator.isCarNameLengthValid
import racingcar.util.Validator.isCarNameNotEmpty
import racingcar.util.Validator.isCarNameUnique
import racingcar.util.Validator.isNumberAttemptsValid

class ApplicationTest : NsTest() {
private val racingController: RacingController = RacingController()

@Test
fun `전진 정지`() {
assertRandomNumberInRangeTest(
Expand All @@ -26,10 +36,128 @@ class ApplicationTest : NsTest() {
}
}

@Test
fun `이름 중복에 대한 예외 처리`() {
assertSimpleTest {
val carNames = listOf("abc", "abc", "aa", "bb")
assertThrows<IllegalArgumentException> { isCarNameUnique(carNames) }
}
}

@Test
fun `이름 길이에 대한 예외 처리`() {
assertSimpleTest {
val carNames = listOf("hello", "sponge", "bob", "haha")
assertThrows<IllegalArgumentException> {
carNames.forEach { isCarNameLengthValid(it) }
}
}
}

@Test
fun `이름 공백에 대한 예외 처리`() {
assertSimpleTest {
val carNames = listOf("hello", " ", "", "hey")
assertThrows<IllegalArgumentException> {
carNames.forEach { isCarNameNotEmpty(it) }
}
}
}

@Test
fun `시도 횟수 입력에 대한 예외 처리`() {
assertSimpleTest {
val attempts = listOf("0", "", "2", "10", "a", "ㄱ")
assertThrows<IllegalArgumentException> {
attempts.forEach { isNumberAttemptsValid(it) }
}
}
}

@Test
fun `전진 정지 판단`() {
assertSimpleTest {
val randomNumbers = listOf(1, 5, 4, 2, 6)
val results: MutableList<Boolean> = MutableList<Boolean>(5) { false }
randomNumbers.forEachIndexed { idx, i -> results[idx] = racingController.determineMoveOrStop(i) }
assertThat(results).isEqualTo(listOf(false, true, true, false, true))
}
}

@Test
fun `단일 우승자 판별`() {
assertSimpleTest {
val cars: List<Car> = listOf(Car("T1", 10), Car("KT", 2), Car("Gen.G", 7))
val results = racingController.getWinner(cars)
assertThat(results).isEqualTo("T1")
}
}

@Test
fun `다중 우승자 판별`() {
assertSimpleTest {
val cars: List<Car> = listOf(Car("Bear", 3), Car("Dog", 6), Car("Cat", 7), Car("Tiger", 7))
val results = racingController.getWinner(cars)
assertThat(results).isEqualTo("Cat, Tiger")
}
}

@Test
fun `움직인 거리 출력`() {
assertSimpleTest {
val attempts = 5
val cars: List<Car> = listOf(Car("Bear", 0), Car("Dog", 0), Car("Cat", 0), Car("Tiger", 0))
racingController.doRacing(attempts, cars)
}
}

@Test
fun `최종 우승자 출력`() {
assertSimpleTest {
run {
val cars: List<Car> = listOf(Car("Bear", 3), Car("Dog", 6), Car("Cat", 7), Car("Tiger", 7))
racingController.printWinner(racingController.getWinner(cars))
}
assertThat(output()).isEqualTo("최종 우승자 : Cat, Tiger")
}
}

@Test
fun `경기 과정 출력`() {
assertSimpleTest {
run {
val cars: List<Car> = listOf(Car("Cat", 1), Car("Dog", 3))
racingController.printMatchProgress(cars)
}
assertThat(output()).isEqualTo("Cat : -\n" + "Dog : ---" )
}
}

@Test
fun `경기 시작 출력`() {
assertSimpleTest {
run {
printRaceStart(RACE_START_MSG)
}
assertThat(output()).isEqualTo("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)")
}
}

@Test
fun `시도 횟수 메세지 출력`() {
assertSimpleTest {
run {
printNumberAttempts(NUMBER_ATTEMPTS_MSG)
}
assertThat(output()).isEqualTo("시도할 횟수는 몇 회인가요?")
}
}

public override fun runMain() {
main()
}


companion object {
private const val MOVING_FORWARD = 4
private const val STOP = 3
Expand Down