ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,73 @@
+package store.model;
+
+import camp.nextstep.edu.missionutils.DateTimes;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+public class Promotion {
+    private String name;
+    private int buy;
+    private int get;
+    private String startDateStr;
+    private String endDateStr;
+
+    public Promotion(String name, int buy, int get, String startdateStr, String enddateStr) {
+        this.name = name;
+        this.buy = buy;
+        this.get = get;
+        this.startDateStr = startdateStr;
+        this.endDateStr = enddateStr;
+    }
+
+    public static Promotion parsePromotions(String line) {
+        List<String> promotion = Arrays.asList(line.split(","));
+
+        String name = promotion.get(0).trim();
+        int buy = Integer.parseInt(promotion.get(1).trim());
+        int get = Integer.parseInt(promotion.get(2).trim());
+        String start_date = promotion.get(3).trim();
+        String end_date = promotion.get(4).trim();
+
+        return new Promotion(name, buy, get, start_date, end_date);
+    }
+
+    public int checkPromotionDate() {
+        final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
+
+        try {
+            Date startDate = DATE_FORMAT.parse(startDateStr);
+            Date endDate = DATE_FORMAT.parse(endDateStr);
+            Date now = DATE_FORMAT.parse(String.valueOf(DateTimes.now()));
+
+            if (!now.before(startDate) && !now.after(endDate)) {
+                return this.buy;
+            }
+        } catch (ParseException e) {
+            return 0;
+        }
+        return 0;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public int getBuy() {
+        return buy;
+    }
+
+    public int getGet() {
+        return get;
+    }
+
+    public String getStartDateStr() {
+        return startDateStr;
+    }
+
+    public String getEndDateStr() {
+        return endDateStr;
+    }
+}

리뷰: 문자열을 프로모션으로 파싱하는 역할을 파싱하는 객체를 만들어서 거기로 넘기는건 어떨까요!!
@@ -0,0 +1,59 @@
+package store.model;
+
+import static store.utils.ErrorMessage.INVALID_FORMAT;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class Order {
+    private List<Product> order;
+
+    public Order(String inputOrder) {
+        validateProductFormat(inputOrder);
+        this.order = generateProducts(inputOrder);
+    }
+
+    public List<Product> generateProducts(String inputOrder) {
+        List<Product> products = new ArrayList<>();
+        String[] orders = inputOrder.split("],\\[");
+
+        for (String order : orders) {
+            String formatOrder = order.replace("[", "").replace("]", "");
+            int quantityIndex = formatOrder.lastIndexOf('-') + 1;
+
+            String name = formatOrder.substring(0, quantityIndex - 1).trim();
+            String quantity = formatOrder.substring(quantityIndex).trim();
+
+            products.add(new Product(name, quantity));
+        }
+        return products;
+    }
+
+    private void validateProductFormat(String inputOrder) {
+        String[] orderList = inputOrder.split(",");
+
+        for (String order : orderList) {
+            if (!isValidProductEntry(order.trim())) {
+                throw new IllegalArgumentException(INVALID_FORMAT);
+            }
+        }
+    }
+
+    private boolean isValidProductEntry(String order) {
+        if (!order.startsWith("[") || !order.endsWith("]")) {
+            return false;
+        }
+
+        String part = order.substring(1, order.length() - 1);
+        String[] parts = part.split("-");
+        if (parts[0].isEmpty()) {
+            return false;
+        }
+
+        return parts.length == 2;
+    }
+
+    public List<Product> getOrder() {
+        return order;
+    }
+}

리뷰: 개인적으로 위의 앞 뒤 [ ] 확인하는 부분과 빈문자열인지 확인하는 로직을 분리해도 될 것 같습니다!
@@ -0,0 +1,50 @@
+package store.model;
+
+public class Receipt {
+    private String name;
+    private int individualPrice;
+    private int quantity;
+    private int promotionQuantity;
+    private int promotionBuy;
+
+    public Receipt(String name, int individualPrice, int quantity, int promotionBuy) {
+        this.name = name;
+        this.individualPrice = individualPrice;
+        this.quantity = quantity;
+        this.promotionBuy = promotionBuy;
+    }
+
+    public void addPromotion() {
+        quantity++;
+    }
+
+    public void removeNonPromotion(int nomDiscountableQuantity) {
+        quantity -= nomDiscountableQuantity;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public int getIndividualPrice() {
+        return individualPrice;
+    }
+
+    public int getQuantity() {
+        return quantity;
+    }
+
+    public int getPromotionQuantity() {
+        return promotionQuantity;
+    }
+
+    public int getPromotionBuy() {
+        return promotionBuy;
+    }
+
+    public void setPromotionQuantity(int promotionQuantity) {
+        this.promotionQuantity = promotionQuantity;
+    }
+}
+
+

리뷰: 이번 과제에서 게터 사용을 줄이기가 쉽지 않아, 저도 결국 사용할 수밖에 없었습니다ㅜㅜ 
게터를 사용하면 객체의 캡슐화를 위반할 위험이 있고, 객체의 상태를 외부에서 쉽게 변경할 수 있게 되어 객체의 책임이 외부로 밀려날 수 있기 때문에 지양해야 한다고 알고 있습니다. 
그래서 앞으로 게터 사용을 줄이기 위해 DTO를 활용해보는 것도 좋은 방법이 될 것 같습니다!
@@ -0,0 +1,89 @@
+package store.controller;
+
+import java.util.ArrayList;
+import java.util.List;
+import store.model.Order;
+import store.model.Promotion;
+import store.model.Receipt;
+import store.model.Store;
+import store.service.StoreService;
+import store.view.InputView;
+import store.view.OutputView;
+
+public class StoreController {
+    private final InputView inputView;
+    private final OutputView outputView;
+    private final StoreService storeService;
+
+    public StoreController(InputView inputView, OutputView outputView, StoreService storeService) {
+        this.inputView = inputView;
+        this.outputView = outputView;
+        this.storeService = storeService;
+    }
+
+    public void run() {
+        List<Store> store = printProductList();
+        List<Promotion> promotions = loadPromotions();
+        Order order;
+
+        do {
+            order = purchaseProduct(store);
+            priceCalculator(store, order, promotions);
+
+        } while (!checkEnd(store, order));
+    }
+
+    private boolean checkEnd(List<Store> store, Order order) {
+        while (true) {
+            try {
+                if (!storeService.isAnswer(inputView.inputEndMessage())) {
+                    return true;
+                }
+                updateStore(store, order);
+                return false;
+            } catch (IllegalArgumentException e) {
+                outputView.printError(e.getMessage());
+            }
+        }
+    }
+
+    private void updateStore(List<Store> store, Order order) {
+        storeService.checkStock(store, order);
+        outputView.printProductList(store);
+    }
+
+    private void priceCalculator(List<Store> store, Order order, List<Promotion> promotions) {
+        List<Receipt> receipts = storeService.parseReceipt(store, order, promotions);
+        storeService.checkTribeQuantity(receipts, store);
+        storeService.checkTribePromotion(receipts, store);
+        int discount = storeService.membershipDiscount(receipts);
+
+        outputView.printReceipt(receipts, discount);
+    }
+
+    private List<Store> printProductList() {
+        List<Store> store = new ArrayList<>();
+        storeService.loadProductsForm(store);
+        outputView.printProductList(store);
+        return store;
+    }
+
+    private List<Promotion> loadPromotions() {
+        List<Promotion> promotions = new ArrayList<>();
+        storeService.loadPromotionsForm(promotions);
+        return promotions;
+    }
+
+    private Order purchaseProduct(List<Store> store) {
+        while (true) {
+            try {
+                String inputOrder = inputView.inputPurchaseProduct();
+                Order order = new Order(inputOrder);
+                storeService.checkProduct(store, order);
+                return order;
+            } catch (IllegalArgumentException e) {
+                outputView.printError(e.getMessage());
+            }
+        }
+    }
+}

리뷰: do while문을 처음 사용해서 잘 몰랐는데 안에서 선언하는 방법은 생각도 못했네요 배워갑니다!
@@ -0,0 +1,73 @@
+package store.model;
+
+import camp.nextstep.edu.missionutils.DateTimes;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+public class Promotion {
+    private String name;
+    private int buy;
+    private int get;
+    private String startDateStr;
+    private String endDateStr;
+
+    public Promotion(String name, int buy, int get, String startdateStr, String enddateStr) {
+        this.name = name;
+        this.buy = buy;
+        this.get = get;
+        this.startDateStr = startdateStr;
+        this.endDateStr = enddateStr;
+    }
+
+    public static Promotion parsePromotions(String line) {
+        List<String> promotion = Arrays.asList(line.split(","));
+
+        String name = promotion.get(0).trim();
+        int buy = Integer.parseInt(promotion.get(1).trim());
+        int get = Integer.parseInt(promotion.get(2).trim());
+        String start_date = promotion.get(3).trim();
+        String end_date = promotion.get(4).trim();
+
+        return new Promotion(name, buy, get, start_date, end_date);
+    }
+
+    public int checkPromotionDate() {
+        final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
+
+        try {
+            Date startDate = DATE_FORMAT.parse(startDateStr);
+            Date endDate = DATE_FORMAT.parse(endDateStr);
+            Date now = DATE_FORMAT.parse(String.valueOf(DateTimes.now()));
+
+            if (!now.before(startDate) && !now.after(endDate)) {
+                return this.buy;
+            }
+        } catch (ParseException e) {
+            return 0;
+        }
+        return 0;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public int getBuy() {
+        return buy;
+    }
+
+    public int getGet() {
+        return get;
+    }
+
+    public String getStartDateStr() {
+        return startDateStr;
+    }
+
+    public String getEndDateStr() {
+        return endDateStr;
+    }
+}

리뷰: 가독성이 더 좋다고 생각되는 방향으로 작성했습니다
@@ -0,0 +1,14 @@
+package store.utils;
+
+public class ErrorMessage {
+    public static final String ERROR = "[ERROR] ";
+    public static final String RETRY = " 다시 입력해 주세요.";
+    public static final String INVALID_FORMAT = " 올바르지 않은 형식으로 입력했습니다. ";
+    public static final String INVALID_NAME = " 존재하지 않는 상품입니다.";
+    public static final String INVALID_QUANTITY = "재고 수량을 초과하여 구매할 수 없습니다.";
+    public static final String INVALID_INPUT = "잘못된 입력입니다.";
+
+    private ErrorMessage() {
+
+    }
+}

리뷰: 값들을 그룹화 하여 사용하지 않고 단순 메세지 출력이기에 상수 클래스로 해도 좋을 것 같다고 생각했습니다
@@ -0,0 +1,73 @@
+package store.model;
+
+import camp.nextstep.edu.missionutils.DateTimes;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+public class Promotion {
+    private String name;
+    private int buy;
+    private int get;
+    private String startDateStr;
+    private String endDateStr;
+
+    public Promotion(String name, int buy, int get, String startdateStr, String enddateStr) {
+        this.name = name;
+        this.buy = buy;
+        this.get = get;
+        this.startDateStr = startdateStr;
+        this.endDateStr = enddateStr;
+    }
+
+    public static Promotion parsePromotions(String line) {
+        List<String> promotion = Arrays.asList(line.split(","));
+
+        String name = promotion.get(0).trim();
+        int buy = Integer.parseInt(promotion.get(1).trim());
+        int get = Integer.parseInt(promotion.get(2).trim());
+        String start_date = promotion.get(3).trim();
+        String end_date = promotion.get(4).trim();
+
+        return new Promotion(name, buy, get, start_date, end_date);
+    }
+
+    public int checkPromotionDate() {
+        final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
+
+        try {
+            Date startDate = DATE_FORMAT.parse(startDateStr);
+            Date endDate = DATE_FORMAT.parse(endDateStr);
+            Date now = DATE_FORMAT.parse(String.valueOf(DateTimes.now()));
+
+            if (!now.before(startDate) && !now.after(endDate)) {
+                return this.buy;
+            }
+        } catch (ParseException e) {
+            return 0;
+        }
+        return 0;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public int getBuy() {
+        return buy;
+    }
+
+    public int getGet() {
+        return get;
+    }
+
+    public String getStartDateStr() {
+        return startDateStr;
+    }
+
+    public String getEndDateStr() {
+        return endDateStr;
+    }
+}

리뷰: 역할 분리를 좀 더 상세히 해야 될 것 같네요.. 참고하겠습니다!
@@ -0,0 +1,50 @@
+package store.model;
+
+public class Receipt {
+    private String name;
+    private int individualPrice;
+    private int quantity;
+    private int promotionQuantity;
+    private int promotionBuy;
+
+    public Receipt(String name, int individualPrice, int quantity, int promotionBuy) {
+        this.name = name;
+        this.individualPrice = individualPrice;
+        this.quantity = quantity;
+        this.promotionBuy = promotionBuy;
+    }
+
+    public void addPromotion() {
+        quantity++;
+    }
+
+    public void removeNonPromotion(int nomDiscountableQuantity) {
+        quantity -= nomDiscountableQuantity;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public int getIndividualPrice() {
+        return individualPrice;
+    }
+
+    public int getQuantity() {
+        return quantity;
+    }
+
+    public int getPromotionQuantity() {
+        return promotionQuantity;
+    }
+
+    public int getPromotionBuy() {
+        return promotionBuy;
+    }
+
+    public void setPromotionQuantity(int promotionQuantity) {
+        this.promotionQuantity = promotionQuantity;
+    }
+}
+
+

리뷰: 말씀하신 내용 듣고 제 코드를 다시 살펴보니, 객체내에서는 단순히 데이터만 담아두고 게터 사용으로 대부분 외부에서 로직을 처리하여 객체지향의 의미를 잃은 코드를 작성한 것 같습니다...
구현하는데 너무 급급했던거같네요ㅠㅠ 피드백 감사합니다
@@ -0,0 +1,16 @@
+package store.constant;
+
+public enum CommonMessage {
+    YES("Y"),
+    NO("N");
+    private final String commonMessage;
+
+    CommonMessage(final String commonMessage) {
+        this.commonMessage = commonMessage;
+    }
+
+    public String getCommonMessage() {
+        return commonMessage;
+    }
+
+}

리뷰: 입력이 둘뿐인 경우에 대해서 Enum으로 관리하는 것도 좋은 방법인 것 같아요!
@@ -0,0 +1,22 @@
+package store.constant;
+
+public enum FileMessage {
+    PRODUCTS_FILE_NAME("products.md"),
+    PROMOTION_FILE_NAME("promotions.md"),
+    NULL("null"),
+    SOFT_DRINK("탄산2+1"),
+    MD_RECOMMEND_PRODUCT("MD추천상품"),
+    FLASH_DISCOUNT("반짝할인"),
+    FILE_START_WORD("name");
+
+    private final String fileMessage;
+
+    FileMessage(final String fileMessage) {
+        this.fileMessage = fileMessage;
+    }
+
+    public String getFileMessage() {
+        return fileMessage;
+    }
+
+}

리뷰: 파일에 있는 프로모션 데이터를 Enum으로 관리하는건 좋지 않은 것 같습니다
프로모션 종류가 3개 고정이 아니라 각기 다른 수백 개, 수천 개로 늘어날 수 있을 것 같아요!
@@ -0,0 +1,71 @@
+package store.domain;
+
+import java.util.ArrayList;
+import java.util.List;
+import store.constant.CommonMessage;
+import store.constant.CommonValue;
+
+public class Receipt {
+    private final double THIRTY_PERCENT = 0.3;
+    private final List<ReceiptItem> receiptItems = new ArrayList<>();
+
+    public void addItem(ReceiptItem receiptItem) {
+        receiptItems.add(receiptItem);
+    }
+
+    public List<ReceiptItem> getReceiptItems() {
+        return receiptItems;
+    }
+
+    public int totalPurchaseAmount() {
+        int sum = CommonValue.ZERO.getValue();
+        for (ReceiptItem receiptItem : receiptItems) {
+            sum += receiptItem.getTotalBuyQuantity() * receiptItem.getPrice();
+        }
+        return sum;
+    }
+
+    public int totalPromotionDiscount() {
+        int sum = CommonValue.ZERO.getValue();
+        for (ReceiptItem receiptItem : receiptItems) {
+            if (receiptItem.getGetQuantity() != CommonValue.ZERO.getValue()) {
+                sum += receiptItem.getGetQuantity() * receiptItem.getPrice();
+            }
+        }
+        return sum;
+    }
+
+    public int getTotalPurchaseCount() {
+        int sum = CommonValue.ZERO.getValue();
+        for (ReceiptItem receiptItem : receiptItems) {
+            sum += receiptItem.getTotalBuyQuantity();
+        }
+        return sum;
+    }
+
+    public int validateMembership(String userAnswer) {
+        if (userAnswer.equals(CommonMessage.YES.getCommonMessage())) {
+            return getMembershipDiscount();
+        }
+        return CommonValue.ZERO.getValue();
+    }
+
+    private int noTotalPromotionAmount() {
+        int sum = CommonValue.ZERO.getValue();
+        for (ReceiptItem receiptItem : receiptItems) {
+            if (receiptItem.getGetQuantity() == CommonValue.ZERO.getValue()) {
+                sum += receiptItem.getBuyQuantity() * receiptItem.getPrice();
+            }
+        }
+        return sum;
+    }
+
+    private int getMembershipDiscount() {
+        int membershipDiscount = (int) (noTotalPromotionAmount() * THIRTY_PERCENT);
+        if (membershipDiscount > CommonValue.EIGHT_THOUSAND.getValue()) {
+            membershipDiscount = CommonValue.EIGHT_THOUSAND.getValue();
+        }
+        return membershipDiscount;
+    }
+
+}

리뷰: model의 역할을 수행하는 클래스로 보이는데 패키지를 분리하지 않고, domain 패키지에 같이 두신 이유가 있을까요?
@@ -0,0 +1,19 @@
+package store.constant;
+
+public enum CommonValue {
+    ZERO(0),
+    ONE(1),
+    TWO(2),
+    EIGHT_THOUSAND(8000);
+
+    private final int value;
+
+    CommonValue(final int value) {
+        this.value = value;
+    }
+
+    public int getValue() {
+        return value;
+    }
+
+}

리뷰: 상수들을 enum으로 잘 관리해주셨는데요!
저는 개인적으로 enum의 값을 꺼내기 위해 `.getValue()` 까지 붙는게 조금 지저분해보입니다.

static 상수의 경우 `Constant.ZERO` 이런식으로 사용할 수 있지만 enum은 `CommonValue.ZERO.getValue()` 로 1depth 더 늘어나는 느낌이랄까요..?
@@ -0,0 +1,71 @@
+package store.domain;
+
+import java.util.ArrayList;
+import java.util.List;
+import store.constant.CommonMessage;
+import store.constant.CommonValue;
+
+public class Receipt {
+    private final double THIRTY_PERCENT = 0.3;
+    private final List<ReceiptItem> receiptItems = new ArrayList<>();
+
+    public void addItem(ReceiptItem receiptItem) {
+        receiptItems.add(receiptItem);
+    }
+
+    public List<ReceiptItem> getReceiptItems() {
+        return receiptItems;
+    }
+
+    public int totalPurchaseAmount() {
+        int sum = CommonValue.ZERO.getValue();
+        for (ReceiptItem receiptItem : receiptItems) {
+            sum += receiptItem.getTotalBuyQuantity() * receiptItem.getPrice();
+        }
+        return sum;
+    }
+
+    public int totalPromotionDiscount() {
+        int sum = CommonValue.ZERO.getValue();
+        for (ReceiptItem receiptItem : receiptItems) {
+            if (receiptItem.getGetQuantity() != CommonValue.ZERO.getValue()) {
+                sum += receiptItem.getGetQuantity() * receiptItem.getPrice();
+            }
+        }
+        return sum;
+    }
+
+    public int getTotalPurchaseCount() {
+        int sum = CommonValue.ZERO.getValue();
+        for (ReceiptItem receiptItem : receiptItems) {
+            sum += receiptItem.getTotalBuyQuantity();
+        }
+        return sum;
+    }
+
+    public int validateMembership(String userAnswer) {
+        if (userAnswer.equals(CommonMessage.YES.getCommonMessage())) {
+            return getMembershipDiscount();
+        }
+        return CommonValue.ZERO.getValue();
+    }
+
+    private int noTotalPromotionAmount() {
+        int sum = CommonValue.ZERO.getValue();
+        for (ReceiptItem receiptItem : receiptItems) {
+            if (receiptItem.getGetQuantity() == CommonValue.ZERO.getValue()) {
+                sum += receiptItem.getBuyQuantity() * receiptItem.getPrice();
+            }
+        }
+        return sum;
+    }
+
+    private int getMembershipDiscount() {
+        int membershipDiscount = (int) (noTotalPromotionAmount() * THIRTY_PERCENT);
+        if (membershipDiscount > CommonValue.EIGHT_THOUSAND.getValue()) {
+            membershipDiscount = CommonValue.EIGHT_THOUSAND.getValue();
+        }
+        return membershipDiscount;
+    }
+
+}

리뷰: int를 double과 곱한 뒤 int로 형변환을 하고 있는데요!
double로 계산되는 과정에서 부동소수점 오류를 피할 수 없을 것으로 보입니다. 이때는 `* 30 / 100` 으로 계산해서 아예 소수점이 안생기도록 계산하시는 것은 어떨까요?
@@ -0,0 +1,38 @@
+package store.domain;
+
+public class GeneralProduct {
+    private final String name;
+    private final String price;
+    private int quantity;
+
+    public GeneralProduct(final String name, final String price, final int quantity) {
+        this.name = name;
+        this.price = price;
+        this.quantity = quantity;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public int getQuantity() {
+        return quantity;
+    }
+
+    public String getPrice() {
+        return price;
+    }
+
+    public void subtraction(int value) {
+        this.quantity -= Math.abs(value);
+    }
+
+    @Override
+    public String toString() {
+        if (quantity == 0) {
+            return String.format("- %s %,d원 재고 없음", name, Integer.parseInt(price));
+        }
+        return String.format("- %s %,d원 %s개", name, Integer.parseInt(price), quantity);
+    }
+
+}

리뷰: 개인적인 궁금증으로 여쭤봅니다
`price`를 `String` 으로 관리하신 특별한 이유가 있으실까요?
@@ -0,0 +1,40 @@
+package store.domain;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+public class Promotion {
+    private final String name;
+    private final int buy;
+    private final int get;
+    private final LocalDate startDate;
+    private final LocalDate endDate;
+
+    public Promotion(final String name, final int buy, final int get, final String startDate, final String endDate) {
+        this.name = name;
+        this.buy = buy;
+        this.get = get;
+        this.startDate = LocalDate.parse(startDate);
+        this.endDate = LocalDate.parse(endDate);
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public int getBuy() {
+        return buy;
+    }
+
+    public int getGet() {
+        return get;
+    }
+
+    public boolean isActive(LocalDateTime dateTime) {
+        LocalDate date = dateTime.toLocalDate();
+        return (date.isEqual(startDate) || date.isAfter(startDate)) &&
+                (date.isEqual(endDate) || date.isBefore(endDate));
+    }
+
+}
+

리뷰: 조건부가 길다보니까 날짜가 기간에 포함되는지 확인하는 메서드를 구현하는 것도 나쁘지 않다고 생각이 들었습니다
```
boolean isBetween(LocalDate date) {
    return !date.isBefore(startDate) && !date.isAfter(endDate);
}
```