/java-bowling

볼링 게임 점수판 구현을 위한 저장소

Primary LanguageJava

볼링 게임 점수판

✔ 전체 회고

  • 객체의 불변성을 유지한 채로 다루는 방법을 이해하였다.
  • 정적 팩토리 메소드의 장단점에 대해 파악하였고, 적절히 활용할 수 있게 되었다.
  • 상황에 따라 interface와 abstract class를 적극 활용하여 객체들간의 유연한 관계를 만들고 중복 코드를 제거할 수 있었다.
미션을 모두 마치고...

볼링 미션을 하면서도 많이 느꼈지만 지난 과정들을 되돌아보니 그동안 성장했다는 걸 코드로 증명해주고 있어서 보람찬 시간이었다.

테스트 코드를 구성하는 것도 어색했고, 도메인을 역할 단위로 잘 쪼개질 못하거나 아니면 객체 내에 선언해둔 정보들을 굳이 바깥으로 빼내면서 단순 wrap의 용도로만 사용했던 지난날의 코드들을 보면서 이런걸 리뷰어님들께 보여드렸단 사실이 좀 부끄러워질 지경이었다.

그리고 그동안 개발해왔던 코드들을 보면서도 많이 느꼈다. 나쁜 냄새가 가득했던 코드들을, 레거시를 리팩토링했던 질문 삭제하기 과정처럼 내 레거시들을 되엎어보는 시간을 가져야겠다는 다짐을 하였다. 👍


🚀 1단계 - 질문 삭제하기 기능 리팩토링

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단계 - 볼링 점수판(그리기)

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단계 - 볼링 점수판(점수 계산)

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단계 피드백
  1. 점수 구분 로직은 Score 내부 수행
  • strike 점수인지, gutter 점수인지 확인하고
  • strike와 spare로 구분하던 더한 횟수를, 애초에 생성시에 남은 횟수 초기화를 해주어서 또다시 if를 사용하지 않도록 변경
  1. 추상메소드만 사용시 추상클래스보다는 Interface
  • 같은 인터페이스를 상속하더라도 중복로직이 있으면 추상클래스로 분리하기
  1. text fixture는 외부에서 재사용되는게 아니면 test case 내부에서 수행

🚀 4단계 - 볼링 점수판(n명)

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단계 피드백
  1. 정적 팩토리 메서드의 장/단점?

    내가 생각하는 의견

    • 장점

      • 인스턴스를 구성하기까지 필요한 과정을 캡슐화할 수 있다는 점 (그리고 생성자에는 생성 역할만 수행할 수 있게 됨)
      • 다형성을 제공 (OneHitof에서 생성자로 수행시 타입이 OneHit으로 고정되어야하겠지만, 수행 로직에따라 상태를 반환할 수 있도록 State 타입을 제공할 수 있음)
    • 단점

      • 정적팩토리 메서드로만 제공하고 생성자를 private으로 두는 경우엔, publicprotected 생성자가 없어서 상속이 어렵기 때문에 확장성을 잃어버릴 수 있음

    지금의 설계로는 인터페이스나 추상클래스를 제외하고는 상속보단 조합으로 구성하고 있어서 정적 팩토리 메서드를 사용했는데 이후의 확장성을 고려하거나 오히려 장점이 독이 될 수는 없을지 고려해야할 것 같다.

    • 이번 리팩토링으로 변경한 부분? 🤔
      • 앞선 장점에서 OneHitof에서 State를 제공했던 점 때문에 무조건 OneHit이 나와야하는 경우 (특히 Strike에서는 of를 재호출하기 때문에 순환참조의 문제가 생길 수 있었음)에는 ofOne을 사용했는데, 생성자로 접근할 수 있도록 변경
        -> 다시 고민해봤는데 생성자에 scoreint값으로 들어올 때, Score 객체로 들어올 때 전부를 유연하게 처리해야하므로 생성자 내부에 로직을 넣어서 바꿔주기보다는 of로 빼내어서 구분로직을 수행하도록 변경
  2. 정적 팩토리 메서드로 구현할 경우 생성자를 줄이기

  3. Player에서 Frames를 그저 wrap하는 용도로만 사용하고 있었으나, Frames 자체에 이름을 추가해서 Player 로 사용하는 방법도 구현해볼것

  4. 메서드에는 한 가지 일만 하도록 최대한 분리할 것