ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,77 @@
+package store.controller;
+
+import store.model.Product;
+import store.model.Promotion;
+import store.model.Store;
+import store.view.OutputView;
+
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+public class StoreController {
+
+    public void run() {
+        Store store = new Store();
+        BuyController buyController = new BuyController(store);
+        promotionListUp(store);
+        productListUp(store);
+        OutputView.printInventoryInformation(store);
+        buyController.buyProduct();
+    }
+
+    private void promotionListUp(Store store) {
+        String productsContent = readMarkdownFile("promotions.md");
+        String[] lines = productsContent.split("\n");
+        Arrays.stream(lines)
+                .map(this::parsePromotion)
+                .filter(Objects::nonNull)
+                .forEach(store::addPromotion);
+    }
+
+    private Promotion parsePromotion(String line) {
+        String[] parts = line.split(",");
+        String name = parts[0];
+        if (name.equals("name")) return null;
+        int buy = Integer.parseInt(parts[1]);
+        int get = Integer.parseInt(parts[2]);
+        String startDate = parts[3];
+        String endDate = parts[4];
+        return new Promotion(name, buy, get, startDate, endDate);
+    }
+
+    private void productListUp(Store store) {
+        String productsContent = readMarkdownFile("products.md");
+        String[] lines = productsContent.split("\n");
+        Arrays.stream(lines)
+                .map(line -> parseProduct(line, store))
+                .filter(Objects::nonNull)
+                .forEach(store::addProduct);
+    }
+
+    private Product parseProduct(String line, Store store) {
+        String[] parts = line.split(",");
+        String name = parts[0];
+        if (name.equals("name")) return null;
+        int price = Integer.parseInt(parts[1]);
+        int quantity = Integer.parseInt(parts[2]);
+        Promotion promotion = store.findPromotionByName(parts[3]);
+        return new Product(name, price, quantity, promotion);
+    }
+
+    private String readMarkdownFile(String fileName) {
+        InputStream inputStream = getClass().getClassLoader().getResourceAsStream(fileName);
+        if (inputStream == null) {
+            throw new IllegalArgumentException("[ERROR] 재고 파일을 찾을 수 없습니다.");
+        }
+        try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
+            return reader.lines().collect(Collectors.joining("\n"));
+        } catch (Exception e) {
+            return null;
+        }
+    }
+}

리뷰: parse에 관련된 기능을 controller 내부에서 구현하신 이유가 궁금해요! controller의 역할로써 적절한 방향일까요?!🤔
@@ -0,0 +1,46 @@
+package store.model;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+public class Store {
+    private final List<Product> productList = new ArrayList<>();
+    private final List<Promotion> promotionList = new ArrayList<>();
+
+    public void addProduct(Product product) {
+        productList.add(product);
+    }
+
+    public List<Product> getProductList() {
+        return productList;
+    }
+
+    public void addPromotion(Promotion promotion) {
+        promotionList.add(promotion);
+    }
+
+    public List<Promotion> getPromotionList() {
+        return promotionList;
+    }
+
+    public Promotion findPromotionByName(String name) {
+        return promotionList.stream()
+                .filter(promo -> promo.getName().equals(name))
+                .findFirst()
+                .orElse(null);
+    }
+
+    public void deductInventory(String productName, int quantity) {
+        productList.stream()
+                .filter(product -> product.getName().equals(productName))
+                .findFirst()
+                .ifPresent(product -> product.setQuantity(product.getQuantity() - quantity));
+    }
+
+    public Optional<Product> findProductByName(String productName) {
+        return productList.stream()
+                .filter(product -> product.getName().equals(productName))
+                .findFirst();
+    }
+}

리뷰: 해당 코드 직관적이어서 굉장히 좋은 것 같아요!
또한 ifPresent()를 사용해 내부에 값이 있을 때만 동작해 NPE 발생을 예방할 수 있겠네요:)
@@ -0,0 +1,77 @@
+package store.controller;
+
+import store.model.Product;
+import store.model.Promotion;
+import store.model.Store;
+import store.view.OutputView;
+
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+public class StoreController {
+
+    public void run() {
+        Store store = new Store();
+        BuyController buyController = new BuyController(store);
+        promotionListUp(store);
+        productListUp(store);
+        OutputView.printInventoryInformation(store);
+        buyController.buyProduct();
+    }
+
+    private void promotionListUp(Store store) {
+        String productsContent = readMarkdownFile("promotions.md");
+        String[] lines = productsContent.split("\n");
+        Arrays.stream(lines)
+                .map(this::parsePromotion)
+                .filter(Objects::nonNull)
+                .forEach(store::addPromotion);
+    }
+
+    private Promotion parsePromotion(String line) {
+        String[] parts = line.split(",");
+        String name = parts[0];
+        if (name.equals("name")) return null;
+        int buy = Integer.parseInt(parts[1]);
+        int get = Integer.parseInt(parts[2]);
+        String startDate = parts[3];
+        String endDate = parts[4];
+        return new Promotion(name, buy, get, startDate, endDate);
+    }
+
+    private void productListUp(Store store) {
+        String productsContent = readMarkdownFile("products.md");
+        String[] lines = productsContent.split("\n");
+        Arrays.stream(lines)
+                .map(line -> parseProduct(line, store))
+                .filter(Objects::nonNull)
+                .forEach(store::addProduct);
+    }
+
+    private Product parseProduct(String line, Store store) {
+        String[] parts = line.split(",");
+        String name = parts[0];
+        if (name.equals("name")) return null;
+        int price = Integer.parseInt(parts[1]);
+        int quantity = Integer.parseInt(parts[2]);
+        Promotion promotion = store.findPromotionByName(parts[3]);
+        return new Product(name, price, quantity, promotion);
+    }
+
+    private String readMarkdownFile(String fileName) {
+        InputStream inputStream = getClass().getClassLoader().getResourceAsStream(fileName);
+        if (inputStream == null) {
+            throw new IllegalArgumentException("[ERROR] 재고 파일을 찾을 수 없습니다.");
+        }
+        try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
+            return reader.lines().collect(Collectors.joining("\n"));
+        } catch (Exception e) {
+            return null;
+        }
+    }
+}

리뷰: 전체적으로 리팩토링이 부족해서 역할 분리가 안된 것 같습니다 😢 좋은 지적 감사합니다!
@@ -0,0 +1,77 @@
+package store.controller;
+
+import store.model.Product;
+import store.model.Promotion;
+import store.model.Store;
+import store.view.OutputView;
+
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+public class StoreController {
+
+    public void run() {
+        Store store = new Store();
+        BuyController buyController = new BuyController(store);
+        promotionListUp(store);
+        productListUp(store);
+        OutputView.printInventoryInformation(store);
+        buyController.buyProduct();
+    }
+
+    private void promotionListUp(Store store) {
+        String productsContent = readMarkdownFile("promotions.md");
+        String[] lines = productsContent.split("\n");
+        Arrays.stream(lines)
+                .map(this::parsePromotion)
+                .filter(Objects::nonNull)
+                .forEach(store::addPromotion);
+    }
+
+    private Promotion parsePromotion(String line) {
+        String[] parts = line.split(",");
+        String name = parts[0];
+        if (name.equals("name")) return null;
+        int buy = Integer.parseInt(parts[1]);
+        int get = Integer.parseInt(parts[2]);
+        String startDate = parts[3];
+        String endDate = parts[4];
+        return new Promotion(name, buy, get, startDate, endDate);
+    }
+
+    private void productListUp(Store store) {
+        String productsContent = readMarkdownFile("products.md");
+        String[] lines = productsContent.split("\n");
+        Arrays.stream(lines)
+                .map(line -> parseProduct(line, store))
+                .filter(Objects::nonNull)
+                .forEach(store::addProduct);
+    }
+
+    private Product parseProduct(String line, Store store) {
+        String[] parts = line.split(",");
+        String name = parts[0];
+        if (name.equals("name")) return null;
+        int price = Integer.parseInt(parts[1]);
+        int quantity = Integer.parseInt(parts[2]);
+        Promotion promotion = store.findPromotionByName(parts[3]);
+        return new Product(name, price, quantity, promotion);
+    }
+
+    private String readMarkdownFile(String fileName) {
+        InputStream inputStream = getClass().getClassLoader().getResourceAsStream(fileName);
+        if (inputStream == null) {
+            throw new IllegalArgumentException("[ERROR] 재고 파일을 찾을 수 없습니다.");
+        }
+        try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
+            return reader.lines().collect(Collectors.joining("\n"));
+        } catch (Exception e) {
+            return null;
+        }
+    }
+}

리뷰: 매직 넘버들은 상수화하는 것도 좋을 것 같아요!
@@ -0,0 +1,42 @@
+package store.model;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+public class Promotion {
+    private final String name;
+    private final int buy;
+    private final int get;
+    private final LocalDateTime startDate;
+    private final LocalDateTime endDate;
+
+    private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm");
+
+    public Promotion(String name, int buy, int get, String startDate, String endDate) {
+        this.name = name;
+        this.buy = buy;
+        this.get = get;
+        this.startDate = LocalDateTime.parse(startDate + "T00:00", formatter);
+        this.endDate = LocalDateTime.parse(endDate + "T23:59", formatter);
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public int getBuy() {
+        return buy;
+    }
+
+    public int getGet() {
+        return get;
+    }
+
+    public LocalDateTime getStartDate() {
+        return startDate;
+    }
+
+    public LocalDateTime getEndDate() {
+        return endDate;
+    }
+}

리뷰: 날짜 형식 상수화 좋네요! 👍
@@ -0,0 +1,42 @@
+package store.model;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+public class Promotion {
+    private final String name;
+    private final int buy;
+    private final int get;
+    private final LocalDateTime startDate;
+    private final LocalDateTime endDate;
+
+    private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm");
+
+    public Promotion(String name, int buy, int get, String startDate, String endDate) {
+        this.name = name;
+        this.buy = buy;
+        this.get = get;
+        this.startDate = LocalDateTime.parse(startDate + "T00:00", formatter);
+        this.endDate = LocalDateTime.parse(endDate + "T23:59", formatter);
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public int getBuy() {
+        return buy;
+    }
+
+    public int getGet() {
+        return get;
+    }
+
+    public LocalDateTime getStartDate() {
+        return startDate;
+    }
+
+    public LocalDateTime getEndDate() {
+        return endDate;
+    }
+}

리뷰: 상수화 등을 통해 조금 더 직관적으로 표현할 수 있을 것 같아요!
@@ -0,0 +1,46 @@
+package store.model;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+public class Store {
+    private final List<Product> productList = new ArrayList<>();
+    private final List<Promotion> promotionList = new ArrayList<>();
+
+    public void addProduct(Product product) {
+        productList.add(product);
+    }
+
+    public List<Product> getProductList() {
+        return productList;
+    }
+
+    public void addPromotion(Promotion promotion) {
+        promotionList.add(promotion);
+    }
+
+    public List<Promotion> getPromotionList() {
+        return promotionList;
+    }
+
+    public Promotion findPromotionByName(String name) {
+        return promotionList.stream()
+                .filter(promo -> promo.getName().equals(name))
+                .findFirst()
+                .orElse(null);
+    }
+
+    public void deductInventory(String productName, int quantity) {
+        productList.stream()
+                .filter(product -> product.getName().equals(productName))
+                .findFirst()
+                .ifPresent(product -> product.setQuantity(product.getQuantity() - quantity));
+    }
+
+    public Optional<Product> findProductByName(String productName) {
+        return productList.stream()
+                .filter(product -> product.getName().equals(productName))
+                .findFirst();
+    }
+}

리뷰: stream을 잘 활용하시는 것 같아요 👍
@@ -0,0 +1,27 @@
+package store.view;
+
+import camp.nextstep.edu.missionutils.Console;
+
+import java.util.Map;
+
+public class InputView {
+    public static Map<String, Integer> purchasedItems() {
+        while (true) {
+            try {
+                String input = Console.readLine();
+                InputParser.validateInputFormat(input);
+                return InputParser.parseItems(input);
+            } catch (IllegalArgumentException e) {
+                System.out.println(e.getMessage());
+            }
+        }
+    }
+
+    public static boolean yOrN() {
+        String input = Console.readLine();
+        if (input.equals("Y")) return true;
+        if (input.equals("N")) return false;
+        System.out.println("[ERROR] 잘못된 입력입니다. 다시 입력해 주세요.");
+        return yOrN();
+    }
+}

리뷰: 조건문은 중괄호로 감싸주세요!
@@ -0,0 +1,34 @@
+package store.view;
+
+import store.model.Product;
+import store.model.Store;
+
+import java.util.List;
+
+public class OutputView {
+    public static void printInventoryInformation(Store store) {
+        System.out.println("안녕하세요. W편의점입니다.");
+        System.out.println("현재 보유하고 있는 상품입니다.");
+        System.out.println();
+        List<Product> productList = store.getProductList();
+        productList.forEach(System.out::println);
+        System.out.println();
+        System.out.println("구매하실 상품명과 수량을 입력해 주세요. (예: [사이다-2],[감자칩-1]");
+    }
+
+    public static void printAddEventProduct(int number, String productName) {
+        System.out.println("현재 " + productName + "은(는) " + number + "개를 무료로 더 받을 수 있습니다. 추가하시겠습니까? (Y/N)");
+    }
+
+    public static void printMemberShipDiscount() {
+        System.out.println("멤버십 할인을 받으시겠습니까? (Y/N)");
+    }
+
+    public static void askForAdditionalPurchase() {
+        System.out.println("감사합니다. 구매하고 싶은 다른 상품이 있나요? (Y/N)");
+    }
+
+    public static void PromotionNotApplied(Product product) {
+        System.out.println("현재 " + product.getName() + " " + product.getPromotionBuy() + "개는 프로모션 할인이 적용되지 않습니다. 그래도 구매하시겠습니까?");
+    }
+}

리뷰: 여기서는 String.format을 사용하지 않은 이유가 있으신가요??
@@ -0,0 +1,34 @@
+package store.view;
+
+import store.model.Product;
+import store.model.Store;
+
+import java.util.List;
+
+public class OutputView {
+    public static void printInventoryInformation(Store store) {
+        System.out.println("안녕하세요. W편의점입니다.");
+        System.out.println("현재 보유하고 있는 상품입니다.");
+        System.out.println();
+        List<Product> productList = store.getProductList();
+        productList.forEach(System.out::println);
+        System.out.println();
+        System.out.println("구매하실 상품명과 수량을 입력해 주세요. (예: [사이다-2],[감자칩-1]");
+    }
+
+    public static void printAddEventProduct(int number, String productName) {
+        System.out.println("현재 " + productName + "은(는) " + number + "개를 무료로 더 받을 수 있습니다. 추가하시겠습니까? (Y/N)");
+    }
+
+    public static void printMemberShipDiscount() {
+        System.out.println("멤버십 할인을 받으시겠습니까? (Y/N)");
+    }
+
+    public static void askForAdditionalPurchase() {
+        System.out.println("감사합니다. 구매하고 싶은 다른 상품이 있나요? (Y/N)");
+    }
+
+    public static void PromotionNotApplied(Product product) {
+        System.out.println("현재 " + product.getName() + " " + product.getPromotionBuy() + "개는 프로모션 할인이 적용되지 않습니다. 그래도 구매하시겠습니까?");
+    }
+}

리뷰: 없습니다... 고민을 많이 하지 않은 것 같습니다 😢
@@ -0,0 +1,77 @@
+package store.controller;
+
+import store.model.Product;
+import store.model.Promotion;
+import store.model.Store;
+import store.view.OutputView;
+
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+public class StoreController {
+
+    public void run() {
+        Store store = new Store();
+        BuyController buyController = new BuyController(store);
+        promotionListUp(store);
+        productListUp(store);
+        OutputView.printInventoryInformation(store);
+        buyController.buyProduct();
+    }
+
+    private void promotionListUp(Store store) {
+        String productsContent = readMarkdownFile("promotions.md");
+        String[] lines = productsContent.split("\n");
+        Arrays.stream(lines)
+                .map(this::parsePromotion)
+                .filter(Objects::nonNull)
+                .forEach(store::addPromotion);
+    }
+
+    private Promotion parsePromotion(String line) {
+        String[] parts = line.split(",");
+        String name = parts[0];
+        if (name.equals("name")) return null;
+        int buy = Integer.parseInt(parts[1]);
+        int get = Integer.parseInt(parts[2]);
+        String startDate = parts[3];
+        String endDate = parts[4];
+        return new Promotion(name, buy, get, startDate, endDate);
+    }
+
+    private void productListUp(Store store) {
+        String productsContent = readMarkdownFile("products.md");
+        String[] lines = productsContent.split("\n");
+        Arrays.stream(lines)
+                .map(line -> parseProduct(line, store))
+                .filter(Objects::nonNull)
+                .forEach(store::addProduct);
+    }
+
+    private Product parseProduct(String line, Store store) {
+        String[] parts = line.split(",");
+        String name = parts[0];
+        if (name.equals("name")) return null;
+        int price = Integer.parseInt(parts[1]);
+        int quantity = Integer.parseInt(parts[2]);
+        Promotion promotion = store.findPromotionByName(parts[3]);
+        return new Product(name, price, quantity, promotion);
+    }
+
+    private String readMarkdownFile(String fileName) {
+        InputStream inputStream = getClass().getClassLoader().getResourceAsStream(fileName);
+        if (inputStream == null) {
+            throw new IllegalArgumentException("[ERROR] 재고 파일을 찾을 수 없습니다.");
+        }
+        try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
+            return reader.lines().collect(Collectors.joining("\n"));
+        } catch (Exception e) {
+            return null;
+        }
+    }
+}

리뷰: if문을 중가로 없이 바로 return 하셨는데
이게 가독성이 더 좋아서 이렇게 하신건가요?
@@ -0,0 +1,77 @@
+package store.controller;
+
+import store.model.Product;
+import store.model.Promotion;
+import store.model.Store;
+import store.view.OutputView;
+
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+public class StoreController {
+
+    public void run() {
+        Store store = new Store();
+        BuyController buyController = new BuyController(store);
+        promotionListUp(store);
+        productListUp(store);
+        OutputView.printInventoryInformation(store);
+        buyController.buyProduct();
+    }
+
+    private void promotionListUp(Store store) {
+        String productsContent = readMarkdownFile("promotions.md");
+        String[] lines = productsContent.split("\n");
+        Arrays.stream(lines)
+                .map(this::parsePromotion)
+                .filter(Objects::nonNull)
+                .forEach(store::addPromotion);
+    }
+
+    private Promotion parsePromotion(String line) {
+        String[] parts = line.split(",");
+        String name = parts[0];
+        if (name.equals("name")) return null;
+        int buy = Integer.parseInt(parts[1]);
+        int get = Integer.parseInt(parts[2]);
+        String startDate = parts[3];
+        String endDate = parts[4];
+        return new Promotion(name, buy, get, startDate, endDate);
+    }
+
+    private void productListUp(Store store) {
+        String productsContent = readMarkdownFile("products.md");
+        String[] lines = productsContent.split("\n");
+        Arrays.stream(lines)
+                .map(line -> parseProduct(line, store))
+                .filter(Objects::nonNull)
+                .forEach(store::addProduct);
+    }
+
+    private Product parseProduct(String line, Store store) {
+        String[] parts = line.split(",");
+        String name = parts[0];
+        if (name.equals("name")) return null;
+        int price = Integer.parseInt(parts[1]);
+        int quantity = Integer.parseInt(parts[2]);
+        Promotion promotion = store.findPromotionByName(parts[3]);
+        return new Product(name, price, quantity, promotion);
+    }
+
+    private String readMarkdownFile(String fileName) {
+        InputStream inputStream = getClass().getClassLoader().getResourceAsStream(fileName);
+        if (inputStream == null) {
+            throw new IllegalArgumentException("[ERROR] 재고 파일을 찾을 수 없습니다.");
+        }
+        try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
+            return reader.lines().collect(Collectors.joining("\n"));
+        } catch (Exception e) {
+            return null;
+        }
+    }
+}

리뷰: 가로 안에 가로 안에 가로 가 있는게 가독성이 떨어지는거 같아요
나중에 이 부분도 리팩터링 하면 좋을거 같습니다.
@@ -0,0 +1,46 @@
+package store.model;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+public class Store {
+    private final List<Product> productList = new ArrayList<>();
+    private final List<Promotion> promotionList = new ArrayList<>();
+
+    public void addProduct(Product product) {
+        productList.add(product);
+    }
+
+    public List<Product> getProductList() {
+        return productList;
+    }
+
+    public void addPromotion(Promotion promotion) {
+        promotionList.add(promotion);
+    }
+
+    public List<Promotion> getPromotionList() {
+        return promotionList;
+    }
+
+    public Promotion findPromotionByName(String name) {
+        return promotionList.stream()
+                .filter(promo -> promo.getName().equals(name))
+                .findFirst()
+                .orElse(null);
+    }
+
+    public void deductInventory(String productName, int quantity) {
+        productList.stream()
+                .filter(product -> product.getName().equals(productName))
+                .findFirst()
+                .ifPresent(product -> product.setQuantity(product.getQuantity() - quantity));
+    }
+
+    public Optional<Product> findProductByName(String productName) {
+        return productList.stream()
+                .filter(product -> product.getName().equals(productName))
+                .findFirst();
+    }
+}

리뷰: getPromotionLsit()는 사용되지 않는 걸로 보여요!
리팩터링 시간이 부족하셔서 남겨두신거 같은데
혹시 이 메서드를 남겨두신 다른 이유가 있을까요?