
시간도 참 빠르다.
학교를 다니며 정신없이 미션을 하다보면 일주일이 후딱 가있다..
요즘 시간이 너무 없다는것도 느끼는 것 같다ㅠㅠ 😭😭
하지만 그래도 2주차 프리코스 미션도 수행 했으니 생각도 정리할 겸 회고록을 작성해 보자!!!!
일정관리
다른 분의 회고에 노션으로 일정 관리를 하시는 것을 보고 기존에 메모장에 관리하던 것에서 노션으로 일정관리 방법을 바꾸었다.

이렇게 일정관리를 적으면서 짜잘짜잘하게 나누니 좀 더 정리가 잘 되고, 성취감도 시각적으로 보여 잘 한 선택이라는 생각이 든다!!
2주차 목표 및 회고
이번 2주차는 레이싱카 게임 구현이였다. 상세 구현은 다음과 같다.
초간단 자동차 경주 게임을 구현한다.
- 주어진 횟수 동안 n대의 자동차는 전진 또는 멈출 수 있다.
- 각 자동차에 이름을 부여할 수 있다. 전진하는 자동차를 출력할 때 자동차 이름을 같이 출력한다.
- 자동차 이름은 쉼표(,)를 기준으로 구분하며 이름은 5자 이하만 가능하다.
- 사용자는 몇 번의 이동을 할 것인지를 입력할 수 있어야 한다.
- 전진하는 조건은 0에서 9 사이에서 무작위 값을 구한 후 무작위 값이 4 이상일 경우이다.
- 자동차 경주 게임을 완료한 후 누가 우승했는지를 알려준다. 우승자는 한 명 이상일 수 있다.
- 우승자가 여러 명일 경우 쉼표(,)를 이용하여 구분한다.
- 사용자가 잘못된 값을 입력할 경우 IllegalArgumentException을 발생시킨 후 애플리케이션은 종료되어야 한다.
출력 예시
경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)
pobi,woni,jun
시도할 횟수는 몇 회인가요?
5
실행 결과
pobi : -
woni :
jun : -
pobi : --
woni : -
jun : --
pobi : ---
woni : --
jun : ---
pobi : ----
woni : ---
jun : ----
pobi : -----
woni : ----
jun : -----
최종 우승자 : pobi, jun
1. OOP 적용
OOP를 적용하려고 고민을 상당히 많이 했다.
객체의 책임이 어디까지인가?? 에 대한 고민을 계속 하면서 코드를 짜고 있었다.
각 객체의 검증은 그 객체가 담당하도록 하고, 그에따라 움직이거나, 우승자를 찾는 등의 행동도 각자 맞는 객체가 하도록 설계했다.
RacingCars.java
public RacingCars play() {
List<RacingCar> movedCars = racingCars.stream()
.map(car -> car.move(Randoms.pickNumberInRange(0, 9)))
.toList();
return RacingCars.fromRacingCars(movedCars);
}
매직넘버 처리를 못한 것이 아쉽지만..
play()라는 메서드로 자동차들을 담고 있는 일급 컬렉션 객체가 각 자동차에게 “움직여라”라는 메시지를 보내며 경주 한 턴을 수행한다.
이때 실제 이동 여부를 판단하고 상태를 바꾸는 책임은 RacingCar 객체가 맡고, RacingCars는 단지 그 결과를 모아 새로운 컬렉션으로 반환한다.
즉, 데이터가 아닌 행동의 주체를 객체에게 위임하는 객체지향적 구조를 따르고 있다.
원본 리스트를 직접 수정하지 않고 새로운 RacingCars를 생성해 반환함으로써 불변성을 지키며, 외부에서 내부 상태를 변경할 수 없도록 캡슐화가 유지된다.
public boolean isMoreThan(RacingCar otherCar) {
return position.isMoreThan(otherCar.position);
}
public boolean isAtSamePositionAs(RacingCar other) {
return position.isSameAs(other.position);
}
getter를 사용하지 않기 위해 설계를 했다.
즉, 외부에서 getPosition()으로 내부 상태를 꺼내 비교하는 대신,
“이 자동차가 다른 자동차보다 더 앞서 있니?”, “같은 위치에 있니?”라는 질문 자체를 객체에게 보내는 방식이다.
이로써 데이터 중심의 절차적 접근을 피하고,
객체가 스스로 판단하도록 함으로써 캡슐화와 응집도를 높이고 객체지향적인 메시지 설계 원칙을 잘 구현하려고 노력했다.
getter를 쓰지 않고 각 객체에게 이런 판단을 하게 하는것을 처음 써봐서 이런 방식도 있구나라고 새롭게 느끼게 되었다.
2. MVC 적용
MVC 적용 부분에서
public class Controller implements Runnable {
private final InputView inputView;
private final OutputView outputView;
public Controller(InputView inputView, OutputView outputView) {
this.inputView = inputView;
this.outputView = outputView;
}
@Override
public void run() {
RacingCars racingCars = createRacingCars();
TryNumber tryNumber = createTryNumber();
Racing racing = Racing.of(tryNumber, racingCars);
Map<Integer, RacingCars> racingResults = racing.playRacing();
printRoundResults(racingResults);
printWinners(racingCars);
}
private void printWinners(RacingCars racingCars) {
RacingCars winner = racingCars.findWinner();
Winner result = Winner.from(winner);
WinnersDto winnersDto = WinnersDto.from(result.winners());
outputView.winnerPrint(winnersDto);
}
private void printRoundResults(Map<Integer, RacingCars> racingResults) {
RoundResultsDto roundResultsDto = RoundResultsDto.of(racingResults);
outputView.roundResultPrint(roundResultsDto);
}
private TryNumber createTryNumber() {
outputView.NumberRequestPrint();
String parsedTryNumber = inputView.inputTryNumber();
return InputNumberParser.parseToTryNumber(parsedTryNumber);
}
private RacingCars createRacingCars() {
outputView.initialPrint();
String inputNames = inputView.inputNames();
List<String> parsedRacingCars = InputRacingCarNamesParser.parseToRacingCars(inputNames);
return RacingCars.from(parsedRacingCars);
}
}
구현한 컨트롤러 전체이다.
보면 알겠지만, 컨트롤러가 많이 뚱뚱하다는 사실을 알 수 있다ㅠㅠ
이는 입력 파싱, DTO 생성, 도메인 실행, 출력 로직 등이 모두 한 클래스에 몰려 있다.
코드리뷰에서도 컨트롤러의 역할이 과도하게 확장되어 있다말씀하셔서 좀 역할을 나눠야 겠다는 생각을 했다.
public class RacingService {
public RacingResultDto run(String inputNames, String tryNumberInput) {
List<String> names = InputRacingCarNamesParser.parseToRacingCars(inputNames);
TryNumber tryNumber = InputNumberParser.parseToTryNumber(tryNumberInput);
RacingCars racingCars = RacingCars.from(names);
Racing racing = Racing.of(tryNumber, racingCars);
Map<Integer, RacingCars> results = racing.playRacing();
RacingCars winner = racingCars.findWinner();
return RacingResultDto.of(results, winner);
}
}
이런식의 서비스를 만들어, 한 레이싱을 담당하는 서비스 계층을 만들 수 있을 것이다!!
public class RoundResultsDto {
private final Map<Integer, RacingCars> racingResults;
public RoundResultsDto(Map<Integer, RacingCars> racingResults) {
this.racingResults = Map.copyOf(racingResults);
}
public static RoundResultsDto of(Map<Integer, RacingCars> racingResults) {
return new RoundResultsDto(racingResults);
}
public Map<Integer, List<RoundResultDto>> asViewRows() {
return racingResults.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
racingCarsEntry -> racingCarsEntry.getValue().getRacingCars().stream()
.map(car -> RoundResultDto.of(car.getRacingCarName(), car.getPosition()))
.toList(),
(roundResultDtos, secondRoundResultDtos) -> roundResultDtos,
TreeMap::new
));
}
}
이부분도 지금 DTO에 해당하는데 dto가 도메인을 알고 있기 때문에 어쨋든 view에서 도메인을 알게 된다.
사실 이 부분은 Map.copyOf()로 해결할 수 있다고 생각했지만, 알고보니 얕은 복사로 같은 참조를 한다는 사실을 새롭게 알게 되었다.
그래서 이번주 목표를 자료구조를 다시 복습하자! 로 세웠다. 아직 갈길이 멀다 ㅠㅠ
3. TDD
TDD 처음 사용할 때는 좋았다. 하나의 유닛 테스트를 할 수 있어 검증까지 한번에 할 수 있어 좋다고 생각을 했다.
하지만 코드를 리펙토링을 하면서, 코드를 하나씩 건들 때 TDD한 테스트 코드들이 점점 무너져 갔다.
이걸 직접 느끼면서 처음부터 설계를 잘 하고 나가야 겠다는 생각이 들었다. 아무리 TDD를 잘 했어도 리펙토링 과정에서 일어나는 변화까지는 예측을 하기 힘들다는 생각도 들었다.
이 세가지를 중점으로 하려했지만 지금 보이는 것과 같이 잘 된 부분과 잘 되지 못한 부분이 존재한다.
앞으로 더 남은 프리코스 더 공부 하며 열심히 수행해야 겠다.
매주매주 발전하고 있는 내가 되고 싶다.
'우테코' 카테고리의 다른 글
| 우테코8기 최종 코테 후기 + 근황 (0) | 2026.03.14 |
|---|---|
| [우테코 8기 프리코스] 디버깅 이해하기 (0) | 2025.10.24 |
| [우테코 8기 프리코스] TDD 이해하기 (0) | 2025.10.24 |
| [우테코 8기 프리코스] 2주차 NsTest 공부하기 (0) | 2025.10.23 |
| 우테코 8기 신청 + 1주차 프리코스 후기 및 회고 (0) | 2025.10.23 |