ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,35 @@
+package store.controller;
+
+import java.util.List;
+import java.util.Map;
+import store.domain.Product;
+import store.domain.Promotion;
+import store.domain.Receipt;
+import store.domain.ShortageQuantity;
+import store.domain.request.PurchaseRequest;
+import store.service.ConvenienceService;
+
+public class ConvenienceController {
+
+    private final ConvenienceService convenienceService;
+
+    public ConvenienceController(ConvenienceService convenienceService) {
+        this.convenienceService = convenienceService;
+    }
+
+    public Receipt sell(List<PurchaseRequest> purchaseRequests, String membership) {
+        return convenienceService.sell(purchaseRequests, membership);
+    }
+
+    public void setPromotion(Map<String, Promotion> promotions) {
+        convenienceService.setPromotion(promotions);
+    }
+
+    public Map<String, Product> getProducts() {
+        return convenienceService.getProducts();
+    }
+
+    public List<ShortageQuantity> getShortageQuantityForPromotion(List<PurchaseRequest> purchases) {
+        return convenienceService.getShortageQuantityForPromotion(purchases);
+    }
+}

리뷰: 스프링에서 사용하는 Controller, Service, Repository를 사용하면서 각각의 입력이 따로 들어오는 것처럼 작성하신게 되게 신선하게 느껴지네요 하지만 컨트롤러는 입력을 받아 유효성 검사를 하는 역할도 있는데 그 역할이 Convenience로 넘어간게 아쉬움이 있습니다.
Supplier를 활용하여 유효성 검사 실패시 재입력을 받도록 유도하는 것은 어떨까요?
@@ -0,0 +1,35 @@
+package store.controller;
+
+import java.util.List;
+import java.util.Map;
+import store.domain.Product;
+import store.domain.Promotion;
+import store.domain.Receipt;
+import store.domain.ShortageQuantity;
+import store.domain.request.PurchaseRequest;
+import store.service.ConvenienceService;
+
+public class ConvenienceController {
+
+    private final ConvenienceService convenienceService;
+
+    public ConvenienceController(ConvenienceService convenienceService) {
+        this.convenienceService = convenienceService;
+    }
+
+    public Receipt sell(List<PurchaseRequest> purchaseRequests, String membership) {
+        return convenienceService.sell(purchaseRequests, membership);
+    }
+
+    public void setPromotion(Map<String, Promotion> promotions) {
+        convenienceService.setPromotion(promotions);
+    }
+
+    public Map<String, Product> getProducts() {
+        return convenienceService.getProducts();
+    }
+
+    public List<ShortageQuantity> getShortageQuantityForPromotion(List<PurchaseRequest> purchases) {
+        return convenienceService.getShortageQuantityForPromotion(purchases);
+    }
+}

리뷰: 컨트롤러의 역할이 너무 없어서 아쉬움에 남겨봐요. 컨트롤러에서 어떤 처리도 하지 않은채 서비스의 메서드를 호출해서 그렇습니다.
@@ -0,0 +1,72 @@
+package store.convenience;
+
+import static store.domain.Agree.YES;
+
+import java.util.List;
+import java.util.Map;
+import java.util.function.Supplier;
+import store.controller.ConvenienceController;
+import store.domain.Product;
+import store.domain.Receipt;
+import store.domain.ShortageQuantity;
+import store.domain.request.PurchaseRequest;
+import store.view.InputView;
+import store.view.OutputView;
+
+public class Convenience {
+    private final ConvenienceController convenienceController;
+    private final OutputView outputView;
+    private final InputView inputView;
+
+    public Convenience(ConvenienceController convenienceController, OutputView outputView, InputView inputView) {
+        this.convenienceController = convenienceController;
+        this.outputView = outputView;
+        this.inputView = inputView;
+    }
+
+    public void operating() {
+        repurchase(this::operate);
+    }
+
+    public void repurchase(Supplier<String> supplier) {
+        while (supplier.get().equals(YES.getValue())) {}
+    }
+
+    private String operate() {
+        Map<String, Product> products = convenienceController.getProducts();
+        outputView.printProducts(products);
+
+        List<PurchaseRequest> purchases = inputView.inputProductAndQuantity(products);
+
+        List<ShortageQuantity> shortageQuantityForPromotion = convenienceController.getShortageQuantityForPromotion(
+                purchases);
+
+        for (ShortageQuantity shortageQuantity : shortageQuantityForPromotion) {
+            addShortageQuantity(shortageQuantity);
+
+            if (!shortageQuantity.isPromotion()) {
+                String notPromotion = inputView.getNotPromotion(shortageQuantity);
+                if (notPromotion.equals("N")) {
+                    return "Y";
+                }
+            }
+        }
+
+        String membership = inputView.inputMembership();
+        Receipt receipt = convenienceController.sell(purchases, membership);
+
+        outputView.printReceipt(receipt);
+        return inputView.inputRePurchase();
+    }
+
+    private void addShortageQuantity(ShortageQuantity shortageQuantity) {
+        if (shortageQuantity.isPromotion()) {
+            String promotion = inputView.getPromotion(shortageQuantity);
+            if (promotion.equals("Y")) {
+                shortageQuantity.getPurchaseRequest().add(shortageQuantity.getQuantity());
+            }
+        }
+    }
+
+
+}

리뷰: 실제 서버에서 운영되듯이 Controller, Service, Repository를 작성하고 실질적인 흐름을 Convenience로 관리하는 것이 어쩌면 이 부분이 프론트고 나머지가 백엔드같아 굉장히 흥미로웠습니다.
@@ -0,0 +1,72 @@
+package store.convenience;
+
+import static store.domain.Agree.YES;
+
+import java.util.List;
+import java.util.Map;
+import java.util.function.Supplier;
+import store.controller.ConvenienceController;
+import store.domain.Product;
+import store.domain.Receipt;
+import store.domain.ShortageQuantity;
+import store.domain.request.PurchaseRequest;
+import store.view.InputView;
+import store.view.OutputView;
+
+public class Convenience {
+    private final ConvenienceController convenienceController;
+    private final OutputView outputView;
+    private final InputView inputView;
+
+    public Convenience(ConvenienceController convenienceController, OutputView outputView, InputView inputView) {
+        this.convenienceController = convenienceController;
+        this.outputView = outputView;
+        this.inputView = inputView;
+    }
+
+    public void operating() {
+        repurchase(this::operate);
+    }
+
+    public void repurchase(Supplier<String> supplier) {
+        while (supplier.get().equals(YES.getValue())) {}
+    }
+
+    private String operate() {
+        Map<String, Product> products = convenienceController.getProducts();
+        outputView.printProducts(products);
+
+        List<PurchaseRequest> purchases = inputView.inputProductAndQuantity(products);
+
+        List<ShortageQuantity> shortageQuantityForPromotion = convenienceController.getShortageQuantityForPromotion(
+                purchases);
+
+        for (ShortageQuantity shortageQuantity : shortageQuantityForPromotion) {
+            addShortageQuantity(shortageQuantity);
+
+            if (!shortageQuantity.isPromotion()) {
+                String notPromotion = inputView.getNotPromotion(shortageQuantity);
+                if (notPromotion.equals("N")) {
+                    return "Y";
+                }
+            }
+        }
+
+        String membership = inputView.inputMembership();
+        Receipt receipt = convenienceController.sell(purchases, membership);
+
+        outputView.printReceipt(receipt);
+        return inputView.inputRePurchase();
+    }
+
+    private void addShortageQuantity(ShortageQuantity shortageQuantity) {
+        if (shortageQuantity.isPromotion()) {
+            String promotion = inputView.getPromotion(shortageQuantity);
+            if (promotion.equals("Y")) {
+                shortageQuantity.getPurchaseRequest().add(shortageQuantity.getQuantity());
+            }
+        }
+    }
+
+
+}

리뷰: Supplier를 작성하신 분들중에 가장 자연스럽게 사용하신 예시인것 같습니다. 굉장히 재미있네요
@@ -0,0 +1,72 @@
+package store.convenience;
+
+import static store.domain.Agree.YES;
+
+import java.util.List;
+import java.util.Map;
+import java.util.function.Supplier;
+import store.controller.ConvenienceController;
+import store.domain.Product;
+import store.domain.Receipt;
+import store.domain.ShortageQuantity;
+import store.domain.request.PurchaseRequest;
+import store.view.InputView;
+import store.view.OutputView;
+
+public class Convenience {
+    private final ConvenienceController convenienceController;
+    private final OutputView outputView;
+    private final InputView inputView;
+
+    public Convenience(ConvenienceController convenienceController, OutputView outputView, InputView inputView) {
+        this.convenienceController = convenienceController;
+        this.outputView = outputView;
+        this.inputView = inputView;
+    }
+
+    public void operating() {
+        repurchase(this::operate);
+    }
+
+    public void repurchase(Supplier<String> supplier) {
+        while (supplier.get().equals(YES.getValue())) {}
+    }
+
+    private String operate() {
+        Map<String, Product> products = convenienceController.getProducts();
+        outputView.printProducts(products);
+
+        List<PurchaseRequest> purchases = inputView.inputProductAndQuantity(products);
+
+        List<ShortageQuantity> shortageQuantityForPromotion = convenienceController.getShortageQuantityForPromotion(
+                purchases);
+
+        for (ShortageQuantity shortageQuantity : shortageQuantityForPromotion) {
+            addShortageQuantity(shortageQuantity);
+
+            if (!shortageQuantity.isPromotion()) {
+                String notPromotion = inputView.getNotPromotion(shortageQuantity);
+                if (notPromotion.equals("N")) {
+                    return "Y";
+                }
+            }
+        }
+
+        String membership = inputView.inputMembership();
+        Receipt receipt = convenienceController.sell(purchases, membership);
+
+        outputView.printReceipt(receipt);
+        return inputView.inputRePurchase();
+    }
+
+    private void addShortageQuantity(ShortageQuantity shortageQuantity) {
+        if (shortageQuantity.isPromotion()) {
+            String promotion = inputView.getPromotion(shortageQuantity);
+            if (promotion.equals("Y")) {
+                shortageQuantity.getPurchaseRequest().add(shortageQuantity.getQuantity());
+            }
+        }
+    }
+
+
+}

리뷰: N이나 Y인 문자열보다 boolean을 사용했으면 어떨까라는 생각이 들어요
@@ -0,0 +1,72 @@
+package store.convenience;
+
+import static store.domain.Agree.YES;
+
+import java.util.List;
+import java.util.Map;
+import java.util.function.Supplier;
+import store.controller.ConvenienceController;
+import store.domain.Product;
+import store.domain.Receipt;
+import store.domain.ShortageQuantity;
+import store.domain.request.PurchaseRequest;
+import store.view.InputView;
+import store.view.OutputView;
+
+public class Convenience {
+    private final ConvenienceController convenienceController;
+    private final OutputView outputView;
+    private final InputView inputView;
+
+    public Convenience(ConvenienceController convenienceController, OutputView outputView, InputView inputView) {
+        this.convenienceController = convenienceController;
+        this.outputView = outputView;
+        this.inputView = inputView;
+    }
+
+    public void operating() {
+        repurchase(this::operate);
+    }
+
+    public void repurchase(Supplier<String> supplier) {
+        while (supplier.get().equals(YES.getValue())) {}
+    }
+
+    private String operate() {
+        Map<String, Product> products = convenienceController.getProducts();
+        outputView.printProducts(products);
+
+        List<PurchaseRequest> purchases = inputView.inputProductAndQuantity(products);
+
+        List<ShortageQuantity> shortageQuantityForPromotion = convenienceController.getShortageQuantityForPromotion(
+                purchases);
+
+        for (ShortageQuantity shortageQuantity : shortageQuantityForPromotion) {
+            addShortageQuantity(shortageQuantity);
+
+            if (!shortageQuantity.isPromotion()) {
+                String notPromotion = inputView.getNotPromotion(shortageQuantity);
+                if (notPromotion.equals("N")) {
+                    return "Y";
+                }
+            }
+        }
+
+        String membership = inputView.inputMembership();
+        Receipt receipt = convenienceController.sell(purchases, membership);
+
+        outputView.printReceipt(receipt);
+        return inputView.inputRePurchase();
+    }
+
+    private void addShortageQuantity(ShortageQuantity shortageQuantity) {
+        if (shortageQuantity.isPromotion()) {
+            String promotion = inputView.getPromotion(shortageQuantity);
+            if (promotion.equals("Y")) {
+                shortageQuantity.getPurchaseRequest().add(shortageQuantity.getQuantity());
+            }
+        }
+    }
+
+
+}

리뷰: enum으로 Agree를 만드셨는데 해당 클래스를 통해 구별하거나 "Y"같은 값을 enum에서 가져와 상수처럼 처리하는 것은 어떨까요?
Agree에서 따로 메서드를 만드는 것도 좋을 것 같습니다.
@@ -0,0 +1,74 @@
+package store.domain;
+
+public class Product {
+    private final String name;
+    private final int price;
+    private int quantity;
+    private int promotionQuantity;
+    private Promotion promotion;
+
+    public Product(String name, int price, int promotionQuantity, Promotion promotion) {
+        this.name = name;
+        this.price = price;
+        this.promotionQuantity = promotionQuantity;
+        this.promotion = promotion;
+    }
+
+    public Product(String name, int price, int quantity) {
+        this.name = name;
+        this.price = price;
+        this.quantity = quantity;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public int getPrice() {
+        return price;
+    }
+
+    public int getQuantity() {
+        return quantity;
+    }
+
+    public int getPromotionQuantity() {
+        return promotionQuantity;
+    }
+
+    public Promotion getPromotion() {
+        return promotion;
+    }
+
+    public boolean existsPromotion() {
+        return promotion != null;
+    }
+
+    @Override
+    public String toString() {
+        return "Product{" +
+                "name='" + name + '\'' +
+                ", price=" + price +
+                ", quantity=" + quantity +
+                ", promotionQuantity=" + promotionQuantity +
+                ", promotion=" + promotion +
+                '}';
+    }
+
+    public void add(int quantity) {
+        this.quantity += quantity;
+    }
+
+
+    public int getShortageQuantityForPromotion(int purchaseQuantity) {
+        if (promotionQuantity < purchaseQuantity) {
+            return promotionQuantity % (promotion.getBuy() + promotion.getGet());
+        }
+
+        int shortage = purchaseQuantity % (promotion.getBuy() + promotion.getGet());
+        if (shortage == 0) {
+            return 0;
+        }
+        return (promotion.getBuy() + promotion.getGet()) - shortage;
+    }
+}

리뷰: 저와 가장 비슷하게 Product의 필드가 구성된 사람을 봐서 신기하네요. name과 price는 변경되지 않아 final로 적으신게 꼼꼼함이 보입니다.
@@ -0,0 +1,46 @@
+package store.domain;
+
+import java.time.LocalDate;
+
+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(String name, int buy, int get, LocalDate startDate, LocalDate endDate) {
+        this.name = name;
+        this.buy = buy;
+        this.get = get;
+        this.startDate = startDate;
+        this.endDate = endDate;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public int getBuy() {
+        return buy;
+    }
+
+    public int getGet() {
+        return get;
+    }
+
+    @Override
+    public String toString() {
+        return "Promotion{" +
+                "name='" + name + '\'' +
+                ", buy=" + buy +
+                ", get=" + get +
+                ", startDate=" + startDate +
+                ", endDate=" + endDate +
+                '}';
+    }
+
+    public boolean isRangePromotion(LocalDate now) {
+        return now.isAfter(startDate) && now.isBefore(endDate);
+    }
+}

리뷰: 이건 디버깅을 위해 넣은 것일까요?
@@ -0,0 +1,46 @@
+package store.domain;
+
+import java.time.LocalDate;
+
+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(String name, int buy, int get, LocalDate startDate, LocalDate endDate) {
+        this.name = name;
+        this.buy = buy;
+        this.get = get;
+        this.startDate = startDate;
+        this.endDate = endDate;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public int getBuy() {
+        return buy;
+    }
+
+    public int getGet() {
+        return get;
+    }
+
+    @Override
+    public String toString() {
+        return "Promotion{" +
+                "name='" + name + '\'' +
+                ", buy=" + buy +
+                ", get=" + get +
+                ", startDate=" + startDate +
+                ", endDate=" + endDate +
+                '}';
+    }
+
+    public boolean isRangePromotion(LocalDate now) {
+        return now.isAfter(startDate) && now.isBefore(endDate);
+    }
+}

리뷰: 저도 Buy와 Get을 따로 만들었었지만 요구사항에 N+1이라는 글을 봐서 get은 항상 1이겠구나 생각하고 나중에 연산에 큰 도움이 되지않았던것 같습니다
@@ -0,0 +1,46 @@
+package store.domain;
+
+import java.time.LocalDate;
+
+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(String name, int buy, int get, LocalDate startDate, LocalDate endDate) {
+        this.name = name;
+        this.buy = buy;
+        this.get = get;
+        this.startDate = startDate;
+        this.endDate = endDate;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public int getBuy() {
+        return buy;
+    }
+
+    public int getGet() {
+        return get;
+    }
+
+    @Override
+    public String toString() {
+        return "Promotion{" +
+                "name='" + name + '\'' +
+                ", buy=" + buy +
+                ", get=" + get +
+                ", startDate=" + startDate +
+                ", endDate=" + endDate +
+                '}';
+    }
+
+    public boolean isRangePromotion(LocalDate now) {
+        return now.isAfter(startDate) && now.isBefore(endDate);
+    }
+}

리뷰: 시작날짜 이상 끝난 날짜이하 즉 시작과 끝을 포함하는 것으로 요구사항을 알고 있는데 isAfter과 isBefore은 동일한 날짜를 만나면 false로 나온다고 알고 있습니다.
@@ -0,0 +1,32 @@
+package store.domain.request;
+
+public class PurchaseRequest {
+    private final String name;
+    private int quantity;
+
+
+    public PurchaseRequest(String name, int quantity) {
+        this.name = name;
+        this.quantity = quantity;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public int getQuantity() {
+        return quantity;
+    }
+
+    @Override
+    public String toString() {
+        return "Purchase{" +
+                "name='" + name + '\'' +
+                ", quantity=" + quantity +
+                '}';
+    }
+
+    public void add(int quantity) {
+        this.quantity += quantity;
+    }
+}

리뷰: request의 경우 view의 패키지안에 존재해야한다고 생각합니다. Domain은 데이터를 저장하는 저장소인데 domain과 dto중에 dto에 더 가까운 클래스인것 같습니다.
@@ -0,0 +1,11 @@
+package store.repository;
+
+import java.util.Map;
+import store.domain.Product;
+
+public record ProductRepository(Map<String, Product> products) {
+
+    public Product findProduct(String name) {
+        return products.get(name);
+    }
+}

리뷰: StoreFileReader에서 먼저 만든 Map을 여기서 만들었으면 좋겠습니다. 저희가 Controller나 Service에서 데이터베이스의 저장 방식을 건들지 않는 이유와 같은 것 같습니다.
@@ -0,0 +1,52 @@
+package store.service;
+
+import java.util.List;
+import java.util.Map;
+import store.domain.Product;
+import store.domain.Promotion;
+import store.domain.Purchase;
+import store.domain.Receipt;
+import store.domain.ShortageQuantity;
+import store.domain.request.PurchaseRequest;
+
+public class ConvenienceService {
+    private final ProductService productService;
+    private final PromotionService promotionService;
+
+    public ConvenienceService(ProductService productService, PromotionService promotionService) {
+        this.productService = productService;
+        this.promotionService = promotionService;
+    }
+
+    public Receipt sell(List<PurchaseRequest> purchaseRequests, String membership) {
+        return calculate(purchaseRequests, membership, productService.getProducts());
+    }
+
+    private Receipt calculate(List<PurchaseRequest> purchaseRequests, String membership, Map<String, Product> products) {
+        List<Purchase> purchases = purchaseRequests.stream()
+                .filter(purchaseRequest -> products.containsKey(purchaseRequest.getName()))
+                .map(purchaseRequest -> {
+                    Product product = products.get(purchaseRequest.getName());
+                    return new Purchase(
+                            purchaseRequest.getName(),
+                            purchaseRequest.getQuantity(),
+                            product.getPrice(),
+                            0);
+                }).toList();
+        return new Receipt(purchases, membership);
+    }
+
+
+    public void setPromotion(Map<String, Promotion> promotions) {
+        promotionService.setPromotion(promotions);
+    }
+
+    public Map<String, Product> getProducts() {
+        return productService.getProducts();
+    }
+
+    public List<ShortageQuantity> getShortageQuantityForPromotion(List<PurchaseRequest> purchases) {
+        return productService.getShortageQuantityForPromotion(purchases);
+    }
+
+}

리뷰: 메서드를 따로 뽑아내면 더 좋을것 같습니다.
@@ -0,0 +1,52 @@
+package store.service;
+
+import java.util.List;
+import java.util.Map;
+import store.domain.Product;
+import store.domain.Promotion;
+import store.domain.Purchase;
+import store.domain.Receipt;
+import store.domain.ShortageQuantity;
+import store.domain.request.PurchaseRequest;
+
+public class ConvenienceService {
+    private final ProductService productService;
+    private final PromotionService promotionService;
+
+    public ConvenienceService(ProductService productService, PromotionService promotionService) {
+        this.productService = productService;
+        this.promotionService = promotionService;
+    }
+
+    public Receipt sell(List<PurchaseRequest> purchaseRequests, String membership) {
+        return calculate(purchaseRequests, membership, productService.getProducts());
+    }
+
+    private Receipt calculate(List<PurchaseRequest> purchaseRequests, String membership, Map<String, Product> products) {
+        List<Purchase> purchases = purchaseRequests.stream()
+                .filter(purchaseRequest -> products.containsKey(purchaseRequest.getName()))
+                .map(purchaseRequest -> {
+                    Product product = products.get(purchaseRequest.getName());
+                    return new Purchase(
+                            purchaseRequest.getName(),
+                            purchaseRequest.getQuantity(),
+                            product.getPrice(),
+                            0);
+                }).toList();
+        return new Receipt(purchases, membership);
+    }
+
+
+    public void setPromotion(Map<String, Promotion> promotions) {
+        promotionService.setPromotion(promotions);
+    }
+
+    public Map<String, Product> getProducts() {
+        return productService.getProducts();
+    }
+
+    public List<ShortageQuantity> getShortageQuantityForPromotion(List<PurchaseRequest> purchases) {
+        return productService.getShortageQuantityForPromotion(purchases);
+    }
+
+}

리뷰: 해당 제품이 없는 경우 filter로 처리하였는데 기존에 존재하지 않은 제품의 경우 에러는 무시하고 넘어가는 것일까요?