ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,56 @@
+package store.domain;
+
+import static store.message.ErrorMessage.INSUFFICIENT_STOCK_ERROR;
+
+import store.validation.CartItemValidator;
+
+public class CartItem {
+    private final Product product;
+    private int quantity;
+    private int freeQuantity;
+
+    public CartItem(Product product, int quantity) {
+        CartItemValidator.validateCartItem(quantity);
+        this.product = product;
+        this.quantity = quantity;
+    }
+
+    public void increaseQuantity(int updateQuantity) {
+        this.freeQuantity += updateQuantity;
+    }
+
+    public void decreaseQuantity(int updateQuantity) {
+        this.quantity -= updateQuantity;
+        if (quantity <= 0) {
+            throw new IllegalArgumentException(INSUFFICIENT_STOCK_ERROR.getMessage());
+        }
+    }
+
+    public String getProductName() {
+        return product.getName();
+    }
+
+    public int totalPrice() {
+        return product.getPrice() * getTotalQuantity();
+    }
+
+    public int getTotalFreePrice() {
+        return product.getPrice() * freeQuantity;
+    }
+
+    public int getQuantity() {
+        return quantity;
+    }
+
+    public int getTotalQuantity() {
+        return quantity + freeQuantity;
+    }
+
+    public Product getProduct() {
+        return product;
+    }
+
+    public int getFreeQuantity() {
+        return freeQuantity;
+    }
+}

리뷰: 디미터 법칙을 준수하기 위해 작성하신 메서드로 보입니다. 저도 이런 방식으로 진행했는데요, 3주차 피드백과 자바 스타일 가이드에서 Getter를 사용하기보다 객체 내에서 처리하는 방식이 더 좋다는 내용이 있었습니다.

이 부분은 어떻게 생각하시나요?
@@ -0,0 +1,55 @@
+package store.domain;
+
+import java.time.LocalDateTime;
+import store.dto.PromotionDto;
+import store.util.DateUtil;
+import store.validation.PromotionValidator;
+
+public class Promotion {
+
+    private String name; //프로모션명
+    private int buy; // 구매조건
+    private int get; //증정수량
+    private LocalDateTime startDate;
+    private LocalDateTime endDate;
+
+    private Promotion() {
+    }
+
+    public Promotion(PromotionDto promotionDto) {
+        PromotionValidator.validate(promotionDto);
+        this.name = promotionDto.name();
+        this.buy = Integer.parseInt(promotionDto.buy());
+        this.get = Integer.parseInt(promotionDto.get());
+        this.startDate = DateUtil.dateParse(promotionDto.startDate());
+        this.endDate = DateUtil.dateParse(promotionDto.endDate());
+    }
+
+    public boolean isPromotionName(String productPromotionName) {
+        return this.name.equals(productPromotionName);
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public int getTotalRequiredQuantity() {
+        return buy + get;
+    }
+
+    public int getBuy() {
+        return buy;
+    }
+
+    public int getGet() {
+        return get;
+    }
+
+    public LocalDateTime getStartDate() {
+        return startDate;
+    }
+
+    public LocalDateTime getEndDate() {
+        return endDate;
+    }
+}

리뷰: 요것도 주석보다는 변수명을 잘 짓는게 좋을 것 같아요!
@@ -0,0 +1,55 @@
+package store.domain;
+
+import java.time.LocalDateTime;
+import store.dto.PromotionDto;
+import store.util.DateUtil;
+import store.validation.PromotionValidator;
+
+public class Promotion {
+
+    private String name; //프로모션명
+    private int buy; // 구매조건
+    private int get; //증정수량
+    private LocalDateTime startDate;
+    private LocalDateTime endDate;
+
+    private Promotion() {
+    }
+
+    public Promotion(PromotionDto promotionDto) {
+        PromotionValidator.validate(promotionDto);
+        this.name = promotionDto.name();
+        this.buy = Integer.parseInt(promotionDto.buy());
+        this.get = Integer.parseInt(promotionDto.get());
+        this.startDate = DateUtil.dateParse(promotionDto.startDate());
+        this.endDate = DateUtil.dateParse(promotionDto.endDate());
+    }
+
+    public boolean isPromotionName(String productPromotionName) {
+        return this.name.equals(productPromotionName);
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public int getTotalRequiredQuantity() {
+        return buy + get;
+    }
+
+    public int getBuy() {
+        return buy;
+    }
+
+    public int getGet() {
+        return get;
+    }
+
+    public LocalDateTime getStartDate() {
+        return startDate;
+    }
+
+    public LocalDateTime getEndDate() {
+        return endDate;
+    }
+}

리뷰: getGet() 메서드명을 보시면 가독성이 매우 떨어지는 것 같아요!
@@ -0,0 +1,68 @@
+package store.parser;
+
+import static store.message.ErrorMessage.INVALID_DATA_FORMAT;
+
+import java.util.List;
+import store.dto.ProductDto;
+import store.dto.PromotionDto;
+
+public class FileReaderParser {
+
+    private static final String COMMA_DELIMITER = ",";
+    private static final String NULL_PROMOTION = "null";
+    private static final int EXPECTED_PRODUCT_LENGTH = 4;
+    private static final int EXPECTED_PROMOTION_LENGTH = 5;
+    private static final int HEADER_SKIP_COUNT = 1;
+
+    public List<ProductDto> parseProduct(List<String> productData) {
+        List<String> productRows = removeHeader(productData);
+        return productRows.stream()
+                .map(this::parseProductRow)
+                .toList();
+    }
+
+    public List<PromotionDto> parsePromotion(List<String> promotionData) {
+        List<String> promotionRows = removeHeader(promotionData);
+        return promotionRows.stream()
+                .map(this::parsePromotionRow)
+                .toList();
+    }
+
+    public ProductDto parseProductRow(String row) {
+        String[] split = row.split(COMMA_DELIMITER);
+        validateDataLength(split, EXPECTED_PRODUCT_LENGTH);
+        return createProductDto(split);
+    }
+
+    public PromotionDto parsePromotionRow(String row) {
+        String[] split = row.split(COMMA_DELIMITER);
+        validateDataLength(split, EXPECTED_PROMOTION_LENGTH);
+        return createPromotionDto(split);
+    }
+
+    public void validateDataLength(String[] split, int expectedLength) {
+        if (split.length != expectedLength) {
+            throw new IllegalStateException(INVALID_DATA_FORMAT.getMessage());
+        }
+    }
+
+    public ProductDto createProductDto(String[] split) {
+        return ProductDto.toProductDto(split[0], split[1], split[2], normalizePromotion(split[3]));
+    }
+
+    public PromotionDto createPromotionDto(String[] split) {
+        return new PromotionDto(split[0], split[1], split[2], split[3], split[4]);
+    }
+
+    public String normalizePromotion(String promotion) {
+        if (NULL_PROMOTION.equals(promotion)) {
+            return null;
+        }
+        return promotion;
+    }
+
+    public List<String> removeHeader(List<String> rows) {
+        return rows.stream().skip(HEADER_SKIP_COUNT).toList();
+    }
+
+}

리뷰: 파서 같은 경우는 유틸 클래스로 따로 작성하지 않으신 이유가 있을까요?
@@ -0,0 +1,21 @@
+package store;
+
+import store.controller.FileReaderController;
+import store.controller.StoreController;
+import store.domain.Store;
+import store.dto.StoreDto;
+
+public class FrontController {
+    private final FileReaderController fileReaderController;
+    private final StoreController storeController;
+
+    public FrontController() {
+        this.fileReaderController = new FileReaderController();
+        this.storeController = new StoreController();
+    }
+
+    public void run() {
+        StoreDto storeDto = fileReaderController.runFileData();
+        storeController.run(storeDto);
+    }
+}

리뷰: frontController를 이용해서 파일 먼저 입력을 받으셨군요 좋은것 같아요! 👍
@@ -0,0 +1,68 @@
+package store.parser;
+
+import static store.message.ErrorMessage.INVALID_DATA_FORMAT;
+
+import java.util.List;
+import store.dto.ProductDto;
+import store.dto.PromotionDto;
+
+public class FileReaderParser {
+
+    private static final String COMMA_DELIMITER = ",";
+    private static final String NULL_PROMOTION = "null";
+    private static final int EXPECTED_PRODUCT_LENGTH = 4;
+    private static final int EXPECTED_PROMOTION_LENGTH = 5;
+    private static final int HEADER_SKIP_COUNT = 1;
+
+    public List<ProductDto> parseProduct(List<String> productData) {
+        List<String> productRows = removeHeader(productData);
+        return productRows.stream()
+                .map(this::parseProductRow)
+                .toList();
+    }
+
+    public List<PromotionDto> parsePromotion(List<String> promotionData) {
+        List<String> promotionRows = removeHeader(promotionData);
+        return promotionRows.stream()
+                .map(this::parsePromotionRow)
+                .toList();
+    }
+
+    public ProductDto parseProductRow(String row) {
+        String[] split = row.split(COMMA_DELIMITER);
+        validateDataLength(split, EXPECTED_PRODUCT_LENGTH);
+        return createProductDto(split);
+    }
+
+    public PromotionDto parsePromotionRow(String row) {
+        String[] split = row.split(COMMA_DELIMITER);
+        validateDataLength(split, EXPECTED_PROMOTION_LENGTH);
+        return createPromotionDto(split);
+    }
+
+    public void validateDataLength(String[] split, int expectedLength) {
+        if (split.length != expectedLength) {
+            throw new IllegalStateException(INVALID_DATA_FORMAT.getMessage());
+        }
+    }
+
+    public ProductDto createProductDto(String[] split) {
+        return ProductDto.toProductDto(split[0], split[1], split[2], normalizePromotion(split[3]));
+    }
+
+    public PromotionDto createPromotionDto(String[] split) {
+        return new PromotionDto(split[0], split[1], split[2], split[3], split[4]);
+    }
+
+    public String normalizePromotion(String promotion) {
+        if (NULL_PROMOTION.equals(promotion)) {
+            return null;
+        }
+        return promotion;
+    }
+
+    public List<String> removeHeader(List<String> rows) {
+        return rows.stream().skip(HEADER_SKIP_COUNT).toList();
+    }
+
+}

리뷰: 매직 넘버를 상수화 해주면 좋을것같네요😃
@@ -0,0 +1,35 @@
+package store.custom.model.product;
+
+public class Product {
+    private final String name;
+    private final int price;
+    private int quantity;
+    private final String promotion;
+
+    public Product(String name, int price, int quantity, String promotion) {
+        this.name = name;
+        this.price = price;
+        this.quantity = quantity;
+        this.promotion = promotion;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public int getPrice() {
+        return price;
+    }
+
+    public int getQuantity() {
+        return quantity;
+    }
+
+    public void setQuantity(int quantity) {
+        this.quantity = quantity;
+    }
+
+    public String getPromotion() {
+        return promotion;
+    }
+}
\ No newline at end of file

리뷰: `product`의 `promotion` 필드가 `null` 값을 가질 수 있어 Editor 로직들에서 `null` 에 대한 처리가 필요합니다.
`Optional`을 사용하여 `promotion` 필드가 값이 없는 경우를 명시적으로 처리하거나, "" 와 같은 기본 값을 가지도록 하면 어떨까요?
@@ -0,0 +1,21 @@
+package store.custom.service.filehandler;
+
+import static store.custom.validator.CustomErrorMessages.FILE_READING_FAIL;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import store.custom.validator.Validator;
+
+public class FileReader {
+    public static List<String> run(String filePath) {
+        Validator.validateFilePath(filePath);
+
+        try {
+            return Files.readAllLines(Path.of(filePath));
+        } catch (IOException e) {
+            throw new RuntimeException(FILE_READING_FAIL + e);
+        }
+    }
+}
\ No newline at end of file

리뷰: readAllLines라는 메서드가 있었네요! 저는 while문으로 파일의 끝까지 읽어오도록 했는데, 사용하신 방법이 참 간단한 것 같아요.
@@ -0,0 +1,35 @@
+package store.custom.model.product;
+
+public class Product {
+    private final String name;
+    private final int price;
+    private int quantity;
+    private final String promotion;
+
+    public Product(String name, int price, int quantity, String promotion) {
+        this.name = name;
+        this.price = price;
+        this.quantity = quantity;
+        this.promotion = promotion;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public int getPrice() {
+        return price;
+    }
+
+    public int getQuantity() {
+        return quantity;
+    }
+
+    public void setQuantity(int quantity) {
+        this.quantity = quantity;
+    }
+
+    public String getPromotion() {
+        return promotion;
+    }
+}
\ No newline at end of file

리뷰: promotion 필드는 프로모션 객체를 직접 참조하도록 하면 어떨까요?
@@ -0,0 +1,63 @@
+package store.custom.model.order;
+
+public class OrderedProduct {
+    private final String name;
+    private int quantity;
+    private int totalPrice;
+    private String promotion;
+    private int buy;
+    private int get;
+
+    public OrderedProduct(String name, int quantity, int totalPrice, String promotion, int buy, int get) {
+        this.name = name;
+        this.quantity = quantity;
+        this.totalPrice = totalPrice;
+        this.promotion = promotion;
+        this.buy = buy;
+        this.get = get;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public int getQuantity() {
+        return quantity;
+    }
+
+    public void setQuantity(int quantity) {
+        this.quantity = quantity;
+    }
+
+    public int getTotalPrice() {
+        return totalPrice;
+    }
+
+    public void setTotalPrice(int totalPrice) {
+        this.totalPrice = totalPrice;
+    }
+
+    public String getPromotion() {
+        return promotion;
+    }
+
+    public void setPromotion(String promotion) {
+        this.promotion = promotion;
+    }
+
+    public int getBuy() {
+        return buy;
+    }
+
+    public void setBuy(int buy) {
+        this.buy = buy;
+    }
+
+    public int getGet() {
+        return get;
+    }
+
+    public void setGet(int get) {
+        this.get = get;
+    }
+}
\ No newline at end of file

리뷰: setter를 열어두는것은 좋지 않다고 배웠던 것 같습니다! 재고를 차감한다던가 다른 값들을 객체를 생성할때 할당한다던가 하는 로직을 따로 만들어서 처리하면 어떨까요?
@@ -0,0 +1,58 @@
+package store.custom.service.parser;
+
+import static store.custom.constants.RegexConstants.SINGLE_COMMA;
+import static store.custom.constants.StringConstants.NO_PROMOTION;
+
+import java.util.ArrayList;
+import java.util.List;
+import store.custom.model.product.Product;
+import store.custom.model.product.Products;
+
+public class ProductParser {
+    public static Products run(List<String> lines) {
+        List<Product> productCatalog = new ArrayList<>();
+
+        if (lines != null) {
+            parseProductLines(lines, productCatalog);
+        }
+
+        return new Products(productCatalog);
+    }
+
+    private static void parseProductLines(List<String> lines, List<Product> productCatalog) {
+        for (int currentLine = 1; currentLine < lines.size(); currentLine++) {
+            List<String> currentLineParts = List.of(lines.get(currentLine).split(SINGLE_COMMA));
+            Product product = createProduct(currentLineParts);
+            productCatalog.add(product);
+        }
+    }
+
+    private static Product createProduct(List<String> parts) {
+        String name = extractProductName(parts);
+        int price = extractProductPrice(parts);
+        int quantity = extractProductQuantity(parts);
+        String promotion = extractProductPromotion(parts);
+
+        return new Product(name, price, quantity, promotion);
+    }
+
+    private static String extractProductName(List<String> parts) {
+        return parts.get(0).trim();
+    }
+
+    private static int extractProductPrice(List<String> parts) {
+        return Integer.parseInt(parts.get(1).trim());
+    }
+
+    private static int extractProductQuantity(List<String> parts) {
+        return Integer.parseInt(parts.get(2).trim());
+    }
+
+    private static String extractProductPromotion(List<String> parts) {
+        String promotion = parts.get(3).trim();
+        if (promotion.equals(NO_PROMOTION)) {
+            return null;
+        }
+        return promotion;
+    }
+}
\ No newline at end of file

리뷰: 별거 아닌 부분이긴 한데, 인덱스를 하드코딩하는 것 보다 상수로 빼서 nameIndex, priceIndex 처럼 두었으면 더 좋았을 것 같아요!
@@ -0,0 +1,63 @@
+package store.custom.model.order;
+
+public class OrderedProduct {
+    private final String name;
+    private int quantity;
+    private int totalPrice;
+    private String promotion;
+    private int buy;
+    private int get;
+
+    public OrderedProduct(String name, int quantity, int totalPrice, String promotion, int buy, int get) {
+        this.name = name;
+        this.quantity = quantity;
+        this.totalPrice = totalPrice;
+        this.promotion = promotion;
+        this.buy = buy;
+        this.get = get;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public int getQuantity() {
+        return quantity;
+    }
+
+    public void setQuantity(int quantity) {
+        this.quantity = quantity;
+    }
+
+    public int getTotalPrice() {
+        return totalPrice;
+    }
+
+    public void setTotalPrice(int totalPrice) {
+        this.totalPrice = totalPrice;
+    }
+
+    public String getPromotion() {
+        return promotion;
+    }
+
+    public void setPromotion(String promotion) {
+        this.promotion = promotion;
+    }
+
+    public int getBuy() {
+        return buy;
+    }
+
+    public void setBuy(int buy) {
+        this.buy = buy;
+    }
+
+    public int getGet() {
+        return get;
+    }
+
+    public void setGet(int get) {
+        this.get = get;
+    }
+}
\ No newline at end of file

리뷰: 위에 언급드린 3주차 피드백 객체를 객체답게 사용하기와 연관된 리뷰가 아닐까 싶네요!

3주차 피드백 중 발췌

>Lotto에서 데이터를 꺼내지(get) 말고 메시지를 던지도록 구조를 바꿔 데이터를 가지는 객체가 일하도록 한다. 이처럼 Lotto 객체에서 데이터를 꺼내(get) 사용하기보다는, 데이터가 가지고 있는 객체가 스스로 처리할 수 있도록 구조를 변경해야 한다. 아래와 같이 데이터를 외부에서 가져와(get) 처리하지 말고, 객체가 자신의 데이터를 스스로 처리하도록 메시지를 던지게 한다.
@@ -0,0 +1,63 @@
+package store.custom.model.order;
+
+public class OrderedProduct {
+    private final String name;
+    private int quantity;
+    private int totalPrice;
+    private String promotion;
+    private int buy;
+    private int get;
+
+    public OrderedProduct(String name, int quantity, int totalPrice, String promotion, int buy, int get) {
+        this.name = name;
+        this.quantity = quantity;
+        this.totalPrice = totalPrice;
+        this.promotion = promotion;
+        this.buy = buy;
+        this.get = get;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public int getQuantity() {
+        return quantity;
+    }
+
+    public void setQuantity(int quantity) {
+        this.quantity = quantity;
+    }
+
+    public int getTotalPrice() {
+        return totalPrice;
+    }
+
+    public void setTotalPrice(int totalPrice) {
+        this.totalPrice = totalPrice;
+    }
+
+    public String getPromotion() {
+        return promotion;
+    }
+
+    public void setPromotion(String promotion) {
+        this.promotion = promotion;
+    }
+
+    public int getBuy() {
+        return buy;
+    }
+
+    public void setBuy(int buy) {
+        this.buy = buy;
+    }
+
+    public int getGet() {
+        return get;
+    }
+
+    public void setGet(int get) {
+        this.get = get;
+    }
+}
\ No newline at end of file

리뷰: 동의합니다! 객체가 수동적인 것이 아닌, '능동적'인 인스턴스라는 것을 고려하시면 더 좋은 코드가 될것 같아요
@@ -0,0 +1,35 @@
+package store.custom.model.product;
+
+public class Product {
+    private final String name;
+    private final int price;
+    private int quantity;
+    private final String promotion;
+
+    public Product(String name, int price, int quantity, String promotion) {
+        this.name = name;
+        this.price = price;
+        this.quantity = quantity;
+        this.promotion = promotion;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public int getPrice() {
+        return price;
+    }
+
+    public int getQuantity() {
+        return quantity;
+    }
+
+    public void setQuantity(int quantity) {
+        this.quantity = quantity;
+    }
+
+    public String getPromotion() {
+        return promotion;
+    }
+}
\ No newline at end of file

리뷰: 동의합니다!
@@ -0,0 +1,58 @@
+package store.custom.service.parser;
+
+import static store.custom.constants.RegexConstants.SINGLE_COMMA;
+import static store.custom.constants.StringConstants.NO_PROMOTION;
+
+import java.util.ArrayList;
+import java.util.List;
+import store.custom.model.product.Product;
+import store.custom.model.product.Products;
+
+public class ProductParser {
+    public static Products run(List<String> lines) {
+        List<Product> productCatalog = new ArrayList<>();
+
+        if (lines != null) {
+            parseProductLines(lines, productCatalog);
+        }
+
+        return new Products(productCatalog);
+    }
+
+    private static void parseProductLines(List<String> lines, List<Product> productCatalog) {
+        for (int currentLine = 1; currentLine < lines.size(); currentLine++) {
+            List<String> currentLineParts = List.of(lines.get(currentLine).split(SINGLE_COMMA));
+            Product product = createProduct(currentLineParts);
+            productCatalog.add(product);
+        }
+    }
+
+    private static Product createProduct(List<String> parts) {
+        String name = extractProductName(parts);
+        int price = extractProductPrice(parts);
+        int quantity = extractProductQuantity(parts);
+        String promotion = extractProductPromotion(parts);
+
+        return new Product(name, price, quantity, promotion);
+    }
+
+    private static String extractProductName(List<String> parts) {
+        return parts.get(0).trim();
+    }
+
+    private static int extractProductPrice(List<String> parts) {
+        return Integer.parseInt(parts.get(1).trim());
+    }
+
+    private static int extractProductQuantity(List<String> parts) {
+        return Integer.parseInt(parts.get(2).trim());
+    }
+
+    private static String extractProductPromotion(List<String> parts) {
+        String promotion = parts.get(3).trim();
+        if (promotion.equals(NO_PROMOTION)) {
+            return null;
+        }
+        return promotion;
+    }
+}
\ No newline at end of file

리뷰: 요것도 인덱스 접근보다 원소 접근으로 바꾸는게 더 직관적이고 효율적일 것 같아요
@@ -0,0 +1,29 @@
+package store.custom.model.product;
+
+import static store.custom.validator.CustomErrorMessages.INVALID_INDEX;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class Products {
+    private final List<Product> productCatalog;
+
+    public Products(List<Product> productCatalog) {
+        this.productCatalog = new ArrayList<>(productCatalog);
+    }
+
+    public List<Product> getProducts() {
+        return productCatalog;
+    }
+
+    public int getProductsSize() {
+        return productCatalog.size();
+    }
+
+    public Product getProductByIndex(int index) {
+        if (index < 0 || index >= productCatalog.size()) {
+            throw new IndexOutOfBoundsException(INVALID_INDEX + index);
+        }
+        return productCatalog.get(index);
+    }
+}
\ No newline at end of file

리뷰: 제가 일급컬렉션을 잘 모르긴 하지만,,, 제가 이해한 바로는 좀 더 멱확하게 캡슐화하는게 장점인 것으로 이해하고 있어요! 근데 일급 컬랙션의 구현이 내부의 요소를 외부로 반출시키는 것만 있는게 오히려 캡슐화, 은닉화를 해칠 수 있지 않을까요? 외부에서 product나 products에서 활용하는 여러 메서드들 역시 모델의 책임 아래에 있으니까 내부로 포함시키는 것도 좋을 것 같아요!