ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,45 @@
+package calculator.model;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.BiFunction;
+
+public enum OperatorType {
+    ADDITION("+", (a, b) -> a + b),
+    SUBTRACTION("-", (a, b) -> a - b),
+    MULTIPLICATION("*", (a, b) -> a * b),
+    DIVISION("/", (a, b) -> {
+        if (b == 0) {
+            throw new ArithmeticException("0으로 나눌 수 없습니다.");
+        }
+        return a / b;
+    });
+
+    private static final Map<String, OperatorType> SYMBOL_MAP = new HashMap<>();
+
+    static {
+        for (OperatorType type : values()) {
+            SYMBOL_MAP.put(type.symbol, type);
+        }
+    }
+
+    private final String symbol;
+    private final BiFunction<Integer, Integer, Integer> operation;
+
+    OperatorType(String symbol, BiFunction<Integer, Integer, Integer> operation) {
+        this.symbol = symbol;
+        this.operation = operation;
+    }
+
+    public static OperatorType fromString(String symbol) {
+        OperatorType type = SYMBOL_MAP.get(symbol);
+        if (type == null) {
+            throw new IllegalArgumentException("잘못된 연산자입니다.");
+        }
+        return type;
+    }
+
+    public int apply(int a, int b) {
+        return operation.apply(a, b);
+    }
+}
\ No newline at end of file

리뷰: 객체를 변경하지 않아서 인스턴스를 생성하지 않아도 된다고 생각했고 
static메소드로 선언하면 인스턴스 생성없이 호출 할 수 있어서 사용했습니다.
@@ -0,0 +1,17 @@
+package calculator.model;
+
+public class Operator {
+
+    public int calculate(String[] values) {
+        int result = Integer.parseInt(values[0]);
+
+        for (int i = 1; i < values.length; i += 2) {
+            String symbol = values[i];
+            int nextNumber = Integer.parseInt(values[i + 1]);
+            OperatorType operatorType = OperatorType.fromString(symbol);
+            result = operatorType.apply(result, nextNumber);
+        }
+
+        return result;
+    }
+}

리뷰: BiFunction 필드를 활용하여 Switch문 없이 연산을 처리 할 수 있게 수정했습니다.
@@ -0,0 +1,27 @@
+package calculator.controller;
+
+import calculator.model.Operator;
+import calculator.util.FormulaParser;
+import calculator.view.InputView;
+import calculator.view.OutputView;
+
+public class CalculatorController {
+    private final Operator operator;
+    private final InputView inputView;
+    private final OutputView outputView;
+    private final FormulaParser formulaParser;
+
+    public CalculatorController(Operator operator, InputView inputView, OutputView outputView, FormulaParser formulaParser) {
+        this.operator = operator;
+        this.inputView = inputView;
+        this.outputView = outputView;
+        this.formulaParser = formulaParser;
+    }
+
+    public void calculatorRun() {
+        String formula = inputView.formulaInput();
+        String[] values = formulaParser.parse(formula);
+        int result = operator.calculate(values);
+        outputView.resultOutput(result);
+    }
+}

리뷰: values는 어떤 용도로 사용되는 변수인가요
@@ -0,0 +1,17 @@
+package calculator.model;
+
+public class Operator {
+
+    public int calculate(String[] values) {
+        int result = Integer.parseInt(values[0]);
+
+        for (int i = 1; i < values.length; i += 2) {
+            String symbol = values[i];
+            int nextNumber = Integer.parseInt(values[i + 1]);
+            OperatorType operatorType = OperatorType.fromString(symbol);
+            result = operatorType.apply(result, nextNumber);
+        }
+
+        return result;
+    }
+}

리뷰: 어떤 매개변수를 받아오는 건가요. 
매개변수가 메소드 내에서 어떤 역할을 하는지 알 수 있도록 지어주세요
@@ -0,0 +1,17 @@
+package calculator.model;
+
+public class Operator {
+
+    public int calculate(String[] values) {
+        int result = Integer.parseInt(values[0]);
+
+        for (int i = 1; i < values.length; i += 2) {
+            String symbol = values[i];
+            int nextNumber = Integer.parseInt(values[i + 1]);
+            OperatorType operatorType = OperatorType.fromString(symbol);
+            result = operatorType.apply(result, nextNumber);
+        }
+
+        return result;
+    }
+}

리뷰: static 메소드를 사용해야할 이유가 있을까요?
@@ -0,0 +1,45 @@
+package calculator.model;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.BiFunction;
+
+public enum OperatorType {
+    ADDITION("+", (a, b) -> a + b),
+    SUBTRACTION("-", (a, b) -> a - b),
+    MULTIPLICATION("*", (a, b) -> a * b),
+    DIVISION("/", (a, b) -> {
+        if (b == 0) {
+            throw new ArithmeticException("0으로 나눌 수 없습니다.");
+        }
+        return a / b;
+    });
+
+    private static final Map<String, OperatorType> SYMBOL_MAP = new HashMap<>();
+
+    static {
+        for (OperatorType type : values()) {
+            SYMBOL_MAP.put(type.symbol, type);
+        }
+    }
+
+    private final String symbol;
+    private final BiFunction<Integer, Integer, Integer> operation;
+
+    OperatorType(String symbol, BiFunction<Integer, Integer, Integer> operation) {
+        this.symbol = symbol;
+        this.operation = operation;
+    }
+
+    public static OperatorType fromString(String symbol) {
+        OperatorType type = SYMBOL_MAP.get(symbol);
+        if (type == null) {
+            throw new IllegalArgumentException("잘못된 연산자입니다.");
+        }
+        return type;
+    }
+
+    public int apply(int a, int b) {
+        return operation.apply(a, b);
+    }
+}
\ No newline at end of file

리뷰: 이걸 필드로 선언한 이유가 있을까요?
데이터 주도 설계가 아닌 도메인 주도 설계를 공부해보세
@@ -0,0 +1,17 @@
+package calculator.model;
+
+public class Operator {
+
+    public int calculate(String[] values) {
+        int result = Integer.parseInt(values[0]);
+
+        for (int i = 1; i < values.length; i += 2) {
+            String symbol = values[i];
+            int nextNumber = Integer.parseInt(values[i + 1]);
+            OperatorType operatorType = OperatorType.fromString(symbol);
+            result = operatorType.apply(result, nextNumber);
+        }
+
+        return result;
+    }
+}

리뷰: 현재 푸쉬한 코드에 존재하지 않는 코드입니다.
@@ -0,0 +1,17 @@
+package calculator.model;
+
+public class Operator {
+
+    public int calculate(String[] values) {
+        int result = Integer.parseInt(values[0]);
+
+        for (int i = 1; i < values.length; i += 2) {
+            String symbol = values[i];
+            int nextNumber = Integer.parseInt(values[i + 1]);
+            OperatorType operatorType = OperatorType.fromString(symbol);
+            result = operatorType.apply(result, nextNumber);
+        }
+
+        return result;
+    }
+}

리뷰: values 말한 거에요
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Bucket
+   uuid = "F89D4C7C-1180-42EB-AF20-BEE64400FB01"
+   type = "1"
+   version = "2.0">
+</Bucket>

리뷰: 이 파일은 무엇인가요 . .?
gitignore 로 안올리는게 좋을것가탸요
@@ -10,11 +10,11 @@ import UIKit
 class HomeViewController: UIViewController {
     
     private var feeds: [Feed] = [
-        Feed(username: "Woody", profileImage: "profileImage", title: "우럭먹다 받은 영감", hashTags: [HashTag(hashTag: "#Guitar"), HashTag(hashTag: "#Jazz")]),
-        Feed(username: "Woody", profileImage: "profileImage", title: "우럭먹다 받은 영감", hashTags: [HashTag(hashTag: "#Guitar"), HashTag(hashTag: "#Jazz")]),
-        Feed(username: "Woody", profileImage: "profileImage", title: "우럭먹다 받은 영감", hashTags: [HashTag(hashTag: "#Guitar"), HashTag(hashTag: "#Jazz")]),
-        Feed(username: "Woody", profileImage: "profileImage", title: "우럭먹다 받은 영감", hashTags: [HashTag(hashTag: "#Guitar"), HashTag(hashTag: "#Jazz")]),
-        Feed(username: "Woody", profileImage: "profileImage", title: "우럭먹다 받은 영감", hashTags: [HashTag(hashTag: "#Guitar"), HashTag(hashTag: "#Jazz")])
+        Feed(username: "Woody", profileImage: "profileImage", image: "wooluck", author: "alice", title: "우럭먹다 받은 영감", hashTags: [HashTag(hashTag: "#Guitar"), HashTag(hashTag: "#Jazz")]),
+        Feed(username: "Woody", profileImage: "profileImage", image: "wooluck", author: "alice", title: "우럭먹다 받은 영감", hashTags: [HashTag(hashTag: "#Guitar"), HashTag(hashTag: "#Jazz")]),
+        Feed(username: "Woody", profileImage: "profileImage", image: "wooluck", author: "alice", title: "우럭먹다 받은 영감", hashTags: [HashTag(hashTag: "#Guitar"), HashTag(hashTag: "#Jazz")]),
+        Feed(username: "Woody", profileImage: "profileImage", image: "wooluck", author: "alice", title: "우럭먹다 받은 영감", hashTags: [HashTag(hashTag: "#Guitar"), HashTag(hashTag: "#Jazz")]),
+        Feed(username: "Woody", profileImage: "profileImage", image: "wooluck", author: "alice", title: "우럭먹다 받은 영감", hashTags: [HashTag(hashTag: "#Guitar"), HashTag(hashTag: "#Jazz")])
     ]
     
     private let feedCollectionView: UICollectionView = {
@@ -79,13 +79,14 @@ extension HomeViewController: UICollectionViewDelegate, UICollectionViewDataSour
         return cell
     }
 
+    // Cell Spacing
     func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
         30.0
     }
     
     // Cell size
     func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
-        return CGSize(width: view.bounds.width - 32.0, height: 524)
+        return CGSize(width: view.bounds.width - 32.0, height: 550)
     }
     
 }

리뷰: 550의 의미는 무엇인가요 !
의미가 있다면 let으로 명명해서 추후에 유지보수 때 알아보기 쉽게하는것이 좋을 듯합니다 ~
@@ -21,3 +21,25 @@ extension UICollectionViewCell {
         NSStringFromClass(self.classForCoder()).components(separatedBy: ".").last!
     }
 }
+
+extension UIImage {
+ 
+    func applyBlur_usingClamp(radius: CGFloat) -> UIImage {
+        let context = CIContext()
+        guard let ciImage = CIImage(image: self),
+            let clampFilter = CIFilter(name: "CIAffineClamp"),
+            let blurFilter = CIFilter(name: "CIGaussianBlur") else {
+            return self
+        }
+        clampFilter.setValue(ciImage, forKey: kCIInputImageKey)
+        blurFilter.setValue(clampFilter.outputImage, forKey: kCIInputImageKey)
+        blurFilter.setValue(radius, forKey: kCIInputRadiusKey)
+        guard let output = blurFilter.outputImage,
+            let cgimg = context.createCGImage(output, from: ciImage.extent) else {
+            return self
+        }
+
+        return UIImage(cgImage: cgimg)
+    }
+}
+

리뷰: Swift는 카멜케이스 ~ 🐫
@@ -21,3 +21,25 @@ extension UICollectionViewCell {
         NSStringFromClass(self.classForCoder()).components(separatedBy: ".").last!
     }
 }
+
+extension UIImage {
+ 
+    func applyBlur_usingClamp(radius: CGFloat) -> UIImage {
+        let context = CIContext()
+        guard let ciImage = CIImage(image: self),
+            let clampFilter = CIFilter(name: "CIAffineClamp"),
+            let blurFilter = CIFilter(name: "CIGaussianBlur") else {
+            return self
+        }
+        clampFilter.setValue(ciImage, forKey: kCIInputImageKey)
+        blurFilter.setValue(clampFilter.outputImage, forKey: kCIInputImageKey)
+        blurFilter.setValue(radius, forKey: kCIInputRadiusKey)
+        guard let output = blurFilter.outputImage,
+            let cgimg = context.createCGImage(output, from: ciImage.extent) else {
+            return self
+        }
+
+        return UIImage(cgImage: cgimg)
+    }
+}
+

리뷰: 계속 extension이 늘어날 예정이라면 UIImage+.swift 로 별도로 빼면어떨까욥
@@ -10,11 +10,11 @@ import UIKit
 class HomeViewController: UIViewController {
     
     private var feeds: [Feed] = [
-        Feed(username: "Woody", profileImage: "profileImage", title: "우럭먹다 받은 영감", hashTags: [HashTag(hashTag: "#Guitar"), HashTag(hashTag: "#Jazz")]),
-        Feed(username: "Woody", profileImage: "profileImage", title: "우럭먹다 받은 영감", hashTags: [HashTag(hashTag: "#Guitar"), HashTag(hashTag: "#Jazz")]),
-        Feed(username: "Woody", profileImage: "profileImage", title: "우럭먹다 받은 영감", hashTags: [HashTag(hashTag: "#Guitar"), HashTag(hashTag: "#Jazz")]),
-        Feed(username: "Woody", profileImage: "profileImage", title: "우럭먹다 받은 영감", hashTags: [HashTag(hashTag: "#Guitar"), HashTag(hashTag: "#Jazz")]),
-        Feed(username: "Woody", profileImage: "profileImage", title: "우럭먹다 받은 영감", hashTags: [HashTag(hashTag: "#Guitar"), HashTag(hashTag: "#Jazz")])
+        Feed(username: "Woody", profileImage: "profileImage", image: "wooluck", author: "alice", title: "우럭먹다 받은 영감", hashTags: [HashTag(hashTag: "#Guitar"), HashTag(hashTag: "#Jazz")]),
+        Feed(username: "Woody", profileImage: "profileImage", image: "wooluck", author: "alice", title: "우럭먹다 받은 영감", hashTags: [HashTag(hashTag: "#Guitar"), HashTag(hashTag: "#Jazz")]),
+        Feed(username: "Woody", profileImage: "profileImage", image: "wooluck", author: "alice", title: "우럭먹다 받은 영감", hashTags: [HashTag(hashTag: "#Guitar"), HashTag(hashTag: "#Jazz")]),
+        Feed(username: "Woody", profileImage: "profileImage", image: "wooluck", author: "alice", title: "우럭먹다 받은 영감", hashTags: [HashTag(hashTag: "#Guitar"), HashTag(hashTag: "#Jazz")]),
+        Feed(username: "Woody", profileImage: "profileImage", image: "wooluck", author: "alice", title: "우럭먹다 받은 영감", hashTags: [HashTag(hashTag: "#Guitar"), HashTag(hashTag: "#Jazz")])
     ]
     
     private let feedCollectionView: UICollectionView = {
@@ -79,13 +79,14 @@ extension HomeViewController: UICollectionViewDelegate, UICollectionViewDataSour
         return cell
     }
 
+    // Cell Spacing
     func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
         30.0
     }
     
     // Cell size
     func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
-        return CGSize(width: view.bounds.width - 32.0, height: 524)
+        return CGSize(width: view.bounds.width - 32.0, height: 550)
     }
     
 }

리뷰: 의미는 없읍니다.. 눈으로 대강 height 쟀습니다 let으로 변수 선언하는것은 해당 func 내부에 선언하는 것이 좋겠죠?
@@ -10,11 +10,11 @@ import UIKit
 class HomeViewController: UIViewController {
     
     private var feeds: [Feed] = [
-        Feed(username: "Woody", profileImage: "profileImage", title: "우럭먹다 받은 영감", hashTags: [HashTag(hashTag: "#Guitar"), HashTag(hashTag: "#Jazz")]),
-        Feed(username: "Woody", profileImage: "profileImage", title: "우럭먹다 받은 영감", hashTags: [HashTag(hashTag: "#Guitar"), HashTag(hashTag: "#Jazz")]),
-        Feed(username: "Woody", profileImage: "profileImage", title: "우럭먹다 받은 영감", hashTags: [HashTag(hashTag: "#Guitar"), HashTag(hashTag: "#Jazz")]),
-        Feed(username: "Woody", profileImage: "profileImage", title: "우럭먹다 받은 영감", hashTags: [HashTag(hashTag: "#Guitar"), HashTag(hashTag: "#Jazz")]),
-        Feed(username: "Woody", profileImage: "profileImage", title: "우럭먹다 받은 영감", hashTags: [HashTag(hashTag: "#Guitar"), HashTag(hashTag: "#Jazz")])
+        Feed(username: "Woody", profileImage: "profileImage", image: "wooluck", author: "alice", title: "우럭먹다 받은 영감", hashTags: [HashTag(hashTag: "#Guitar"), HashTag(hashTag: "#Jazz")]),
+        Feed(username: "Woody", profileImage: "profileImage", image: "wooluck", author: "alice", title: "우럭먹다 받은 영감", hashTags: [HashTag(hashTag: "#Guitar"), HashTag(hashTag: "#Jazz")]),
+        Feed(username: "Woody", profileImage: "profileImage", image: "wooluck", author: "alice", title: "우럭먹다 받은 영감", hashTags: [HashTag(hashTag: "#Guitar"), HashTag(hashTag: "#Jazz")]),
+        Feed(username: "Woody", profileImage: "profileImage", image: "wooluck", author: "alice", title: "우럭먹다 받은 영감", hashTags: [HashTag(hashTag: "#Guitar"), HashTag(hashTag: "#Jazz")]),
+        Feed(username: "Woody", profileImage: "profileImage", image: "wooluck", author: "alice", title: "우럭먹다 받은 영감", hashTags: [HashTag(hashTag: "#Guitar"), HashTag(hashTag: "#Jazz")])
     ]
     
     private let feedCollectionView: UICollectionView = {
@@ -79,13 +79,14 @@ extension HomeViewController: UICollectionViewDelegate, UICollectionViewDataSour
         return cell
     }
 
+    // Cell Spacing
     func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
         30.0
     }
     
     // Cell size
     func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
-        return CGSize(width: view.bounds.width - 32.0, height: 524)
+        return CGSize(width: view.bounds.width - 32.0, height: 550)
     }
     
 }

리뷰: > 눈으로 대강 height 쟀습니다

🤔 ..?????

>  let으로 변수 선언하는것은 해당 func 내부에 선언하는 것이 좋겠죠?

물론이죠 !
@@ -0,0 +1,29 @@
+import ItemCard from "@/components/ItemCard";
+import styled from "styled-components";
+import { flexCenter } from "@/styles/common";
+import { Product } from "@/types/products";
+import SeaOtterVideo from "@/components/SeaOtterVideo";
+
+const ItemCardList = ({ products, isLoading }: { products: Product[]; isLoading: boolean }) => {
+  return (
+    <ItemCardWrapper>
+      {products.length || isLoading ? (
+        products.map((product) => <ItemCard key={Math.random() * 10000} product={product} />)
+      ) : (
+        <>
+          <SeaOtterVideo />
+          <h2>🦦 눈 씻고 찾아봐도 상품이 없어요.. ㅠㅠ</h2>
+        </>
+      )}
+    </ItemCardWrapper>
+  );
+};
+
+export default ItemCardList;
+
+const ItemCardWrapper = styled.div`
+  display: flex;
+  gap: 14px;
+  flex-wrap: wrap;
+  ${flexCenter}
+`;

리뷰: 여기서 product.id 가 key 값이 아니라 Math.random 으로 주신 이유가 따로 있으실까용 ??
@@ -0,0 +1,15 @@
+import * as S from "@/components/Header/style";
+
+export const HeaderMain = ({ children }: React.PropsWithChildren) => {
+  return <S.Header>{children}</S.Header>;
+};
+
+export const Title = ({ text }: { text: string }) => {
+  return <S.Title>{text}</S.Title>;
+};
+
+const Header = Object.assign(HeaderMain, {
+  Title,
+});
+
+export default Header;

리뷰: 너무 좋아요~!
@@ -0,0 +1,29 @@
+import ItemCard from "@/components/ItemCard";
+import styled from "styled-components";
+import { flexCenter } from "@/styles/common";
+import { Product } from "@/types/products";
+import SeaOtterVideo from "@/components/SeaOtterVideo";
+
+const ItemCardList = ({ products, isLoading }: { products: Product[]; isLoading: boolean }) => {
+  return (
+    <ItemCardWrapper>
+      {products.length || isLoading ? (
+        products.map((product) => <ItemCard key={Math.random() * 10000} product={product} />)
+      ) : (
+        <>
+          <SeaOtterVideo />
+          <h2>🦦 눈 씻고 찾아봐도 상품이 없어요.. ㅠㅠ</h2>
+        </>
+      )}
+    </ItemCardWrapper>
+  );
+};
+
+export default ItemCardList;
+
+const ItemCardWrapper = styled.div`
+  display: flex;
+  gap: 14px;
+  flex-wrap: wrap;
+  ${flexCenter}
+`;

리뷰: 서버에서 간헐적으로 중복된 key 를 반환하는 문제가 있어서 한 번씩 동일한 key 값으로 에러가 발생하기에 random 값을 key 로 설정해줬어요!
@@ -0,0 +1,23 @@
+package store.constants;
+
+import static store.constants.StringConstants.NEW_LINE;
+import static store.constants.StringConstants.ONE_SPACE;
+import static store.constants.StringConstants.TAP;
+
+public class OutputMessages {
+    public static String WELCOME_MESSAGE = "안녕하세요. W편의점입니다." + NEW_LINE + "현재 보유하고 있는 상품입니다." + NEW_LINE.repeat(2);
+    public static String PURCHASER_LIST_FORMAT =
+            "===========W 편의점=============" + NEW_LINE + "상품명" + TAP.repeat(2) + "수량" + TAP + "금액" + NEW_LINE;
+    public static String FREEBIE_LIST_FORMAT = "===========증" + TAP + "정=============" + NEW_LINE;
+    public static String AMOUNT_INFORMATION_FORMAT = "==============================" + NEW_LINE;
+    
+    public static String TOTAL_PURCHASE_AMOUNT = "총구매액" + TAP.repeat(2);
+    public static String PROMOTION_DISCOUNT = "행사할인" + TAP.repeat(3);
+    public static String MEMBERSHIP_DISCOUNT = "멤버십할인" + TAP.repeat(3);
+    public static String TOTAL_PRICE = "내실돈" + TAP.repeat(3) + ONE_SPACE;
+
+    public static String CURRENCY_UNIT = "원";
+    public static String QUANTITY_UNIT = "개";
+    public static String OUT_OF_STOCK = "재고 없음";
+
+}

리뷰: 이렇게 작은 하드코딩 문자열 같은 경우에 대해서 학과 선배님께 물어봤는데, 이런 작은 부분(문자열을 조립해야하는 부분)은 많지 않으면 클래스 내에서 선언하고 쓴다는 의견을 들었습니다!
@@ -0,0 +1,88 @@
+package store.controller;
+
+import static store.constants.InputMessages.PRODUCTS_FILE_NAME;
+import static store.constants.InputMessages.PROMOTIONS_FILE_NAME;
+
+import java.util.List;
+import store.config.IoConfig;
+import store.domain.Choice;
+import store.domain.MembershipManager;
+import store.domain.Product;
+import store.domain.PromotionManager;
+import store.domain.Receipt;
+import store.domain.StoreHouse;
+import store.dto.PromotionInfo;
+import store.dto.Purchase;
+import store.io.InputView;
+import store.io.OutputView;
+import store.service.StoreService;
+
+public class StoreController {
+
+
+    private final InputView inputView;
+    private final OutputView outputView;
+    private StoreService service;
+
+    public StoreController(IoConfig ioConfig) {
+        this.inputView = ioConfig.getInputView();
+        this.outputView = ioConfig.getOutputView();
+    }
+
+    public void run() {
+        try {
+            storeOpen();
+        } catch (IllegalArgumentException e) {
+            System.out.println(e.getMessage());
+        }
+    }
+
+    public void storeOpen() {
+        StoreHouse storeHouse = inputView.readProductsFileInput(PRODUCTS_FILE_NAME);
+        List<PromotionInfo> promotionInfos = inputView.readPromotionsFileInput(PROMOTIONS_FILE_NAME);
+
+        PromotionManager promotionManager = new PromotionManager(promotionInfos, storeHouse, inputView);
+        MembershipManager membershipManager = new MembershipManager(storeHouse);
+        service = new StoreService(promotionManager, membershipManager);
+
+        List<Product> allProduct = storeHouse.getProductList();
+        processPurchase(allProduct, storeHouse, membershipManager);
+    }
+
+    private void processPurchase(List<Product> allProduct, StoreHouse storeHouse, MembershipManager membershipManager) {
+        while (true) {
+            outputView.printProductList(allProduct);
+            Receipt receipt = getUserPurchase(storeHouse);
+            applyMembershipDiscount(receipt);
+            outputView.printReceipt(receipt, storeHouse, membershipManager);
+            if (inputView.readAdditionalPurchaseChoice().equals(Choice.N)) {
+                break;
+            }
+        }
+    }
+
+    private Receipt getUserPurchase(StoreHouse storeHouse) {
+        while (true) {
+            List<Purchase> purchaseList = inputView.readProductNameAndQuantity();
+            try {
+                return getReceipt(storeHouse, purchaseList);
+            } catch (IllegalArgumentException e) {
+                System.out.println(e.getMessage());
+            }
+        }
+    }
+
+    private Receipt getReceipt(StoreHouse storeHouse, List<Purchase> purchaseList) {
+        Receipt receipt = service.purchase(purchaseList, storeHouse);
+        receipt.setPurchaseList(purchaseList);
+        return receipt;
+    }
+
+    private void applyMembershipDiscount(Receipt receipt) {
+        Choice membershipDiscountApplicationChoice = inputView.readMembershipDiscountApplicationChoice();
+        if (membershipDiscountApplicationChoice.equals(Choice.Y)) {
+            service.applyMembershipDiscount(receipt);
+        }
+    }
+
+}

리뷰: 동의합니다!
@@ -0,0 +1,15 @@
+package store.constants;
+
+public class StringConstants {
+    public static final String COMMA = ",";
+    public static final String DASH = "-";
+    public static final String NEW_LINE = "\n";
+    public static final String TAP = "\t";
+    public static final String OPEN_SQUARE_BRACKETS = "[";
+    public static final String CLOSE_SQUARE_BRACKETS = "]";
+    public static final String EMPTY_STRING = "";
+    public static final String ONE_SPACE = " ";
+    public static final String DATE_TIME_FORMATTER_PATTERN = "yyyy-MM-dd";
+    public static final String NUMBER_FORMAT_WITH_COMMA = "%,d";
+
+}

리뷰: 꼼꼼한 상수 처리! 볼 때마다 감탄을 자아냅니다!