inflearn logo
강의

강의

N
챌린지

챌린지

멘토링

멘토링

N
클립

클립

로드맵

로드맵

지식공유

인프런 워밍업 스터디 클럽 3기 백엔드-code 2주 차 발자국

tteokboki
0

글은 박우빈님의 readable-code강의 참조하여 작성한 글입니다.

정신없이 진도표를 따라 강의를 수강하고 미션을 진행하였더니 일주일이 순식간에 지나가버렸다.
처음에는 할 만하다고 느꼈었는데,,,강의 내용을 습득하여 스스로의 힘으로 코드에 적용까지 하려니 우빈님이 당부하신 대로 쉽지 않았던 나날들이었던 것 같다,,🥹

특히 이번 스터디카페 리팩토링 미션에 뻥 안치고 10시간 이상은 투자한 것 같은데,,,ㅋㅋㅋㅋ(미션 당일 + 자고 읽어나서 맑은 정신으로 한 번 더 도전)
이후에 리팩토링 강의를 수강하며 내가 전혀 고려하지 못했던 부분을 리팩토링하신 것을 보고 놀랐다. 뿐만 아니라, 리팩토링을 뚝딱 해내시는 모습에서도 감탄했다.
원래 누군가가 뭔가를 쉽게 해내는 것처럼 보이면 진짜 고수라는 말이 딱 맞는 것 같다...!
강의 속에서 리팩토링 하시는 모습은 굉장히 쉬워보였는데...혼자서 해내려니 정말 막막했다 ㅋㅋㅋㅋ
그래도 나도 경험을 더 쌓고 나면, 지금보다 더 짧은 시간 안에 더 객체지향적으로 리팩토링을 해낼 수 있겠지!!
이를 위해 다른 분들이 리팩토링 하신 코드도 많이 읽으면서 여러 번 리팩토링 해봐야겠다.

 

학습 내용 요약

 

주석의 양면성

변수와 메서드의 나열 순서

패키지 나누기

은탄환은 없다

 

미션

 

Day 4 미션 피드백

public boolean validateOrder(Order order) {
    if (order.getItems().size() == 0) {
        log.info("주문 항목이 없습니다.");
        return false;
    } else {
        if (order.getTotalPrice() > 0) {
            if (!order.hasCustomerInfo()) {
                log.info("사용자 정보가 없습니다.");
                return false;
            } else {
                return true;
            }
        } else if (!(order.getTotalPrice() > 0)) {
            log.info("올바르지 않은 총 가격입니다.");
            return false;
        }
    }
    return true;
}

 

public class Order {

    private long id;
    private List<Item> items;
    private Customer customer;

    public Order(final List<Item> items, final Customer customer) {
        validateOrder(items, customer);
        this.items = items;
        this.customer = customer;
    }

    private void validateOrder(final List<Item> items, final Customer customer) {
        if (doesNotHaveItems(items)) {
            throw new RuntimeException("주문 항목이 없습니다.");
        }
        if (doesNotHaveCustomerInfo(customer)) {
            throw new RuntimeException("사용자 정보가 없습니다.");
        }
    }

    public boolean doesNotHaveItems(final List<Item> items) {
        return items.isEmpty();
    }

    public boolean doesNotHaveCustomerInfo(final Customer customer) {
        return customer == null;
    }
}

 

=> boolean을 return하고 있는 기존 메서드에 대한 리팩토링으로 예외를 던지는 것으로 변경하는 것은 좋을 수도, 나쁠 수도 있다.
(자칫하면 오버엔지니어링이 될 수 있음)

📌 예외를 던지는 것은 비싸기 때문에, 신중하게 메서드의 사용현황을 파악 후 상황에 맞게 리팩토링 할 것!!

 

public class Item {

    private static final int MINIMUM_VALUE = 1;

    private long id;
    private String name;
    private int price;

    public Item(final long id, final String name, final int price) {
        validatePositivePrice(price);
        this.id = id;
        this.name = name;
        this.price = price;
    }

    private void validatePositivePrice(final int price) {
            if (price < MINIMUM_VALUE) {
                 throw new RuntimeException("올바르지 않은 가격입니다.");
            }
    }

}

고민이었던 부분: Order 클래스 내에 Item 클래스를 새로 만들어, 이를 리스트 형태로 Order에 의존성 주입하도록 코드 리팩토링을 진행하였다.
이 때 Item 객체를 생성할 때 마다 금액이 양수인지 검증하고 있는데, Order 클래스에서 전체 총 금액이 양수인지 검증을 다시 한번 해주는 게 좋을지 고민이 됐었다.

=>  우빈님 답변: 이미Item 객체를 생성할 때 금액의 유효성을 보장하고 있으니, Order 클래스는 굳이 하지 않아도 될 것 같다.
물론 비지니스 로직이 복잡해지면, 오히려 필요하다고 느끼는 순간이 올 수 있으니, 그 때 추가해도 늦지 않다!

 

Day 7 미션

public boolean isSamePassTypeWith(final StudyCafePassType studyCafePassType) {
        return passType == studyCafePassType;
    }

passType를 비교해야 하는 곳에서 passType를 게터를 통해 비교해주는 게 아닌, isSamePassTypeWith 메서드와 같이 객체에 메세지를 전달하자!

public interface StudyCafePassHandler {

    boolean isAppliable(final StudyCafePassType studyCafePassType);

    StudyCafePasses findCandidateStudyCafePasses(final StudyCafePasses studyCafePasses);

}

=========

# StudyCafePassMachine

    private void processUserSelection(final StudyCafePassType studyCafePassType) {
        final StudyCafePasses availablePasses = getAvailablePasses(studyCafePassType);

        outputHandler.showPassListForSelection(availablePasses);
        final StudyCafePass selectedPass = inputHandler.getSelectPass(availablePasses);
        if (studyCafePassType == StudyCafePassType.FIXED) {
            checkLockerPass(selectedPass);
            return;
        }
        outputHandler.showPassOrderSummary(selectedPass, null);
    }

Hourly, Weekly, Fixed라는 3종류의 카페 이용권이 존재하기 때문에, 인터페이스를 정의하여 if문 사용을 자제하고, 상황에 맞는 이용권을 가져왔다.

 

리팩토링 놓친 부분

public enum StudyCafePassType {

    HOURLY("1", "시간 단위 이용권"),
    WEEKLY("2", "주 단위 이용권"),
    FIXED("3", "1인 고정석");

    private final String command;
    private final String description;

    StudyCafePassType(final String command, final String description) {
        this.command = command;
        this.description = description;
    }

    public static StudyCafePassType from(final String userInput) {
        return Arrays.stream(StudyCafePassType.values())
                .filter(studyCafePassType -> studyCafePassType.command.equals(userInput))
                .findAny()
                .orElseThrow(() -> new AppException("잘못된 입력입니다."));
    }

}

 PassType은 중요한 도메인 모델인데, Input과 관련된 의미를 지닌 command가 침투되었다.
passType을 선택하는 command가 변경된다면, 단순히 입력 방식을 바꿨을 뿐인데 도메인 모델을 수정해야 하는 좋지 않은 상황이 발생한다.

public enum StudyCafePassType {

    HOURLY("시간 단위 이용권"),
    WEEKLY("주 단위 이용권"),
    FIXED("1인 고정석");

    private static final Set<StudyCafePassType> LOCKER_TYPES = Set.of(FIXED);

    private final String description;

    StudyCafePassType(String description) {
        this.description = description;
    }

    public boolean isLockerType() {
        return LOCKER_TYPES.contains(this);
    }

    public boolean isNotLockerType() {
        return !isLockerType();
    }

}

전혀 생각지도 못했지만, 알아두면 참 좋은 객체에 메세지를 보내는 방법에 대해 배웠다.

LOCKER_TYPES를 StudyCafePassType enum내에 적용하여 처리할 수 있다니

도메인 지식이 부족해서 그랬나, 나는 생각지도 못했던 방법이다.

나는 상위 도메인에서 if문을 통해 매번 확인해주었는데, 우빈님이 하신 방법이 더 책임 분리가 잘되어있고 객체에 메세지를 보내는 좋은 방법인 것 같다!!
또한 Locker type이 늘어나도, set에 내용만 추가해주면 된다는 점에서 유지보수도 훨씬 쉬울 것 같다!

 

마무리

강의를 굉장히 빠른 시간 안에 완강했다!!!

그렇지만 강의 내용을 완전히 내 것으로 만들었다기엔 부족하다,,,,

강의 볼 때는,,'이럴 때 조합, 일급 컬렉션, VO 등을 적용 하는구나~!' 를 배우면서, 앞으로 스스로 잘 판단해 낼 줄 알았는데,,,
혼자서 해 보려니까 너무나도 막막했다.

그래도 강의 내에서 주신 미션을 통해 내가 어느 부분이 부족한지 파악할 수 있었던 것 같다.

무엇보다도 너무 재밌었다,,,시간 가는 줄 모르고 했던 것 같다.

이제 강의는 끝이 났지만 지뢰찾기랑 스터디카페 코드에 대해 복습할 것이다. 리팩토링 적용하기 어려웠던 부분을 반복 작성해 보면서 체득시킬 생각이다. 이렇게 반복하다보면 우빈님의 사고법을 체득할 수 있겠지요...?

다음 주부터는 테스트 코드에 대해 배우는데,,평소 테스트 코드에 대해 공부를 많이 하지 않았어서 새로 배우는 양이 어마어마 할 것 같다.

강의 내용을 잘 습득할 수 있도록 메타인지 열심히 해야겠다!

나 자신 아자아자 화이팅이다💪🏻

 

[출처]

백엔드 클린코드 테스트코드 발자국 워밍업스터디클럽

답변 0