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

3단계 - 자동차 경주 #1179

Open
wants to merge 36 commits into
base: sksskaw
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
d6579d2
클래스 설계
sksskaw Jun 10, 2023
0a389cc
난수 생성 함수 구현
sksskaw Jun 10, 2023
65e5cc8
난수 생성 함수 단위테스트 생성
sksskaw Jun 10, 2023
b6a707e
car 구현
sksskaw Jun 10, 2023
89800ba
RacingField 구현
sksskaw Jun 10, 2023
1e10266
안내 메세지 view 클래스 구현
sksskaw Jun 10, 2023
79dbf57
자동차수, 실행횟수 입력로직 구현
sksskaw Jun 11, 2023
e526b93
UI 테스트 제외
sksskaw Jun 11, 2023
8d7e0d0
경주 실행 함수 구현
sksskaw Jun 11, 2023
cbab930
CarTest 코드 작성
sksskaw Jun 11, 2023
734d12b
불필요 테스트 제거
sksskaw Jun 11, 2023
2ea612f
Car 상수 리팩토링
sksskaw Jun 11, 2023
898f250
CarTest 불필요 테스트 제거
sksskaw Jun 11, 2023
18b9fa4
racingFieldSetGameCountTest 테스트 코드 수정
sksskaw Jun 11, 2023
32d0518
RacingField의 ResultView 의존성 제거
sksskaw Jun 11, 2023
0d2b28e
gameStart 함수 수정
sksskaw Jun 11, 2023
4b12b88
gameStart 함수 수정에 따른 main 로직 수정
sksskaw Jun 11, 2023
4c3d54b
InputView enum InputType으로 리팩토링
sksskaw Jun 11, 2023
ba2303c
RacingField 생성자 리팩토링
sksskaw Jun 11, 2023
ad20d7f
RacingField 수정에 따른 테스트 코드 수정
sksskaw Jun 11, 2023
392ae80
InputType print 책임 수정
Jun 12, 2023
20277d6
car distance 필드 숨기기
Jun 12, 2023
8d1a29f
repeat 적용하기
Jun 12, 2023
705fe34
입력값 음수 체크 추가
Jun 12, 2023
6259c0a
CarMoveTest 테스트 코드 작성
Jun 12, 2023
0eb9eb2
System.getenv("OPERA TE_MODE") NPE 처리
Jun 12, 2023
0022183
Elvis 표현식 적용
sksskaw Jun 12, 2023
fa451f7
cars 타입 mutable -> non-mutable 수정
sksskaw Jun 12, 2023
78bf4bc
경기 결과 출력 로직 수정
sksskaw Jun 12, 2023
1e7d6b3
RandomNumber 난수 생성기 interface로 리팩토링,
sksskaw Jun 12, 2023
d3d4436
프로젝트 패키징
sksskaw Jun 12, 2023
628653b
테스트 명 수정
sksskaw Jun 12, 2023
a91b693
기대값이 null일 경우 isEqualTo로 수정
sksskaw Jun 12, 2023
fd66916
RacingField 생성 시 필드변수 양수 강제주입
sksskaw Jun 12, 2023
9450131
readOnlyDistance 커스텀 접근자 적용
sksskaw Jun 13, 2023
a04e357
불필요 코드 제거
sksskaw Jun 13, 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
Empty file removed src/main/kotlin/.gitkeep
Empty file.
24 changes: 24 additions & 0 deletions src/main/kotlin/racingCar/Car.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package racingCar

class Car(
private var distance: Int = 0,
private val randomNumber: RandomNumber
) {
val readOnlyDistance: Int
Copy link

Choose a reason for hiding this comment

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

리뷰드렸던대로 읽기전용 프로퍼티를 활용하여 잘 표현해주셨네요 👍
하지만 변수명이 너무 구체적입니다.
readOnly라는 이름이 val가 아닌 var로 고쳤을때는 알맞은 이름이라고 볼 수 없을것 같습니다.

아쉽게도 IDE는 그러한 부분까지 같이 고쳐주지는 않으니까요.

그러한 맥락에서 변수명이 변수의 특정 행위가 가능한지를 나타내는것은 유지보수를 하고 사용하는데 유리한 이름이 아닌것 같아 보입니다.

다른 이름을 고려해주세요.

get() {
return distance
}

companion object {
const val FORWARD_CONDITIONS: Int = 4
}

fun move() {
if (checkForwardCondition())
distance++
}

private fun checkForwardCondition(): Boolean {
return randomNumber.getRandomNumber() >= FORWARD_CONDITIONS
}
}
31 changes: 31 additions & 0 deletions src/main/kotlin/racingCar/InputIO.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package racingCar

import java.lang.NumberFormatException

class InputIO {
fun inputNumber(numberType: InputType): Int {
inputView(numberType)

return checkNumber(readlnOrNull()) ?: return inputNumber(numberType)
}

private fun inputView(numberType: InputType) {
println(numberType.message)
}

fun checkNumber(inputString: String?): Int? {
try {
val value = Integer.parseInt(inputString)

if (value < 0) {
println("양수만 입력해 주세요")
return null
}
Comment on lines +17 to +23
Copy link

Choose a reason for hiding this comment

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

4단계 요구사항인 indent 2를 넘어가는 조건을 위배하였습니다.

수정해주세요.


return value
} catch (e: NumberFormatException) {
println("숫자를 입력해 주세요")
return null
}
}
}
6 changes: 6 additions & 0 deletions src/main/kotlin/racingCar/InputType.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package racingCar

enum class InputType(val message: String) {
CAR_COUNT("자동차 대수는 몇 대인가요?"),
GAME_COUNT("시도할 횟수는 몇 회인가요?");
}
31 changes: 31 additions & 0 deletions src/main/kotlin/racingCar/RacingField.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package racingCar

class RacingField(
private val cars: List<Car>,
private val gameCount: Int
) {
constructor(carCount: Int, gameCount: Int) : this(
List(if (carCount <= 0) 1 else carCount) { Car(randomNumber = RandomNumberGenerator()) },
Copy link

Choose a reason for hiding this comment

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

carCount가 0보다 작으면 1로 취급해야한다는 요구사항은 없는것으로 알고있습니다.
요구사항을 확인하고 다시 작성해주세요.

그리고 if 절을 사용한 뒤에는 중괄호를 반드시 작성해주시기 바랍니다.
다른곳도 마찬가지입니다.
https://kotlinlang.org/docs/coding-conventions.html#control-flow-statements

if (gameCount <= 0) 1 else gameCount
Copy link

Choose a reason for hiding this comment

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

이곳도 if 절을 사용한 뒤에는 중괄호를 반드시 작성해주시기 바랍니다.

https://kotlinlang.org/docs/coding-conventions.html#control-flow-statements

)

fun getGameCount(): Int {
Copy link

Choose a reason for hiding this comment

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

제가 읽기전용 프로퍼티를 이용해서 사용자 정의 게터를 이용하여 표현하는것을 적용해 달라고 말씀드렸는데

이 부분을 비롯해 다른 곳들은 누락하신것 같습니다.

전체적으로 코드를 다시한번 살펴 보시면서 리뷰를 반영하실 수 있는 부분을 찾아보시고 적용해주세요.

return gameCount
}

fun gameStart() {
Copy link

Choose a reason for hiding this comment

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

메서드 이름은 동사가 먼저 오도록 변경해주세요.

moveCars()
}

private fun moveCars() {
cars.forEach { it.move() }
}

fun getCarCount(): Int {
return cars.size
}

fun getCarsDistance(): List<Int> {
return cars.map { it.readOnlyDistance }
}
}
11 changes: 11 additions & 0 deletions src/main/kotlin/racingCar/RandomNumberGenerator.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package racingCar

interface RandomNumber {
fun getRandomNumber(): Int
}

class RandomNumberGenerator : RandomNumber {
Copy link

Choose a reason for hiding this comment

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

RandomNumber는 인터페이스의 이름으로 적절해보이지 않습니다.

메서드의 이름으로 미루어보아 조금 더 추상적인 이름인 NumberGenerator는 어떠신가요?

override fun getRandomNumber(): Int {
return (0..9).random()
}
}
23 changes: 23 additions & 0 deletions src/main/kotlin/racingCar/ResultView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package racingCar

class ResultView {

companion object {
Copy link

Choose a reason for hiding this comment

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

자바의 static 변수를 맨위에 적는 컨벤션이 있어 헷갈리실수 있을것 같은데

코틀린에서 보통 상수를 표현하기 위해 사용되는 companion object는 클래스의 최하단에 적는 것이 좋습니다.

https://kotlinlang.org/docs/coding-conventions.html#class-layout

const val DISPLAY_STRING = "-"
}

private fun resultMessage() {
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

Copy link

Choose a reason for hiding this comment

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

그리고 지난번 리뷰사항 확인 부탁드립니다.

#1133 (comment)

println("실행 결과")
}

private fun carsDistanceDisplay(distances: List<Int>) {
distances.forEach { distance ->
println(DISPLAY_STRING.repeat(distance))
}
}

fun racingResultDisplay(distances: List<Int>) {
resultMessage()
carsDistanceDisplay(distances)
}
}
16 changes: 16 additions & 0 deletions src/main/kotlin/racingCar/main.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package racingCar

fun main() {
val inputIO = InputIO()
val carCount = inputIO.inputNumber(InputType.CAR_COUNT)
val gameCount = inputIO.inputNumber(InputType.GAME_COUNT)

val racingField = RacingField(carCount, gameCount)

val resultView = ResultView()

repeat(gameCount) {
racingField.gameStart()
resultView.racingResultDisplay(racingField.getCarsDistance())
}
}
Empty file removed src/test/kotlin/.gitkeep
Empty file.
21 changes: 21 additions & 0 deletions src/test/kotlin/racingCarTest/CarMoveTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package racingCarTest

import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import racingCar.Car

class CarMoveTest {
@Test
fun `전진 조건이 맞지 않는 경우`() {
val car = Car(randomNumber = FixedNumberGenerator(3))
car.move()
assertThat(car.readOnlyDistance).isEqualTo(0)
}

@Test
fun `전진 조건이 맞는 경우`() {
val car = Car(randomNumber = FixedNumberGenerator(7))
car.move()
assertThat(car.readOnlyDistance).isEqualTo(1)
}
}
22 changes: 22 additions & 0 deletions src/test/kotlin/racingCarTest/CheckNumberTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package racingCarTest

import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import racingCar.InputIO

class CheckNumberTest {
@Test
fun `입력값이 음수일 경우 null 을 리턴한다`() {
val inputIO = InputIO()
val negativeValue = inputIO.checkNumber("-1")
assertThat(negativeValue).isNull()
}

@Test
fun `입력값이 양수일 경우 입력한 값을 Int 로 변환하여 리턴한다`() {
val inputIO = InputIO()

val positiveValue = inputIO.checkNumber("3")
assertThat(positiveValue).isEqualTo(3)
}
}
11 changes: 11 additions & 0 deletions src/test/kotlin/racingCarTest/FixedNumberGenerator.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package racingCarTest

import racingCar.RandomNumber

class FixedNumberGenerator(
Copy link

Choose a reason for hiding this comment

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

💯

private val fixedNumber: Int
) : RandomNumber {
override fun getRandomNumber(): Int {
return fixedNumber
}
}
43 changes: 43 additions & 0 deletions src/test/kotlin/racingCarTest/RacingFieldTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package racingCarTest

import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import racingCar.RacingField

class RacingFieldTest {
@Test
fun `자동차 수 테스트`() {
val racingField = RacingField(3, 0)
assertThat(racingField.getCarCount()).isEqualTo(3)
}

@Test
fun `자동차 수 음수 테스트`() {
val racingField = RacingField(-3, 0)
assertThat(racingField.getCarCount()).isEqualTo(1)
}

@Test
fun `경기 실행 수 설정 테스트`() {
val racingField = RacingField(0, 5)
assertThat(racingField.getGameCount()).isEqualTo(5)
}

@Test
fun `경기 실행 수 음수 테스트`() {
val racingField = RacingField(0, -5)
assertThat(racingField.getGameCount()).isEqualTo(1)
}

@Test
fun `레이싱 경기 게임 실행 테스트`() {
Copy link

Choose a reason for hiding this comment

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

이름이 너무 추상적입니다.

행위에 따른 결과를 나타낼수 있도록 이름을 지어주는 것이 좋습니다.

테스트 이름을 짓는 게 난해하시다면 검색을 해보았을때 도움이 될만한 글들이 많으니 참고해보면 좋을것 같습니다.

예로 what is good name of test code라고 구글에 검색하면 나오는 글입니다.

https://methodpoet.com/unit-test-method-naming-convention

val carCount = 5
val gameCount = 100
val racingField = RacingField(carCount, gameCount)

repeat(gameCount) {
racingField.gameStart()
assertThat(racingField.getCarsDistance().stream().allMatch { it in 0..gameCount })
}
}
}