[플레이그라운드] TDD, 클린 코드 (6) - 자동차 경주 (피드백 후)



피드백 강의 영상 시청 후 변경한 부분을 써보겠다.


전체 코드

https://github.com/Kyuyoung11/java-racingcar-playground


기능 목록

[기능 목록]
1. 각 자동차 이름 부여  
    1-1. 자동차 이름 입력 받기 (* 5자 초과 안됨)  
    1-2. , 기준으로 자동차 이름 구분  
2. 시도할 횟수 입력  
3. 시도횟수 만큼 자동차 전진 시도  
   3-1. 0~9 random값 구하기   
   3-2. 4이상이면 전진  
   3-3. 전진
   3-4. 출력  
4. 우승자 출력

1. Car 전진 후 출력하는 메소드 분리

기존에는 Car에 toString을 Override하여 구현하였다.

[수정  코드]

public class Car {
    ...

    @Override
    public String toString() {
        StringBuilder distanceBuilder = new StringBuilder();
        for (int i=0; i<this.distance; i++)
            distanceBuilder.append('-');
        return name + " : " + distanceBuilder.toString();
    }
}

해당 코드의 문제점

  • 비즈니스로직(Car 기존 메소드)과 UI로직(view용 print)가 분리되어야 함.
    • distance를 ‘-‘로 표현하는건 view용 print니까 단일 책임 원칙 위배~
[수정  코드]

public class RacingPrinter {
    public static String makeHyphens(int count) {
        StringBuilder sb = new StringBuilder();
        for (int i=0; i<count; i++) {
            sb.append('-');
        }
        return sb.toString();
    }

    public static void printCar(Car car) {
        System.out.print(car.getName()+" : " + makeHyphens(car.getPositionValue()));
    }
}

2. Strategy패턴 이용한 MovingStrategy 구현

장점

  • Random 관련된 메소드를 분리시켜서, 테스트 불가능한 메소드를 최소화할 수 있음
public class Car {
  ...
  public void move(MovingStrategy movingStrategy) {
    if (movingStrategy.movable())
      this.position = this.position.add();
  }
  ...
}

move 메소드를 호출하면서, 클래스를 따로 써줄 수 있음

car.move(new RandomMovingStrategy());
car.move(() -> true);

//이것과 동일한 코드
car.move(new MovingStrategy() {
    @Override
    public boolean movable(){
        return true;
    }        
});

이걸 단위테스트코드에 적용하면 아주 용이하다~~


3. 원시값 포장

Car의 멤버변수들을 클래스로 구현

public class Car {
    private final Name name;
    private Position position;
  ...
}


public class Name {
  private static final int MAX_NAME_LENGTH = 5;

  private final String name;
  
  public Name(String name) {
    _validate(name);
    this.name = name;
  }
  private void _validate(String name) {
    if (name.length()<=0) {
      throw new IllegalArgumentException("자동차 이름은 1자 이상이어야 합니다.");
    }

    if (name.length()>=MAX_NAME_LENGTH) {
      throw new IllegalArgumentException("자동차 이름은 "+MAX_NAME_LENGTH+"자를 초과할 수 없습니다.");
    }
  }
  ...
}

장점 (주관적인 의견임)

  • MAX_NAME_LENGTH 같은 상수가 좀 더 클래스 자체와 어울리는 위치에 온 기분이다.
  • validate 메소드까지 Car에서 가져올 수 있으니, 역할이 분명해진 것 같다.

강의를 통해 알게 된 내용

  1. Strategy 패턴을 적절히 사용함으로써 테스트가 어려운 로직 분리
  2. 일급 컬렉션
  3. 원시값 포장
  4. GIT 커밋 메시지에 더 신경쓰기

나는 아직 멀었다

다른 글