자바 플레이그라운드 - 자동차 경주 게임(afterFeedback)

이호석·2022년 9월 14일
0

지금 이후로 beforeFeedback에 대한 포스트는 하지 않을 예정이다. 피드백 이후 내가 코드를 고치면서 어떠한 변화를 주었는지에 대해 더 집중하기 위함이다.

자동차 경주 게임

기능 요구사항

  • 각 자동차에 이름을 부여할 수 있다. 자동차 이름은 5자를 초과할 수 없다.
  • 전진하는 자동차를 출력할 때 자동차 이름을 같이 출력한다.
  • 자동차 이름은 쉼표(,)를 기준으로 구분한다.
  • 전진하는 조건은 0에서 9 사이에서 random 값을 구한 후 random 값이 4이상일 경우이다.
  • 자동차 경주 게임을 완료한 후 누가 우승했는지를 알려준다. 우승자는 한명 이상일 수 있다.

프로그래밍 요구사항

  • 자바 코드 컨벤션을 지키면서 프로그래밍한다.
    • 기본적으로 Google Java Style Guide을 원칙으로 한다.
    • 단, 들여쓰기는 '2 spaces'가 아닌 '4 spaces'로 한다.
  • indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다.
    • 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다.
    • 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메소드)를 분리하면 된다.
  • else 예약어를 쓰지 않는다.
    • 힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다.
    • else를 쓰지 말라고 하니 switch/case로 구현하는 경우가 있는데 switch/case도 허용하지 않는다.
  • 3항 연산자를 쓰지 않는다.
  • 함수(또는 메소드)가 한 가지 일만 하도록 최대한 작게 만들어라.
  • 모든 기능을 TDD로 구현해 단위 테스트가 존재해야 한다. 단, UI(System.out, System.in) 로직은 제외
    • 핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 구분한다.
    • UI 로직을 InputView, ResultView와 같은 클래스를 추가해 분리한다.
  • 모든 원시 값과 문자열을 포장한다.
  • 일급 컬렉션을 쓴다.

코드

코드량이 많아서 링크로 첨부한다.
깃허브 링크

피드백 이후의 변화

원시값, 문자열 포장, 일급 컬렉션, stream도 작성해봄
강의에서는 getMaxPosition의 반환값이 int인채로 끝이 났지만 난 Position으로 다 바꿔서 해봄

피드백 영상들을 보고난 뒤 반드시 구현해보고 싶은 것은 원시값, 문자열 포장과 일급 컬렉션이었다.

원시값 포장

public class Car {
	private String name;
    private int currentPosition = 1;

    public Car(String name) {
        validateName(name);
        this.name = name;
    }

    private void validateName(String name) {
        if(name.length() > 5){
            throw new IllegalArgumentException();
        }
    }
}

기존코드

public class Car {
	private static final int FORWARD_LIMIT_NUM = 4;

    private Name name;
    private Position position;

    public Car(String name) {
        this(name, 0);
    }

    public Car(String name, int position) {
        this.name = new Name(name);
        this.position = new Position(position);
    }
}

변경코드

기존에 원시값들이었던 것들을 다 포장하였다(원시값 포장이란?). 변경전에는 자동차의 이름을 Car 클래스에서 유효성 체크를 하였지만, 변경후에는 Name클래스에 위임되었고 단일 책임 원칙이 잘 지켜지고 있는 것을 확인할 수 있다(Position도 동일).

Car 클래스의 두번째 생성자에서 position을 인자로 받는 이유는 원활한 테스트를 위해서이다. TDD를 할 때는 테스트를 위한 코드는 어느정도 있어도 괜찮은 것 같다.

불변 객체

Car의 Position을 불변 객체가 되게끔했다.

public class Position {
    private final int position;

    public Position(int position) {
        if(position < 0){
            throw new IllegalArgumentException("위치는 0보다 작을 수 없습니다.");
        }
        this.position = position;
    }

    public Position increase() {
        return new Position(position + 1);
    }

    public boolean lessThan(Position position) {
        return this.position < position.getPosition();
    }
    
    public boolean equals(....) {....}

불변 객체를 만듬으로써 코드가 조금 더 안전한 코드가 되었다. 인스턴스의 데이터가 변하지 않으므로 개발자는 이것의 변경에 대한 걱정을 할 필요가 없게된 것이다.(그밖에 대해서는 추후 포스팅할 예정이다.)

Car의 Position 값을 증가하고 싶으면 불변 객체 유지를 위해서 position++;가 아니라 new 키워드를 통해서 새로운 객체를 반환한다.

Getter, 반환

getter의 사용을 자제해야하지만 써야되는 상황이 생기기 마련이다. 그럴 때는 위에 있는 포장값 그대로의 반환을 추천한다.

private Position getMaxPosition() {
        Position position = new Position(0);
        for (Car car : carList) {
            if(!car.getMaxPosition(position).equals(position)){
                position = car.getMaxPosition(position);
            }
        }
        return position;
    }

list< car >을 가진 일급컬렉션 상태인 Cars의 메소드이다. car.getMaxPosition()은 Position을 반환하는데, 이러면 equals 메소드를 통해 객체끼리 비교가 가능해진다.(조금 더 객체지향에 가까워진다)

스트림

private List<Car> getWinnerList(Position position) {
        List<Car> winnerList = new ArrayList<>();
        carList.stream().filter(car -> car.isWinner(position)).forEach(winnerList::add);

        /*for (Car car : carList) {
            if(car.isWinner(position)){
                winnerList.add(car);
            }
        }*/

        return winnerList;
    }

박재성님은 객체지향 연습을 할 때는 stream 사용을 자제하라 하셔서 앞으로의 강의에서는 쓰지 않을 예정이다. 일단 나는 만들어 보고 싶어서 둘다 만들었다.

후기

출력을 위한 ResultView를 작성하기 전까지는 getter가 없었지만, 출력 때문에 어쩔 수 없이 getter를 만들어야 했다. 박재성님은 get메소드 사용보다는 객체에게 메시지를 보내라(메소드 작성)고 말했지만, 그러면 Car 클래스가 출력을 하게 되는 상황이 생기면서 단일 책임 원칙을 지키지 않게 된다. 이럴때는 어떻게 해야할까?

사실 오랜만에 듣는 TDD 강의라 피드백 영상을 보기전에는 박재성님의 조언대로 코드부터 개발하고 테스트를 만들었다. 다음번에는 테스트 코드 먼저 작성하고 비즈니스 로직을 작성하고 싶지만 마음처럼 쉽지가 않다... 다행히 완강에 가까워질수록 성장하는 테스트 코드의 소중함이 커지는 느낌이다.

0개의 댓글