Search
Duplicate

4. [클린 코드 with Java] 3단계 - 자동차경주

링크
점수
⭐️⭐️⭐️⭐️
완료일
2023/04/12
상태
완료
유형
인강

기능 요구사항

초간단 자동차 경주 게임을 구현한다.
주어진 횟수 동안 n대의 자동차는 전진 또는 멈출 수 있다.
사용자는 몇 대의 자동차로 몇 번의 이동을 할 것인지를 입력할 수 있어야 한다.
전진하는 조건은 0에서 9 사이에서 random 값을 구한 후 random 값이 4이상일 경우이다.
자동차의 상태를 화면에 출력한다. 어느 시점에 출력할 것인지에 대한 제약은 없다.

실행 결과

위 요구사항에 따라 3대의 자동차가 5번 움직였을 경우 프로그램을 실행한 결과는 다음과 같다.

코드 구성

작성코드

피드백

리뷰어 : 자동차의 위치를 -와 같은 기호로 변경하여 출력하는 건 View 클래스가 처리
생각정리 : 화면이 변경되도 클래스 로직은 변경되지 않도록 철저하게 뷰를 변경하는게 좋을 것 같다.
import java.util.Random; public class Car { private StringBuilder move;
Java
복사
다음과 같이 변경
package study.step3; import java.util.Random; public class Car { private int move = 0; private static final Random random = new Random(); private static final int STARTING_CONDITION = 4; private static final int RANDOM_RANGE = 10; private int attemptCount = 0;
Java
복사
출력에서 화면 노출되는 -은 뷰로 분리
package study.step3; import java.util.List; public class ResultView { public static void outPut(Track track) { StringBuilder stringBuilder = new StringBuilder(); List<Car> cars = track.getCars(); for (Car car : cars) { changeCarMoveToString(stringBuilder, car); stringBuilder.append("\n"); } System.out.println(stringBuilder); } private static void changeCarMoveToString(StringBuilder stringBuilder, Car car) { for (int i = 0; i < car.getMove(); i++) { stringBuilder.append("-"); } } }
Java
복사
리뷰어 : 객체지향 생활 체조 원칙 줄여쓰지 않는다.(축약 금지) calc 와 같은 축약어보다는 와전한 언어를 쓰는게 좋다.
생각 정리: 실무에서 항상 계산할 때는 calc라는 언어를 많이 사용한 것 같다. 하지만 이동하는 함수라면 'calcMove'보다는 자동차의 입장에서는 'move'라는 행위로 보는 것이 좋다는 의견이 있다. 이것은 맞는 말인 것 같고, 주체가 누구인지를 고려하면서 더 생각해야 할 것 같다.
move = new StringBuilder(); } public void calcMove() {
Java
복사
다음과 같이 변경
public void move() { if (isMove(random.nextInt(RANDOM_RANGE))) { move++; } attemptCount++; }
Java
복사
리뷰어 : 단위 테스트 작성 시, 경계 조건을 테스트 자동차의 이동 여부를 결정하는 기준 값이 4 이니, 4를 전달했을 이동하는지, 3을 전달했을 때 멈추는지를 검증
생각 정리: TDD를 하지 않아서, 어떤 경계를 테스트해야 하는지 아직 부족한 것 같다. 객체의 입장에서 값을 결정하는 부분을 테스트하려고 노력하자!
for (int i = 0; i < 10; i++) { if (i >= 4) { //when move = car.isMove(i); //then assertThat(move).isEqualTo(true); } }
Java
복사
다음과 같이 변경
@Test public void 자동차테스트_이동() { //given Boolean move; for (int i = 0; i < 10; i++) { if (i >= 4) { //when move = car.isMove(i); //then assertThat(move).isEqualTo(true); } } } @Test public void 자동차이동테스트_멈춤() { //given Boolean move; for (int i = 0; i < 4; i++) { if (i < 4) { //when move = car.isMove(i); //then assertThat(move).isEqualTo(false); } } }
Java
복사
리뷰어 : Track 인스턴스를 생성할 때, 자동차를 대수만큼 생성해서 전달해보는 건 어떨까요?누군가 실수로 setCar()를 호출하지 않는다면 의도치 않은 결과가 발생할 수 있을 것 같아요.
생각 정리: 트랙 입장에서 자동차를 설정한다는 느낌받았다. set이 좋지 않음에도 불구하고 의미적으로 좋지 않을까 작성하게 되다. 처음에는 생성자로 했었는데, 역시 set은 의도치 않은 결과를 발생시킬 여지가 충분하니까... 생성자를 사용하자
public void setCar(int count) { for (int i = 0; i < count; i++) { cars.add(new Car()); } };
Java
복사
다음과 같이 변경
Track(int carCount, int finish) { this.finish = finish; for (int i = 0; i < carCount; i++) { cars.add(new Car()); } }
Java
복사
리뷰 : 정적 메서드만을 갖는 유틸리티 클래스이니, private 생성자를 추가해보면 어떨까요? 링크를 참고해보시면 좋을 것 같습니다.
생각 정리: 한 번도 정적 메서드만 있다고 해서 막으려고 생각해 본 적은 없는 것 같다. 메모리에 올라가 있기 때문에 바로 사용할 수 있었는데, 생성자도 막는 것이 더 안전하다는 생각이 들었다.
왜 생성자를 막는 것이 안전한가? 생성자를 명시하지 않으면 컴파일러가 기본 생성자를 구현하기 때문에 상속받아서 인스턴스화를 할 수 있기 때문이다. 이것도 막기 위해서 private 생성자를 만든다.
package study.step3; import java.util.List; public class ResultView { public static void outPut(Track track) { StringBuilder stringBuilder = new StringBuilder(); List<Car> cars = track.getCars(); for (Car car : cars) { changeCarMoveToString(stringBuilder, car); stringBuilder.append("\n"); } System.out.println(stringBuilder); } private static void changeCarMoveToString(StringBuilder stringBuilder, Car car) { for (int i = 0; i < car.getMove(); i++) { stringBuilder.append("-"); } } }
Java
복사
다음과 같이 변경
public class InputView { private InputView() { // 생성자 내부 호출 -> 명시적 Exception throw new AssertionError(); }
Java
복사

피드백 이후 소스코드

작성코드

후기

오늘은 자동차 경주를 만들게 되었다. 혼자 생각하는 것보다 누군가 함께 한다는 게 정말 큰 도움이 된다는 것을 다시 한 번 느끼게 되었다. 잘 굴러가면 되는 것이 아니라 모르는 사람도 보고 수정할 수 있게, 그리고 안전하게 만드는 것이 대단한 일이라는 것을 다시 한 번 느꼈다. 많이 물어보고, 많이 배워서 유지보수하기 쉬운 소스를 지향하자!
깃허브 링크
4367
pull
출처