우아한 테크 코스 2023년 6기 프리코스 4주차 회고
긴 여정이었던 4주간의 과정 중
마지막 4주차 회고...
프리코스 중 마지막인 4주차가 시작되었다.

프리코스 3주차가 나에게 정말 어려웠기에,
기존의 1. 클래스를 분리하는 연습을 한다. 2. 단위 테스트를 작성하는 연습을 한다.
라는 목표를 그대로 가져가게 되어 정말 다행이라고 여겼다.
그런데 처음 4주차 요구사항 메일을 받았을때는 꽤나 당황했다.
모든 요구사항이 메일 형식으로 작성되어져 있었다.

뭔가 정성가득한 미션.. 이라고 느껴졌다.
그래서 그런지 사실 많은 요구사항에, 이전과 다른 제출 방식까지 내용이 가장 길었지만
스터디원들도 그렇고 모두 재미있어 보인다 고 느꼈다ㅋㅋㅋ
이번 주차는 제출 방식도 다르고,
3주차와 같이 산학연계 프로젝트와 팀 프로젝트에서 일이 발생할 수도 있으니
리뷰와 코드 작성을 병행하며 일찍 시작했다.
특히 private으로 생성 후, Wooteco를 초대해야해서 혹시 몰라 첫날에 초대하고
방식이 맞는지 최소 5번은 확인해줬다.
또 이번에는 4주차인 만큼,
연습보다는 기존에 있던 우테코의 피드백을 반영하는 것을 우선으로 삼아
기능 명세에도 우테코의 피드백을 나열해뒀다.

그리고 목표는 3가지만을 작성했다,
3주차에서 고난을 겪고
테스트 코드를 작성하는 이유에 대해서 깨닳을 수 있게 되었었다.
그래서 이번 4주차 에서는
기능을 하나 구현할때마다 Test를 작성했다.
코드를 수정할때는, 간단히 테스트 코드를 돌리고 요구사항에 맞췄는지
확인 하며 작성해 나갔다.🧐
public class MenuBoardTest {
private static final String NOTHING = "없음";
private static final String COUNT = "개";
private static final String BLANK = " ";
@DisplayName("메뉴 이름을 String으로 잘 만드는지 Test")
@ParameterizedTest
@CsvSource({
"ZERO_COLA, 2", "CAESAR_SALAD, 4"
})
void toMenuEventStringTest(String menuName, int menuCount) {
EnumMap<MenuBoard, Integer> menus = new EnumMap<>(MenuBoard.class);
MenuBoard menuBoard = MenuBoard.valueOf(menuName);
menus.put(menuBoard, menuCount);
String result = MenuBoard.toMenuEventString(menus);
assertEquals(result, menuBoard.menuName + " " + menuCount + "개");
}
@DisplayName("샴페인이 있는 경우 잘 가져오는지 Test")
@ParameterizedTest
@ValueSource(ints = {1})
void toChampagneAmountStringTest(int count) {
String champagneString = MenuBoard.toChampagneAmountString(count);
MenuBoard champagne = MenuBoard.CHAMPAGNE;
assertEquals(champagneString, champagne.menuName + BLANK + count + COUNT);
}
@DisplayName("주문 Type에 따른 boolean return Test")
@ParameterizedTest
@CsvSource({
"true, RED_WINE, CHAMPAGNE",
"false, T_BONE_STEAK, CHRISTMAS_PASTA"
})
void checkAllMenuTypesDrinkMethodTest(boolean result, String menuName, String menuName2) {
MenuBoard menuBoard = MenuBoard.valueOf(menuName);
MenuBoard menuBoard2 = MenuBoard.valueOf(menuName2);
Set<MenuBoard> menuBoards = Set.of(menuBoard, menuBoard2);
assertEquals(MenuBoard.isAllMenuTypesDrink(menuBoards), result);
}
@DisplayName("샴페인의 측정 가격에 따라 총 가격을 잘 가져오는지 Test")
@ParameterizedTest
@ValueSource(ints = {1})
void getChampagneTotalPrice(int amount) {
int totalChampagnePrice = MenuBoard.calculateTotalChampagnePrice(amount);
MenuBoard champagne = MenuBoard.CHAMPAGNE;
assertEquals(totalChampagnePrice, champagne.menuPrice * amount);
}
}
주기적으로 확인하며 불안해할 필요도 없고, 테스트 코드를 만들 시간을 따로 빼지 않아도 되어
마음의 여유가 생겼다.
method 하나로 여러 정보를 전달하기이번 크리스마스 프로모션은 상황에 따라 달라지는 부분들이 많았고,
method하나로 정보를 모두 전달할 방법은 없을지 고민했다.
예를 들어
주말일 경우, 메인 메뉴 개수당 2,023원을
평일일 경우, 디저트 메뉴 개수당 2,023원을 할인한다.
라는 부분이 있었고 나는 이를
// planMenu.java
public DateEventDto calculateDateDiscountAmount(PlanDate planDate, PlanMenu planMenu) {
boolean isWeekDay = planDate.isWeekDate();
if (isWeekDay) {
return planMenu.calculateTotalWeekDayDiscount();
}
return planMenu.calculateTotalWeekendDiscount();
}
// .... DateEventDto.java
public record DateEventDto(String dateType, int discount) {
}
record를 만들어 상호작용하도록 작성했다.
3주차에서는 getter을 이용해서 판단했다면, 4주차는 판단한 결과를 통해서 다시 객체에게 메시지를 전달하는 방식으로 구현해보았다.
개인적으로 메시지를 던지고
이를 받았을때 잘 처리할 수 있을 좋은 구조 또한 고민이 들게 되었다.
객체에게 스스로 일하도록 시키더라도,
그 안에서 일을 비효율적으로 한다면 의미가 없지 않을까?
특히 처음에는 menu의 종류를 모두 손으로 작성하려고 했었다.
그러나 그럴 경우 오타나, 변경 그리고 일치하는지 확인하는 부분에서 문제가 발생할 수 있다고 판단했다. 🔔
public enum MenuBoard {
MUSHROOM_SOUP(MenuType.APPETIZER, "양송이수프", 6000),
TAPAS(MenuType.APPETIZER, "타파스", 5500),
CAESAR_SALAD(MenuType.APPETIZER, "시저샐러드", 8000),
CHOCOLATE_CAKE(MenuType.DESSERT, "초코케이크", 15000),
ICE_CREAM(MenuType.DESSERT, "아이스크림", 5000),
ZERO_COLA(MenuType.DRINK, "제로콜라", 3000),`
...
final MenuType menuType;
final String menuName;
final int menuPrice;
MenuBoard(MenuType menuType, String menuName, int menuPrice) {
this.menuType = menuType;
this.menuName = menuName;
this.menuPrice = menuPrice;
}
...
public static boolean isAllMenuTypesDrink(Set<MenuBoard> menus) {
return menus.stream()
.allMatch(menu -> menu.getMenuType() == MenuType.DRINK);
}
이와 같이 MenuType이라는 Enum을 만들어 놓으면
타입을 추가하기도, 변경하기도 확인하기도 용이한 상태가 되었다고 생각했다!
물론 더 좋은 방식이 있을수도!
4주차가 끝나면서 가장 궁금했던 점이 있었다.
나 스스로는 성장했다고 느꼈지만 정말로 성장했을까? 😔😔
리뷰와 피드백을 내가 4주동안 흡수 했을까? 라는 의문이 들었다.
이를 위해 스터디를 진행하며 새롭게 2주차를 리팩토링 후 비교해보았다!

전과 후의 Class 모습

물론 내부 코드를 봐야 하겠지만
확실히 도메인에서 분리가 많이 늘어난 것 같았다!
특히 2주차도 5시간이라는 똑같은 제한을 두고 구현했음을 생각해봤을때
시야나 생각하는 방식이 바뀌었다고 느껴졌다!
// car.java
package racingcar.domain.car;
public class Car {
private final String name;
private int distance = 0;
public Car(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public int getDistance() {
return this.distance;
}
protected void moveCar() {
distance++;
}
}
이전에는 Car 객체가 get 메서드만 가지고 다른 일들을 하지 않은 모습으로 구현되어 있었다. 특히 이를 Cars에서 가져가 결과를 추출하는데 사용하곤 했다.
package racingcar.domain;
import racingcar.domain.dto.CarRaceResult;
public class RacingCar {
private final String name;
private int distance = 0;
public RacingCar(String name) {
this.name = name;
}
public CarRaceResult getRaceResult() {
boolean isEngineOn = new Engine().isOn();
if (isEngineOn) {
distance += 1;
}
return new CarRaceResult(name, distance);
}
public int checkGoFurther(int maxDistance) {
return Math.max(distance, maxDistance);
}
public boolean isMaxDistanceCar(int maxDistance) {
return distance == maxDistance;
}
public String getName() {
return name;
}
}
이번에는 RacingCar이 maxDistance를 가지고 직접 자신이 판단하고
그 결과를 return하는 방식으로 구현해 볼 수 있었다!
getName의 경우에도 Output 출력만을 위해 사용했다! 🥳
public class Application {
public static void main(String[] args) {
// TODO: 프로그램 구현
printMessage(OutputMessage.PLEASE_ENTER_CAR_NAME);
Cars cars = new Cars(inputNames());
RandomGenerator randomGenerator = new RandomNumberGenerator();
CarController carController = new CarController(cars, randomGenerator);
carController.run();
}
}
이 전에는 이와 같이 다소 복잡한 모습의 Application을 가지고 있었고,
첫 메시지 출력까지 Application이 담당하고 있었다.
public class Application {
public static void main(String[] args) {
InputView inputView = new InputView();
OutputView outputView = new OutputView();
new RagingGameController(inputView, outputView).run();
}
}
이후는 자주 호출하는 InputView와 OutputView를 의존성 주입하는 모습을
더 깔끔하게 구현해 볼 수 있었다.
package racingcar.view.output;
import java.util.Map;
public class Output {
public static void printMessage(OutputMessage outputMessage) {
System.out.println(outputMessage.message);
}
public static void printMessage(OutputMessage outputMessage, String messageValue) {
System.out.printf((outputMessage.message) + "%n", messageValue);
}
public static void printStatusMessage(OutputMessage outputMessage, Map<String, String> statusMap) {
statusMap.forEach((name, distance) ->
System.out.printf(outputMessage.message + "%n", name, distance));
}
public static void printNewLine() {
System.out.println();
}
}
이전에는 View에서 타입에 따른 출력 방식을 각각 명시해주었었다!
사실 이 방법도 마음에 들었지만 리뷰하며
새로운 방식을 알게 되었고
package racingcar.view.output;
public class OutputView implements Printer {
@Override
public void println() {
System.out.println();
}
@Override
public void printMessage(Output output) {
System.out.println(output.message);
}
@Override
public void printfMessage(Output output, Object... args) {
System.out.printf(output.message, args);
println();
}
}
리팩토링 때는 이러한 가변인자를 이용한 방식으로
적용해 볼 수 있었다!
private static void validateNameLength(List<String> names) {
long countNames = names.stream()
.dropWhile(name -> name.length() >= MINIMUM_NAME_LENGTH && name.length() <= MAXIMUM_NAME_LENGTH)
.count();
if (countNames > 0) {
throw new InputIllegalArgumentException(InputError.EXCEEDED_MAXIMUM_ERROR, MAXIMUM_NAME_LENGTH,
MINIMUM_NAME_LENGTH);
}
}
이전에는 이와 같이 검증하고, 이를 확인하여
InputIllegalArgumentException을 발생시키는 책임까지 모두 검증에서 해주고 있었다.
private void validateNotOverMaxLength(List<String> carNames) {
if (isOverLengthName(carNames)) {
throw new InputIllegalArgument(InputError.CAR_NAME_IS_OVER_MAX_LENGTH);
}
}
private boolean isOverLengthName(List<String> carNames) {
return carNames.stream()
.anyMatch(carName -> carName.length() > InputConstant.MAX_NAME_LENGTH);
}
리팩토링에서는
검증하고, 그 결과를 확인하는 책임을 분리하여 좀 더 깔끔한 모습이 되었다!
정말 4주동안 열심히 리뷰하고 피드백을 적용하려 했던 노력이
헛되지 않았던 것 같아 조금 뿌듯하다. 🥹🥹
아직 갈길이 멀지만 작은 성장을 했다는 것에 뿌듯해하며.. 이 영광을 코드리뷰어 분들께 드립니다.
4주 내내 떨어지더라도 아쉽긴 하겠지만 이게 최선이었다고 자신할 만큼
정말 우테코에 쏟아부었던 기간이었기에 순간순간에 후회되는 순간들은 없어 정말 다행이었다.
덕분에 새로운 사람들도 만나고
특히 우리 디스코드 새벽반과 우테코 송파구 모각코 방 분들...😊😊
4주차가 끝나고 나서도 일주일에 2번은 만날정도로
좋은 인연을 얻어간 것 같아 즐거운 시간들을 보내고 있다.
4주차까지 리뷰도 하고 리팩토링도 해보면서 앞으로의 날들을 이 지식과 경험들을 가지고
어떻게 나아갈 수 있을지 함께 고민하며 지내고 있다!
정말 힘들었지만 행복했고 다함께 열심히 해서 다른 자리에서 또 만날 수 있기를!
👋👋바이 우테코 프리코스!👋👋
그리고 하이 우테코를 할 수 있기를
오호 전, 후 비교라..! 1차 결과 기다리면서 자존감이 하늘과 바닥을 오가는데 바닥일 때 한 번씩 저도 프리코스 전 코드 들춰봐야겠어요😆😆
피드백 수용에 항상 열려있던 유진님이어서 성장이 분명했던 것 같다는 생각이 들어요! 우리 커비들 수고 많았다 진짜...😋