ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,44 @@
+package vendingmachine.service;
+
+import java.util.Optional;
+import vendingmachine.domain.HeldCoins;
+import vendingmachine.domain.PaymentAmount;
+import vendingmachine.domain.Product;
+import vendingmachine.dtos.PurchaseInputDto;
+import vendingmachine.repository.ProductRepository;
+
+public class PaymentService {
+    private final ProductRepository productRepository;
+
+    public PaymentService(ProductRepository productRepository) {
+        this.productRepository = productRepository;
+    }
+
+    public void purchase(PurchaseInputDto input) {
+        PaymentAmount paymentAmount = input.paymentAmount();
+        String productName = input.productName();
+
+        // 해당 상품이 있는지 확인
+        Optional<Product> foundProduct = this.productRepository.findByName(productName);
+        if (foundProduct.isEmpty()) {
+            throw new IllegalArgumentException("Product not found");
+        }
+        Product product = foundProduct.get();
+
+        // 상품의 재고가 있는지 확인
+        if (!product.isAvailableToBuy()) {
+            throw new IllegalArgumentException("Product is not available to buy");
+        }
+
+        // 가지고 있는 돈으로 해당 상품을 구매할 수 있는지 확인
+        if (paymentAmount.getAmount() < product.getPrice()) {
+            throw new IllegalArgumentException("Payment amount is too small");
+        }
+
+        // 투입금액 감소
+        paymentAmount.decreaseAmount(product.getPrice());
+
+        this.productRepository.update(product.getName(),
+                new Product(product.getName(), product.getPrice(), product.getQuantity() - 1));
+    }
+}

리뷰: 로직이 좀 긴 것 같습니당
주석을 남겨주신 부분별로 각 역할이 있는 걸로 보이는데, 그 부분을 함수로 추출하는 건 어떨까용?
@@ -1,7 +1,21 @@
 package vendingmachine;
 
+import vendingmachine.controller.CoinController;
+import vendingmachine.controller.MoneyController;
+import vendingmachine.controller.OrderController;
+import vendingmachine.controller.ProductController;
+import vendingmachine.model.Products;
+
 public class Application {
     public static void main(String[] args) {
-        // TODO: 프로그램 구현
+        CoinController coinController = new CoinController();
+        ProductController productController = new ProductController();
+        MoneyController moneyController = new MoneyController();
+        OrderController orderController = new OrderController();
+
+        int[] coins = coinController.initMoney();
+        Products products = productController.initStock();
+        int money = moneyController.receiveMoney();
+        orderController.makeOrder(products, money);
     }
 }

리뷰: Service나 Domain 계층에 책임을 위임하지 않고, Controller에 구현하신 우진님만의 의도가 궁금합니다!
@@ -1,7 +1,21 @@
 package vendingmachine;
 
+import vendingmachine.controller.CoinController;
+import vendingmachine.controller.MoneyController;
+import vendingmachine.controller.OrderController;
+import vendingmachine.controller.ProductController;
+import vendingmachine.model.Products;
+
 public class Application {
     public static void main(String[] args) {
-        // TODO: 프로그램 구현
+        CoinController coinController = new CoinController();
+        ProductController productController = new ProductController();
+        MoneyController moneyController = new MoneyController();
+        OrderController orderController = new OrderController();
+
+        int[] coins = coinController.initMoney();
+        Products products = productController.initStock();
+        int money = moneyController.receiveMoney();
+        orderController.makeOrder(products, money);
     }
 }

리뷰: Collection이 아닌 배열을 사용하신 이유가 궁금합니다!
@@ -0,0 +1,47 @@
+package vendingmachine.controller;
+
+import static vendingmachine.view.InputView.input;
+import static vendingmachine.view.OutputView.printOrderMessage;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+import vendingmachine.model.Product;
+import vendingmachine.model.Products;
+
+public class ProductController {
+
+    public Products initStock() {
+        printOrderMessage();
+        return makeProductList();
+    }
+
+    private Products makeProductList() {
+        String stockInput = input();
+        StringTokenizer semicolonSt = new StringTokenizer(stockInput, ";");
+        List<Product> products = new ArrayList<>();
+        return processProductInput(semicolonSt, products);
+    }
+
+    private Products processProductInput(StringTokenizer semicolonSt, List<Product> productList) {
+        while (semicolonSt.hasMoreTokens()) {
+            String processingInput = semicolonSt.nextToken();
+            processingInput = processingInput.replace("[", "").replace("]", "");
+            String name = processingInput.split(",")[0];
+            int price = Integer.parseInt(processingInput.split(",")[1]);
+            int stock = Integer.parseInt(processingInput.split(",")[2]);
+            productList.add(new Product(name, price, stock));
+        }
+        return new Products(productList);
+    }
+
+    public static boolean validateEmptyStock(Products products) {
+        //재고가 하나라도 있으면 진행 가능
+        return products.getProducts().stream().anyMatch(product -> product.getStock() > 0);
+    }
+
+    public static int minPrice(Products products) {
+        return products.getProducts().stream().mapToInt(Product::getPrice).min()
+            .orElse(Integer.MAX_VALUE);
+    }
+}

리뷰: 재고 확인 검증을 Domain이 아닌 Controller에서 하신 이유가 궁금합니다!
@@ -0,0 +1,21 @@
+package vendingmachine.model;
+
+import java.util.List;
+
+public class Products {
+
+    private static List<Product> products;
+
+    public Products(List<Product> productList) {
+        this.products = productList;
+    }
+
+    public List<Product> getProducts() {
+        return products;
+    }
+
+    public static Product getProductByName(String name) {
+        return products.stream().filter(product -> product.getName().equals(name)).findFirst()
+            .orElse(null);
+    }
+}

리뷰: 상품 이름으로 Product를 반환하는 메서드라면, findAny()를 사용하는게 더 좋지 않을까요?? 우진님의 의견은 어떠신지 궁금해요!
@@ -0,0 +1,14 @@
+package vendingmachine.view;
+
+import static camp.nextstep.edu.missionutils.Console.readLine;
+
+public class InputView {
+
+    public static int inputInteger() {
+        return Integer.parseInt(input());
+    }
+
+    public static String input() {
+        return readLine();
+    }
+}

리뷰: 항상 Console.readLint()을 사용해왔는데, 해당 라인처럼 더 간결하게 표현할 수 있어서 좋은 것 같아요!👍
@@ -0,0 +1,62 @@
+package vendingmachine.controller;
+
+import static camp.nextstep.edu.missionutils.Randoms.pickNumberInList;
+import static vendingmachine.view.InputView.inputInteger;
+import static vendingmachine.view.OutputView.printCoins;
+import static vendingmachine.view.OutputView.printHavingMoneyMessage;
+
+import java.util.ArrayList;
+import java.util.List;
+import vendingmachine.model.Coin;
+
+public class CoinController {
+
+    private static final int size = Coin.values().length;
+    private static int[] coins = new int[size];
+
+    public int[] initMoney() {
+        printHavingMoneyMessage();
+        return initCoins(inputInteger());
+    }
+
+    private int[] initCoins(int total) {
+        Coin[] coinType = Coin.values();
+        int index = 0;
+        while (total > 0) {
+            //동전 합이 total이 될 때까지 loop
+            //0부터 size까지를 왔다갔다할 인덱스
+            int coinPrice = coinType[index].getAmount();
+            //현재 index에 해당하는 동전의 액면가
+            int possibleCoin = total / coinPrice;
+            //현재 index의 동전의 최대 개수
+            List<Integer> numberRange = makeNumberRange(possibleCoin);
+            //pickNumberInList의 매개변수 타입에 맞게 0부터 possibleCoin까지의 모든 수를 넣은 List
+            if (possibleCoin > 0) {
+                //가능한 코인 개수가 있으면
+                int pickedNumber = pickNumberInList(numberRange);
+                //랜덤 돌려서
+                coins[index] += pickedNumber;
+                //코인 개수에 추가
+                total -= coinPrice * pickedNumber;
+                //while문 조건에 반영하기 위해 동전개수 * 액면가를 total에서 제해줌
+            }
+            index++;
+            if (index == size) {
+                //모든 종류의 코인 다 돌았으면
+                index = 0;
+                //Index 초기화
+            }
+        }
+        //보유 금액만큼 동전 생성 후 출력
+        printCoins(coins);
+        return coins;
+    }
+
+    private static List<Integer> makeNumberRange(int possibleCoin) {
+        List<Integer> numberRange = new ArrayList<>();
+        for (int i = 0; i <= possibleCoin; i++) {
+            numberRange.add(i);
+        }
+        return numberRange;
+    }
+}

리뷰: 주석을 남겨두신 이유가 궁금해요!🧐
@@ -0,0 +1,12 @@
+package vendingmachine.controller;
+
+import static vendingmachine.view.InputView.inputInteger;
+import static vendingmachine.view.OutputView.printPurchasingMoneyMessage;
+
+public class MoneyController {
+
+    public int receiveMoney() {
+        printPurchasingMoneyMessage();
+        return inputInteger();
+    }
+}

리뷰: Console과 같은 라이브러리뿐만 아니라, InputView와 같은 클래스까지 모두 static import 하신 의도가 궁금합니다!
InputView를 명시해주는 편이 해당 클래스 내에서 역할 및 관계를 명확히 이해할 수 있다고 생각하는데 우진님의 의견은 어떠신가요??
@@ -0,0 +1,30 @@
+package vendingmachine.controller;
+
+import static vendingmachine.controller.ProductController.minPrice;
+import static vendingmachine.controller.ProductController.validateEmptyStock;
+import static vendingmachine.model.Products.getProductByName;
+import static vendingmachine.view.InputView.input;
+import static vendingmachine.view.OutputView.printPurchasingItemMessage;
+import static vendingmachine.view.OutputView.printPurchasingMoney;
+
+import vendingmachine.model.Product;
+import vendingmachine.model.Products;
+
+public class OrderController {
+
+    public void makeOrder(Products products, int purchasingMoney) {
+        while (validateEmptyStock(products) && purchasingMoney > minPrice(products)) {
+            printPurchasingMoney(purchasingMoney);
+            //투입금액(남은금액) 출력
+            printPurchasingItemMessage();
+            String purchasingItemName = input();
+            Product purchasingItem = getProductByName(purchasingItemName);
+            purchasingMoney -= purchasingItem.getPrice();
+        }
+        giveChange();
+    }
+
+    public void giveChange() {
+        
+    }
+}

리뷰: 이 부분은 아직 작성되지 않은건가요?!
@@ -1,7 +1,21 @@
 package vendingmachine;
 
+import vendingmachine.controller.CoinController;
+import vendingmachine.controller.MoneyController;
+import vendingmachine.controller.OrderController;
+import vendingmachine.controller.ProductController;
+import vendingmachine.model.Products;
+
 public class Application {
     public static void main(String[] args) {
-        // TODO: 프로그램 구현
+        CoinController coinController = new CoinController();
+        ProductController productController = new ProductController();
+        MoneyController moneyController = new MoneyController();
+        OrderController orderController = new OrderController();
+
+        int[] coins = coinController.initMoney();
+        Products products = productController.initStock();
+        int money = moneyController.receiveMoney();
+        orderController.makeOrder(products, money);
     }
 }

리뷰: 컨트롤러가 많네요..
구현과 동시에 리팩토링을 추구하신 건가요??
@@ -0,0 +1,12 @@
+package vendingmachine.controller;
+
+import static vendingmachine.view.InputView.inputInteger;
+import static vendingmachine.view.OutputView.printPurchasingMoneyMessage;
+
+public class MoneyController {
+
+    public int receiveMoney() {
+        printPurchasingMoneyMessage();
+        return inputInteger();
+    }
+}

리뷰: 컨트롤러 분리가 조금 과한 감이 있는 것 같아요. 코드가 많이 길어지면 그 때 가서 분리해도 좋을 것 같습니다!
@@ -0,0 +1,62 @@
+package vendingmachine.controller;
+
+import static camp.nextstep.edu.missionutils.Randoms.pickNumberInList;
+import static vendingmachine.view.InputView.inputInteger;
+import static vendingmachine.view.OutputView.printCoins;
+import static vendingmachine.view.OutputView.printHavingMoneyMessage;
+
+import java.util.ArrayList;
+import java.util.List;
+import vendingmachine.model.Coin;
+
+public class CoinController {
+
+    private static final int size = Coin.values().length;
+    private static int[] coins = new int[size];
+
+    public int[] initMoney() {
+        printHavingMoneyMessage();
+        return initCoins(inputInteger());
+    }
+
+    private int[] initCoins(int total) {
+        Coin[] coinType = Coin.values();
+        int index = 0;
+        while (total > 0) {
+            //동전 합이 total이 될 때까지 loop
+            //0부터 size까지를 왔다갔다할 인덱스
+            int coinPrice = coinType[index].getAmount();
+            //현재 index에 해당하는 동전의 액면가
+            int possibleCoin = total / coinPrice;
+            //현재 index의 동전의 최대 개수
+            List<Integer> numberRange = makeNumberRange(possibleCoin);
+            //pickNumberInList의 매개변수 타입에 맞게 0부터 possibleCoin까지의 모든 수를 넣은 List
+            if (possibleCoin > 0) {
+                //가능한 코인 개수가 있으면
+                int pickedNumber = pickNumberInList(numberRange);
+                //랜덤 돌려서
+                coins[index] += pickedNumber;
+                //코인 개수에 추가
+                total -= coinPrice * pickedNumber;
+                //while문 조건에 반영하기 위해 동전개수 * 액면가를 total에서 제해줌
+            }
+            index++;
+            if (index == size) {
+                //모든 종류의 코인 다 돌았으면
+                index = 0;
+                //Index 초기화
+            }
+        }
+        //보유 금액만큼 동전 생성 후 출력
+        printCoins(coins);
+        return coins;
+    }
+
+    private static List<Integer> makeNumberRange(int possibleCoin) {
+        List<Integer> numberRange = new ArrayList<>();
+        for (int i = 0; i <= possibleCoin; i++) {
+            numberRange.add(i);
+        }
+        return numberRange;
+    }
+}

리뷰: 주석은 설계 과정에서 스스로 쉽게 알아보기 위해 작성한 건가요?!
@@ -0,0 +1,41 @@
+package vendingmachine.view;
+
+import vendingmachine.model.Coin;
+
+public class OutputView {
+
+    private static final String HAVING_MONEY_MESSAGE = "자판기가 보유하고 있는 금액을 입력해 주세요.";
+    private static final String HAVING_COIN_MESSAGE = "\n자판기가 보유한 동전";
+    private static final String ORDER_MESSAGE = "\n상품명과 가격, 수량을 입력해 주세요.";
+    private static final String PURCHASING_MONEY_MESSAGE = "\n투입 금액을 입력해 주세요.";
+    private static final String PURCHASING_MONEY = "\n투입 금액: %d원";
+    private static final String PURCHASING_ITEM_MASSAGE = "구매할 상품명을 입력해 주세요.";
+    private static final String printForm = "%d원 -  %d개";
+
+    public static void printHavingMoneyMessage() {
+        System.out.println(HAVING_MONEY_MESSAGE);
+    }
+
+    public static void printCoins(int[] coins) {
+        System.out.println(HAVING_COIN_MESSAGE);
+        for (int i = 0; i < coins.length; i++) {
+            System.out.println(String.format(printForm, Coin.values()[i].getAmount(), coins[i]));
+        }
+    }
+
+    public static void printOrderMessage() {
+        System.out.println(ORDER_MESSAGE);
+    }
+
+    public static void printPurchasingMoneyMessage() {
+        System.out.println(PURCHASING_MONEY_MESSAGE);
+    }
+
+    public static void printPurchasingMoney(int money) {
+        System.out.println(String.format(PURCHASING_MONEY, money));
+    }
+
+    public static void printPurchasingItemMessage() {
+        System.out.println(PURCHASING_ITEM_MASSAGE);
+    }
+}

리뷰: 운영체제에 의존적이지 않은 줄바꿈 방법을 사용하면 더 좋을 것 같아요!
@@ -0,0 +1,41 @@
+package vendingmachine.view;
+
+import vendingmachine.model.Coin;
+
+public class OutputView {
+
+    private static final String HAVING_MONEY_MESSAGE = "자판기가 보유하고 있는 금액을 입력해 주세요.";
+    private static final String HAVING_COIN_MESSAGE = "\n자판기가 보유한 동전";
+    private static final String ORDER_MESSAGE = "\n상품명과 가격, 수량을 입력해 주세요.";
+    private static final String PURCHASING_MONEY_MESSAGE = "\n투입 금액을 입력해 주세요.";
+    private static final String PURCHASING_MONEY = "\n투입 금액: %d원";
+    private static final String PURCHASING_ITEM_MASSAGE = "구매할 상품명을 입력해 주세요.";
+    private static final String printForm = "%d원 -  %d개";
+
+    public static void printHavingMoneyMessage() {
+        System.out.println(HAVING_MONEY_MESSAGE);
+    }
+
+    public static void printCoins(int[] coins) {
+        System.out.println(HAVING_COIN_MESSAGE);
+        for (int i = 0; i < coins.length; i++) {
+            System.out.println(String.format(printForm, Coin.values()[i].getAmount(), coins[i]));
+        }
+    }
+
+    public static void printOrderMessage() {
+        System.out.println(ORDER_MESSAGE);
+    }
+
+    public static void printPurchasingMoneyMessage() {
+        System.out.println(PURCHASING_MONEY_MESSAGE);
+    }
+
+    public static void printPurchasingMoney(int money) {
+        System.out.println(String.format(PURCHASING_MONEY, money));
+    }
+
+    public static void printPurchasingItemMessage() {
+        System.out.println(PURCHASING_ITEM_MASSAGE);
+    }
+}

리뷰: stream을 활용할 수 있을 것 같아요!
@@ -0,0 +1,72 @@
+package store.controller;
+
+import static store.constant.AnswerConstant.ANSWER_YES;
+
+import java.util.Map;
+
+import store.constant.file.FilePath;
+import store.model.domain.customer.Customer;
+import store.model.domain.product.Products;
+import store.model.domain.product.Promotions;
+import store.service.CalculateService;
+import store.service.FileService;
+import store.util.InventoryManager;
+import store.util.PromotionHandler;
+import store.util.ReceiptGenerator;
+import store.view.InputView;
+import store.view.OutputView;
+
+public class StoreController {
+	private final FileService fileService;
+	private final CalculateService calculateService;
+
+	public StoreController(FileService fileService, CalculateService calculateService) {
+		this.fileService = fileService;
+		this.calculateService = calculateService;
+	}
+
+	public void start() {
+		try {
+			boolean isProceed = true;
+			Products products = fileService.createProducts(FilePath.PRODUCT_FILE_PATH.getFilePath());
+			Promotions promotions = fileService.createPromotions(FilePath.PROMOTION_FILE_PATH.getFilePath());
+
+			while (isProceed) {
+				processBuying(products, promotions);
+				isProceed = shouldProceed();
+				OutputView.printNewLine();
+			}
+		} catch (IllegalArgumentException e) {
+			OutputView.printError(e.getMessage());
+			start();
+		}
+	}
+
+	private void processBuying(Products products, Promotions promotions) {
+		OutputView.promptCurrentStock(products);
+		Customer customer = getCustomerBuyProduct(products);
+
+		PromotionHandler.handlePromotionMessages(customer, products, promotions);
+		InventoryManager.decreaseProductQuantities(customer, products);
+		ReceiptGenerator.generateReceipt(customer, products, promotions, calculateService);
+	}
+
+	private boolean shouldProceed() {
+		OutputView.promptContinueMessage();
+		String checkProceed = InputView.checkProceedPurchase();
+		return checkProceed.equals(ANSWER_YES);
+	}
+
+	private Customer getCustomerBuyProduct(Products products) {
+		while (true) {
+			try {
+				OutputView.promptBuyProductMessage();
+				Map<String, Integer> buyingProduct = InputView.buyProduct();
+				return new Customer(buyingProduct, products);
+			} catch (IllegalArgumentException e) {
+				OutputView.printError(e.getMessage());
+			}
+		}
+	}
+}
+

리뷰: processBuying, shouldProceed 등의 메서드를 더 세부적으로 나누고, try-catch 블록을 별도로 분리하면 더 깔끔해질 수 있을 것 같아요!
@@ -0,0 +1,48 @@
+package store.service;
+
+import camp.nextstep.edu.missionutils.DateTimes;
+import store.model.domain.customer.Customer;
+import store.model.domain.product.Product;
+import store.model.domain.product.Products;
+import store.model.domain.product.Promotion;
+import store.model.domain.product.Promotions;
+
+public class CalculateService {
+	public int calculateBuyProductTotalPrice(Customer customer, Products products) {
+		return customer.getBuyingProduct().entrySet().stream()
+			.mapToInt(entry -> products.getProductPrice(entry.getKey()) * entry.getValue())
+			.sum();
+	}
+
+	public int calculateGiftProductTotalPrice(Customer customer, Products products) {
+		return customer.getGiftProduct().entrySet().stream()
+			.mapToInt(entry -> products.getProductPrice(entry.getKey()) * entry.getValue())
+			.sum();
+	}
+
+	public int calculatePromotionDiscount(Customer customer, Products products, Promotions promotions) {
+		return customer.getBuyingProduct().entrySet().stream()
+			.mapToInt(entry -> applyPromotion(entry.getKey(), entry.getValue(), products, promotions))
+			.sum();
+	}
+
+	private int applyPromotion(String productName, int quantity,
+		Products products, Promotions promotions) {
+		Product product = products.findProductByName(productName);
+		Promotion promotion = (product != null) ? promotions.findPromotionByName(product.getPromotion()) : null;
+
+		return getAdditionalQuantity(quantity, product, promotion);
+	}
+
+	private static int getAdditionalQuantity(int quantity, Product product, Promotion promotion) {
+		if (promotion != null && promotion.isApplyPromotion(quantity, DateTimes.now())) {
+			int additionalQuantity = 0;
+			additionalQuantity = (quantity / promotion.getBuy()) * promotion.getGet();
+
+			if (additionalQuantity > 0) {
+				return product.getPrice() * additionalQuantity;
+			}
+		}
+		return 0;
+	}
+}

리뷰: 모듈화가 아주 잘 되어있는 것 같아요!
@@ -0,0 +1,48 @@
+package store.service;
+
+import camp.nextstep.edu.missionutils.DateTimes;
+import store.model.domain.customer.Customer;
+import store.model.domain.product.Product;
+import store.model.domain.product.Products;
+import store.model.domain.product.Promotion;
+import store.model.domain.product.Promotions;
+
+public class CalculateService {
+	public int calculateBuyProductTotalPrice(Customer customer, Products products) {
+		return customer.getBuyingProduct().entrySet().stream()
+			.mapToInt(entry -> products.getProductPrice(entry.getKey()) * entry.getValue())
+			.sum();
+	}
+
+	public int calculateGiftProductTotalPrice(Customer customer, Products products) {
+		return customer.getGiftProduct().entrySet().stream()
+			.mapToInt(entry -> products.getProductPrice(entry.getKey()) * entry.getValue())
+			.sum();
+	}
+
+	public int calculatePromotionDiscount(Customer customer, Products products, Promotions promotions) {
+		return customer.getBuyingProduct().entrySet().stream()
+			.mapToInt(entry -> applyPromotion(entry.getKey(), entry.getValue(), products, promotions))
+			.sum();
+	}
+
+	private int applyPromotion(String productName, int quantity,
+		Products products, Promotions promotions) {
+		Product product = products.findProductByName(productName);
+		Promotion promotion = (product != null) ? promotions.findPromotionByName(product.getPromotion()) : null;
+
+		return getAdditionalQuantity(quantity, product, promotion);
+	}
+
+	private static int getAdditionalQuantity(int quantity, Product product, Promotion promotion) {
+		if (promotion != null && promotion.isApplyPromotion(quantity, DateTimes.now())) {
+			int additionalQuantity = 0;
+			additionalQuantity = (quantity / promotion.getBuy()) * promotion.getGet();
+
+			if (additionalQuantity > 0) {
+				return product.getPrice() * additionalQuantity;
+			}
+		}
+		return 0;
+	}
+}

리뷰: getAdditionalQuantity()는 프로모션 세부 구현에 가까워 보이는데 해당 클래스 안에 작성하신 이유가 있을까요?