ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,76 @@
+package store.view;
+
+import java.util.List;
+import store.view.dto.response.BuyProductResponse;
+import store.view.dto.response.ErrorResponse;
+import store.view.dto.response.FreeProductResponse;
+import store.view.dto.response.ProductCalculateResponse;
+
+public class OutputView {
+
+    private static final String LINE_SEPARATOR = System.lineSeparator();
+
+    public void welcomeStore() {
+        System.out.println(LINE_SEPARATOR + "안녕하세요. W편의점입니다." + LINE_SEPARATOR + "현재 보유하고 있는 상품입니다." + LINE_SEPARATOR);
+    }
+
+    public void showInventory(String name, int price, String quantity, String promotionType) {
+        System.out.printf(
+                "- %s %,d원 %s %s"
+                        + LINE_SEPARATOR,
+                name, price, quantity, promotionType
+        );
+    }
+
+    public void getCustomerProducts() {
+        System.out.println(LINE_SEPARATOR + "구매하실 상품명과 수량을 입력해 주세요. (예: [사이다-2],[감자칩-1])");
+    }
+
+    public void guideBuyProducts() {
+        System.out.printf(
+                LINE_SEPARATOR +
+                "==============W 편의점================"
+                        + LINE_SEPARATOR
+                        + "%-10s %9s %9s%n"
+                , "상품명", "수량" ,"금액"
+        );
+    }
+
+    public void showBuyProducts(List<BuyProductResponse> buyProducts) {
+        for (BuyProductResponse product : buyProducts) {
+            System.out.printf(
+                    "%-10s %9d %,14d" + LINE_SEPARATOR,
+                    product.name(), product.quantity(), product.totalPrice()
+            );
+        }
+    }
+
+    public void guideFreeGetQuantity() {
+        System.out.println("=============증\t\t정===============");
+    }
+
+    public void showFreeQuantity(List<FreeProductResponse> freeProductResponses) {
+        for (FreeProductResponse freeProductResponse : freeProductResponses) {
+            System.out.printf(
+                    "%-10s %9d" + LINE_SEPARATOR,
+                    freeProductResponse.name(), freeProductResponse.quantity()
+            );
+        }
+    }
+
+    public void showCalculateResult(ProductCalculateResponse calculateInfo) {
+        System.out.printf(
+                "====================================" + LINE_SEPARATOR
+                        + "총구매액\t\t\t\t%d\t\t  %,d" + LINE_SEPARATOR
+                        + "행사할인\t\t\t\t\t\t  -%,d" + LINE_SEPARATOR
+                        + "멤버십할인\t\t\t\t\t\t  -%,d" + LINE_SEPARATOR
+                        + "내실돈\t\t\t\t\t\t  %,d" + LINE_SEPARATOR
+                , calculateInfo.buyCounts(), calculateInfo.buyProductsPrice(),
+                 calculateInfo.promotionPrice(), calculateInfo.memberShip(), calculateInfo.totalPrice()
+        );
+    }
+
+    public void showError(ErrorResponse errorResponse) {
+        System.out.println(errorResponse.message());
+    }
+}

리뷰: System.lineSeparator()를 활용할 수 있군요!! 배워갑니다:)
@@ -0,0 +1,63 @@
+package store.domain;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import store.domain.exception.ProductErrorCode;
+import store.domain.exception.ProductException;
+
+public class BuyProductParser {
+
+    private static final int NAME_INDEX = 1;
+    private static final int PRICE_INDEX = 2;
+    private static final String INPUT_DELIMITER = ",";
+    private static final String ORDER_FORM = "\\[(.+)-(.+)]";
+
+    private final Map<String, String> products;
+
+    public BuyProductParser(String buyProduct) {
+        this.products = makeProductsForm(buyProduct);
+    }
+
+    public Map<String, String> getProducts() {
+        return Collections.unmodifiableMap(products);
+    }
+
+    private Map<String, String> makeProductsForm(String buyProducts) {
+        Map<String, String> products = new LinkedHashMap<>();
+        parseProducts(buyProducts, products);
+        return products;
+    }
+
+    private void parseProducts(String buyProducts, Map<String, String> products) {
+        Pattern pattern = Pattern.compile(ORDER_FORM);
+        parseByRest(buyProducts).forEach(product -> {
+            validateFrom(product);
+            Matcher matcher = pattern.matcher(product);
+            validateMatcher(matcher);
+            products.put(matcher.group(NAME_INDEX).trim(), matcher.group(PRICE_INDEX).trim());
+        });
+    }
+
+    private void validateMatcher(Matcher matcher) {
+        if (!matcher.matches()) {
+            throw new ProductException(ProductErrorCode.WRONG_INPUT);
+        }
+    }
+
+    private List<String> parseByRest(String buyProduct) {
+        if (buyProduct.contains(INPUT_DELIMITER)) {
+            return List.of(buyProduct.split(INPUT_DELIMITER));
+        }
+        return List.of(buyProduct);
+    }
+
+    private void validateFrom(String buyProducts) {
+        if (!buyProducts.matches(ORDER_FORM)) {
+            throw new ProductException(ProductErrorCode.WRONG_INPUT);
+        }
+    }
+}

리뷰: parse 클래스는 domain보다 util 패키지 내에 있는게 적합하다고 생각하는데, 상휘님의 의견은 어떠신가요?
@@ -0,0 +1,54 @@
+package store.domain;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import store.domain.exception.ProductErrorCode;
+import store.domain.exception.ProductException;
+
+public class FileReader {
+
+    private static final String CONTENT_SPLIT_DELIMITER = ",";
+
+    private final List<List<String>> fileContents;
+
+    public FileReader(String filePath) {
+        this.fileContents = makeFileContents(filePath);
+    }
+
+    public List<List<String>> fileContents() {
+        return Collections.unmodifiableList(fileContents);
+    }
+
+    private List<List<String>> makeFileContents(String filePath) {
+        List<List<String>> fileContents = new ArrayList<>();
+
+        try (BufferedReader fileReader = new BufferedReader(new java.io.FileReader(filePath))) {
+            skipOneLine(fileReader);
+            makeFileContent(fileReader, fileContents);
+        } catch (IOException e) {
+            throw new ProductException(ProductErrorCode.NOT_FOUND_FILE_PATH);
+        }
+
+        return fileContents;
+    }
+
+    private void makeFileContent(BufferedReader fileReader, List<List<String>> fileContents) throws IOException {
+        String line;
+
+        while ((line = fileReader.readLine()) != null) {
+            List<String> contentInfo = splitProducts(line);
+            fileContents.add(contentInfo);
+        }
+    }
+
+    private void skipOneLine(BufferedReader fileReader) throws IOException {
+        fileReader.readLine();
+    }
+
+    private List<String> splitProducts(String productInformation) {
+        return List.of(productInformation.split(CONTENT_SPLIT_DELIMITER));
+    }
+}

리뷰: FileReader도 util 패키지 내에 있는게 적합하다고 생각합니다!
@@ -0,0 +1,86 @@
+package store.domain;
+
+import java.util.Objects;
+import store.domain.vo.ProductName;
+import store.domain.vo.ProductPrice;
+import store.domain.vo.ProductQuantity;
+
+public class Product {
+
+    private final ProductName name;
+    private final ProductPrice price;
+    private final ProductQuantity quantity;
+    private final Promotion promotion;
+
+    public Product(String name, String price, String quantity, Promotion promotion) {
+        this.name = ProductName.from(name);
+        this.price = ProductPrice.from(price);
+        this.quantity = ProductQuantity.from(quantity);
+        this.promotion = promotion;
+    }
+
+    public String getName() {
+        return name.getName();
+    }
+
+    public int getPrice() {
+        return price.getPrice();
+    }
+
+    public int getQuantity() {
+        return quantity.getQuantity();
+    }
+
+    public String showQuantity() {
+        return quantity.showQuantity();
+    }
+
+    public String getPromotionType() {
+        return promotion.getPromotionType();
+    }
+
+    public boolean isPromotion() {
+        return promotion.isPromotion();
+    }
+
+    public int getPromotionBuyQuantity() {
+        return promotion.getBuyQuantity();
+    }
+
+    public int getPromotionGettableQuantity() {
+        return promotion.getGettableQuantity();
+    }
+
+    public int getPromotionQuantity() {
+        return (quantity.getQuantity() / getOnePromotionCycle()) * promotion.getGettableQuantity();
+    }
+
+    public int getOnePromotionCycle() {
+        return promotion.getOnePromotionCycle();
+    }
+
+    public void buyProduct(int buyQuantity) {
+        quantity.minusQuantity(buyQuantity);
+    }
+
+    public void freeProduct(int addQuantity) {
+        quantity.addQuantity(addQuantity);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        Product that = (Product) o;
+        return name.equals(that.name);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(name);
+    }
+}

리뷰: 도메인 패키지 내에 클래스가 많은 것 같아요!
product와 promotion 등의 패키지를 한번 더 만들어서 분리해주시면 도메인의 역할이 명확히 보일 것 같아요!
@@ -0,0 +1,63 @@
+package store.domain;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import store.domain.exception.ProductErrorCode;
+import store.domain.exception.ProductException;
+
+public class BuyProductParser {
+
+    private static final int NAME_INDEX = 1;
+    private static final int PRICE_INDEX = 2;
+    private static final String INPUT_DELIMITER = ",";
+    private static final String ORDER_FORM = "\\[(.+)-(.+)]";
+
+    private final Map<String, String> products;
+
+    public BuyProductParser(String buyProduct) {
+        this.products = makeProductsForm(buyProduct);
+    }
+
+    public Map<String, String> getProducts() {
+        return Collections.unmodifiableMap(products);
+    }
+
+    private Map<String, String> makeProductsForm(String buyProducts) {
+        Map<String, String> products = new LinkedHashMap<>();
+        parseProducts(buyProducts, products);
+        return products;
+    }
+
+    private void parseProducts(String buyProducts, Map<String, String> products) {
+        Pattern pattern = Pattern.compile(ORDER_FORM);
+        parseByRest(buyProducts).forEach(product -> {
+            validateFrom(product);
+            Matcher matcher = pattern.matcher(product);
+            validateMatcher(matcher);
+            products.put(matcher.group(NAME_INDEX).trim(), matcher.group(PRICE_INDEX).trim());
+        });
+    }
+
+    private void validateMatcher(Matcher matcher) {
+        if (!matcher.matches()) {
+            throw new ProductException(ProductErrorCode.WRONG_INPUT);
+        }
+    }
+
+    private List<String> parseByRest(String buyProduct) {
+        if (buyProduct.contains(INPUT_DELIMITER)) {
+            return List.of(buyProduct.split(INPUT_DELIMITER));
+        }
+        return List.of(buyProduct);
+    }
+
+    private void validateFrom(String buyProducts) {
+        if (!buyProducts.matches(ORDER_FORM)) {
+            throw new ProductException(ProductErrorCode.WRONG_INPUT);
+        }
+    }
+}

리뷰: private 메서드로 메서드를 분리하는 것은 어떨까요?
@@ -0,0 +1,56 @@
+package store.domain;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import store.domain.exception.ProductErrorCode;
+import store.domain.exception.ProductException;
+
+public class BuyProducts {
+
+    private final List<Product> products;
+
+    public BuyProducts(Map<String, String> splitProducts, Promotions promotions, Products products) {
+        this.products = createBuyProducts(splitProducts, promotions);
+        validateExistence(products);
+        validateQuantity(products);
+    }
+
+    public List<Product> products() {
+        return Collections.unmodifiableList(products);
+    }
+
+    private List<Product> createBuyProducts(Map<String, String> splitProducts, Promotions promotions) {
+        List<Product> products = new ArrayList<>();
+
+        for (Entry<String, String> entry : splitProducts.entrySet()) {
+            Product product = new Product(
+                    entry.getKey(),
+                    ProductInfo.findPriceByName(entry.getKey()),
+                    entry.getValue(),
+                    promotions.getPromotionByType(PromotionInfo.findPromotionByName(entry.getKey())));
+            products.add(product);
+        }
+
+        return products;
+    }
+
+    private void validateExistence(Products inventoryProducts) {
+        for (Product buyProduct : products) {
+            if (!inventoryProducts.getProducts().contains(buyProduct)) {
+                throw new ProductException(ProductErrorCode.NOT_FOUND_PRODUCT_NAME);
+            }
+        }
+    }
+
+    private void validateQuantity(Products inventoryProducts) {
+        for (Product buyProduct : products) {
+            int availableQuantity = inventoryProducts.countProductsByName(buyProduct.getName());
+            if (availableQuantity < buyProduct.getQuantity()) {
+                throw new ProductException(ProductErrorCode.OVER_QUANTITY);
+            }
+        }
+    }
+}

리뷰: 반환해줄떄 데이터의 안정성을 지키기 위해 사용하셨군요 배워갑니다
@@ -0,0 +1,56 @@
+package store.domain;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import store.domain.exception.ProductErrorCode;
+import store.domain.exception.ProductException;
+
+public class BuyProducts {
+
+    private final List<Product> products;
+
+    public BuyProducts(Map<String, String> splitProducts, Promotions promotions, Products products) {
+        this.products = createBuyProducts(splitProducts, promotions);
+        validateExistence(products);
+        validateQuantity(products);
+    }
+
+    public List<Product> products() {
+        return Collections.unmodifiableList(products);
+    }
+
+    private List<Product> createBuyProducts(Map<String, String> splitProducts, Promotions promotions) {
+        List<Product> products = new ArrayList<>();
+
+        for (Entry<String, String> entry : splitProducts.entrySet()) {
+            Product product = new Product(
+                    entry.getKey(),
+                    ProductInfo.findPriceByName(entry.getKey()),
+                    entry.getValue(),
+                    promotions.getPromotionByType(PromotionInfo.findPromotionByName(entry.getKey())));
+            products.add(product);
+        }
+
+        return products;
+    }
+
+    private void validateExistence(Products inventoryProducts) {
+        for (Product buyProduct : products) {
+            if (!inventoryProducts.getProducts().contains(buyProduct)) {
+                throw new ProductException(ProductErrorCode.NOT_FOUND_PRODUCT_NAME);
+            }
+        }
+    }
+
+    private void validateQuantity(Products inventoryProducts) {
+        for (Product buyProduct : products) {
+            int availableQuantity = inventoryProducts.countProductsByName(buyProduct.getName());
+            if (availableQuantity < buyProduct.getQuantity()) {
+                throw new ProductException(ProductErrorCode.OVER_QUANTITY);
+            }
+        }
+    }
+}

리뷰: 저도 entry를 코테를 볼 때 별도의 클래스 작성이 필요없고 편해 많이 사용하는데 이는 가독성 면에서 많이 떨어진다고 느낍니다 이보다는 따로 DTO를 만드는게 나을것 같습니다
@@ -0,0 +1,56 @@
+package store.domain;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import store.domain.exception.ProductErrorCode;
+import store.domain.exception.ProductException;
+
+public class BuyProducts {
+
+    private final List<Product> products;
+
+    public BuyProducts(Map<String, String> splitProducts, Promotions promotions, Products products) {
+        this.products = createBuyProducts(splitProducts, promotions);
+        validateExistence(products);
+        validateQuantity(products);
+    }
+
+    public List<Product> products() {
+        return Collections.unmodifiableList(products);
+    }
+
+    private List<Product> createBuyProducts(Map<String, String> splitProducts, Promotions promotions) {
+        List<Product> products = new ArrayList<>();
+
+        for (Entry<String, String> entry : splitProducts.entrySet()) {
+            Product product = new Product(
+                    entry.getKey(),
+                    ProductInfo.findPriceByName(entry.getKey()),
+                    entry.getValue(),
+                    promotions.getPromotionByType(PromotionInfo.findPromotionByName(entry.getKey())));
+            products.add(product);
+        }
+
+        return products;
+    }
+
+    private void validateExistence(Products inventoryProducts) {
+        for (Product buyProduct : products) {
+            if (!inventoryProducts.getProducts().contains(buyProduct)) {
+                throw new ProductException(ProductErrorCode.NOT_FOUND_PRODUCT_NAME);
+            }
+        }
+    }
+
+    private void validateQuantity(Products inventoryProducts) {
+        for (Product buyProduct : products) {
+            int availableQuantity = inventoryProducts.countProductsByName(buyProduct.getName());
+            if (availableQuantity < buyProduct.getQuantity()) {
+                throw new ProductException(ProductErrorCode.OVER_QUANTITY);
+            }
+        }
+    }
+}

리뷰: 단순히 getProducts후 contains를 직접적으로 하기보단 Products에 public 메서드를 따로 두는 것은 어떤가요?
@@ -0,0 +1,79 @@
+package store.domain;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map.Entry;
+import store.domain.vo.MemberShip;
+import store.domain.vo.ProductQuantity;
+import store.view.dto.response.BuyProductResponse;
+import store.view.dto.response.FreeProductResponse;
+import store.view.dto.response.ProductCalculateResponse;
+
+public class CustomerReceipt {
+
+    private final Receipt receipt;
+    private final MemberShip memberShipDiscount;
+
+    public CustomerReceipt(Receipt receipt, MemberShip memberShipDiscount) {
+        this.receipt = receipt;
+        this.memberShipDiscount = memberShipDiscount;
+    }
+
+    public List<BuyProductResponse> showBuyProducts() {
+        List<BuyProductResponse> products = new ArrayList<>();
+        for (Entry<Product, ProductQuantity> entry : receipt.getReceipt().entrySet()) {
+            products.add(new BuyProductResponse(entry.getKey().getName(), entry.getKey().getQuantity(),
+                    entry.getKey().getQuantity() * entry.getKey().getPrice()));
+        }
+        return Collections.unmodifiableList(products);
+    }
+
+    public List<FreeProductResponse> showFreeProductInfo() {
+        List<FreeProductResponse> freeProductResponses = new ArrayList<>();
+        for (Entry<Product, ProductQuantity> entry : receipt.getReceipt().entrySet()) {
+            freeProductResponses.add(
+                    new FreeProductResponse(entry.getKey().getName(), entry.getValue().getQuantity())
+            );
+        }
+        return Collections.unmodifiableList(freeProductResponses);
+    }
+
+    public ProductCalculateResponse showCalculateInfo() {
+        return new ProductCalculateResponse(
+                productsAllQuantity(),
+                productsAllPrice(),
+                promotionPrice(),
+                memberShipDiscount.getDisCountPrice(),
+                totalPrice()
+        );
+    }
+
+    private int totalPrice() {
+        return productsAllPrice() - promotionPrice() - memberShipDiscount.getDisCountPrice();
+    }
+
+    private int productsAllPrice() {
+        return receipt.getReceipt()
+                .keySet()
+                .stream()
+                .mapToInt(product -> product.getPrice() * product.getQuantity())
+                .sum();
+    }
+
+    private int promotionPrice() {
+        return receipt.getReceipt()
+                .entrySet()
+                .stream()
+                .mapToInt(entry -> entry.getKey().getPrice() * entry.getValue().getQuantity())
+                .sum();
+    }
+
+    private int productsAllQuantity() {
+        return receipt.getReceipt()
+                .keySet()
+                .stream()
+                .mapToInt(Product::getQuantity)
+                .sum();
+    }
+}

리뷰: 엔트리를 직접적으로 사용하는 경우 코드의 가독성이 떨어진다고 생각합니다. getKey(), getValue()과 같이 키와 값으로 나타내는 메서드는 코드를 읽는 사람에게 key는 무엇이지부터 시작하게 되어 따로 만드는게 좋을것같습니다
@@ -0,0 +1,78 @@
+import {
+  ChampagnePromotionAvailable,
+  receivedD_dayPromotion,
+  toTalPriceLogic,
+} from "../src/DomainLogic";
+import menuAndQuantity from "../src/utils/menuAndQuantity";
+
+jest.mock("../src/InputView", () => ({
+  christmasInstance: {
+    getMenus: jest.fn(),
+    getDate: jest.fn(),
+  },
+}));
+
+jest.mock("../src/utils/menuAndQuantity", () => ({
+  __esModule: true,
+  default: jest.fn(),
+}));
+
+jest.mock("../src/DomainLogic", () => ({
+  ...jest.requireActual("../src/DomainLogic"),
+  toTalPriceLogic: jest.fn(),
+}));
+
+describe("DomainLogic 기능 테스트", () => {
+  test("할인 전 총주문 금액", () => {
+    const mockedMenus = ["양송이수프-2", "양송이수프-1", "티본스테이크-3"];
+    require("../src/InputView").christmasInstance.getMenus.mockReturnValue(
+      mockedMenus
+    );
+
+    menuAndQuantity
+      .mockReturnValueOnce({ MENU_NAME: "양송이수프", QUANTITY: 2 })
+      .mockReturnValueOnce({ MENU_NAME: "초코케이크", QUANTITY: 1 })
+      .mockReturnValueOnce({ MENU_NAME: "티본스테이크", QUANTITY: 3 });
+
+    const MENUS_PRICE = {
+      양송이수프: 6000,
+      초코케이크: 15000,
+      티본스테이크: 55000,
+    };
+
+    toTalPriceLogic.mockReturnValueOnce(192000);
+
+    const result = toTalPriceLogic(MENUS_PRICE);
+    const expectedTotalPrice = 2 * 6000 + 1 * 15000 + 3 * 55000;
+
+    expect(result).toBe(expectedTotalPrice);
+  });
+
+  test("증정 메뉴", () => {
+    const mockedMenus = ["아이스크림-2", "초코케이크-1"];
+    require("../src/InputView").christmasInstance.getMenus.mockReturnValue(
+      mockedMenus
+    );
+
+    toTalPriceLogic.mockReturnValueOnce(25000);
+
+    const result = ChampagnePromotionAvailable();
+    expect(result).toBe(false);
+  });
+
+  test("크리스마스 디데이 할인", () => {
+    require("../src/InputView").christmasInstance.getDate.mockReturnValue(25);
+
+    const result = receivedD_dayPromotion();
+
+    expect(result).toBe(3400);
+  });
+
+  test("크리스마스 디데이 할인 예외", () => {
+    require("../src/InputView").christmasInstance.getDate.mockReturnValue(26);
+
+    const result = receivedD_dayPromotion();
+
+    expect(result).toBe(0);
+  });
+});

리뷰: 각 테스트케이스마다 인스턴스를 생성하는 식이 아닌 mock으로 사용하신 이유가 있을까요?
@@ -1,5 +1,29 @@
+import { MissionUtils } from "@woowacourse/mission-utils";
+import InputView from "./InputView.js";
+import OutputView from "./OutputView.js";
+
 class App {
-  async run() {}
+  async run() {
+    await christmasPromotionProcess();
+  }
+}
+
+async function christmasPromotionProcess() {
+  try {
+    OutputView.printIntroduction();
+    await InputView.readDate();
+    await InputView.readOrderMenu();
+    OutputView.printBenefitIntroduction();
+    OutputView.printMenu();
+    OutputView.printTotalPrice();
+    OutputView.printChampagnePromotion();
+    OutputView.printReceivedPromotion();
+    OutputView.printReceivedTotalBenefitPrice();
+    OutputView.printTotalPriceAfterDiscount();
+    OutputView.printEventBadge();
+  } catch (error) {
+    MissionUtils.Console.print(error.message);
+  }
 }
 
 export default App;

리뷰: 결과 출력에 따라 다시 메서드로 묶어서 간결하게 보여주는 건 어떨까요?

예를 들면 현재 Menu부터 Badge까지 항목들은 다 결과출력에 해당되니까 printResult () 같이 메서드를 하나 만들고 그 안에서 전부 다 호출하는 식으로 각 세부사항은 숨기면 더 읽기 편할 거 같아요