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

[1단계 - 자동차 경주 구현] 레디(최동근) 미션 제출합니다. #728

Merged
merged 33 commits into from
Feb 15, 2024

Conversation

reddevilmidzy
Copy link

안녕하세요 범블비!

레디라고 합니다:)

리뷰 받았으면 좋을 것 같은 부분 남겨보아요!

  • 예외 커스텀

    • 자동차 경주에서 사용자가 입력하는 값 중 발생할 수 있는 예외는 이름 입력할 때와 시도횟수 입력할 때가 있습니다.
    • 이 부분에 대해서 IllegalArgumentException을 상속받아 예외를 커스텀 할지부터 예외 메시지를 던져줄때 자세하게 알려주는게 좋을지 아니면 크게 "유효하지 않은 자동차 이름입니다." 라는 형태로 사용하는게 좋을지 구민을 하였습니다.
    • 예외를 커스텀하지 않은 이유에 대해서 설명해보자면 예외를 커스텀한다고 하여도 결국에 커스텀 하는 건 클래스 이름뿐이였습니다.
    • 클래스 이름만 커스텀하는 것이 메시지를 다르게 출력하는 것보다 장점이 크게 없다고 보아 예외를 커스텀하지는 않았습니다.
    • (질문을 작성하다보니 예외 클래스 커스텀은 자바에게 알려주고 메시지는 사용자에게 알려준다는 차이점이 있다는 것을 발견했네요:))
    • 메시지를 사용하기로 정하고, 다시 고민했던 점은 메시지를 세분화해야할지에 대한 고민이였습니다.
    • "유효하지 않은 자동차 이름입니다." 라는 형태가 "자동차 이름의 길이는 5를 초과할 수 없습니다" 보다 여러 군데에서 사용할 수 있다는 장점이 있어, 해당 형태를 택하였는데, 이렇게 사용자에게 무엇이 잘못됐는지 자세하게 알려주지 않아도 괜찮을까요?
  • 예외 입력시 재입력 받는 기능을 구현하는 코드

    • 이 기능을 구현하기 위해 while 문을 사용할지 재귀를 사용할지 페어(백호)와 갈등이 있었어요.
    • 이번 프로그래밍 요구사항에서 한 메서드에 오직 한 단계의 들여쓰기(indent)만 한다.가 있어 이 조건을 맞추기 위해 결국 재귀를 사용하여 구현을 하였습니다.
    • 하지만 재귀를 사용한다면 특정 값 이상 예외를 입력하면 스택 오버 플로우가 발생한다는 것을 확인하였습니다.
    • 요약해보자면,, 프로그래밍 요구사항에 따를지 아니면 과감히 어겨보고 스택 오버 플로우에 안전한 코드를 작성할지 고민이 있습니다. (아니면 재귀를 쓰지않으면서 한 단계의 들여쓰기를 만족할 수 있는 방법도 있을까요??)
  • final과 관련된 질문

    • 파라미터 값에도 final이 붙고 메서드 안에서도 final 이 붙어있는 변수들이 많이 있는데, 이렇게 하다보니 너무 과도하다는 생각이 들었습니다.
    • 메서드 안에서의 final 과연 필요할까요??

Copy link

@ddaaac ddaaac left a comment

Choose a reason for hiding this comment

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

안녕하세요 레디 👋

첫 번째 미션 구현 잘 해주셨습니다.

몇 가지 코멘트 남겼으니 다음 단계에 반영해주세요!

CarName carName = new CarName(name);
Assertions.assertThat(carName.getName()).isEqualTo(name);
}
}
Copy link

Choose a reason for hiding this comment

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

POSIX new line에 대해서 알아볼까요?

인텔리제이에서는 옵션으로 항상 개행을 붙여줄 수 있습니다 :)

Copy link
Author

Choose a reason for hiding this comment

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

테스트 패키지 안에 클래스만 개행이 추가가 안되고 있었는데 올려주신 링크 덕분에 해결했습니다!


public class ExceptionRoofer {

public static <T> T generate(final Supplier<T> supplier, final Consumer<String> consumer) {
Copy link

Choose a reason for hiding this comment

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

제가 구현을 한다면 에러가 날 가능성이 있는 재귀를 사용하지는 않을 것 같네요.
depth를 1로 두면서 재입력을 받게 구현도 가능합니다. 이건 시간을 들여서 고민해보시면 좋을 것 같아요.

다만, 에러가 나면 그냥 에러가 나게 두는 것도 방법입니다. 특별한 요구사항이 없다면 재입력 없이 에러가 나게 둬도 좋을 것 같아요.

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 +6 to +8
public class ExceptionRoofer {

public static <T> T generate(final Supplier<T> supplier, final Consumer<String> consumer) {
Copy link

Choose a reason for hiding this comment

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

람다를 파라미터로 받을 때의 가독성, 유지보수의 용이함 등을 고민해보시면 좋을 것 같습니다.

Comment on lines +5 to +8
public record CarDto(String name, int position) {

public static CarDto from(final Car car) {
return new CarDto(car.getName(), car.getPosition());
Copy link

Choose a reason for hiding this comment

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

이 미션에서 DTO가 필요할까요? DTO의 어떤 특징/장점을 살리고 싶어서 사용하셨나요?

Copy link
Author

Choose a reason for hiding this comment

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

DTO의 장점은 model과 view의 중간단계에서 model와 view가 서로 직접적으로 의존하지 않게 함으로써 변경에 쉽게 대응할 수 있다는 점이라고 생각합니다.

질문을 주신걸 보고 다시 코드를 살펴보니 현재 CarDto는 Car와 필드가 완전히 똑같기에 위의 장점을 살리지 못하고 있는거 같네요🤨

Comment on lines +17 to +21
throw new IllegalArgumentException(ErrorMessage.INVALID_CAR_NAME.get());
}

if (name.trim().length() > MIN_LENGTH) {
throw new IllegalArgumentException(ErrorMessage.INVALID_CAR_NAME.get());
Copy link

Choose a reason for hiding this comment

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

  • 저도 이번 미션에서는 표준 예외를 사용했을 것 같습니다. 이번 미션에서는 커스텀 예외를 사용하는 장점을 느끼기 힘들 것 같아요.
  • 예외 내용의 상세함은 정책에 따라 달라지는 사항입니다. 보안이 필요하다면 자세하게 알려주지 않는 게 더 나을 때도 있겠죠. 이 부분은 레디가 미션에 맞게 고민해보시면 좋을 것 같아요. 다만, 많은 경우에서 예외 내용이 상세했을 때 얻는 이득이 더 컸던 것 같네요.


import racingcar.message.ErrorMessage;

public class CarName {
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.

CarName이라는 명확한 이름이 생겼고, 자동차 이름은 5이하라는 제약조건을 자동차가 아닌 자동차 이름이 책임질 수 있어 응집도가 높아진다고 생각합니다!

import java.util.stream.Collectors;
import racingcar.message.ErrorMessage;

public class Cars {
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.

비즈니스에 딱 맞는 자료구조를 만들 수 있습니다! 일급 컬렉션을 만들면 검증이나 요구사항을 외부에서 수행하는 것이 아니라 외부에서는 수행하라는 메시지만을 보내고 수행 로직은 일급 컬렉션이 스스로 결정할 수 있다는 장점이 있습니다.

Comment on lines +45 to +58
public List<Car> findWinner() {
final int maxPosition = getMaxPosition();

return cars.stream()
.filter(car -> car.getPosition() == maxPosition)
.toList();
}

private int getMaxPosition() {
return cars.stream()
.mapToInt(Car::getPosition)
.max()
.orElseThrow(IllegalStateException::new);
}
Copy link

Choose a reason for hiding this comment

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

지금 로직도 훌륭하지만 조금 더 극단적으로 getter를 사용하지 않는 훈련을 해봐도 좋을 것 같아요.

Car 객체에 적당한 책임을 부여하는 방식을 사용하면 getter를 사용하지 않아도 될 것 같아보여요.
두 Car 중에 winner가 누군지 찾는 로직, 두 Car 객체가 비겼는지 판단하는 로직 등을 만들어보는 건 어떨까요?

return cars.stream()
.mapToInt(Car::getPosition)
.max()
.orElseThrow(IllegalStateException::new);
Copy link

Choose a reason for hiding this comment

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

예외에 메세지를 담아볼까요?

Comment on lines +51 to +56
static class TestNumberGenerator implements NumberGenerator {

private static int value = 2;
@Override
public int generate() {
return value++;
Copy link

Choose a reason for hiding this comment

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

꽤나 위험한 방식의 테스트인 것 같습니다.
테스트가 추가/제거될 때 TestNumberGenerator 때문에 다른 테스트가 깨질 수도 있을 것 같아요.

항상 움직이는 Generator와 항상 움직이지 않는 Geneartor를 만들어서 테스트에 사용하면 테스트도 명확해지고 테스트 간의 연관관계도 끊어낼 수 있을 것 같습니다.

@ddaaac ddaaac merged commit 57ae5d3 into woowacourse:reddevilmidzy Feb 15, 2024
Copy link
Author

@reddevilmidzy reddevilmidzy left a comment

Choose a reason for hiding this comment

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

남겨주신 질문에 대한 답변 남깁니다!

CarName carName = new CarName(name);
Assertions.assertThat(carName.getName()).isEqualTo(name);
}
}
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 +27 to +29
public Car copy() {
return new Car(name, position);
}
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 +5 to +8
public record CarDto(String name, int position) {

public static CarDto from(final Car car) {
return new CarDto(car.getName(), car.getPosition());
Copy link
Author

Choose a reason for hiding this comment

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

DTO의 장점은 model과 view의 중간단계에서 model와 view가 서로 직접적으로 의존하지 않게 함으로써 변경에 쉽게 대응할 수 있다는 점이라고 생각합니다.

질문을 주신걸 보고 다시 코드를 살펴보니 현재 CarDto는 Car와 필드가 완전히 똑같기에 위의 장점을 살리지 못하고 있는거 같네요🤨


public class ExceptionRoofer {

public static <T> T generate(final Supplier<T> supplier, final Consumer<String> consumer) {
Copy link
Author

Choose a reason for hiding this comment

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

재입력 받고 싶은 욕심이 있었네요😅


import racingcar.message.ErrorMessage;

public class CarName {
Copy link
Author

Choose a reason for hiding this comment

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

CarName이라는 명확한 이름이 생겼고, 자동차 이름은 5이하라는 제약조건을 자동차가 아닌 자동차 이름이 책임질 수 있어 응집도가 높아진다고 생각합니다!

import java.util.stream.Collectors;
import racingcar.message.ErrorMessage;

public class Cars {
Copy link
Author

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.

None yet

3 participants