우아한 테크코스 3주차

MINJU·2022년 11월 15일
4

🖥 실행 결과 예시

구입금액을 입력해 주세요.
8000

8개를 구매했습니다.
[8, 21, 23, 41, 42, 43] 
[3, 5, 11, 16, 32, 38] 
[7, 11, 16, 35, 36, 44] 
[1, 8, 11, 31, 41, 42] 
[13, 14, 16, 38, 42, 45] 
[7, 11, 30, 40, 42, 43] 
[2, 13, 22, 32, 38, 45] 
[1, 3, 5, 14, 22, 45]

당첨 번호를 입력해 주세요.
1,2,3,4,5,6

보너스 번호를 입력해 주세요.
7

당첨 통계
---
3개 일치 (5,000원) - 1개
4개 일치 (50,000원) - 0개
5개 일치 (1,500,000원) - 0개
5개 일치, 보너스 볼 일치 (30,000,000원) - 0개
6개 일치 (2,000,000,000원) - 0개
총 수익률은 62.5%입니다.


해당 실행 결과와, 실행 조건을 보면서 작성한 최초의 기능 목록은 다음과 같다.
https://velog.velcdn.com/images/alswn9938/post/f69b0c76-24e3-4b16-bda8-29e8e32dbbfe/image.png
https://velog.velcdn.com/images/alswn9938/post/c4d517f5-7602-4c93-b43c-530fe66195da/image.png

예외 상황을 따로 작성해도 좋으나, 각 기능 단위 별로 발생할 수 있는 예외 상황필요한 조건을 정의하니 보다 쉽게 README를 작성할 수 있었어서 이렇게 작성하게 되었다.

하지만 지금 생각해보니 Validator를 활용해 검증 로직을 분리한 만큼, 다음 README에선 예외 상황 목차를 만들어 따로 분리해놓는 것이 더 보기 좋을 것 같다 🙄





이번 미션에서 내가 (특히) 집중한 부분은


1. 설계 순서 변경하기

숫자 야구 미션에서는 (1) Application에 전반적인 로직을 먼저 구현하고 -> (2) 로직의 구조를 파악 한 다음 -> (3) 그 구조에 맞게 클래스를 분리 하는 방식으로 설계했다.

이러한 방식이 개인적으로는 나한테 더 잘 맞는 것 같기는 한데
(나는 아직 다 하지 못한 일이 많으면 거기에 압도되어 초조해하는 성격을 가지고 있다🤣)

미션과 같은 작은 프로젝트가 아닌, 현업에서 큰 프로젝트를 구현하게 된다면 위와 같은 방식은 프로젝트 진행 속도를 늦추고 불필요한 시간을 많이 잡아먹게 될 수도 있겠다는 생각이 들었다.

따라서 이번 로또 미션에서는 (1) 프로젝트의 구조를 설계하고 -> (2) 그에 맞게 구현한 다음 -> (3) 리팩토링 하는 방식으로 진행하고자 노력했다!


2. 변수 이름에 자료형 사용하지 말기

예를 들어, 로또 번호를 담고 있는 List를 만들 때, 예전의 나는 당연하게도 LottoNumberList로 네이밍 했었다. 해당 변수가 로또 번호를 담고 있는 리스트임을 강조하는 것이 옳다고 생각하여 이와 같이 진행했는데, 좋은 코드를 위한 자바 변수명 네이밍 게시글을 통해, 이는 자료구조에 종속적인 네이밍이 되어 문제가 발생한다는 사실을 알게 됐다.

따라서 이번에는 LottoNumbers와 같이 최대한 직관적이고, 자료구조에 종속적이지 않은 네이밍을 사용하고자 노력했다


3. 일급 컬렉션 활용하기

이번 미션에선 이미 주어진 Lotto 클래스가 존재했다.
해당 클래스는 로또 번호를 담은 List<Integer> numbers가 존재하고, 이를 생성자에서 주입받는 코드로 구현되어 있었는데

해당 클래스를 보자마자

😮 아! 일급 컬렉션!

을 외칠 수 있었다!
CS 스터디에서 최근 발표한 내용이었기 때문이다.

컬렉션을 Wrapping한 일급 컬렉션이 기존 코드로 주어진만큼, 아직 익숙하진 않지만 최대한 일급 컬렉션 개념을 활용해보고자 노력했다.

가장 초점을 맞춘 부분은 일급 컬렉션을 사용하는 이유를 지키는 것이었다.

  1. 해당 컬렉션을 사용하는 곳에서는 일급 컬렉션 생성자만 사용하면 되기 때문에 편리한 코드를 만들 수 있다.
  2. 컬렉션과 관련된 역할을 일급 컬렉션에 부여하여 상태와 로직을 한 곳에서 관리할 수 있다.

내가 정리했던 일급 컬렉션의 장점이다.

이러한 장점을 최대한 살리고자 노력했고 또 끊임없이 고민했다.
그 고민이 코드에 잘 녹아났으면. . . ^___^


4. 읽기 쉬운 코드 만들기

이것은 로또 미션에서도 지키고자 노력했던 부분이다!
우테코 프리코스를 진행하기 전에는,

내가 작성한 코드를 남이 볼 수도 있겠구나 🙄

라는 생각은 안중에도 없었는데(ㅎㅎ..)
프리코스를 진행하면서 로직을 모르는 누군가가 봐도 쉽게 이해할 수 있는 코드를 작성하는 것에 대한 중요성을 깨달았다.

이러한 깨달음을 체화하고자 이번에도 최대한 읽기 쉬운 코드를 만들고자 노력했다.

메소드를 분리하여 그 결과를 로직 순서대로 통합하였고
이해하기 쉬운 네이밍을 위해 끊임없이 고민했다!


5. 직관적인 테스트명

테스트 결과만 보더라도, 어떤 테스트를 통과 했는지 그리고 어떤 테스트에서 에러가 발생했는지 쉽게 확인할 수 있도록 네이밍했다.

지난 주 숫자 야구의 테스트 코드 네이밍이 아래와 같다면

이번 주 미션의 테스트 코드 네이밍은 아래와 같다.

나는 나름 발전했다고 생각하는데 다른 분들은 어떻게 생각하실지 궁금하다..ㅎㅎ..



이와 더불어 제공해주신 피드백, 그리고 구현 조건도 끊임없이 고려하고 또 이에 부합하게 작성했는지 계속해서 확인했다.

내 코드가 옳다는 확신은.. 아직 들지 않지만..
그래도 끊임없이 고민하는 과정에서 지난 미션의 나 보단 성장할 수 있었다고 생각한다 :)




🖥 프로젝트 진행

위의 다짐(?)을 기반으로 프로젝트를 진행했다.

🦴 구조 설계

수도 코드

구조를 설계하기 위해 먼저 수도코드를 작성했다.

main{
	금액 입력 문장 출력;
    사용자 입력 받음;
    
    사용자 로또 개수 출력;
    사용자 로또 번호 출력;
    
    당첨번호 입력 문장 출력;
    당첨 번호 입력 받음;
    
    보너스 번호 입력 문장 출력;
    보너스 번호 입력 받음;
    
    당첨통계 문장 출력; (두 줄)
    결과 출력;
    수익률 출력;
}


이를 기반으로 작성한 초반 Skeleton은 다음과 같다.

- domain (비즈니스 로직)
  - Revenue (수익 및 수익률 계산)
  - Score (등수 계산)
- model (일급컬렉션)
  - Bonus (보너스 숫자)
  - Lotto (개별 로또)
  - Lottos (로또들)
- validation 
  - Validation (모델 생성 검증 외 다른 검증 로직)
- view
  - input
    - CreateBonus (보너스 숫자 생성)
    - CreateLott (로또 생성)
    - UserInput (유저 인풋 받음)
  - output
    - Guide (가이드 문장 출력)
    - Result (결과 통계 출력)
- util
  - Constants (상수)


고민 (1) : 비즈니스 로직이란?

이번 과제에 비즈니스 로직이라는 명칭이 포함되어 있어 스켈레톤 생성 과정에서 domain이라는 클래스를 따로 구현했다.

하지만 대체 비즈니스 로직이 무엇인지 명확히 이해 되지 않았고, model과 domain을 분리한 내 설계가 과연 옳은 설계인지 확신이 서지 않았다.

그러던 중, 해당 블로그를 통해 비즈니스 로직을 판별하는 방법은 이 코드가 현실 문제, 즉 비즈니스에 대한 의사 결정을 하고 있는가?를 고민하는 것임을 알게 되었다.

일급 컬렉션에 해당하는 Lotto, Lottos는 모두 로또 게임의 핵심 로직 (ex. 6개의 숫자를 가진 로또를 만들어낸다)을 담고 있는 클래스였으므로 domain 클래스에 들어가야한다는 것을 깨달았고, 이를 기반으로 model 패키지를 삭제했다.

많은 고민 끝에 완성된 구조이지만, 아직 내 방식이 완전히 옳다고는 생각하지 않는다. 이번 미션이 끝나면 다른 분들의 회고를 구경하면서 다른 사람들은 어떻게 비즈니스 로직을 관리하는지 확인해봐야할 것 같다.



고민 (2) : Bonus도 일급 컬렉션으로 관리해야하는가?

위의 초기 Skeleton을 확인하면 Bonus도 일급 컬렉션을 모아놓은 패키지인 model에 포함되어 있음을 확인할 수 있다.

보너스 번호라는 한 자리의 int도 일급 컬렉션으로 관리해야하는가..에 대해 계속 고민했는데, 결론적으로 Bonus는 일급 컬렉션으로 wrapping 하지 않는다고 결정했다.


일단 명칭에서도 알 수 있듯 일급 '컬렉션'컬렉션과 관련된 로직을 한 곳에 모아 놓기 위한 구조이기 때문에 int 타입인 보너스 숫자와는 의미적으로 부합하지 않는다는 생각이 들었고

보너스 번호는 "당첨 번호"에 포함되는 개념으로서 따로 관리되는 것이 아닌 "당첨 번호"와 함께 관리되어야 한다고 생각했기 때문이다.


만약 Bonus 클래스와 WinningNumber 클래스가 따로 존재한다면, 다른 사람이 코드를 봤을 때 "아~ 보너스 숫자와 당첨 번호는 서로 연관이 있구나~"라고 생각하기는 쉽지 않을 것이다.

따라서 Bonus를 Wrapping 하는 대신, int bonusLotto lotto를 포함하는 WinningLotto 객체를 만들었다.


public class WinningLotto {
    private final Lotto lotto;
    private int bonus;

    public WinningLotto(List<Integer> numbers){
        this.lotto = new Lotto(numbers);
    }

    public void updateBonus(int bonus){
        validateBonus(bonus);
        this.bonus = bonus;
    }

    private void validateBonus(int bonus){
        Validator.validateNumberRange(bonus);
        Validator.validateNonDuplicatedBonusNumber(this, bonus);
    }

    public boolean contains(Integer number) {
        return lotto.contains(number);
    }

    public boolean isBonusEqualTo(Integer number) {
        return bonus == number;
    }
}

고민 끝에 더 이해하기 쉬운 구조가 만들어진 것 같아 개인적으로 만족스럽다🙃



고민 (3) : 일급 컬렉션 검증 로직의 위치

검증 로직을 Validator에 분리했다.
하지만 기존에 제공된 Lotto 컬렉션에

public class Lotto {
    private final List<Integer> numbers;

    public Lotto(List<Integer> numbers) {
        validate(numbers);
        this.numbers = numbers;
    }

    private void validate(List<Integer> numbers) {
        if (numbers.size() != 6) {
            throw new IllegalArgumentException();
        }
    }

    // TODO: 추가 기능 구현
}

아래와 같이 이미 검증 로직이 구현되어 있었다.

일급 컬렉션 검증은 Validator가 아닌 컬렉션 내부에 구성해야하나?는 고민이 있었는데,
이렇게 내부에 모든 검증 로직을 몰아 넣으면 코드가 길어지고 가독성이 떨어질 것 같다고 생각했다.


따라서 기존 코드와 같이, 메인 검증 로직은 Validator에 구현하고

public class Lotto {
    private final List<Integer> numbers;

    public Lotto(List<Integer> numbers) {
        validate(numbers);
        this.numbers = numbers;
    }

    private void validate(List<Integer> numbers) {
        Validator.validateSize(numbers, LOTTO_SIZE);
        Validator.validateNonDuplicatedList(numbers);
        Validator.validateNumbersRange(numbers);
    }

위와 같이 일급 컬렉션 내부에선 Validator에서 로직을 꺼내쓰는 것으로 ! 구현했다.



🏃‍♀️ 구현 과정


고민 (4) : Guide 내부 static 메소드

콘솔에 문장을 출력하는 코드를 모아놓은 Guide 클래스 내부 메소드를 지난주 미션에선 public static 으로 구현했었다.

하지만 이번 미션 설계 구조에 따르면, Guide 내부의 함수를 호출하는 부분이 LottoController 안에서만 존재함을 파악하고 해당 컨트롤러 내부에 Guide guide = new Guide()를 생성하여 해당 인스턴스를 통해 메소드를 호출하는 방식으로 변경했다.

해당 결정은 왜 자바에서 static 사용을 지양해야하는가?라는 게시글 내용에 기반한다.



고민 (5) : 숫자 판별 로직에서 +- 예외처리 여부

String -> Integer 변환 가능 여부 판단에 정규식을 활용했다.


    public static void validateStringIsNumeric(String input){
        if(!input.matches("[+-]?\\d*(\\.\\d+)?")){
            throw new IllegalArgumentException("[ERROR] input이 숫자가 아닙니다.");
        }
    }

해당 코드는 +100, -100도 True로 넘겨주는 정규식인데,
이것을 수정해서 +- 기호가 포함돼도 false로 반환되게 만들까하다가

구입금액을 입력해주세요:
+1000

의미적으로 잘못된 부분이 없는 것 같아 유지하기로 결정했다.



하지만 구매 금액이 음수일 수는 없으므로

    public static void validatePriceRange(int price){
        if(price < PRICE_MINIMUM) {
            throw new IllegalArgumentException(INPUT_IS_NEGATIVE_NUMBER);
        }
    }

검증 로직을 추가했다.
현재 구매 금액의 MIN 값은 0인데, 수정이 필요한 상황이 오면 상수를 변경하여 검증 로직 조건문을 변경할 수도 있다!


고민 (6) : Controller 분리?

Application을 돌릴 때 로또 게임이 돌아간다는 것을 명시적으로 알려주고 싶어서 LottoController를 만들었다.


만들어 놓고 보니 "컨트롤러의 역할이 뭐지?" 라는 생각이 들었고

찾아보니 주로 사용되는 개념인 "스프링에서의 Controller"는 사용자의 요청을 처리한 뒤, 지정된 뷰에 모델 객체를 넘겨주는 역할을 수행하는 것임을 알게 되었다.


사용자의 요청을 받고 (ex. input) 결과를 반환하는 단위로 Controller를 생성해야겠다는 생각에 아래와 같이 사용자 Input을 받는 단위로 여러 컨트롤러를 생성하고 로직을 분리했는데

public class GameController {
    public void start(){
        LottoCountController countController = new LottoCountController();
        int lottoCount = countController.generate();

        LottoController lottoController = new LottoController();
        LottoGroup lottoGroup = lottoController.generateGroup(lottoCount);

        WinningNumberController winningNumberController = new WinningNumberController();
        WinningNumber winningNumber = winningNumberController.generate();
        winningNumberController.getAndUpdateBonus(winningNumber);

        ResultController resultController = new ResultController();
        Map<RankingType, Integer> countByRankingType =
                resultController.getCountByRankingType(lottoGroup, winningNumber);
        resultController.calcAndPrintProfit(countByRankingType, lottoCount);
    }
}

Guide도 계속 생성해야했고, controller끼리 값을 계속 주고 받아야해서 더 구조가 복잡해지고 지저분해짐을 확인할 수 있었다.

따라서 LottoController를 제외한 나머지 컨트롤러를 전부 삭제하고,
LottoController 내부에 여러 메서드를 생성해 메서드 값을 서로 주고 받는 방식으로 구현을 완료했다. LottoController 코드



고민 (7-1) : 일급 컬렉션 로직

LottoController에는 아래와 같은 로직이 있다.

  int bonus = getBonus();
  winningLotto.updateBonus(bonus);
  

해당 로직은, 생성된 WinningLotto 객체에 보너스 번호를 대입해주는 코드인데, 해당 코드를

public void updateBonus(WinningLotto winningLotto, int bonus){
	winningLotto.bonus = bonus;
}

와 같이 따로 메소드로 구현할지에 대한 고민이 있었다.


하지만, 일급컬렉션인만큼 컬렉션과 관련된 로직을 모두 일급 컬렉션에 위임 하는 것이 맞는 것 같다는 생각 하에, 이를 따로 메소드로 뽑지 않고 winningLotto 내부 메소드에 bonus를 넘겨주는 방식으로 구현을 완료했다.

최대한 일급 컬렉션의 장점을 살리겠다는 다짐을 실현한 코드 같아서 마음에 든다ㅎ__ㅎ


고민 (7-2) : 일급 컬렉션 로직

Lotto에서 본인이 가지고 있는 번호 list와 WinningLotto의 번호 list를 비교해서 자신의 RankingType을 결정하는 로직을 구현했다.

// Lotto

public RankingType getRankingType(WinningLotto winningLotto) {
        int matchingPoint = getMatchingPoint(winningLotto);
        
		...
        
        return rankingTypeCandidate;
    }

이로 인해, 로또(일급 컬렉션)가 가진 numbers(컬렉션)이 외부에 노출되지 않을 수 있었고, numbers(컬렉션)을 활용해야하는 로직을 Lotto(일급 컬렉션)에 위임하여 LottoController에서는 보다 가독성 좋은 코드를 구현할 수 있었음에 의미있었다.

// LottoController
        Map<RankingType, Integer> rankingTypeCounts =lottos.getRankingTypeCounts(winningLotto);
        printResult(rankingTypeCounts);



고민 (8) : 자료구조를 명시하지 않은 네이밍

자료구조명을 사용하지 않는 네이밍을 하는데 많은 고민이 있었다ㅠㅠ

내 코드를 보면, RankingType 별 개수를 세기 위한 Map<RankingType, Integer> 자료구조가 존재하는데, 이 자료구조를 위한 직관적인 네이밍이 잘 생각이 안났다.

보통 valueByKey 내지는 keyToValue가 사용됨을 알게 되고
CountsByRankingType, RnakingTypeToCount로 네이밍을 했지만,

뭔가.. 길고 직관적이지 않은 것 같아 맘에 안들었다.

고민하던 중 해당 글에서 힌트를 얻어 RankingTypeCounts로 변수를 네이밍했다.

단순히 RankingType의 개수를 카운팅하기 위한 Map이기 때문에, 아래와 같은 코드를 보면 훨씬 더 직관적으로 네이밍이 된 것 같은데

int counts = rankingTypesCount.get(rankingType);

확신하진 못한다 ㅎㅎ..
어떤 네이밍이 최적의 네이밍인지는 끊임없이 고민하고 또 수정해야만 하는 것 같다.


고민 (9) : Enum의 적절한 활용

기존 결과 통계 출력 코드의 경우

    private String getConditionString(int condition){
        return String.format("%d개 일치", condition);
    }

위와 같이, 일치해야하는 값(condition)을 밖에서 입력받으면 출력할 수 있도록 구현했다. 그래서 각 Ranking 별 condition 값을 FIRST_RANKING_CONDITION과 같이 상수로 관리하고자 하였는데,

이렇게 된다면 RankingType과 condition이 서로 연관이 있음을 직관적으로 알기 어렵다는 생각이 들었다.


이것을 어떻게 해결하면 제일 좋을 지 고민하다가, 그럼 Enum에 condition 값도 포함하면 되겠구나 라는 생각이 들어 아래와 같이 Enum 클래스를 변경했다.

(isNeedBonus의 경우, 보너스가 필요한 Ranking을 명시하기 위해 도입했다.)

이렇게 된다면 단순히 상수화하는 것보다, 변수들 사이의 연관관계를 쉽게 파악할 수 있게 된 것 같아 뿌듯했다.


고민 (10) : 상수화의 범위

 private static void updateRankingTypeCounts(Map<RankingType, Integer> rankingTypeCounts,
                                                List<RankingType> lottosRankingTypes) {
        for (RankingType rankingType : lottosRankingTypes) {
            int originCount = rankingTypeCounts.get(rankingType);
            rankingTypeCounts.put(rankingType, originCount + 1);
        }
    }

해당 로직은 rankingTypeCounts를 업데이트하기 위한 로직이다.

가만히 보면 RankingType의 기존 count를 가져와서, 그 값에 1을 더한 값을 다시 업데이트 해주는 것을 확인할 수 있는데 count를 업데이트 한다는 의미가, 당연스럽게도 한 개를 더 count하겠다는 의미이기 때문에 굳이 이 1도 상수화를 해줘야하는지에 대한 고민이 들었다.

고민 끝에 아래와 같이 코드를 작성했다.

 private static void updateRankingTypeCounts(Map<RankingType, Integer> rankingTypeCounts,
                                                List<RankingType> lottosRankingTypes) {
        for (RankingType rankingType : lottosRankingTypes) {
            int originCount = rankingTypeCounts.get(rankingType);
            rankingTypeCounts.put(rankingType, originCount + VALUE_FOR_UPDATE_COUNT);
        }
    }

하드코딩을 피하는 이유는 유지보수를 쉽게 하기 위함에 있다.

랭킹 하나가 발견될 때마다 두 번 카운팅해라! 라는 새로운 조건이 들어올 수 있기 때문에 이러한 부분에서 종속성을 줄이는 것이 더 좋다고 판단하여 상수화를 진행하였다.

너무 읽기 힘들지만 않는다면 상수화 하여 하드코딩을 피하는 것이 제일 좋다는 생각이 들었다 :)




🤦‍♀️ 에러 상황

구현 중 마주한 기록할만한 에러 상황은 다음과 같다.

에러 (1) : ImmutableCollections -> 방어적 복사 적용

ApplicationTest 로직을 돌리니 다음과 같은 에러가 발생했다.

Test 로직에서 List.of라는 불변 객체를 "Lotto의 numbers"로서 전달하고 있는데

내 기존 코드는 Lotto 내부에서 Collections.sort(numbers)를 하는 로직이 존재하기 때문에 발생하는 예외였다.


이를 어떻게 처리하면 좋을지 고민하다가 해당 글을 발견하게 되었다. 해당 글에서는 일급 컬렉션에서 방어적 복사와 unmodifiable Collection을 사용하는 이유에 대해 설명하고 있다.


결론적으로 나는 기존에 Lotto에서

    public Lotto(List<Integer> numbers) {
        validate(numbers);
        this.numbers = numbers;
    }

위와 같이 numbers를 외부에서 받아와서 초기화 하고 있었기 때문에, 외부에서 들어오는 List.of라는 불변 컬렉션이 그대로 들어와 정렬 과정에서 에러가 발생하는 것이었다.

이를 수정하기 위해, 그리고 컬렉션 내부 값의 안정성을 위해 일급 컬렉션 생성 로직은 방어적 복사 로직으로 변경하였다.

    public Lotto(List<Integer> numbers) {
        validate(numbers);
        this.numbers = numbers;
    }

(해당 글에선 일급 컬렉션 내부에서 getter를 선언할 때 unmodifiable Collection을 활용할 수 있다고 조언한다. 하지만 나는 컬렉션 내부의 값을 최대한 밖으로 노출시키지 않기 위해 getter를 사용하지 않았으므로 해당 방법을 적용하지 않았다.)


이렇게 생성하니 생성자 파라미터로 불변 객체가 들어와도 내부 변수는 방어적 복사를 기반으로 새로 생성되기 때문에 아래와 같은 정렬 로직이 문제없이 돌아감을 확인할 수 있었다.


    public String getSortedNumbersString() {
        Collections.sort(numbers);
        return numbers.toString();
    }


에러 (2) : 에러 처리 로직

구현을 다 끝내고 ApplicationTest를 돌리니 아래와 같은 에러가 발생했다.

해당 로직이 왜 터지는가 확인하기 위해 테스트 코드를 확인했는데

    public static void main(String[] args) {
        LottoController game = new LottoController();
        game.start();
    }

다음과 같이 코드가 구성되어있음을 확인할 수 있었다.

    void 예외_테스트() {
        assertSimpleTest(() -> {
            runException("1000j");
            assertThat(output()).contains(ERROR_MESSAGE);
        });
    }

즉, runException()을 통해 실행이 다 끝나고, 콘솔 창에 "[ERROR]"가 출력되는지 확인하는 테스트코드인데, 기존의 내 코드는 Validator에서 에러가 발생하면 throw IllegalArgumentException(메세지)를 발생 시키고 어디에서도 catch를 하지 않아 로직이 예외로 인해 끝나버리기 때문에 해당 테스트를 통과 하지 못하는 것이었다.


결국 Application 코드를

    public static void main(String[] args) {
        try {
            LottoController game = new LottoController();
            game.start();
        } catch (IllegalArgumentException e) {
            System.out.println(e.getMessage());
        }
    }

이와 같이 구현하여 해결하였는데,


굳이 왜 catch를 해줘야하는가?에 대한 궁금증이 들었다.


내가 생각한 이유는 아래와 같다.

우리는 사용자가 사용하는 서비를 만들고 있고, 내가 예외 처리 해 준 상황은 대부분 사용자 입력이 잘못 된 상황이다.
따라서 사용자가 잘못된 값을 입력했을 때 예외가 터지며 콘솔에 로그만 찍히고 비정상 적으로 종료되는 것은 옳지 않다.
정상적으로 시스템이 종료 되면서 "에러 안내 메세지"가 출력되어 사용자의 정상적인 입력을 유도하는 것이 옳기 때문이지 않을까..! 라는 생각이 들었다.

나름 합리적인 추론이었다고 생각한다..!
우테코를 하면서 모든 상황에 끊임없이 고민해보자라는 목표를 세웠는데, 이를 잘 실천하고 있음을 보여주는 사례가 아니었나 싶다 :)





👏 마치며


👀 부족했던 부분

미션을 진행하며 아쉬웠던 부분과 앞으로의 계획은 다음과 같다.


1. 기능 단위 커밋 부족

어려워진 문제 + 구조 설계 과정 도입 등으로 인해 체계적인 커밋을 진행하지 못했다.
커밋 컨벤션은 최대한 지키고자 노력했지만, docs를 한꺼번에 수정한다든지, test 커밋을 작성하지 못했다든지와 같은 문제가 발생했다.
다음 주 미션에서는 이러한 부분을 최대한 고려하며 신중히 커밋을 진행해야겠다. 커밋 히스토리는 일종의 문서이므로!

2. docs 관리 부족

기능 목록을 구현해놓고, 마지막에 일괄적으로 docs를 수정했다.
다음 주 미션에서는 말그대로 살아있는 문서를 만들기 위해 노력해야겠다.

3. OOP 설계 개념 부족

지난 주 프리코스 크루분들이 작성해주신 회고록을 구경하며, 많은 분들이 OOP에 대해 끊임없이 고민하고 계심을 알게 됐다.

나도 OOP를 적용해볼까? 라는 생각에 개념으로만 공부했던 SOLID 개념에 대해 찾아봤는데 해당 글이 정말 많은 도움이 되었다.

그래서 discussion에 공유했지만.. 아무 반응을 받지 못했다😂

개념으로만 공부했던 SOLID가 코드에선 어떻게 녹아나는지 위 글을 통해 공부할 수 있었지만 회고록을 쓰는 지금 시점에 되돌아보면, 그닥 잘 녹여내진 못한 것 같다는 생각이 든다.

DIP와 같은 법칙은 지켰지만, 인터페이스를 활용해 역할을 분리하는 것은 아직 부족하다는 생각이 든다.

어려운 개념이라 한 번에 완벽하게! 적용할 수는 없겠지만 OOP에 대해 계속해서 고민하는 과정을 통해 조금씩 성장하고자 노력해볼 것이다.

🎉 소감

더 성장했다.....는 멘트, 이젠 진짜 지겨울 수도 있지만, 우테코 프리코스 과정을 통해 나는 정말 정말로!!!! 꾸준히 그리고 크게크게 성장하고 있다. 당사자인 나도 체감이 될만큼!

예전 같으면 고민 없이 구현했을 로직을 끊임없이 고민하고, 이유를 찾고, 근거를 찾아가는 과정에서 정말 내가 예상한 것 이상으로 성장할 수 있었고 이러한 태도가 점점 체화되고 있음에 더할 나위 없이 뿌듯하다 :)


가독성을 위해 네이밍, 구조, 순서를 고민하고,
이론으로만 공부했던 일급 컬렉션을 활용해보고,
해당 구조의 장점을 최대한 살리기 위해 고민하고,
구조를 먼저 설계 해본 경험은

개발자로서의 내 양분이 되어줄 것이라 생각한다. 🍀

아쉬웠던 부분도 분명히 존재하지만, 이를 반성하고 또 새로운 목표를 세우는 과정을 통해 끊임없이 성장해나가고 싶다.





얼른 다른 분들 회고록도 구경하면서 내가 생각하지 못했던 부분에 대해 고민하는 시간을 가지고 싶다! ! ~ ~

다들 화이팅 화이팅 화이팅 😊❤

10개의 댓글

comment-user-thumbnail
2022년 11월 16일

enums 활용과 일급컬렉션에 대해 많이 배우고 갑니다. 이번 주에 활용해 봐야겠어요

1개의 답글
comment-user-thumbnail
2022년 11월 16일

굉장히 많은 부분을 고민하신게 글에서 느껴지네요...! 남은 기간도 파이팅하시길 바랍니다 :)

1개의 답글
comment-user-thumbnail
2022년 11월 16일

고민과정에서 제가 미처 생각하지못한부분이 수두룩하네요..!!
많이 배워갑니다🙇‍♂️

1개의 답글
comment-user-thumbnail
2022년 11월 16일

"모든 상황에 끊임없이 고민해보자" 대단하십니다.. 그리고 Enum 활용과 네이밍.. 진짜 잘하십니다!

1개의 답글
comment-user-thumbnail
2022년 11월 16일

enum 활용한 내용에 대해서 많이 배웠습니다. 남은 기간 파이팅하셔서 다같이 성장하는 일주일이 되길 바랍니다!!

1개의 답글