ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,65 @@
+package subway.exception;
+
+import java.util.List;
+import java.util.Objects;
+import subway.domain.Line;
+import subway.domain.Station;
+import subway.repository.LineRepository;
+import subway.repository.StationRepository;
+
+public class InputValidator {
+
+    public static void validateInput(String input) {
+        ValidatorBuilder.from(input)
+                .validate(Objects::isNull, ErrorMessage.INVALID_INPUT)
+                .validate(String::isBlank, ErrorMessage.INVALID_INPUT);
+    }
+
+    public static void validateNameLength(String input) {
+        ValidatorBuilder.from(input)
+                .validate(value -> value.length() < 2, ErrorMessage.INVALID_NAME_LENGTH);
+    }
+
+    public static void validateStationsIsLessThanTwo(String[] stations) {
+        ValidatorBuilder.from(stations)
+                .validate(value -> value.length < 2, ErrorMessage.STATIONS_SHORTAGE);
+    }
+
+    public static void validateStationExists(String name) {
+        ValidatorBuilder.from(name)
+                .validate(value -> StationRepository.findStation(value).isEmpty()
+                        , ErrorMessage.STATION_NOT_EXISTS);
+    }
+
+    public static void validateLineExists(String name) {
+        ValidatorBuilder.from(name)
+                .validate(value -> LineRepository.findLine(value).isEmpty()
+                        , ErrorMessage.LINE_NOT_EXISTS);
+    }
+
+    public static void validateDuplicateInput(Station upperStation, String lowerStationName) {
+        ValidatorBuilder.from(lowerStationName)
+                .validate(value -> upperStation.getName().equals(value)
+                        , ErrorMessage.DUPLICATE_STATION_INPUT_FOR_LINE);
+    }
+
+    public static int validateOrder(Line line, String input) {
+        List<Station> stations = line.getStations();
+
+        return ValidatorBuilder.from(input)
+                .validateIsInteger()
+                .validateInteger(value -> value < 1 && value > stations.size() - 1
+                        , ErrorMessage.INVALID_SECTION_ORDER)
+                .getNumericValue();
+    }
+
+    public static void validateIsInLine(String findStation) {
+        ValidatorBuilder.from(findStation)
+                .validate(
+                        station -> LineRepository.lines().stream()
+                                .flatMap(line -> line.getStations().stream())
+                                .anyMatch(existingStation -> existingStation.getName().equals(station))
+                        , ErrorMessage.ALREADY_IN_LINE
+                );
+    }
+}

리뷰: 감사합니다!
@@ -0,0 +1,89 @@
+package subway.repository;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import subway.domain.Line;
+import subway.domain.Station;
+import subway.exception.ErrorMessage;
+
+public class LineRepository {
+    private static final int LEAST_STATION_COUNT = 2;
+    private static final List<Line> lines = new ArrayList<>();
+
+    public static List<Line> lines() {
+        return Collections.unmodifiableList(lines);
+    }
+
+    public static void addLine(Line line) {
+        if (isDuplicate(line.getName())) {
+            throw new IllegalArgumentException(ErrorMessage.DUPLICATE_LINE_NAME.toString());
+        }
+
+        lines.add(line);
+    }
+
+    public static void deleteLineByName(String name) {
+        if (findLine(name).isEmpty()) {
+            throw new IllegalArgumentException(ErrorMessage.LINE_NOT_EXISTS.toString());
+        }
+
+        lines.removeIf(line -> Objects.equals(line.getName(), name));
+    }
+
+    public static void addSection(Line line, Station station, int order) {
+        if (containsStation(line, station)) {
+            throw new IllegalArgumentException(ErrorMessage.LINE_ALREADY_HAS_THIS_STATION.toString());
+        }
+
+        LinkedList<Station> stations = new LinkedList<>(line.getStations());
+        stations.add(order - 1, station);
+
+        deleteLineByName(line.getName());
+
+        Line newLine = new Line(line.getName(), stations);
+        lines.add(newLine);
+    }
+
+    public static void deleteSection(Line line, Station station) {
+        if (!containsStation(line, station)) {
+            throw new IllegalArgumentException(ErrorMessage.LINE_DOES_NOT_HAVE_THIS_STATION.toString());
+        }
+
+        LinkedList<Station> stations = new LinkedList<>(line.getStations());
+
+        if (stations.size() <= LEAST_STATION_COUNT) {
+            throw new IllegalArgumentException(ErrorMessage.STATIONS_SHORTAGE.toString());
+        }
+
+        stations.remove(station);
+
+        deleteLineByName(line.getName());
+
+        Line newLine = new Line(line.getName(), stations);
+        lines.add(newLine);
+    }
+
+    public static Optional<Line> findLine(String name) {
+        return lines.stream()
+                .filter(line -> line.getName().equals(name))
+                .findFirst();
+    }
+
+    public static boolean containsStation(Line line, Station station) {
+        return line.getStations()
+                .contains(station);
+    }
+
+    public static void clearLines() {
+        lines.clear();
+    }
+
+    private static boolean isDuplicate(String name) {
+        return lines.stream()
+                .anyMatch(station -> Objects.equals(station.getName(), name));
+    }
+}

리뷰: 확실히 그렇네요. Line 도메인이 하는 역할이 너무 적어 고민이었는데 검증을 내부에서 처리한다면 해결될 것 같습니다!
@@ -0,0 +1,89 @@
+package subway.repository;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import subway.domain.Line;
+import subway.domain.Station;
+import subway.exception.ErrorMessage;
+
+public class LineRepository {
+    private static final int LEAST_STATION_COUNT = 2;
+    private static final List<Line> lines = new ArrayList<>();
+
+    public static List<Line> lines() {
+        return Collections.unmodifiableList(lines);
+    }
+
+    public static void addLine(Line line) {
+        if (isDuplicate(line.getName())) {
+            throw new IllegalArgumentException(ErrorMessage.DUPLICATE_LINE_NAME.toString());
+        }
+
+        lines.add(line);
+    }
+
+    public static void deleteLineByName(String name) {
+        if (findLine(name).isEmpty()) {
+            throw new IllegalArgumentException(ErrorMessage.LINE_NOT_EXISTS.toString());
+        }
+
+        lines.removeIf(line -> Objects.equals(line.getName(), name));
+    }
+
+    public static void addSection(Line line, Station station, int order) {
+        if (containsStation(line, station)) {
+            throw new IllegalArgumentException(ErrorMessage.LINE_ALREADY_HAS_THIS_STATION.toString());
+        }
+
+        LinkedList<Station> stations = new LinkedList<>(line.getStations());
+        stations.add(order - 1, station);
+
+        deleteLineByName(line.getName());
+
+        Line newLine = new Line(line.getName(), stations);
+        lines.add(newLine);
+    }
+
+    public static void deleteSection(Line line, Station station) {
+        if (!containsStation(line, station)) {
+            throw new IllegalArgumentException(ErrorMessage.LINE_DOES_NOT_HAVE_THIS_STATION.toString());
+        }
+
+        LinkedList<Station> stations = new LinkedList<>(line.getStations());
+
+        if (stations.size() <= LEAST_STATION_COUNT) {
+            throw new IllegalArgumentException(ErrorMessage.STATIONS_SHORTAGE.toString());
+        }
+
+        stations.remove(station);
+
+        deleteLineByName(line.getName());
+
+        Line newLine = new Line(line.getName(), stations);
+        lines.add(newLine);
+    }
+
+    public static Optional<Line> findLine(String name) {
+        return lines.stream()
+                .filter(line -> line.getName().equals(name))
+                .findFirst();
+    }
+
+    public static boolean containsStation(Line line, Station station) {
+        return line.getStations()
+                .contains(station);
+    }
+
+    public static void clearLines() {
+        lines.clear();
+    }
+
+    private static boolean isDuplicate(String name) {
+        return lines.stream()
+                .anyMatch(station -> Objects.equals(station.getName(), name));
+    }
+}

리뷰: 감사합니다!
@@ -0,0 +1,89 @@
+package subway.repository;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import subway.domain.Line;
+import subway.domain.Station;
+import subway.exception.ErrorMessage;
+
+public class LineRepository {
+    private static final int LEAST_STATION_COUNT = 2;
+    private static final List<Line> lines = new ArrayList<>();
+
+    public static List<Line> lines() {
+        return Collections.unmodifiableList(lines);
+    }
+
+    public static void addLine(Line line) {
+        if (isDuplicate(line.getName())) {
+            throw new IllegalArgumentException(ErrorMessage.DUPLICATE_LINE_NAME.toString());
+        }
+
+        lines.add(line);
+    }
+
+    public static void deleteLineByName(String name) {
+        if (findLine(name).isEmpty()) {
+            throw new IllegalArgumentException(ErrorMessage.LINE_NOT_EXISTS.toString());
+        }
+
+        lines.removeIf(line -> Objects.equals(line.getName(), name));
+    }
+
+    public static void addSection(Line line, Station station, int order) {
+        if (containsStation(line, station)) {
+            throw new IllegalArgumentException(ErrorMessage.LINE_ALREADY_HAS_THIS_STATION.toString());
+        }
+
+        LinkedList<Station> stations = new LinkedList<>(line.getStations());
+        stations.add(order - 1, station);
+
+        deleteLineByName(line.getName());
+
+        Line newLine = new Line(line.getName(), stations);
+        lines.add(newLine);
+    }
+
+    public static void deleteSection(Line line, Station station) {
+        if (!containsStation(line, station)) {
+            throw new IllegalArgumentException(ErrorMessage.LINE_DOES_NOT_HAVE_THIS_STATION.toString());
+        }
+
+        LinkedList<Station> stations = new LinkedList<>(line.getStations());
+
+        if (stations.size() <= LEAST_STATION_COUNT) {
+            throw new IllegalArgumentException(ErrorMessage.STATIONS_SHORTAGE.toString());
+        }
+
+        stations.remove(station);
+
+        deleteLineByName(line.getName());
+
+        Line newLine = new Line(line.getName(), stations);
+        lines.add(newLine);
+    }
+
+    public static Optional<Line> findLine(String name) {
+        return lines.stream()
+                .filter(line -> line.getName().equals(name))
+                .findFirst();
+    }
+
+    public static boolean containsStation(Line line, Station station) {
+        return line.getStations()
+                .contains(station);
+    }
+
+    public static void clearLines() {
+        lines.clear();
+    }
+
+    private static boolean isDuplicate(String name) {
+        return lines.stream()
+                .anyMatch(station -> Objects.equals(station.getName(), name));
+    }
+}

리뷰: 확실히 더 나은 방법인 것 같습니다! 감사합니다 :)
@@ -0,0 +1,49 @@
+package subway.service;
+
+import subway.constants.StationLineFeature;
+import subway.domain.Station;
+import subway.exception.InputValidator;
+import subway.repository.StationRepository;
+
+public class StationService {
+
+    public StationLineFeature selectStationFeature(String selectedFeature) {
+        InputValidator.validateInput(selectedFeature);
+
+        return StationLineFeature.getFeatureFromInput(selectedFeature);
+    }
+
+    public void addStation(String stationName) {
+        InputValidator.validateInput(stationName);
+        InputValidator.validateNameLength(stationName);
+
+        StationRepository.addStation(new Station(stationName));
+    }
+
+    public void deleteStation(String stationName) {
+        InputValidator.validateInput(stationName);
+        InputValidator.validateNameLength(stationName);
+        InputValidator.validateIsInLine(stationName);
+
+        StationRepository.deleteStation(stationName);
+    }
+
+    public Station getStation(String stationName) {
+        InputValidator.validateInput(stationName);
+        InputValidator.validateNameLength(stationName);
+        InputValidator.validateStationExists(stationName);
+
+        return StationRepository.findStation(stationName)
+                .orElseThrow();
+    }
+
+    public Station getLowerStation(Station upperStation, String lowerStation) {
+        InputValidator.validateInput(lowerStation);
+        InputValidator.validateNameLength(lowerStation);
+        InputValidator.validateStationExists(lowerStation);
+        InputValidator.validateDuplicateInput(upperStation, lowerStation);
+
+        return StationRepository.findStation(lowerStation)
+                .orElseThrow();
+    }
+}

리뷰: 생각지 못한 부분인데 감사합니다! 따로 처리를 꼭 해야 할 것 같습니다
@@ -0,0 +1,35 @@
+package controller;
+
+import java.util.Objects;
+import model.Orders;
+import model.Receipts;
+import view.InputView;
+import view.OutputView;
+
+public final class ApplicationController {
+    public void run() {
+        LoadDataController.loadData();
+        do {
+            runTurn();
+        } while (!exitCheck());
+    }
+
+    public void runTurn() {
+        clear();
+        OutputView.outputInventory();
+        OrderController.order();
+        PurchaseController.purchaseExec();
+        MembershipController.membership();
+        OutputView.outputReceipts(Receipts.getPurchases());
+    }
+
+    public static void clear() {
+        Orders.clearOrders();
+        Receipts.clear();
+    }
+
+    public boolean exitCheck() {
+        String input = InputView.exitCheck();
+        return Objects.equals(input, "N");
+    }
+}

리뷰: static으로 설정하신 이유가 있을까요?
@@ -0,0 +1,35 @@
+package controller;
+
+import config.PathConfig;
+import dto.ProductDTO;
+import java.util.List;
+import model.Inventory;
+import model.Promotion;
+import model.Promotions;
+import util.FileUtil;
+import util.SplitUtil;
+
+public final class LoadDataController {
+    public static void loadData() {
+        promotionsData();
+        productsData();
+    }
+
+    public static void productsData() {
+        List<String> fileContents = FileUtil.readFile(PathConfig.PRODUCTS_FILE_PATH.getFilePath());
+        for(String content: fileContents) {
+            ProductDTO productDTO = SplitUtil.createProductDTOFromContent(content);
+            Inventory.productAdd(productDTO);
+        }
+    }
+
+    public static void promotionsData() {
+        List<String> fileContents = FileUtil.readFile(PathConfig.PROMOTIONS_FILE_PATH.getFilePath());
+        fileContents.stream()
+                .map(SplitUtil::createPromotionFromContent)
+                // 처음 promotions.promotion에 현재 기간에 행사 진행하는 것만 넣을 건지 다 넣을 건지 filter
+                .filter(promotion -> Promotion.currentPromotion(promotion.startTime(), promotion.endTime()))
+                .forEach(Promotions::add);
+    }
+}
+

리뷰: 메소드 명은 동사나 전치사로 시작하는 걸 추천드립니다
@@ -0,0 +1,35 @@
+package controller;
+
+import config.PathConfig;
+import dto.ProductDTO;
+import java.util.List;
+import model.Inventory;
+import model.Promotion;
+import model.Promotions;
+import util.FileUtil;
+import util.SplitUtil;
+
+public final class LoadDataController {
+    public static void loadData() {
+        promotionsData();
+        productsData();
+    }
+
+    public static void productsData() {
+        List<String> fileContents = FileUtil.readFile(PathConfig.PRODUCTS_FILE_PATH.getFilePath());
+        for(String content: fileContents) {
+            ProductDTO productDTO = SplitUtil.createProductDTOFromContent(content);
+            Inventory.productAdd(productDTO);
+        }
+    }
+
+    public static void promotionsData() {
+        List<String> fileContents = FileUtil.readFile(PathConfig.PROMOTIONS_FILE_PATH.getFilePath());
+        fileContents.stream()
+                .map(SplitUtil::createPromotionFromContent)
+                // 처음 promotions.promotion에 현재 기간에 행사 진행하는 것만 넣을 건지 다 넣을 건지 filter
+                .filter(promotion -> Promotion.currentPromotion(promotion.startTime(), promotion.endTime()))
+                .forEach(Promotions::add);
+    }
+}
+

리뷰: 외부에서 사용하지 않기 때문에 private으로 닫아도 될 것 같습니다
@@ -0,0 +1,35 @@
+package controller;
+
+import java.util.Objects;
+import model.Orders;
+import model.Receipts;
+import view.InputView;
+import view.OutputView;
+
+public final class ApplicationController {
+    public void run() {
+        LoadDataController.loadData();
+        do {
+            runTurn();
+        } while (!exitCheck());
+    }
+
+    public void runTurn() {
+        clear();
+        OutputView.outputInventory();
+        OrderController.order();
+        PurchaseController.purchaseExec();
+        MembershipController.membership();
+        OutputView.outputReceipts(Receipts.getPurchases());
+    }
+
+    public static void clear() {
+        Orders.clearOrders();
+        Receipts.clear();
+    }
+
+    public boolean exitCheck() {
+        String input = InputView.exitCheck();
+        return Objects.equals(input, "N");
+    }
+}

리뷰: 사용자로부터 받는 Y,N은 상수나 이넘으로 빼는건 어떨까요?
@@ -0,0 +1,21 @@
+package io;
+
+import camp.nextstep.edu.missionutils.Console;
+import validation.InputValidation;
+import view.OutputView;
+
+public final class Input {
+    public static String inputMessage() {
+        return Console.readLine();
+    }
+
+    public static String inputYOrN() {
+        while(true) {
+            try {
+                return InputValidation.isYorN(inputMessage());
+            } catch (IllegalArgumentException error){
+                OutputView.outputErrorMessage(error);
+            }
+        }
+    }
+}

리뷰: 저는 사용자로부터 입력을 받는 거 자체가 inputView의 역할이라 생각이 되어 Console.readLine()을 사용한 사용자 입력부분을 inputView에 담아뒀습니다. 혹시 Input이라는 클래스로 역할을 나누신 이유가 있을까요?? 제가 모르는 부분이 있다면 배우고 싶어 여쭤봅니다!
@@ -0,0 +1,35 @@
+package controller;
+
+import java.util.Objects;
+import model.Orders;
+import model.Receipts;
+import view.InputView;
+import view.OutputView;
+
+public final class ApplicationController {
+    public void run() {
+        LoadDataController.loadData();
+        do {
+            runTurn();
+        } while (!exitCheck());
+    }
+
+    public void runTurn() {
+        clear();
+        OutputView.outputInventory();
+        OrderController.order();
+        PurchaseController.purchaseExec();
+        MembershipController.membership();
+        OutputView.outputReceipts(Receipts.getPurchases());
+    }
+
+    public static void clear() {
+        Orders.clearOrders();
+        Receipts.clear();
+    }
+
+    public boolean exitCheck() {
+        String input = InputView.exitCheck();
+        return Objects.equals(input, "N");
+    }
+}

리뷰: clear() 함수 안에 있는 Orders.clearOrders(); Receipts.clear(); 2개의 명령이 주문 내역과 영수증 내역을 초기화하는 명령입니다.
이 내역들은 static List로 구현되어 있고 함수 자체가 static이라서 static 함수로 만들었습니다.
@@ -0,0 +1,35 @@
+package controller;
+
+import config.PathConfig;
+import dto.ProductDTO;
+import java.util.List;
+import model.Inventory;
+import model.Promotion;
+import model.Promotions;
+import util.FileUtil;
+import util.SplitUtil;
+
+public final class LoadDataController {
+    public static void loadData() {
+        promotionsData();
+        productsData();
+    }
+
+    public static void productsData() {
+        List<String> fileContents = FileUtil.readFile(PathConfig.PRODUCTS_FILE_PATH.getFilePath());
+        for(String content: fileContents) {
+            ProductDTO productDTO = SplitUtil.createProductDTOFromContent(content);
+            Inventory.productAdd(productDTO);
+        }
+    }
+
+    public static void promotionsData() {
+        List<String> fileContents = FileUtil.readFile(PathConfig.PROMOTIONS_FILE_PATH.getFilePath());
+        fileContents.stream()
+                .map(SplitUtil::createPromotionFromContent)
+                // 처음 promotions.promotion에 현재 기간에 행사 진행하는 것만 넣을 건지 다 넣을 건지 filter
+                .filter(promotion -> Promotion.currentPromotion(promotion.startTime(), promotion.endTime()))
+                .forEach(Promotions::add);
+    }
+}
+

리뷰: 오 감사합니다!! 놓쳤던 부분 발견해 주셔서 감사합니다.!!
@@ -0,0 +1,35 @@
+package controller;
+
+import java.util.Objects;
+import model.Orders;
+import model.Receipts;
+import view.InputView;
+import view.OutputView;
+
+public final class ApplicationController {
+    public void run() {
+        LoadDataController.loadData();
+        do {
+            runTurn();
+        } while (!exitCheck());
+    }
+
+    public void runTurn() {
+        clear();
+        OutputView.outputInventory();
+        OrderController.order();
+        PurchaseController.purchaseExec();
+        MembershipController.membership();
+        OutputView.outputReceipts(Receipts.getPurchases());
+    }
+
+    public static void clear() {
+        Orders.clearOrders();
+        Receipts.clear();
+    }
+
+    public boolean exitCheck() {
+        String input = InputView.exitCheck();
+        return Objects.equals(input, "N");
+    }
+}

리뷰: 좋은 것 같습니다!!
@@ -0,0 +1,21 @@
+package io;
+
+import camp.nextstep.edu.missionutils.Console;
+import validation.InputValidation;
+import view.OutputView;
+
+public final class Input {
+    public static String inputMessage() {
+        return Console.readLine();
+    }
+
+    public static String inputYOrN() {
+        while(true) {
+            try {
+                return InputValidation.isYorN(inputMessage());
+            } catch (IllegalArgumentException error){
+                OutputView.outputErrorMessage(error);
+            }
+        }
+    }
+}

리뷰: 이 Input 클래스의 inputMessage함수는  Console.readLine() 받아오는 부분을 유지보수성 높이고 싶어서 만들었습니다.
 inputYOrN 함수는 많이 사용되는 함수라서 InputView클래스 보다는 Input에 선언해서 더 가독성을 높이고 싶어서 나누어서 개발했습니다.
 
InputView에 Console.readLine()을 사용하지 않고 Input에 개발함으로써 나중에 검증 코드나 다른 코드의 추가가 있을때 한번에 변환할 수 있어서 좋다고 생각합니다.

Input은 사용자 입력 처리 역할
InputView는 사용자와의 인터페이스 역할
@@ -0,0 +1,35 @@
+package controller;
+
+import java.util.Objects;
+import model.Orders;
+import model.Receipts;
+import view.InputView;
+import view.OutputView;
+
+public final class ApplicationController {
+    public void run() {
+        LoadDataController.loadData();
+        do {
+            runTurn();
+        } while (!exitCheck());
+    }
+
+    public void runTurn() {
+        clear();
+        OutputView.outputInventory();
+        OrderController.order();
+        PurchaseController.purchaseExec();
+        MembershipController.membership();
+        OutputView.outputReceipts(Receipts.getPurchases());
+    }
+
+    public static void clear() {
+        Orders.clearOrders();
+        Receipts.clear();
+    }
+
+    public boolean exitCheck() {
+        String input = InputView.exitCheck();
+        return Objects.equals(input, "N");
+    }
+}

리뷰: `retryCheck`로 네이밍하고, 입력이 `Y`인지 확인한다면, Not 연산자는 사용하지 않을 수 있을 것 같아요! 아니면 메서드 내에서 Not 연산자를 적용해서 반환하면 좋을 것 같아요
@@ -0,0 +1,24 @@
+package controller;
+
+import java.util.Objects;
+import model.Membership;
+import model.Receipts;
+import view.InputView;
+
+public final class MembershipController {
+    public static final int MAX_LIMIT = 8000;
+
+    public static void membership() {
+        String input = InputView.membershipDiscount();
+        if (Objects.equals(input, "Y")){
+            Membership.setMembershipDiscount(membershipCalculate());
+            return;
+        }
+        Membership.setMembershipDiscount(0);
+    }
+
+    public static int membershipCalculate() {
+        int discountPrice = (Receipts.membershipDiscountEligiblePrice() * 30) / 100;
+        return Math.min(discountPrice, MAX_LIMIT);
+    }
+}

리뷰: `30`은 멤버십 계산에서 핵심 내용이니 상수 처리하면 어떨까요?