자동차 경주!
이전 기수 프리코스에서도 많이 등장했던 미션이어서 꽤나 반가웠다.
사실 7기 프리코스 시작 전 진행했던 스터디에서 자동차 경주 미션을 이미 한 번 시도했던 터라 리팩토링의 느낌이 강하지 않을까 싶었다.
하지만 1주 차 미션을 진행하면서 미션 수행 과정을 [최소한의 기능 구현 -> 기본 테스트 코드 통과 확인 -> 리팩토링 -> 추가 요구 기능 수립 -> 테스트 코드 추가]로 정해두었기에 2주 차 미션도 이에 맞추어 진행하기로 했다.
1주 차 미션이 워밍업 정도라는 것을 2주 차 미션을 하면서 더더욱 느꼈다.
1주 차에는 Application의 main method에서 구현을 하면서 '객체를 사용하고 싶다'라는 생각을 하지는 않았는데, 이번주엔 main method에서 구현을 위해 컬렉션을 활용하면서 '컬렉션보다 객체를 사용하는 것이 더 좋을 것 같은데..'라는 생각을 하기도 했다.
스터디로 이미 미션을 경험해 본 터라 이런 생각이 들었을지도 모른다.
과제를 진행하며 느낀 여러가지 소감을 더 자세히 기록하기 전에, 2주 차 중간 회고를 해보려 한다.
지원서에 작성한 목표의 달성률과 그 이유
1주차 회고에도 작성했듯, 지원서에 작성한 목표는 딱 두 가지였다.
- 건강하게 개발하기
- 숲과 나무를 모두 보기
1주 차를 끝내고 난 시점에서는 두 가지를 모두 100% 잘 달성했다고 생각했다.
2주 차 미션을 마친 지금도 두 가지 목표는 모두 100% 달성했다고 생각한다.
건강하게 개발하기는 잡아두었던 기준점인 '최소 주 3회 8000보 이상 걷기'를 만족했기 때문에 100% 달성했다.
숲과 나무를 모두 보기는 이번 주에도 역시 제공되었던 요구사항들을 모두 꼼꼼하게 확인하며 진행했고, 빠짐없이 구현했다고 생각하기 때문에 100% 달성했다고 본다.
지원서에 작성한 목표의 변경 여부와 그 이유
지원서에 작성한 목표를 변경해야 한다고 묻는다면 변경할 필요는 없다고 생각한다.
1주 차, 2주 차에서 해왔듯 남은 프리코스 기간 동안에도 세웠던 목표를 꾸준히 잘 달성해 나가면 될 것 같다.
지원서를 작성하기 전 들었던 입학 설명회에서 방향성을 정하고, 구체적으로 나를 위해 달성했을 때 뿌듯한 목표를 설정하라는 코치들의 팁을 듣고 세웠던 목표이다.
더 나은 개발자가 되기 위해 세웠던 건강하게 개발하기, 숲과 나무를 모두 보기 라는 목표는 여전히 내게 매주 성장할 기회를 주는 목표들이다.
프리코스를 진행하면서 느낀 눈에 띄는 변화나 깨달은 점
프리코스를 진행하면서 MBTI가 S인 나도 샤워하면서 다른 생각을 할 수 있다는 것을 깨달았다.
성급한 일반화의 오류일 수 있겠지만, 대부분 S인 사람들은 샤워할 때 다른 생각을 하지 않고 '아~ 따뜻하다, 빨리 씻고 나가야지'와 같은 현재 상황을 생각한다고 한다.
미션을 하다가 생각대로 잘 풀리지 않을 때, 산책을 한다거나 샤워를 한다거나 환기를 시키려고 노력했는데 샤워를 하면서도 풀리지 않았던 미션의 내용을 계속 생각하고 있는 나를 발견했다.
이렇게 환기가 되었을 때 생각난 것들을 시도해보면 참신한 생각들이라서 꽤나 효과가 좋았다.
그리고, 프리코스 커뮤니티에서 많지는 않지만 코드 리뷰를 몇 번 해보았는데 생각보다 재미있었다.
원래의 나라면 다른 사람의 코드를 보는 것도 어려워하고, 내 코드를 보여주는 것도 부끄러워했을 텐데 우테코를 거쳐간 많은 이들이 코드 리뷰의 장점을 많이 말했던 터라 경험해보고 싶었다.
몇 명 안 되었지만 코드 리뷰를 하면서 내가 리뷰어일 땐 상대방의 코드에 담겨있는 의미를 파악하려고 노력하게 되었고, 의미를 파악하지 못했거나 개인적으로 궁금한 부분이 있을 때 질문할 수 있었다.
또, 내가 고민했던 것들을 상대방이 모르고 있는 듯 하거나 고민 중인 것 같다면 내게 도움이 되었던 자료들을 함께 공유할 수 있었다.
내가 리뷰이가 되었을 땐 깊은 생각 없이 작성했던 코드들에 대한 반성을 할 수 있었다.
단순히 객체 생성을 하지 않고자 작성했던 view의 static method였는데, 생성자를 private으로 막아주지 않으면 어디서든 잘못된 객체가 생성될 수 있다는 것처럼 내가 보지 못했던 부분들을 알 수 있는 계기가 되었다.
1주 차 공통 피드백 - Git 커밋 메시지
이번 미션에서는 1주 차 피드백도 받아보고 이를 최대한 반영해 보려는 노력도 했는데, 이 중에서 특히 신경 쓴 것은 Git 커밋 메시지였다.
단순히 '기능 구현을 했어요'라고 커밋 메시지를 작성할 게 아니라 제목과 상세 내용을 나누어 자세히 작성할 수 있다는 것을 처음 알게 되었다.

물론 제목에 내용을 축약하여 간단히 작성한다면 훨씬 가독성이 좋은 커밋 메시지가 되겠지만, '무엇을'보다 '왜'에 집중하여 왜 이런 기능을 구현했는지, 왜 이런 리팩토링을 했는지 내용에 상세하게 작성할 수 있어서 좋았다.
1주 차 공통 피드백 - 디버깅
여전히 System.out.println()을 사용하여 프로그램의 실행 정도나 오류를 판단하곤 한다.
그렇지만 의식적으로라도 디버깅을 통해 문제점을 찾으려고 노력 중이다.
특히, 테스트 코드로 프로덕션 코드를 확인할 때 문제가 발생한다면 디버깅을 활용하기 좋다고 생각했다.
객체 생성 시점에 대한 고민
자동차 객체를 언제 생성해야 하는가 고민했다.
사용자로부터 입력받는 것을 검증하고, InputData라는 별도의 입력값 객체를 생성한 뒤에 컨트롤러에서 자동차 객체를 생성해주어야 하는가, 아니면 자동차 이름을 검증한 후 바로 자동차 객체를 생성해주어야 하는가.
처음에는 자동차 이름을 입력받고, 시도 횟수까지 입력받아야 자동차 객체를 생성하는 것이 맞지 않을까? 시도 횟수가 올바르지 않은 입력값이라면 쓸데없이 자동차 객체를 생성하는 것이 아닐까?라는 생각을 했다.
그렇지만 InputData라는 새로운 객체를 굳이 생성할 필요도 없겠다는 결론을 냈고, 검증된 자동차 이름은 바로 객체로 생성하면 되겠다고 생각했다.
객체과 하는 일과 일급 컬렉션
1주 차에서도 고민했던 부분인데, 이번에도 객체가 하는 일에 대해 고민하게 되었다.
Car 객체와 Cars 객체가 그 대상이었다.
'getter를 사용하는 대신 객체에 메시지를 보내자' 라는 테코블 글을 읽고 최대한 객체에게 메시지를 보내서 역할을 부여하도록 노력했다.
Car 객체 내부에 name, position을 두고 경주 라운드를 돌 때마다 랜덤값에 따라 position을 변경하도록 해야 했다.
객체 외부에서 객체로부터 position을 받아 변경하는 것이 아니라 랜덤값을 객체 내부로 받아 position을 변경하도록 처리했다.
최대한 원시값을 포장하려고 했는데, name과 position은 포장하지 못했다.
name과 position까지 포장하게 된다면 getter를 사용할 수밖에 없지 않을까?
이 문제에 대한 것은 1주 차에 공부했던 것처럼 객체가 같은 타입의 멤버 변수를 여러 개 가질 때 공부하는 것으로 남겨두고 싶다.
우승자를 선정하는 일은 다른 객체를 생성하여 부여할까 생각하다가, 어찌 되었든 List 형식의 Car를 꺼내와 결정해야 하는 부분이므로 이 역시도 Cars 객체 내부에 일임하기로 했다.
우승자를 선정하는 로직은
- 경주를 끝낸 자동차들을 position이 큰 순서대로 내림차순 정렬한다.
- position이 가장 큰 Car 객체를 변수에 저장하고, 이 Car 객체와 position이 같지 않다면 Cars에서 제거하여 Cars를 우승자 리스트로 만든다.
- 이 과정을 Cars 객체 내부에 우승자 선정 메서드로 만들고, 외부에서는 우승자 선정 메서드만 호출한다.
로 정하였다.
첫 번째 자동차를 position이 큰 순서대로 정렬하기 위해 Comparator 인터페이스의 compareTo 메서드를 Override 하여 재정의해주었다. 일반적인 사전 순서나 숫자의 오름차순이 아닌 position의 내림차순으로 컬렉션을 정렬하기 위함이었다.
- 참고한 글 : Java 정렬방법 Collections
또, Car 객체의 name이 동일하다면 같은 객체로 보아야 하므로 equals, hashCode 메서드를 Override 해서 재정의해주는 것에 대해서도 공부했다.
이 부분에 대해서도 'equals와 hashCode는 왜 같이 재정의해야 할까?' 라는 테코블 글이 굉장히 도움이 많이 되었다.
Cars 내부에서 List 자체를 수정하려다 보니 ConcurrentModificationException이 발생했다.
처음 보는 예외여서 찾아보니 컬렉션에서 내부 요소를 순회하면서 수정 또는 삭제하려고 할 때 발생한다고 한다.
처음엔 for문을 돌면서 최대 position을 갖는 자동차와 position이 같을 경우 리스트에서 자동차 객체를 삭제하려고 했는데, IDE에서 노란 줄로 수정된 코드를 알려주었다.
removeIf라는 멋진 메서드를 통해 해결할 수 있었다.
// 맨 처음 작성한 코드 ConcurrentModificationException이 발생한다.
for (Car car : cars) {
if (car.compareTo(maxPositionCar) != "0") {
cars.remove(car);
}
}
// IDE가 수정해준 코드
cars.removeIf(car -> car.compareTo(maxPositionCar) != "0");
테스트를 위한 getter
Car 객체 내부에서 position을 처리하게 되어 getter를 사용하지 않을 수 있었는데, Test에서 position을 확인해야 해서 getter를 만들 수밖에 없었다.
테코블을 읽다 보니 Test에서 getter를 사용하지 않고 객체 내부의 필드값을 확인할 수 있는 방법이 있었는데, assertThat의 extracting method를 사용하는 것이었다.
@ParameterizedTest
@CsvSource(value = {"4,1", "3,0"}, delimiter = ',')
@DisplayName("전달된 값이 4 이상일 때만 자동차가 이동하는지")
void moveCar(int power, int expectedPosition) {
Car car = new Car("pobi");
car.move(power);
assertThat(car).extracting("position").isEqualTo(expectedPosition);
} // moveCar
새롭게 알아가는 기능들이 많다.
출력 테스트
출력값을 테스트하는 것을 처음 시도해 보았다.
표준 스트림을 Test에서 재할당해 주고 테스트 결과가 어떻게 출력되는지 확인하고 비교할 수 있었다.
랜덤값 테스트에 대해서도 어떻게 해야 하나 싶었는데, 우테코에서는 어떻게 테스트를 돌리지? 싶어 ApplicationTest를 뜯어보니 제공된 라이브러리의 assertRandomNumberInRangeTest method를 사용하고 있었다.
나도 이걸 활용할 수 있겠다 싶어서 적용해 보았더니 꽤나 마음에 드는 테스트 코드를 만들 수 있었다.

아직 학습 목표에 확장 가능성에 대한 언급이 없어 깊게 공부하지 않았는데, 앞으로 나오게 된다면 디자인 패턴에 대해서도 더 추가로 공부해보고 싶다고 생각했다.
다음 미션으로는 어떤 미션이 나올까?
여전히 기대된다!😆
2주 차 프리코스 코드 레포지토리 : https://github.com/ljhee92/java-racingcar-7/tree/ljhee92
'study > 우아한테크코스' 카테고리의 다른 글
| 우아한테크코스 7기 프리코스 3주 차 회고+ (부제: 왜요? 제가 0점 처리될 사람처럼 보이세요?) (2) | 2024.11.05 |
|---|---|
| 우아한테크코스 7기 프리코스 3주 차 회고 (9) | 2024.11.04 |
| 우아한테크코스 7기 프리코스 1주 차 회고 (3) | 2024.10.21 |
| 우테코 백엔드 6기 프리코스 포기! (3) | 2023.10.26 |
| 우아한테크코스 웹 백앤드 6기 지원 (0) | 2023.10.15 |