ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,86 @@
+package store.model;
+
+import java.util.List;
+
+public class Products {
+    public static final int HAVE_PROMOTION_BENEFIT_SIZE = 2;
+    public static final int HAVE_NO_PROMOTION_BENEFIT_SIZE = 1;
+    List<Product> products;
+
+    public Products(List<Product> products) {
+        this.products = products;
+    }
+
+    public boolean isGetOneFree(Order order) {
+        Product promotion = getPromotionProduct();
+        return isPromotionPeriod() &&
+                isPromotionMoreThanOrder(order, promotion) &&
+                isPromotionBenefitPossibleLeft(order, promotion) &&
+                isPromotionStockEnough(order, promotion);
+    }
+
+    public boolean isBuyOriginalPrice(Order order) {
+        return countBuyOriginalPrice(order) > 0;
+    }
+
+    public boolean isPromotionPeriod() {
+        Product promotion = getPromotionProduct();
+        return promotion.isPromotionPeriod();
+    }
+
+    private boolean isPromotionMoreThanOrder(Order order, Product promotion) {
+        return promotion.getQuantity() > order.getQuantity();
+    }
+
+    private boolean isPromotionBenefitPossibleLeft(Order order, Product promotion) {
+        return promotion.isPromotionBenefitPossibleLeft(order);
+    }
+
+    private boolean isPromotionStockEnough(Order order, Product promotion) {
+        return promotion.getQuantity() >= order.getQuantity() + 1;
+    }
+
+    public int countBuyOriginalPrice(Order order) {
+        Product promotion = getPromotionProduct();
+        if (promotion.isPromotionPeriod() && !isPromotionMoreThanOrder(order, promotion)) {
+            return promotion.getNoPromotionBenefit(promotion.getQuantity()) + order.getQuantity() - promotion.getQuantity();
+        }
+        return 0;
+    }
+
+    public int countReducePromotionWhen(Order order) {
+        return order.getQuantity() - countBuyOriginalPrice(order);
+    }
+
+    public void doNotOrderOriginalPrice(Order order) {
+        order.decreaseQuantity(countBuyOriginalPrice(order));
+    }
+
+    public boolean isOrderQuantityBuyOnlyPromotionStock(Order order) {
+        return countBuyOriginalPrice(order) == order.getQuantity();
+    }
+
+    public Product getPromotionProduct() {
+        if (products.size() == HAVE_PROMOTION_BENEFIT_SIZE) {
+            return products.get(0);
+        }
+        if (products.size() == HAVE_NO_PROMOTION_BENEFIT_SIZE) {
+            return new Product(getNormalProduct().getName() + "," + getNormalProduct().getPrice() + ",0,null");
+        }
+        throw new IllegalArgumentException();
+    }
+
+    public Product getNormalProduct() {
+        if (products.size() == HAVE_PROMOTION_BENEFIT_SIZE) {
+            return products.get(1);
+        }
+        if (products.size() == HAVE_NO_PROMOTION_BENEFIT_SIZE) {
+            return products.get(0);
+        }
+        throw new IllegalArgumentException();
+    }
+
+    public List<Product> getProducts() {
+        return products;
+    }
+}

리뷰: 새로운 클래스를 통해 해당 부분을 메서드로 만든다면 더 좋을 것 같습니다.
@@ -0,0 +1,86 @@
+package store.model;
+
+import java.util.List;
+
+public class Products {
+    public static final int HAVE_PROMOTION_BENEFIT_SIZE = 2;
+    public static final int HAVE_NO_PROMOTION_BENEFIT_SIZE = 1;
+    List<Product> products;
+
+    public Products(List<Product> products) {
+        this.products = products;
+    }
+
+    public boolean isGetOneFree(Order order) {
+        Product promotion = getPromotionProduct();
+        return isPromotionPeriod() &&
+                isPromotionMoreThanOrder(order, promotion) &&
+                isPromotionBenefitPossibleLeft(order, promotion) &&
+                isPromotionStockEnough(order, promotion);
+    }
+
+    public boolean isBuyOriginalPrice(Order order) {
+        return countBuyOriginalPrice(order) > 0;
+    }
+
+    public boolean isPromotionPeriod() {
+        Product promotion = getPromotionProduct();
+        return promotion.isPromotionPeriod();
+    }
+
+    private boolean isPromotionMoreThanOrder(Order order, Product promotion) {
+        return promotion.getQuantity() > order.getQuantity();
+    }
+
+    private boolean isPromotionBenefitPossibleLeft(Order order, Product promotion) {
+        return promotion.isPromotionBenefitPossibleLeft(order);
+    }
+
+    private boolean isPromotionStockEnough(Order order, Product promotion) {
+        return promotion.getQuantity() >= order.getQuantity() + 1;
+    }
+
+    public int countBuyOriginalPrice(Order order) {
+        Product promotion = getPromotionProduct();
+        if (promotion.isPromotionPeriod() && !isPromotionMoreThanOrder(order, promotion)) {
+            return promotion.getNoPromotionBenefit(promotion.getQuantity()) + order.getQuantity() - promotion.getQuantity();
+        }
+        return 0;
+    }
+
+    public int countReducePromotionWhen(Order order) {
+        return order.getQuantity() - countBuyOriginalPrice(order);
+    }
+
+    public void doNotOrderOriginalPrice(Order order) {
+        order.decreaseQuantity(countBuyOriginalPrice(order));
+    }
+
+    public boolean isOrderQuantityBuyOnlyPromotionStock(Order order) {
+        return countBuyOriginalPrice(order) == order.getQuantity();
+    }
+
+    public Product getPromotionProduct() {
+        if (products.size() == HAVE_PROMOTION_BENEFIT_SIZE) {
+            return products.get(0);
+        }
+        if (products.size() == HAVE_NO_PROMOTION_BENEFIT_SIZE) {
+            return new Product(getNormalProduct().getName() + "," + getNormalProduct().getPrice() + ",0,null");
+        }
+        throw new IllegalArgumentException();
+    }
+
+    public Product getNormalProduct() {
+        if (products.size() == HAVE_PROMOTION_BENEFIT_SIZE) {
+            return products.get(1);
+        }
+        if (products.size() == HAVE_NO_PROMOTION_BENEFIT_SIZE) {
+            return products.get(0);
+        }
+        throw new IllegalArgumentException();
+    }
+
+    public List<Product> getProducts() {
+        return products;
+    }
+}

리뷰: 새로운 생성자를 만들어 처리한다면 이런 작업이 필요 없을 것 같습니다.
@@ -0,0 +1,86 @@
+package store.model;
+
+import java.util.List;
+
+public class Products {
+    public static final int HAVE_PROMOTION_BENEFIT_SIZE = 2;
+    public static final int HAVE_NO_PROMOTION_BENEFIT_SIZE = 1;
+    List<Product> products;
+
+    public Products(List<Product> products) {
+        this.products = products;
+    }
+
+    public boolean isGetOneFree(Order order) {
+        Product promotion = getPromotionProduct();
+        return isPromotionPeriod() &&
+                isPromotionMoreThanOrder(order, promotion) &&
+                isPromotionBenefitPossibleLeft(order, promotion) &&
+                isPromotionStockEnough(order, promotion);
+    }
+
+    public boolean isBuyOriginalPrice(Order order) {
+        return countBuyOriginalPrice(order) > 0;
+    }
+
+    public boolean isPromotionPeriod() {
+        Product promotion = getPromotionProduct();
+        return promotion.isPromotionPeriod();
+    }
+
+    private boolean isPromotionMoreThanOrder(Order order, Product promotion) {
+        return promotion.getQuantity() > order.getQuantity();
+    }
+
+    private boolean isPromotionBenefitPossibleLeft(Order order, Product promotion) {
+        return promotion.isPromotionBenefitPossibleLeft(order);
+    }
+
+    private boolean isPromotionStockEnough(Order order, Product promotion) {
+        return promotion.getQuantity() >= order.getQuantity() + 1;
+    }
+
+    public int countBuyOriginalPrice(Order order) {
+        Product promotion = getPromotionProduct();
+        if (promotion.isPromotionPeriod() && !isPromotionMoreThanOrder(order, promotion)) {
+            return promotion.getNoPromotionBenefit(promotion.getQuantity()) + order.getQuantity() - promotion.getQuantity();
+        }
+        return 0;
+    }
+
+    public int countReducePromotionWhen(Order order) {
+        return order.getQuantity() - countBuyOriginalPrice(order);
+    }
+
+    public void doNotOrderOriginalPrice(Order order) {
+        order.decreaseQuantity(countBuyOriginalPrice(order));
+    }
+
+    public boolean isOrderQuantityBuyOnlyPromotionStock(Order order) {
+        return countBuyOriginalPrice(order) == order.getQuantity();
+    }
+
+    public Product getPromotionProduct() {
+        if (products.size() == HAVE_PROMOTION_BENEFIT_SIZE) {
+            return products.get(0);
+        }
+        if (products.size() == HAVE_NO_PROMOTION_BENEFIT_SIZE) {
+            return new Product(getNormalProduct().getName() + "," + getNormalProduct().getPrice() + ",0,null");
+        }
+        throw new IllegalArgumentException();
+    }
+
+    public Product getNormalProduct() {
+        if (products.size() == HAVE_PROMOTION_BENEFIT_SIZE) {
+            return products.get(1);
+        }
+        if (products.size() == HAVE_NO_PROMOTION_BENEFIT_SIZE) {
+            return products.get(0);
+        }
+        throw new IllegalArgumentException();
+    }
+
+    public List<Product> getProducts() {
+        return products;
+    }
+}

리뷰: 1, 0에 해당하는 인덱스가 어떤 제품의 인덱스인지 표시할 상수를 작성하면 더 좋을 것 같습니다.
@@ -0,0 +1,60 @@
+package store.model;
+
+import camp.nextstep.edu.missionutils.DateTimes;
+import store.util.Reader;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.List;
+
+public class PromotionPolicy {
+    private int promotionBuyCount;
+    private int promotionGetCount;
+    private String startDate;
+    private String endDate;
+
+    public PromotionPolicy(String productPromotion) {
+        promotionBuyCount = 0;
+        promotionGetCount = 0;
+        startDate = "9999-12-31";
+        endDate = "9999-12-31";
+        initPromotionInfo(productPromotion);
+    }
+
+    private void initPromotionInfo(String productPromotion) {
+        Reader reader = new Reader();
+        List<String> lines = reader.readLines("src/main/resources/promotions.md").stream().skip(1).toList();
+        lines.forEach(line -> updatePolicy(line, productPromotion));
+    }
+
+    private void updatePolicy(String line, String productPromotion) {
+        String[] promotionInfo = line.split(",");
+        String promotionName = promotionInfo[0];
+        if (promotionName.equals(productPromotion)) {
+            promotionBuyCount = Integer.parseInt(promotionInfo[1]);
+            promotionGetCount = Integer.parseInt(promotionInfo[2]);
+            startDate = promotionInfo[3];
+            endDate = promotionInfo[4];
+        }
+    }
+
+    public boolean isValidPeriod() {
+        LocalDateTime targetTime = DateTimes.now();
+
+        String[] startInfo = startDate.split("-");
+        String[] endInfo = endDate.split("-");
+
+        LocalDate targetDate = LocalDate.of(targetTime.getYear(), targetTime.getMonth(), targetTime.getDayOfMonth());
+        LocalDate startDate = LocalDate.of(Integer.parseInt(startInfo[0]), Integer.parseInt(startInfo[1]), Integer.parseInt(startInfo[2]));
+        LocalDate endDate = LocalDate.of(Integer.parseInt(endInfo[0]), Integer.parseInt(endInfo[1]), Integer.parseInt(endInfo[2]));
+        return !targetDate.isBefore(startDate) && !targetDate.isAfter(endDate);
+    }
+
+    public int getPromotionCount() {
+        return promotionBuyCount + promotionGetCount;
+    }
+
+    public int getPromotionBuyCount() {
+        return promotionBuyCount;
+    }
+}

리뷰: LocalDate.parse()를 사용하면 더 쉽게 바꿀 수 있으니 이를 참고하시면 좋을 것 같습니다.
@@ -0,0 +1,45 @@
+package store.strategy.PromotionPeriod;
+
+import store.model.Order;
+import store.model.Product;
+import store.model.Products;
+import store.strategy.StockManager;
+
+public class BuyOriginalPrice implements StockManager {
+    private final boolean canGetDiscount;
+
+    public BuyOriginalPrice(Order order, Products products) {
+        this.canGetDiscount = products.isOrderQuantityBuyOnlyPromotionStock(order);
+    }
+
+    @Override
+    public int calculateNormalReduction(Order order, Products products, boolean getOriginalPrice) {
+        Product promotion = products.getPromotionProduct();
+
+        int reduceNormal = 0;
+        if (promotion.getQuantity() < order.getQuantity()) {
+            reduceNormal = order.getQuantity() - promotion.getQuantity();
+        }
+        if (!getOriginalPrice) {
+            reduceNormal = 0;
+        }
+        return reduceNormal;
+    }
+
+    @Override
+    public int calculatePromotionReduction(Order order, Products products, boolean getOriginalPrice) {
+        Product promotion = products.getPromotionProduct();
+
+        int reducePromotion = promotion.getQuantity();
+        if (!getOriginalPrice) {
+            reducePromotion = products.countReducePromotionWhen(order);
+            products.doNotOrderOriginalPrice(order);
+        }
+        return reducePromotion;
+    }
+
+    @Override
+    public boolean getCanGetDiscount() {
+        return canGetDiscount;
+    }
+}

리뷰: 이렇게 여러가지로 분류되는 것이라면 상속도 좋지만 enum은 어떤가요?
@@ -0,0 +1,15 @@
+package store.util;
+
+public interface Task<T> {
+    static <T> T repeatUntilValid(Task<T> task) {
+        while (true) {
+            try {
+                return task.run();
+            } catch (IllegalArgumentException e) {
+                System.out.println(e.getMessage());
+            }
+        }
+    }
+
+    T run();
+}

리뷰: 이게 반복하는 부분이였네요 Supplier를 사용하신다면 더 편하게 할 수 있을 것 같습니다.
@@ -0,0 +1,67 @@
+package store.product.controller;
+
+import java.util.ArrayList;
+import java.util.List;
+import store.error.ErrorMessage;
+import store.product.domain.PurchaseProduct;
+import store.product.dto.PurchaseProductRequest;
+import store.product.service.ProductService;
+import store.product.view.ProductOutputView;
+import store.state.PurchaseState;
+import store.util.LoopTemplate;
+import store.util.StringParser;
+import store.util.StringValidator;
+import store.view.InputView;
+
+public class ProductController {
+
+    private final InputView inputView;
+    private final ProductOutputView outputView;
+    private final ProductService productService;
+
+    public ProductController(final InputView inputView, final ProductOutputView outputView,
+                             final ProductService productService) {
+        this.inputView = inputView;
+        this.outputView = outputView;
+        this.productService = productService;
+    }
+
+    public void run() {
+        responseProductStatuses();
+        final List<PurchaseProduct> purchaseProducts = requestPurchaseProduct();
+        final PurchaseState purchaseState = PurchaseState.getInstance();
+        purchaseState.updatePurchaseProducts(purchaseProducts);
+    }
+
+    private void responseProductStatuses() {
+        outputView.printIntro();
+        outputView.printProductStatus(productService.getProductStatuses());
+    }
+
+    private List<PurchaseProduct> requestPurchaseProduct() {
+        return LoopTemplate.tryCatchLoop(() -> {
+            outputView.printAskPurchaseProduct();
+            final List<String> products = inputView.readProducts();
+            final List<PurchaseProductRequest> purchaseProductRequests = generatePurchaseProductRequests(products);
+            return productService.generatePurchaseProducts(purchaseProductRequests);
+        });
+    }
+
+    private List<PurchaseProductRequest> generatePurchaseProductRequests(final List<String> values) {
+        final List<PurchaseProductRequest> purchaseProductRequests = new ArrayList<>();
+        for (String value : values) {
+            StringValidator.validateFormat(value, ErrorMessage.INVALID_FORMAT_PRODUCT_AND_QUANTITY);
+            final String product = StringParser.removePattern(value.trim(), "[\\[\\]]");
+            purchaseProductRequests.add(createPurchaseProductRequest(product));
+        }
+        return purchaseProductRequests;
+    }
+
+    private PurchaseProductRequest createPurchaseProductRequest(final String product) {
+        final String[] splitProduct = product.split("-");
+        final String name = splitProduct[0];
+        final int quantity = StringParser.parseToNumber(splitProduct[1]);
+        return new PurchaseProductRequest(name, quantity);
+    }
+
+}

리뷰: #### 📚 배워갑니다. 🦦 좋은 코드 감사합니다!
try - catch 구조를 템플릿 패턴으로  정형화 한 방식 좋은 것 같습니다. 
이번에 이 구조를 공부하고 적용해서 최종코테에 적용할 수 있도록 하고 싶은 마음이 뿜뿜들어요 ㅋㅋ
그리고 좀 여기서 더 개선할 수 있는지도 찾아보고 싶네요.
@@ -0,0 +1,67 @@
+package store.product.controller;
+
+import java.util.ArrayList;
+import java.util.List;
+import store.error.ErrorMessage;
+import store.product.domain.PurchaseProduct;
+import store.product.dto.PurchaseProductRequest;
+import store.product.service.ProductService;
+import store.product.view.ProductOutputView;
+import store.state.PurchaseState;
+import store.util.LoopTemplate;
+import store.util.StringParser;
+import store.util.StringValidator;
+import store.view.InputView;
+
+public class ProductController {
+
+    private final InputView inputView;
+    private final ProductOutputView outputView;
+    private final ProductService productService;
+
+    public ProductController(final InputView inputView, final ProductOutputView outputView,
+                             final ProductService productService) {
+        this.inputView = inputView;
+        this.outputView = outputView;
+        this.productService = productService;
+    }
+
+    public void run() {
+        responseProductStatuses();
+        final List<PurchaseProduct> purchaseProducts = requestPurchaseProduct();
+        final PurchaseState purchaseState = PurchaseState.getInstance();
+        purchaseState.updatePurchaseProducts(purchaseProducts);
+    }
+
+    private void responseProductStatuses() {
+        outputView.printIntro();
+        outputView.printProductStatus(productService.getProductStatuses());
+    }
+
+    private List<PurchaseProduct> requestPurchaseProduct() {
+        return LoopTemplate.tryCatchLoop(() -> {
+            outputView.printAskPurchaseProduct();
+            final List<String> products = inputView.readProducts();
+            final List<PurchaseProductRequest> purchaseProductRequests = generatePurchaseProductRequests(products);
+            return productService.generatePurchaseProducts(purchaseProductRequests);
+        });
+    }
+
+    private List<PurchaseProductRequest> generatePurchaseProductRequests(final List<String> values) {
+        final List<PurchaseProductRequest> purchaseProductRequests = new ArrayList<>();
+        for (String value : values) {
+            StringValidator.validateFormat(value, ErrorMessage.INVALID_FORMAT_PRODUCT_AND_QUANTITY);
+            final String product = StringParser.removePattern(value.trim(), "[\\[\\]]");
+            purchaseProductRequests.add(createPurchaseProductRequest(product));
+        }
+        return purchaseProductRequests;
+    }
+
+    private PurchaseProductRequest createPurchaseProductRequest(final String product) {
+        final String[] splitProduct = product.split("-");
+        final String name = splitProduct[0];
+        final int quantity = StringParser.parseToNumber(splitProduct[1]);
+        return new PurchaseProductRequest(name, quantity);
+    }
+
+}

리뷰: #### 📚 배워갑니다. 🦦 좋은 코드 감사합니다!

[ ] 을 지울 때 replaceAll 메서드가 있었군요..  이것도 메모해 둬야겠습니다 😄

----
보여주기 부끄러운 저의 코드 :
```java
    private String extractContentFromBrackets(final String input) {
        return input.substring(1, input.length() - 1);
    }```
@@ -0,0 +1,68 @@
+package store.product.domain;
+
+import java.util.Objects;
+import store.promotion.domain.Promotion;
+
+public class Product {
+
+    private static final String BLANK = "";
+    private static final int ZERO  = 0;
+    private final long id;
+    private final ProductInfo productInfo;
+    private int quantity;
+
+    public Product(final Long id, final ProductInfo productInfo, final int quantity) {
+        this.id = id;
+        this.productInfo = productInfo;
+        this.quantity = quantity;
+    }
+
+    public Long getId() {
+        return this.id;
+    }
+
+    public void updateQuantity(final int quantity) {
+        this.quantity = quantity;
+    }
+
+    public String getPromotionName() {
+        final Promotion promotion = productInfo.promotion();
+        if (Objects.equals(promotion, null)) {
+            return BLANK;
+        }
+        return promotion.getPromotionName();
+    }
+
+    public boolean canReceiveMorePromotion(final int quantity) {
+        return productInfo.promotion().canPromotion(quantity);
+    }
+
+
+    public int calculateRemainingQuantity(final int quantity) {
+        final Promotion promotion = productInfo.promotion();
+        if (!Objects.equals(promotion, null)) {
+            return promotion.getRemainingQuantity(quantity);
+        }
+        return ZERO;
+    }
+
+    public int getBuyQuantity() {
+        return productInfo.promotion().getBuy();
+    }
+
+    public int getPrice() {
+        return productInfo.price();
+    }
+
+    public String getName() {
+        return productInfo.name();
+    }
+
+    public Promotion getPromotion() {
+        return productInfo.promotion();
+    }
+
+    public int getQuantity() {
+        return quantity;
+    }
+}

리뷰: ### 𝐐. Product 도메인의 설계 과정이 궁금합니다.
#### 질문의도:
이번에 프르모션 할인 때문에 Product와 Promotion은 개인적으로 서로가 필요한 관계처럼 느껴졌었습니다.
그래서  제겐 도메인 설계가  큰 과제였습니다. 그래서 이렇게 구상하시게 된 이유를 들어보고 싶습니다. 🥹

아래 사진은 수달님이 처음 설계하셨던 Product, 이후 최종 수정된 Product 입니다.
<img width="1188" alt="image" src="https://github.com/user-attachments/assets/d87d75f0-6788-46c9-bc8b-836599692032">
@@ -0,0 +1,67 @@
+package store.product.controller;
+
+import java.util.ArrayList;
+import java.util.List;
+import store.error.ErrorMessage;
+import store.product.domain.PurchaseProduct;
+import store.product.dto.PurchaseProductRequest;
+import store.product.service.ProductService;
+import store.product.view.ProductOutputView;
+import store.state.PurchaseState;
+import store.util.LoopTemplate;
+import store.util.StringParser;
+import store.util.StringValidator;
+import store.view.InputView;
+
+public class ProductController {
+
+    private final InputView inputView;
+    private final ProductOutputView outputView;
+    private final ProductService productService;
+
+    public ProductController(final InputView inputView, final ProductOutputView outputView,
+                             final ProductService productService) {
+        this.inputView = inputView;
+        this.outputView = outputView;
+        this.productService = productService;
+    }
+
+    public void run() {
+        responseProductStatuses();
+        final List<PurchaseProduct> purchaseProducts = requestPurchaseProduct();
+        final PurchaseState purchaseState = PurchaseState.getInstance();
+        purchaseState.updatePurchaseProducts(purchaseProducts);
+    }
+
+    private void responseProductStatuses() {
+        outputView.printIntro();
+        outputView.printProductStatus(productService.getProductStatuses());
+    }
+
+    private List<PurchaseProduct> requestPurchaseProduct() {
+        return LoopTemplate.tryCatchLoop(() -> {
+            outputView.printAskPurchaseProduct();
+            final List<String> products = inputView.readProducts();
+            final List<PurchaseProductRequest> purchaseProductRequests = generatePurchaseProductRequests(products);
+            return productService.generatePurchaseProducts(purchaseProductRequests);
+        });
+    }
+
+    private List<PurchaseProductRequest> generatePurchaseProductRequests(final List<String> values) {
+        final List<PurchaseProductRequest> purchaseProductRequests = new ArrayList<>();
+        for (String value : values) {
+            StringValidator.validateFormat(value, ErrorMessage.INVALID_FORMAT_PRODUCT_AND_QUANTITY);
+            final String product = StringParser.removePattern(value.trim(), "[\\[\\]]");
+            purchaseProductRequests.add(createPurchaseProductRequest(product));
+        }
+        return purchaseProductRequests;
+    }
+
+    private PurchaseProductRequest createPurchaseProductRequest(final String product) {
+        final String[] splitProduct = product.split("-");
+        final String name = splitProduct[0];
+        final int quantity = StringParser.parseToNumber(splitProduct[1]);
+        return new PurchaseProductRequest(name, quantity);
+    }
+
+}

리뷰: 감사합니다~ ☺️