post-custom-banner

1주차가 끝나고...

4주차의 시작도 어김없이 리뷰로 시작했다. 2주차 과제 리뷰때 너무 많은 리뷰를 하는건 오히려 독이라는걸 깨닫고, 이번엔 상대적으로 적은 사람들과 리뷰를 진행했다.

처음 생각한 max 리뷰 횟수는 일곱 분이었는데, 리뷰 마감 이후에 이전에 리뷰했던 분들도 감사하게도 또 찾아와주셔서 지금까지 9명의 리뷰를 진행했다.
| ⬆️ 저번 주 보다 훨씬 줄은 리뷰 알림 메일

이후에 달린 리뷰 요청들은 시간이 날때 기분전환으로 짬짬이 해야겠다!


리뷰 피드백

3주차 과제에서 작성 시점까지 받은 피드백들과 느낀 점들은 이렇다.

1. enum 으로 선언한 정규식들의 이름을 더 명확하게 설정할 것

PURCHASE_AMOUNT_REGEX("^[0-9]+$") 

변경 후

PURCHASE_AMOUNT_REGEX_ONLY_DIGIT("^[0-9]+$") 

2. enum 에서도 공통으로 사용되는 문자를 한번 더 분리할 것

LOTTO_UNIQUE_ERROR_MESSAGE("[ERROR] 로또에 중복된 번호가 존재합니다.");

변경 후

LOTTO_UNIQUE_ERROR_MESSAGE(ERROR_ALRAM.getMessage() + "로또에 중복된 번호가 존재합니다.");

3. 공통된 enum 클래스들을 별도의 패키지로 분리하기
예 : 에러 메시지를 가지는 enum 클래스들을 별도 패키지로 분리


4. enum으로 다룰 수 있는 값 한번 더 확인하기

 WINNING_STATISTICS("\n당첨 통계\n"
            + "---\n"
            + "3개 일치 (5,000원) - %d개\n"
            + "4개 일치 (50,000원) - %d개\n"
            + "5개 일치 (1,500,000원) - %d개\n"
            + "5개 일치, 보너스 볼 일치 (30,000,000원) - %d개\n"
            + "6개 일치 (2,000,000,000원) - %d개\n"
            + "총 수익률은 %s%%입니다.");

변경 후

 WINNING_STATISTICS("\n당첨 통계\n"
            + "---\n"
            + "3개 일치 (%d원) - %d개\n"
            + "4개 일치 (%d원) - %d개\n"
            + "5개 일치 (%d원) - %d개\n"
            + "5개 일치, 보너스 볼 일치 (%d원) - %d개\n"
            + "6개 일치 (%d원) - %d개\n"
            + "총 수익률은 %s%%입니다.");

5. 컨트롤러가 최대한 필드를 적게 가지게 할 것

6. 철저한 책임 분리 vs 가독성을 잘 판단할 것. 절대적인 건 없음.

7. 직관적인 메서드명을 사용할 것

8. 높은 수는 _ 를 통해 분리할 수 있다.

9. 스트림을 사용해라

10. 매개변수로 많은 값을 받아야 할 때는 파라미터 객체를 사용해라.

이 중에서 10번 파라미터 객체는 조금 전 학습했는데, 정말 유용하게 쓸 수 있을 것 같았다.

public class InputSettings {
    private final Validator<String> validator;
    private final Supplier<String> inputSupplier;
    private final Runnable requestMessage;
    private final Consumer<String> errorMessageConsumer;

    public InputSettings(Validator<String> validator, Supplier<String> inputSupplier,
                         Runnable requestMessage, Consumer<String> errorMessageConsumer) {
        this.validator = validator;
        this.inputSupplier = inputSupplier;
        this.requestMessage = requestMessage;
        this.errorMessageConsumer = errorMessageConsumer;
    }

    // 각각에 대한 getter 메소드 추가...
}
public int selectPurchaseAmount(InputSettings settings) {
        String validInput = getValidInput(settings);
        return IntParser.parseInt(validInput);
    }

원래 4개의 값을 매개변수로 받는 selectPurchaseAmount() 메서드가 단 하나의 매개변수만을 받게 되었다. 가독성이 너무 좋아진 것 같다...


3주차 공통 피드백

3주차 공통 피드백에는 정말 좋은 내용들이 있었다.

객체는 객체스럽게 사용한다

상태를 가지는 객체를 추가했다면 객체가 제대로 된 역할을 하도록 구현해야 한다.
객체가 로직을 구현하도록 해야한다.

상태 데이터를 꺼내 로직을 처리하도록 구현하지 말고 객체에 메시지를 보내 일을 하도록 리팩토링한다. - 포비

이전의 코드들을 돌아보면 어느새 진정한 객체지향 보다는 그저 클래스 분리, 메서드 분리에만 집중했던 것 같다. 객체지향 프로그래밍은 객체가 스스로 일을 하도록 하는 프로그래밍인데 말이다.

필드(인스턴스 변수)의 수를 줄이기 위해 노력한다

이건 내가 3주차 과제에서 달성하지 못했던 것이다. 로또 컨트롤러 클래스를 보자.

public class LottoController{
    private final LottoServices services;
    private final LottoValidators validators;
    private int purchaseAmount;
    private int dividedPurchaseAmount;
    private HashSet<PurchasedLotto> purchasedLotto;
    private List<Integer> winningNumbers;
    private int bonusNumber;
    private LottoResultDto lottoResultDto;

    public LottoController(LottoServices services, LottoValidators validators) {
        this.services = services;
        this.validators = validators;
    }

    public void run() {
        setPurchaseAmount();
        printPurchasedLottoAmount();
        purchaseLotto();
        setWinningNumbers();
        setBonusNumber();
        compare();
        printLottoResult();
    }

    private void setPurchaseAmount() {
        purchaseAmount = services.lottoSettingService.selectPurchaseAmount(validators.purchaseAmountValidator, InputView::getUserInput,
                OutputView::printPurchaseAmountRequestMessage,  OutputView::printErrorMessage);
    }

    private void printPurchasedLottoAmount() {
        dividePurchaseAmount();
        OutputView.printPurchasedAmountResultMessage(dividedPurchaseAmount);
    }

    private void purchaseLotto() {
        purchasedLotto = services.lottoPurchaseService.purchase(dividedPurchaseAmount);
        printPurchasedLotto(purchasedLotto);
    }

    private void printPurchasedLotto(HashSet<PurchasedLotto> purchasedLotto) {
        for (PurchasedLotto lotto : purchasedLotto) {
            OutputView.printPurchasedLotto(lotto.getNumbers());
        }
    }

    private void setWinningNumbers() {
        winningNumbers = services.lottoSettingService.selectWinningNumbers(validators.winningLottoValidator, InputView::getUserInput,
                OutputView::printWinningNumberRequestMessage,  OutputView::printErrorMessage);
    }

    private void setBonusNumber() {
        BonusNumberValidator bonusNumberValidator = new BonusNumberValidator(winningNumbers);
        bonusNumber = services.lottoSettingService.selectBonusNumber(bonusNumberValidator, InputView::getUserInput,
                OutputView::printBonusNumberRequestMessage,  OutputView::printErrorMessage);
    }

    private void compare() {
        WinningLotto winningLotto = services.lottoService.createWinningLotto(winningNumbers, bonusNumber);
        WinningLottoDto winningLottoDto = winningLotto.toDto();

        services.lottoService.compareNumbers(winningLotto, purchasedLotto);
        lottoResultDto = services.lottoResultService.generateLottoResult(purchasedLotto, winningLottoDto, purchaseAmount);
    }

    private void printLottoResult() {
        OutputView.printWinningStatistics(lottoResultDto.fifthPrizeCount(), lottoResultDto.fourthPrizeCount(),
                lottoResultDto.thirdPrizeCount(), lottoResultDto.secondPrizeCount(),
                lottoResultDto.firstPrizeCount(), lottoResultDto.profitRate());
    }

    private void dividePurchaseAmount() {
        dividedPurchaseAmount = purchaseAmount / PURCHASE_DIVISIBLE_AMOUNT.getValue();
    }

}

컨트롤러가 수많은 필드를 가진다. run() 메서드 내부에 지역 변수로 설정해주는 방법도 있었는데, 처음엔 그렇게 했었다.

근데 그러다 보니 run() 메서드 내부의 코드가 너무 많아지고 가독성이 떨어져서, 어쩔 수 없이 클래스 필드를 많이 가지게 했었다.

이번 주 과제에서는 메서드 체이닝을 사용하거나, 지역 변수를 사용해야겠다..!


도메인 객체에 대한 의문

그런데 의문이 생겼다. 도메인 객체인 Lotto 클래스가 너무 뚱뚱해진다면?

상태로 가지는 로또 번호 를 스스로가 다루기 위해 수많은 메서드들을 가지게 된다면? 이 때 일어나는 클래스 분리도 해서는 안 되는걸까?

만약 클래스 분리를 하면 상태를 다른 객체가 관리하게 되는 것 같다는 생각이 또 든다.
예를 들어 너무 많아진 검증 조건을 LottoValidator 라는 클래스를 만들어 이 객체에서 검증 후 Lotto 클래스의 생성자에 값을 전달해준다면, 로또 번호라는 상태를 Lotto 객체 스스로가 관리하지 못하는게 아닌가?

갑자기 '상태를 객체 스스로가 관리해야 한다', '객체에 메시지를 보내 스스로가 일하게 해야 한다' 라는 말이 너무 헷갈렸다.

그래서 위의 의문을 한번 더 학습하고 정리해봤다.

| [우테코 프리코스 4주차] - 도메인 객체가 뚱뚱해진다면?

결론만 말하자면, 분리해도 되고, 내가 하고 있는 방식이 얼추 맞다.


마무리

오늘은 여기까지가 끝이다. 어제 회고글들을 한번 더 점검한다고 새벽 3시에 잤더니 10시 쯤 일어나게 됐다.

점심 먹고 리뷰와 코수타 참여, 피드백 내용과 도메인 객체에 대한 의문을 학습하니 벌써 하루가 끝나간다. 설계를 아직 시작하지는 못했지만, 되게 애매했던 개념인 도메인 객체를 확실하게 잡고 갈 수 있어서 좋았다. 이 개념을 제대로 잡지 않으면 설계할 때 매우 헷갈릴 것 같았기 때문...!

좋았던 점

1. 도메인 객체의 역할과 분리에 대해 확실히 학습한 것

2. 여러 매개변수를 전달받기 위한 방법인 파라미터 객체를 배운 것

3. 피드백을 통해 개선할 점을 많이 알게된 것

4. 이번 주 과제가 매우 어려운 것.

코수타 때 준 코치님이 엄청 어려울 거라고 얘기하셨는데, 요구사항을 훑어보니 정말 많은 기능을 구현해야 되겠다고 생각했다.

그리고 그 요구사항들을 보면서 솔직히 좀 설렜다.
이 설렘이 말로 설명하긴 조금 힘든데, 새로운 문제에 대한 기대감, 어려운 문제에 도전하는 승부욕, 과제를 성공적으로 구현했을 때 기대되는 성취감 등이 복합적으로 작용한 것 같다.

내일부터는 설계를 시작한다. 이번에도 즐길 수 있기를!

profile
자바 백엔드 개발자
post-custom-banner

0개의 댓글