ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,47 @@
+import { FollowUserType, followList } from '@/types/types';
+import axios from 'axios';
+import { SetterOrUpdater } from 'recoil';
+
+const PER_PAGE = 100;
+export const getFollowingList = async (setFollowings: SetterOrUpdater<followList[]>, tokenValue: string) => {
+  try {
+    const followingData = await axios.get(`${process.env.NEXT_PUBLIC_BASE_URL}/user/following?per_page=${PER_PAGE}`, {
+      headers: {
+        Accept: 'application/vnd.github+json',
+        Authorization: `Bearer ${tokenValue}`,
+      },
+    });
+
+    const data = followingData.data;
+
+    const followingList = data.map((user: FollowUserType) => {
+      const { id, login, avatar_url } = user;
+      return { id, login, avatar_url };
+    });
+
+    setFollowings(followingList);
+  } catch {
+    console.error('error');
+  }
+};
+
+export const getFollowerList = async (setFollowers: SetterOrUpdater<followList[]>, tokenValue: string) => {
+  try {
+    const followerData = await axios.get(`${process.env.NEXT_PUBLIC_BASE_URL}/user/followers?per_page=${PER_PAGE}`, {
+      headers: {
+        Accept: 'application/vnd.github+json',
+        Authorization: `Bearer ${tokenValue}`,
+      },
+    });
+    const data = followerData.data;
+
+    const followerList = data.map((user: FollowUserType) => {
+      const { id, login, avatar_url } = user;
+      return { id, login, avatar_url };
+    });
+
+    setFollowers(followerList);
+  } catch {
+    console.error('error');
+  }
+};

리뷰: 여기서 서버에서 내려주는 값들 중 클라이언트에서 필요한 타입들만 쏙쏙 뽑아내주는 과정이 이뤄진다고 생각하는데요!

클라이언트에선 camelCase를 사용하고 있으니, 넘겨주는 김에 camelCase로 변경해주는 작업을 여기서 진행해도 좋을 것 같네요! 직접 바꿔주어도 괜찮고, 요런 역할을 해주는 라이브러리도 있으니 ([예시](https://www.npmjs.com/package/camelcase-keys)) 참고해보면 좋을 것 같아요.
@@ -0,0 +1,35 @@
+'use client';
+
+import FollowList from '@/components/checkfollow/FollowList';
+import { useRecoilValue, useSetRecoilState } from 'recoil';
+import FollowOrNotBtn from '../../components/checkfollow/FollowOrNotBtn';
+import Profile from '../../components/checkfollow/Profile';
+import { putFollowList } from '@/apis/putFollowList';
+import { followState, inputToken, loginClick } from '@/recoil/atoms';
+
+export default function CheckFollow() {
+  const loginClickState = useRecoilValue(loginClick);
+  const inputTokenValue = useRecoilValue(inputToken);
+  const setFollowState = useSetRecoilState(followState);
+
+  async function handleFllow(username: string) {
+    const success = await putFollowList(username, inputTokenValue);
+    if (success) {
+      setFollowState('follow');
+      window.location.reload();
+    }
+  }
+
+  return (
+    <article className="flex flex-col px-[25px] py-[37px]">
+      <Profile />
+      <FollowOrNotBtn />
+      <FollowList />
+      <button
+        onClick={() => handleFllow(loginClickState)}
+        className="mt-[34px] h-[45px] rounded-[5px] border-2 border-solid border-grey02 bg-sub01 text-title01 text-white hover:bg-grey02">
+        맞팔하기
+      </button>
+    </article>
+  );
+}

리뷰: 요거는,,, 사실 put 이후에 화면에 바로 보여지게 하도록 하고 싶어서,,,, 새로고침을 시도했었습니다ㅠㅠ!
@@ -0,0 +1,14 @@
+import { useRouter } from 'next/navigation';
+
+export default function ConfirmFollowButton() {
+  const router = useRouter();
+
+  const handleOnclick = () => {
+    router.push('/checkfollow');
+  };
+  return (
+    <div className="commonFlex followBackButton  hover:bg-grey02" onClick={handleOnclick}>
+      나의 맞팔 확인하러 가기
+    </div>
+  );
+}

리뷰: 헉 감사합니다!!!!!
@@ -0,0 +1,80 @@
+## 로또
+
+### 규칙
+1. 로또 번호의 숫자 범위는 1~45까지 이다.
+2. 로또 번호는 총 6개의 숫자로 이루어져있다.
+3. 복권은 한장당 1,000원이다.
+4. 사용자는 구입한 복권 장 수 만큼 당첨을 계산한다.
+5. 사용자는 당첨 번호와 보너스 번호를 입력한다.
+6. 구매한 로또 번호와 당첨 번호를 비교하여 당첨 기준에 따라 금액을 받는다. 당첨 기준과 금액은 아래와 같다.
+    - 1등: 6개 번호 일치 / 2,000,000,000원
+    - 2등: 5개 번호 + 보너스 번호 일치 / 30,000,000원
+    - 3등: 5개 번호 일치 / 1,500,000원
+    - 4등: 4개 번호 일치 / 50,000원
+    - 5등: 3개 번호 일치 / 5,000원
+7. 사용자가 잘못된 값 입력할 경우 에러메세지 출력 후,부분부터 입력을 다시 받는다.
+
+### 프로그래밍 요구 사항
+1. 함수(또는 메서드)가 한 가지 일만 하도록 최대한 작게 만들어라.
+2. 함수(또는 메서드)의 길이가 15라인을 넘어가지 않도록 구현한다.
+3. indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다.
+4. 3항 연산자를 쓰지 않는다.
+5. else 예약어를 쓰지 않는다. switch/case도 허용하지 않는다.
+6. Java Enum을 적용한다.
+7. 도메인 로직에 단위 테스트를 구현해야 한다. 단, UI(System.out, System.in, Scanner) 로직은 제외한다도메인 로직에 단위 테스트를 구현해야 한다. 단, UI(System.out, System.in, Scanner) 로직은 제외한다.
+8. 핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 분리해 구현한다.
+
+### 주요 객체
+- 로또
+- 사용자
+- 통계
+
+### 주요 객체들의 속성과 역할
+- 로또
+  - 1부터 45까지 서로 다른 6개의 숫자를 가지고 있다.
+  - 6개 숫자를 뽑아서 준다.
+
+- 당첨 로또번호
+  - 당첨 로또번호를 발행한다.
+  - 발행된 로또번호 리스트를 가지고 있다.
+  - 당첨 번호와 일치하는 갯수를 확인한다.
+
+- 통계
+  - 수익률을 낸다.
+### 기능 정의
+1. 입력
+- [x] 로또 구입 금액을 입력 받는다.
+- [x] 당첨 번호를 입력 받는다. 번호는 쉼표(,)를 기준으로 구분한다.
+- [x] 보너스 번호를 입력 받는다.
+
+2. 출력
+- [x] 발행한 로또 수량 및 번호를 출력한다. 로또 번호는 오름차순으로 정렬하여 보여준다.
+- [x] 당첨 내역을 출력한다.
+- [x] 번호 범위가 넘어갈 시에 에러문구 출력한다.
+
+3. 로또 번호
+- [x] 6개 숫자를 가진다.
+- [x] 6개인지 확인한다.
+- [x] 숫자 포함여부를 확인해 포함 개수를 반환한다.
+
+4. 추측 로또 
+- [x] 로또 번호를 생성한다.
+
+5. 추측 로또 티켓들
+- [x] 가진 로또 번호들이랑 당첨 번호랑 비교한다.
+
+6. 당첨 번호
+- [x] 로또 번호와 보너스 숫자를 가진다.
+
+7. 통계
+- [x] 당첨 확률을 게산한다. 
+- [x] 로또 번호의 당첨 내역을 계산한다.
+
+### 커밋 컨벤션
+[commit_type] : commit_message
+- [Feature]: 새로운 기능을 추가하는 경우
+- [Bugfix]: 버그를 수정하는 경우
+- [Refactor]: 코드 리팩토링을 수행하는 경우
+- [Docs]: 문서를 업데이트하는 경우
+- [Style]: 코드 스타일 변경 또는 포맷팅을 수행하는 경우
+- [Test]: 테스트 관련 변경을 수행하는 경우

리뷰: ~~이 부분이 궁금한게 있는데, 일반적으로 우리가 로또를 살 때는 그냥 숫자 6개를 선택하고, 2등은 뽑을 때, 보너스 번호를 추첨해서 비교하는데, 이것과는 다른 요구사항이었던 것인가요?~~

아 // 코드 확인했습니다. WinningLotto라는 클래스가 있는 것이군요.
@@ -1,8 +1,8 @@
 'use client';
 
 import { ReactNode } from 'react';
-import { useInView } from 'react-intersection-observer';
 
+import { keyframes } from '@emotion/react';
 import { Box, Card, CardMedia, SxProps, Typography } from '@mui/material';
 import { Theme } from '@mui/system';
 
@@ -14,40 +14,49 @@ interface CommentCardProps {
   isFilled?: boolean;
   isCharacter?: boolean;
   isChat?: boolean;
+  isFixed?: boolean;
   sx?: SxProps<Theme>;
+  positionY?: string;
 }
 
+const gradientAnimation = keyframes`
+    0% {
+        background-position: 0% 50%;
+    }
+    50% {
+        background-position: 100% 50%;
+    }
+    100% {
+        background-position: 0% 50%;
+    }
+`;
+
 const CommentCard = ({
   content,
   isStroke = false,
   isFilled = false,
   isCharacter = false,
   isChat = false,
+  isFixed = false,
   sx,
+  positionY = '40px',
 }: CommentCardProps) => {
-  const { ref, inView } = useInView({
-    threshold: 0.1,
-  });
-
   return (
     <Card
-      ref={ref}
       sx={{
         display: 'flex',
         boxShadow: 'none',
         alignItems: 'center',
         bgcolor: 'transparent',
-        ...(isChat && {
-          opacity: inView ? 1 : 0,
-          transform: inView ? 'translateY(0)' : 'translateY(20px)',
-          transition: 'opacity 0.6s ease-out, transform 0.6s ease-out',
-        }),
-        ...(!isChat && {
-          width: '100%',
-        }),
-        ...(!isCharacter && {
-          mb: 1.3,
-        }),
+        position: isFixed ? 'fixed' : 'relative',
+        bottom: isFixed ? positionY : 'unset',
+        right: isFixed ? '20px' : 'unset',
+        zIndex: isFixed ? 9999 : 'unset',
+        transition: 'transform 0.3s ease-in-out',
+        '&:hover': {
+          transform: 'scale(1.1)',
+        },
+        ...sx,
       }}
     >
       {isCharacter && (
@@ -59,19 +68,21 @@ const CommentCard = ({
           borderRadius: 5,
           ml: 1,
           padding: '2px',
-          background: isStroke ? `linear-gradient(${color.gradient_blue_dark}, ${color.gradient_blue_light})` : 'none',
-          ...(!isChat && {
-            width: '100%',
-          }),
+          background: isStroke
+            ? `linear-gradient(45deg, ${color.gradient_blue_dark}, ${color.gradient_blue_light})`
+            : 'none',
+          width: '100%',
         }}
       >
         <Box
           borderRadius="18px"
           padding={2}
           sx={{
             background: isFilled
-              ? `linear-gradient(${color.gradient_blue_dark}, ${color.gradient_blue_light})`
+              ? `linear-gradient(45deg, ${color.gradient_blue_dark}, ${color.gradient_blue_light})`
               : 'white',
+            backgroundSize: '200% 200%',
+            animation: isFixed && isFilled ? `${gradientAnimation} 6s ease infinite` : 'none',
             ...sx,
           }}
         >

리뷰: 인사이트와 챗봇을 포함한 모든 말풍선 카드에 마우스를 호버하면 커지는 효과가 나타나서 조건을 주셔야 할 것 같아요~!
지금 당장 쓰이는 곳이 이것밖에 없으니 `isFixed`로 하거나 다른 속성 하나 추가해주셔도 되고 마음대로 하시지용~!
@@ -0,0 +1,65 @@
+package oncall.domain.date;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class Day {
+    private static final String WEEK_HOLIDAY_MESSAGE = "(휴일) ";
+
+    private final Month month;
+    private final Date date;
+    private final DayOfWeek dayOfWeek;
+
+    public Day(Month month, Date date, DayOfWeek dayOfWeek) {
+        this.month = month;
+        this.date = date;
+        this.dayOfWeek = dayOfWeek;
+    }
+
+    public Day makeNextDay() {
+        return new Day(month, date.makeNextDate(), dayOfWeek.getNextDayOfWeek());
+    }
+
+    public DateType checkDateType() {
+        if (isWeekend()) {
+            return DateType.WEEKEND;
+        }
+        if (isHoliday()) {
+            return DateType.WEEK_HOLIDAY;
+        }
+        return DateType.WEEK;
+    }
+
+    private boolean isWeekend() {
+        return DayOfWeek.checkWeekend(dayOfWeek);
+    }
+
+    private boolean isHoliday() {
+        return Holidays.checkHoliday(month, date);
+    }
+
+    private boolean isWeekHoliday() {
+        return !isWeekend() && isHoliday();
+    }
+
+    public Month getMonth() {
+        return month;
+    }
+
+    public int getTotalDate() {
+        return month.getTotalDate();
+    }
+
+    @Override
+    public String toString() {
+        List<String> stringBuffer = new ArrayList<>();
+        stringBuffer.add(month.toString());
+        stringBuffer.add(date.toString());
+        stringBuffer.add(dayOfWeek.toString());
+        if (this.isWeekHoliday()) {
+            stringBuffer.add(WEEK_HOLIDAY_MESSAGE);
+        }
+        return stringBuffer.stream().collect(Collectors.joining(" ",""," "));
+    }
+}

리뷰: 저도 놓친 부분이긴 한데, "(휴일)"을 붙이는 경우가 무조건 공휴일인 경우에 붙이는 것이 아니라 주말이 아닌 공휴일에만 붙이는 조건이더라구요..!
@@ -0,0 +1,54 @@
+package oncall.domain.employee;
+
+import java.util.List;
+import oncall.util.exception.ErrorMessage;
+import oncall.util.exception.MyIllegalArgumentException;
+
+public class EmployeeRepository {
+    private static final int MIN_EMPLOYEES_NUMBER = 5;
+    private static final int MAX_EMPLOYEES_NUMBER = 35;
+
+    private final List<WeekWorkEmployee> weekWorkEmployees;
+    private final List<WeekendWorkEmployee> weekendWorkEmployees;
+
+    public EmployeeRepository(List<WeekWorkEmployee> weekWorkEmployees,
+                              List<WeekendWorkEmployee> weekendWorkEmployees) {
+        validateEmployees(weekWorkEmployees, weekendWorkEmployees);
+
+        this.weekWorkEmployees = weekWorkEmployees;
+        this.weekendWorkEmployees = weekendWorkEmployees;
+    }
+
+    private void validateEmployees(List<WeekWorkEmployee> weekWorkEmployees,
+                                   List<WeekendWorkEmployee> weekendWorkEmployees) {
+        if (weekWorkEmployees.size() < MIN_EMPLOYEES_NUMBER || weekWorkEmployees.size() > MAX_EMPLOYEES_NUMBER) {
+            throw new MyIllegalArgumentException(ErrorMessage.WRONG_EMPLOYEE_NUMBER);
+        }
+        validateSameWeekWorkEmployee(weekWorkEmployees);
+        validateSameWeekendWorkEmployee(weekendWorkEmployees);
+    }
+
+    private static void validateSameWeekendWorkEmployee(List<WeekendWorkEmployee> weekendWorkEmployees) {
+        if (weekendWorkEmployees.stream().distinct().count() != weekendWorkEmployees.size()) {
+            throw new MyIllegalArgumentException(ErrorMessage.NOT_SAME_EMPLOYEE_IN_ONE_WORK);
+        }
+    }
+
+    private static void validateSameWeekWorkEmployee(List<WeekWorkEmployee> weekWorkEmployees) {
+        if (weekWorkEmployees.stream().distinct().count() != weekWorkEmployees.size()) {
+            throw new MyIllegalArgumentException(ErrorMessage.NOT_SAME_EMPLOYEE_IN_ONE_WORK);
+        }
+    }
+
+    public WeekWorkEmployee getWeekWorkEmployee(int index) {
+        return weekWorkEmployees.get(index);
+    }
+
+    public WeekendWorkEmployee getWeekendWorkEmployees(int index) {
+        return weekendWorkEmployees.get(index);
+    }
+
+    public int getNumberOfEmployee() {
+        return weekWorkEmployees.size();
+    }
+}

리뷰: 중복확인 로직을 메서드로 분리한 것 처럼 길이 검증도 메서드로 분리하면 좋을 거 같네요!
@@ -0,0 +1,33 @@
+package oncall.domain.schedule;
+
+import java.util.List;
+import oncall.domain.date.Day;
+import oncall.domain.employee.Employee;
+
+public class DaySchedule {
+    private final Day day;
+    private final Employee employee;
+
+    public DaySchedule(Day day, Employee employee) {
+        this.day = day;
+        this.employee = employee;
+    }
+
+    public static boolean isContinuousWork(DaySchedule daySchedule_pre, DaySchedule daySchedule_post) {
+        return daySchedule_pre.employee.equals(daySchedule_post.employee);
+    }
+
+    public boolean isWeek() {
+        return day.checkDateType().isWeek();
+    }
+
+    public List<DaySchedule> changeEmployee(DaySchedule otherDaySchedule) {
+        DaySchedule firstDaySchedule = new DaySchedule(this.day, otherDaySchedule.employee);
+        DaySchedule secondDaySchedule = new DaySchedule(otherDaySchedule.day, this.employee);
+        return List.of(firstDaySchedule, secondDaySchedule);
+    }
+
+    public String toString() {
+        return day.toString() + employee.getName();
+    }
+}

리뷰: 이부분 자바 컨벤션에 따라 카멜케이스를 사용하는게 좋을 거 같군요! 아니면 혹시 이부분만 스네이크케이스를 사용하신 이유가 있으신걸까요?
@@ -0,0 +1,33 @@
+package oncall.domain.schedule;
+
+import java.util.List;
+import oncall.domain.date.Day;
+import oncall.domain.employee.Employee;
+
+public class DaySchedule {
+    private final Day day;
+    private final Employee employee;
+
+    public DaySchedule(Day day, Employee employee) {
+        this.day = day;
+        this.employee = employee;
+    }
+
+    public static boolean isContinuousWork(DaySchedule daySchedule_pre, DaySchedule daySchedule_post) {
+        return daySchedule_pre.employee.equals(daySchedule_post.employee);
+    }
+
+    public boolean isWeek() {
+        return day.checkDateType().isWeek();
+    }
+
+    public List<DaySchedule> changeEmployee(DaySchedule otherDaySchedule) {
+        DaySchedule firstDaySchedule = new DaySchedule(this.day, otherDaySchedule.employee);
+        DaySchedule secondDaySchedule = new DaySchedule(otherDaySchedule.day, this.employee);
+        return List.of(firstDaySchedule, secondDaySchedule);
+    }
+
+    public String toString() {
+        return day.toString() + employee.getName();
+    }
+}

리뷰: @Override 애노테이션을 붙여주는 것이 좋을 거 같아요
@@ -0,0 +1,24 @@
+package oncall.util.exception;
+
+public enum ErrorMessage {
+    WRONG_DELIMITER ("[ERROR]", "구분자를 잘못 입력하셨습니다. 콤마(,) 를 사용해주세요."),
+    NOT_NUMBER ("[ERROR]", "월에는 숫자를 입력해 주세요."),
+    NOT_DAY_OF_WEEK ("[ERROR]", "잘못된 요일을 입력했습니다."),
+    WRONG_MONTH_VALUE("[ERROR]", "월에는 0~12의 숫자를 입력해 주세요."),
+    WRONG_EMPLOYEE_NUMBER("[ERROR]", "직원 숫자는 최소 5명, 최대 35명 입니다."),
+    NOT_SAME_EMPLOYEE_IN_ONE_WORK("[ERROR]", "평일, 혹은 휴일 비상근무 스케쥴에 중복 인원이 들어갈 수 없습니다."),
+    TOO_LONG_EMPLOYEE_NAME("[ERROR]", "임직원의 이름은 5자까지 가능합니다");
+
+
+    private final String suffix;
+    private final String errorMessage;
+
+    ErrorMessage(String suffix, String errorMessage) {
+        this.suffix = suffix;
+        this.errorMessage = errorMessage;
+    }
+
+    public String makeErrorMessage() {
+        return String.join(" ", suffix, errorMessage);
+    }
+}

리뷰: 월에는 1~12의 숫자를 입력해달라는 메세지가 맞지 않나요?
@@ -0,0 +1,60 @@
+package oncall.view;
+
+
+import camp.nextstep.edu.missionutils.Console;
+import java.util.List;
+import oncall.util.converter.ViewConverter;
+import oncall.util.exception.ErrorMessage;
+import oncall.util.exception.MyIllegalArgumentException;
+import oncall.view.dto.InputMonthAndDayDto;
+
+public class InputView {
+    private static final String INPUT_START_MONTH_AND_DAY_MESSAGE = "비상 근무를 배정할 월과 시작 요일을 입력하세요> ";
+    private static final String INPUT_WEEK_WORK_EMPLOYEE_MESSAGE = "평일 비상 근무 순번대로 사원 닉네임을 입력하세요> ";
+    private static final String INPUT_WEEKEND_WORK_EMPLOYEE_MESSAGE = "휴일 비상 근무 순번대로 사원 닉네임을 입력하세요> ";
+    private static final String DELIMITER = ",";
+
+    private final ViewConverter viewConverter;
+
+    public InputView(ViewConverter viewConverter) {
+        this.viewConverter = viewConverter;
+    }
+
+    public InputMonthAndDayDto readMonthAndDay() {
+        System.out.println(INPUT_START_MONTH_AND_DAY_MESSAGE);
+        String inputMessage = Console.readLine();
+        if (!inputMessage.contains(DELIMITER)) {
+            throw new MyIllegalArgumentException(ErrorMessage.WRONG_DELIMITER);
+        }
+        String[] splitInput = inputMessage.split(DELIMITER);
+        validateMonthIsNumber(splitInput);
+
+        return viewConverter.convertToInputMonthAndDayDto(splitInput);
+    }
+
+    private static void validateMonthIsNumber(String[] splitInput) {
+        try {
+            Integer.parseInt(splitInput[0]);
+        } catch (NumberFormatException e) {
+            throw new MyIllegalArgumentException(ErrorMessage.NOT_NUMBER);
+        }
+    }
+
+    public List<String> readWeekWorkEmployee() {
+        System.out.println(INPUT_WEEK_WORK_EMPLOYEE_MESSAGE);
+        return readEmployeeName();
+    }
+
+    public List<String> readWeekendWorkEmployee() {
+        System.out.println(INPUT_WEEKEND_WORK_EMPLOYEE_MESSAGE);
+        return readEmployeeName();
+    }
+
+    private static List<String> readEmployeeName() {
+        String inputMessage = Console.readLine();
+        if (!inputMessage.contains(DELIMITER)) {
+            throw new MyIllegalArgumentException(ErrorMessage.WRONG_DELIMITER);
+        }
+        return List.of(inputMessage.split(DELIMITER));
+    }
+}

리뷰: 사소하긴 하지만 예제 입력 형태보면 "비상 근무를 배정할 월과 시작 요일을 입력하세요> " 메시지 바로 옆에서부터 입력할 수 있는 형태입니다! 
System.out.println(...) 대신 System.out.print() 를 사용하면 위처럼 한줄 뛰어서 입력받는게 아니라 출력하고 바로 다음 입력받을 수 있습니다
@@ -0,0 +1,78 @@
+package oncall.domain.schedule;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+import oncall.domain.date.Month;
+
+public class MonthSchedule {
+    private final Month month;
+    private final List<DaySchedule> daySchedules;
+
+    public MonthSchedule(Month month, List<DaySchedule> daySchedules) {
+        this.month = month;
+        this.daySchedules = daySchedules;
+    }
+
+    public MonthSchedule adjustSchedule() {
+        List<DaySchedule> continuousWork = findContinuousWork();
+        if (continuousWork.isEmpty()) {
+            return this;
+        }
+
+        List<DaySchedule> newDaySchedule = new ArrayList<>(daySchedules);
+        for (DaySchedule daySchedule : continuousWork) {
+            DaySchedule nextSameDateType = findNextSameDateType(daySchedule);
+            int indexOne = daySchedules.indexOf(daySchedule);
+            int indexTwo = daySchedules.indexOf(nextSameDateType);
+            List<DaySchedule> newDaySchedules = daySchedule.changeEmployee(nextSameDateType);
+            newDaySchedule.set(indexOne, newDaySchedules.get(0));
+            newDaySchedule.set(indexTwo, newDaySchedules.get(1));
+        }
+        MonthSchedule newMonthSchedule = new MonthSchedule(month, newDaySchedule);
+        if (!newMonthSchedule.findContinuousWork().isEmpty()) {
+            adjustSchedule();
+        }
+        return new MonthSchedule(month, newDaySchedule);
+    }
+
+    private DaySchedule findNextSameDateType(DaySchedule daySchedule) {
+        int index = daySchedules.indexOf(daySchedule);
+        DaySchedule nextDaySchedule;
+        if (daySchedule.isWeek()) {
+            do {
+                index += 1;
+                nextDaySchedule = daySchedules.get(index);
+            } while (nextDaySchedule.isWeek());
+            return daySchedules.get(index);
+        }
+        do {
+            index += 1;
+            nextDaySchedule = daySchedules.get(index);
+        } while (!nextDaySchedule.isWeek());
+        return daySchedules.get(index);
+    }
+
+    private List<DaySchedule> findContinuousWork() {
+        List<DaySchedule> continuousWorkDaySchedule = new ArrayList<>();
+        for (int index = 0; index < daySchedules.size() - 1; index++) {
+            int index_post = index + 1;
+            DaySchedule daySchedule_pre = daySchedules.get(index);
+            DaySchedule daySchedule_post = daySchedules.get(index_post);
+
+            if (DaySchedule.isContinuousWork(daySchedule_pre, daySchedule_post)) {
+                continuousWorkDaySchedule.add(daySchedule_post);
+            }
+        }
+        return continuousWorkDaySchedule;
+    }
+
+    @Override
+    public String toString() {
+        return daySchedules.stream()
+                .map(DaySchedule::toString)
+                .limit(month.getTotalDate())
+                .collect(Collectors.joining("\n"));
+    }
+
+}

리뷰: 이 부분에서 재귀를 탈출하지 못해 스택오버플로우가 발생하고 있어요..!
@@ -0,0 +1,31 @@
+package oncall.service;
+
+import java.util.ArrayList;
+import java.util.List;
+import oncall.domain.date.Day;
+import oncall.domain.employee.Employee;
+import oncall.domain.employee.EmployeeProvider;
+import oncall.domain.schedule.DaySchedule;
+import oncall.domain.schedule.MonthSchedule;
+
+public class ScheduleService {
+    private static final int BUFFER = 20;
+    private final EmployeeProvider employeeProvider;
+
+    public ScheduleService(EmployeeProvider employeeProvider) {
+        this.employeeProvider = employeeProvider;
+    }
+
+    public MonthSchedule makeMonthSchedule(Day firstDay) {
+        int totalDate = firstDay.getTotalDate();
+        List<DaySchedule> employeeSchedule = new ArrayList<>();
+        Day day = firstDay;
+        for (int i = 0; i < totalDate + BUFFER; i++) {
+            Employee employee = employeeProvider.provideEmployee(day);
+            DaySchedule daySchedule = new DaySchedule(day, employee);
+            employeeSchedule.add(daySchedule);
+            day = day.makeNextDay();
+        }
+        return new MonthSchedule(firstDay.getMonth(), employeeSchedule);
+    }
+}

리뷰: 요 버퍼는 어떤 역할을 하나요?
@@ -0,0 +1,24 @@
+package oncall.util.exception;
+
+public enum ErrorMessage {
+    WRONG_DELIMITER ("[ERROR]", "구분자를 잘못 입력하셨습니다. 콤마(,) 를 사용해주세요."),
+    NOT_NUMBER ("[ERROR]", "월에는 숫자를 입력해 주세요."),
+    NOT_DAY_OF_WEEK ("[ERROR]", "잘못된 요일을 입력했습니다."),
+    WRONG_MONTH_VALUE("[ERROR]", "월에는 0~12의 숫자를 입력해 주세요."),
+    WRONG_EMPLOYEE_NUMBER("[ERROR]", "직원 숫자는 최소 5명, 최대 35명 입니다."),
+    NOT_SAME_EMPLOYEE_IN_ONE_WORK("[ERROR]", "평일, 혹은 휴일 비상근무 스케쥴에 중복 인원이 들어갈 수 없습니다."),
+    TOO_LONG_EMPLOYEE_NAME("[ERROR]", "임직원의 이름은 5자까지 가능합니다");
+
+
+    private final String suffix;
+    private final String errorMessage;
+
+    ErrorMessage(String suffix, String errorMessage) {
+        this.suffix = suffix;
+        this.errorMessage = errorMessage;
+    }
+
+    public String makeErrorMessage() {
+        return String.join(" ", suffix, errorMessage);
+    }
+}

리뷰: ```suggestion
    private static final String prefix = "[ERROR] ";
```

항상 `prefix` 부분은 중복되기에 해당 부분을 상수로 추출하는 것도 괜찮을 것 같다고 생각해요.