개발자는 기록이 답이다
우테코 6기 프리코스 2주차 회고 본문
이번에는 1주차와 다르게 다른 사람 코드를 참고하지 않고 온전히 저의 생각대로 설계해서 훨씬 뿌듯했던 것 같습니다.
1주차 미션 제출이 끝나고 커뮤니티에서 코드리뷰 스터디를 구해서 1주차에 대한 피드백을 서로 해주기로 했습니다. 제가 기존에는 알지 못했던 키워드나, 애매하게 생각했던 내용들에 대해 토론해볼 수 있어서 더욱 의미있던 시간이었습니다 😊
자동차 경주 게임
🚀 기능 요구 사항
초간단 자동차 경주 게임을 구현한다.
- 주어진 횟수 동안 n대의 자동차는 전진 또는 멈출 수 있다.
- 각 자동차에 이름을 부여할 수 있다. 전진하는 자동차를 출력할 때 자동차 이름을 같이 출력한다.
- 자동차 이름은 쉼표(,)를 기준으로 구분하며 이름은 5자 이하만 가능하다.
- 사용자는 몇 번의 이동을 할 것인지를 입력할 수 있어야 한다.
- 전진하는 조건은 0에서 9 사이에서 무작위 값을 구한 후 무작위 값이 4 이상일 경우이다.
- 자동차 경주 게임을 완료한 후 누가 우승했는지를 알려준다. 우승자는 한 명 이상일 수 있다.
- 우승자가 여러 명일 경우 쉼표(,)를 이용하여 구분한다.
- 사용자가 잘못된 값을 입력할 경우 IllegalArgumentException을 발생시킨 후 애플리케이션은 종료되어야 한다.
경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)
pobi,woni,jun
시도할 회수는 몇회인가요?
5
실행 결과
pobi : -
woni :
jun : -
pobi : --
woni : -
jun : --
pobi : ---
woni : --
jun : ---
pobi : ----
woni : ---
jun : ----
pobi : -----
woni : ----
jun : -----
최종 우승자 : pobi, jun
이번에는 1주차와는 조금 다르게 추가적인 요구 사항이 있었습니다.
🚀추가된 요구 사항
- indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다.
- 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다.
- 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메서드)를 분리하면 된다.
- 3항 연산자를 쓰지 않는다.
- 함수(또는 메서드)가 한 가지 일만 하도록 최대한 작게 만들어라.
- JUnit 5와 AssertJ를 이용하여 본인이 정리한 기능 목록이 정상 동작함을 테스트 코드로 확인한다.
- 테스트 도구 사용법이 익숙하지 않다면 test/java/study를 참고하여 학습한 후 테스트를 구현한다.
인덴트에 대해 신경써야 한다는 걸 이번 프리코스를 통해 알게 되었습니다!
- 인덴트가 3 이상 깊어진다면, 코드 블록이 복잡해질 가능성이 높습니다. 다른 개발자가 코드의 흐름을 이해하기 어려워집니다.
- 메소드를 분리하면 코드 블록을 작게 유지할 수 있고, 각 메소드는 단일 책임을 갖게 되므로 코드를 이해하고 디버그하기가 훨씬 쉬워지게 됩니다.
- 메소드가 작고 단일 책임을 갖으면 유닛 테스트를 작성하기가 더 용이해집니다. 각 메소드를 개별적으로 테스트할 수 있고, 테스트 케이스의 관리가 쉬워집니다.
UML 다이어그램
+------------+ +--------------------------+ +----------------------------+
| Car |<|-----| Position |-------| Race |
+------------+ +--------------------------+ +----------------------------+
| name:String| | distance:int | | |
+------------+ | position : StringBuilder | | position: List<Position> |
+--------------------------+ | tryCnt: TryCnt |
| |
+---------------+ | |
| TryCnt |---------------------| |
+---------------+ | |
| tryCnt:int | | |
+---------------+ +----------------------------+
+-----------------------------+
| Winners |
+-----------------------------+
| positions: List<Position> |
| tryCnt: int |
+-----------------------------+
디렉토리 구조
src
├─ main
│ └─ java
│ └─ racingcar
│ ├─ domain
│ │ ├─ constants
│ │ │ └─ Constants.java
│ │ ├─ Car.java
│ │ ├─ CarValidator.java
│ │ ├─ Position.java
│ │ ├─ Race.java
│ │ ├─ TryCnt.java
│ │ ├─ TryCntValidator.java
│ │ └─ Winners.java
│ ├─ exception
│ │ ├─ car
│ │ │ ├─ BlankCarNameException.java
│ │ │ ├─ CarNameNumberLimitException.java
│ │ │ └─ DuplicateCarNameException.java
│ │ └─ trycnt
│ │ ├─ TryCntIsNumberException.java
│ │ └─ TryCntIsPositiveException.java
│ ├─ view
│ │ └─ InputView.java
│ └─ Application.java
└─ test
└─ java
├─ racingcar
│ ├─ domain
│ │ ├─ CarTest.java
│ │ ├─ CarValidatorTest.java
│ │ ├─ PositionTest.java
│ │ ├─ TryCntTest.java
│ │ └─ TryCntValidatorTest.java
│ └─ ApplicationTest.java
└─ study
└─ StringTest.java
피드백 반영하기
1. Getter대신 TDA 원칙 사용
Getter메서드는 객체의 상태를 읽어오기 위해 사용되지만, 남용하면 여러 문제점들이 발생할 수 있습니다.
1. 데이터 응집성 문제 : 객체가 자신의 데이터를 외부에 노출하면 데이터와 메서드가 분리되어 데이터 응집성이 떨어질 수 있습니다.
2. 캡슐화 위반: 객체의 상태를 직접 접근하면 객체의 캡슐화가 깨질 수 있으며, 객체 내부의 상태가 무분별하게 변경될 수 있습니다.
3. 의존성 증가: 다른 객체가 "Getter"를 통해 객체의 상태를 직접 읽으면 객체 간의 의존성이 증가하고 유지 보수가 어려워질 수 있습니다.
"Tell, Don't Ask" 원칙은 객체가 외부에 노출된 데이터를 직접 읽는 대신, 객체에게 필요한 작업을 요청하도록 하는 원칙입니다. 객체가 스스로 상태를 관리하고 외부에서 객체의 상태를 조작하는 대신, 객체에게 작업을 시키는 방식으로 상호작용을 설계합니다. 이러한 방식의 이점은 다음과 같습니다:
사실 이부분에 대해서 완벽하게 이해한건 아닙니다! 참고할만한 추천 링크를 받게 되었는데, 결국엔 메소드 이름에 get이 안들어갈 뿐 따로 메소드명을 변경해서 내부의 인스턴스 값을 return하는거 아닌가? 생각이 들기도 하는데, 2주차 코드리뷰를 통해 다시 한번 의견을 나눠보는 것도 좋을 것 같습니다!
2. 피드백 반영 - import시 와일드 카드 지양하기
원래의 코드에서는 Message 클래스에 여러 매직 리터럴 값을 정의하고, 다른 클래스에서 이 값을 사용하기 위해 와일드 카드(import wildcard)를 사용하여 Message 클래스를 호출하는 경우가 있었습니다. 이렇게 하면 필요하지 않은 정적 멤버까지 import되어 코드의 가독성이 떨어지고 유지 보수가 어려워지는 문제가 있었습니다.
사실 이부분에 대해서 단축키로 간단하게 할 수 있는는데, 굳이 상단에 올라가서 와일드 카드를 지우고 멤버변수 명으로 수정해줘야 할까? 고민했었습니다. 와일드 카드를 사용하면 간단하게 처리할 수 있었지만, 명시적 import를 통해 불필요한 멤버가 들어오지 않도록 하는 것이 코드 품질을 더 높이는 방향으로 결정하기 위해 스터디 팀원들과 의견을 공유했고, 더 나은 코드 품질을 달성하기 위해 팀원들의 의견을 존중하면서 협력의 중요성에 대해서도 배울 수 있었습니다!
3. 피드백 반영 - Input에 대한 검증의 역할은 다른 클래스에서 적용하기
저는 특정 도메인 관련된 검증은 해당 도메인 클래스에서 모두 몰아넣었었는데, 다른분들 코드랑 비교해보니까 검증관련된 내용은 다른 클래스에서 해주시더라구요! 이게 객체지향의 원칙에서 객체의 역할,책임,협력과 관련된 부분인 것 같습니다. 객체의 역할과 책임을 명확하게 하게 한다는 것을 배우게 되었습니다.
객체의 역할 (Role of an Object)이란 해당 객체가 어떤 작업을 수행하거나 어떤 정보를 유지하고 제공하는지를 정의합니다. 자동차 "엔진"객체는 "동작하는 엔진"의 역할을 가지며, "바퀴"객체는 회전하는 바퀴"의 역할을 가잡니다.
객체의 책임 (Responsibility of an Object)이란 역할을 수행하는 동안 다양한 책임을 갖게 되는데, 예를 들어 자동차의 "엔진" 객체의 책임은 연료를 연소하고 움직임을 생성하는 것일 수 있으며, "바퀴" 객체의 책임은 회전 속도를 조절하고 차량을 움직이게 하는 것이 될 수 있습니다.
객체 간의 협력 (Collaboration between Objects)이란 다른 객체와 협력하여 기능을 수행하는것을 의미합니다. 예를 들어, 자동차 시스템에서 "운전자" 객체는 "액셀러레이터" 객체에 액셀러레이터를 밟을 것을 요청하고, "엔진" 객체는 연료를 연소하여 움직임을 생성하고, "바퀴" 객체는 회전을 수행하여 자동차를 움직이게 합니다.
- 검증 코드를 별도의 검증 클래스로 이동: 입력 데이터에 대한 검증 코드를 별도의 검증 클래스로 분리했습니다. 도메인별 데이터 검증과 관련된 모든 로직을 담당하게 구성했고, 다른 클래스에서 이 검증 클래스를 호출하여 검증을 수행하게 됩니다.
'우아한테크코스' 카테고리의 다른 글
우테코 6기 프리코스 4주차 회고 (0) | 2023.11.15 |
---|---|
우테코 6기 프리코스 3주차 회고 (0) | 2023.11.15 |
우테코 6기 프리코스 1주차 회고 (0) | 2023.11.01 |