✔ 전체 회고
- 객체의 불변성을 유지한 채로 다루는 방법을 이해하였다.
- 정적 팩토리 메소드의 장단점에 대해 파악하였고, 적절히 활용할 수 있게 되었다.
- 상황에 따라 interface와 abstract class를 적극 활용하여 객체들간의 유연한 관계를 만들고 중복 코드를 제거할 수 있었다.
미션을 모두 마치고...
볼링 미션을 하면서도 많이 느꼈지만 지난 과정들을 되돌아보니 그동안 성장했다는 걸 코드로 증명해주고 있어서 보람찬 시간이었다.
테스트 코드를 구성하는 것도 어색했고, 도메인을 역할 단위로 잘 쪼개질 못하거나 아니면 객체 내에 선언해둔 정보들을 굳이 바깥으로 빼내면서 단순 wrap의 용도로만 사용했던 지난날의 코드들을 보면서 이런걸 리뷰어님들께 보여드렸단 사실이 좀 부끄러워질 지경이었다.
그리고 그동안 개발해왔던 코드들을 보면서도 많이 느꼈다. 나쁜 냄새가 가득했던 코드들을, 레거시를 리팩토링했던 질문 삭제하기 과정처럼 내 레거시들을 되엎어보는 시간을 가져야겠다는 다짐을 하였다. 👍
1단계 요구사항
- 질문 데이터를 완전히 삭제하는 것이 아니라 데이터의 상태를 삭제 상태(deleted - boolean type)로 변경한다.
- 로그인 사용자와 질문한 사람이 같은 경우 삭제 가능하다.
- 답변이 없는 경우 삭제가 가능하다.
- 질문자와 답변 글의 모든 답변자 같은 경우 삭제가 가능하다.
- 질문을 삭제할 때 답변 또한 삭제해야 하며, 답변의 삭제 또한 삭제 상태(deleted)를 변경한다.
- 질문자와 답변자가 다른 경우 답변을 삭제할 수없다.
- 질문과 답변 삭제 이력에 대한 정보를 DeleteHistory를 활용해 남긴다.
- qna.service.QnaService의 deleteQuestion()는 앞의 질문 삭제 기능을 구현한 코드이다. 이 메소드는 단위 테스트하기 어려운 코드와 단위 테스트 가능한 코드가 섞여 있다.
- 단위 테스트하기 어려운 코드와 단위 테스트 가능한 코드를 분리해 단위 테스트 가능한 코드 에 대해 단위 테스트를 구현한다.
1단계 체크리스트
도메인 분리
- 작성정보는 글의 내용과 글쓴이 정보, 삭제여부를 가진다.
- 작성정보는 글쓴이가 동일한지 확인할 수 있다.
- 삭제정보는 글의 id와 타입, 그리고 삭제자의 정보를 가진다.
- 글의 타입은 해당 글이 질문글인지 답변글인지 구분할 수 있다.
비즈니스 로직을 도메인으로 이동
- 질문글과 답변 목록중에 작성자와 다른 사람이 있는지 확인한다.
- 질문글을 삭제하면 그와 관련된 답변 목록을 삭제할 수 있다.
- 질문글과 답변 삭제시 삭제 기록을 남길 수 있다.
리팩토링
- 사용하지 않는 메소드를 제거한다.
1단계 피드백
피드백 링크
- 상속 depth는 한 번정도, AbstractEntity에서는 보통 도메인의 공통관심사 정도 구현
- 생성자 내부 validation 로직보다는 static of에서 접근, static validation로 추출하여 각각 활용
- parameter에 list를 넘기기보다는 새 리스트 반환이 사이드 이펙트 방지
- ❗Instanceof는 안티패턴❗ 하위 타입에서 메소드 재정의하는 방식으로 사용 (getContentType처럼)
- 메소드명은 이름을 내포하게끔 (isOwner? validOwner?)
2단계 요구사항
- 최종 목표는 볼링 점수를 계산하는 프로그램을 구현한다. 1단계 목표는 점수 계산을 제외한 볼링 게임 점수판을 구현하는 것이다.
- 각 프레임이 스트라이크이면 "X", 스페어이면 "9 | /", 미스이면 "8 | 1", 과 같이 출력하도록 구현한다.
- 스트라이크(strike) : 프레임의 첫번째 투구에서 모든 핀(10개)을 쓰러트린 상태
- 스페어(spare) : 프레임의 두번재 투구에서 모든 핀(10개)을 쓰러트린 상태
- 미스(miss) : 프레임의 두번재 투구에서도 모든 핀이 쓰러지지 않은 상태
- 거터(gutter) : 핀을 하나도 쓰러트리지 못한 상태. 거터는 "-"로 표시
- 10 프레임은 스트라이크이거나 스페어이면 한 번을 더 투구할 수 있다.
플레이어 이름은(3 english letters)?: PJS
| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 |
| PJS | | | | | | | | | | |
1프레임 투구 : 10
| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 |
| PJS | X | | | | | | | | | |
2프레임 투구 : 8
| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 |
| PJS | X | 8 | | | | | | | | |
2프레임 투구 : 2
| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 |
| PJS | X | 8|/ | | | | | | | | |
3프레임 투구 : 7
| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 |
| PJS | X | 8|/ | 7 | | | | | | | |
3프레임 투구 : : 0
| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 |
| PJS | X | 8|/ | 7|- | | | | | | | |
...
2단계 체크리스트
- 핀은 시도한 투구횟수와 남아있는 핀의 개수로 구성된다.
- 투구개수는 쓰러뜨릴 핀의 수로 구성된다.
- 투구시 투구개수만큼 핀의 개수를 무너뜨릴 수 있다.
- 투구결과는 투구결과타입을 지닌다.
- 투구결과 타입은 타입에 따른 표현 방식을 지닌다.
- 각 프레임은 인덱스와 핀, 투구결과로 구성된다.
- 투구결과를 toString 문자열로 표현할 수 있다.
- 1~9번째 프레임은 일반프레임, 10번째는 마지막 프레임으로 구현한다.
- 일반프레임과 마지막 프레임간의 구분되는 roll 로직을 구성한다.
- 프레임들을 관리하는 프레임목록에서 한 경기씩 실행한다.
- 세 글자의 이름을 지닌 플레이어를 생성한다.
- 플레이어를 입력받는다.
- 해당번째 프레임의 투구수를 입력받는다.
- 실행 결과를 출력한다.
2단계 피드백
- 모든 객체가 불변성을 띌 필요는 없음 상황에 적절하게 사용할 것
Pin
의 불변화는 실제 볼링장처럼 서있는지, 넘어져있는지를 표현할 수 있음 (10개의 핀을 관리하는Pins
와 함께)public
메소드를private
보다 상단에 위치시키기Interface
를 통한 상수 정의는 적절X
=> 상수를 전달시켜야할 필요가 있다면abstract class
로 전환해보는 걸 고려햐기
3단계 요구사항
사용자 1명의 볼링 게임 점수를 관리할 수 있는 프로그램을 구현한다.
객체지향 5원칙을 지키면서 프로그래밍한다.
플레이어 이름은(3 english letters)?: PJS
| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 |
| PJS | | | | | | | | | | |
| | | | | | | | | | | |
1프레임 투구 : 10
| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 |
| PJS | X | | | | | | | | | |
| | | | | | | | | | | |
2프레임 투구 : 8
| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 |
| PJS | X | 8 | | | | | | | | |
| | | | | | | | | | | |
2프레임 투구 : 2
| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 |
| PJS | X | 8|/ | | | | | | | | |
| | 20 | | | | | | | | | |
3프레임 투구 : 8
| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 |
| PJS | X | 8|/ | 8 | | | | | | | |
| | 20 | 38 | | | | | | | | |
3프레임 투구 : 1
| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 |
| PJS | X | 8|/ | 8|1 | | | | | | | |
| | 20 | 38 | 47 | | | | | | | |
...
3단계 체크리스트
- 점수 객체는 점수값과 추가로 합산된 횟수를 관리한다.
- 결과타입은 타입에 따라 추가로 점수를 합산할 수 있다.
- 각 프레임들은 추가점수 합산을 할 수 있고, 점수 결과를 보여줄 수 있다.
- 점수결과 목록을 출력할 수 있다.
3단계 피드백
- 점수 구분 로직은 Score 내부 수행
- strike 점수인지, gutter 점수인지 확인하고
- strike와 spare로 구분하던 더한 횟수를, 애초에 생성시에 남은 횟수 초기화를 해주어서 또다시 if를 사용하지 않도록 변경
- 추상메소드만 사용시 추상클래스보다는 Interface
- 같은 인터페이스를 상속하더라도 중복로직이 있으면 추상클래스로 분리하기
- text fixture는 외부에서 재사용되는게 아니면 test case 내부에서 수행
4단계 요구사항
1명 이상의 사용자가 사용할 수 있는 볼링게임 점수판을 구현한다.
- 객체지향 생활 체조 원칙을 지키면서 프로그래밍한다.
- 객체지향 5원칙을 지키면서 프로그래밍한다.
How many people? 2
플레이어 1의 이름은?(3 english letters): PJS
플레이어 2의 이름은?(3 english letters): KYJ
| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 |
| PJS | | | | | | | | | | |
| | | | | | | | | | | |
| KYJ | | | | | | | | | | |
| | | | | | | | | | | |
PJS's turn : 10
| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 |
| PJS | X | | | | | | | | | |
| | | | | | | | | | | |
| KYJ | | | | | | | | | | |
| | | | | | | | | | | |
KYJ's turn : 8
| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 |
| PJS | X | | | | | | | | | |
| | | | | | | | | | | |
| KYJ | 8 | | | | | | | | | |
| | | | | | | | | | | |
KYJ's turn : 2
| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 |
| PJS | X | | | | | | | | | |
| | | | | | | | | | | |
| KYJ | 8|/ | | | | | | | | | |
| | | | | | | | | | | |
PJS's turn : 8
| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 |
| PJS | X | 8 | | | | | | | | |
| | | | | | | | | | | |
| KYJ | 8|/ | | | | | | | | | |
| | | | | | | | | | | |
PJS's turn : 2
| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 |
| PJS | X | 8|/ | | | | | | | | |
| | 20 | | | | | | | | | |
| KYJ | 8|/ | | | | | | | | | |
| | | | | | | | | | | |
KYJ's turn : 10
| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 |
| PJS | X | 8|/ | | | | | | | | |
| | 20 | | | | | | | | | |
| KYJ | 8|/ | X | | | | | | | | |
| | 20 | | | | | | | | | |
PJS's turn :
...
4단계 체크리스트
- 플레이어는 이름과 프레임 목록을 지닌다
- 플레이어는 자신의 차례가 오면 볼을 한 번 굴릴 수 있다.
- 플레이어는 경기 상황과 경기가 완료되었는지를 파악할 수 있다.
- 플레이어 목록은 차례를 돌아가며 게임을 진행한다.
- 플레이어 목록은 플레이어 전원이 종료될때까지 게임을 진행한다.
- 모든 게임이 종료된 후 우승자를 발표한다.
4단계 피드백
-
정적 팩토리 메서드의 장/단점?
-
장점
- 인스턴스를 구성하기까지 필요한 과정을 캡슐화할 수 있다는 점 (그리고 생성자에는 생성 역할만 수행할 수 있게 됨)
- 다형성을 제공 (
OneHit
의of
에서 생성자로 수행시 타입이OneHit
으로 고정되어야하겠지만, 수행 로직에따라 상태를 반환할 수 있도록State
타입을 제공할 수 있음)
-
단점
- 정적팩토리 메서드로만 제공하고 생성자를
private
으로 두는 경우엔,public
과protected
생성자가 없어서 상속이 어렵기 때문에 확장성을 잃어버릴 수 있음
- 정적팩토리 메서드로만 제공하고 생성자를
지금의 설계로는 인터페이스나 추상클래스를 제외하고는 상속보단 조합으로 구성하고 있어서 정적 팩토리 메서드를 사용했는데 이후의 확장성을 고려하거나 오히려 장점이 독이 될 수는 없을지 고려해야할 것 같다.
- 이번 리팩토링으로 변경한 부분? 🤔
- 앞선 장점에서
OneHit
의of
에서State
를 제공했던 점 때문에 무조건OneHit
이 나와야하는 경우 (특히Strike
에서는 of를 재호출하기 때문에 순환참조의 문제가 생길 수 있었음)에는ofOne
을 사용했는데, 생성자로 접근할 수 있도록 변경
-> 다시 고민해봤는데 생성자에score
가int
값으로 들어올 때,Score
객체로 들어올 때 전부를 유연하게 처리해야하므로 생성자 내부에 로직을 넣어서 바꿔주기보다는 of로 빼내어서 구분로직을 수행하도록 변경
- 앞선 장점에서
-
-
정적 팩토리 메서드로 구현할 경우 생성자를 줄이기
-
Player
에서Frames
를 그저 wrap하는 용도로만 사용하고 있었으나,Frames
자체에 이름을 추가해서Player
로 사용하는 방법도 구현해볼것 -
메서드에는 한 가지 일만 하도록 최대한 분리할 것