1주 차의 Problem을 해결하자.
회고의 목적은 문제를 인식하고 개선을 하는 것입니다.
1주 차의 Problem
1. JDK 17을 효과적으로 활용하지 못했다.
이 문제점을 해결하기 위해 JDK 17의 Record을 사용했습니다.
public record RoundResultDto(String name, int location) {
}
public record GameResultDto(List<String> winners) {
}
Record를 사용한 이유
- 간결성과 불변성이라는 장점을 가지고 있다.
- 필드 정의와 getter 메서드 작성 등의 반복적인 작업을 한 줄로 간결하게 표현할 수 있습니다.
- equals, toString, hashCode 등의 메서드를 자동으로 생성해 줍니다.
2. 의존성 주입
private final InputView inputView;
private final OutputView outputView;
private final RacingService racingService;
public RacingController(InputView inputView, RacingService racingService, OutputView outputView) {
this.outputView = outputView;
this.inputView = inputView;
this.racingService = racingService;
}
기존의 필드 주입에서 생성자 주입으로 전환하게 된 이유
- 필드 주입은 객체의 불변성을 보장하기에 어려움이 있습니다.
- 필드 주입은 객체가 필요로 하는 의존성을 알 수 없습니다.
- 의존성 주입은 인자를 통해 전달해 주기 때문에 객체가 필요로 하는 의존성을 알 수 있습니다.
3. Controller가 아닌 Model에서 View에게 값을 넘겨주었다.
MVC패턴에서는 Controller을 통해 View에게 값을 전달해줘야 한다고 알았는데, 공부를 해보니 잘못된 말이었다.
https://solution-is-here.tistory.com/200
각 사용자에게 다르게 보이는 코드는 Model에게 데이터를 받아서 출력합니다.
각 사용자에게 다르게 보여지는 값 외에는 Controller에서 View에게 값을 전달해서 출력합니다.
2주 차에서는 일급 컬렉션을 포함하고 있는 클래스에서 비즈니스 로직을 처리함으로써 Model에서 처리하지 않았습니다.
public class Cars {
private static final int THRESHOLD = 3;
private List<Car> carList;
public Cars(List<String> carNames) {
this.carList = carNames.stream()
.map(Car::new)
.collect(Collectors.toList());
}
public void playGame() {
for (Car car : carList) {
checkNumberAndMoveCar(car);
printCarInfo(car);
}
ConsoleOutput.printNewLine();
}
private void printCarInfo(Car car) {
OutputView outputView = new OutputView();
outputView.printRoundResult(car.nameAndLocation());
}
}
printCarInfo는 각 라운드별 정보를 출력하는 메서드인데 이때, 라운드별 정도는 각 사용자마다 다르게 보는 화면이므로 일급 컬렉션을 포함한 클래스에서 바로 View로 값을 전달해 출력했다.
4. 1주 차의 Problem을 해결하면서 느낀 점
1주차의 Problem을 개선하면서 참 많은 것들을 느꼈다.
"왜? 이렇게 했을까?"라는 생각을 가장 많이 했던 것 같다. 그만큼 우테코를 통해 한 주 동안 폭풍성장한 나를 볼 수 있었다.
무엇보다도 블로그를 통해 정리를 한 뒤, 사용해 보니 정리를 하기 전에 사용했던 것보다 훨씬 수월하게 사용할 수 있어 좋았다.
3주 차 문제를 풀 때도 새로 적용하고자 하는 개념이 있으면 블로그로 먼저 작성을 해야겠다는 생각을 했다.
Keep
1. Dto를 통한 데이터 전달
Dto가 아닌 인자를 통해 데이터를 전달할 수도 있지만 Dto를 통해 전달하는 가장 큰 이유는 "유지보수성"입니다.
인자를 통해 데이터를 전달한다고 가정했을 때 매개변수의 개수가 4~5개이면 보내야 하는 인자의 종류도 4~5개이므로 메서드가 복잡해질 수 있습니다. 또한 매개변수의 순서에 대한 의존성이 존재해, 결합도가 높아지는 단점이 있습니다.
이러한 단점을 해결하고자 Dto를 통해 데이터 전달하는 방법을 3주 차에서도 적용하려고 합니다.
2. MVC 패턴
MVC 패턴의 가장 큰 장점은 역할별로 클래스를 나눔으로 인해, 응집도가 높아지고 결합도가 낮아지죠.
그러므로 인해 자연스럽게 유지보수성이 높아집니다.
public class RacingController {
private final InputView inputView;
private final OutputView outputView;
private final RacingService racingService;
public RacingController(InputView inputView, RacingService racingService, OutputView outputView) {
this.outputView = outputView;
this.inputView = inputView;
this.racingService = racingService;
}
public void run() {
racingGameStart();
}
private void racingGameStart() {
List<String> carNames = getCarNames();
Cars cars = new Cars(carNames);
int tries = getTries();
racingGame(cars, tries);
}
private List<String> getCarNames() {
ConsoleOutput.displayGetCarNameMessage();
String input = inputView.getCarNames();
List<String> carNames = racingService.stringToList(input);
InputValidator.isLengthGreaterThanFive(carNames);
return carNames;
}
private int getTries() {
ConsoleOutput.displayTryMessage();
int tries = inputView.getTries();
InputValidator.isGreaterThanZero(tries);
ConsoleOutput.printNewLine();
return tries;
}
private void racingGame(Cars cars, int tries) {
ConsoleOutput.displayRaceResultMessage();
IntStream.range(0, tries)
.forEach(i -> cars.playGame());
outputView.printGameResult(cars.getWinner());
}
}
위 코드는 2주 차 미션의 Controller 클래스입니다.
Controller 클래스에서 일급 컬렉션을 포함하고 있는 함수를 통해 데이터를 가공하고, View를 통해 출력하고 있는 모습을 볼 수 있습니다. 만약 일급 컬렉션을 포함하고 있는 함수 내부 로직이 변경된다고 가정했을 때 이는 Controller에게 전혀 영향을 주지 않습니다. 이처럼 MVC는 코드의 역할이 분리되어 있기 때문에 필요한 부분을 신속하게 수정할 수 있습니다.
3. 도메인 로직을 생성하자
public class Car {
private String name;
private int location;
public Car(String name) {
this.name = name;
}
public void moveCar() {
location += 1;
}
public RoundResultDto nameAndLocation() {
return new RoundResultDto(name, location);
}
public boolean isSameLocation(int otherLocation) {
return this.location == otherLocation;
}
public String name() {
return this.name;
}
public int location() {
return this.location;
}
}
위 코드는 도메인 로직을 포함하고 있는 Car 클래스입니다.
이 클래스는 Car의 상태를 변화하거나 조회하는 로직을 내부에 포함하고 있으므로, 수정이 필요할 경우 해당 클래스만을 수정하면 되어 유지보수가 편합니다. 또한 도메인 객체는 다른 부분에서 재사용될 수 있으므로 재사용성이 크게 높아집니다. 그리고 테스트를 할 때 특정 입력에 대한 올바른 출력, 상태 변화를 하는지 테스트하기에 용이해집니다.
Problem
1. 값을 하드코딩 하지 않는다.(공통 피드백)
public class Car {
private String name;
private int location;
public Car(String name) {
this.name = name;
}
public void moveCar() {
location += 1;
}
}
Car 클래스의 moveCar 메서드를 보면 location += 1;을 하는 모습을 볼 수 있습니다.
이때 1은 차가 움직이는 거리임으로 변하지 않는 값입니다.
이러한 값들은 상수로 정의를 해야 합니다.
2. 변수 이름에 자료형은 사용하지 않는다. (공통 피드백)
private List<Car> carList;
변수 이름에 자료형, 자료구조 등을 사용하지 않습니다.
3. 기능 목록을 업데이트한다. (공통 피드백)
2주 차 미션의 커밋 기록을 보면 끝낸 뒤, 한꺼번에 기능 목록을 업데이트하는 커밋을 볼 수 있습니다.
시작할 때 모든 기능 목록을 완벽하게 정리해야 한다는 부담을 가지기보다는 기능을 구현해 가면서 문서를 업데이트해야 한다. (죽은 문서보다는 살아있는 문서로)
Try
1. 핵심 기능에 가까운 부분부터 작게 테스트를 만들어나간다.
ex) 물건을 구매했을 때 한도 내의 물건인지 확인하고, 결과를 알려준다.
-> 물건을 구매했을 때 한도내의 물건이면 구매를 진행한다.
-> 물건을 구매했을 때 한도 초과를 한다면 구매를 진행하지 않는다.
2. 구현 순서도 코딩 컨벤션이다.
상수 - 멤버 변수 - 생성자 - 메서드 순으로 작성해 보자.
3. else를 사용하지 말자
if에서 return을 사용해 else를 사용하지 말자.
다짐
주차를 거듭할수록 실력이 기하급수적으로 늘어나는 것을 느낄 수 있어서 너무 뿌듯했다.
하지만 공통 피드백을 보고 아직도 내가 잘못 알고 있는 게 많구나 라는 생각을 했다.
프리코스를 통해서 많은 것들을 접해서 좋고, 발전해서 좋다.3주 차도 파이팅