이번 3주차는 진행했던 모든 주차들 중 가장 아쉬운 주차였다.
평소 제대로 실천하지 못하여 익숙하지 않은 Domain 로직과 UI로직 분할을 너무 만만하게 봤던 나의 실책이었다.
Enum을 사용해야한다는 요구 사항도 추가되었는데,
Enum을 사용하는 방법을 제대로 알지 못하여 더욱 애먹었다.
보통 서버를 개발할 때 Enum을 유저 권한(일반 유저, 관리자)을 부여할 때만 사용하였기 때문에 Enum의 활용법을 알지 못하였다.
일급 컬렉션을 통해 Lotto 클래스를 구현하고자 했던 부분도 아쉬움이 많이 남았다.
Lotto 클래스를 List<LottoNumber> 컬렉션 객체를 포장한 일급 컬렉션으로 만들어 사용하려하였지만, 일급 컬렉션에 대한 이해도가 부족하여 이를 구현하지 못하였다.
분명 어려운 내용이었는데 방심하여 충분한 시간을 투자하지 못하였고 이는 객체지향과 거리가 먼 결과물로 나에게 돌아왔다.
그래서 이번 주차는 내가 놓쳤던 부분과 아쉬웠던 부분에 대해 기록하며 반성하고 다시는 헷갈리지 않고자한다.
:: Enum 사용
아래 자료를 보며 학습 하였지만 비슷한 값들끼리 묶는다는 개념을 제대로 이해하지 못해 사용에 어려움을 느꼈다.
때문에 아쉬움을 남긴 체로 끝낸 3주차와 새롭게 시작하는 4주차 사이의 약간의 준비 기간에 enum에 대해 다시 학습하였다.
위 글이 표현하는 비슷한 값들을 묶어 사용하는 Enum은 아래 예시와 같다.
: Enum 예시
만약 boolean값 처리에 관하여 true,1 / false,0 로 관리하는 상황이라고 가정하자
이렇게 되면 같은 의미를 가진 값이지만 if문을 통해 아래와 같이 판별하는 과정을 거쳐야만 한다.
int value = checkTrueFalse(-1);
if(value == 1){
return true;
} else if(value == 0){
return false;
}
int checkTrueFalse(int a){
if(a > 0){
return 1;
} else if(a < 0){
return 0;
}
문제가 느껴지지 않는가?
value변수에는 이미 참 또는 거짓에 대한 정보가 담겨있는데, if문으로 참,거짓을 한번 더 판별하여 return하게 된다.
checkTrueFalse() 메서드의 리턴 타입을 boolean으로 바꿔줄 수 있다면 그 방법이 best지만 만약 그렇게 할 수 없고 두 값을 하나의 의미로 사용해야만 하는 상황이라면 어떻게 해야할까?
그때 필요한 것이 바로 Enum이다.
Enum은 같은 성질의 값을 하나의 의미로 묶어주는 역할을 한다.
이렇게 말하면 이해가 어려울테니 Enum을 적용하여 위 코드를 리팩터링해보자.
return checkTrueFalse(-1);
Check checkTrueFalse(int a){
if(a > 0){
return Check.trueValue;
} else if(a < 0){
return Check.falseValue;
}
Enum Check{
trueValue(true,1),
falseValue(false,0);
private final boolean booleanValue;
private final int intValue;
public boolean getBooleanValue(){
return booleanValue;
}
public int getIntValue(){
return intValue;
}
public Check(boolean booleanValue, int intValue){
this.booleanValue = booleanValue;
this.intValue = intValue;
}
}
조금 더 알아보기 편하지 않은가?
같은 뜻을 가진 코드여도 Enum을 사용하여 관리하면 값에 이름을 붙여 관리할 수 있고,
다른 타입의 값을 하나의 값을 관리할 수 있게 되기 때문에 코드 가독성이 향상된다.
실패를 통해 부족함을 발견하고 보완하는 개발자의 태도로
현재에 좌절하지 않고 4주차에선 Enum을 제대로 활용하여 구현해내고자 한다.
:: Domain 로직 / UI 로직의 분리
도메인 기능과 UI기능 분리를 해야했지만 이 또한 제대로된 이해가 부족하여 뜻에 맞게 분리하는 것에 실패한 것 같다.
가장 아쉬웠던 부분 중 하나였기에 틈날 때마다 학습하여 이제는 분리가 어느정도 가능해졌기에 이에 대해 간략하게 적어본다.
: Domain 로직 - 현실적인 것
LottoGame 을 만들 때 현실적인 것이라고 하면 예를 들어 Lotto, LottoNumber, LottoMachine, Rank, Money 등이 있다.
Domain로직 내부에선 저장된 값에 대한 검증 로직또한 수행한다.
만약 Domain 로직 내부에서 내부 변수를 사용하여 수행할 수 있는 기능(ex 값을 검증하는 기능) 은 Domain 클래스 안에서 구현하도록 한다.
:: Lotto Domain 클래스 예시
public class Lotto {
private final List<LottoNumber> lotto;
public Lotto(List<LottoNumber>lotto) {
this.lotto =lotto;
}
private void validateLotto(List<LottoNumber>lotto){
// 검증 로직
// Collection의 size가 6개인지 검증
}
}
:: LottoNumber Domain 클래스 예시
public class LottoNumber {
private final int lottoNumber;
public LottoNumber(intlottoNumber) {
validateNumber(lottoNumber);
this.lottoNumber =lottoNumber;
}
private void validateNumber(intlottoNumber){
//검증 로직
//lottoNumber가 1~45 사이의 값인지 검증
}
}
3주차 과제를 기준으로 도메인을 선별해보면 아래와 같은 결론이 나온다.
- LottoNumber : 로또 번호를 담는 객체로 각각의 로또 번호를 LottoNumber 에 담는다.
- Lotto : 로또 객체로 LottoNumber 6개로 만들어진다.
- LottoMachine : 입력받은 금액으로 Lotto를 발급해주는 객체이다.
- Rank : 등수와 등수별 당첨금을 저장하는 Enum객체이다.
- Money : 입력받은 금액을 저장하는 객체이다.
이렇듯 현실과 가까운 코드을 도메인 로직 또는 비즈니스 로직이라고 한다.
헷갈릴 수 있으니 이번 글에서는 도메인 로직이라고만 부르겠다.
: UI 로직 - 입출력 기능
입출력을 담당하는 로직으로 로또 번호, 구입 금액을 입력하고 당첨 여부를 출력하는 등 입출력에 사용한다.
- 로또 구입 금액 입력 메시지 출력
- 로또 구입 금액 입력
- 당첨 로또 번호 입력 메세지 출력
- 당첨 로또 번호 입력
- 구입한 로또번호 출력
- 당첨된 등수 출력
- 수익률 출력
이 외에 도메인 내부값 뿐만 아니라 다른 값들을 함께 활용하는 기능(수익률 계산, 등수 계산)은 따로 구현한 Service객체를 통해 수행하는 방식을 선택했다.
:: 테스트 구현
TDD방식으로 수행 해보고자 했으나 조급한 마음에 클래스와 메서드를 제대로 분리하지 못하여 기능단위로 테스트하는 것에 어려움을 느꼈다. 때문에 제대로 단위 테스트를 구현하여 제출하지 못하였다. 테스트를 잘 구현하기 위해서는 메서드부터 기능 단위로 제대로 분할할 줄 알아야한다는 것을 다시 한번 깨닫고 Domain로직과 UI로직 학습에 전념하는 시간을 갖기로하였다. 아쉬운 마음이 컸기에 다음 주차에서는 기필코 TDD방식을 적용하며 개발할 수 있도록 분할을 잘 해내야겠다고 다짐하였다.
:: 일급 컬렉션 적용
일급 컬렉션이란 단어는 소트웍스 앤솔로지 의 객체지향 생활체조 파트에서 언급된 내용이다.
규칙 8: 일급 콜렉션 사용
List<String> 등과 같은 컬렉션은 클래스로 감싸주는 것을 원칙으로 한다.
만약 List<LottoNumber> 라는 것이 있으면 이것은 그냥 LottoNumber의 컬렉션 객체로 밖에 보이지 않는다. 하지만 이를 아래와같이 Lotto라는 클래스로 묶어준다면?
public class Lotto {
private final List<LottoNumber> lotto;
public Lotto(List<LottoNumber>lotto) {
this.lotto =lotto;
}
private void validateLotto(List<LottoNumber>lotto){
// 검증 로직
// Collection의 size가 6개인지 검증
}
}
어떻게 보이는가? 로또번호들로 이루어진 하나의 로또로 보이지 않는가?
그럼 이렇게 사용한 것의 장점이 무엇일까?
- 비지니스에 종속적인 자료구조
원래였으면 Lotto로 쓰이는 모든 LottoNumber컬렉션에 대해 검증해주어야하는 것을 일급 컬렉션 내부에서 값 검증하도록 사용할 수 있음. - Collection의 불변성을 보장
- private final로 컬렉션 변수를 선언한다.
- setter 메서드를 구현하지 않는다.
- final은 재할당만 금지하는 것이므로 add() 등은 가능하다. 때문에 getter를 구현할 때 Collections.unmodifiableList(컬렉션) 으로 묶어서 리턴하도록 한다. - 상태와 행위를 한 곳에서 관리
만약 값을 저장하는 일급 컬렉션이 있는데 값을 통한 계산을 다른 곳에서 하게 된다면 효율성이 떨어지지 않을까? 이를 해결하기 위해 값**(상태)에 대한 계산(행위)**을 일급 컬렉션 내에서 처리하도록 하여, 메서드 중복 생성을 막는 등 코드의 효율성을 높인다. - 이름이 있는 컬렉션
내가 생각하는 큰 장점 중 하나가 바로 이것이다. 컬렉션에 이름을 붙여 사용할 수 있다. 위에서 말했듯이 List<LottoNumber> 을 개발한 사람이 아닌 다른 사람이 접했을 때, 뭐라고 생각하게 될까?
”로또 번호의 집합? 그냥 로또번호를 모아놓은 것인가? 용도가 무엇이지?” 나는 이렇게 생각이 들 것 같다.
하지만 이를 Lotto 라는 일급 컬렉션으로 감싸준다면?
Lotto라는 객체 안에 List<LottoNumber>가 있게 된다.
그렇게 되면 처음 접한 사람도 “Lotto라는 클래스가 List<LottoNumber>를 감싸고 있으니 로또 안에 로또번호 6개가 저장되어 있을 것이라고 단번에 추측할 수 있게 될 것이다.
! 주의해야 할 점
일급 컬렉션은 컬렉션 외에 다른 필드가 없어야 한다. 일급 컬렉션은 하나의 컬렉션을 감싸주는 것이기 때문에 다른 필드는 존재할 수 없다.
reference
이렇게나 길게 정리될 정도로 3주차는 내게 너무나도 큰 아쉬움으로 남았다.
이 아쉬움은 4주차에선 같은 실수를 반복하지 않겠다고 다짐하는 계기가 되어주었다.
4주차 회고는 밝고 활기차게 돌아올 수 있도록 노력하겠다.
마지막 한 주를 불태워보자🔥🔥🔥
'부트캠프 > 우아한테크코스 5기 프리코스' 카테고리의 다른 글
[우테코 5기] 최종 코딩테스트 회고록 (2) | 2023.01.02 |
---|---|
[우테코 5기] 프리코스 4주차 회고록 (2) | 2022.12.24 |
[우테코 5기] 프리코스 2주차 회고록 (1) | 2022.11.20 |
[우테코 5기] 프리코스 1주차 회고록 (0) | 2022.11.09 |