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

5단계 - 자동차 경주(리팩터링) #52

Merged

Conversation

JiwonDev
Copy link

No description provided.

@JiwonDev JiwonDev self-assigned this Aug 23, 2023
Copy link
Member

@Hyeon9mak Hyeon9mak left a comment

Choose a reason for hiding this comment

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

지원님 5단계 리팩터링 고생하셨습니다! 몇 가지 간단한 코멘트 남겼어요.

build.gradle.kts Outdated
@@ -1,5 +1,6 @@
plugins {
kotlin("jvm") version "1.8.10"
id("org.jlleitschuh.gradle.ktlint") version "10.3.0"
Copy link
Member

Choose a reason for hiding this comment

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

이번 미션에서 지원님 제안으로 린트가 제거되었죠!

Copy link
Author

Choose a reason for hiding this comment

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

앗 몰래 다시 쓰고 있었는데 들켰네요. 제거하도록 하겠습니다 🫡

Comment on lines 53 to 66
fun main() {
val app = RacingGameController(
userInterface = ConsoleUserInterface(),
racingGameView = StringRacingGameView(),
racingGameService = RacingGameService(
carFactory = CarFactory(),
racingGameRepository = InMemoryRacingGameRepository(),
),
)

val gameId: GameId = app.initialize()
app.playing(gameId = gameId)
app.showResult(gameId = gameId)
}
Copy link
Member

Choose a reason for hiding this comment

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

오잉 main 이 2개 존재하네요!

Copy link
Author

Choose a reason for hiding this comment

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

앗 정줄 놓고 코딩해서 똑같은걸 2번 작성했네요. 여기 있는건 삭제하도록 할게요! 😀

Comment on lines 61 to 80

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false

other as RacingGame

if (id != other.id) return false
if (carList != other.carList) return false
if (tryCount != other.tryCount) return false
return gameState == other.gameState
}

override fun hashCode(): Int {
var result = id.hashCode()
result = 31 * result + carList.hashCode()
result = 31 * result + tryCount
result = 31 * result + gameState.hashCode()
return result
}
Copy link
Member

Choose a reason for hiding this comment

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

모든 프로퍼티에 대한 equals hashcode 구현이 되었는데요, 요러면 id 가 일치하더라도 나머지 값이 다르면 서로 다른 객체로 인지하게 될거 같아요 😱

Copy link
Author

Choose a reason for hiding this comment

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

id 를 만들어놓고 equals를 무지성으로 작성했네요..😵 둘 다 id만 비교하도록 바꿔놓겠습니다!

Comment on lines 13 to 17
fun createGame(): GameId {
val game: RacingGame = racingGameRepository.create()

return game.id
}
Copy link
Member

Choose a reason for hiding this comment

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

repository 는 각 원소들을 마치 컬렉션처럼 다룰 수 있는 개념이라고 생각해요.
때문에 우선 RacingGame 객체를 생성하는 것은 repository 의 관할 밖이고,
저장하여 ID 를 발급 받는 것 까지가 repository 의 역할이라고 생각하는데
(마치 key-value 구조에 저장해서 key 를 기억하는 느낌)
지원님은 어떻게 생각하시나요?

Copy link
Author

Choose a reason for hiding this comment

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

넵 repository에서는 그냥 데이터 저장한다만 담당하고 create 같은 의미있는 기능은 도메인이 들고있어야한다 생각합니다.

요게 밖에서 생성한 RacingGame ID 중복이 발생해서 저장에 실패했을 떄 ID 처리가 어려워서 이렇게한거였는데
생각해보니 save(dto:RacingGameDto) 처럼 전달해서 repository에서 id를 만들도록 하면 되겠네요. 수정하겠습니다 😀

override fun create(): RacingGame {
       // 만약 밖에서 RacingGame을 생성해서 전달한다면, id를 어떻게 다시 매핑해주지..?
        val createdGame = RacingGame()

        val previousValue: RacingGame? = data.putIfAbsent(/* key = */ createdGame.id, /* value = */ createdGame)
        if (previousValue != null) {
            throw IllegalStateException("같은 ID의 게임이 이미 존재합니다.")
        }

        return createdGame
    }

Comment on lines +19 to +21
fun initialize(gameId: GameId, carNames: List<String>, tryCount: Int) {
val game: RacingGame = racingGameRepository.findById(gameId = gameId)
?: throw IllegalArgumentException("게임을 찾을 수 없습니다.")
Copy link
Member

Choose a reason for hiding this comment

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

👍 👍 데이터가 없다 그 자체는 repository 의 문제가 아니고, 도메인 영역의 문제죠 👍

Comment on lines +29 to +31
fun play(gameId: GameId): List<RacingGameStatus> {
val game: RacingGame = racingGameRepository.findById(gameId = gameId)
?: throw IllegalArgumentException("게임을 찾을 수 없습니다.")
Copy link
Member

Choose a reason for hiding this comment

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

계속해서 반복되므로 이걸 private 메서드로 분리하는 것도 좋아보이네요!

Copy link
Author

Choose a reason for hiding this comment

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

처음 생성할 때에는 중복으로 보이겠지만,
기능이 조금만 추가되도 각각의 예외처리나 예외 메시지가 달라질 것 같아 private 분리하지 않는게 더 좋아보이긴합니다. private로 분리할 만큼 크게 복잡하지 않은 코드이기도 하구요!

Comment on lines +3 to +4
@JvmInline
value class GameId(val value: Long)
Copy link
Member

Choose a reason for hiding this comment

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

👍

Comment on lines 1 to 15
package racingcar.infra

import racingcar.domain.vo.CarStatus
import racingcar.domain.vo.RacingGameStatus
import racingcar.view.MessageCode
import racingcar.view.RacingGameView
import racingcar.view.ViewModel

class StringRacingGameView : RacingGameView {

override fun getView(racingGameStatus: RacingGameStatus): ViewModel {
return racingGameStatus.carStatues
.joinToString(separator = "\n") { "${it.name} : " + "-".repeat(it.position) }
.let { StringViewModel(it) }
}
Copy link
Member

Choose a reason for hiding this comment

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

infra 패키지에는 domain, view 패키지에 있는 인터페이스들의 구현체가 모두 몰려있네요.
마치 모두가 의존하는 만인의 패키지가 된 느낌인데, domain 을 유지보수 하고 싶을 때도 view 를 유지보수 하고 싶을 때도 infra 를 함께 바라보아야 할거 같아요.
만약 패키지를 기준으로 모듈을 분리한다고 하면, infra 패키지는 분리가 어려울 것 같아요.
지원님은 어떻게 생각하시나요?

Copy link
Author

Choose a reason for hiding this comment

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

개인적인 의견으론 패키지를 domain, adapter, infra 3개로 쪼개서
domain(interface) <- adapter(Impl) <<- 여러 개의 infra 들(외부의존성, 실제구현) 으로 사용하는게 제일 좋다고 생각해요.
만약 순수 자바코드라면 domain(interface) <- adpater(Impl) 으로 되구요.

만약 패키지를 기준으로 모듈을 분리한다고 하면, infra 패키지는 분리가 어려울 것 같아요.

도메인을 POJO로 추상화 한 것 자체가, 도메인이 어떤것도 의존하지 않게 구성한다는 거고
이건 domain package만 그대로 뜯어가서 로직 재사용이 가능하게 만드는 것과 같다고 생각해요.

adapter(* 지금 코드상에선 infra 패키지)는 한군데 다 몰아놔도 큰 상관 없다고 괜찮다고 보긴합니다.

그러면 모듈 분리할 때 adatper를 하나씩 찾아줘야해서 패키지 분리가 번거롭지 않나? 의 답변은
인프라는 언제든시 교체될 수 있는 의존성이기에, domain 모듈이 분리되어 변경되었다면 그에 맞는 adapter는 다시 작성해주는게 맞지 않나 생각하긴합니다. 그것도 번거롭다면 그냥 adapter 안에 하위 패키지를 만들어서 간단하게만 나눠줘도 괜찮을 거 같네요.

- controller
  - ..
- domain
  - ..
- adpater
  - controller-adapter
  - domain-adapter
  - other-adapter

@JiwonDev JiwonDev merged commit 2d5c355 into mission-study-to-finish-in-15-days:jiwondev Aug 24, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants