이어서..
계속해서 리팩토링을 하고 있다. 솔직히 해도 해도 개선할 것이 끊이지가 않는다. 만들면 만들수록 욕심이 생긴다. 처음에는 'mvc 패턴 사용해보고 메서드 분리만 잘 해보자!' 였는데, dto, service계층, 객체지향 체조 원칙을 넘어 이제는 SOLID, 캡슐화, enum과 하드코딩까지 고민하고 있다.
SOLID와 추상화, 인터페이스 사용을 이번 주 과제에 적용하기는 어려울 것 같다. 소감문과 전체 코드 해석도 작성해야 하고, 아직 그것들을 쓸만큼 이해하지 못했다. 가능하다면 2주차 과제 때 써보고 싶다..!
오늘 한 것도 정말 많다. dto 분리, 상수와 enum 사용, 자잘한 리팩토링들... 그리고 객체지향 원칙들을 지켰는지 체크하고, 최종 테스트까지 정말 많은 걸 했다.
그렇다고 힘이 빠지지는 않았다. 오히려 '좀 더 나은 개선을 할 방법이 분명히 있을건데-' 하는 생각에 의욕이 타올랐다. 만일 3일 정도 더 있었다면 새로운 개선들을 또 할 수 있겠다는 생각이 들었다.
하지만 소망과 별개로 현실은 현실. 이번 주 과제를 슬슬 마무리 해야 한다. 내가 쓸 수 있는 모든 시간을 쏟아부었기에 후회는 하지 않는다.
Controller의 DTO 의존도 다 옮겨버리자!
어제 Model 계층에서 DTO를 의존하거나 관련된 코드를 다 Service 계층으로 옮겼다. 나는 최종적으로 (이 애플리케이션에서는) 한 객체가 DTO를 담당하게 만들고 싶었기에, Controller 에 있는 DTO 관련 코드도 Service 계층으로 옮기기로 했다.
| BaseballGameController
에 존재하는 DTO 클래스 의존
| BaseballGameService
에 DTO 와의 소통을 넘긴 후. 이제 Controller 는 Service, View 와만 소통한다.
run() 메서드 리팩토링
애플리케이션의 시작점인 Apllication-main()
메서드에서는 BaseballGameController
의 run()
메서드를 실행시킴으로서 야구 게임을 시작하게 한다. 그런데 이 중요한 run()
메서드의 가독성이 떨어져보여서, 메서드 분리로 리팩토링했다.
이제 run()
메서드는 단 3줄의 코드만을 가진다.
내부 생성자의 접근 제한자 리팩토링
public
이었던 일부 생성자를 private
로 바꿨다. 이 생성자들은 내부 클래스 의 생성자들인데, 이 생성자들은 해당 상위 클래스의 메서드에만 사용되기 때문!
하드 코딩 개선
전체적으로 코드 내부에 하드 코딩된 값들이 존재해서, 이들을 열거형 enum 또는 상수로 바꾸고자 했다.
열거형과 상수중 어떤 것을 사용할까 고민했는데, 공통적으로 사용될 부분에는 열거형을, 한 곳에만 쓰이는 곳에는 상수를 적용했다.
특히 게임 결과를 결정하는 GameResult
클래스의 result()
메서드에는 많은 하드코딩 값을 사용했는데, 이 로직 전체를 enum
으로 분리했다.
[우아한 기술블로그]Java Enum 활용기
이 포스팅에서 영감을 받았는데, enum
이 공통 사용될 상수를 설정하는 기능 말고도 다른 기능을 할 수 있다는 것을 알게 됐다.
enum 사용 - GameResultType
| enum
분리 이전의 result()
메서드
result()
메서드는 이렇게 하드 코딩된 값이 많았다. 이를 상수로 분리하지 않고 enum
클래스에 따로 분리한 이유는, enum
을 잘 활용해보고 싶었기 때문이다. 그리고 0
, 1
int
값을 상수로 정하기도 좀 애매하다고 느꼈다.
enum
클래스인 GameResultType
을 만들었고, 이 과정에서 정말 많은 것들을 배웠다.
열거형 타입에 람다식을 쓸 수 있다는 것도 처음 알았고, enum 클래스에 로직을 작성해도 된다는 것도 처음 알았다.
코드를 간단히 설명하자면, 클래스 필드에 게임 결과들인 NOTHING
, ANSWER
, ONLY_STRIKE
등 (이 기준은 출력 요구사항에 따른 기준들이다.) 을 정의해 놓고,** 판별식, 게임 결과 문자열, 정답 여부를 설정**해 놓았다.
그리고 findType()
메서드로 입력받은 strike
, ball
의 갯수와 일치하는 열거형이 있다면 그 열거형을 반환한다. 요청한 쪽에서는 게임 결과와 정답 여부만을 필요로 하기 때문에 formatMessage()
와 isCorrectAnswer()
메서드로 그것들을 반환해준다.
| enum
분리 이후의 GameResult(2)
클래스. 혹시나 사용하지 않을 때를 대비해서 복사본을 만들어서 고쳤다.
그런데 분리한 enum
클래스에서 이 formatMessage()
때문에 고민이 생겼다. 이러면 게임 결과를 담은 Type
을 반환하는 역할 말고 '문자열 합치기' 라는 역할도 가지는게 아닌가? 라는 고민이었다. 그래서 이 메서드를 GameResult
클래스로 이동시켰다. GameResult
클래스는 '게임 결과 결정' 이라는 역할을 가지므로 formatMessage()
메서드가 이 역할에 어울린다고 생각했기 때문이다.
| 이렇게 옮겨줬다!
참고로 나는 enum
을 쓰는 과정에서 아래 메서드의 작성이 특히 맘에 들었는데,
열거형 값들만을 사용하면서 게임 결과에 따라 값이 다르게 출력되게 했다. 되게 깔끔하고 멋져 보였다.
enum 사용
아래 상수 사용 - InputNumberValidator
에서 보면 알겠지만, "1"
과 "2"
라는 하드 코딩된 값이 존재한다. 이 두 값은 게임 재시작을 결정하는 값들인데, 이 값은 View 쪽과 Service 쪽에서도 쓰이고 있었다. 그래서 공통되는 값이라 판단했고, enum
클래스로 분리했다.
| 분리된 GameRestartOption
클래스
상수 사용
그 밖에 나머지 하드 코딩된 값들을 상수화 해줬다.
)
)
Controller 리팩토링
BaseballGameController
에서 아래 메서드들을 제거해줬다. OutputView
클래스에 직접 접근하는 것과 다를 바가 없다고 생각되어, 메서드의 필요성이 없다고 생각했기 때문이다. 이 메서드들을 제거하고 바로 정적인 OutputView
에서 바로 메서드를 호출했다.
| 제거된 메서드들
Service 에서 DTO 책임 분리?
문득 이 애플리케이션의 유일한 Service 계층인 BaseballGameService
가 너무 많은 책임을 분담한다고 느꼈다. 그래서 DTO에 값을 저장하고 가져오는, DTO만 관리하는 DtoManager
클래스를 만들어 BaseballGameService
에서 DTO를 관리하는 역할을 옮겨줬다.
그런데 작성하고 보니 이 DtoManager
의 가독성이 너무 떨어졌다. 3개의 DTO 클래스를 한 곳에서 관리하니까 코드가 많을 수 밖에 없다.
| DtoManaer
클래스
이 부분을 어떻게 할지는 고민 해봐야겠다.
내일이 과제 제출 마감이라 할게 참 많다. 최종 회고글 작성, 소감문, 가능하면 코드 해석...
리팩토링을 그만해야 하는데 멈출 수가 없다...............
오늘은 여기까지!
참고로 테스트는 통과했다.
++ 참고로 다음 날 DtoManaer
클래스를 결국 각 DTO 클래스 별로 분리했다. DtoManager
클래스가 3개가 됐지만 그래도 가독성이 좋아졌으니 뭐...
마무리
내일이면 1주차 과제가 끝이다. 매일 회고글을 작성 해보니까 여간 힘든게 아니다. 포스팅 하나 쓰는데 최소 1.5시간은 걸리고, 개발 도중에도 포스팅 할 만한 것들을 생각하고 있어야 한다. 어쩌면 2주차 부터는 매일 쓰지 못할 수도 있을 것 같다. 시간만 된다면 쓰는데, 구현하느라 시간이 부족할 수도 있으니까...
새로 알게된 점
1. enum의 역할
enum이 단순히 공통으로 사용할 상수를 그룹화 해놓은 것 뿐만 아니라 다양한 역할을 할 수 있다는 것을 알게 됐다. 하지만 아직 enum에 대해 배울게 많이 남았다. 아마 아직 10퍼센트도 모르지 않을까?
2. 가독성 vs 복잡성
가독성을 택할 것이냐, 복잡성을 완화할 것이냐? 클래스 분리(또는 메서드 분리, 변수의 명시화)를 하면 가독성이 좋아지지만, 복잡성이 커진다. 반대의 경우 복잡성이 낮아지지만 가독성이 나빠진다.
좋았던 점
1. enum을 적극적으로 사용했던 것
2. 코드 내에 하드 코딩을 없앤 것
3. 클래스, 메서드 분리, 가독성에 대해 끊임없이 고민했던 것
4. 객체지향 생활체조 9가지 원칙을 지킬려고 노력했던 것
객체지향 생활체조 9가지 원칙
- 한 메서드에서 한 단계 들여쓰기만 사용하자
- 꼭 사용해야만 하는 한 곳(
GameResultType
) 말고는 모두 적용했다.
- 꼭 사용해야만 하는 한 곳(
- else 예약어를 쓰지 말자
- 사용하지 않았다.
- 모든 원시값과 문자열을 포장하자
- 아직 이것에 대해서는 잘 모르겠다. 공부를 더 해봐야 한다.
- 일급 컬렉션을 사용하자
- 이것도 공부중이지만, 일단 코드에서 컬렉션 쓰는 곳이 한 곳 밖에 없다. 그것도 우테코 제공 라이브러리를 사용해야 하기 때문이었다. 아마 어기지 않지 않았을까?
- 한 줄에서 한개의 점만 사용하자
- Service 계층에서 서로 다른 객체를 호출하느라 .을 2개 이상 사용한 부분도 있었지만, 한 객체 안에서 연쇄적으로 .을 사용하지는 않았다.
- 축약하지 말자
- 클래스, 메서드, 변수의 의미를 가능한 나타내려 노력했다.
- 모든 엔티티를 작게 유지하자. 클래스는 50줄을, 패키지는 파일 10개를 넘기지 않아야한다(기준은 바뀔수 있다).
- 가독성 때문에 그런 것 같은데, 핵심 로직을 제외하곤 다 지켰다. 너무 클래스 분리를 하면 복잡성이 증가할까봐.
- 클래스는 변수 두 개를 넘지 않게 하자 - 더 높은 응집력을 갖고 더 나은 캡슐화가 가능하다. 여기서 "두 개"는, 클래스를 많이 분리하도록 강요하기 위한 임의의 숫자이다.
- 이건 못 지켰다. 이건 너무 어려운 것 같다. 클래스가 너무 많아져도 좋지 않은 것 아닌가? 좀 더 공부해봐야겠다.
- Getter / Setter / Properties를 사용하지 말자
- dto의 getter, setter를 제외하고 getter는 단순 조회 및 데이터 전송에만 쓰였고, setter는 아예 쓰지 않았다.
아쉬웠던 점
- 가독성을 택할 것이냐, 복잡성을 완화할 것이냐? 결국 이 둘중 어느 것에 비중을 두느냐의 차이인 것 같다. 나는 아직 확실히 기준을 정하지 못한 것 같다. 어떨 때에 가독성을 중요시 할 타이밍이고 어떨 때에 복잡성을 완화해야 할 타이밍인지 아직 분별이 서지 않는다. 그래서 코드 전체에 일관성이 없었던 것 같기도 하다.
도움이 된 자료들
| 객체지향 생활체조 9가지 원칙
| 🔥 객체지향 생활체조
| [객체지향 생활체조 원칙] 규칙 4. 한 줄에 점을 하나만 찍는다
| Java Enum 활용기
| 하드코딩 - 해쉬넷
| 열거형(enum)과 어노테이션
| ☕ 자바 Enum 열거형 타입 문법 & 응용 💯 정리
| Java: enum의 뿌리를 찾아서...
| [Java] 람다식과 함수형 인터페이스 - 2
| [StackOverFlow]Cannot Instantiate the type Error in enum
| 일급컬렉션의 정의와 필요성
| #20200117 데브로그 : 풀 리퀘스트 취소 & prettier X VSC settings 설정
'우아한테크코스' 카테고리의 다른 글
[우테코 6기 프리코스] 2주차 - 1일차 회고 (1) | 2023.12.20 |
---|---|
[우테코 6기 프리코스] 1주차 - 7일차 + 1주 전체 회고 (1) | 2023.12.20 |
[우테코 6기 프리코스] 1주차 - 5일차 회고 (1) | 2023.12.20 |
[우테코 6기 프리코스] 1주차 - 4일차 회고 (1) | 2023.12.20 |
[우테코 6기 프리코스] 1주차 - 3일차 회고 (0) | 2023.12.20 |