-
Notifications
You must be signed in to change notification settings - Fork 117
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
[자동차 경주] 이현제 미션 제출합니다. #104
base: main
Are you sure you want to change the base?
Changes from all commits
30dbddc
bf650cc
4a7aebd
2af0f9f
b798892
e3ea0ea
2cfaa44
dd97387
85cb6fc
70006e7
264b9df
26ac6c3
c08ac6a
7fbd961
459bf63
2ff2177
0fa3033
df5a4f8
2992cc8
32a34c9
f349145
7c62c2b
10eef29
a63dbf1
f245451
9dd5110
5936a82
2f7d228
96d9ce1
efa2cb0
281d940
17eb041
26c033b
248f8d8
bd7dc92
db19f39
a526ae6
88019d0
10ede80
81b0953
b8f2f72
abd8407
8bee7c6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,79 @@ | ||
# kotlin-racingcar-precourse | ||
|
||
초간단 자동차 경주 게임을 구현한다. | ||
|
||
# 구현 기능 목록 | ||
|
||
- 몇 번 이동할 것인지 횟수 입력 기능 | ||
- 자동차의 이름 입력 기능 | ||
- 쉼표(`,`) 기준으로 한 번에 n 대의 이름 입력 받기 | ||
- 해당 기능에서 자동차 갯수 확인 | ||
- 각각의 이름은 5자 이하 | ||
- 자동차의 전진 기능 | ||
- 전진 조건은 0~9 사이 무작위 값이 4 이상일 경우 | ||
- 각 실행 결과 출력 기능 | ||
- 우승자 출력 기능 | ||
- 우승자는 한 명 이상일 수 있음 | ||
- 쉼표(`,`)로 우승자 여러 명 출력 | ||
- 잘못된 입력값에 대해 IllegalArgumentException 발생 기능 | ||
|
||
# 프로그래밍 요구사항 | ||
|
||
- **`indent depth` 2 이하로 구현할 것** | ||
- 함수는 한 가지 일만 담당하게끔 구현 | ||
- 위의 기능 목록에 대해 각각을 테스트할 것 | ||
- 외부 라이브러리 사용 불가 | ||
- `Kotlin Style Guide` 원칙 | ||
|
||
# 라이브러리 | ||
- `camp.nextstep.edu.missionutils` | ||
- `Randoms.pickNumberInRange()` | ||
- `Console.readLine()` | ||
|
||
# 실행 결과 예시 | ||
``` | ||
경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분) | ||
pobi,woni,jun | ||
시도할 횟수는 몇 회인가요? | ||
5 | ||
|
||
실행 결과 | ||
pobi : - | ||
woni : | ||
jun : - | ||
|
||
pobi : -- | ||
woni : - | ||
jun : -- | ||
|
||
pobi : --- | ||
woni : -- | ||
jun : --- | ||
|
||
pobi : ---- | ||
woni : --- | ||
jun : ---- | ||
|
||
pobi : ----- | ||
woni : ---- | ||
jun : ----- | ||
|
||
최종 우승자 : pobi, jun | ||
``` | ||
|
||
--- | ||
|
||
# 슈도 코드 작성 | ||
|
||
```kotlin | ||
val carMap: Map<String, Int> | ||
val attemptNumber: Int | ||
|
||
for (attemptNumber) { | ||
randomDice(carMap) | ||
printRaceResult(carMap) // carMap의 Int 이용 | ||
} | ||
|
||
val winner = getWinner(carMap) // carMap의 Int 이용 최댓값 추출 | ||
printWinner(winner) | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,82 @@ | ||
package racingcar | ||
|
||
import camp.nextstep.edu.missionutils.Console.readLine | ||
import camp.nextstep.edu.missionutils.Randoms.pickNumberInRange | ||
import kotlin.collections.set | ||
|
||
val isCarNameValidLength = { carName: String -> carName.length <= 5 } | ||
val isMoveForwardValid = { pickNumberInRange(0, 9) >= 4 } | ||
val isWinnerValid = { carMap: MutableMap<String, Int>, count: Int -> count == carMap.values.maxOrNull() } | ||
|
||
fun main() { | ||
// TODO: 프로그램 구현 | ||
|
||
promptForCarNames() | ||
val carList = processCarNames(readLine()) | ||
val carMap = initializeCarMap(carList) | ||
|
||
promptForAttemptNumber() | ||
val attemptNumber = processAttemptNumber(readLine()) | ||
|
||
printAllRaceResults(carMap, attemptNumber) | ||
|
||
printWinners(carMap) | ||
} | ||
|
||
fun promptForCarNames() = println(Strings.MESSAGE_INPUT_CAR_NAME) | ||
fun promptForAttemptNumber() = println(Strings.MESSAGE_INPUT_ATTEMPT_NUMBER) | ||
|
||
fun processCarNames(input: String): List<String> { | ||
val carList = input.split(",").map { it.trim() } | ||
require(carList.all { it.isNotBlank() }) { Strings.MESSAGE_EXCEPTION_INPUT_CAR_NAMES } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. require로 검증하는 방법도 있군요, 하나 배워갑니다! |
||
return carList | ||
} | ||
|
||
fun processAttemptNumber(input: String): Int { | ||
require(input.toIntOrNull() != null) { Strings.MESSAGE_EXCEPTION_INPUT_ATTEMPT_NUMBER } | ||
return input.toInt() | ||
} | ||
|
||
|
||
fun initializeCarMap(carList: List<String>): MutableMap<String, Int> { | ||
val carMap = mutableMapOf<String, Int>() | ||
|
||
for (carName in carList) { | ||
validateCarName(carName) | ||
carMap.put(carName, 0) | ||
} | ||
|
||
return carMap | ||
} | ||
|
||
fun printAllRaceResults(carMap: MutableMap<String, Int>, attemptNumber: Int) { | ||
println(Strings.MESSAGE_OUTPUT_RESULT) | ||
|
||
for (i in 0 until attemptNumber) { | ||
printRaceResult(carMap) | ||
println() | ||
} | ||
} | ||
|
||
fun printRaceResult(carMap: MutableMap<String, Int>) { | ||
carMap.forEach { key, value -> | ||
moveForwardIfValid(carMap, key, value) | ||
|
||
println("$key : " + "-".repeat(carMap[key]!!)) | ||
} | ||
} | ||
|
||
fun moveForwardIfValid(carMap: MutableMap<String, Int>, key: String, value: Int) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 함수명을 보면 '유효할 경우 앞으로 이동하는 작업'이라는 점을 알 수 있긴 하지만, '무엇이' 유효한지에 대해서 빠진 게 아쉽다고 생각합니다. 결국 가독성을 떨어뜨릴 수 있다고 합니다..! 만약 Car 클래스의 메서드로 moveForwardIfValid였다면 대충 차를 앞으로 보내겠구나 생각을 하겠지만, 함수로만 구현되었기 때문에 매개변수와 함수내용을 보고 로직으로 판단해야 해서 가독 비용이 높아진 것 같습니다..! |
||
if (isMoveForwardValid()) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. isMoveForwardValid()라는 검증 메서드를 호출하고 있지만, 위로 한참 스크롤해야 하는 점에서 응집도가 떨어지는 것 같습니다. 관련성이 높으면 붙어있는 것도 좋은 방법이라고 생각합니다! |
||
carMap[key] = value + 1 | ||
} | ||
} | ||
|
||
fun printWinners(carMap: MutableMap<String, Int>) = println( | ||
Strings.MESSAGE_OUTPUT_WINNER + filterWinners(carMap).joinToString(", ") | ||
) | ||
|
||
fun filterWinners(carMap: MutableMap<String, Int>) = carMap.filterValues { isWinnerValid(carMap, it) }.keys | ||
|
||
fun validateCarName(carName: String) { | ||
require(isCarNameValidLength(carName)) { Strings.MESSAGE_EXCEPTION_CAR_NAME_LENGTH } | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package racingcar | ||
|
||
object Strings { | ||
const val MESSAGE_INPUT_CAR_NAME = "경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)" | ||
const val MESSAGE_INPUT_ATTEMPT_NUMBER = "시도할 횟수는 몇 회인가요?" | ||
const val MESSAGE_OUTPUT_RESULT = "실행 결과" | ||
const val MESSAGE_OUTPUT_WINNER = "최종 우승자 : " | ||
const val MESSAGE_EXCEPTION_CAR_NAME_LENGTH = "자동차 이름은 5자 이하만 가능합니다." | ||
const val MESSAGE_EXCEPTION_INPUT_CAR_NAMES = "자동차 이름은 값이 없거나 공백이 아니여야 합니다." | ||
const val MESSAGE_EXCEPTION_INPUT_ATTEMPT_NUMBER = "입력 횟수는 정수값이여야 합니다." | ||
} | ||
Comment on lines
+1
to
+11
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 상수를 String으로 분리해서 관리하는 부분 좋은 것 같습니다! |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,7 +9,7 @@ import org.junit.jupiter.api.assertThrows | |
|
||
class ApplicationTest : NsTest() { | ||
@Test | ||
fun `기능 테스트`() { | ||
fun `통합 테스트`() { | ||
assertRandomNumberInRangeTest( | ||
{ | ||
run("pobi,woni", "1") | ||
|
@@ -20,9 +20,73 @@ class ApplicationTest : NsTest() { | |
} | ||
|
||
@Test | ||
fun `예외 테스트`() { | ||
fun `통합 테스트 3명 이상`() { | ||
assertRandomNumberInRangeTest( | ||
{ | ||
run("pobi,woni,jun", "5") | ||
assertThat(output()).contains("pobi : -", "woni : ", "jun : ", "최종 우승자 : pobi") | ||
}, | ||
MOVING_FORWARD, STOP | ||
) | ||
} | ||
|
||
@Test | ||
fun `통합 테스트 2명 이상의 우승자`() { | ||
assertRandomNumberInRangeTest( | ||
{ | ||
run("pobi,woni,jun,a,b", "3") | ||
assertThat(output()).contains( | ||
"pobi : -", | ||
"woni : ", | ||
"jun : ", | ||
"a : ", | ||
"b : ", | ||
"최종 우승자 : pobi, woni, jun, a, b" | ||
) | ||
}, | ||
MOVING_FORWARD | ||
) | ||
} | ||
|
||
@Test | ||
fun `통합 테스트 공백 제거`() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 테스트 함수명을 정할 때도 어떤 역할을 하는 함수인지 명확히 해주시면 더 좋다고 알고 있습니다. 어떤 입력인지 적은 건 좋았다고 생각합니다! 하지만 여기에 무엇을 했을 때, 어떤 결과를 기대하는지까지 적는 테스트 네이밍 룰도 있다고 하는데 개발자마다 적절한 테스트 네이밍 룰을 찾아보는 것도 좋을 거 같아요! |
||
assertRandomNumberInRangeTest( | ||
{ | ||
run(" a , b , c", "3") | ||
assertThat(output()).contains( | ||
"a : -", | ||
"b : -", | ||
"c : -", | ||
"최종 우승자 : a, b, c" | ||
) | ||
}, | ||
MOVING_FORWARD | ||
) | ||
} | ||
|
||
@Test | ||
fun `단위 테스트 공백 제거`() { | ||
val input = " tesla , audi , bmw " | ||
val result = processCarNames(input) | ||
|
||
assertThat(result).containsExactly("tesla", "audi", "bmw") | ||
} | ||
|
||
@Test | ||
fun `예외 테스트 자동차 이름 5자 초과`() { | ||
assertSimpleTest { | ||
val exception = assertThrows<IllegalArgumentException> { runException("pobi,javaji", "1") } | ||
|
||
exception.printStackTrace() | ||
} | ||
} | ||
|
||
@Test | ||
fun `예외 테스트 공백으로 이뤄진 자동차 이름`() { | ||
assertSimpleTest { | ||
assertThrows<IllegalArgumentException> { runException("pobi,javaji", "1") } | ||
val exception = assertThrows<IllegalArgumentException> { runException(" , ,", "1") } | ||
|
||
exception.printStackTrace() | ||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
리뷰 요청에 클래스 필요성에 대한 요청사항이 있어서 글 남깁니다...!
클래스는 메서드 행위의 주체, 프로퍼티 상태의 주체라서 현제님께서 작성하신 함수들이 클래스로 적절하게 묶였을 때, 그 클래스 명만 보고도 어떤 역할을하는 객체인지 쉽게 예측 가능하다는 장점이 있습니다.
클린 코드 책을 보면 코드를 작성할 때 좋은 글을 쓰려는 것처럼 하라는 말이 있더라구요...! 문장을 잘 써도 문단으로 묶는 게 중요한 것처럼, 클래스로 객체의 행위와 상태를 적절히 묶는 것도 중요하다고 합니다!