Skip to content

Comments

크리스마스 프로모션 코드리뷰 ❤︎ #3

Open
jisu-om wants to merge 88 commits intoreviewfrom
main
Open

크리스마스 프로모션 코드리뷰 ❤︎ #3
jisu-om wants to merge 88 commits intoreviewfrom
main

Conversation

@jisu-om
Copy link
Owner

@jisu-om jisu-om commented Nov 16, 2023

아래는 제가 궁금한 점들 입니다.

이벤트 날짜 조건을 어떻게 일반화 할 수 있을지?

설계를 좀더 깔끔하게 해보고 싶습니다.
2-1) BenefitCalculator, EventFinder, BadgeGenerator 객체를 만들었다가 삭제하였습니다. 사용되는 곳이 한 곳이라 그냥 메서드로 만들어서 처리하였습니다.
2-2) 확장을 위해서는 이벤트의 조건과 혜택금액을 enum(EventDetail)으로 만들기보다 이벤트를 상속받는 각 이벤트를 객체로 만드는 것이 좋았을지 싶었습니다.

OutputView 에 정보를 전달하는 방법
ResultDto를 생성하여 outputView 에서 getter로 필요한 정보를 꺼낼 수 있도록 만들었습니다.

재참여율 목표를 5%로 설정했을까?
코드로 이런 점을 구현할 수 있을까?

…생성, 이를 총괄하는 christmasEventManager 생성
Copy link

@Arachneee Arachneee left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

리뷰남기고 갑니다~

Comment on lines 11 to 18
public class OrdersInputHandler {
private final InputView inputView;
private final OutputView outputView;

private OrdersInputHandler(InputView inputView, OutputView outputView) {
this.inputView = inputView;
this.outputView = outputView;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

핸들러 클래스가 적절하게 사용된거 같아 인상적이네요. 핸들러를 사용하면서 Controller에 inputView가 사용안되는데 Controller가 inputView에 의존하지않고 핸들러에만 의존하는 건 어떨까요?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오 그러네요!!! @.@ 감사합니다 :)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

두 핸들러 모두 같은 로직을 처리하고 있으니 이걸 인터페이스로 만들어서 상속할 수도 있지 않을까요!

Comment on lines 18 to 32
CHRISTMAS_D_DAY(
"크리스마스 디데이 할인",
date -> date >= EVENT_START && date <= CHRISTMAS_EVENT_END,
BASE_PRICE_CONDITION,
orders -> true,
(date, orders) -> BASE_DISCOUNT + calculatePassedDays(date) * CHRISTMAS_RATE),
WEEKDAYS(
"평일 할인",
date -> date >= EVENT_START && date <= CHRISTMAS_EVENT_END && (date % 7 >= 3 || date % 7 == 0),
BASE_PRICE_CONDITION,
orders -> orders.existsOrderItemByCategory(DESSERT),
(date, orders) -> orders.countOrderItemByCategory(DESSERT) * DAILY_RATE),
WEEKENDS(
"주말 할인",
date -> date >= EVENT_START && date <= EVENT_END && (date % 7 == 1 || date % 7 == 2),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

date 객체를 전달하고 date 메소드로 로직을 넣는 것이 더 깔끔할 거 같아요

Copy link
Owner Author

@jisu-om jisu-om Nov 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Arachneee
findByCondition에서 VisitingDate , Orders 를 전달받아 해당되는 이벤트를 찾아내는데
Date 라는 객체를 새로 만들어서... 그 안에 date 관련 로직을 넣으라는 말씀이실까요?
이해가 잘 안되어서... 혹시 가능하다면 예시 코드를 보여주실 수 있으실까요? :)

Comment on lines 10 to 12
public static final long GIVE_AWAY_PRICE_CONDITION = 120_000L;
public static final long GIVE_AWAY_PRICE = MenuItem.샴페인.getPrice();
public static final String GIVE_AWAY_ITEM = MenuItem.샴페인.name();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

샴페인 증정이 하드코딩된 느낌이 듭니다.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

억 맞습니다

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Constants를 Enum으로 관리하지 않고 final로 관리했을때
enum 대비 어떤 이점이 있을 수 있을까요?

Comment on lines 6 to 9
STAR("별", 5_000L, 10_000L),
TREE("트리", 10_000L, 20_000L),
SANTA("산타", 20_000L, Long.MAX_VALUE),
NONE("없음", 0L, 5_000L);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maxCondition이 다음 타입의 minCondition이랑 같아서 중복되는 것 같습니다.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

맞네요.. 이렇게 말고 다르게 처리하도록 바꿔야겠어요!! :)

Comment on lines 13 to 23
public class OrdersValidationUtils {
private static final int MAXIMUM_ORDER_TOTAL_QUANTITY = 20;

public static void validateDuplicates(List<OrderItem> orders) {
Set<MenuItem> uniqueOrders = orders.stream()
.map(OrderItem::provideMenuItem)
.collect(Collectors.toSet());
if (orders.size() != uniqueOrders.size()) {
throw new IllegalArgumentException(INVALID_ORDERS.getMessage());
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

검증로직을 분리한 이유가 있으신가요?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Arachneee
OrdersValidationUtilsOrderItemValidationUtils 로 나눈 이유를 말씀하실까요?
섞이는게 싫어서 분리했는데 지금와서 생각해보니 굳이 분리할 필요가 없었을 수도 있겠다 싶어요
OrderItem 은 Orders 를 구성하는(?) 객체이기도 해서 하나의 ValidationUtils 로 묶어도 되겠네요

Copy link

@junslog junslog left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

지수님! 너무 깔끔하게 잘 짜신 코드라 생각합니다. 많이 배워가요 :)
코멘트 드릴게 없어서 몇번씩 보면서 쥐어 짜냈네요!
4주차까지 달려오느라 고생하셨고, 앞으로도 같이 성장해나가요ㅎㅎ

public enum BadgeCondition {
STAR("별", 5_000L, 10_000L),
TREE("트리", 10_000L, 20_000L),
SANTA("산타", 20_000L, Long.MAX_VALUE),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍👍👍 명시적으로 Long.MAX_VALUE 로 표현한게 인상깊어요

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

세번째 필드는 굳이 안써도 되었을 것 같아요ㅠㅠ ㅎㅎㅎ

.filter(badge ->
totalBenefitAmount >= badge.getMinCondition() && totalBenefitAmount < badge.getMaxCondition())
.findFirst()
.orElse(NONE);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 로직에서는 음수인 값이( 입력 예외 상황 ) 들어오면 배지가 없음을 나타내는 식으로 구현하셨는데,
이 때 예외 값도 던져주고 호출하는 쪽에서 처리해줘도 좋았을 거 같아요!
물론 안정적이고 너무 좋은 코드라 생각합니다.

import java.util.function.Predicate;

import static christmas.constants.DateConstants.*;
import static christmas.constants.DiscountConstants.*;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

지금은 큰 상관 없지만 와일드카드 import 는 지양하는게 어떨까요?

public String getEventName() {
return eventName;
}
} No newline at end of file
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이벤트를 이렇게 Enum으로 싹다 모아서 관리하니 너무 보기 편하고 응집되어 있는 코드 느낌이 나네요.
배워갑니다!

public static final int EVENT_START = 1;
public static final int EVENT_END = 31;
public static final int CHRISTMAS_EVENT_END = 25;
} No newline at end of file
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Enum으로 관리하지 않고 클래스로 관리한 이유가 궁금합니다!

}

public List<OrderItemDto> getOrderItemDtos() {
return List.copyOf(orderItemDtos);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이미 정적 팩토리 메서드 형식으로 생성될 때
.toList() 로 불변으로 만들어 두었는데, List.copyOf() 사용하는 이유가 있을까요?

public static void validateDuplicates(List<OrderItem> orders) {
Set<MenuItem> uniqueOrders = orders.stream()
.map(OrderItem::provideMenuItem)
.collect(Collectors.toSet());
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아래와 같이 불변 컬렉션으로 만드는 건 어떨까요?
.collect(Collectors.toUnmodifiableSet());

}

private static void validateEmpty(String input) {
if (input == null || input.isEmpty()) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

input이 null일 경우는 언제일까요?

}

public void printResultStart(int date) {
System.out.printf((RESULT_SRART_FORMAT) + "%n", date);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'%n' 을 상수로 관리하는것도 좋을 것 같습니다.


public class VisitingDateTest {
@DisplayName("1 이상 31 이하이면 방문 날짜 정상 생성된다.")
@Test
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@valuesource 를 사용하시지 않은 이유가 있을까요?

Copy link

@kkonii kkonii left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

지수 님 고생많으셨어요!
그동안의 피드백들을 코드에 많이 녹여내신 게 보여요🔥
배울 점이 많았던 리뷰였습니다!

Comment on lines 11 to 18
public class OrdersInputHandler {
private final InputView inputView;
private final OutputView outputView;

private OrdersInputHandler(InputView inputView, OutputView outputView) {
this.inputView = inputView;
this.outputView = outputView;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

두 핸들러 모두 같은 로직을 처리하고 있으니 이걸 인터페이스로 만들어서 상속할 수도 있지 않을까요!

Comment on lines +24 to +26
public OrderItem toOrderItem() {
return OrderItem.of(menuName, quantity);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

동사 기반의 이름 규칙!

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

view 패키지로 분류하신 게 인상깊어요 :)

import christmas.view.InputView;
import christmas.view.OutputView;

public class ChristmasController {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 컨트롤러에 모든 비즈니스 로직의 운영 책임이 몰려있군요!
그렇다면, 컨트롤러 Layer에서 단위테스트를 계획하게 된다면, 문제가 발생하지 않을까요?
단위테스트를 계획했지만, 실상 통합테스트 환경에서 테스트하게 되니까요!

Comment on lines 20 to 29
public VisitingDate createVisitingDate() {
while (true) {
try {
int input = inputView.readVisitingDate();
return VisitingDate.from(input);
} catch (IllegalArgumentException e) {
outputView.printError(e.getMessage());
}
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

공통된 ExceptionHandling 로직을 제네릭 또는 인터페이스로 추상화해서 활용해보면 좋을 것 같아요

(date, orders) -> BASE_DISCOUNT + calculatePassedDays(date) * CHRISTMAS_RATE),
WEEKDAYS(
"평일 할인",
date -> date >= EVENT_START && date <= CHRISTMAS_EVENT_END && (date % 7 >= 3 || date % 7 == 0),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분도 메소드로 한번 더 분리해주면 좋을 것 같아요.
가령 isWeekDay()라는 메소드로 분리해준다면,
해당 메소드의 기능을 직관적으로 파악할 수 있겠군요!

Comment on lines 49 to 53
private final String eventName;
private final Predicate<Integer> dateCondition;
private final long priceCondition;
private final Function<Orders, Boolean> itemCondition;
private final BiFunction<VisitingDate, Orders, Long> benefitCalculator;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

멤버변수 5개..!

우아한테크코스 클린코드 컨벤션에서 3개 이상의 인스턴스 변수 사용을 금지하고 있습니다!
이 부분은 Enum 값 분리를 통해 책임을 분산시켜주면 좋을 것 같아요!

Comment on lines +9 to +20
양송이수프(APPETIZER, 6_000L),
타파스(APPETIZER, 5_500L),
시저샐러드(APPETIZER, 8_000L),
티본스테이크(MAIN, 55_000L),
바비큐립(MAIN, 54_000L),
해산물파스타(MAIN, 35_000L),
크리스마스파스타(MAIN, 25_000L),
초코케이크(DESSERT, 15_000L),
아이스크림(DESSERT, 5_000L),
제로콜라(DRINK, 3_000L),
레드와인(DRINK, 60_000L),
샴페인(DRINK, 25_000L);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오버플로우를 고려하셔서 long Type으로 정의하신 걸까요?
20개가 최대 주문이기에, 오버플로우가 날 것이라 예견되지는 않지만,
추후 변화에 따른 사이드 이펙트에 대응하기에 적절한 설계인 것 같아 인상적이네요!

Copy link

@h-beeen h-beeen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

안녕하세요! 지수님!
4주간 정말 고생 많으셨습니다!
제 코드리뷰가 마지막 유종의 미를 거두는데,
조금이나마 도움이 되길 바라는 마음에
꼼꼼하게 코드리뷰를 진행했답니다 :)

4주간 너무 고생 많으셨고
추후 최종 코테에서 뵙길 바래요.

감사합니다 :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants