1. 요구사항 분석 및 목표 설정
지난 2주차에 대한 공통 피드백에 주어졌고, 요구사항 또한 추가되어 구현 단계에서 고려해야 할 요소들이 많아졌다. 그렇다고 뭔가 엄청난 것을 더해야 하는 것은 아니었지만, 지난 2주차도 만만치 않게 느껴졌던지라 이번 주차에서도 주의해야 할 점들에 대해 유의해서 살펴보았다.
그리고 지난 주차에서 아쉬웠던 점들을 열거하자면 다음과 같다.
- 처음 구현 단계에서 곧바로 클래스를 분리하지 못한 점
- 테스트 코드를 다양화하지 못한 점
- 예외 처리에 대해 좀 더 깊게 고민해 보지 못한 점
그래서 이번에는 좀 더 신경써서 위의 것들을 고려하면서도 발전된 형태로 실행해보기 위해 아래의 자그마한 목표들을 설정하였다.
- 처음부터 OOP 원칙을 준수하며 프로그램 구현하기: 메서드, 클래스의 역할을 생각하고 객체 분리 시 항상 따져보기
- 설계 패턴 적용해보기: 다른 분들의 결과물을 보며 대부분 MVC 패턴을 사용하셨는데 확실히 가독성이 커지고 역할 분리가 명확해진다는 것을 깨달았다.
- 테스트 방법에 대해 공부하고 더 나은 코드 작성하기: 테스트하기 좋은 코드에 대해 알아보고 충분히 고민할 시간 가지기
이어서 2주차 공통 피드백 내용은 다음과 같다.
- README.md를 상세히 작성한다.
- 기능 목록을 재검토하고, 작업하면서 업데이트한다.
- 값을 하드 코딩하지 않는다.
- 구현 순서도 코딩 컨벤션이다.
- 클래스는 상수, 멤버 변수, 생성자, 메서드 순으로 작성한다.
- 변수 이름에 자료형은 사용하지 않는다.
- 한 함수가 한 가지 기능만 담당하게 한다.
- 함수가 한 가지 기능을 하는지 확인하는 기준을 세운다.
- 테스트를 작성하는 이유에 대해 본인의 경험을 토대로 정리해본다.
- 처음부터 큰 단위의 테스트를 만들지 않는다.
2주차 회고에도 작성했지만, 변수 이름에 자료형을 사용하는 것은 오랜 코딩 습관이었는데 이번 기회에 알게된 뒤로부터는 사용을 지양하고 있다. 처음에는 그저 ~List, ~Map을 붙이던 것을 다른 이름으로 사용하려 하니 고민하는 시간이 길어졌는데, 이것도 어느정도 적응되는 것 같았다. ㅎㅎ
또 테스트를 작성하는 이유에 대해 정리해볼 것을 권유했는데, 후술하겠지만 이번 기회에 몸소 잘 느낄 수 있었다.
그리고 이번 주차에 추가된 요구사항으로 Java Enum을 적용하고 도메인 로직에 단위 테스트를 구현 (단, UI 로직은 제외)이 있었기 때문에, 이 또한 고려하여 작업해야 했다.
2. 기능 구현
이번 주차 과제는 로또 게임으로, 확실히 요구사항과 난이도가 지난 자동차 경주 게임에 비해 늘었다는 것이 느껴졌다.
https://github.com/woowacourse-precourse/java-lotto-6
기능 구현을 하기 앞서, 지난 주차 미션에 대해 다른 분들의 코드를 참고하여 잘 짜여진 코드가 무엇인지 고민해보고, 어떤 부분을 시도해봐야 할지 정리하는 시간을 가졌다. 특히 커밋 히스토리를 따라가며 구현 순서를 비교하며 어떻게 하면 효율적으로 코드를 작성할 수 있을지에 대해서도 배울 수 있는 시간이었다.
이후에는 객체를 선정하는 것으로 구현을 시작했고, 이번 주차 미션에서는 'Lotto' 클래스가 기본적으로 제공되었고 이를 활용해야 하는 조건이 있었기에 Lotto 클래스와 더불어 필요한 것이 무엇인지 생각하며 객체를 구분하였다. 특히 Enum도 사용해야 했기에 이 또한 Enum 클래스를 두어 활용하고자 했다.
프로그램이 잘 동작하는 것이 가장 기본적으로 중요하기 때문에 클래스를 분리한 상태로 기능 구현을 완료하고, 이후 리팩토링 과정에서 설계 패턴을 적용하고자 마음을 먹었다. 구현 단계에서 가장 신경 썼던 부분은 Enum 클래스의 활용이었는데, 이번 주차를 통해 알게된 것 중 하나가 Enum을 하나의 객체로써 보았을 때 여타 클래스와 같이 메소드를 내장시킬 수 있고, 이게 Enum 객체의 장점을 더욱 크게 해준다는 것을 알게 되었다.
package lotto.domain;
public enum Rank {
FIRST(6, 2000000000),
SECOND(5, 30000000),
THIRD(5, 1500000),
FOURTH(4, 50000),
FIFTH(3, 5000),
NOTHING(0, 0);
private int numberOfMatch;
private int winnings;
private Rank(int numberOfMatch, int winnings) {
this.numberOfMatch = numberOfMatch;
this.winnings = winnings;
}
public static Rank getRank(int numberOfMatch, boolean hasBonus) {
if (hasBonus && SECOND.checkMatch(numberOfMatch)) {
return SECOND;
}
return getOtherRank(numberOfMatch);
}
private static Rank getOtherRank(int numberOfMatch) {
for (Rank rank : values()) {
if (rank != SECOND && rank.checkMatch(numberOfMatch)) {
return rank;
}
}
return NOTHING;
}
private boolean checkMatch(int numberOfMatch) {
return this.numberOfMatch == numberOfMatch;
}
public int getNumberOfMatch() {
return numberOfMatch;
}
public int getWinnings() {
return winnings;
}
}
내 나름대로 나눈 클래스 하에 프로그램이 정상적으로 동작하는 것을 확인하고는, 리팩토링을 하기 전 테스트 코드를 작성했다. 그 이유는 다음과 같다.
- 기능을 테스트하면서 만든 기능의 불확실성을 감소시키고 미처 생각하지 못한 예외에 대해 알게 될 수 있다.
- 잘못된 리팩토링으로 인해 기존의 기능에 영향이 가는 것을 우려했다.
- 테스트 코드들 또한 일종의 문서가 될 수 있다.
위의 이유들로 인해 우선 테스트 코드를 작성해 정상적으로 동작하는 것을 확인함과 동시에, List를 파싱하는 데에 있어서 미처 고려하지 못한 실수가 있다는 것을 알게 되었다.
그렇게 테스트 코드 작성을 마친 뒤에는, 아래의 블로그 글을 참고해 MVC 패턴을 적용하고자 했다.
사실 MVC 패턴을 이전에 몇번 사용해본 적은 있으나, 그저 따라하는 수준에 불과했기 때문에 필요성을 크게 느끼지 못했고, 사용하지 않았을 때와 대비하여 어떤 차이가 있는지 세세하게 알지 못했는데, 이번에 차례차례 구현해보며 설계 패턴의 장점을 명확하게 몸소 느낄 수 있었다.
물론 MVC 각각의 계층에 맞게 딱딱 나뉘는게 아니라 여러가지 시행착오도 있었고 끝내 해결하지 못한 문제도 있었지만, 어찌저찌 마무리를 지었다. 막상 적용은 했지만 '잘 나누었다'라는 느낌을 전혀 받지 못해 아쉬웠지만, 그래도 흉내라도 낸 것에 만족할 수 밖에 없었다.
나름의 노력 끝의 결과물 구조는 아래와 같다.
java
└── lotto
├── Application.java
├── controller
│ └── Controller.java
├── domain
│ ├── Lotto.java
│ ├── LottoGenerator.java
│ ├── Rank.java
│ ├── UserLotto.java
│ └── WinningLotto.java
├── message
│ └── ErrorMessages.java
├── util
│ └── Util.java
├── validator
│ └── InputValidator.java
└── view
├── InputView.java
└── OutputView.java
3. 소감
- 아쉬운 점 및 부족한 점
- getter 사용 지양: 객체지향 생활 체조 원칙에도 나와있는 내용인데, setter 뿐만 아니라 getter를 사용하는 것도 지양하는 것이 좋다는 것을 뒤늦게 알게 되었다. 하지만 무조건적으로 사용하지 않을 수는 없고(그러면 구현하는게 거의 불가능? 하다...) 사용해도 되는 상황과 아닌 상황을 구분하는 기준을 정확하게 알 필요가 있다.
- MVC의 완벽한 분리: MVC 패턴을 적용하기는 했지만, 각 계층 간의 의존관계가 남아있다는 것을 느꼈고 특히 View와 Controller의 역할이 명확하게 구분되지 않아 아쉬웠다.
- 단위 테스트 assert 최소화: 좋은 단위 테스트는 assert를 최소화 하는 것인데, 이번 과제에서 assert를 하나의 테스트 메서드 안에 여러개를 사용한 것이 있어 아쉽다.
- 다음 주에 도전해 볼 것들 및 개선할 점
- 코드 리뷰: 여태 코드 리뷰에 대해 망설이다가 참여하지 못했는데, 주차가 진행되면서 코드 리뷰의 필요성을 절실히 느끼고 있다. 작성한 코드가 올바른 것인지, 어떤게 좋은 코드인지 확신이 서지 않을 때가 많아 프리코스 커뮤니티 시스템을 잘 활용해 코드 리뷰를 주고 받으며 발전하고자 한다.
- 테스트 코드를 구현하면서 작성하기: 모든 기능 구현을 끝내고 테스트 코드를 작성하는 것이 아닌, 기능을 구현하는 중간에도 테스트 코드를 작성하여 좀 더 테스트하기 쉬운 코드를 작성할 수 있도록 하는 것이 목표이다. 테스트 코드의 힘을 이번 주차에 느꼈기에 더욱 도전해보고 싶다.
- MVC의 완벽한 분리: MVC 패턴의 완벽한 분리가 결코 쉬운 것이 아니지만 항상 완벽하게 의존 관계를 없애고 패턴을 잘 짜도록 노력하고자 한다.
매주 알아가는 것이 하나, 둘이 아니라 정말 여러 개이고 이를 모두 받아들이고 소화하지 못하는 것이 아쉽다. 그래도 새로 알아야 하는 것이 무엇인지 아는 것 또한 큰 수확이다. 화이팅!!! (ง •̀ω•́)ง
'기타' 카테고리의 다른 글
[취준] 싸피 SSAFY 12기 전공반 지원 후기 (9) | 2024.07.16 |
---|---|
[우아한테크코스] 프리코스 2주차 회고 (0) | 2023.11.03 |
SW 코칭프로그램 1~6주차 내용정리 (0) | 2022.07.15 |