ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,63 @@
+package store;
+
+import store.domain.Stock;
+import store.domain.receipt.Receipt;
+import store.domain.order.OrderItems;
+import store.view.View;
+
+public class StoreController {
+    private final StoreService storeService;
+    private final Stock stock;
+
+    public StoreController(StoreService storeService, Stock stock) {
+        this.storeService = storeService;
+        this.stock = stock;
+    }
+
+    public void run() {
+        do {
+            runStoreProcess();
+        } while (askContinueShopping());
+    }
+
+    private void runStoreProcess() {
+        printGreetingMessage();
+        printStockStatus(stock);
+        OrderItems orderItems = getOrderItems();
+        try {
+            Receipt receipt = proceedPurchase(orderItems);
+            printReceipt(receipt);
+        } catch (Exception e) {
+            printErrorMessage(e.getMessage());
+        }
+    }
+
+    private void printGreetingMessage() {
+        View.getInstance().printGreetingMessage();
+    }
+
+    private void printStockStatus(Stock stock) {
+        View.getInstance().printStockStatus(stock.getProducts());
+    }
+
+    private OrderItems getOrderItems() {
+        String input = View.getInstance().promptBuyItems();
+        return storeService.getOrderItems(input);
+    }
+
+    private Receipt proceedPurchase(OrderItems orderItems) {
+        return storeService.proceedPurchase(stock, orderItems);
+    }
+
+    private void printReceipt(Receipt receipt) {
+        View.getInstance().printReceipt(receipt);
+    }
+
+    private boolean askContinueShopping() {
+        return View.getInstance().promptContinueShopping();
+    }
+
+    private void printErrorMessage(String errorMessage) {
+        View.getInstance().printErrorMessage(errorMessage);
+    }
+}

리뷰: View를 싱글톤으로 관리하는 이유가 있으신지 여쭤보고 싶습니다
@@ -0,0 +1,19 @@
+package store.domain.order;
+
+import java.util.List;
+
+/**
+ * OrderItems 는 OrderItem 의 일급 컬렉션입니다.
+ * @see OrderItem
+ */
+public class OrderItems {
+    private final List<OrderItem> orderItems;
+
+    public OrderItems(List<OrderItem> orderItems) {
+        this.orderItems = List.copyOf(orderItems);
+    }
+
+    public List<OrderItem> getOrderItems() {
+        return orderItems;
+    }
+}

리뷰: OrderItems는 별도의 로직 없이 리스트를 필드로만 가지고 있는 것 같은데, 이런 구조는 의도된 설계인가요?
일반적으로 일급 컬렉션에서는 컬렉션을 직접 반환하는 getter 사용이 지양되는 것으로 알고 있습니다.
getter만 있는 경우, 외부에서 컬렉션을 직접 조작하거나, 관련 로직이 분산될 가능성이 있어 보여 질문드립니다.
@@ -0,0 +1,29 @@
+package subway.config;
+
+import java.util.Scanner;
+import subway.controller.SubwayController;
+import subway.service.LineService;
+import subway.service.MainService;
+import subway.service.SectionService;
+import subway.service.StationService;
+import subway.view.InputView;
+import subway.view.OutputView;
+
+public enum AppConfig {
+
+    INSTANCE;
+
+    private final Scanner scanner = new Scanner(System.in);
+    private final InputView inputView = new InputView(scanner);
+    private final OutputView outputView = new OutputView();
+    private final MainService mainService = new MainService();
+    private final StationService stationService = new StationService();
+    private final LineService lineService = new LineService();
+    private final SectionService sectionService = new SectionService();
+    private final SubwayController subwayController = new SubwayController(inputView, outputView, mainService,
+            stationService, lineService, sectionService);
+
+    public SubwayController getController() {
+        return subwayController;
+    }
+}

리뷰: enum을 활용하여 controller에 대해 singleton을 보장하는 방식이 인상 깊습니다~!
@@ -0,0 +1,31 @@
+package subway.constants;
+
+import subway.exception.ErrorMessage;
+
+public enum MainFeature {
+    SELECT_ONE("1"),
+    SELECT_TWO("2"),
+    SELECT_THREE("3"),
+    SELECT_FOUR("4"),
+    QUIT("Q");
+
+    final String message;
+
+    MainFeature(String message) {
+        this.message = message;
+    }
+
+    @Override
+    public String toString() {
+        return message;
+    }
+
+    public static MainFeature getFeatureFromInput(String input) {
+        for (MainFeature feature : MainFeature.values()) {
+            if (feature.toString().equalsIgnoreCase(input)) {
+                return feature;
+            }
+        }
+        throw new IllegalArgumentException(ErrorMessage.INVALID_FEATURE.toString());
+    }
+}

리뷰: "Q"와 같은 경우 "QUIT" 이라는 의미를 명확하게 부여했지만, 1이나 2와 같은 경우에 상대적으로 명확하지 않은 것 같습니다.
이를 더 명확하게 할 방법에 대해 더 고민해보면 좋을 것 같습니다.
@@ -0,0 +1,31 @@
+package subway.constants;
+
+import subway.exception.ErrorMessage;
+
+public enum MainFeature {
+    SELECT_ONE("1"),
+    SELECT_TWO("2"),
+    SELECT_THREE("3"),
+    SELECT_FOUR("4"),
+    QUIT("Q");
+
+    final String message;
+
+    MainFeature(String message) {
+        this.message = message;
+    }
+
+    @Override
+    public String toString() {
+        return message;
+    }
+
+    public static MainFeature getFeatureFromInput(String input) {
+        for (MainFeature feature : MainFeature.values()) {
+            if (feature.toString().equalsIgnoreCase(input)) {
+                return feature;
+            }
+        }
+        throw new IllegalArgumentException(ErrorMessage.INVALID_FEATURE.toString());
+    }
+}

리뷰: 감사합니다! 노선 관리와 역 관리를 한 번에 처리하다 보니 더 명확하지 않은 것도 있는 것 같습니당...
수정한다면 ADD, DELETE, SHOW 등으로 수정할 것 같습니다!
@@ -0,0 +1,29 @@
+package subway.config;
+
+import java.util.Scanner;
+import subway.controller.SubwayController;
+import subway.service.LineService;
+import subway.service.MainService;
+import subway.service.SectionService;
+import subway.service.StationService;
+import subway.view.InputView;
+import subway.view.OutputView;
+
+public enum AppConfig {
+
+    INSTANCE;
+
+    private final Scanner scanner = new Scanner(System.in);
+    private final InputView inputView = new InputView(scanner);
+    private final OutputView outputView = new OutputView();
+    private final MainService mainService = new MainService();
+    private final StationService stationService = new StationService();
+    private final LineService lineService = new LineService();
+    private final SectionService sectionService = new SectionService();
+    private final SubwayController subwayController = new SubwayController(inputView, outputView, mainService,
+            stationService, lineService, sectionService);
+
+    public SubwayController getController() {
+        return subwayController;
+    }
+}

리뷰: 저도 프리코스 기간동안 코드리뷰를 하며 알게된 사실인데, 함수 매개변수의 개수가 3개가 넘어가면 안좋다고 합니다. 3개 이하로 줄여보는 것은 어떨까요?
@@ -0,0 +1,31 @@
+package subway.constants;
+
+import subway.exception.ErrorMessage;
+
+public enum MainFeature {
+    SELECT_ONE("1"),
+    SELECT_TWO("2"),
+    SELECT_THREE("3"),
+    SELECT_FOUR("4"),
+    QUIT("Q");
+
+    final String message;
+
+    MainFeature(String message) {
+        this.message = message;
+    }
+
+    @Override
+    public String toString() {
+        return message;
+    }
+
+    public static MainFeature getFeatureFromInput(String input) {
+        for (MainFeature feature : MainFeature.values()) {
+            if (feature.toString().equalsIgnoreCase(input)) {
+                return feature;
+            }
+        }
+        throw new IllegalArgumentException(ErrorMessage.INVALID_FEATURE.toString());
+    }
+}

리뷰: 저는 getter를 사용하는 것이 메서드 이름을 통해 명확히 "어떤 값(message)을 가져오는지" 드러낼 수 있어 의도에 맞는 함수라고 생각했기 때문에 getter 대신에 toString을 재정의하셔서 사용한 이유가 궁금합니다.
@@ -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
+                );
+    }
+}

리뷰: 2와 같은 매직넘버는 상수로 관리하는 것이 좋아보여요
@@ -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));
+    }
+}

리뷰: Optional로 반환하기 보다는, 해당 메서드에서 Line객체가 null 일 경우, 예외를 터뜨리도록 구현하는 것은 어떨까요?
해당 함수에서 처리한다면, findLine 메서드를 호출하는 다른 메서들에서 null 값을 반복적으로 처리해야하는 중복 작업을 줄일 수 있을 것 같습니다.
@@ -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();
+    }
+}

리뷰: 기본적으로, orElseThrow()는 NoSuchElementException을 던지도록 설계되어있기 때문에 새로운 예외인 `IllegalArgumentExcepion`을 던지도록 하는 작업이 필요해보여요. 그렇지 않을 경우, RetryHandler가 해당 예외를 잡지 못할 것 같습니다.
@@ -0,0 +1,29 @@
+package subway.config;
+
+import java.util.Scanner;
+import subway.controller.SubwayController;
+import subway.service.LineService;
+import subway.service.MainService;
+import subway.service.SectionService;
+import subway.service.StationService;
+import subway.view.InputView;
+import subway.view.OutputView;
+
+public enum AppConfig {
+
+    INSTANCE;
+
+    private final Scanner scanner = new Scanner(System.in);
+    private final InputView inputView = new InputView(scanner);
+    private final OutputView outputView = new OutputView();
+    private final MainService mainService = new MainService();
+    private final StationService stationService = new StationService();
+    private final LineService lineService = new LineService();
+    private final SectionService sectionService = new SectionService();
+    private final SubwayController subwayController = new SubwayController(inputView, outputView, mainService,
+            stationService, lineService, sectionService);
+
+    public SubwayController getController() {
+        return subwayController;
+    }
+}

리뷰: 해당 코드는 의존성 주입을 위한 부분이라고 생각되는데, DI를 적용하지 않고 클래스 내부에서 의존성을 직접 생성하는 것을 지향하시는걸까요??
@@ -0,0 +1,29 @@
+package subway.config;
+
+import java.util.Scanner;
+import subway.controller.SubwayController;
+import subway.service.LineService;
+import subway.service.MainService;
+import subway.service.SectionService;
+import subway.service.StationService;
+import subway.view.InputView;
+import subway.view.OutputView;
+
+public enum AppConfig {
+
+    INSTANCE;
+
+    private final Scanner scanner = new Scanner(System.in);
+    private final InputView inputView = new InputView(scanner);
+    private final OutputView outputView = new OutputView();
+    private final MainService mainService = new MainService();
+    private final StationService stationService = new StationService();
+    private final LineService lineService = new LineService();
+    private final SectionService sectionService = new SectionService();
+    private final SubwayController subwayController = new SubwayController(inputView, outputView, mainService,
+            stationService, lineService, sectionService);
+
+    public SubwayController getController() {
+        return subwayController;
+    }
+}

리뷰: 저도 이 부분 의견이 궁금합니다! @kgy1008
@@ -0,0 +1,31 @@
+package subway.constants;
+
+import subway.exception.ErrorMessage;
+
+public enum MainFeature {
+    SELECT_ONE("1"),
+    SELECT_TWO("2"),
+    SELECT_THREE("3"),
+    SELECT_FOUR("4"),
+    QUIT("Q");
+
+    final String message;
+
+    MainFeature(String message) {
+        this.message = message;
+    }
+
+    @Override
+    public String toString() {
+        return message;
+    }
+
+    public static MainFeature getFeatureFromInput(String input) {
+        for (MainFeature feature : MainFeature.values()) {
+            if (feature.toString().equalsIgnoreCase(input)) {
+                return feature;
+            }
+        }
+        throw new IllegalArgumentException(ErrorMessage.INVALID_FEATURE.toString());
+    }
+}

리뷰: 다른 코드에서 이렇게 활용하시는 걸 봐서 따라해 본 것인데, 말씀해주신 부분을 생각해보니 getXXX로 사용하는 편이 의도가 확실할 것 같습니다.