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

[step5] 자동차 경주(리팩토링) #1556

Open
wants to merge 2 commits into
base: mkkim90
Choose a base branch
from

Conversation

mkkim90
Copy link

@mkkim90 mkkim90 commented Nov 17, 2023

안녕하세요. 마지막 5단계입니다. 일전 피드백을 잘 주셔서 감사합니다.
추가 적용 사항 있으시면 말씀부탁드립니다.

늦은시간에 리뷰요청드리곤 했는데 상세히 잘 봐주셔서 감사합니다.

- 구분자, 기호 등 상수 처리
- 컨트롤러 서비스로 이동
- buildString dsl 활용
Copy link

@catsbi catsbi left a comment

Choose a reason for hiding this comment

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

안녕하세요 민균님. 포기하지 않고 꾸준히 해주셔서 감사합니다 👍 💯
5단계의 핵심 키워드는 객체간의 의존성을 분리하고, 핵심 로직들이 테스트하기 쉽게 만들어지는 것인데요. 현재 비즈니스 로직이 있는 부분들에 UI 로직들이 있다보니 테스트하기가 힘들어보이는데, 이를 분리해보면 좋을 것 같습니다!

리뷰 확인 후 다시 리뷰요청 부탁드려요!

return racingService.play(join())
}

private fun join(): Cars = Cars.of(inputCarName().split(RACING_CAR_DELIMITER))
Copy link

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.

Cars 클래스에 위임하겠습니다.

fun play(racingCars: Cars): Cars {
repeat(inputTryCount()) {
racingCars.move()
OutputView.printRacing(racingCars)
Copy link

Choose a reason for hiding this comment

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

지금 구조를 보면 의존성의 흐름이 Controller -> Service -> View Layer입니다.
보통 의존성은 밖에서 안으로 단방향으로 흐르는 편이에요. 즉 서비스 계층에서 출력 계층으로 의존성이 흐르는건 청신호는 아니죠. 서비스 계층이라면 테스트 객체인데, 저희는 콘솔창에 어떻게 출력되는지를 테스트할건 아니니까요(요구사항중 UI는 테스트하지 않는다는 내용도 있었죠. )

이런 출력 계층에 대한 정보는 컨트롤러 이상이 알 필요가 없도록 고쳐보면 좋을 것 같아요

Copy link
Author

Choose a reason for hiding this comment

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

controller 와 service 간에 레이싱 스냅샷 데이터를 출력할 수 있는 DTO 를 추가했습니다

private const val RACING_COURSE = "-"
fun printRacing(racingCars: Cars) {
val result = buildString {
racingCars.cars.forEach {
Copy link

Choose a reason for hiding this comment

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

a.b.c() 현재 a라는 객체에서 c라는 함수호출에 대해 알 필요가 있을까요? 디미터 법칙에 위배됩니다.
또한 OOP관점으로 볼 때 우리는 객체간에 요청과 응답을 하면서 서로의 내부정보를 알 필요 없도록하도록 캡슐화를 제공하는데, 이렇게 내부 값을 꺼내어 연산을 수행하면 캡슐화가 깨지게 되겠죠!

racingCar.contentForEach { ... } 이런식으로 함수를 만들어서 제공해도 좋을 것 같아요.

Copy link
Author

Choose a reason for hiding this comment

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

DTO에게 출력 문자열을 위임했습니다.

@mkkim90
Copy link
Author

mkkim90 commented Nov 18, 2023

혹시 피드백 주신 부분 개선하려다보니 일부 수정이 많이 된 것 같습니다.
도메인이 아닌 도메인 관련 DTO를 두었습니다.

아쉬운 부분이나 추가 개선 주실 부분 있으시면 말씀부탁드립니다.

Copy link

@catsbi catsbi left a comment

Choose a reason for hiding this comment

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

민균님, 피드백 반영하느라 고생하셨습니다. 다만, 현재 새로 작성된 CarsTest는 테스트가 통과되지 않고 있으며, 전체적으로 만든 객체들(UI제외)에 대한 테스트가 너무 부족한 것 같습니다. TDD 미션인만큼 테스트 코드는 좀 더 작성해보면 어떨까요?

그리고 ktlint도 검사한 뒤 제출해주시면 좋을 것 같아요 현재 ktlint check에서 실패를 하고 있어요.

자동차 경주 유종의 미를 거둘 수 있도록 다음 항목들을 해결해봅시다.

  1. UI 제외 모든 public API에 대한 성공 및 실패 테스트 작성
  2. ktlintCheck해서 리팩터링

피드백 반영 후 다시 리뷰요청 부탁드려요!

fun of(names: List<String>): Cars {
private const val RACING_CAR_DELIMITER = ","

fun of(inputName: String): Cars {
Copy link

Choose a reason for hiding this comment

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

보통 정적 팩토리 함수에서 매개변수를 하나만 받는 경우에는 from이나 valueOf등을 사용하는걸 관례로 하고 있습니다.
참고: https://kyucumber.github.io/posts/book/effective-kotlin/effective-kotlin-item-33

}
}

data class CarsDto(val carsDto: List<CarDto>) {
Copy link

Choose a reason for hiding this comment

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

DTO의 의미는 data transfer object입니다. 즉 데이터 전송용 바스켓 같은 목적으로 사용되죠.
그런데 이 dto에는 출력과, 최대 거리 찾기 등 비즈니스 로직들이 있는 것 같아요 🤔

repeat(inputTryCount()) {
racingCars.move()
OutputView.printRacing(racingCars)
snapshots.add(CarsDto(racingCars.toCarDtoList()))
Copy link

Choose a reason for hiding this comment

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

서비스에서 dto를 반환해도 되는가? 다른 서비스에서 같은 로직을 수행하는데 dto가 아닌 domain을 받고 싶다면 어떻게 하지? 이런 고민을 해보면 좋은 경험이 될 것 같아요 ㅎㅎ (정답이 있거나 이게 틀렸다는 피드백이 아닙니다 ㅎㅎ)

import org.junit.jupiter.params.provider.ValueSource
import racingcar.domain.Cars

class CarsTest {
Copy link

Choose a reason for hiding this comment

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

테스트가 성공하지 않고 있네요. ㅠ

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