2주차 미션이 종료되었다.
이번 미션은 '얼마나 가독성이 좋아야하는가'를 초점으로 맞춰 시작하였다. 회고에 앞서 지난 1주차 때 세웠던 Try를 먼저 확인해보자!
지난 Try
- 가독성에 초점을 맞춰보자⭕
- 들여쓰기 깊이 2를 절대 엄수하자⭕
- 적당한 예외메시지도 적용해보자⭕
- MVC 패턴 적용도 고려해보자⭕
- 코드리뷰 열심히 달고 내 코드도 리뷰 받으면서 개선해보자⭕
- 스펀지가 되어보자🧽
public final class MovingCount {
private final int count;
private MovingCount(String input) {
validateInputIsDigit(input);
this.count = validateMoreThanMaxInteger(input);
}
public static MovingCount createMovingCount(String input) {
return new MovingCount(input);
}
//...//
}
모델의 생성자는 private
으로 닫아 놓고 정적 팩토리 메서드를 이용해 객체를 생성하는 로직을 사용하였다.
이를 이용해 각각의 메서드명으로 반환 객체의 특성을 나타내려했다..
다른 장단점은 추후 게시글로 작성해보려한다.
public final class RacingCars {
//...//
private void validateNamesEmpty(List<String> names) {
if (names.isEmpty()) {
throw new IllegalArgumentException(BLANK_CAR_NAME.toString());
}
}
private void validateNameNotBlank(String name) {
if (StringUtils.isBlank(name)) {
throw new IllegalArgumentException(BLANK_CAR_NAME.toString());
}
}
private void validateNameLength(String name) {
if (name.length() > CAR_NAME_LENGTH_LIMIT) {
throw new IllegalArgumentException(EXCEEDED_LIMIT_CAR_LENGTH.toString());
}
}
//...//
}
각 예외에 적절한 메시지를 위와 같이 나타내려하였다.
public enum InputError {
BLANK_CAR_NAME("자동차 이름은 비어있을 수 없습니다."),
EXCEEDED_LIMIT_CAR_LENGTH("자동차 이름은 제한 길이를 초과할 수 없습니다."),
NOT_DIGIT_MOVING_COUNT("이동 횟수는 숫자만 가능합니다."),
EXCEEDED_MAXIMUM_INPUT("이동 횟수는 2147483647을 넘을 수 없습니다.");
private final String message;
InputError(String message) {
this.message = message;
}
@Override
public String toString() {
return message;
}
}
관련 예외는 enum
으로 처리하였다. 상수값들을 한눈에 볼 수 있어 좋았다. 또한 테스트 코드에도 메세지 출력이 정상적으로 되는지 확인해 볼 때 오타의 걱정이 없었다.
@Nested
@DisplayName("입력값을 받아 경주 자동차를 생성한다.")
class createRacingCars {
@DisplayName("성공 - 입력값은 쉼표를 기준으로 분리된다.")
@Test
void success_Multiple_Input_With_Delimiter() {
// given
String input = "apple,bear,cake";
List<String> expected = List.of(input.split(","));
// when
RacingCars racingCars = RacingCars.createRacingCars(input);
// then
assertThat(racingCars).hasFieldOrPropertyWithValue("names", expected);
}
@DisplayName("실패 - 구분자로 분리한 입력값이 제한 길이를 초과하면 예외가 발생한다.")
@Test
void exception_Invalid_Input_Exceeded_Limit_Length() {
// given
String input = "apple,bear,chameleon";
// when // then
assertThatThrownBy(() -> RacingCars.createRacingCars(input))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage(EXCEEDED_LIMIT_CAR_LENGTH.toString());
}
}
getter
를 모든 모델 객체에 적용하지 않아 어떻게 테스트할지 막막했었다. 그러나
hasFieldOrPropertyWithValue("필드명", 객체);
메서드를 이용해 객체의 필드를 확인할 수 있다는 걸 새로 배웠고! 활용하였다.
좀 더 성장한 사고방식을 위해 다른 분들의 코드리뷰를 먼저 진행하였다. 가독성을 적용하기 위해 다양한 고수분들이 고안한 비법서들이 보였다. 그래서 이 모든 걸 적용해보겠다는 욕심을 가지고 프로젝트에 뛰어들었다.
처음부터 리팩토링을 최대한 줄이고 싶었다. 매직넘버를 적었다가 상수로 바꾸고 변수명, 메서드명도 썼다 지웠다를 반복했다. 간단한 반복문도 '더 나은 방법이 있어, 좀 더 찾아보자'를 고민하였다. 그러다 보니 구현이 상당히 지체되었고, 머리가 복잡해 진도가 나가지 않았다.
그렇게 이틀이 사라졌다. 그때 깨달았다. 순서가 잘못되었음을.
최우선 목표는 기능 구현을 완료하라
였다.
그렇게 동작하는 스파게티 코드를 만들어보자가 나의 최우선 코드가 되었고 진행속도는 상당히 향상되었다.
리팩토링은 그 이후가 좋았다. 코드 반영도 빨랐고 핵심 로직에 방해되지도 않았다.
물론 이렇게 된 이유는 아직 리팩토링 과정이 체화되지 않아서라 생각한다. 좀 더 숙달하면 처음부터 원시값 포장과 일급 컬렉션은 적용할 수 있지 않을까 란 기대가 생긴다.
이번 미션에는 상호 연관이없던 이동횟수와 차량위치를 이용한 로직이 필요하였고 '이게 Service 레이어에서 하나보다!' 라는 마음의 소리가 갑자기 들렸고 그렇게 구현하였다.
public class RaceService {
private final MovingCount movingCount;
private final CarsPosition carsPosition;
//...//
public RaceResultTexts getTotalResult() {
List<String> totalResult = new ArrayList<>();
for (int i = 0; movingCount.isNotEqual(i); i++) {
carsPosition.race();
RaceResultTexts eachRaceResult = carsPosition.getEachRaceResult();
totalResult.add(eachRaceResult.convertOneLine());
}
return new RaceResultTexts(totalResult);
}
//...//
}
아무래도 MVC 패턴을 프로젝트에 적용하려 하니 웹 프로젝트에 있던 Service 레이어를 둬야할거 같은 강박이 있었던거 같다.
그리고 들려온 피드백
'객체지향 설계'를 보고 아차 싶었다. 내가 잘못 생각하고 있음을 깨달았다. 충분히 객체들간의 협력으로 표현할 수 있던 것이었다.
다음 미션의 최대 목표.. 너로 정했다.
3주차도 역시 클린 코드를 적용해보려한다. 지난 Try 목록은 자연스레 keep으로 쌓여가게 된다.