ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,34 @@
+package store.util;
+
+import static store.constant.ErrorMessage.FILE_NOT_FOUND;
+
+import java.io.BufferedReader;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.util.ArrayList;
+import java.util.List;
+
+public class FileLines {
+    private final List<String> lines;
+
+    public FileLines(String file) {
+        try {
+            BufferedReader reader = new BufferedReader(new FileReader(file));
+
+            lines = new ArrayList<>(reader.lines().toList());
+        } catch (FileNotFoundException e) {
+            throw new IllegalArgumentException(FILE_NOT_FOUND.getMessage());
+        }
+    }
+
+    public boolean hasMoreLines() {
+        return !lines.isEmpty();
+    }
+
+    public String nextLine() {
+        String line = lines.getFirst();
+
+        lines.removeFirst();
+        return line;
+    }
+}

리뷰: 파일이 없는 경우는예외 처리할 생각을 못했는데 예외를 꼼꼼하게 처리하셨네요
@@ -0,0 +1,27 @@
+package store.util;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class Spliter {
+    private static final int INCLUDE_TRAILING = -1;
+
+    private final List<String> tokens;
+
+    public Spliter(String toBeSplited, String regex) {
+        tokens = new ArrayList<>(List.of(
+                toBeSplited.split(regex, INCLUDE_TRAILING)
+        ));
+    }
+
+    public boolean hasMoreTokens() {
+        return !tokens.isEmpty();
+    }
+
+    public String nextToken() {
+        String token = tokens.getFirst();
+
+        tokens.removeFirst();
+        return token;
+    }
+}

리뷰: 혹시 이게 주문 상품 관련된 것 일까요? 제가 코드가 잘 이해가 안되서 질문드립니다...
@@ -0,0 +1,44 @@
+package store.util;
+
+import static store.constant.ErrorMessage.INVALID_ORDER_FORMAT;
+import static store.constant.ErrorMessage.WRONG_INPUT;
+import static store.constant.OrderInfo.ORDER_PATTERN;
+import static store.constant.OrderInfo.PRODUCT_COUNT_GROUP;
+import static store.constant.OrderInfo.PRODUCT_NAME_GROUP;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.regex.Matcher;
+import store.model.order.Order;
+
+public class Converter {
+    public static int toInteger(String number) {
+        try {
+            return Integer.parseInt(number);
+        } catch (NumberFormatException e) {
+            throw new IllegalArgumentException(WRONG_INPUT.getMessage());
+        }
+    }
+
+    public static LocalDateTime toLocalDateTime(String localDate, String dateFormat) {
+        return LocalDate.parse(
+                        localDate,
+                        DateTimeFormatter.ofPattern(dateFormat)
+                )
+                .atStartOfDay();
+    }
+
+    public static Order toOrder(String order) {
+        Matcher matcher = ORDER_PATTERN.matcher(order);
+
+        if (!matcher.matches()) {
+            throw new IllegalArgumentException(INVALID_ORDER_FORMAT.getMessage());
+        }
+
+        return new Order(
+                matcher.group(PRODUCT_NAME_GROUP),
+                Converter.toInteger(matcher.group(PRODUCT_COUNT_GROUP))
+        );
+    }
+}

리뷰: 저는 프로모션 내부에서 처리했는데 따로 빼는 것도 좋은 방법이네요
@@ -0,0 +1,59 @@
+package store.model.product;
+
+import static store.constant.StoreInfo.NO_PROMOTION;
+import static store.constant.StoreInfo.PRODUCTS_DELIMITER;
+
+import java.util.ArrayList;
+import java.util.List;
+import store.model.promotion.Promotions;
+import store.util.Converter;
+import store.util.FileLines;
+import store.util.Spliter;
+
+public class ProductsLoader {
+    private final List<Product> products;
+    private final List<PromotionProduct> promotionProducts;
+
+    public ProductsLoader(String file, Promotions promotions) {
+        FileLines fileLines = new FileLines(file);
+        fileLines.nextLine();
+
+        products = new ArrayList<>();
+        promotionProducts = new ArrayList<>();
+        while (fileLines.hasMoreLines()) {
+            addProduct(new Spliter(fileLines.nextLine(), PRODUCTS_DELIMITER), promotions);
+        }
+        addProductsOnlyInPromotionProducts();
+    }
+
+    private void addProduct(Spliter spliter, Promotions promotions) {
+        String name = spliter.nextToken();
+        final int price = Converter.toInteger(spliter.nextToken());
+        final int quantity = Converter.toInteger(spliter.nextToken());
+        String promotionName = spliter.nextToken();
+
+        if (promotionName.equals(NO_PROMOTION)) {
+            products.add(new Product(name, price, quantity));
+            return;
+        }
+        promotionProducts.add(new PromotionProduct(name, price, quantity, promotions.getNameWith(promotionName)));
+    }
+
+    private void addProductsOnlyInPromotionProducts() {
+        List<String> productNames = products.stream().map(Product::getName).toList();
+
+        promotionProducts.stream()
+                .filter(promotionProduct -> !productNames.contains(promotionProduct.getName()))
+                .forEach(promotionProduct ->
+                        products.add(new Product(promotionProduct.getName(), promotionProduct.getPrice(), 0))
+                );
+    }
+
+    public List<Product> getProducts() {
+        return products;
+    }
+
+    public List<PromotionProduct> getPromotionProducts() {
+        return promotionProducts;
+    }
+}

리뷰: 프로모션과 상품을 파일에서 읽어오신걸 저장하는 클래스가 있던데 따로 만드신 이유가 있을까요?
@@ -0,0 +1,27 @@
+package store.constant;
+
+public enum ErrorMessage {
+    FILE_NOT_FOUND("파일을 찾을 수 없습니다."),
+    INVALID_PERIOD("기간이 올바르지 않습니다."),
+    PRICE_NEGATIVE("가격이 음수입니다."),
+    QUANTITY_NEGATIVE("수량이 음수입니다."),
+    BUY_COUNT_NOT_POSITIVE("증정 조건이 올바르지 않습니다."),
+    GET_COUNT_NOT_POSITIVE("증정 개수가 올바르지 않습니다."),
+    CANNOT_FIND_PRODUCT("존재하지 않는 상품입니다. 다시 입력해 주세요."),
+    INVALID_ORDER_FORMAT("올바르지 않은 형식으로 입력했습니다. 다시 입력해 주세요."),
+    EXCEEDED_QUANTITY("재고 수량을 초과하여 구매할 수 없습니다. 다시 입력해 주세요."),
+    WRONG_INPUT("잘못된 입력입니다. 다시 입력해 주세요."),
+    ;
+
+    private static final String ERROR_PREFIX = "[ERROR] ";
+
+    private final String message;
+
+    ErrorMessage(String message) {
+        this.message = ERROR_PREFIX + message;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+}

리뷰: 본인만의 상수를 ENUM 타입으로 관리하는 기준이 있을까요?
@@ -0,0 +1,30 @@
+package store.constant;
+
+public enum Answer {
+    YES("Y"),
+    NO("N"),
+    ;
+
+    private final String message;
+
+    Answer(String message) {
+        this.message = message;
+    }
+
+    public static Answer getMatchingAnswer(String toFind) {
+        for (Answer answer : Answer.values()) {
+            if (answer.matches(toFind)) {
+                return answer;
+            }
+        }
+        return null;
+    }
+
+    private boolean matches(String answer) {
+        return message.equals(answer);
+    }
+
+    public String getMessage() {
+        return message;
+    }
+}

리뷰: null로 반환해서 외부에서 잘못된 입력인지 확인하는거 보다 여기서 예외를 던지는 것도 좋을거같습니다 ㅎㅎ!😊
@@ -0,0 +1,44 @@
+package store.util;
+
+import static store.constant.ErrorMessage.INVALID_ORDER_FORMAT;
+import static store.constant.ErrorMessage.WRONG_INPUT;
+import static store.constant.OrderInfo.ORDER_PATTERN;
+import static store.constant.OrderInfo.PRODUCT_COUNT_GROUP;
+import static store.constant.OrderInfo.PRODUCT_NAME_GROUP;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.regex.Matcher;
+import store.model.order.Order;
+
+public class Converter {
+    public static int toInteger(String number) {
+        try {
+            return Integer.parseInt(number);
+        } catch (NumberFormatException e) {
+            throw new IllegalArgumentException(WRONG_INPUT.getMessage());
+        }
+    }
+
+    public static LocalDateTime toLocalDateTime(String localDate, String dateFormat) {
+        return LocalDate.parse(
+                        localDate,
+                        DateTimeFormatter.ofPattern(dateFormat)
+                )
+                .atStartOfDay();
+    }
+
+    public static Order toOrder(String order) {
+        Matcher matcher = ORDER_PATTERN.matcher(order);
+
+        if (!matcher.matches()) {
+            throw new IllegalArgumentException(INVALID_ORDER_FORMAT.getMessage());
+        }
+
+        return new Order(
+                matcher.group(PRODUCT_NAME_GROUP),
+                Converter.toInteger(matcher.group(PRODUCT_COUNT_GROUP))
+        );
+    }
+}

리뷰: 유틸 클래스로 나누는 본인만의 기준이 있나요? 궁금합니다!
@@ -0,0 +1,35 @@
+package store.view;
+
+import static store.constant.ErrorMessage.WRONG_INPUT;
+import static store.constant.OrderInfo.ORDER_DELIMITER;
+
+import camp.nextstep.edu.missionutils.Console;
+import java.util.ArrayList;
+import java.util.List;
+import store.constant.Answer;
+import store.model.order.Order;
+import store.util.Converter;
+import store.util.Spliter;
+
+public class InputView {
+    public static List<Order> readOrders() {
+        Spliter spliter = new Spliter(Console.readLine(), ORDER_DELIMITER);
+        List<Order> orders = new ArrayList<>();
+
+        while (spliter.hasMoreTokens()) {
+            String nextToken = spliter.nextToken();
+
+            orders.add(Converter.toOrder(nextToken));
+        }
+        return orders;
+    }
+
+    public static Answer readAnswer() {
+        Answer answer = Answer.getMatchingAnswer(Console.readLine());
+
+        if (answer == null) {
+            throw new IllegalArgumentException(WRONG_INPUT.getMessage());
+        }
+        return answer;
+    }
+}

리뷰: 주입을 안받고 유틸 클래스로 사용하신 이유가 있나요??
@@ -0,0 +1,46 @@
+package store.model.receipt;
+
+import java.util.List;
+
+public class PurchaseInfos {
+    private final List<PurchaseInfo> purchaseInfos;
+
+    public PurchaseInfos(List<PurchaseInfo> purchaseInfos) {
+        this.purchaseInfos = purchaseInfos;
+    }
+
+    public List<PurchaseInfo> toList() {
+        return List.copyOf(purchaseInfos);
+    }
+
+    public List<PurchaseInfo> toPromotionDiscountedList() {
+        return purchaseInfos.stream()
+                .filter(PurchaseInfo::isPromotionDiscounted)
+                .toList();
+    }
+
+    public int getTotalBuyCount() {
+        return purchaseInfos.stream()
+                .mapToInt(PurchaseInfo::getBuyCount)
+                .sum();
+    }
+
+    public int getTotalAmount() {
+        return purchaseInfos.stream()
+                .mapToInt(PurchaseInfo::getTotalAmount)
+                .sum();
+    }
+
+    public int getPromotionDiscountAmount() {
+        return toPromotionDiscountedList().stream()
+                .mapToInt(PurchaseInfo::getPromotionDiscountAmount)
+                .sum();
+    }
+
+    public int getNotPromotionDiscountAppliedProductsAmount() {
+        return purchaseInfos.stream()
+                .filter(PurchaseInfo::isNotPromotionDiscounted)
+                .mapToInt(PurchaseInfo::getTotalAmount)
+                .sum();
+    }
+}

리뷰: copyof을 통해 읽기용 리스트로 반환하는거 좋은거 같습니다! 
Collections.unmodifiableList(purchaseInfos); 이 방법도 있습니다!
@@ -0,0 +1,30 @@
+package store.constant;
+
+public enum Answer {
+    YES("Y"),
+    NO("N"),
+    ;
+
+    private final String message;
+
+    Answer(String message) {
+        this.message = message;
+    }
+
+    public static Answer getMatchingAnswer(String toFind) {
+        for (Answer answer : Answer.values()) {
+            if (answer.matches(toFind)) {
+                return answer;
+            }
+        }
+        return null;
+    }
+
+    private boolean matches(String answer) {
+        return message.equals(answer);
+    }
+
+    public String getMessage() {
+        return message;
+    }
+}

리뷰: enum형 활용해서 Y,N만 받을 수 있도록 구현할 수 있는 방법이 있었군요.
저는 생각해보지 못했던 방식이라 새롭네요.. 배워갑니다!!
@@ -0,0 +1,28 @@
+package store.constant;
+
+public class OutputInfo {
+    public static final String NEW_LINE = "%n";
+    public static final String WELCOME_MESSAGE = "안녕하세요. W편의점입니다.";
+    public static final String PRODUCT_START_MESSAGE = "현재 보유하고 있는 상품입니다." + NEW_LINE;
+    public static final String PRODUCT_FORMAT = "- %s %s원 %s" + NEW_LINE;
+    public static final String PROMOTION_PRODUCT_FORMAT = "- %s %s원 %s %s" + NEW_LINE;
+    public static final String NO_QUANTITY = "재고 없음";
+    public static final String QUANTITY_FORMAT = "%d개";
+    public static final String MAY_I_TAKE_YOUR_ORDER = "구매하실 상품명과 수량을 입력해 주세요. (예: [사이다-2],[감자칩-1])";
+    public static final String FREE_GETTABLE_COUNT_QUESTION =
+            "현재 %s은(는) %d개를 무료로 더 받을 수 있습니다. 추가하시겠습니까? (Y/N)" + NEW_LINE;
+    public static final String BUY_COUNT_WITHOUT_PROMOTION_QUESTION =
+            "현재 %s %d개는 프로모션 할인이 적용되지 않습니다. 그래도 구매하시겠습니까? (Y/N)" + NEW_LINE;
+    public static final String MEMBERSHIP_DISCOUNT = "멤버십 할인을 받으시겠습니까? (Y/N)";
+    public static final String RECEIPT_START_MESSAGE = "==============W 편의점================" + NEW_LINE;
+    public static final String RECEIPT_PURCHASE_INFO_START_MESSAGE = "상품명\t\t수량\t금액" + NEW_LINE;
+    public static final String RECEIPT_PURCHASE_INFO_FORMAT = "%s\t\t%d \t%s" + NEW_LINE;
+    public static final String RECEIPT_FREE_START_MESSAGE = "=============증\t정===============" + NEW_LINE;
+    public static final String RECEIPT_FREE_PRODUCT_FORMAT = "%s\t\t%d" + NEW_LINE;
+    public static final String RECEIPT_PAY_START_MESSAGE = "====================================" + NEW_LINE;
+    public static final String RECEIPT_TOTAL_PRICE_FORMAT = "총구매액\t\t%d\t%s" + NEW_LINE;
+    public static final String RECEIPT_PROMOTION_DISCOUNT_FORMAT = "행사할인\t\t\t-%s" + NEW_LINE;
+    public static final String RECEIPT_MEMBERSHIP_DISCOUNT_FORMAT = "멤버십할인\t\t\t-%s" + NEW_LINE;
+    public static final String RECEIPT_AMOUNT_OF_PAY_FORMAT = "내실돈\t\t\t %s" + NEW_LINE;
+    public static final String WANNA_BUY_MORE = "감사합니다. 구매하고 싶은 다른 상품이 있나요? (Y/N)";
+}

리뷰: 저도 OutputView에서 사용되는 문자열이 상단에 너무 많아 별도의 파일로 관리해야 할지 고민했었습니다. 
그러나 해당 문자열들이 OutputView에서만 사용된다고 판단해 별도의 클래스를 작성하지 않았는데요,
정답이 정해져있는 문제는 아니지만 별도의 클래스를 두신 이유가 궁금합니다!
@@ -0,0 +1,29 @@
+package store.model.order;
+
+import static store.constant.ErrorMessage.WRONG_INPUT;
+
+import java.util.List;
+
+public class Orders {
+    private final List<Order> orders;
+
+    public Orders(List<Order> orders) {
+        validate(orders);
+        this.orders = orders;
+    }
+
+    private void validate(List<Order> orders) {
+        validateOrderNotDuplicated(orders);
+    }
+
+    private void validateOrderNotDuplicated(List<Order> orders) {
+        int distinctCount = (int) orders.stream().map(Order::getProductName).distinct().count();
+        if (distinctCount != orders.size()) {
+            throw new IllegalArgumentException(WRONG_INPUT.getMessage());
+        }
+    }
+
+    public List<Order> toList() {
+        return List.copyOf(orders);
+    }
+}

리뷰: `validate` 메소드에서 실행하는 것이 `validateOrderNotDuplicated` 하나인데, `validate`라는 메소드를 따로 만드신 이유가 궁금합니다.
`Orders` 생성자에서 바로 `validateOrderNotDuplicated`를 호출해도 되지 않나요?
@@ -0,0 +1,50 @@
+package store.model.product;
+
+import static store.constant.ErrorMessage.PRICE_NEGATIVE;
+import static store.constant.ErrorMessage.QUANTITY_NEGATIVE;
+
+public class Product {
+    protected final String name;
+    protected final int price;
+    protected int quantity;
+
+    public Product(String name, final int price, final int quantity) {
+        validate(price, quantity);
+        this.name = name;
+        this.price = price;
+        this.quantity = quantity;
+    }
+
+    private void validate(final int price, final int quantity) {
+        validatePriceNotNegative(price);
+        validateQuantityNotNegative(quantity);
+    }
+
+    private void validatePriceNotNegative(final int price) {
+        if (price < 0) {
+            throw new IllegalArgumentException(PRICE_NEGATIVE.getMessage());
+        }
+    }
+
+    private void validateQuantityNotNegative(final int quantity) {
+        if (quantity < 0) {
+            throw new IllegalArgumentException(QUANTITY_NEGATIVE.getMessage());
+        }
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public int getQuantity() {
+        return quantity;
+    }
+
+    public int getPrice() {
+        return price;
+    }
+
+    public void sell(final int amount) {
+        quantity -= amount;
+    }
+}

리뷰: `validate`라는 메서드 명이 반복되어서 사용되고 있는데, 저는 개인적으로 다른 클래스에 있더라도 동일한 메서드명을 사용하는 것을 꺼리게 되더라구요. 
두 메소드 다 static이 없어서 객체명으로 구분이 되겠지만, 그래도 `validateOrders`나 `validateProduct`와 같이 작성했을 것 같은데, 이에 대한 의견이 궁금합니다!
@@ -0,0 +1,44 @@
+package store.util;
+
+import static store.constant.ErrorMessage.INVALID_ORDER_FORMAT;
+import static store.constant.ErrorMessage.WRONG_INPUT;
+import static store.constant.OrderInfo.ORDER_PATTERN;
+import static store.constant.OrderInfo.PRODUCT_COUNT_GROUP;
+import static store.constant.OrderInfo.PRODUCT_NAME_GROUP;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.regex.Matcher;
+import store.model.order.Order;
+
+public class Converter {
+    public static int toInteger(String number) {
+        try {
+            return Integer.parseInt(number);
+        } catch (NumberFormatException e) {
+            throw new IllegalArgumentException(WRONG_INPUT.getMessage());
+        }
+    }
+
+    public static LocalDateTime toLocalDateTime(String localDate, String dateFormat) {
+        return LocalDate.parse(
+                        localDate,
+                        DateTimeFormatter.ofPattern(dateFormat)
+                )
+                .atStartOfDay();
+    }
+
+    public static Order toOrder(String order) {
+        Matcher matcher = ORDER_PATTERN.matcher(order);
+
+        if (!matcher.matches()) {
+            throw new IllegalArgumentException(INVALID_ORDER_FORMAT.getMessage());
+        }
+
+        return new Order(
+                matcher.group(PRODUCT_NAME_GROUP),
+                Converter.toInteger(matcher.group(PRODUCT_COUNT_GROUP))
+        );
+    }
+}

리뷰: Integer.pasreInt와 같이 자주 사용하는 메소드들에 대해서 매변 try-catch를 작성하는 것이 불편하다고 생각했는데, 이런 방식으로 활용할 수 있다는 것을 깨닫고 갑니다!
@@ -0,0 +1,73 @@
+package store;
+
+public enum StoreViewMessage {
+
+    WELCOME("안녕하세요. W편의점입니다.\n"
+            + "현재 보유하고 있는 상품입니다."),
+    PURCHASE_GUIDE("구매하실 상품명과 수량을 입력해 주세요. (예: [사이다-2],[감자칩-1])"),
+    MEMBERSHIP_GUIDE("멤버십 할인을 받으시겠습니까? (Y/N)"),
+
+    RECEIPT_HEAD("==============W 편의점================"),
+    RECEIPT_HEAD_PROMOTION("=============증\t정==============="),
+    RECEIPT_HEAD_ACCOUNT("===================================="),
+    RETRY_PURCHASE("감사합니다. 구매하고 싶은 다른 상품이 있나요? (Y/N)"),
+
+    ERROR_INVALID_FORMAT("[ERROR] 올바르지 않은 형식으로 입력했습니다. 다시 입력해 주세요."),
+
+    ERROR_NOT_EXIST_PRODUCT("[ERROR] 존재하지 않는 상품입니다. 다시 입력해 주세요."),
+    ERROR_OVERFLOW_PURCHASE("[ERROR] 재고 수량을 초과하여 구매할 수 없습니다. 다시 입력해 주세요."),
+    ERROR_NO_COUNT_PRODUCT("[ERROR] 재고가 없는 상품입니다. 다시 입력해 주세요.");
+
+    private final String message;
+
+    static public final String PURCHASE_PATTERN = "\\[([a-zA-Z가-힣0-9]+)-(\\d+)]";
+    static public final String PURCHASE_LIST_DELIMITER = ",";
+    static public final String ANSWER_NO = "N";
+    static public final String ANSWER_YES = "Y";
+
+    private StoreViewMessage(String message){
+        this.message = message;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public void printMessage(){
+        System.out.println(this.message);
+    }
+
+    public static void printReceiptFormat(String first, String second, String third){
+        final int FIRST_WIDTH = 15;
+        final int SECOND_WIDTH = 8;
+        final int THIRD_WIDTH = 10;
+        System.out.printf("%-" + FIRST_WIDTH + "s%-" + SECOND_WIDTH + "s%-" + THIRD_WIDTH + "s\n", first, second, third);
+    }
+
+    public static void printReceiptProductListHead(){
+        final String productNameColumn = "상품명";
+        final String countColumn = "수량";
+        final String priceColumn = "금액";
+        printReceiptFormat(productNameColumn,countColumn,priceColumn);
+    }
+
+    public static void printProduct(String ProductName, int price, int count, String promotionName){
+        final String format = "- %s %,d원 %d개 %s\n";
+        System.out.printf(format,ProductName,price,count,promotionName);
+    }
+
+    public static void printInsufficientProduct(String ProductName, int price, String promotionName){
+        final String format = "- %s %,d원 재고 없음 %s\n";
+        System.out.printf(format,ProductName,price,promotionName);
+    }
+
+    public static void printPromotionReturnAvailable(String productName){
+        String format = "현재 %s은(는) 1개를 무료로 더 받을 수 있습니다. 추가하시겠습니까? (Y/N)\n";
+        System.out.printf(format,productName);
+    }
+
+    public static void printPromotionWarning(String productName,int count) {
+        String format = "현재 %s %d개는 프로모션 할인이 적용되지 않습니다. 그래도 구매하시겠습니까? (Y/N)\n";
+        System.out.printf(format, productName, count);
+    }
+}

리뷰: 그런데 여기서 선언된 상수들은 전역에서 사용하실 목적으로 선언하신 건지 궁금합니다! 저는 `enum`클래스에서 따로 상수로 선언하는 부분은, 해당 enum 파일 내에서 사용되는 것이 목적이라고 생각했거든요,

해당 상수들을 선언하는 클래스를 따로 분리하거나, 위의 메시지들과 같이 `enum 상수`로 생성해도 괜찮을 것 같은데, 어떻게 생각하시나요?

+) 다른 분들의 코드 중에서 "Y", "N"를 하나의 enum으로 구분하신 경우도 있더라구요!
@@ -0,0 +1,32 @@
+package store.io;
+
+public enum ProductFile {
+    NAME(0,"name",String.class),
+    PRICE(1,"price",Integer.class),
+    QUANTITY(2,"quantitiy",Integer.class),
+    PROMOTION(3,"promotion",String.class);
+
+    private int columnIdx;
+    private String columName;
+    private Class<?> type;
+
+    static final public String COLUMN_DELIMITER = ",";
+
+    private ProductFile(int colunmIdx, String columName, Class<?> type){
+        this.columName = columName;
+        this.columnIdx = colunmIdx;
+        this.type = type;
+    }
+
+    public int getColumnIdx(){
+        return this.columnIdx;
+    }
+
+    public String getColumName(){
+        return this.columName;
+    }
+
+    public Class<?> getType(){
+        return this.type;
+    }
+}

리뷰: `enum`을 이렇게도 사용할 수가 있네요! 좋은 방법 알아갑니다👍
@@ -0,0 +1,58 @@
+package store.product;
+
+import store.product.promotion.PromotionState;
+import store.utils.ExceptionFactory;
+import store.utils.ExceptionType;
+import store.utils.Validator;
+
+public class PurchaseRequest {
+    private String productName;
+    private int countPurchased;
+    private PromotionState promotionState;
+    public PurchaseRequest(){
+
+    }
+
+    public PurchaseRequest(String productName, int countPurchased, PromotionState promotionState){
+        this.countPurchased = countPurchased;
+        this.productName = productName;
+        this.promotionState = promotionState;
+    }
+
+    public String getProductName(){
+        return this.productName;
+    }
+    public int getCountPurchased(){
+        return this.countPurchased;
+    }
+
+    public PromotionState getPromotionState(){
+        return this.promotionState;
+    }
+
+    public void increaseCount(int value){
+        Validator.validatePositiveNumber(value);
+        this.countPurchased += value;
+    }
+
+    public PurchaseRequest tryToIncrease(boolean isIncrease){
+        int step = 1;
+        if(isIncrease){
+            this.increaseCount(step);
+            return this;
+        }
+        return this;
+    }
+
+    public void decreaseCount(int value){
+        if(value > this.countPurchased){
+            ExceptionFactory.throwIllegalStateException(ExceptionType.INTERNAL_ERROR);
+        }
+        this.countPurchased -=value;
+    }
+
+    public boolean isCountMet(){
+        int zero = 0;
+        return this.countPurchased == zero;
+    }
+}

리뷰: `public` 기본 생성자를 선언하신 이유가 궁금합니다!