ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,47 @@
+import React from "react";
+import styled from "styled-components";
+import { Link } from "react-router-dom";
+
+const Navigation = () => {
+  return (
+    <NavigationBlock>
+      <Link to="/">신청곡</Link>
+      <Link to="/ranking">신청곡 순위</Link>
+      <Link to="/contents">신청곡 유튜브 영상</Link>
+      <Link to="/musicsheets">리얼피아노 악보집</Link>
+    </NavigationBlock>
+  );
+};
+
+const NavigationBlock = styled.div`
+  height: 100%;
+  display: flex;
+  align-items: center;
+
+  & > * {
+    /* padding: 25px 5px 10px 5px; */
+    box-sizing: border-box;
+    display: flex;
+    align-items: center;
+    margin: 0px 30px;
+    padding-top: 10px;
+    font-size: 1.2rem;
+    font-weight: 600;
+    text-decoration: none;
+    align-self: center;
+    color: inherit;
+    opacity: 0.5;
+    height: 100%;
+    /* border-bottom: 1px solid rgba(0, 0, 0, 0); */
+    border-bottom: 3px solid rgba(250, 162, 193, 0);
+    transition: 0.3s;
+
+    :hover {
+      opacity: 1;
+      /* color: #faa2c1; */
+      border-bottom: 3px solid rgba(250, 162, 193, 1);
+    }
+  }
+`;
+
+export default Navigation;

리뷰: 처음에는 말씀 하신 것처럼 px과 rem을 구분해서 사용 하다가 상대 값인 rem으로 단위를 통일하고 적용 했습니다. 혹시 이렇게 할 경우 주의해야 될 고려사항 같은게 있을까요?
@@ -0,0 +1,29 @@
+import React from "react";
+import styled from "styled-components";
+import LetterContainer from "../../containers/Letter/LetterContainer";
+
+const LetterList = ({ letters }) => {
+  return (
+    <LetterListBlock>
+      {letters.map(letter => (
+        <LetterContainer
+          key={letter.id}
+          id={letter.id}
+          user={letter.user}
+          song={letter.song}
+          songStory={letter.songStory}
+          createdDateTime={letter.createdDateTime}
+        />
+      ))}
+    </LetterListBlock>
+  );
+};
+
+const LetterListBlock = styled.ul`
+  width: 100%;
+  display: flex;
+  flex-wrap: wrap;
+  margin-top: 4.5rem;
+`;
+
+export default LetterList;

리뷰: Container를 언제 사용해야 할 지 명확한 기준이 없었는데, 리뷰 해주신 내용을 보고 고민 되던 것들이 많이 덜어지는 느낌이에요.
다음 수정 때 반영 하겠습니다 🙂
@@ -0,0 +1,30 @@
+import React, { useEffect, useState } from "react";
+import ModalTemplate from "../Template/Modal/ModalTemplate";
+import LetterDetailsSong from "./LetterDetailsSong";
+import LetterDetailsSongStory from "./LetterDetailsSongStory";
+import LetterModalTemplate from "../Template/LetterModal/LetterModalTemplate";
+import LetterDetailsUser from "./LetterDetailsUser";
+import LetterModalDiv from "../LetterModal/LetterModalContents/LetterModalDiv";
+import LetterModalHiddenButtonContainer from "../../containers/LetterModal/LetterModalHiddenButtonContainer";
+import LetterModalButtonContainer from "../../containers/LetterModal/LetterModalButtonContainer";
+import { useSelector } from "react-redux";
+
+const LetterDetails = ({ letter }) => {
+  const { song, songStory, createdDateTime, user } = letter;
+
+  return (
+    <ModalTemplate>
+      <LetterModalTemplate>
+        <LetterModalHiddenButtonContainer />
+        <LetterModalDiv>
+          <LetterDetailsSong song={song} />
+          <LetterDetailsSongStory songStory={songStory} />
+          <LetterDetailsUser user={user} createdDateTime={createdDateTime} />
+          <LetterModalButtonContainer />
+        </LetterModalDiv>
+      </LetterModalTemplate>
+    </ModalTemplate>
+  );
+};
+
+export default LetterDetails;

리뷰: 가능하다면 객체 자체를 넘기는 것이 여러 측면에서 더 좋을거 같다는 생각이었는데, 아닌 경우도 있겠다는 생각이 드네요!
수정할 때 반영 하겠습니다 👍
@@ -0,0 +1,45 @@
+import React, { useEffect } from "react";
+import { MdMoreHoriz } from "react-icons/md";
+import styled, { css } from "styled-components";
+import LetterDetailsHiddenMenuButton from "./LetterDetailsHiddenMenuButton";
+
+const LetterDetailsHiddenMenu = ({
+  isMouseEnter,
+  isMenuOpen,
+  onToggle,
+  changeToEdit
+}) => {
+  return (
+    <ButtonBlock isMouseEnter={isMouseEnter} onClick={onToggle}>
+      <HiddenMenu />
+      {isMenuOpen && (
+        <LetterDetailsHiddenMenuButton changeToEdit={changeToEdit} />
+      )}
+    </ButtonBlock>
+  );
+};
+
+const ButtonBlock = styled.div`
+  ${props =>
+    props.isMouseEnter
+      ? css`
+          visibility: visible;
+          opacity: 1;
+        `
+      : css`
+          visibility: hidden;
+          opacity: 0;
+        `}
+  transition: 0.7s;
+`;
+
+const HiddenMenu = styled(MdMoreHoriz)`
+  position: absolute;
+  bottom: 93%;
+  right: 3%;
+  cursor: pointer;
+  font-size: 2.1rem;
+  color: gray;
+`;
+
+export default LetterDetailsHiddenMenu;

리뷰: `display: none`은 DOM에 영향을 미치지 않는 반면 `visibility: hidden` 은 DOM에 영향을 주는 것으로 알고 있었어요ㅎㅎ `transition` 은 `display: none`에서 작동하지 않아서 `visibility` 속성을 사용하게 됐는데, 혹시 컨벤션이 따로 있다거나 더 효율적인 방법이 있을까요?
@@ -0,0 +1,28 @@
+import React from "react";
+import LetterModalSong from "../LetterModal/LetterModalSong/LetterModalSong";
+import SongAbout from "../LetterModal/LetterModalSong/SongAbout";
+import SongArticle from "../LetterModal/LetterModalSong/SongArticle";
+import SongArticleItem from "../LetterModal/LetterModalSong/SongArticleItem";
+import SongArticleName from "../LetterModal/LetterModalSong/SongArticleName";
+import SongImage from "../LetterModal/LetterModalSong/SongImage";
+
+const LetterDetailsSong = ({ song }) => {
+  const { title, artist, imageUrl } = song;
+  return (
+    <LetterModalSong>
+      <SongImage imageUrl={imageUrl} />
+      <SongAbout>
+        <SongArticle>
+          <SongArticleName articleName={"TITLE"} />
+          <SongArticleItem articleName={"TITLE"} item={title} />
+        </SongArticle>
+        <SongArticle>
+          <SongArticleName articleName={"ARTIST"} />
+          <SongArticleItem articleName={"ARTIST"} item={artist} />
+        </SongArticle>
+      </SongAbout>
+    </LetterModalSong>
+  );
+};
+
+export default LetterDetailsSong;

리뷰: 컴포넌트를 완전히 다 분리 한 뒤에 한번 더 정리를 했어야 하는데 이런 중복은 묶어서 줄이는게 좋을거 같아요. 반영 해보겠습니다. 알려주셔서 감사합니다!
@@ -0,0 +1,108 @@
+import React from "react";
+import styled from "styled-components";
+import { BiSearch } from "react-icons/bi";
+import { useDispatch } from "react-redux";
+import { searchSong } from "../../../modules/song";
+
+const SearchForm = ({ onCloseModal }) => {
+  const dispatch = useDispatch();
+
+  const onSearchSong = event => {
+    event.preventDefault();
+    dispatch(searchSong());
+  };
+
+  return (
+    <Form>
+      <SearchInputWrap>
+        <SearchLabel>아티스트</SearchLabel>
+        <SearchInput type="text" name="artist" />
+      </SearchInputWrap>
+      <SearchInputWrap>
+        <SearchLabel>제목</SearchLabel>
+        <SearchInput type="text" name="title" />
+      </SearchInputWrap>
+      <SearchButton type="submit" onClick={onSearchSong}>
+        <SearchIcon />
+      </SearchButton>
+    </Form>
+  );
+};
+
+const Form = styled.form`
+  box-sizing: border-box;
+  width: 95%;
+  display: flex;
+  justify-content: center;
+  margin: 0rem auto 3.5rem auto;
+`;
+
+const SearchLabel = styled.span`
+  display: flex;
+  align-items: center;
+  position: absolute;
+  top: 0rem;
+  left: 0rem;
+  height: 2.37rem;
+  font-size: 1rem;
+  background-color: #727272;
+  padding: 0rem 0.7rem;
+  border-radius: 0.3rem 0rem 0rem 0.3rem;
+  color: white;
+  font-weight: 600;
+  box-shadow: 1px 0px 3px 1px rgba(0, 0, 0, 0.1);
+`;
+
+const SearchInput = styled.input`
+  border: none;
+  outline: none;
+  color: #868e96;
+  width: 100%;
+  margin: 0rem 0.5rem 0rem 5.5rem;
+  font-size: 1.2rem;
+  background-color: #fbfbfd;
+`;
+
+const SearchInputWrap = styled.div`
+  display: flex;
+  align-items: center;
+  position: relative;
+  border: 1px solid #e9ecef;
+  border-radius: 1rem 0rem 0rem 1rem;
+  padding: 0.1rem 0rem;
+  background: #fbfbfd;
+  height: 2.1rem;
+  flex: 11;
+
+  &:nth-child(2) {
+    & > ${SearchLabel} {
+      border-radius: 0.2rem;
+      margin-left: -0.25rem;
+    }
+
+    & > ${SearchInput} {
+      margin-left: 3.8rem;
+    }
+  }
+`;
+
+const SearchButton = styled.button`
+  display: flex;
+  position: relative;
+  justify-content: center;
+  align-items: center;
+  border: none;
+  outline: none;
+  cursor: pointer;
+  background-color: #e1999c;
+  border-radius: 0rem 0.5rem 0.5rem 0rem;
+  flex: 1;
+  box-shadow: 1px 0px 3px 1px rgba(0, 0, 0, 0.1);
+`;
+
+const SearchIcon = styled(BiSearch)`
+  color: #fff;
+  font-size: 1.4rem;
+`;
+
+export default SearchForm;

리뷰: 지금 정도의 폼 사용에서는 직접 구현도 무리가 없었지만, 폼이 복잡해지면 확실히 관리하기도 쉽지 않을 듯 하더라구요. 
기억 해두었다가 필요할 때 적용 해보겠습니다 :) 알려주셔서 감사합니다.
@@ -0,0 +1,33 @@
+import handleMovie from '../services/handleMovie';
+import Button from './Button';
+
+function MainContent(initialPage) {
+    let page = initialPage;
+    const mainElement = document.createElement('main');
+    mainElement.id = 'main';
+
+    mainElement.innerHTML = `
+        <section class="item-view">
+            <h2>지금 인기 있는 영화</h2>
+            <ul class="item-list"></ul>
+        </section>
+    `;
+
+    const sectionElement = mainElement.querySelector('.item-view');
+    sectionElement.appendChild(
+        Button({
+            classNames: ['btn', 'primary', 'full-width'],
+            type: 'button',
+            id: 'more-movies',
+            name: '더 보기',
+            onClick: () => {
+                page += 1;
+                handleMovie(page);
+            }
+        })
+    );
+
+    return mainElement;
+}
+
+export default MainContent;

리뷰: ```suggestion
    const mainElement = html(`
      <main id="main">
        <section class="item-view">
            <h2>지금 인기 있는 영화</h2>
            <ul class="item-list"></ul>
        </section>
      <main>
    `);
```
이렇게 표현할 수 있도록 함수를 하나 만들어주시면 어떨까요!?
@@ -0,0 +1,33 @@
+import handleMovie from '../services/handleMovie';
+import Button from './Button';
+
+function MainContent(initialPage) {
+    let page = initialPage;
+    const mainElement = document.createElement('main');
+    mainElement.id = 'main';
+
+    mainElement.innerHTML = `
+        <section class="item-view">
+            <h2>지금 인기 있는 영화</h2>
+            <ul class="item-list"></ul>
+        </section>
+    `;
+
+    const sectionElement = mainElement.querySelector('.item-view');
+    sectionElement.appendChild(
+        Button({
+            classNames: ['btn', 'primary', 'full-width'],
+            type: 'button',
+            id: 'more-movies',
+            name: '더 보기',
+            onClick: () => {
+                page += 1;
+                handleMovie(page);
+            }
+        })
+    );
+
+    return mainElement;
+}
+
+export default MainContent;

리뷰: 위에서는 html template으로 표현되고 여기서는 이렇게 표현되고 있어서 코드를 읽는 사람 입장에서는 조금 헷갈리지 않을까 싶어요!
@@ -0,0 +1,47 @@
+import { BASE_URL } from '../constants/api';
+import { APIError } from '../apis/error';
+
+interface ApiClientType {
+    method: 'GET' | 'POST' | 'PUT' | 'DELETE';
+    endpoint: string;
+    params?: Record<string, string>;
+    headers?: Record<string, string>;
+    body?: Record<string, any>;
+}
+
+class ApiClient {
+    static async request({ method, endpoint, params, headers = {}, body }: ApiClientType) {
+        const url = params
+            ? this.createUrlSearchParams({ baseUrl: `${BASE_URL}/${endpoint}`, params })
+            : `${BASE_URL}/${endpoint}`;
+
+        const options: RequestInit = {
+            method,
+            headers: {
+                'Content-Type': 'application/json',
+                ...headers
+            },
+            ...(body && { body: JSON.stringify(body) })
+        };
+
+        try {
+            const response = await fetch(url, options);
+            const data = await response.json();
+
+            if (!response.ok) {
+                throw new APIError(data.status_code, data.status_message);
+            }
+            return data;
+        } catch (error: any) {
+            throw new APIError(400, error.message || 'An unknown error occurred');
+        }
+    }
+
+    static createUrlSearchParams({ baseUrl, params }: { baseUrl: string; params: Record<string, string> }) {
+        const url = new URL(baseUrl);
+        url.search = new URLSearchParams(params).toString();
+        return url.toString();
+    }
+}
+
+export default ApiClient;

리뷰: 아하.. 이 파일 때문에 tsconfig가 생긴거였군요
@@ -0,0 +1,47 @@
+import { BASE_URL } from '../constants/api';
+import { APIError } from '../apis/error';
+
+interface ApiClientType {
+    method: 'GET' | 'POST' | 'PUT' | 'DELETE';
+    endpoint: string;
+    params?: Record<string, string>;
+    headers?: Record<string, string>;
+    body?: Record<string, any>;
+}
+
+class ApiClient {
+    static async request({ method, endpoint, params, headers = {}, body }: ApiClientType) {
+        const url = params
+            ? this.createUrlSearchParams({ baseUrl: `${BASE_URL}/${endpoint}`, params })
+            : `${BASE_URL}/${endpoint}`;
+
+        const options: RequestInit = {
+            method,
+            headers: {
+                'Content-Type': 'application/json',
+                ...headers
+            },
+            ...(body && { body: JSON.stringify(body) })
+        };
+
+        try {
+            const response = await fetch(url, options);
+            const data = await response.json();
+
+            if (!response.ok) {
+                throw new APIError(data.status_code, data.status_message);
+            }
+            return data;
+        } catch (error: any) {
+            throw new APIError(400, error.message || 'An unknown error occurred');
+        }
+    }
+
+    static createUrlSearchParams({ baseUrl, params }: { baseUrl: string; params: Record<string, string> }) {
+        const url = new URL(baseUrl);
+        url.search = new URLSearchParams(params).toString();
+        return url.toString();
+    }
+}
+
+export default ApiClient;

리뷰: try 구간에서 오류 발생 -> catch로 진입 -> 무조건 status는 400으로 고정하여 다시 error throw

이렇게 됩니다.

유의미한 에러 플로우일까요~?
@@ -0,0 +1,47 @@
+import { BASE_URL } from '../constants/api';
+import { APIError } from '../apis/error';
+
+interface ApiClientType {
+    method: 'GET' | 'POST' | 'PUT' | 'DELETE';
+    endpoint: string;
+    params?: Record<string, string>;
+    headers?: Record<string, string>;
+    body?: Record<string, any>;
+}
+
+class ApiClient {
+    static async request({ method, endpoint, params, headers = {}, body }: ApiClientType) {
+        const url = params
+            ? this.createUrlSearchParams({ baseUrl: `${BASE_URL}/${endpoint}`, params })
+            : `${BASE_URL}/${endpoint}`;
+
+        const options: RequestInit = {
+            method,
+            headers: {
+                'Content-Type': 'application/json',
+                ...headers
+            },
+            ...(body && { body: JSON.stringify(body) })
+        };
+
+        try {
+            const response = await fetch(url, options);
+            const data = await response.json();
+
+            if (!response.ok) {
+                throw new APIError(data.status_code, data.status_message);
+            }
+            return data;
+        } catch (error: any) {
+            throw new APIError(400, error.message || 'An unknown error occurred');
+        }
+    }
+
+    static createUrlSearchParams({ baseUrl, params }: { baseUrl: string; params: Record<string, string> }) {
+        const url = new URL(baseUrl);
+        url.search = new URLSearchParams(params).toString();
+        return url.toString();
+    }
+}
+
+export default ApiClient;

리뷰: 그런데 이 파일이 왜 domain 에 해당하는걸까요~?
domain이라고 하기엔 말 그대로 사이드 이펙트(client)를 표현한거라서... 전혀 연관이 없어보여요!
@@ -0,0 +1,24 @@
+import getMovieList from '../apis/getMovieList';
+import ConfirmModal from '../components/modal/ConfirmModal';
+import { Modal } from '../components/modal/container/Modal';
+import { NOT_MORE_MOVIES_MESSAGE } from '../constants/message';
+import renderMovies from '../view/renderMovies';
+import renderSkeletonMovies from '../view/renderSkeletonMovies';
+
+export default async function handleMovie(page) {
+    try {
+        renderSkeletonMovies({ loading: true });
+        const movieList = await getMovieList(page);
+        renderSkeletonMovies({ loading: false });
+
+        if (movieList.results.length === 0) {
+            document.getElementById('more-movies').remove();
+            new Modal(document.querySelector('body'), ConfirmModal(NOT_MORE_MOVIES_MESSAGE));
+        }
+
+        renderMovies({ movieList: movieList.results, resetHTML: false });
+    } catch (error) {
+        renderSkeletonMovies({ loading: false });
+        new Modal(document.querySelector('body'), ConfirmModal(error.message));
+    }
+}

리뷰: api에서 발생한 에러 중에 message만 사용하고 있는데, CustomError를 만들어서 쓰는 의미가 있는건가 싶어요!
@@ -0,0 +1,47 @@
+import { BASE_URL } from '../constants/api';
+import { APIError } from '../apis/error';
+
+interface ApiClientType {
+    method: 'GET' | 'POST' | 'PUT' | 'DELETE';
+    endpoint: string;
+    params?: Record<string, string>;
+    headers?: Record<string, string>;
+    body?: Record<string, any>;
+}
+
+class ApiClient {
+    static async request({ method, endpoint, params, headers = {}, body }: ApiClientType) {
+        const url = params
+            ? this.createUrlSearchParams({ baseUrl: `${BASE_URL}/${endpoint}`, params })
+            : `${BASE_URL}/${endpoint}`;
+
+        const options: RequestInit = {
+            method,
+            headers: {
+                'Content-Type': 'application/json',
+                ...headers
+            },
+            ...(body && { body: JSON.stringify(body) })
+        };
+
+        try {
+            const response = await fetch(url, options);
+            const data = await response.json();
+
+            if (!response.ok) {
+                throw new APIError(data.status_code, data.status_message);
+            }
+            return data;
+        } catch (error: any) {
+            throw new APIError(400, error.message || 'An unknown error occurred');
+        }
+    }
+
+    static createUrlSearchParams({ baseUrl, params }: { baseUrl: string; params: Record<string, string> }) {
+        const url = new URL(baseUrl);
+        url.search = new URLSearchParams(params).toString();
+        return url.toString();
+    }
+}
+
+export default ApiClient;

리뷰: domain이 apis를 참조하는건 어색해보여요!
@@ -1,2 +1 @@
 export const BASE_URL = 'https://api.themoviedb.org';
-export const IMAGE_BASE_URL = 'https://image.tmdb.org/t/p/w500';

리뷰: 이런 정보는 아예 API를 요청하는 쪽에서 가지고 있으면 어떨까요?
@@ -0,0 +1 @@
+export const NOT_MORE_MOVIES_MESSAGE = '더이상 영화가 존재하지 않습니다.';

리뷰: 이런 에러 메세지도 메세지를 사용하는 쪽에서 가지고 있으면 어떨까요?
다른 구간에서는 아예 사용될 것 같지 않아서요!
@@ -0,0 +1,75 @@
+package bridge.controller;
+
+import bridge.BridgeRandomNumberGenerator;
+import bridge.constants.GameValue;
+import bridge.domain.Bridge;
+import bridge.domain.BridgeGame;
+import bridge.domain.BridgeMaker;
+import bridge.view.handler.InputHandler;
+import bridge.view.OutputView;
+
+public class BridgeController {
+    private final InputHandler inputHandler;
+    private final OutputView outputView;
+    private final BridgeGame bridgeGame;
+    private boolean status = true;
+
+    public BridgeController(InputHandler inputHandler, OutputView outputView, BridgeGame bridgeGame) {
+        this.inputHandler = inputHandler;
+        this.outputView = outputView;
+        this.bridgeGame = bridgeGame;
+    }
+
+    public void start() {
+        outputView.printStartMessage();
+        Bridge bridge = createBridge();
+        run(bridge);
+    }
+
+    private Bridge createBridge() {
+        outputView.printBridgeSizeInputMessage();
+        BridgeMaker bridgeMaker = new BridgeMaker(new BridgeRandomNumberGenerator());
+        return inputHandler.createValidatedBridge(bridgeMaker);
+    }
+
+    private void run(Bridge bridge) {
+        while (status) {
+            move(bridge);
+            if (isFinished()) {
+                finishGame();
+            }
+            if (!isFinished()) {
+                askRetry();
+            }
+        }
+    }
+
+    private void move(Bridge bridge) {
+        for (String square : bridge.getBridge()) {
+            outputView.printMoveInputMessage();
+            String moveCommand = inputHandler.readMoving();
+            bridgeGame.move(square, moveCommand);
+            outputView.printMap(bridgeGame.getMoveResult());
+        }
+    }
+
+    private boolean isFinished() {
+        return bridgeGame.isSuccessful();
+    }
+
+    private void finishGame() {
+        outputView.printResult(bridgeGame);
+        status = false;
+    }
+
+    private void askRetry() {
+        outputView.printRetryMessage();
+        String retryCommand = inputHandler.readGameCommand();
+        if (retryCommand.equals(GameValue.RETRY_COMMAND)) {
+            bridgeGame.retry();
+        }
+        if (retryCommand.equals(GameValue.QUIT_COMMAND)) {
+            outputView.printResult(bridgeGame);
+        }
+    }
+}

리뷰: 다리를 돌면서 이동을 받고 있는 형태라서, 
이동이 실패해도 바로 재시도 여부를 묻지 않고, 그냥 진행이 되는 것 같습니다..!
@@ -0,0 +1,75 @@
+package bridge.controller;
+
+import bridge.BridgeRandomNumberGenerator;
+import bridge.constants.GameValue;
+import bridge.domain.Bridge;
+import bridge.domain.BridgeGame;
+import bridge.domain.BridgeMaker;
+import bridge.view.handler.InputHandler;
+import bridge.view.OutputView;
+
+public class BridgeController {
+    private final InputHandler inputHandler;
+    private final OutputView outputView;
+    private final BridgeGame bridgeGame;
+    private boolean status = true;
+
+    public BridgeController(InputHandler inputHandler, OutputView outputView, BridgeGame bridgeGame) {
+        this.inputHandler = inputHandler;
+        this.outputView = outputView;
+        this.bridgeGame = bridgeGame;
+    }
+
+    public void start() {
+        outputView.printStartMessage();
+        Bridge bridge = createBridge();
+        run(bridge);
+    }
+
+    private Bridge createBridge() {
+        outputView.printBridgeSizeInputMessage();
+        BridgeMaker bridgeMaker = new BridgeMaker(new BridgeRandomNumberGenerator());
+        return inputHandler.createValidatedBridge(bridgeMaker);
+    }
+
+    private void run(Bridge bridge) {
+        while (status) {
+            move(bridge);
+            if (isFinished()) {
+                finishGame();
+            }
+            if (!isFinished()) {
+                askRetry();
+            }
+        }
+    }
+
+    private void move(Bridge bridge) {
+        for (String square : bridge.getBridge()) {
+            outputView.printMoveInputMessage();
+            String moveCommand = inputHandler.readMoving();
+            bridgeGame.move(square, moveCommand);
+            outputView.printMap(bridgeGame.getMoveResult());
+        }
+    }
+
+    private boolean isFinished() {
+        return bridgeGame.isSuccessful();
+    }
+
+    private void finishGame() {
+        outputView.printResult(bridgeGame);
+        status = false;
+    }
+
+    private void askRetry() {
+        outputView.printRetryMessage();
+        String retryCommand = inputHandler.readGameCommand();
+        if (retryCommand.equals(GameValue.RETRY_COMMAND)) {
+            bridgeGame.retry();
+        }
+        if (retryCommand.equals(GameValue.QUIT_COMMAND)) {
+            outputView.printResult(bridgeGame);
+        }
+    }
+}

리뷰: 실패하고 재시도는 종료를 입력했을 때, 게임이 종료되지 않고 계속 다음 이동 입력을 받는 것 같습니다..!
실패하고 여기로 들어와서 status를 false로 바꿔주는 부분이 없어서인 것 같아요!