우선 4주차에 들어가기 전에 3주차 공통 피드백 중 내가 지키지 못한 것들에 대해 짚으면서
4주차에서는 피드백을 수용하려고 노력했다.
또한, 메일에서 받은 4주차의 목표는 다음과 같았다.
1. 클래스(객체) 분리
2. 리팩토링
따라서 이번 4주차에서는 3주차 피드백 수용 & 4주차 목표 달성을 목표로 두고 진행했다.
3주차 공통 피드백 중 내가 지키지 못한 것들
1. 발생할 수 있는 예외 상황에 대해 고려한다.
공통 피드백에 적혀져 있는 예외 상황들은 다 예외 처리를 해줬지만,
커뮤니티를 보다보니 예외 상황이 훨씬 많았고 (ex : 엄청 많은 금액 입력, 공백 입력 등)
설계 시 생각나는 예외만 적고 이후에 추가하자는 식으로 설계를 했었는데
프로그램 구현이 끝나고는 너무 지쳐버려서 추가 예외를 생각하지 못했다.
따라서, 이번 4주차에서는 예외 상황에 대해 더 고민을 많이하게 됐다.
(끝나고 보니, 이번 주차는 예외 상황이 많지 않았던 것 같다.)
2. 객체의 상태 접근을 제한한다.
3주차에서는 인스턴스 변수의 접근 제어자를 까먹고 생략해서
접근 제어자가 defalut로 적용되었다. (생략 시 default 적용)
InputView inputView = new InputView();
OutputView outputView = new OutputView();
LottoGameService lottoGameService = new LottoGameService();
따라서, 이번 주차에서는 객체의 접근 제어자를 private으로 설정하는 것을 잊지 않고 적용했다.
3. 객체(클래스) 분리하기
3주차를 진행하면서, MVC 패턴에만 집중해서 Model, View, Controller를 만드는 것에만 집중했다.
그러다보니 package인 model, view, controller 안에 객체들이 많아봐야 2개가 다였다.
그래서 하나 하나의 객체들이 수행하는 역할들이 많아졌었다.
이번 4주차의 목표가 객체를 분리하는 것이니 만큼 '객체 분리'를 머릿속에 넣고 진행했다.
(끝나고 보니 잘 한지는 모르겠다,,)
4주차 목표
1. 클래스 분리
이번 주차의 목표인 클래스 분리를 진행해봤다.
기본 조건 클래스들은 미션에서 제공해줘서 자동으로 분리가 됐었다.
3주차와 마찬가지로 Controller와 Service는 분리하여
Controller가 게임 로직 + 게임 진행 역할을 하던 것을 게임 진행 역할만 가지도록 분리했다.
3주차 코드와 다르게 클래스를 분리했던 점은 크게 다음과 같다.
1. Bridge Model 내에 여러 클래스 분리
2. Validate 관련 클래스 분리
첫 번째로, Bridge 관련 Model들의 클래스를 분리했다는 점이다.
3주차에서는 Model에 Lotto 하나의 객체가 여러 역할을 담당했었다.
아마, 클래스 분리라는 것을 생각하지 않고 3주차처럼 했다면,
이번 주차 Model도 Bridge 객체 하나로 했었을 것 같다.
하지만, 클래스 분리라는 목표를 가지고 설계했기 때문에 다음과 같이 단일 책임을 부여하여 클래스를 분리했다.
1. GeneratedBridge : 생성된 다리 관련 객체
2. PlayerBridge : 플레이어 이동 다리 관련 객체
3. PrintBridge : 출력용 다리 관련 객체
두 번째로, Validate 관련 클래스를 분리했다는 점이다.
이전에는 검증 로직을 Service 내에서 모두 처리해서
게임 로직과 검증 로직이 Service안에 섞여 단일 책임이 아니었다.
물론, 검증 로직을 게임 로직으로 본다면 단일 책임이었겠지만
내가 생각하기에는 검증 로직과 게임 로직은 다른 책임이라고 생각하여
이번에 검증만 처리하는 ValidateService를 생성하여 분리하게 되었다.
또한, 이전에는 여러 검증 요소들의 검증하고 예외를 체크하는 로직들을 Service 한 곳에서 모두 처리하였다.
이것도 요소 하나마다 검증기를 만들어서 처리하는 것이 단일 책임과 가독성에도 좋을 것 같아서
검증 요소마다 검증기를 만들어서 분리 후 ValidateService에서 검증기를 사용하는 것으로 구현했다.
2. 리팩토링
이번 주차의 또 다른 목표는 리팩토링이었다.
코수타 중에서 다음과 같은 과정으로 리팩토링을 하는 것을 추천한다고 하셨다.
1. 동작하는 코드를 먼저 만든다.
2. 동작하는 코드를 보호하는 테스트 코드를 만든다.
3. 테스트 코드가 보호해주는 것을 믿고 리팩토링을 진행한다.
따라서 이 과정으로 리팩토링을 진행하게 됐다.
이번 주차에서 리팩토링 한 것은 크게 다음과 같다.
1. 컨트롤러의 역할이 너무 커져서 서비스로 분리
2. 연관성이 있는 상수는 static final 대신 ENUM으로 활용
3. if문 ENUM 리팩토링
4. getter 리팩토링
1. 컨트롤러 역할이 너무 커져서 서비스로 분리
앞서 클래스 분리에서 말한 것처럼 컨트롤러의 역할이 너무 커져서 서비스로 분리하게 됐다.
이때, 확실히 리팩토링 시 테스트가 도움이 됐다.
게임 실행 관련 로직을 리팩토링하는 것이었기 때문에,
리팩토링한 코드가 맞는지 확인하려면 계속 실행을 해봤어야했다.
하지만, 이미 기능 테스트를 하는 ApplicationTest의
기능 테스트 메소드가 있었기 때문에 이를 실행하며 편하게
리팩토링한 코드가 잘 작동하는지 확인할 수 있었다.
2. 연관성이 있는 상수는 static final 대신 ENUM으로 활용
리팩토링 전에 내 코드를 살펴보니, 연관성이 있는 상수로는 다음과 같이 3가지가 존재했다.
1. 게임 커맨드 상수 (재시작 / 종료 커맨드)
2. 게임 결과 상수 (성공 / 실패)
3. 출력용 괄호 ("[" / "]")
처음에는 공통 피드백인 ENUM 활용을 하고자 세 가지를 모두 ENUM으로 처리하려고 했으나,
ENUM으로 만들면 값을 꺼내올 때 결국 getter를 사용해야했다.
따라서, 유지 보수 측면에서 ENUM이 유리한 것만 처리했다.
세 가지 모두 한 곳에서만 사용되었기 떄문에 리팩토링 시 범위는 모두 적었지만,
게임 결과와 출력용 괄호는 추후 확장이 되지 않는다고 판단했다.
게임 커맨드는 추후에 확장될 수도 있다고 판단하여 ENUM으로 만들게 되었다.
3. if문 ENUM으로 리팩토링
private static final String CAN_MOVE_SHAPE = " O ";
private static final String CANNOT_MOVE_SHAPE = " X ";
private static final String BLANK_SHAPE = " ";
...
private void moveUpStep(boolean canMove) {
if (canMove) {
upShape.add(CAN_MOVE_SHAPE);
}
if (!canMove) {
upShape.add(CANNOT_MOVE_SHAPE);
}
downShape.add(BLANK_SHAPE);
}
private void moveDownStep(boolean canMove) {
if (canMove) {
downShape.add(CAN_MOVE_SHAPE);
}
if (!canMove) {
downShape.add(CANNOT_MOVE_SHAPE);
}
upShape.add(BLANK_SHAPE);
}
👉 리팩토링 전 코드는 위와 같다.
사용자가 선택한 칸의 이동 가능 여부를 파라미터로 받아서 그에 따라 모양을 다르게 저장한 것이다.
이때, if문이 너무 많이 쓰이고, 부정문(!)도 들어가서 처음에 마음에 안들었다.
따라서 이전 3주차처럼 ENUM으로 리팩토링하게 됐다.
public enum BridgeShape {
CAN_MOVE(" O ", true),
CANNOT_MOVE(" X ", false);
...
public static String getNextShape(boolean canMove) {
return Arrays.stream(BridgeShape.values())
.filter(bridgeShape -> bridgeShape.canMove == canMove)
.map(bridgeShape -> bridgeShape.shape)
.findAny()
.orElseThrow(() -> new IllegalArgumentException(
ErrorConstant.ERROR_PREFIX + "해당 모양을 찾을 수 없습니다."));
}
private void moveUpStep(boolean canMove) {
upShape.add(BridgeShape.getNextShape(canMove));
downShape.add(BLANK_SHAPE);
}
private void moveDownStep(boolean canMove) {
downShape.add(BridgeShape.getNextShape(canMove));
upShape.add(BLANK_SHAPE);
}
👉 이렇게 ENUM 안에 다음 칸을 반환하는 로직을 만들어서 if문을 깔끔하게 처리했다.
지난 주차와 달랐던 점은, ENUM으로 리팩토링 전에 테스트 코드를 작성해뒀다는 점이다.
테스트 코드가 있음으로 인해서 테스트 코드가 리팩토링 후에 기능이 잘 작동하는지
검증해주는 보호 도구가 되었고, 리팩토링 후 테스트 코드를 통과하는 것을 보고
‘리팩토링이 잘 되었구나!’ 하면서 안심하게 되었다.
4. getter 리팩토링
내가 getter를 사용했던 코드는 다음과 같다.
private final List<String> upShape = new ArrayList<>();
private final List<String> downShape = new ArrayList<>();
...
public List<String> getUpShape() {
return upShape;
}
public List<String> getDownShape() {
return downShape;
}
👉 단순 출력용으로 윗 칸 모양과, 아래 칸 모양을 반환했다.
처음에 이를 어떻게 getter를 사용하지 않고 반환하여 출력할 수 있을까? 생각을 해보았다.
List 같은 Collection이 아닌, 일반 String, int 같은 필드였다면
‘객체에 메세지를 보내라’ 라는 피드백을 인상깊게 들었어서 객체 내부에서 처리해서 반환할 텐데,
Collection이고, 단순 출력용이기 때문에 어떻게 할지 몰랐다.
여기서 공통 피드백에서 올려주신 블로그를 참고했다.
https://tecoble.techcourse.co.kr/post/2020-04-28-ask-instead-of-getter/
블로그에서는, 첫 번째로는 당연히 ‘객체에 메세지를 보내라’ 라는 것을 강조하고 있었고,
그 후에 ‘getter를 무조건 사용하지 말라는 것은 아니다!’ 라고 하며,
출력을 위한 값 등 순수 값 프로퍼티를 가져오기 위해서라면 어느정도 getter는 허용된다고 하셨다.
이때, Collection을 출력을 위해 getter로 반환할 때 주의점이 있었다.
단순히 getter로 Collection을 반환하면, 외부 사용자가 Collection의 값을 변경할 수 있었다.
따라서, Collections.unmodifiableList()와 같은 Unmodifiable Collecion을 사용해야했다.
public List<String> getUpShape() {
return Collections.unmodifiableList(upShape);
}
public List<String> getDownShape() {
return Collections.unmodifiableList(downShape);
}
이러한 점을 3주차에서 공부했었는데, 기억이 안나서 처음 코드 작성 시 적용을 못한 것이 아쉽다.
다음에는 꼭 적용해봐야겠다.
최종 코드
https://github.com/KSH-beginner/wooteco5th-java-bridge/tree/KSH-beginner
후기?
드디어 대망의 4주차가 끝나고 프리코스가 마무리됐다.
1주차 시작 전에는 시작하면 꼭 의지를 불태워서 열심히 해야겠다고 다짐했었다.
그 다짐이 1주차까지는 이어졌던 것 같지만,
그 이후에는 1주차처럼 의지 넘치게 임한 것 같지는 못해서 아쉽다.
2주차부터 수요일에 미션이 나오면 그 후부터 거의 하루종일 구현에 힘쓰다보니,
일요일쯤에는 내 자신이 너무 지쳐있었던 것 같다.
그래서 3, 4주차에서는 거의 월, 화는 리팩토링을 진행하지 않았다.
프리코스를 진행하면서 배우는 부분이 정말 많았지만,
계속 혼자 하다보니 불안감이 생기고 오래 코드를 보다보니 지쳤던 부분이 많아 아쉬웠다.
1차 합격이 될지는 모르겠지만, 재충전해서 다시 열심히 살아보도록 해야겠다!
'우아한테크코스 5기 프리코스' 카테고리의 다른 글
우아한테크코스 5기 최종 합격 (feat. 우테코 선발 과정 후기) (9) | 2023.01.02 |
---|---|
우아한테크코스 5기 1차 합격 & 최종 코딩테스트 후기 (7) | 2022.12.18 |
우아한테크코스 프리코스 3주차 후기 & 공부한 부분 (0) | 2022.11.16 |
우아한테크코스 프리코스 2주차 후기 & 공부한 부분 (4) | 2022.11.09 |
우아한테크코스 프리코스 1주차 후기 & 공통 피드백 및 공부한 부분 (4) | 2022.11.02 |