본문 바로가기

우테코

우테코 4주차- [크리스마스 이벤트] 회고

 

우테코의 4주차 회고를 시작해보자.

 

이번 미션을 시작할 때 3주차 공통피드백에서 크게 신경을 썼던 점은 다음과 같다.

- 무분별한 getter보다 객체에 메시지를 보내 객체가 로직을 수행하도록 노력하자

- 만약 getter를 통해 직접 멤버변수에 접근해야 한다면 외부에서 변강하지 못하도록 unmodifiableList를 설정해주자

- 테스트를 위한 코드는 구현 코드와 별개로 이루어져야 한다.

- 메소드 시그니처 변형을 통해 테스트하기 좋은 메서드를 만든다.

- 오류가 진행된 부분부터 다시입력을 받는다.=> 이 파트는 지난 주 요구사항에 제대로 반영되지 못해 더 신경썼다.

 

1) enum 클래스을 통한 관련있는 변수 관리

연관있는 변수끼리 같이 관리하기 위해 메뉴이름-가격-카테고리를 enum클래스로 만들어 관리했다.

이후, 메뉴에서 이름을 찾거나 가격연산을 할 때 더욱 편리하게 구현할 수 있었다.

public enum Menu {
    MUSHROOMSOUP("양송이수프", 6000, "APPETIZER"),
    TAPAS("타파스", 5500, "APPETIZER"),
    SALAD("시저샐러드", 8000, "APPETIZER"),
    TBORN("티본스테이크", 55000, "MAIN"),
    BARBEQUE("바비큐립", 54000, "MAIN"),
    SEAFOODPASTA("해산물파스타", 35000, "MAIN"),
    CHRISTMAXPASTA("크리스마스파스타", 25000, "MAIN"),
    CHOCOCAKE("초코케이크", 15000, "DESSERT"),
    ICECREAM("아이스크림", 5000, "DESSERT"),
    ZEROCOKE("제로콜라", 3000, "DRINK"),
    REDWINE("레드와인", 60000, "DRINK"),
    CHAMPAGNE("샴페인", 25000, "DRINK");
    .......

 

 

2) 반복되는 메소드의 경우 옵션을 통해 하나로 통일

인풋값의 정합성을 검사하는 과정에서 비슷한 기능을 수행해야 할 일이 많았다.

예를 들어 날짜값을 검사하는 과정에서 입력값이 숫자인지 검사해야 했고

메뉴를 검사하는 과정에서도 메뉴개수가 숫자인지 검사해야 했다.

  public static int checkIsNumber(String input, int flag) {
        try {
            return Integer.parseInt(input);
        } catch (NumberFormatException e) {
            if (flag == 0) {
                Messages.isNotNumberException_1();
                throw new IllegalArgumentException();
            }
            Messages.isNotNumberException_2();
            throw new IllegalArgumentException();
        }
    }

따라서, 우선 기능을 하나로 통일시키고 옵션에 따라 오류가 났을 때 출력되는 메시지를 다르게 바꾸어주었다.

이렇게 중복 메소드 작성을 피할 수 있었고, 오류구분도 그대로 가능하다는 장점이 있었다.

다만 각 옵션이 많아질 경우 적용에 한계가 있고,

또 이 옵션이 무엇을 의미하는지 직관적으로 이해가 되지 않는다는 문제가 우려되기도 하였다.

 

3) 오류의 원인을 담은 메소드 구현

각 오류가 왜 발생했는지 추적하기 위해 오류 메시지 출력을 메소드로 구현하였다.

public static void notEnoughMenuExcepiton() {
        System.out.println(UNVALID_ORDER_MESSAGE);
    }

 

4) 외부 수정방지를 위한 메소드 사용

Food 객체 리스트를 꺼낼 경우 외부 변경을 방지하기 위해 Collections.unmodifiableList 사용했다.

  public List<Food> getFoodList() {
        return Collections.unmodifiableList(orderedFoods);
    }

 

5) 구현과정에서 메소드 시그니처 수정을 통한 TDD에 적합한 메소드 구현

readDate를 하는 과정에서 Console.readLine()이 아니라 String으로 입력을 받아 테스트할 수 있도록 시그니처를 수정했다. 원하는 test를 더 직관적으로 수행할 수 있었고, 원하는 값을 넣어주면서 예외처리 기능이 제대로 수행되는지 확인할 수 있었다.

    @DisplayName("날짜입력에 범위를 넘는 숫자를 넣었을 때 예외발생")
    @Test
    void date_범위예외_test(){
        assertThrows(IllegalArgumentException.class, () -> {
            InputView view = new InputView();
            view.readDate("32");
        });
    }

 

6) 단일 책임의 원칙을 의식하자

메소드는 하나의 책임만을 가지고 수행되어야 한다.

그런데 구현하는 과정에서 계속 여러 부가적 기능이 붙는 경우가 많았다.

 

특히 메뉴를 받고 그것을 FoodList로 만드는 과정은

1) 먼저 ,를 기준으로 menu를 tokenize한다.

2) 각 토큰을 -를 기준으로 나눈다.

3) 메뉴이름- 메뉴개수를 인수로 Food 객체를 만든다.

4) 만든 Food 객체를 리스트에 추가해준다.

5) Food List의 적법성을 검사한다.(중복메뉴, 20개 이상인지, 음료만 주문했는지)

6) 만약 오류가 나면 다시 입력받는다.

 

이런 기능들이 모두 하나의 메소드에 합쳐져 있었다.

따라서 Food 객체를 만드는 과정을 -> makeFoodobj 메소드로 분리

FoodList 적법성을 검사하는 과정 -> checkAllOrder 메소드로 분리

이렇게 경량화 하니 15줄 이내로 메소드가 구현될 수 있었다.

 

   public static Foods readOrder() {
        Foods temp = new Foods();
        System.out.println(ORDER_MESSAGE);

        String input = Console.readLine();
        String[] orderTokens = input.strip().split(",");
        try {
            for (String token : orderTokens) {
                String[] order = token.split("-");
                temp.appendFood(makeFoodobj(order));
            }
            checkAllOrder(temp);
            return temp;
        } catch (IllegalArgumentException e) {
            return readOrder();
        }
    }

 

 

구현 이후 아쉬웠던 점 및 개선점

 

- 몇가지 하드코딩 요소

증정메뉴와 관련된 요소가 하드코딩된 감이 없지 않아 있다. 따라서 증정메뉴가 변경되거나, 그 요소가 많아졌을 때 코드의 많은 요소가 수정되어야할 필요를 느꼈다. 

 

- 결국, 또 getter

: 결국 무언가 꼬이는 상황이 오면 이런 상황을 접근자를 통해 해결하고자 했던 것 같다. 

: 완성하고보니 객체 여기저기 웜홀을 뚫어놓은 느낌이라 제대로 정보은닉이 안된 것 같다는 생각이 들었다.

: 아직 객체간 메시지를 주고 받게 하는 연습이 부족함을 느꼈다.

 

- 카테고리 개수를 List로 처리했던 것

: 카테고리별 메뉴개수를 카운트하는 메소드 반환값을 리스트로 만들었다.

각 카테고리별 메뉴를 카운트하기엔 중복되는 코드가 많았기 때문이다.

: [에피타이저-메인메뉴-디저트- 음료 ] 순으로 메뉴 카운트 정수리스트를 반환했는데,

막상 구현하고 보니 알고리즘 풀던 습관이 들어난 것 같았다.

구현은 더 쉬워졌지만 이게 협업에 적합한 방식의 구현인지는 의문이 들었다.

우선 급한대로 주석을 달아 무엇을 의미하고자 이런 메소드를 구현한 것인지 설명을 달아놓았다.


개인적 회고

 

자바를 모르는 상태로 경험삼아 해보자는 마음으로 시작한 우테코

그런 우테코의 4주차 회고를 작성하고 있다는 사실이 믿기지 않는다.

 

프리코스가 시작되기 전,

우테코 프리코스가 좋은 경험이이자 성장이었다는 후기를 보았다.

결국 독학을 하는 건데 그렇게 많은 도움이 될까?

합격을 위한 가식적인 이야기라 생각했다. 

 

 4주가 지난 지금,

나에게 그러한 후기가 가식이냐 묻는다면

자신있게 그렇지 않다고 대답할 것 같다.

 

실력이 모자란 걸 알기에 합격은 처음부터 바라지 않았고

오직 프리코스에서 무엇을 얻어갈 수 있느냐에 집중했다.

 

프리코스는 독학이 필요한 과정이지만,

개발자가 왜 독학을 해야하는지,

어떻게 독학을 해야하는지 알려주는 과정이었다.

 

무엇을 위해 공부해야하는지 매주 마일스톤이 주어졌고, 그 방법도 주어졌다.

내가 해야하는 건 시간을 비워두고 주어진 길을 따라 걸어가는 것 뿐이었다.

 

무엇보다 내가 어떤 부분에서 부족한지 명확하게 바라볼 수 있었다.

나는 협업 경험이 부족했고, 객체지향적으로 프로그램을 설계하는 능력이 부족했다.

자바 코드 피지컬도 문제지만, 그 간극은 시간을 쏟는다면 생각보다 쉽게 메워질 수 있는 간극이란 점도 느꼈다.

 

실력에 대한 갈증이 더 심해졌고

그 갈증을 긍정적으로 바라보고 있다.

물론 이번년도는 많은 부분에서 부족했기에 합격은 과분하겠지만

본래 목표가 내년이었던 만큼 부족한 시간을 쪼개 완주한 내가 참 대견하다.

결과는 부족했더라도 스스로에겐 수고했다고 이야기해주고 싶다.

 

그럼 내년에 더 완성된 모습으로 뵙겠습니다:)