ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,32 @@
+package christmas.domain;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import java.math.BigDecimal;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class BadgeTest {
+
+    @Test
+    @DisplayName("산타_뱃지_경계값_테스트")
+    void getBadge_SantaBadgeAmount_ShouldReturnSantaBadge() {
+        BigDecimal santaBadgeAmount = new BigDecimal(20000);
+        assertEquals(Badge.SANTA_BADGE, Badge.getBadge(santaBadgeAmount));
+    }
+
+    @Test
+    @DisplayName("트리_뱃지_경계값_테스트")
+    void getBadge_BetweenTreeAndSantaAmount_ShouldReturnTreeBadge() {
+        BigDecimal betweenTreeAndSantaAmount = new BigDecimal(15000);
+        assertEquals(Badge.TREE_BADGE, Badge.getBadge(betweenTreeAndSantaAmount));
+    }
+
+    @Test
+    @DisplayName("별_뱃지_경계값_테스트")
+    void getBadge_StarBadgeAmount_ShouldReturnStarBadge() {
+        BigDecimal starBadgeAmount = new BigDecimal(5000);
+        assertEquals(Badge.STAR_BADGE, Badge.getBadge(starBadgeAmount));
+    }
+}

리뷰: import static org.junit.jupiter.api.Assertions.*; 를 사용하기보다 더 간편하게 테스트를 할 수 있는 import static org.junit.jupiter.api.Assertions.*;를 사용해 보는 건 어떨까요?  
https://jwkim96.tistory.com/168
@@ -0,0 +1,36 @@
+package christmas.domain;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import java.math.BigDecimal;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class BillTest {
+    Order testOrder = new Order();
+
+    @Test
+    @DisplayName("전체_가격_계산_테스트")
+    public void testBillTotalPriceCalculation() {
+        testOrder.takeOrder("양송이수프-1,티본스테이크-2,초코케이크-3");
+        Bill bill = new Bill(testOrder);
+
+        BigDecimal expectedTotalPrice = BigDecimal
+                .valueOf(1).multiply(Menu.APPETIZER_MUSHROOM_SOUP.getPrice())
+                .add(BigDecimal.valueOf(2).multiply(Menu.MAIN_T_BONE_STEAK.getPrice()))
+                .add(BigDecimal.valueOf(3).multiply(Menu.DESSERT_CHOCO_CAKE.getPrice()));
+
+        assertEquals(expectedTotalPrice, bill.getTotalPrice());
+    }
+
+    @Test
+    @DisplayName("가격_할인_적용_테스트")
+    public void testBillDiscountPrice() {
+        testOrder.takeOrder("양송이수프-5"); //30,000원
+        Bill bill = new Bill(testOrder);
+        bill.discountPrice(new BigDecimal(10000));  //30,000 - 10,000 원
+
+        assertEquals(bill.getTotalPrice(), new BigDecimal(20000));
+    }
+}

리뷰: given when then 패턴을 사용해서 테스트코드를 나눠 보는 건 어떨까요?
https://brunch.co.kr/@springboot/292
@@ -0,0 +1,69 @@
+package christmas.controller;
+
+import christmas.domain.Bill;
+import christmas.domain.Order;
+import christmas.domain.ReservationDay;
+import christmas.view.InputView;
+import christmas.view.OutputView;
+
+public class PlannerController {
+    private final Order order;
+    private final ReservationDay reservationDay;
+    private final EventController eventController;
+
+    private Bill bill;
+
+    public PlannerController(EventController eventController) {
+        this.eventController = eventController;
+        this.reservationDay = new ReservationDay();
+        this.order = new Order();
+    }
+
+    public void run() {
+        OutputView.printStartMessage();
+
+        try {
+            inputDay();
+            inputOrder();
+        } catch (IllegalArgumentException e) {
+            System.out.println(e.getMessage());
+            return;
+        }
+
+        processEvent();
+    }
+
+    private void inputDay() {
+        while (true) {
+            try {
+                String dayInput = InputView.inputDate();
+                reservationDay.reserveDay(dayInput);
+                break;
+            } catch (IllegalArgumentException e) {
+                System.out.println(e.getMessage());
+            }
+        }
+    }
+
+    private void inputOrder() {
+        while (true) {
+            try {
+                String orderInput = InputView.inputOrder();
+                order.takeOrder(orderInput.replace(" ", ""));
+                break;
+            } catch (IllegalArgumentException e) {
+                System.out.println(e.getMessage());
+            }
+        }
+    }
+
+    private void processEvent() {
+        OutputView.printEventPreviewMessage(reservationDay.getDay());
+        OutputView.printOrderDetails(order);
+
+        bill = new Bill(order);
+
+        eventController.applyEvent(reservationDay, order, bill);
+        eventController.showEventDiscountDetails(bill);
+    }
+}

리뷰: 예외 메시지 출력도 `OutputView` 에서 처리하는건 어떨까요?
@@ -0,0 +1,69 @@
+package christmas.controller;
+
+import christmas.domain.Bill;
+import christmas.domain.Order;
+import christmas.domain.ReservationDay;
+import christmas.view.InputView;
+import christmas.view.OutputView;
+
+public class PlannerController {
+    private final Order order;
+    private final ReservationDay reservationDay;
+    private final EventController eventController;
+
+    private Bill bill;
+
+    public PlannerController(EventController eventController) {
+        this.eventController = eventController;
+        this.reservationDay = new ReservationDay();
+        this.order = new Order();
+    }
+
+    public void run() {
+        OutputView.printStartMessage();
+
+        try {
+            inputDay();
+            inputOrder();
+        } catch (IllegalArgumentException e) {
+            System.out.println(e.getMessage());
+            return;
+        }
+
+        processEvent();
+    }
+
+    private void inputDay() {
+        while (true) {
+            try {
+                String dayInput = InputView.inputDate();
+                reservationDay.reserveDay(dayInput);
+                break;
+            } catch (IllegalArgumentException e) {
+                System.out.println(e.getMessage());
+            }
+        }
+    }
+
+    private void inputOrder() {
+        while (true) {
+            try {
+                String orderInput = InputView.inputOrder();
+                order.takeOrder(orderInput.replace(" ", ""));
+                break;
+            } catch (IllegalArgumentException e) {
+                System.out.println(e.getMessage());
+            }
+        }
+    }
+
+    private void processEvent() {
+        OutputView.printEventPreviewMessage(reservationDay.getDay());
+        OutputView.printOrderDetails(order);
+
+        bill = new Bill(order);
+
+        eventController.applyEvent(reservationDay, order, bill);
+        eventController.showEventDiscountDetails(bill);
+    }
+}

리뷰: 필드가 생성자가 아닌 다른 메서드에서 초기화되는게 어색한 것 같습니다!
@@ -0,0 +1,35 @@
+package christmas.domain;
+
+import java.math.BigDecimal;
+
+public enum Badge {
+    SANTA_BADGE("산타", new BigDecimal(20000)),
+    TREE_BADGE("트리", new BigDecimal(10000)),
+    STAR_BADGE("별", new BigDecimal(5000));
+
+    private final String name;
+    private final BigDecimal standardAmount;
+
+    Badge(String name, BigDecimal standardAmount) {
+        this.name = name;
+        this.standardAmount = standardAmount;
+    }
+
+    public String getName() {
+        if (this == null) return "";
+        return name;
+    }
+
+    public static Badge getBadge(BigDecimal totalBenefit) {
+        if (totalBenefit.compareTo(SANTA_BADGE.standardAmount) >= 0) {
+            return SANTA_BADGE;
+        }
+        if (totalBenefit.compareTo(TREE_BADGE.standardAmount) >= 0) {
+            return TREE_BADGE;
+        }
+        if (totalBenefit.compareTo(STAR_BADGE.standardAmount) >= 0) {
+            return STAR_BADGE;
+        }
+        return null;
+    }
+}

리뷰: 이름이 `null`이 되는 경우가 있을까요?
@@ -0,0 +1,35 @@
+package christmas.domain;
+
+import java.math.BigDecimal;
+
+public enum Badge {
+    SANTA_BADGE("산타", new BigDecimal(20000)),
+    TREE_BADGE("트리", new BigDecimal(10000)),
+    STAR_BADGE("별", new BigDecimal(5000));
+
+    private final String name;
+    private final BigDecimal standardAmount;
+
+    Badge(String name, BigDecimal standardAmount) {
+        this.name = name;
+        this.standardAmount = standardAmount;
+    }
+
+    public String getName() {
+        if (this == null) return "";
+        return name;
+    }
+
+    public static Badge getBadge(BigDecimal totalBenefit) {
+        if (totalBenefit.compareTo(SANTA_BADGE.standardAmount) >= 0) {
+            return SANTA_BADGE;
+        }
+        if (totalBenefit.compareTo(TREE_BADGE.standardAmount) >= 0) {
+            return TREE_BADGE;
+        }
+        if (totalBenefit.compareTo(STAR_BADGE.standardAmount) >= 0) {
+            return STAR_BADGE;
+        }
+        return null;
+    }
+}

리뷰: `null` 반환이 위험한 것 같아요! `NONE` 과 같은 인스턴스를 추가하여 이를 반환하는건 어떨까요?
@@ -0,0 +1,32 @@
+package christmas.domain;
+
+import java.math.BigDecimal;
+import java.util.Map;
+
+public class Bill {
+    private final Map<Menu, Integer> orderDetails;
+
+    private BigDecimal totalPrice = new BigDecimal(0);
+
+    public Bill(Order order) {
+        this.orderDetails = order.getOrderDetails();
+        calculateTotalPrice();
+    }
+
+    private void calculateTotalPrice() {
+        for (Map.Entry<Menu, Integer> entry : orderDetails.entrySet()) {
+            Menu menu = entry.getKey();
+            int count = entry.getValue();
+            BigDecimal price = menu.getPrice().multiply(BigDecimal.valueOf(count));
+            totalPrice = totalPrice.add(price);
+        }
+    }
+
+    public void discountPrice(BigDecimal discountValue) {
+        totalPrice = totalPrice.subtract(discountValue);
+    }
+
+    public BigDecimal getTotalPrice() {
+        return totalPrice;
+    }
+}

리뷰: 메뉴 입력 시 이름, 수량 외에 다른 값이 추가되는걸 감안하여 `Map<K, V>` 대신 별도의 클래스 사용은 어떨까요?
@@ -0,0 +1,29 @@
+package christmas.domain;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+public class DecemberCalendar {
+    private final List<Integer> weekdays = Stream.of(3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 17, 18, 19, 20, 21, 24, 25, 26, 27, 28, 31)
+            .collect(Collectors.toList());
+    private final List<Integer> weekends = Stream.of(1, 2, 8, 9, 15, 16, 22, 23, 29, 30)
+            .collect(Collectors.toList());
+    private final List<Integer> specialDays = Stream.of(3, 10, 17, 24, 25, 31)
+            .collect(Collectors.toList());
+
+    public boolean isWeekday(int day) {
+        if (weekdays.contains(day)) return true;
+        return false;
+    }
+
+    public boolean isWeekend(int day) {
+        if (weekends.contains(day)) return true;
+        return false;
+    }
+
+    public boolean isSpecialDay(int day) {
+        if (specialDays.contains(day)) return true;
+        return false;
+    }
+}

리뷰: `Arrays.asList()` 대신 Stream을 사용하신 이유가 있을까요?
@@ -0,0 +1,62 @@
+package christmas.domain;
+
+import christmas.utils.ErrorMessage;
+import christmas.parser.Parser;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static christmas.domain.validator.OrderValidator.*;
+
+public class Order {
+    private final Map<Menu, Integer> orderDetails = new HashMap<>();
+    private int quantity = 0;
+
+    public void takeOrder(String input) {
+        List<String> eachOrderedMenu = Parser.inputToEachOrderedMenu(input);
+        try {
+            eachOrderedMenu.forEach(menuInformation -> processOrderedMenu(menuInformation));
+
+            calculateMenuQuantity();
+            validateOnlyBeverage(orderDetails);
+            validateMaximumQuantity(quantity);
+        } catch (Exception e) {
+            throw new IllegalArgumentException(ErrorMessage.INPUT_ORDER_ERROR_MESSAGE.getMessage());
+        }
+    }
+
+    private void processOrderedMenu(String menuInformation) {
+        String[] menuNameAndNumber = Parser.inputToMenu(menuInformation);
+        String menuName = menuNameAndNumber[0];
+        int menuNumber = Parser.stringToIntPaser(menuNameAndNumber[1]);
+        Menu orderedMenu = Menu.findMenu(menuName);
+
+        validateDuplicateMenu(orderDetails, orderedMenu);
+        validateOrderFormat(menuNameAndNumber);
+        orderDetails.put(orderedMenu, menuNumber);
+    }
+
+    private void calculateMenuQuantity() {
+        for (int count : orderDetails.values()) {
+            quantity += count;
+        }
+    }
+
+    public String toString() {
+        StringBuilder output = new StringBuilder();
+        for (Map.Entry<Menu, Integer> entry : orderDetails.entrySet()) {
+            Menu menu = entry.getKey();
+            int count = entry.getValue();
+            output.append(menu.getMenuItem().getName())
+                    .append(" ")
+                    .append(count)
+                    .append("개\n");
+        }
+        return output.toString();
+    }
+
+    public Map<Menu, Integer> getOrderDetails() {
+        return orderDetails;
+    }
+}

리뷰: `int`의 경우 필드에서 값을 대입하지 않아도 객체 생성시 0으로 초기화 됩니다!
@@ -0,0 +1,62 @@
+package christmas.domain;
+
+import christmas.utils.ErrorMessage;
+import christmas.parser.Parser;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static christmas.domain.validator.OrderValidator.*;
+
+public class Order {
+    private final Map<Menu, Integer> orderDetails = new HashMap<>();
+    private int quantity = 0;
+
+    public void takeOrder(String input) {
+        List<String> eachOrderedMenu = Parser.inputToEachOrderedMenu(input);
+        try {
+            eachOrderedMenu.forEach(menuInformation -> processOrderedMenu(menuInformation));
+
+            calculateMenuQuantity();
+            validateOnlyBeverage(orderDetails);
+            validateMaximumQuantity(quantity);
+        } catch (Exception e) {
+            throw new IllegalArgumentException(ErrorMessage.INPUT_ORDER_ERROR_MESSAGE.getMessage());
+        }
+    }
+
+    private void processOrderedMenu(String menuInformation) {
+        String[] menuNameAndNumber = Parser.inputToMenu(menuInformation);
+        String menuName = menuNameAndNumber[0];
+        int menuNumber = Parser.stringToIntPaser(menuNameAndNumber[1]);
+        Menu orderedMenu = Menu.findMenu(menuName);
+
+        validateDuplicateMenu(orderDetails, orderedMenu);
+        validateOrderFormat(menuNameAndNumber);
+        orderDetails.put(orderedMenu, menuNumber);
+    }
+
+    private void calculateMenuQuantity() {
+        for (int count : orderDetails.values()) {
+            quantity += count;
+        }
+    }
+
+    public String toString() {
+        StringBuilder output = new StringBuilder();
+        for (Map.Entry<Menu, Integer> entry : orderDetails.entrySet()) {
+            Menu menu = entry.getKey();
+            int count = entry.getValue();
+            output.append(menu.getMenuItem().getName())
+                    .append(" ")
+                    .append(count)
+                    .append("개\n");
+        }
+        return output.toString();
+    }
+
+    public Map<Menu, Integer> getOrderDetails() {
+        return orderDetails;
+    }
+}

리뷰: 도메인이 View에 의존하는 것 같습니다! `OutputView`에서 `Order`에 대한 값을 가져온 후 출력하는건 어떨까요?
@@ -1,3 +1,5 @@
+import {ReviewLikeButtonProps} from "@review-canvas/theme";
+
 export type Shadow = 'NONE' | 'SMALL' | 'MEDIUM' | 'LARGE';
 export type FocusAreaLayout = 'BEST_REVIEW_TOP' | 'BEST_REVIEW_BOTTOM' | 'BEST_REVIEW_LEFT' | 'BEST_REVIEW_RIGHT';
 export type ReviewAreaLayout = 'REVIEW_TOP' | 'REVIEW_BOTTOM' | 'REVIEW_LEFT' | 'REVIEW_RIGHT';
@@ -6,6 +8,7 @@ export type AlignmentPosition = 'LEFT' | 'CENTER' | 'RIGHT';
 export type DetailViewType = 'SPREAD' | 'MODAL';
 export type PagingType = 'PAGE_NUMBER' | 'SEE_MORE_SCROLL';
 export type FilterType = 'LIST' | 'DROPDOWN';
+export type ReviewLikeButtonType = 'NONE' | 'THUMB_UP_WITH_TEXT' | 'THUMB_UP';
 
 export interface Margin {
   left: string;
@@ -43,11 +46,11 @@ export interface Round {
 }
 
 export interface ReviewLike {
-  buttonType: string;
-  iconColor: string;
-  textColor: string;
   buttonBorderColor: string;
   buttonRound: Round;
+  buttonType: ReviewLikeButtonType;
+  iconColor: string;
+  textColor: string;
 }
 
 export interface ReviewLayout {

리뷰: 전체적으로 Prettier가 적용이 안 되어 있는 것 같은데 Prettier 전체적으로 적용해 주세요!
@@ -1,3 +1,5 @@
+import {ReviewLikeButtonProps} from "@review-canvas/theme";
+
 export type Shadow = 'NONE' | 'SMALL' | 'MEDIUM' | 'LARGE';
 export type FocusAreaLayout = 'BEST_REVIEW_TOP' | 'BEST_REVIEW_BOTTOM' | 'BEST_REVIEW_LEFT' | 'BEST_REVIEW_RIGHT';
 export type ReviewAreaLayout = 'REVIEW_TOP' | 'REVIEW_BOTTOM' | 'REVIEW_LEFT' | 'REVIEW_RIGHT';
@@ -6,6 +8,7 @@ export type AlignmentPosition = 'LEFT' | 'CENTER' | 'RIGHT';
 export type DetailViewType = 'SPREAD' | 'MODAL';
 export type PagingType = 'PAGE_NUMBER' | 'SEE_MORE_SCROLL';
 export type FilterType = 'LIST' | 'DROPDOWN';
+export type ReviewLikeButtonType = 'NONE' | 'THUMB_UP_WITH_TEXT' | 'THUMB_UP';
 
 export interface Margin {
   left: string;
@@ -43,11 +46,11 @@ export interface Round {
 }
 
 export interface ReviewLike {
-  buttonType: string;
-  iconColor: string;
-  textColor: string;
   buttonBorderColor: string;
   buttonRound: Round;
+  buttonType: ReviewLikeButtonType;
+  iconColor: string;
+  textColor: string;
 }
 
 export interface ReviewLayout {

리뷰: Plugin 설치를 하면 완료인 줄 알았는데 설정에서 Manually로 바꾸어줘야 했군요... 현재 Prettier를 활성화 상태로 변경하였습니다. 전체 파일에 적용하여 다시 커밋하겠습니다!
@@ -0,0 +1,18 @@
+package com.codesquad.baseball09.controller;
+
+import java.text.SimpleDateFormat;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("hcheck")
+public class HealthCheckRestController {
+
+  private final static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+
+  @GetMapping
+  public String healthCheck() {
+    return dateFormat.format(System.currentTimeMillis());
+  }
+}

리뷰: Spring Boot Actuator 사용 검토해봅니다.
https://supawer0728.github.io/2018/05/12/spring-actuator/
@@ -0,0 +1,99 @@
+package com.codesquad.baseball09.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+
+public class BattingLog {
+
+  private Long id;
+  private Long gameId;
+  private Long playerId;
+  private int inning;
+  private Status status;
+
+  private BattingLog(Long id, Long gameId, Long playerId, int inning,
+      Status status) {
+    this.id = id;
+    this.gameId = gameId;
+    this.playerId = playerId;
+    this.inning = inning;
+    this.status = status;
+  }
+
+  public Long getId() {
+    return id;
+  }
+
+  public Long getGameId() {
+    return gameId;
+  }
+
+  public Long getPlayerId() {
+    return playerId;
+  }
+
+  public int getInning() {
+    return inning;
+  }
+
+  public Status getStatus() {
+    return status;
+  }
+
+  @JsonIgnore
+  public int getStatusInt() {
+    return status.getValue();
+  }
+
+  public static class Builder {
+
+    private Long id;
+    private Long gameId;
+    private Long playerId;
+    private int inning;
+    private Status status;
+
+    public Builder() {
+    }
+
+    public Builder id(Long id) {
+      this.id = id;
+      return this;
+    }
+
+    public Builder gameId(Long gameId) {
+      this.gameId = gameId;
+      return this;
+    }
+
+    public Builder playerId(Long playerId) {
+      this.playerId = playerId;
+      return this;
+    }
+
+    public Builder inning(int inning) {
+      this.inning = inning;
+      return this;
+    }
+
+    public Builder status(int value) {
+      this.status = Status.of(value);
+      return this;
+    }
+
+    public BattingLog build() {
+      return new BattingLog(id, gameId, playerId, inning, status);
+    }
+  }
+
+  @Override
+  public String toString() {
+    return new ToStringBuilder(this)
+        .append("id", id)
+        .append("gameId", gameId)
+        .append("playerId", playerId)
+        .append("inning", inning)
+        .append("status", status)
+        .toString();
+  }
+}

리뷰: 적절한 builder 사용 좋네요. 👍
@@ -0,0 +1,18 @@
+package com.codesquad.baseball09.controller;
+
+import java.text.SimpleDateFormat;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("hcheck")
+public class HealthCheckRestController {
+
+  private final static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+
+  @GetMapping
+  public String healthCheck() {
+    return dateFormat.format(System.currentTimeMillis());
+  }
+}

리뷰: 다음 프로젝트에 한번 적용해보겠습니다
@@ -0,0 +1,27 @@
+package racingcar.dao;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
+import racingcar.dao.entity.GameEntity;
+
+public class InsertGameDao {
+
+    private final SimpleJdbcInsert insertActor;
+
+    public InsertGameDao(final JdbcTemplate jdbcTemplate) {
+        insertActor = new SimpleJdbcInsert(jdbcTemplate)
+                .withTableName("game")
+                .usingGeneratedKeyColumns("game_id");
+    }
+
+    public GameEntity insert(final GameEntity gameEntity) {
+        final Map<String, Object> parameters = new HashMap<>(3);
+        parameters.put("game_id", gameEntity.getGameId().getValue());
+        parameters.put("trial_count", gameEntity.getTrialCount());
+        parameters.put("created_at", gameEntity.getCreatedAt());
+
+        return new GameEntity(insertActor.executeAndReturnKey(parameters).intValue(), gameEntity.getTrialCount());
+    }
+}

리뷰: 이거 외 않 늠?
@@ -0,0 +1,97 @@
+package christmas.config;
+
+import christmas.controller.EventController;
+import christmas.exception.RetryHandler;
+import christmas.service.EventService;
+import christmas.service.MenuService;
+import christmas.validator.InputValidator;
+import christmas.view.input.ConsoleReader;
+import christmas.view.input.InputView;
+import christmas.view.input.Reader;
+import christmas.view.output.ConsoleWriter;
+import christmas.view.output.OutputView;
+import christmas.view.output.Writer;
+
+public class AppConfig {
+    private static AppConfig appConfig;
+    private EventController eventController;
+    private EventService eventService;
+    private MenuService menuService;
+    private InputValidator inputValidator;
+    private RetryHandler retryHandler;
+    private InputView inputView;
+    private OutputView outputView;
+    private Reader reader;
+    private Writer writer;
+
+    public static AppConfig getInstance() {
+        if (appConfig == null) {
+            appConfig = new AppConfig();
+        }
+        return appConfig;
+    }
+
+    public EventController eventController() {
+        if (eventController == null) {
+            eventController = new EventController(discountService(), menuService(),
+                    inputView(), outputView(), exceptionHandler());
+        }
+        return eventController;
+    }
+
+    public EventService discountService() {
+        if (eventService == null) {
+            eventService = new EventService();
+        }
+        return eventService;
+    }
+
+    public MenuService menuService() {
+        if (menuService == null) {
+            menuService = new MenuService();
+        }
+        return menuService;
+    }
+
+    public RetryHandler exceptionHandler() {
+        if (retryHandler == null) {
+            retryHandler = new RetryHandler(outputView());
+        }
+        return retryHandler;
+    }
+
+    public InputView inputView() {
+        if (inputView == null) {
+            inputView = new InputView(reader(), inputValidator());
+        }
+        return inputView;
+    }
+
+    public InputValidator inputValidator() {
+        if (inputValidator == null) {
+            inputValidator = new InputValidator();
+        }
+        return inputValidator;
+    }
+
+    public Reader reader() {
+        if (reader == null) {
+            reader = new ConsoleReader();
+        }
+        return reader;
+    }
+
+    public OutputView outputView() {
+        if (outputView == null) {
+            outputView = new OutputView(writer());
+        }
+        return outputView;
+    }
+
+    public Writer writer() {
+        if (writer == null) {
+            writer = new ConsoleWriter();
+        }
+        return writer;
+    }
+}

리뷰: 전체적으로 null 체크를 해주신 이유가 궁금합니다!