ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,61 @@
+import { forwardRef, useContext } from "react";
+import * as PI from "./ProductItem.style";
+import CartControlButton from "../../Button/CartControlButton";
+import { deleteProductInCart, postProductInCart } from "@api/index";
+import { useError } from "@hooks/index";
+import { CartItemsContext } from "@context/CartItemsContext";
+
+interface ProductProps {
+  product: Product;
+}
+
+const ProductItem = forwardRef<HTMLDivElement, ProductProps>(
+  ({ product }, ref) => {
+    const { cartItems, refreshCartItems } = useContext(CartItemsContext);
+
+    const cartItemIds = cartItems.map((item) => item.product.id);
+    const isInCart = cartItemIds.includes(product.id);
+
+    const { showError } = useError();
+
+    const handleIsInCart = async () => {
+      try {
+        if (!isInCart) {
+          await postProductInCart(product.id);
+          refreshCartItems();
+          return;
+        }
+
+        const filteredItem = cartItems.find(
+          (item) => item.product.id === product.id,
+        );
+        if (filteredItem) {
+          await deleteProductInCart(filteredItem.id);
+          refreshCartItems();
+        }
+      } catch (error) {
+        if (error instanceof Error) {
+          showError(error.message);
+        }
+      }
+    };
+
+    return (
+      <PI.ProductItemStyle ref={ref}>
+        <PI.ProductImg
+          src={`${product.imageUrl}`}
+          alt={`${product.name} 상품 이미지`}
+        />
+        <PI.ProductGroup>
+          <PI.ProductContent>
+            <PI.ProductName>{product.name}</PI.ProductName>
+            <span>{product.price.toLocaleString("ko-kr")}원</span>
+          </PI.ProductContent>
+          <CartControlButton onClick={handleIsInCart} isInCart={isInCart} />
+        </PI.ProductGroup>
+      </PI.ProductItemStyle>
+    );
+  },
+);
+
+export default ProductItem;

리뷰: 진짜 정성어린 답변 최고..
@@ -0,0 +1,44 @@
+import { FILTER_CATEGORIES, SORT_PRICE } from "@constants/rules";
+import * as PLH from "./ProductListHeader.style";
+
+interface ProductListHeaderProps {
+  handleCategory: (category: Category) => void;
+  handleSort: (sort: Sort) => void;
+}
+
+const ProductListHeader = ({
+  handleCategory,
+  handleSort,
+}: ProductListHeaderProps) => {
+  return (
+    <PLH.Header>
+      <PLH.Title>bpple 상품 목록</PLH.Title>
+      <PLH.SelectBoxGroup>
+        <PLH.SelectBox
+          name=""
+          id=""
+          onChange={(e) => handleCategory(e.target.value as Category)}
+        >
+          {Object.entries(FILTER_CATEGORIES).map(([key, value]) => (
+            <option value={key} key={key}>
+              {value}
+            </option>
+          ))}
+        </PLH.SelectBox>
+        <PLH.SelectBox
+          name=""
+          id=""
+          onChange={(e) => handleSort(e.target.value as Sort)}
+        >
+          {Object.entries(SORT_PRICE).map(([key, value]) => (
+            <option value={key} key={key}>
+              {value}
+            </option>
+          ))}
+        </PLH.SelectBox>
+      </PLH.SelectBoxGroup>
+    </PLH.Header>
+  );
+};
+
+export default ProductListHeader;

리뷰: 앗.. 그것은 미처 작성하지 못한 저의 실수..
@@ -0,0 +1,44 @@
+import { FILTER_CATEGORIES, SORT_PRICE } from "@constants/rules";
+import * as PLH from "./ProductListHeader.style";
+
+interface ProductListHeaderProps {
+  handleCategory: (category: Category) => void;
+  handleSort: (sort: Sort) => void;
+}
+
+const ProductListHeader = ({
+  handleCategory,
+  handleSort,
+}: ProductListHeaderProps) => {
+  return (
+    <PLH.Header>
+      <PLH.Title>bpple 상품 목록</PLH.Title>
+      <PLH.SelectBoxGroup>
+        <PLH.SelectBox
+          name=""
+          id=""
+          onChange={(e) => handleCategory(e.target.value as Category)}
+        >
+          {Object.entries(FILTER_CATEGORIES).map(([key, value]) => (
+            <option value={key} key={key}>
+              {value}
+            </option>
+          ))}
+        </PLH.SelectBox>
+        <PLH.SelectBox
+          name=""
+          id=""
+          onChange={(e) => handleSort(e.target.value as Sort)}
+        >
+          {Object.entries(SORT_PRICE).map(([key, value]) => (
+            <option value={key} key={key}>
+              {value}
+            </option>
+          ))}
+        </PLH.SelectBox>
+      </PLH.SelectBoxGroup>
+    </PLH.Header>
+  );
+};
+
+export default ProductListHeader;

리뷰: 따로 분리할까 고민했었는데, 의견이 들어왔으니 분리해보겠습니다!
@@ -0,0 +1,23 @@
+# https://www.acmicpc.net/problem/1181
+# https://blockdmask.tistory.com/543
+# set을 이용하면 중복이 제거됨
+# https://always-challenger-lab.tistory.com/22
+# 람다 이용해서 다중조건 정렬
+import sys
+
+n = int(sys.stdin.readline())
+s = []
+m = []
+for i in range(n):
+    s.append(str(sys.stdin.readline().strip()))
+
+
+set_s = list(set(s))
+for j in set_s:
+    m.append([j,len(j)])
+
+print(m)
+m.sort(key=lambda x:(x[1],x[0]))
+
+for i in m:
+    print(i[0])
\ No newline at end of file

리뷰: 람다 사용이 신박헤네요 ㅇ.ㅇ 참고가 됐습니다.
@@ -0,0 +1,9 @@
+package com.codesquad.baseballgame.domain.game.controller;
+
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class GameController {
+
+
+}

리뷰: 음. 이런 컨트롤러는 쓰지 않는다면 그때그때 지워주는 것이 좋겠습니다.
@@ -0,0 +1,24 @@
+package com.codesquad.baseballgame.domain.game.controller;
+
+import com.codesquad.baseballgame.domain.game.service.MatchService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequiredArgsConstructor
+public class MatchController {
+
+    private final MatchService matchService;
+
+    @PostMapping("games/{id}")
+    public ResponseEntity<Boolean> matching(@PathVariable int id) {
+        if (matchService.matchStatus(id)) {
+            return new ResponseEntity<>(true, HttpStatus.OK);
+        }
+        return new ResponseEntity<>(false, HttpStatus.OK);
+    }
+}

리뷰: 설계하기 나름이긴 하지만, `@PathVariable` 만을 파라미터로 받는 엔드포인트에서 `POST` 를 쓰는건 다소 어색해보이긴 하네요. 
뭔가가 누락된 건 아닌가요. 리턴 바디에 `true` `false` 만을 싣는 것도 좀 어색합니다.
@@ -0,0 +1,14 @@
+package com.codesquad.baseballgame.domain.game.dto;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.ToString;
+
+@Getter @ToString
+@RequiredArgsConstructor
+public class BallCountDto {
+
+    private int strike;
+    private int ball;
+    private int out;
+}

리뷰: 애노테이션은 한 줄에 하나를 쓰는 것을 원칙으로 해 주세요.

```suggestion
@Getter
@ToString
@@ -0,0 +1,14 @@
+package com.codesquad.baseballgame.domain.game.dto;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.ToString;
+
+@Getter @ToString
+@RequiredArgsConstructor
+public class BallCountDto {
+
+    private int strike;
+    private int ball;
+    private int out;
+}

리뷰: 생성자가 원하는 대로 생성되었나요? 
제가 알기로 이 애노테이션은 `final` 필드에 대해서만 생성자를 만드는데요.
아래 어떤 필드도 `final` 예약어를 갖지 않네요.
@@ -0,0 +1,10 @@
+package com.codesquad.baseballgame.domain.game.service;
+
+import org.springframework.stereotype.Service;
+
+@Service
+public class GameService {
+
+
+
+}

리뷰: 왜 이렇게 비어있는 클래스들이 많은거죠...
@@ -0,0 +1,23 @@
+package com.codesquad.baseballgame.domain.game.service;
+
+import com.codesquad.baseballgame.domain.team.dao.TeamDao;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Isolation;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@RequiredArgsConstructor
+public class MatchService {
+
+    private final TeamDao teamDao;
+
+    @Transactional(isolation = Isolation.SERIALIZABLE)
+    public Boolean matchStatus(int id) {
+        return countMatchUser(id) == 2;
+    }
+
+    public Integer countMatchUser(int id) {
+        return teamDao.countMatchUser(id);
+    }
+}

리뷰: `SERIALIZABLE` 정말 괜찮을까요? 정확히 이해하고 사용하신 건가요. 
대부분의 경우 `SERIALIZABLE` 까지 고립시켜야 할 트랜잭션은 없습니다.
애플리케이션 레이어에서 이런 수준의 고립이 필요한 경우, 애플리케이션 로직을 다시 검토해서 정말 이정도의 트랜잭션이 필요한지 살펴보고, 
정말 고립 수준 조정이 필요한 경우에도, 사실은 고립 수준 조정이 아니라 propagation 전략 수정으로 대응할 수 있지는 않은지 다시 한 번 생각해봐야 합니다. 

아마 갯수를 세는 쿼리가 있다보니 쿼리가 도는 동안 새로운 커밋이 일어나서 결과가 뒤바뀌는 일을 우려했을지도 모르겠습니다.
그런 경우라면 `READ COMMITTED` 로 충분하지 않나요. 음.....
@@ -0,0 +1,23 @@
+package com.codesquad.baseballgame.domain.game.service;
+
+import com.codesquad.baseballgame.domain.team.dao.TeamDao;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Isolation;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@RequiredArgsConstructor
+public class MatchService {
+
+    private final TeamDao teamDao;
+
+    @Transactional(isolation = Isolation.SERIALIZABLE)
+    public Boolean matchStatus(int id) {
+        return countMatchUser(id) == 2;
+    }
+
+    public Integer countMatchUser(int id) {
+        return teamDao.countMatchUser(id);
+    }
+}

리뷰: `@Transactional` 이 선언된 메소드에서 이렇게 메소드를 호출하면, 트랜잭션이 적용되지 않습니다. 
https://mommoo.tistory.com/92
@@ -0,0 +1,20 @@
+package com.codesquad.baseballgame.domain.hitter.mapper;
+
+import com.codesquad.baseballgame.domain.hitter.dto.HitterListDto;
+import org.springframework.jdbc.core.RowMapper;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class HitterListMapper implements RowMapper<HitterListDto> {
+
+    @Override
+    public HitterListDto mapRow(ResultSet rs, int rowNum) throws SQLException {
+        return HitterListDto.builder()
+                .teamName(rs.getString("name"))
+                .totalsBatterBox(rs.getInt("total_sum"))
+                .totalsHit(rs.getInt("total_hit"))
+                .totalsOut(rs.getInt("total_out"))
+                .build();
+    }
+}

리뷰: `RowMapper` 에서 바로 DTO로 매핑하네요.
모델 클래스가 하나 있는게 좋긴 한데... 모델 클래스의 필요성이 없었나보네요.
@@ -0,0 +1,15 @@
+package com.codesquad.baseballgame.domain.scoreboard.controller;
+
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class ScoreBoardController {
+
+    @GetMapping("games/{id}/scoreboard")
+    public void showScoreBoard(@PathVariable int id) {
+
+
+    }
+}

리뷰: 음......
@@ -0,0 +1,23 @@
+package com.codesquad.baseballgame.domain.scoreboard.dao;
+
+import com.codesquad.baseballgame.domain.scoreboard.dto.ScoreBoardInningDto;
+import com.codesquad.baseballgame.domain.team.mapper.TeamDtoMapper;
+import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
+import org.springframework.stereotype.Repository;
+
+import javax.sql.DataSource;
+
+@Repository
+public class ScoreBoardDao {
+
+    private final NamedParameterJdbcTemplate namedJdbcTemplate;
+    private final TeamDtoMapper teamDtoMapper = new TeamDtoMapper();
+
+    public ScoreBoardDao(DataSource dataSource) {
+        this.namedJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
+    }
+
+//    public ScoreBoardInningDto findInningById(int id) {
+//        String inningSql = ""
+//    }
+}

리뷰: 주석은 지워주세요.
@@ -0,0 +1,9 @@
+package com.codesquad.baseballgame.domain.game.controller;
+
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class GameController {
+
+
+}

리뷰: 네 꼭 지우도록 하겠습니다 절대 해선 안되는 짓을 해버렸군요. 꼭 명심하겠습니다
@@ -0,0 +1,24 @@
+package com.codesquad.baseballgame.domain.game.controller;
+
+import com.codesquad.baseballgame.domain.game.service.MatchService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequiredArgsConstructor
+public class MatchController {
+
+    private final MatchService matchService;
+
+    @PostMapping("games/{id}")
+    public ResponseEntity<Boolean> matching(@PathVariable int id) {
+        if (matchService.matchStatus(id)) {
+            return new ResponseEntity<>(true, HttpStatus.OK);
+        }
+        return new ResponseEntity<>(false, HttpStatus.OK);
+    }
+}

리뷰: 쓰면서 아뿔싸.. 하고 느낌은 왔었는데.. 마지막에 만드느라 기본 법칙을 잊고 막 만든게 원인이었습니다. 급하더라도 지킬건 지키면서 만들어야하는데 제가 너무 서둘렀네요. 최소한의 Json은 만들었어야했는데 다음에는 지키도록 하겠습니다.
@@ -0,0 +1,14 @@
+package com.codesquad.baseballgame.domain.game.dto;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.ToString;
+
+@Getter @ToString
+@RequiredArgsConstructor
+public class BallCountDto {
+
+    private int strike;
+    private int ball;
+    private int out;
+}

리뷰: 여러생각 하면서 코딩하다가 아무거나 다 넣는바람에 생긴 문제입니다. 기능 하나만들 때 그때 집중했어야 했는데 이것 저것하다가 이도저도 아니게 되었습니다. 제가 이걸 넣었다는것도 지금 알았네요. Dto 에선 넣을 생각 없었는데.... 이번 기회로 정신 똑바로 차려야겠습니다
@@ -0,0 +1,23 @@
+package com.codesquad.baseballgame.domain.game.service;
+
+import com.codesquad.baseballgame.domain.team.dao.TeamDao;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Isolation;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@RequiredArgsConstructor
+public class MatchService {
+
+    private final TeamDao teamDao;
+
+    @Transactional(isolation = Isolation.SERIALIZABLE)
+    public Boolean matchStatus(int id) {
+        return countMatchUser(id) == 2;
+    }
+
+    public Integer countMatchUser(int id) {
+        return teamDao.countMatchUser(id);
+    }
+}

리뷰: 트랜젝션에 대한 내용을 블로그에서 가볍게 보고 이 때 쓰는건가 하고 썼는데 좀 더 공부가 필요하는군요. Read Committed 에 대해서 조사하겠습니다.
@@ -0,0 +1,20 @@
+package com.codesquad.baseballgame.domain.hitter.mapper;
+
+import com.codesquad.baseballgame.domain.hitter.dto.HitterListDto;
+import org.springframework.jdbc.core.RowMapper;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class HitterListMapper implements RowMapper<HitterListDto> {
+
+    @Override
+    public HitterListDto mapRow(ResultSet rs, int rowNum) throws SQLException {
+        return HitterListDto.builder()
+                .teamName(rs.getString("name"))
+                .totalsBatterBox(rs.getInt("total_sum"))
+                .totalsHit(rs.getInt("total_hit"))
+                .totalsOut(rs.getInt("total_out"))
+                .build();
+    }
+}

리뷰: 모델 클래스의 필요성이 없었다기보단 설계가 잘못되었습니다. 만약 모델클래스에 맞게 했다면 지금보다 더욱 빠르게 코딩이 되었을텐데 아쉽습니다. 설계를 꼭 제대로 맞춰야겠네요
@@ -0,0 +1,23 @@
+package com.codesquad.baseballgame.domain.scoreboard.dao;
+
+import com.codesquad.baseballgame.domain.scoreboard.dto.ScoreBoardInningDto;
+import com.codesquad.baseballgame.domain.team.mapper.TeamDtoMapper;
+import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
+import org.springframework.stereotype.Repository;
+
+import javax.sql.DataSource;
+
+@Repository
+public class ScoreBoardDao {
+
+    private final NamedParameterJdbcTemplate namedJdbcTemplate;
+    private final TeamDtoMapper teamDtoMapper = new TeamDtoMapper();
+
+    public ScoreBoardDao(DataSource dataSource) {
+        this.namedJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
+    }
+
+//    public ScoreBoardInningDto findInningById(int id) {
+//        String inningSql = ""
+//    }
+}

리뷰: 넵
@@ -0,0 +1,15 @@
+package com.codesquad.baseballgame.domain.scoreboard.controller;
+
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class ScoreBoardController {
+
+    @GetMapping("games/{id}/scoreboard")
+    public void showScoreBoard(@PathVariable int id) {
+
+
+    }
+}

리뷰: 용서해주십시오...
@@ -0,0 +1,23 @@
+package com.codesquad.baseballgame.domain.game.service;
+
+import com.codesquad.baseballgame.domain.team.dao.TeamDao;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Isolation;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@RequiredArgsConstructor
+public class MatchService {
+
+    private final TeamDao teamDao;
+
+    @Transactional(isolation = Isolation.SERIALIZABLE)
+    public Boolean matchStatus(int id) {
+        return countMatchUser(id) == 2;
+    }
+
+    public Integer countMatchUser(int id) {
+        return teamDao.countMatchUser(id);
+    }
+}

리뷰: 트랜젝션에 대해서 확실하게 공부해야겠네요. 어설픈 사용은 지양해보도록 하겠습니다.
@@ -0,0 +1,23 @@
+package com.codesquad.baseballgame.domain.game.service;
+
+import com.codesquad.baseballgame.domain.team.dao.TeamDao;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Isolation;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@RequiredArgsConstructor
+public class MatchService {
+
+    private final TeamDao teamDao;
+
+    @Transactional(isolation = Isolation.SERIALIZABLE)
+    public Boolean matchStatus(int id) {
+        return countMatchUser(id) == 2;
+    }
+
+    public Integer countMatchUser(int id) {
+        return teamDao.countMatchUser(id);
+    }
+}

리뷰: 아 오늘 정확하게 수업을 하는군요 리뷰보자마자 수업을 하니 기쁩니다. Repeatable Read로만 커버가 가능하네요
@@ -0,0 +1,47 @@
+package bridge.view;
+
+import java.util.List;
+import java.util.StringJoiner;
+
+public class StringUtil {
+
+    public static String createBridge(List<String> userBridge, List<String> gameBridge) {
+        StringJoiner bridge = new StringJoiner("\n");
+
+        StringJoiner upBridge = new StringJoiner(" | ");
+        StringJoiner downBridge = new StringJoiner(" | ");
+
+        for (int i = 0; i < userBridge.size(); i++) {
+            addBridgeSegment(userBridge.get(i), gameBridge.get(i), upBridge, downBridge);
+        }
+        return bridge.add(String.format("[ %s ]", upBridge)).add(String.format("[ %s ]", downBridge)).toString();
+    }
+
+    private static void addBridgeSegment(String userSegment, String gameSegment, StringJoiner upBridge, StringJoiner downBridge) {
+        if (userSegment.equals(gameSegment)) {
+            addMatchingSegment(userSegment, upBridge, downBridge);
+        } else {
+            addNonMatchingSegment(userSegment, upBridge, downBridge);
+        }
+    }
+
+    private static void addMatchingSegment(String segment, StringJoiner upBridge, StringJoiner downBridge) {
+        if (segment.equals("U")) {
+            upBridge.add("O");
+            downBridge.add(" ");
+        } else if (segment.equals("D")) {
+            upBridge.add(" ");
+            downBridge.add("O");
+        }
+    }
+
+    private static void addNonMatchingSegment(String segment, StringJoiner upBridge, StringJoiner downBridge) {
+        if (segment.equals("U")) {
+            upBridge.add("X");
+            downBridge.add(" ");
+        } else if (segment.equals("D")) {
+            upBridge.add(" ");
+            downBridge.add("X");
+        }
+    }
+}

리뷰: 문자열들을 StringUtil 을 구현해서 OutputView의 책임을 덜어준 건 좋다고 생각합니다!
다만 파라미터가 다소 많고, 모든 결과에 대한 결과 비교를 StringUtil에서 다 하고 있는 부분은 조금 아쉬운 것 같습니다
서비스 계층에서 어느정도 계산한 결과를 가공해서 반환할 수 있도록 구현하면 어떨까 싶습니다
@@ -0,0 +1,47 @@
+package bridge.view;
+
+import java.util.List;
+import java.util.StringJoiner;
+
+public class StringUtil {
+
+    public static String createBridge(List<String> userBridge, List<String> gameBridge) {
+        StringJoiner bridge = new StringJoiner("\n");
+
+        StringJoiner upBridge = new StringJoiner(" | ");
+        StringJoiner downBridge = new StringJoiner(" | ");
+
+        for (int i = 0; i < userBridge.size(); i++) {
+            addBridgeSegment(userBridge.get(i), gameBridge.get(i), upBridge, downBridge);
+        }
+        return bridge.add(String.format("[ %s ]", upBridge)).add(String.format("[ %s ]", downBridge)).toString();
+    }
+
+    private static void addBridgeSegment(String userSegment, String gameSegment, StringJoiner upBridge, StringJoiner downBridge) {
+        if (userSegment.equals(gameSegment)) {
+            addMatchingSegment(userSegment, upBridge, downBridge);
+        } else {
+            addNonMatchingSegment(userSegment, upBridge, downBridge);
+        }
+    }
+
+    private static void addMatchingSegment(String segment, StringJoiner upBridge, StringJoiner downBridge) {
+        if (segment.equals("U")) {
+            upBridge.add("O");
+            downBridge.add(" ");
+        } else if (segment.equals("D")) {
+            upBridge.add(" ");
+            downBridge.add("O");
+        }
+    }
+
+    private static void addNonMatchingSegment(String segment, StringJoiner upBridge, StringJoiner downBridge) {
+        if (segment.equals("U")) {
+            upBridge.add("X");
+            downBridge.add(" ");
+        } else if (segment.equals("D")) {
+            upBridge.add(" ");
+            downBridge.add("X");
+        }
+    }
+}

리뷰: 맞는말인거같습니다.. 인자가 4개나 되다보니 정말 맘에 안드는 메서드였는데요 여길 리팩토링 하려고하니 map을 사용하거나 해야했는데 계속 리팩토링 도전해봤는데 기존 구조를 깨고 만들어야되는 부분이 존재해서 기존구조 유지하고 가느라 아쉬운 부분이 나온것 같습니다ㅜㅜ
@@ -0,0 +1,28 @@
+package christmas.util;
+
+public class DateValidator {
+    private static final String INVALID_DATE_MESSAGE = "유효하지 않은 날짜입니다. 다시 입력해 주세요.";
+
+    public static void validateInput(String input) {
+        validateNumeric(input);
+        int date = Integer.parseInt(input);
+        validateDate(date);
+    }
+
+    private static void validateNumeric(String input) {
+        if (input == null) {
+            throw new ValidationException(INVALID_DATE_MESSAGE);
+        }
+        try {
+            Integer.parseInt(input);
+        } catch (NumberFormatException e) {
+            throw new ValidationException(INVALID_DATE_MESSAGE);
+        }
+    }
+
+    private static void validateDate(int date) {
+        if (date < 1 || date > 31) {
+            throw new ValidationException(INVALID_DATE_MESSAGE);
+        }
+    }
+}

리뷰: 31일로 끝나지 않는 달도 있기때문에 12월에 대한 의존성을 주입받아서 해당하는 월(月)에 맞춰 최대 일수를 변경 하던지 해당 이벤트에 대한 상수를 사용하거나 그 외 다양한 방식으로 조건을 조절하는 것도 좋을거라 생각합니다!