[우아한 테크코스 프리코스 6기 BE] 2주차 회고 - 자동차 경주

Haiseong Jeong·2023년 10월 31일
0
post-thumbnail

경주에서 이기는 법

인생은 반환점 없는 마라톤이라고 할 수 있다. 되돌릴 수 없는 인생을 후회 없이 마무리하기 위해 언제나 최선을 다해야 한다. - 손기정

우아한 테크코스 프리코스 1주차가 끝나고 2주차에 들어섰다. 1주차 백앤드 과제 pr수가 2500개가 넘었다고 한다. 아직 경쟁자가 2500명 남았다는 것이다. 꼭 우아한테크코스 본과정에 들어가고 싶지만 붙지 못하더라도 후회없이 최선을 다해보자고 다짐했다. 이전에 과제를 하면 몰아서 하곤 했는데 프리코스 중에는 최소 4시간 매일매일 투자해서 과제를 하고있다. 이렇게 한다면 경주에서 이길 수 있지 않을까..?

코드리뷰 후기

지난주 과제를 커뮤니티에 올려 코드리뷰를 받았다. 또한 나도 다른사람들의 코드를 리뷰하며 여러 피드백을 받았다. 이 리뷰를 바탕으로 이번주 과제를 하면서 생각했던 것들을 적어본다.

  • 플로우 차트 : 다른사람들이 코드를 볼때 어떤 흐름으로 동작하는지 이해하기 쉬웠다는 피드백을 받았다.
  • 주석과 클래스 다이어그램 : 다른사람의 코드를 읽을때 주석과 클래스 다이어그램이 많이 도움이 되었다. 나도 이번 과제는 클래스 다이어그램을 그리고 주석을 잘 활용해야겠다.
  • view의 책임 : 지난 과제 코드에서 OutputView 부분을 지적받았다. OutputView가 출력할 문장을 controller가 들고 있었다. 이점에 대해서 리뷰를 받았었는데 이 메시지들을 OutputView가 가지고 이를 출력하는 메서드를 만드는게 더 맞다고 생각되었다.

자동차 경주

2주차의 미션은 자동차 경주였다. 우선 실행결과 예시를 보는게 좋을것 같다.

경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)
pobi,woni,jun
시도할 회수는 몇회인가요?
5

실행 결과
pobi : -
woni : 
jun : -

pobi : --
woni : -
jun : --

pobi : ---
woni : --
jun : ---

pobi : ----
woni : ---
jun : ----

pobi : -----
woni : ----
jun : -----

최종 우승자 : pobi, jun

자동차들의 이름을 입력받고 시도할 횟수(이하 차수)를 입력받는다. 차수만큼 라운드를 진행하는데 각 라운드마다 자동차들은 1~9의 무작위수를 발생시켜 4이상이면 전진한다. 가장 많이 간 차들이 우승자가 된다.

컬랙션 변경하기

처음에 이름을 키로, 자동차 객체를 값으로 매핑하는 Map 컬랙션을 이용해 자동차들을 관리하고자 했다. 일반적인 HashMap은 순서를 저장하지 않아 출력시 순서를 유지할 수 없으므로 LinkedHashMap을 이용해 구현했다. 그런데 구현하고 보니 Map을 사용하는 이점이 전혀 없었다. 오히려 코드가 복잡하기만 했다.

우선 순서를 저장해야 하므로 List 컬랙션을 생각했다. 여러 List중 순차적으로 값을 집어넣으므로 LinkedList보다는 ArrayList로 구현하는것이 효율적일 것이라 생각되었다.

처음에 망연자실하여 코드를 통으로 갈아엎어야 겠다고 생각했는데 생각보다 많이 걸리지는 않았다. 물론 원래 Map의 키로 사용하던 이름을 자동차의 맴버변수로 추가해야 하는 작업이 있었고 이걸 사용하는 controller의 수정이 있었지만 그것을 제외하면 다른 코드의 변경작업이 필요 없었다. 내 설계가 완벽하지는 않겠지만 꽤 구조가 잘 잡혀있어서 그런가 싶었다.

private 메서드 테스트하기

자동차 객체에서 한칸 음직이는 기능이나 음직일 조건을 판단하는 메서드가 필요했다. 이 메서드들은 자동차 객체 외에는 알 필요도 없고 알아서도 안된다고 생각되어 private 접근 지정자를 사용했다. 그러나 문제가 있었다. private 형식지정자를 사용하니 테스트가 불가능 했다. 특히나 이번과제의 결과가 렌덤값에 따라 달라지니 더욱 테스트 하기가 어려웠다.

그래서 고민에 빠졌다. 'private를 public으로 바꾸고 테스트를 할까?', '그냥 간단한 기능이니 테스트를 하지 말까?' 고민이 있었다. 특히 이번 게임에 가장 중요한 로직중 하나인 '1 ~ 9의 무작위 숫자를 발생시켜 4 이상이면 전진한다.' 라는 기능을 테스트 하는데 고민했던 질문들이다.

나는 처음에 전진할 조건을 판단하는 메서드인 isMovable에서 Randoms.pickNumberInRange(1, 9)를 바로 호출했는데 이는 테스트를 불가능하게 했다. 이를 해결하기 위해서 랜덤한 숫자를 발생시키는 부분을 분리해 하나의 객체로 다시 만들었다.

RandomNumberGenerator 인터페이스

public interface RandomNumberGenerator {
    int generateRandomNumber();
}


public class CarRandomNumberGenerator implements RandomNumberGenerator {
    private static final int RANDOM_START_INCLUSIVE = 1;
    private static final int RANDOM_END_INCLUSIVE = 9;

	...

	@Override
    public int generateRandomNumber() {
        return Randoms.pickNumberInRange(RANDOM_START_INCLUSIVE, RANDOM_END_INCLUSIVE);
    }
}

public class RacingCar {
    private static final int START_DISTANCE = 0;
    private static final int MINIMUM_RANDOM_NUMBER_TO_MOVE = 4;

    private final String name;
    private int distance;
    private final RandomNumberGenerator randomNumberGenerator;

    public RacingCar(String name) {
        this.name = name;
        this.distance = START_DISTANCE;
        this.randomNumberGenerator = new CarRandomNumberGenerator();
    }

    public RacingCar(String name, RandomNumberGenerator randomNumberGenerator) {
        this.name = name;
        this.distance = START_DISTANCE;
        this.randomNumberGenerator = randomNumberGenerator;
    }
    
    ...
}

RandomNumberGenerator를 만들고 RacingCar 클래스에 맴버변수로 추가해줬다. RandomNumberGenerator를 구현한 CarRandomNumberGenerator를 만들어 RacingCar의 생성자 public RacingCar(String name)에서 새로운 객체로 넣어주었다. 다른 생성자 public RacingCar(String name, RandomNumberGenerator randomNumberGenerator) 에서는 사용자가 직접 RandomNumberGenerator를 주입할 수 있게 하였다.

이를 이용해 자동차가 발생한 수에 따라서 이동을 잘 하는지 멈춰있는지의 결과를 테스트 할 수 있었다.

정적 팩토리 메서드

모든 RacingCar 객체가 CarRandomNumberGenerator를 새로 생성하여 가지고 있는것은 비효율적인것 같다. 그래서 CarRandomNumberGenerator를 리팩토링 했다.


public class CarRandomNumberGenerator implements RandomNumberGenerator {
    private static final int RANDOM_START_INCLUSIVE = 1;
    private static final int RANDOM_END_INCLUSIVE = 9;
    private static final CarRandomNumberGenerator CAR_RANDOM_NUMBER_GENERATOR = new CarRandomNumberGenerator();

    /**
     * 외부에서 객체를 생성할 수 없게함
     */
    private CarRandomNumberGenerator() {
    }

    /**
     * 인스턴스를 새로 만들지 않고 리턴
     *
     * @return CarRandomNumberGenerator 인스턴스
     */
    public static CarRandomNumberGenerator getInstance() {
        return CAR_RANDOM_NUMBER_GENERATOR;
    }

    /**
     * 1 ~ 9의 랜덤값을 리턴한다.
     * @return 1 ~ 9 사이의 정수
     */
    @Override
    public int generateRandomNumber() {
        return Randoms.pickNumberInRange(RANDOM_START_INCLUSIVE, RANDOM_END_INCLUSIVE);
    }
}

정적 팩토리 메서드를 이용하여 구현하기로 했다.
https://tecoble.techcourse.co.kr/post/2020-05-26-static-factory-method/
https://hudi.blog/effective-java-static-factory-method/

이를 이용해서 RacingCar가 만들어 질때마다 새로운 객체를 생성하는게 아니라 이미 만들어진 객체를 getInstance()를 이용해 가져와 사용한다.

그러나 '정적 팩토리 메서드를 이용해 객체를 만들 필요없이 generateRandomNumber()를 static 메서드로 구현하는게 더 좋을까?' 하는 의문이 남았다. 나는 코드가 지져분해지는게 보기 싫어서 정적 팩토리 메서드를 사용하는 방식을 유지했는데 다음주에 코드리뷰를 받으며 다른사람의 의견을 물어보고 싶다.

지난주 보다 나아진 점

  • 객체 설계
    • 객체를 설계하는데 지난주에는 '어떤 객체가 필요할것 같다' 정도만 미리 설계하고 들어갔었다면 이번주는 '어떤어떤 객체가 필요하고 여기에는 어떤 메서드가 필요하겠다' 정도의 레벨로 올라왔다. 그러나 아직 계속 설계사항이 수정되는건 어쩔 수 없나보다.
  • view 객체의 책임
    • 지난주 코드리뷰를 받고 바로 이번주 과제에 반영할 수 있어서 기뻤다. 코드리뷰를 적극 활용하여 다음주 과제를 더 잘하고 싶다.
  • 클래스 다이어그램
    • 처음 만들어본 클래스 다이어그램인데 잘 만들었는지는 모르겠다. 그러나 확실히 객체간의 상호작용을 볼 수 있어 좋은것 같다. 다음주에도 활용해봐야겠다.

과제를 진행하면서 아쉬웠던 부분

  • 커밋 메시지
    • 과제를 진행하고 로그를 보니 어떤 커밋에서 어떤 파일이 수정되었는지 보기가 어려운것 같다. 다음 과제에는 이 부분을 기억해서 어떤 파일이 수정되었는지 기록해야겠다.
  • 테스트 미리 만들기
    • 코드를 짜고 나니 테스트를 하기 어려웠다는 상황이 있었는데 다음주는 미리 어떤식으로 메서드가 테스트 될지를 생각하고 코드를 짜야겠다.

느낀점

우아한 테크코스 과제들은 어떻게 보면 간단한 문제를 푸는데 생각을 많이하게 되는것 같다. 이런 과정이 반복된다면 어려운 문제를 만나도 충분히 고민해보고 해결할 수 있을 것 같다. 다음주도 재미있는 과제가 나오면 좋겠다.

profile
나는 개발자다. 5000만큼 코딩한다.

0개의 댓글