ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,117 @@
+# Created by https://www.toptal.com/developers/gitignore/api/intellij
+# Edit at https://www.toptal.com/developers/gitignore?templates=intellij
+
+### Intellij ###
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# AWS User-specific
+.idea/**/aws.xml
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn.  Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# SonarLint plugin
+.idea/sonarlint/
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+### Intellij Patch ###
+# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
+
+# *.iml
+# modules.xml
+# .idea/misc.xml
+# *.ipr
+
+# Sonarlint plugin
+# https://plugins.jetbrains.com/plugin/7973-sonarlint
+.idea/**/sonarlint/
+
+# SonarQube Plugin
+# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
+.idea/**/sonarIssues.xml
+
+# Markdown Navigator plugin
+# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
+.idea/**/markdown-navigator.xml
+.idea/**/markdown-navigator-enh.xml
+.idea/**/markdown-navigator/
+
+# Cache file creation bug
+# See https://youtrack.jetbrains.com/issue/JBR-2257
+.idea/$CACHE_FILE$
+
+# CodeStream plugin
+# https://plugins.jetbrains.com/plugin/12206-codestream
+.idea/codestream.xml
+
+# Azure Toolkit for IntelliJ plugin
+# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij
+.idea/**/azureSettings.xml
+
+# End of https://www.toptal.com/developers/gitignore/api/intellij
\ No newline at end of file

리뷰: 이쪽 부분 옵션으로 어떠한 내역 추가했는지 알려주실 수 있을까요?
@@ -0,0 +1,9 @@
+package input;
+
+public interface Input {
+
+  public String selectInput();
+
+  public String expressionInput();
+
+}

리뷰: 제너릭을 사용한 이유가 있을까요? Input은 바이트 혹은 문자열로 들어올텐데 어떠한 생각으로 제너릭을 사용했는지가 궁금합니당
@@ -0,0 +1,9 @@
+
+import calculator.Calculator;
+
+public class main {
+
+  public static void main(String[] args) {
+    new Calculator().run();
+  }
+}

리뷰: IOException이 애플리케이션이 실행되는 부분까지 전파되었습니다. 이러면 입출력 예외 발생 시 프로그램이 비정상적으로 종료될 가능성이 있겠네요.
@@ -0,0 +1,20 @@
+package repository;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class Repository {
+
+  private List<String> list = new ArrayList<>();
+
+  public void store(String expression, String result) {
+    StringBuilder formattedExpression = new StringBuilder(expression).append(" = ").append(result);
+    list.add(formattedExpression.toString());
+
+  }
+
+  public List<String> getResult() {
+    return new ArrayList<>(list);
+  }
+
+}

리뷰: 저장소를 List로 표현하신 이유를 알 수 있을까요? 동일한 연산식이여도 리스트에 추가하는게 맞을까요? 메모리를 최대한 아낄 수 있는 방법을 생각해보는게 좋을 것 같습니다
@@ -0,0 +1,19 @@
+import convertor.InfixToPostfixConverter;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+
+public class ChangToPostfixTest {
+
+  @ParameterizedTest
+  @DisplayName("중위 표기식 후위 표기식 변환")
+  @CsvSource(value = {"7 * 4 / 2 + 3 - 1 :'7 4 * 2 / 3 + 1 - '",
+      "9 - 5 * 2 + 6 / 3: '9 5 2 * - 6 3 / + '"}, delimiter = ':')
+  public void InfixToPostfixTest(String infixExpression, String postFinExpression) {
+    InfixToPostfixConverter calculator = new InfixToPostfixConverter();
+    String result = calculator.changeToPostFix(infixExpression);
+    Assertions.assertEquals(postFinExpression, result);
+  }
+
+}

리뷰: 테스트 코드 내용이 많이 부실합니다. 단위 테스트에 대해서 생각해보셨으면 좋겠습니다.
적어도 작성하신 코드에서 View 영역을 제외한 나머지 영역은 테스트가 가능합니다.
@@ -0,0 +1,22 @@
+package christmas.config;
+
+public enum ErrorMessage {
+    WRONG_DATE("유효하지 않은 날짜입니다."),
+    WRONG_ORDER("유효하지 않은 주문입니다."),
+    OVER_MAX_ORDER("메뉴는 한 번에 최대 20개까지만 주문할 수 있습니다."),
+    ONLY_DRINK_ORDER("음료만 주문할 수 없습니다.");
+
+    private static final String PREFIX = "[ERROR]";
+    private static final String SUFFIX = "다시 입력해 주세요.";
+    private static final String DELIMITER = " ";
+
+    private final String message;
+
+    ErrorMessage(String message) {
+        this.message = message;
+    }
+
+    public String getMessage() {
+        return String.join(DELIMITER, PREFIX, message, SUFFIX);
+    }
+}

리뷰: 해당 에러메시지는 왜 enum을 사용하셨나요? 
상수를 만드는 데 일반 클래스와 enum 클래스를 사용하신 기준이 궁금합니다!
@@ -0,0 +1,57 @@
+package christmas.controller;
+
+import christmas.domain.*;
+import christmas.dto.OrderDTO;
+import christmas.util.IntParser;
+import christmas.util.OrderParser;
+import christmas.util.RetryExecutor;
+import christmas.view.InputView;
+import christmas.view.OutputView;
+
+import java.util.List;
+
+public class EventController {
+    private Bill bill;
+    private EventHandler eventHandler;
+    private final InputView inputView;
+    private final OutputView outputView;
+
+    public EventController(InputView inputView, OutputView outputView) {
+        this.inputView = inputView;
+        this.outputView = outputView;
+    }
+
+    public void run() {
+        outputView.printHelloMessage();
+        RetryExecutor.execute(this::setBill, outputView::printErrorMessage);
+        RetryExecutor.execute(this::setOrder, error -> {
+            outputView.printErrorMessage(error);
+            bill.clearOrder();
+        });
+        printResult();
+    }
+
+    private void setBill() {
+        String input = inputView.inputDate().trim();
+        bill = Bill.from(IntParser.parseIntOrThrow(input));
+        eventHandler = EventHandler.from(bill);
+    }
+
+    private void setOrder() {
+        String input = inputView.inputOrder();
+        List<OrderDTO> orders = OrderParser.parseOrderOrThrow(input);
+        orders.forEach(it -> bill.add(Order.create(it.menuName(), it.count())));
+        bill.validateOnlyDrink();
+    }
+
+    private void printResult() {
+        outputView.printEventPreviewTitle(bill.getDateValue());
+        outputView.printOrder(bill.getAllOrders());
+        outputView.printTotalPrice(bill.getTotalPrice());
+        outputView.printGift(eventHandler.hasChampagneGift());
+        outputView.printAllBenefit(eventHandler.getAllBenefit());
+        outputView.printBenefitPrice(eventHandler.getTotalBenefitPrice());
+        outputView.printAfterDiscountPrice(bill.getTotalPrice() - eventHandler.getTotalDiscountPrice());
+        outputView.printBadge(Badge.getBadgeNameWithBenefitPrice(eventHandler.getTotalBenefitPrice()));
+    }
+}

리뷰: 오.. 이런 방법으로 사용하는 함수형 인터페이스는 처음 보는 것 같아요 배울 부분이 많네요 ㅠ 👍👍
@@ -0,0 +1,26 @@
+package christmas.domain;
+
+import java.util.Arrays;
+
+public enum Badge {
+    SANTA("산타", 20000),
+    TREE("트리", 10000),
+    STAR("별", 5000),
+    NOTHING("없음", 0);
+
+    private final String badgeName;
+    private final int threshold;
+
+    Badge(String badgeName, int threshold) {
+        this.badgeName = badgeName;
+        this.threshold = threshold;
+    }
+
+    public static String getBadgeNameWithBenefitPrice(int benefitPrice) {
+        return Arrays.stream(Badge.values())
+                .filter(it -> it.threshold <= benefitPrice)
+                .map(it -> it.badgeName)
+                .findFirst()
+                .orElse(NOTHING.badgeName);
+    }
+}

리뷰: stream 사용을 잘하시는 것 같아요! 혹시 어떻게 공부하면 좋을지... 팁이 있으실까요?
@@ -0,0 +1,94 @@
+package christmas.domain;
+
+import christmas.config.Constant;
+import christmas.dto.OrderDTO;
+import christmas.config.ErrorMessage;
+import christmas.config.MenuType;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class Bill implements CheckEventDate {
+    private final List<Order> orders = new ArrayList<>();
+    private final Date date;
+
+    private Bill(Date date) {
+        this.date = date;
+    }
+
+    public static Bill from(int date) {
+        return new Bill(Date.from(date));
+    }
+
+    public Bill add(Order order) {
+        validateDuplicates(order);
+        orders.add(order);
+        validateMaxOrder();
+        return this;
+    }
+
+    private void validateDuplicates(Order newOrder) {
+        long duplicated = orders.stream()
+                .filter(it -> it.isSameMenu(newOrder))
+                .count();
+        if (duplicated != Constant.FILTER_CONDITION) {
+            throw new IllegalArgumentException(ErrorMessage.WRONG_ORDER.getMessage());
+        }
+    }
+
+    private void validateMaxOrder() {
+        if (Order.accumulateCount(orders) > Constant.MAX_ORDER) {
+            throw new IllegalArgumentException(ErrorMessage.OVER_MAX_ORDER.getMessage());
+        }
+    }
+
+    public void validateOnlyDrink() {
+        if (Order.accumulateCount(orders) == getTypeCount(MenuType.DRINK)) {
+            throw new IllegalArgumentException(ErrorMessage.ONLY_DRINK_ORDER.getMessage());
+        }
+    }
+
+    public void clearOrder() {
+        orders.clear();
+    }
+
+    public int getTotalPrice() {
+        return orders.stream()
+                .map(Order::getPrice)
+                .reduce(Constant.INIT_VALUE, Integer::sum);
+    }
+
+    public int getTypeCount(MenuType type) {
+        return Order.accumulateCount(orders, type);
+    }
+
+    public List<OrderDTO> getAllOrders() {
+        return orders.stream().map(Order::getOrder).toList();
+    }
+
+    // 아래는 Date 관련 method
+
+    @Override
+    public boolean isWeekend() {
+        return date.isWeekend();
+    }
+
+    @Override
+    public boolean isSpecialDay() {
+        return date.isSpecialDay();
+    }
+
+    @Override
+    public boolean isNotPassedChristmas() {
+        return date.isNotPassedChristmas();
+    }
+
+    @Override
+    public int timePassedSinceFirstDay() {
+        return date.timePassedSinceFirstDay();
+    }
+
+    public int getDateValue() {
+        return date.getDateValue();
+    }
+}

리뷰: 해당 리스트를 클래스로 만들어 선언 하는 것은 어떨까요?
@@ -0,0 +1,75 @@
+package christmas.domain;
+
+import christmas.config.Menu;
+import christmas.config.MenuType;
+
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+public enum Event {
+    CHRISTMAS(
+            "크리스마스 디데이 할인",
+            true,
+            Bill::isNotPassedChristmas,
+            bill -> bill.timePassedSinceFirstDay() * 100 + 1000
+    ),
+    WEEKDAY(
+            "평일 할인",
+            true,
+            bill -> !bill.isWeekend(),
+            bill -> bill.getTypeCount(MenuType.DESSERT) * 2023
+    ),
+    WEEKEND(
+            "주말 할인",
+            true,
+            Bill::isWeekend,
+            bill -> bill.getTypeCount(MenuType.MAIN) * 2023
+    ),
+    SPECIAL(
+            "특별 할인",
+            true,
+            Bill::isSpecialDay,
+            bill -> 1000
+    ),
+    CHAMPAGNE(
+            "증정 이벤트",
+            false,
+            bill -> bill.getTotalPrice() >= 120000,
+            bill -> Menu.CHAMPAGNE.getPrice()
+    );
+
+    private static final int ALL_EVENT_THRESHOLD = 10000;
+
+    private final String eventName;
+    private final boolean isDiscount;
+    private final Predicate<Bill> condition;
+    private final Function<Bill, Integer> benefit;
+
+    Event(
+            String eventName,
+            boolean isDiscount,
+            Predicate<Bill> condition,
+            Function<Bill, Integer> benefit
+    ) {
+        this.eventName = eventName;
+        this.isDiscount = isDiscount;
+        this.condition = condition;
+        this.benefit = benefit;
+    }
+
+    public String getEventName() {
+        return eventName;
+    }
+
+    public boolean isDiscount() {
+        return isDiscount;
+    }
+
+    public boolean checkCondition(Bill bill) {
+        return condition.test(bill) && bill.getTotalPrice() >= ALL_EVENT_THRESHOLD;
+    }
+
+    public int getBenefit(Bill bill) {
+        return benefit.apply(bill);
+    }
+}

리뷰: 오.. 할인 리스트를 enum 클래스를 만들어 선언한 것 되게 좋은 아이디어네요 👍👍
@@ -0,0 +1,38 @@
+package christmas.util;
+
+import christmas.config.ErrorMessage;
+
+public class IntParser {
+    private static final int MAX_STRING_LENGTH = 11;
+
+    private IntParser() {
+        // 인스턴스 생성 방지
+    }
+
+    public static int parseIntOrThrow(String numeric) {
+        validateNumericStringLength(numeric);
+        long parsed = parseLongOrThrow(numeric);
+        validateIntRange(parsed);
+        return Integer.parseInt(numeric);
+    }
+
+    private static long parseLongOrThrow(String numeric) {
+        try {
+            return Long.parseLong(numeric);
+        } catch (NumberFormatException e) {
+            throw new IllegalArgumentException(ErrorMessage.WRONG_DATE.getMessage());
+        }
+    }
+
+    private static void validateNumericStringLength(String numeric) {
+        if (numeric.length() > MAX_STRING_LENGTH) {
+            throw new IllegalArgumentException(ErrorMessage.WRONG_DATE.getMessage());
+        }
+    }
+
+    private static void validateIntRange(long number) {
+        if (number > Integer.MAX_VALUE || number < Integer.MIN_VALUE) {
+            throw new IllegalArgumentException(ErrorMessage.WRONG_DATE.getMessage());
+        }
+    }
+}

리뷰: 인스턴스 생성 방지라는 것이 무엇인지 잘 모르겠는데 빈 생성자를 만들어 두면 무엇이 좋나요..??
@@ -0,0 +1,91 @@
+package christmas.view;
+
+import christmas.dto.BenefitDTO;
+import christmas.dto.OrderDTO;
+
+import java.util.List;
+
+public class OutputView {
+    private void printMessage(ViewMessage message) {
+        System.out.println(message.getMessage());
+    }
+
+    private void printFormat(ViewMessage message, Object... args) {
+        System.out.printf(message.getMessage(), args);
+        newLine();
+    }
+
+    private void printTitle(ViewTitle title) {
+        System.out.println(title.getTitle());
+    }
+
+    public void printHelloMessage() {
+        printMessage(ViewMessage.HELLO);
+    }
+
+    public void printEventPreviewTitle(int date) {
+        printFormat(ViewMessage.EVENT_PREVIEW_TITLE, date);
+        newLine();
+    }
+
+    public void printOrder(List<OrderDTO> orders) {
+        printTitle(ViewTitle.ORDER_MENU);
+        orders.forEach(it -> printFormat(ViewMessage.ORDER_FORMAT, it.menuName(), it.count()));
+        newLine();
+    }
+
+    public void printTotalPrice(int price) {
+        printTitle(ViewTitle.TOTAL_PRICE);
+        printFormat(ViewMessage.PRICE_FORMAT, price);
+        newLine();
+    }
+
+    public void printGift(boolean hasGift) {
+        printTitle(ViewTitle.GIFT_MENU);
+        if (hasGift) {
+            printMessage(ViewMessage.CHAMPAGNE);
+            newLine();
+            return;
+        }
+        printMessage(ViewMessage.NOTHING);
+        newLine();
+    }
+
+    public void printAllBenefit(List<BenefitDTO> benefits) {
+        printTitle(ViewTitle.BENEFIT_LIST);
+        if (benefits.isEmpty()) {
+            printMessage(ViewMessage.NOTHING);
+            newLine();
+            return;
+        }
+        benefits.forEach(it -> printFormat(ViewMessage.BENEFIT_FORMAT, it.EventName(), it.price()));
+        newLine();
+    }
+
+    public void printBenefitPrice(int price) {
+        printTitle(ViewTitle.BENEFIT_PRICE);
+        printFormat(ViewMessage.PRICE_FORMAT, -price);
+        newLine();
+    }
+
+    public void printAfterDiscountPrice(int price) {
+        printTitle(ViewTitle.AFTER_DISCOUNT_PRICE);
+        printFormat(ViewMessage.PRICE_FORMAT, price);
+        newLine();
+    }
+
+    public void printBadge(String badge) {
+        printTitle(ViewTitle.BADGE);
+        System.out.println(badge);
+    }
+
+    public void printErrorMessage(IllegalArgumentException error) {
+        newLine();
+        System.out.println(error.getMessage());
+        newLine();
+    }
+
+    public void newLine() {
+        System.out.println();
+    }
+}

리뷰: 새로운 라인을 만들때는 System.lineSeperator()를 추천 해주시더라구요. 참고하시면 좋을 것 같습니다!
@@ -0,0 +1,22 @@
+package christmas.config;
+
+public enum ErrorMessage {
+    WRONG_DATE("유효하지 않은 날짜입니다."),
+    WRONG_ORDER("유효하지 않은 주문입니다."),
+    OVER_MAX_ORDER("메뉴는 한 번에 최대 20개까지만 주문할 수 있습니다."),
+    ONLY_DRINK_ORDER("음료만 주문할 수 없습니다.");
+
+    private static final String PREFIX = "[ERROR]";
+    private static final String SUFFIX = "다시 입력해 주세요.";
+    private static final String DELIMITER = " ";
+
+    private final String message;
+
+    ErrorMessage(String message) {
+        this.message = message;
+    }
+
+    public String getMessage() {
+        return String.join(DELIMITER, PREFIX, message, SUFFIX);
+    }
+}

리뷰: 사실 저도 상수를 처리할 때 `enum`을 사용할지 `class`를 사용할지 고민이 많았습니다. 어떤 기준을 세워야 할지도 스스로 확립하지 못했구요.

그래서 우선은 아무런 기능 없이 상수값만 저장하는 경우는 `class`를 사용하고, 약간의 기능이라도 함께 사용한다면 `enum`을 사용하려고 해봤습니다.

여기 `ErrorMessage`에서는 공통적으로 사용되는 prefix와 suffix를 `getMessage()`메서드에서 함께 합쳐서 반환하도록 구상했기 때문에 `enum`을 사용해봤습니다. 사실 `class`로 사용해도 무방할거 같네요. 😂
@@ -0,0 +1,57 @@
+package christmas.controller;
+
+import christmas.domain.*;
+import christmas.dto.OrderDTO;
+import christmas.util.IntParser;
+import christmas.util.OrderParser;
+import christmas.util.RetryExecutor;
+import christmas.view.InputView;
+import christmas.view.OutputView;
+
+import java.util.List;
+
+public class EventController {
+    private Bill bill;
+    private EventHandler eventHandler;
+    private final InputView inputView;
+    private final OutputView outputView;
+
+    public EventController(InputView inputView, OutputView outputView) {
+        this.inputView = inputView;
+        this.outputView = outputView;
+    }
+
+    public void run() {
+        outputView.printHelloMessage();
+        RetryExecutor.execute(this::setBill, outputView::printErrorMessage);
+        RetryExecutor.execute(this::setOrder, error -> {
+            outputView.printErrorMessage(error);
+            bill.clearOrder();
+        });
+        printResult();
+    }
+
+    private void setBill() {
+        String input = inputView.inputDate().trim();
+        bill = Bill.from(IntParser.parseIntOrThrow(input));
+        eventHandler = EventHandler.from(bill);
+    }
+
+    private void setOrder() {
+        String input = inputView.inputOrder();
+        List<OrderDTO> orders = OrderParser.parseOrderOrThrow(input);
+        orders.forEach(it -> bill.add(Order.create(it.menuName(), it.count())));
+        bill.validateOnlyDrink();
+    }
+
+    private void printResult() {
+        outputView.printEventPreviewTitle(bill.getDateValue());
+        outputView.printOrder(bill.getAllOrders());
+        outputView.printTotalPrice(bill.getTotalPrice());
+        outputView.printGift(eventHandler.hasChampagneGift());
+        outputView.printAllBenefit(eventHandler.getAllBenefit());
+        outputView.printBenefitPrice(eventHandler.getTotalBenefitPrice());
+        outputView.printAfterDiscountPrice(bill.getTotalPrice() - eventHandler.getTotalDiscountPrice());
+        outputView.printBadge(Badge.getBadgeNameWithBenefitPrice(eventHandler.getTotalBenefitPrice()));
+    }
+}

리뷰: 저도 함수형 인터페이스라는 걸 처음 써봤답니다! 3주차 코드리뷰를 하면서 어깨너머로 배운거라, @HongYeseul 님도 이번 코드리뷰를 기회로 한번 공부해보시는건 어떨까요? 😁
@@ -0,0 +1,26 @@
+package christmas.domain;
+
+import java.util.Arrays;
+
+public enum Badge {
+    SANTA("산타", 20000),
+    TREE("트리", 10000),
+    STAR("별", 5000),
+    NOTHING("없음", 0);
+
+    private final String badgeName;
+    private final int threshold;
+
+    Badge(String badgeName, int threshold) {
+        this.badgeName = badgeName;
+        this.threshold = threshold;
+    }
+
+    public static String getBadgeNameWithBenefitPrice(int benefitPrice) {
+        return Arrays.stream(Badge.values())
+                .filter(it -> it.threshold <= benefitPrice)
+                .map(it -> it.badgeName)
+                .findFirst()
+                .orElse(NOTHING.badgeName);
+    }
+}

리뷰: 정말 놀랍게도 저도 1주차와 2주차 코드 리뷰를 하면서 대부분의 리뷰에 `stream`사용을 잘 하시는게 부럽다! 라는식으로 많이 리뷰를 남기고 다녔어요. 그렇게 많은 분들의 코드를 보면서 `stream`과 점점 친숙해지다 보니 저도 `stream`을 한번 써볼까? 라는 생각이 들었고, 그렇게 점점 익숙해진 것 같아요! 😊

4주차가 끝난 지금은 학교 도서관에서 '이것이 자바다'라는 책을 빌려서 읽고 있어요. 자바에 익숙해지긴 했지만, 그래도 컬렉션이나 스트림과 같은 부분을 제대로 공부하고 싶어서요. 이런 자바 서적을 하나 정하셔서 기본 문법을 정리하시는 시간을 가지는 것도 좋아보입니다! 😄
@@ -0,0 +1,38 @@
+package christmas.util;
+
+import christmas.config.ErrorMessage;
+
+public class IntParser {
+    private static final int MAX_STRING_LENGTH = 11;
+
+    private IntParser() {
+        // 인스턴스 생성 방지
+    }
+
+    public static int parseIntOrThrow(String numeric) {
+        validateNumericStringLength(numeric);
+        long parsed = parseLongOrThrow(numeric);
+        validateIntRange(parsed);
+        return Integer.parseInt(numeric);
+    }
+
+    private static long parseLongOrThrow(String numeric) {
+        try {
+            return Long.parseLong(numeric);
+        } catch (NumberFormatException e) {
+            throw new IllegalArgumentException(ErrorMessage.WRONG_DATE.getMessage());
+        }
+    }
+
+    private static void validateNumericStringLength(String numeric) {
+        if (numeric.length() > MAX_STRING_LENGTH) {
+            throw new IllegalArgumentException(ErrorMessage.WRONG_DATE.getMessage());
+        }
+    }
+
+    private static void validateIntRange(long number) {
+        if (number > Integer.MAX_VALUE || number < Integer.MIN_VALUE) {
+            throw new IllegalArgumentException(ErrorMessage.WRONG_DATE.getMessage());
+        }
+    }
+}

리뷰: 모든 메서드가 `static`인 유틸리티성 클래스의 경우 `new` 키워드를 사용해 객체를 생성할 필요가 전혀 없다고 생각했습니다. 그래서 혹시라도 의도치 않게 객체가 생성될 경우를 방지하기 위해 생성자를 `private`으로 선언했고, 빈 생성자로 그냥 두긴 좀 밋밋해서 주석을 살짝 달아봤습니다! 😁