ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,42 @@
+import CONSTANTS from '../constants/constants.js';
+import MESSAGE from '../constants/message.js';
+
+class Hint {
+  #numbers;
+
+  #computerNumbers;
+
+  constructor(numbers, computerNumbers) {
+    this.#numbers = numbers;
+    this.#computerNumbers = computerNumbers;
+  }
+
+  calculateStrikeCount() {
+    return this.#numbers.reduce(
+      (count, digit, index) => (digit === this.#computerNumbers[index] ? count + 1 : count),
+      0,
+    );
+  }
+
+  calculateBallCount(strikeCount) {
+    return (
+      this.#numbers.reduce(
+        (count, digit) => (this.#computerNumbers.includes(digit) ? count + 1 : count),
+        0,
+      ) - strikeCount
+    );
+  }
+
+  generateHintMessage(strikeCount, ballCount) {
+    const hintMessage = [];
+    if (ballCount !== CONSTANTS.number.zero) hintMessage.push(`${ballCount}${MESSAGE.print.ball}`);
+    if (strikeCount !== CONSTANTS.number.zero)
+      hintMessage.push(`${strikeCount}${MESSAGE.print.strike}`);
+    if (ballCount === CONSTANTS.number.zero && strikeCount === CONSTANTS.number.zero)
+      hintMessage.push(MESSAGE.print.nothing);
+
+    return hintMessage;
+  }
+}
+
+export default Hint;

리뷰: 처음에 Hint 클래스명에 의문이 들었는데 게임이니까 얼마나 맞췄는지 알려주는 역할이라 적절할 수 있겠네요! 👍🏻
@@ -0,0 +1,20 @@
+import { Console } from '@woowacourse/mission-utils';
+import MESSAGE from '../constants/message.js';
+import NumbersValidator from '../validators/NumbersValidator.js';
+import RestartValidator from '../validators/RestartValidator.js';
+
+const InputView = {
+  async readNumbers() {
+    const numbers = await Console.readLineAsync(MESSAGE.read.numbers);
+    NumbersValidator.validateNumbers(numbers);
+    return [...numbers].map(Number);
+  },
+
+  async readRestart() {
+    const restart = await Console.readLineAsync(MESSAGE.read.restart);
+    RestartValidator.validateRestart(restart);
+    return Number(restart);
+  },
+};
+
+export default InputView;

리뷰: 이 부분에 대해서 저도 고민을 많이 해봤는데 프론트(InputVIew)와 백엔드(model)의 역할이 있다고 가정했을 때, view와 model 둘 다 유효성 검사를 진행하는 것이 맞다고 판단하여 작성하게 되었습니다.
@@ -0,0 +1,37 @@
+import CONSTANTS from '../constants/constants.js';
+import ERROR from '../constants/error.js';
+
+class NumbersValidator {
+  static validateNumbers(numbers) {
+    const validators = [
+      this.#validateEmpty,
+      this.#validateNegative,
+      this.#validateNaN,
+      this.#validateLength,
+      this.#validateDuplicated,
+    ];
+    validators.forEach(validator => validator(numbers));
+  }
+
+  static #validateLength(numbers) {
+    if (numbers.length !== CONSTANTS.number.numberSize) throw new Error(ERROR.numbers.length);
+  }
+
+  static #validateNaN(numbers) {
+    if (Number.isNaN(Number(numbers))) throw new Error(ERROR.numbers.notANumber);
+  }
+
+  static #validateNegative(numbers) {
+    if (Number(numbers) < CONSTANTS.number.zero) throw new Error(ERROR.numbers.negative);
+  }
+
+  static #validateDuplicated(numbers) {
+    if (numbers.length !== new Set(numbers).size) throw new Error(ERROR.numbers.duplicated);
+  }
+
+  static #validateEmpty(numbers) {
+    if (numbers.length === CONSTANTS.number.zero) throw new Error(ERROR.numbers.empty);
+  }
+}
+
+export default NumbersValidator;

리뷰: 유효성 검사를 모두 시행하기 위해서 하나의 메서드를 호출함으로서 각 유효성 검사가 시행되도록 작성해보았습니다!
@@ -0,0 +1,38 @@
+# 기능 구현 체크리스트
+
+## 기능 목록
+
+### 사용자의 입력
+
+- [x] 세 자리 숫자를 입력한다.
+    - [x] 숫자가 아닌 경우 예외 처리 (1 ~ 9) - 정규 표현식
+    - [x] 숫자가 3개가 아닌 경우 예외 처리
+    - [x] 중복되는 숫자가 존재하는 경우 예외 처리
+
+### 컴퓨터의 수 생성
+
+- [x] 3개의 숫자 생성 (지정된 라이브러리 이용)
+    - [x] 중복된 숫자가 발생한 경우 재생성
+
+### 사용자가 입력한 수와 컴퓨터가 생성한 수 비교
+
+- [x] 각 자리 숫자 비교하기
+    - [x] 숫자가 똑같다면 스트라이크 +1
+    - [x] 다른 위치에 있다면 볼 +1
+
+### 결과
+
+- [x] 볼, 스트라이크, 낫싱 출력
+- [x] 3스트라이크인 경우 게임 종료
+
+### 재시작 확인
+
+- [x] 재시작 입력 숫자를 입력한다.
+    - [x] 숫자가 아닌 경우 예외 처리
+    - [x] 1(재시작), 2(종료)가 아닌 경우 예외 처리
+- [x] 1을 입력받은 경우 재시작
+- [x] 2를 입력받은 경우 종료
+
+## 목표
+
+1. 객체를 객체스럽게 사용하자!!!
\ No newline at end of file

리뷰: 리뷰 시작에 앞서 리드미에 각 클래스별 역할에 대해 기재해주시면 코드 파악에 더 도움이 될거 같습니다!
@@ -0,0 +1,58 @@
+package baseball.controller;
+
+import baseball.domain.Computer;
+import baseball.domain.Referee;
+import baseball.domain.Result;
+import baseball.util.GameProgress;
+import baseball.view.InputView;
+import baseball.view.OutputView;
+import java.util.List;
+
+public class GameController {
+
+    private final Computer computer;
+    private final Referee referee;
+    private GameProgress gameProgress;
+
+    public GameController() {
+        this.computer = new Computer();
+        this.referee = new Referee();
+        this.gameProgress = GameProgress.START;
+    }
+
+    public void play() {
+        playOrEnd();
+    }
+
+    private void playOrEnd() {
+        OutputView.printStart();
+        while (!GameProgress.isQuit(gameProgress)) {
+            List<Integer> computerNumbers = computer.generateRandomNumbers();
+            playUntilThreeStrikes(computerNumbers);
+            selectRetry();
+        }
+    }
+
+    private void selectRetry() {
+        OutputView.printRetryOrEnd();
+        gameProgress = GameProgress.judgeEnd(InputView.readNumber());
+    }
+
+    private void playUntilThreeStrikes(List<Integer> computerNumbers) {
+        while (!GameProgress.isQuit(gameProgress)) {
+            OutputView.printInputNumbers();
+            Result result = referee.judge(InputView.readNumbers(), computerNumbers);
+            OutputView.printResult(result.announceResult());
+            judgeRetry(result);
+        }
+    }
+
+    private void judgeRetry(Result result) {
+        if (!result.isThreeStrikes()) {
+            gameProgress = GameProgress.RETRY;
+            return;
+        }
+        OutputView.printGameEnd();
+        gameProgress = GameProgress.QUIT;
+    }
+}

리뷰: 플레이 메서드가 단순히 playOrEnd를 호출하고 있는데 play나 playOrEnd중 하나를 없애고 합쳐도 될 것 같습니다.
@@ -0,0 +1,58 @@
+package baseball.controller;
+
+import baseball.domain.Computer;
+import baseball.domain.Referee;
+import baseball.domain.Result;
+import baseball.util.GameProgress;
+import baseball.view.InputView;
+import baseball.view.OutputView;
+import java.util.List;
+
+public class GameController {
+
+    private final Computer computer;
+    private final Referee referee;
+    private GameProgress gameProgress;
+
+    public GameController() {
+        this.computer = new Computer();
+        this.referee = new Referee();
+        this.gameProgress = GameProgress.START;
+    }
+
+    public void play() {
+        playOrEnd();
+    }
+
+    private void playOrEnd() {
+        OutputView.printStart();
+        while (!GameProgress.isQuit(gameProgress)) {
+            List<Integer> computerNumbers = computer.generateRandomNumbers();
+            playUntilThreeStrikes(computerNumbers);
+            selectRetry();
+        }
+    }
+
+    private void selectRetry() {
+        OutputView.printRetryOrEnd();
+        gameProgress = GameProgress.judgeEnd(InputView.readNumber());
+    }
+
+    private void playUntilThreeStrikes(List<Integer> computerNumbers) {
+        while (!GameProgress.isQuit(gameProgress)) {
+            OutputView.printInputNumbers();
+            Result result = referee.judge(InputView.readNumbers(), computerNumbers);
+            OutputView.printResult(result.announceResult());
+            judgeRetry(result);
+        }
+    }
+
+    private void judgeRetry(Result result) {
+        if (!result.isThreeStrikes()) {
+            gameProgress = GameProgress.RETRY;
+            return;
+        }
+        OutputView.printGameEnd();
+        gameProgress = GameProgress.QUIT;
+    }
+}

리뷰: while의 조건문을 긍정문으로 하는 것이 가독성에 더 도움이 된다고 하더라구요. 
```suggestion
        while (GameProgress.isRetry(gameProgress)) {
```
이렇게 바꿔보시는 것도 한번 제안드려봅니다!
@@ -0,0 +1,23 @@
+package baseball.domain;
+
+import camp.nextstep.edu.missionutils.Randoms;
+import java.util.ArrayList;
+import java.util.List;
+
+public class Computer {
+
+    private static final int NUMBERS_LENGTH = 3;
+    private static final int START_RANGE_NUMBER = 1;
+    private static final int END_RANGE_NUMBER = 9;
+
+    public List<Integer> generateRandomNumbers() {
+        List<Integer> computer = new ArrayList<>();
+        while (computer.size() < NUMBERS_LENGTH) {
+            int randomNumber = Randoms.pickNumberInRange(START_RANGE_NUMBER, END_RANGE_NUMBER);
+            if (!computer.contains(randomNumber)) {
+                computer.add(randomNumber);
+            }
+        }
+        return computer;
+    }
+}

리뷰: 정적 메서드를 선언하신 기준을 살펴보면 Computer도 유틸 기능 + 상태값을 가지지 않기 때문에 정적 메서드로 선언해도 된다고 생각하는데 어떠신가요?
@@ -0,0 +1,17 @@
+package baseball.domain;
+
+import java.util.List;
+
+public class Referee {
+
+    public Result judge(List<Integer> userNumbers, List<Integer> computerNumbers) {
+        Result result = new Result();
+        for (int i = 0; i < userNumbers.size(); i++) {
+            if (result.isStrike(userNumbers, computerNumbers, i)) {
+                continue;
+            }
+            result.countBall(userNumbers, computerNumbers, i);
+        }
+        return result;
+    }
+}

리뷰: 컴퓨터와 마찬가지로 레퍼리도 정적 메서드로 선언해도 괜찮지 않을까 하는 생각입니다!
@@ -0,0 +1,19 @@
+package baseball.util;
+
+public enum GameProgress {
+
+    START, RETRY, QUIT;
+
+    private static final int RETRY_NUMBER = 1;
+
+    public static boolean isQuit(GameProgress gameProgress) {
+        return gameProgress == QUIT;
+    }
+
+    public static GameProgress judgeEnd(int number) {
+        if (number == RETRY_NUMBER) {
+            return START;
+        }
+        return QUIT;
+    }
+}

리뷰: 프로그램의 확장성을 고려하셔서 enum으로 만드신거 맞으실까요? 저도 처음에 종료 메뉴를 enum으로 만드려고 하다가 경우의 수가 2가지여서 class로 만들었습니다. 만약 경우의 수가 더 많았다면 저도 enum으로 만들었을 것 같네요!
@@ -0,0 +1,19 @@
+package baseball.util;
+
+public enum GameProgress {
+
+    START, RETRY, QUIT;
+
+    private static final int RETRY_NUMBER = 1;
+
+    public static boolean isQuit(GameProgress gameProgress) {
+        return gameProgress == QUIT;
+    }
+
+    public static GameProgress judgeEnd(int number) {
+        if (number == RETRY_NUMBER) {
+            return START;
+        }
+        return QUIT;
+    }
+}

리뷰: 따로 상수를 선언하시기 보다 RETRY(1), QUIT(2); 이런 식으로 값을 지정해주시는건 어떠신지 제안드려봅니다!
@@ -0,0 +1,34 @@
+package baseball.view;
+
+import baseball.util.Validator;
+import camp.nextstep.edu.missionutils.Console;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+public class InputView {
+
+    public static final String DELIMITER = "";
+
+    public static List<Integer> readNumbers() {
+        String input = input();
+        Validator.validateNumbersPattern(input);
+
+        List<Integer> numbers = Stream.of(input.split(DELIMITER))
+                .map(Integer::parseInt)
+                .distinct()
+                .collect(Collectors.toList());
+        Validator.validateDuplication(numbers);
+        return numbers;
+    }
+
+    public static int readNumber() {
+        String input = input();
+        Validator.validateNumberPattern(input);
+        return Integer.parseInt(input);
+    }
+
+    private static String input() {
+        return Console.readLine();
+    }
+}

리뷰: view가 inputd을 domain에서 사용될 형태로 가공해서 넘겨주는 것이 맞는 걸까요??? 저도 프리코스를 하면서 항상 의구심 가져왔던 부분이라서 레이싱카 다시 만들기에서는 시도횟수 또한 String 값으로만 넘겨줬었는데요. 이 부분 목요일에 꼭 얘기 나눠봤으면 좋겠습니다!
@@ -0,0 +1,65 @@
+package baseball.domain;
+
+import java.util.List;
+
+public class Result {
+
+    private static final int DEFAULT_NUMBER = 0;
+    private static final int END_NUMBER = 3;
+
+    private int ball;
+    private int strike;
+
+    public Result() {
+        this.ball = DEFAULT_NUMBER;
+        this.strike = DEFAULT_NUMBER;
+    }
+
+    public boolean isStrike(List<Integer> userNumbers, List<Integer> computerNumbers, int index) {
+        if (userNumbers.get(index).equals(computerNumbers.get(index))) {
+            strike++;
+            return true;
+        }
+        return false;
+    }
+
+    public void countBall(List<Integer> userNumbers, List<Integer> computerNumbers, int index) {
+        if (computerNumbers.contains(userNumbers.get(index))) {
+            ball++;
+        }
+    }
+
+    public boolean isThreeStrikes() {
+        return strike == END_NUMBER;
+    }
+
+    public String announceResult() {
+        if (ball == DEFAULT_NUMBER && strike == DEFAULT_NUMBER) {
+            return Message.NOTHING.getMessage();
+        }
+        if (ball == DEFAULT_NUMBER) {
+            return String.format(Message.STRIKE.getMessage(), strike);
+        }
+        if (strike == DEFAULT_NUMBER) {
+            return String.format(Message.BALL.getMessage(), ball);
+        }
+        return String.format(Message.BALL_AND_STRIKE.getMessage(), ball, strike);
+    }
+
+    private enum Message {
+        NOTHING("낫싱"),
+        BALL("%d볼"),
+        STRIKE("%d스트라이크"),
+        BALL_AND_STRIKE("%d볼 %d스트라이크");
+
+        private final String message;
+
+        Message(String message) {
+            this.message = message;
+        }
+
+        public String getMessage() {
+            return message;
+        }
+    }
+}

리뷰: 해당 출력에 대한 책임을 OutputView에 넘겨주시는건 어떠신가요?
@@ -0,0 +1,32 @@
+package baseball.util;
+
+import java.util.List;
+import java.util.regex.Pattern;
+
+public class Validator {
+
+    private static final String DUPLICATION_MESSAGE = "중복된 숫자가 존재합니다. 다시 입력해주세요.";
+    private static final String NUMBERS_PATTERN_MESSAGE = "1부터 9까지의 숫자 중 3가지 숫자를 입력해주세요.";
+    private static final String NUMBER_PATTERN_MESSAGE = "재시작을 원하면 1, 종료를 원하면 2를 입력해주세요.";
+    private static final Pattern REGEX_NUMBERS = Pattern.compile("^[1-9]{3}$");
+    private static final Pattern REGEX_NUMBER = Pattern.compile("^[1-2]{1}$");
+    private static final int NUMBERS_LENGTH = 3;
+
+    public static void validateDuplication(List<Integer> input) {
+        if (input.size() < NUMBERS_LENGTH) {
+            throw new IllegalArgumentException(DUPLICATION_MESSAGE);
+        }
+    }
+
+    public static void validateNumbersPattern(String input) {
+        if (!REGEX_NUMBERS.matcher(input).matches()) {
+            throw new IllegalArgumentException(NUMBERS_PATTERN_MESSAGE);
+        }
+    }
+
+    public static void validateNumberPattern(String input) {
+        if (!REGEX_NUMBER.matcher(input).matches()) {
+            throw new IllegalArgumentException(NUMBER_PATTERN_MESSAGE);
+        }
+    }
+}

리뷰: 정규식 표현이 매우 깔끔하네요! 배우고 갑니다.
@@ -0,0 +1,58 @@
+package baseball.controller;
+
+import baseball.domain.Computer;
+import baseball.domain.Referee;
+import baseball.domain.Result;
+import baseball.util.GameProgress;
+import baseball.view.InputView;
+import baseball.view.OutputView;
+import java.util.List;
+
+public class GameController {
+
+    private final Computer computer;
+    private final Referee referee;
+    private GameProgress gameProgress;
+
+    public GameController() {
+        this.computer = new Computer();
+        this.referee = new Referee();
+        this.gameProgress = GameProgress.START;
+    }
+
+    public void play() {
+        playOrEnd();
+    }
+
+    private void playOrEnd() {
+        OutputView.printStart();
+        while (!GameProgress.isQuit(gameProgress)) {
+            List<Integer> computerNumbers = computer.generateRandomNumbers();
+            playUntilThreeStrikes(computerNumbers);
+            selectRetry();
+        }
+    }
+
+    private void selectRetry() {
+        OutputView.printRetryOrEnd();
+        gameProgress = GameProgress.judgeEnd(InputView.readNumber());
+    }
+
+    private void playUntilThreeStrikes(List<Integer> computerNumbers) {
+        while (!GameProgress.isQuit(gameProgress)) {
+            OutputView.printInputNumbers();
+            Result result = referee.judge(InputView.readNumbers(), computerNumbers);
+            OutputView.printResult(result.announceResult());
+            judgeRetry(result);
+        }
+    }
+
+    private void judgeRetry(Result result) {
+        if (!result.isThreeStrikes()) {
+            gameProgress = GameProgress.RETRY;
+            return;
+        }
+        OutputView.printGameEnd();
+        gameProgress = GameProgress.QUIT;
+    }
+}

리뷰: 함수명을 ThreeStrike처럼 우승 조건에 대해 직접적으로 나타내는 것보다 playUntilWin 같은 방식으로 작성하는건 어떨까요??
@@ -0,0 +1,65 @@
+package baseball.domain;
+
+import java.util.List;
+
+public class Result {
+
+    private static final int DEFAULT_NUMBER = 0;
+    private static final int END_NUMBER = 3;
+
+    private int ball;
+    private int strike;
+
+    public Result() {
+        this.ball = DEFAULT_NUMBER;
+        this.strike = DEFAULT_NUMBER;
+    }
+
+    public boolean isStrike(List<Integer> userNumbers, List<Integer> computerNumbers, int index) {
+        if (userNumbers.get(index).equals(computerNumbers.get(index))) {
+            strike++;
+            return true;
+        }
+        return false;
+    }
+
+    public void countBall(List<Integer> userNumbers, List<Integer> computerNumbers, int index) {
+        if (computerNumbers.contains(userNumbers.get(index))) {
+            ball++;
+        }
+    }
+
+    public boolean isThreeStrikes() {
+        return strike == END_NUMBER;
+    }
+
+    public String announceResult() {
+        if (ball == DEFAULT_NUMBER && strike == DEFAULT_NUMBER) {
+            return Message.NOTHING.getMessage();
+        }
+        if (ball == DEFAULT_NUMBER) {
+            return String.format(Message.STRIKE.getMessage(), strike);
+        }
+        if (strike == DEFAULT_NUMBER) {
+            return String.format(Message.BALL.getMessage(), ball);
+        }
+        return String.format(Message.BALL_AND_STRIKE.getMessage(), ball, strike);
+    }
+
+    private enum Message {
+        NOTHING("낫싱"),
+        BALL("%d볼"),
+        STRIKE("%d스트라이크"),
+        BALL_AND_STRIKE("%d볼 %d스트라이크");
+
+        private final String message;
+
+        Message(String message) {
+            this.message = message;
+        }
+
+        public String getMessage() {
+            return message;
+        }
+    }
+}

리뷰: isStrike와 통일하여 isBall로 안한 이유가 따로 있나요 !?
@@ -0,0 +1,65 @@
+package baseball.domain;
+
+import java.util.List;
+
+public class Result {
+
+    private static final int DEFAULT_NUMBER = 0;
+    private static final int END_NUMBER = 3;
+
+    private int ball;
+    private int strike;
+
+    public Result() {
+        this.ball = DEFAULT_NUMBER;
+        this.strike = DEFAULT_NUMBER;
+    }
+
+    public boolean isStrike(List<Integer> userNumbers, List<Integer> computerNumbers, int index) {
+        if (userNumbers.get(index).equals(computerNumbers.get(index))) {
+            strike++;
+            return true;
+        }
+        return false;
+    }
+
+    public void countBall(List<Integer> userNumbers, List<Integer> computerNumbers, int index) {
+        if (computerNumbers.contains(userNumbers.get(index))) {
+            ball++;
+        }
+    }
+
+    public boolean isThreeStrikes() {
+        return strike == END_NUMBER;
+    }
+
+    public String announceResult() {
+        if (ball == DEFAULT_NUMBER && strike == DEFAULT_NUMBER) {
+            return Message.NOTHING.getMessage();
+        }
+        if (ball == DEFAULT_NUMBER) {
+            return String.format(Message.STRIKE.getMessage(), strike);
+        }
+        if (strike == DEFAULT_NUMBER) {
+            return String.format(Message.BALL.getMessage(), ball);
+        }
+        return String.format(Message.BALL_AND_STRIKE.getMessage(), ball, strike);
+    }
+
+    private enum Message {
+        NOTHING("낫싱"),
+        BALL("%d볼"),
+        STRIKE("%d스트라이크"),
+        BALL_AND_STRIKE("%d볼 %d스트라이크");
+
+        private final String message;
+
+        Message(String message) {
+            this.message = message;
+        }
+
+        public String getMessage() {
+            return message;
+        }
+    }
+}

리뷰: 오 .. 이방식으로 하는거 좋은거 같네요 
근데 아래 조건에는 else if 로 나타내도 좋을거같아요 !
@@ -29,7 +29,7 @@ public void initialize() {
         beanFactory.initialize();
     }
 
-    public Collection<Object> controllers() {
+    public Collection<Object> getControllers() {
         return beanFactory.beansAnnotatedWith(Controller.class);
     }
 }

리뷰: 피드백 반영 잘 해주셨네요 👍
@@ -1,6 +1,5 @@
 package next.controller;
 
-import core.annotation.Inject;
 import core.annotation.web.Controller;
 import core.annotation.web.RequestMapping;
 import core.annotation.web.RequestMethod;
@@ -22,14 +21,8 @@
 public class ApiQnaController extends AbstractNewController {
     private static final Logger logger = LoggerFactory.getLogger( ApiQnaController.class );
 
-    private final QuestionDao questionDao;
-    private final AnswerDao answerDao;
-
-    @Inject
-    public ApiQnaController(QuestionDao questionDao, AnswerDao answerDao) {
-        this.questionDao = questionDao;
-        this.answerDao = answerDao;
-    }
+    private final QuestionDao questionDao = QuestionDao.getInstance();
+    private final AnswerDao answerDao = AnswerDao.getInstance();
 
     @RequestMapping(value = "/api/qna/list", method = RequestMethod.GET)
     public ModelAndView questions(HttpServletRequest req, HttpServletResponse resp) throws Exception {

리뷰: BeanFactory, getInstantiateSingletons NPE 발생 문제로 getInstance() 호출로 수정하신거 맞을까요? 
원인이 무엇이었나요?
이전 코드(생성자 inject) 방식으로 문제를 해결해보면 좋을 것 같아요.
MyConfiguration 클래스에서 등록한 DataSource도 빈으로 등록했으니 JdbcTemplate에서 사용해보면 좋을 것 같구요!
@@ -0,0 +1,19 @@
+import { ShoppingCartIcon } from "../../assets";
+import { BaseButton } from "./BaseButton";
+import { StyledCartButtonImg, StyledCartCount, StyledContainer } from "./CartButton.styled";
+
+interface CartButtonProps {
+  quantity: number;
+  onClick: () => void;
+}
+
+export const CartButton = ({ quantity, onClick }: CartButtonProps) => {
+  return (
+    <BaseButton onClick={onClick}>
+      <StyledContainer>
+        <StyledCartButtonImg src={ShoppingCartIcon} />
+        {quantity && <StyledCartCount>{quantity}</StyledCartCount>}
+      </StyledContainer>
+    </BaseButton>
+  );
+};

리뷰: 애플리케이션이 처음 실행됐을 때 fetch한 이후에는, 담기/빼기 버튼을 눌러도 productCount가 변경되지 않기 때문에 실시간으로 개수를 업데이트하지 못하는 것으로 이해했어요. prop으로 `productCount`값과 `updateProductCount` 함수를 만들어 CartActionButton로 넘겨주고, CartActionButton에서는 action이 일어나면 `updateProductCount`에 바뀐 개수를 CartButton으로 전달하면 업데이트할 수 있을 것 같아요!😊
@@ -0,0 +1,37 @@
+import { Suspense, useState } from "react";
+import { useSearchParams } from "react-router-dom";
+
+import { InputSection } from "./components/InputSection";
+import { ListSection } from "./components/ListSection/ListSection";
+
+export const SearchPage = () => {
+  const [, setSearchParams] = useSearchParams();
+
+  const [keyword, setKeyword] = useState<string>("");
+
+  const handleKeywordChange = (keyword: string) => {
+    setKeyword(keyword);
+  };
+
+  const handleSearch = (keyword: string) => {
+    console.log("검색어: ", keyword);
+    setSearchParams({ keyword });
+  };
+
+  return (
+    <main>
+      <InputSection
+        keyword={keyword}
+        onKeywordChange={handleKeywordChange}
+        onInputSubmit={handleSearch}
+      />
+      {keyword ? (
+        <Suspense fallback={<p>검색중...</p>}>
+          <ListSection />
+        </Suspense>
+      ) : (
+        <p>검색어를 입력해주세요</p>
+      )}
+    </main>
+  );
+};

리뷰: url의 쿼리 파라미터를 이용해 검색어 키워드를 관리하도록 설정한 것은, url을 전달하며 검색 결과도 함께 전달할 수 있도록 하기 위함이에요. 그런데 현재는 쿼리 파라미터를 검색어의 기본값으로 내려받지 못하는 문제가 발생했어요. 

TODO: 서치파라미터 값을 키워드 초기값으로 설정

```suggestion
  const [searchParams, setSearchParams] = useSearchParams();

  const [keyword, setKeyword] = useState<string>(searchParams.get("keyword") || "");
```
@@ -0,0 +1,30 @@
+import { useLazyLoadQuery } from "react-relay";
+import { useSearchParams } from "react-router-dom";
+
+import { graphql } from "relay-runtime";
+
+import { ListSectionRepositorySearchQuery } from "./__generated__/ListSectionRepositorySearchQuery.graphql";
+
+import { RepositoryPagination } from "./RepositoryPagination";
+
+export const ListSection = () => {
+  const [searchParams] = useSearchParams();
+  const keyword = searchParams.get("keyword") || "";
+
+  const data = useLazyLoadQuery<ListSectionRepositorySearchQuery>(
+    graphql`
+      query ListSectionRepositorySearchQuery($keyword: String!) {
+        ...RepositoryPagination @arguments(keyword: $keyword)
+      }
+    `,
+    {
+      keyword,
+    }
+  );
+
+  return (
+    <ul>
+      <RepositoryPagination data={data} />
+    </ul>
+  );
+};

리뷰: TODO: List Section 내에서 키워드를 직접 가져오지 말고, Search Page에서 props로 키워드를 주입받도록 변경