Skip to content

[자동차 경주] 임아리 미션 제출합니다. #78

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

Merged
merged 30 commits into from
Feb 18, 2025

Conversation

dldb-chamchi
Copy link

느낀점

이번 학습 주 목표는 MVC패턴 학습이었습니다. 처음 적용 해보았기 때문에 시행착오가 많았습니다. 특히 어떤 방식으로 view, model, controller를 나누어야할지 잘 몰랐습니다. controller가 무슨 역할을 하는지 이해하지 못했는데 많은 검색과 예제를 통해 게임할 때 사용하는 컨트롤러와 유사하다는 깨달음을 얻어 MVC패턴을 적용해보았습니다. 아직 미숙해서 제대로 잘 되었을지는 모르겠습니다...

MVC 패턴

MVC패턴을 적용하기 전에는 코드가 매우 복잡하고 가독성이 떨어졌습니다. 또한 어떻게 단위 테스트 코드를 작성할지가 가장 문제였습니다. 하지만 리팩터링을 하면서 MVC패턴을 적용한 뒤에는 코드가 깔끔해졌고 단위 테스트를 어떻게 적용할지에 대한 생각도 떠올랐습니다. 학습한 바로는 수정이 용이하고 유지보수가 좋다는 장점을 가지고 있는데 확실히 그런 것 같습니다. 서로 의존하는 부분이 없으니 수정을 해도 많이 바뀌지 않았습니다.

요구사항

이번 요구사항에서 인상 깊었던 점은 depth가 1이하이어야 한다는 점이었습니다. depth가 1이하를 요구한다는 점은 모듈화를 잘할 수 있는지를 테스트 하는 것이라 생각했습니다. 실제로 코드를 작성하면서 모듈화를 해야할 부분이 많지는 않았지만 어느정도는 있었습니다. 간단한 부분은 쉬웠지만 게임에서 승리한 자동차를 찾을 때 조금 복잡한 코드라 모듈화가 들어가야했습니다. 하지만 모듈화를 하니 굉장히 코드가 더러워졌고, 그에 생각해낸것이 스트림 라이브러리였습니다. 아직은 미숙하지만 서치와 IDE의 도움을 받아 작성해보았습니다.

아쉬운 점

요구사항에는 UI부분은 제외하고 모든 로직에 단위 테스트를 구현하라하였습니다. 하지만 솔직히 어떻게 랜덤 값을 테스트하고 적용하는지는 아직까지 잘 모르겠습니다. 또한 모든 로직에 구현하라는 것이 어떻게 하라는지 예시를 생각해낼 수 없어 더욱 힘들었습니다. (검색을 해봤는데 Mock을 통한 테스트 라는 것이 있다는데 솔직히 잘 이해하지 못해서 적용하지는 못했습니다.)그래서 이번 경우에는 테스트를 2개 밖에 작성하지 못했습니다. 후에 피드백이 들어오면 더 작성해보려 합니다.

집중 피드백 받고 싶은 점

MVC패턴이 잘 되어있는지와 그에 관해 더욱 개선할 점을 피드백 받고 싶습니다. 또한 이런 랜덤 값을 테스트할 경우에 생각해낼 수 있는 단위 테스트들의 예시를 알고 싶습니다. (제가 작성한 단위 테스트가 올바른지도 확인 받고 싶습니다)

@GilHyeonwoo
Copy link

수고많으셨습니다!!
저는 우승자를 찾는 매서드를 maxDistance란 변수를 만들어서 최대값을 비교해서 넣은 다음 최대값과 같은 객체가 있다면 우승자에 추가하는 식으로 작성하였는데 스트림으로 변환하여 간단하게 짤 수 있는 것을 배우고 갑니다!

Copy link

@20HyeonsuLee 20HyeonsuLee left a comment

Choose a reason for hiding this comment

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

아리님 2주차 미션 고생하셨습니다👏👏

고민한 흔적을 PR설명에 적어주셔서 어떤 부분을 중점적으로 리뷰해야할지 파악하기 좋았습니다👍

학습테스트에 대한 느낀점과 배운점도 적어주시면 좋을 것 같아요!

코멘트 확인하시고 반영 해주시면, 추가적으로 리뷰 해드리겠습니다!

import java.util.Scanner;

public class GameView {
static Scanner in = new Scanner(System.in);

Choose a reason for hiding this comment

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

static선언 좋습니다👍👍

Comment on lines 16 to 23
int runCount;
try{
runCount = in.nextInt();
runCountMinusCheck(runCount);
}
catch (NumberFormatException e) {
throw new RuntimeException("정수만 가능합니다.");
}

Choose a reason for hiding this comment

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

입력 값에 대한 유효성 검사는 view, controller, model중 어떤 친구의 역할일까요?
어디서 검증해주면 좋을지 아리님의 생각이 궁급합니다!

Copy link
Author

Choose a reason for hiding this comment

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

저는 유효성 검사는 view 에서 하는게 맞다고 생각했습니다. 일단 요구사항에는 없었지만 유효하지 못한 값을 입력 받았을 때 유효한 값을 받을 때까지 input을 받는다고 가정하면 그 과정은 view에서 처리하는 것이 맞다고 생각하여 그렇게 진행했습니다! 하지만 지금 다시 생각해보면 model중 RacingCar 클래스의 멤버변수로 runcount가 있으므로 model에서 처리하는 것도 맞다고 생각합니다.

Comment on lines 35 to 44
public void printRound(List<RacingCar> racingCars) {
for (RacingCar car : racingCars) {
System.out.println(car.getCarName() + " : " + "-".repeat(car.getForwardCount()));
}
System.out.println();
}

public void printWinner(List<String> winnerCars){
System.out.println("최종 우승자 : " + String.join(", ", winnerCars));
}

Choose a reason for hiding this comment

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

view도 inputView와 outputView를 나눠보는건 어떨까요?

Copy link
Author

Choose a reason for hiding this comment

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

확실히 다른 분들의 코드를 보니 둘로 나누어졌을 때 더 가독성이 좋았던 것 같습니다. 참고하겠습니다!


public void printRound(List<RacingCar> racingCars) {
for (RacingCar car : racingCars) {
System.out.println(car.getCarName() + " : " + "-".repeat(car.getForwardCount()));

Choose a reason for hiding this comment

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

string.format을 사용하면 문자열 결합 비용을 줄일 수 있답니다😃
소소하지만 성능을 생각하는 개발자 되기!
https://velog.io/@kai6666/Java-String.format-%EB%AC%B8%EC%9E%90%EC%97%B4-%ED%98%95%EC%8B%9D-%EC%A7%80%EC%A0%95

Copy link
Author

Choose a reason for hiding this comment

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

아직 자바가 많이 미숙한 것 같습니다... 알려주셔서 감사합니다!

import java.util.List;
import java.util.Scanner;

public class GameView {

Choose a reason for hiding this comment

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

관심사 별로 view, model, controller로 패키지를 분리해 보는건 어떨까요?
패키지를 분리했을 때 model에서 view를 참조하면 view패키지를 임포트 해야하기 때문에 더 직관적으로 의존 여부를 확인할 수 있는 장점도 있답니다😃

Copy link
Author

Choose a reason for hiding this comment

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

자바에서 패키지 별로 관리하는 것은 알고 있었는데 막상 적용하려하니 헷갈리네요... 참고하여 더 공부한 후 적용해보도록 하겠습니다!

this.random = random;
}

public void initializeGame(String[] carNames, int runCount) {

Choose a reason for hiding this comment

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

initializeGame은 생성자의 역할에 가까운 것 같습니다!

setRunCount, joinCars와 같은 함수로 분리해보는건 어떨까요?

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 40 to 51
List<String> winners = new ArrayList<>();
for (RacingCar car : racingCars) {
checkWinners(car, maxPosition, winners);
}
return winners;
}

private void checkWinners(RacingCar car, int maxPosition, List<String> winners){
if (car.getForwardCount() == maxPosition) {
winners.add(car.getCarName());
}
}

Choose a reason for hiding this comment

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

void함수에서 인자의 참조를 이용해서 winners에 자동차를 추가하고 있어요!
checkWinners라는 메소드의 이름과 다르게 인자의 값을 바꿔버리는 예상치 못한 동작을 할 수 있을 것 같습니다.
(다른 사람이 해당 함수를 사용할 때 우승자인지 검사할줄 알았는데 인자로 넘겨준 winners가 바뀌면 당황스럽겠죠?)

이런 경우에는 checkWinner라는 함수에서 boolean을 반환하고, 우승자가 맞다면 winners에 추가해주는것도 좋아보입니다!

Suggested change
List<String> winners = new ArrayList<>();
for (RacingCar car : racingCars) {
checkWinners(car, maxPosition, winners);
}
return winners;
}
private void checkWinners(RacingCar car, int maxPosition, List<String> winners){
if (car.getForwardCount() == maxPosition) {
winners.add(car.getCarName());
}
}
List<String> winners = new ArrayList<>();
for (RacingCar car : racingCars) {
if (checkWinner(car, maxPosition)) {
winners.add(car.getCarName);
}
}
return winners;
}
private boolean checkWinner(RacingCar car, int maxPosition){
return car.getForwardCount() == maxPosition;
}

Copy link
Author

Choose a reason for hiding this comment

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

depth 1길이에 맞춰 모듈화를 하다보니 예상치 못한 실수를 한것 같습니다... 꼼꼼히 봐주셔서 감사합니다! 머리로는 알고 있어도 막상 코드를 짜보니 boolean을 통한 검사는 생각치 못했네요..

Comment on lines +10 to +12
public RacingGame(Random random){
this.random = random;
}

Choose a reason for hiding this comment

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

이렇게 생성자를 이용해서 초기화 해주는 방식을 이용하면 의존성 주입을 해줄 수 있습니다!
테스트에서는 요 Random을 테스트의 입맞에 맞게 넣어주면 되겠죠??
방법은 어려가지가 있을 수 있겠지만 현재 짜여진 코드를 바꾸지 않고 원하는 랜덤값으로 테스트를 해보려면 다음과 같은 방법이 있을 것 같아요!

  1. Random을 상속하는 mockRandom을 만든다.
  2. nextInt()함수를 재정의 한다. 이때 함수의 반환값은 nextInt의 인자를 그대로 반환한다.
  3. 테스트에서는 RacingGame을 생성할 때 mockRandom을 이용하여 초기화 한다.
public class MockRandom extends Random {

    private int value = 0;

    public void setRandom(int value) {
        this.value = value;
    }

    @Override
    public int nextInt(int bound) {
        return value;
    }
}
@Test
    void 자동차_전진_테스트() {
        MockRandom mockRandom = new MockRandom();
        RacingGame game = new RacingGame(mockRandom);
        mockRandom.setRandom(4); // 랜덤값이 4만 뜨게 설정
        .
        .
        .
    }

아리님의 코드를 수정하지 않고 랜덤값을 테스트 하기 위해서 상속을 사용했지만, interface를 활용할 수 도 있답니다. 이부분에 대해서는 추가적으로 학습해 보시면 좋을 것 같아요!

Copy link
Author

Choose a reason for hiding this comment

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

감사합니다! mock이 정확히 무엇인지 이해하지 못했는데 많은 도움이 됐습니다! 알려주신 내용 참고하여 학습해보겠습니다!

@boorownie boorownie merged commit 0a4f1d6 into next-step:dldb-chamchi Feb 18, 2025
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.

4 participants