ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,25 @@
+package menu.domain;
+
+import static org.assertj.core.api.AssertionsForClassTypes.*;
+
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+public class CantEatingMenusTest {
+    @Test
+    void 각_코치의_못먹는_메뉴가_0개_이상_2개_이하가_아니면_예외가_발생한다() {
+        List<String> menus = List.of("규동", "우동", "미소시루");
+
+        assertThatThrownBy(() -> new CantEatingMenus(menus))
+            .isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    void 각_코치가_못먹는_메뉴가_없는_메뉴이면_예외가_발생한다() {
+        List<String> menus = List.of("김치찜", "닭볶음탕");
+
+        assertThatThrownBy(() -> new CantEatingMenus(menus))
+            .isInstanceOf(IllegalArgumentException.class);
+    }
+}

리뷰: 과제 구현을 위해 필요한 최소한의 테스트를 잘 작성해주신 것 같아요! 👍
@@ -0,0 +1,50 @@
+package menu.domain;
+
+import static menu.exception.ErrorMessage.*;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class CantEatingMenus {
+    private static final int MAXIMUM_MENUS_LENGTH = 2;
+
+    private List<Menu> cantEatingMenus = new ArrayList<>();
+
+    public CantEatingMenus(List<String> cantEatingMenus) {
+        validateMenusLength(cantEatingMenus);
+        validateIsMenuExist(cantEatingMenus);
+        this.cantEatingMenus = cantEatingMenus(cantEatingMenus);
+    }
+
+    private void validateMenusLength(List<String> cantEatingMenus) {
+        if(!(cantEatingMenus.size() <= MAXIMUM_MENUS_LENGTH)) {
+            throw new IllegalArgumentException(INVALID_MENU_COUNT.getMessage());
+        }
+    }
+
+    private void validateIsMenuExist(List<String> cantEatingMenus) {
+       if(!isMenuExist(cantEatingMenus)) {
+           throw new IllegalArgumentException(NOT_EXIST_MENU.getMessage());
+       }
+    }
+
+    public List<Menu> getCantEatingMenus() {
+        return cantEatingMenus;
+    }
+
+    private List<Menu> cantEatingMenus(List<String> cantEatingMenus) {
+        return cantEatingMenus.stream()
+            .map(Menu::new)
+            .collect(Collectors.toList());
+    }
+
+    private boolean isMenuExist(List<String> cantEatingMenus) {
+        List<String> allMenus = Arrays.stream(Category.values())
+            .flatMap(category -> category.getMenus().stream())
+            .collect(Collectors.toList());
+
+        return cantEatingMenus.stream().anyMatch(allMenus::contains);
+    }
+}

리뷰: ```getCantEatingMenus()``` 메서드에서 cantEatinMenus를 반환해서 필요하다고 생각했습니다!

이런 경우는 상관 없는건가요? 🫤
@@ -0,0 +1,50 @@
+package menu.domain;
+
+import static menu.exception.ErrorMessage.*;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class CantEatingMenus {
+    private static final int MAXIMUM_MENUS_LENGTH = 2;
+
+    private List<Menu> cantEatingMenus = new ArrayList<>();
+
+    public CantEatingMenus(List<String> cantEatingMenus) {
+        validateMenusLength(cantEatingMenus);
+        validateIsMenuExist(cantEatingMenus);
+        this.cantEatingMenus = cantEatingMenus(cantEatingMenus);
+    }
+
+    private void validateMenusLength(List<String> cantEatingMenus) {
+        if(!(cantEatingMenus.size() <= MAXIMUM_MENUS_LENGTH)) {
+            throw new IllegalArgumentException(INVALID_MENU_COUNT.getMessage());
+        }
+    }
+
+    private void validateIsMenuExist(List<String> cantEatingMenus) {
+       if(!isMenuExist(cantEatingMenus)) {
+           throw new IllegalArgumentException(NOT_EXIST_MENU.getMessage());
+       }
+    }
+
+    public List<Menu> getCantEatingMenus() {
+        return cantEatingMenus;
+    }
+
+    private List<Menu> cantEatingMenus(List<String> cantEatingMenus) {
+        return cantEatingMenus.stream()
+            .map(Menu::new)
+            .collect(Collectors.toList());
+    }
+
+    private boolean isMenuExist(List<String> cantEatingMenus) {
+        List<String> allMenus = Arrays.stream(Category.values())
+            .flatMap(category -> category.getMenus().stream())
+            .collect(Collectors.toList());
+
+        return cantEatingMenus.stream().anyMatch(allMenus::contains);
+    }
+}

리뷰: 말씀해주신 방어적 복사는 생성자가 아니라 getter에서 이루어지는 게 더 자연스러울 것 같아요.
생성자에서 방어적 복사를 사용한다면 getter는 방어적 복사를 하지 않아도 된다고 생각하시나요?
@@ -0,0 +1,50 @@
+package menu.domain;
+
+import static menu.exception.ErrorMessage.*;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class CantEatingMenus {
+    private static final int MAXIMUM_MENUS_LENGTH = 2;
+
+    private List<Menu> cantEatingMenus = new ArrayList<>();
+
+    public CantEatingMenus(List<String> cantEatingMenus) {
+        validateMenusLength(cantEatingMenus);
+        validateIsMenuExist(cantEatingMenus);
+        this.cantEatingMenus = cantEatingMenus(cantEatingMenus);
+    }
+
+    private void validateMenusLength(List<String> cantEatingMenus) {
+        if(!(cantEatingMenus.size() <= MAXIMUM_MENUS_LENGTH)) {
+            throw new IllegalArgumentException(INVALID_MENU_COUNT.getMessage());
+        }
+    }
+
+    private void validateIsMenuExist(List<String> cantEatingMenus) {
+       if(!isMenuExist(cantEatingMenus)) {
+           throw new IllegalArgumentException(NOT_EXIST_MENU.getMessage());
+       }
+    }
+
+    public List<Menu> getCantEatingMenus() {
+        return cantEatingMenus;
+    }
+
+    private List<Menu> cantEatingMenus(List<String> cantEatingMenus) {
+        return cantEatingMenus.stream()
+            .map(Menu::new)
+            .collect(Collectors.toList());
+    }
+
+    private boolean isMenuExist(List<String> cantEatingMenus) {
+        List<String> allMenus = Arrays.stream(Category.values())
+            .flatMap(category -> category.getMenus().stream())
+            .collect(Collectors.toList());
+
+        return cantEatingMenus.stream().anyMatch(allMenus::contains);
+    }
+}

리뷰: String 타입의 리스트를 Menu 타입의 리스트로 변환하고 있어 생성자에서의 방어적 복사는 필요하지 않을 것 같습니다. 

다만 말씀해주신 부분들처럼 getCantEatingMenus()는 내부 리스트를 그대로 반환하고 있어서 방어적 복사를 적용하는 것이 더 적절하겠네요:) 앞으로 더 꼼꼼히 생각해봐야겠어요 피드백 감사합니다!
@@ -0,0 +1,43 @@
+package menu.domain;
+
+import java.util.Arrays;
+import java.util.List;
+
+import camp.nextstep.edu.missionutils.Randoms;
+
+public enum Category {
+    JAPANESE("일식", Arrays.asList("규동", "우동", "미소시루", "스시", "가츠동", "오니기리", "하이라이스", "라멘", "오코노미야끼")),
+    KOREAN("한식", Arrays.asList("김밥", "김치찌개", "쌈밥", "된장찌개", "비빔밥", "칼국수", "불고기", "떡볶이", "제육볶음")),
+    CHINESE("중식", Arrays.asList("깐풍기", "볶음면", "동파육", "짜장면", "짬뽕", "마파두부", "탕수육", "토마토 달걀볶음", "고추잡채")),
+    ASIAN("아시안", Arrays.asList("팟타이", "카오 팟", "나시고렝", "파인애플 볶음밥", "쌀국수", "똠얌꿍", "반미", "월남쌈", "분짜")),
+    WESTERN("양식", Arrays.asList("라자냐", "그라탱", "뇨끼", "끼슈", "프렌치 토스트", "바게트", "스파게티", "피자", "파니니"));;
+
+    private final String categoryName;
+    private final List<String> menus;
+
+    Category(String categoryName, List<String> menus) {
+        this.categoryName = categoryName;
+        this.menus = menus;
+    }
+
+    public String getCategoryName() {
+        return categoryName;
+    }
+
+    public List<String> getMenus() {
+        return menus;
+    }
+
+    public static Category randomCategory() {
+        int randomIndex = Randoms.pickNumberInRange(0, 4);
+        return Category.values()[randomIndex];
+    }
+
+    public static List<String> getMenusByCategory(Category category) {
+        return category.menus;
+    }
+
+    public static String getMenu(List<String> menus) {
+        return Randoms.shuffle(menus).get(0);
+    }
+}

리뷰: README에서 아래와 같은 주석을 확인한 후, 변경했습니다! 혹시 제가 잘못 이해한걸까요?!
```md
// 예시 코드. 사용하는 자료 구조에 따라 난수를 적절하게 가공해도 된다.
String category = categories.get(Randoms.pickNumberInRange(1, 5));
```
@@ -34,27 +34,35 @@ BUILD SUCCESSFUL in 0s
 
 한 주의 점심 메뉴를 추천해 주는 서비스다.
 
-- 코치들은 월, 화, 수, 목, 금요일에 점심 식사를 같이 한다.
-- 메뉴를 추천하는 과정은 아래와 같이 이뤄진다.
-  1. 월요일에 추천할 카테고리를 무작위로 정한다.
-  2. 각 코치가 월요일에 먹을 메뉴를 추천한다.
-  3. 화, 수, 목, 금요일에 대해 i, ii 과정을 반복한다.
-- 코치의 이름은 최소 2글자, 최대 4글자이다.
-- 코치는 최소 2명, 최대 5명까지 식사를 함께 한다.
-- 각 코치는 최소 0개, 최대 2개의 못 먹는 메뉴가 있다. (`,` 로 구분해서 입력한다.)
-  - 먹지 못하는 메뉴가 없으면 빈 값을 입력한다.
-  - 추천을 못하는 경우는 발생하지 않으니 고려하지 않아도 된다.
-- 한 주에 같은 카테고리는 최대 2회까지만 고를 수 있다.
-- 각 코치에게 한 주에 중복되지 않는 메뉴를 추천해야 한다.
+- [x] 코치들은 월, 화, 수, 목, 금요일에 점심 식사를 같이 한다.
+- [x] 메뉴를 추천하는 과정은 아래와 같이 이뤄진다.
+  - [x] 월요일에 추천할 카테고리를 무작위로 정한다.
+  - [x] 각 코치가 월요일에 먹을 메뉴를 추천한다.
+  - [x] 화, 수, 목, 금요일에 대해 i, ii 과정을 반복한다.
+- [x] 코치의 이름은 최소 2글자, 최대 4글자이다.
+- [x] 코치는 최소 2명, 최대 5명까지 식사를 함께 한다.
+- [x] 각 코치는 최소 0개, 최대 2개의 못 먹는 메뉴가 있다. (`,` 로 구분해서 입력한다.)
+  - [x] 먹지 못하는 메뉴가 없으면 빈 값을 입력한다.
+  - [x] 추천을 못하는 경우는 발생하지 않으니 고려하지 않아도 된다.
+- [x] 한 주에 같은 카테고리는 최대 2회까지만 고를 수 있다.
+- [x] 각 코치에게 한 주에 중복되지 않는 메뉴를 추천해야 한다.
   - 예시)
-    - 구구: 비빔밥, 김치찌개, 쌈밥, 규동, 우동 → 한식을 3회 먹으므로 불가능
-    - 토미: 비빔밥, 비빔밥, 규동, 우동, 볶음면 → 한 코치가 같은 메뉴를 먹으므로 불가능
-    - 제임스: 비빔밥, 김치찌개, 스시, 가츠동, 짜장면 → 매일 다른 메뉴를 먹으므로 가능
-    - 포코: 비빔밥, 김치찌개, 스시, 가츠동, 짜장면 → 제임스와 메뉴가 같지만, 포코는 매번 다른 메뉴를 먹으므로 가능
-- 메뉴 추천을 완료하면 프로그램이 종료된다.
-- 사용자가 잘못된 값을 입력할 경우 `IllegalArgumentException`를 발생시키고, "[ERROR]"로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시
+    - [x] 구구: 비빔밥, 김치찌개, 쌈밥, 규동, 우동 → 한식을 3회 먹으므로 불가능
+    - [x] 토미: 비빔밥, 비빔밥, 규동, 우동, 볶음면 → 한 코치가 같은 메뉴를 먹으므로 불가능
+    - [x] 제임스: 비빔밥, 김치찌개, 스시, 가츠동, 짜장면 → 매일 다른 메뉴를 먹으므로 가능
+    - [x] 포코: 비빔밥, 김치찌개, 스시, 가츠동, 짜장면 → 제임스와 메뉴가 같지만, 포코는 매번 다른 메뉴를 먹으므로 가능
+- [x] 메뉴 추천을 완료하면 프로그램이 종료된다.
+- [x] 사용자가 잘못된 값을 입력할 경우 `IllegalArgumentException`를 발생시키고, "[ERROR]"로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시
   받는다.
-  - `Exception`이 아닌 `IllegalArgumentException`, `IllegalStateException` 등과 같은 명확한 유형을 처리한다.
+  - [x] `Exception`이 아닌 `IllegalArgumentException`, `IllegalStateException` 등과 같은 명확한 유형을 처리한다.
+
+과제 시작 시간: 14:30  <br>
+TDD 종료 : 17:17 (2시간 47분) <br>
+MVP 완성 : 19:13 (TDD + 1시간 56분) <br>
+리팩토링 종료 : 19:29 (MVP 완성 + 16분) <br>
+총 4시간 59분 <br>
+
+아쉬운 점 : 함수형 인터페이스 적용, Service 클래스 도입, 리팩토링
 
 ### 입출력 요구 사항
 

리뷰: 이전에는 TDD 및 Controller, VIew 등의 클래스를 동시에 작성하면서 구현했기에 주요 로직에 대한 이해도가 적었던 것 같습니다.

페어프로그래밍에서 겪었던 과정을 기반으로 주요 로직에 대한 테스트코드를 먼저 작성하고나니, 로직에 대한 이해도가 높아지는 것은 물론 이미 구현이 완료된 '안전구역'이 확보된 느낌을 받아 훨씬 안정적이었습니다!
@@ -0,0 +1,75 @@
+package menu.controller;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import menu.domain.CantEatingMenus;
+import menu.domain.Category;
+import menu.domain.Coaches;
+import menu.domain.Menu;
+import menu.util.GenerateCategory;
+import menu.util.GenerateMenus;
+import menu.view.InputView;
+import menu.view.OutputView;
+
+public class RecommendController {
+    private Coaches coaches;
+    private CantEatingMenus cantEatingMenus;
+
+    public void start() {
+        OutputView.start();
+        execute();
+        OutputView.finish();
+    }
+
+    private void execute() {
+        List<String> inputCoachNames = getCoachNames();
+        getCantEatingMenus(inputCoachNames);
+        getResult(inputCoachNames);
+    }
+
+    private List<String> getCoachNames() {
+        List<String> inputCoachNames = new ArrayList<>();
+
+        while (true) {
+            try {
+                OutputView.inputCoachNames();
+                inputCoachNames = InputView.inputCoachNames();
+                coaches = new Coaches(inputCoachNames);
+                break;
+            } catch (IllegalArgumentException e) {
+                OutputView.printErrorMessage(e);
+            }
+        }
+        return inputCoachNames;
+    }
+
+    private void getCantEatingMenus(List<String> inputCoachNames) {
+        for (int index = 0; index < inputCoachNames.size(); index++) {
+            while (true) {
+                try {
+                    OutputView.inputCantEatingMenus(coaches.getCoachName(index));
+                    List<String> inputCantEatingMenus = InputView.inputCantEatingMenus();
+                    cantEatingMenus = new CantEatingMenus(inputCantEatingMenus);
+                    break;
+                } catch (IllegalArgumentException e) {
+                    OutputView.printErrorMessage(e);
+                }
+            }
+        }
+    }
+
+    private static List<Category> getCategories() {
+        List<Category> categories = GenerateCategory.generate();
+        OutputView.printDayAndCategory(categories);
+        return categories;
+    }
+
+    private void getResult(List<String> inputCoachNames) {
+        List<Category> categories = getCategories();
+        for (int index = 0; index < inputCoachNames.size(); index++) {
+            List<Menu> menus = GenerateMenus.generate(categories, cantEatingMenus);
+            OutputView.printResult(coaches.getCoachName(index), menus);
+        }
+    }
+}

리뷰: depth가 3이라서 확실히 고민해야봐야할 부분인 것 같네요🥲
@@ -0,0 +1,50 @@
+package menu.domain;
+
+import static menu.exception.ErrorMessage.*;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class CantEatingMenus {
+    private static final int MAXIMUM_MENUS_LENGTH = 2;
+
+    private List<Menu> cantEatingMenus = new ArrayList<>();
+
+    public CantEatingMenus(List<String> cantEatingMenus) {
+        validateMenusLength(cantEatingMenus);
+        validateIsMenuExist(cantEatingMenus);
+        this.cantEatingMenus = cantEatingMenus(cantEatingMenus);
+    }
+
+    private void validateMenusLength(List<String> cantEatingMenus) {
+        if(!(cantEatingMenus.size() <= MAXIMUM_MENUS_LENGTH)) {
+            throw new IllegalArgumentException(INVALID_MENU_COUNT.getMessage());
+        }
+    }
+
+    private void validateIsMenuExist(List<String> cantEatingMenus) {
+       if(!isMenuExist(cantEatingMenus)) {
+           throw new IllegalArgumentException(NOT_EXIST_MENU.getMessage());
+       }
+    }
+
+    public List<Menu> getCantEatingMenus() {
+        return cantEatingMenus;
+    }
+
+    private List<Menu> cantEatingMenus(List<String> cantEatingMenus) {
+        return cantEatingMenus.stream()
+            .map(Menu::new)
+            .collect(Collectors.toList());
+    }
+
+    private boolean isMenuExist(List<String> cantEatingMenus) {
+        List<String> allMenus = Arrays.stream(Category.values())
+            .flatMap(category -> category.getMenus().stream())
+            .collect(Collectors.toList());
+
+        return cantEatingMenus.stream().anyMatch(allMenus::contains);
+    }
+}

리뷰: setUp 등의 메서드명과 input 등의 변수명으로 수정하는게 더 좋겠네요! 피드백 감사합니다!
@@ -0,0 +1,50 @@
+package menu.domain;
+
+import static menu.exception.ErrorMessage.*;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class CantEatingMenus {
+    private static final int MAXIMUM_MENUS_LENGTH = 2;
+
+    private List<Menu> cantEatingMenus = new ArrayList<>();
+
+    public CantEatingMenus(List<String> cantEatingMenus) {
+        validateMenusLength(cantEatingMenus);
+        validateIsMenuExist(cantEatingMenus);
+        this.cantEatingMenus = cantEatingMenus(cantEatingMenus);
+    }
+
+    private void validateMenusLength(List<String> cantEatingMenus) {
+        if(!(cantEatingMenus.size() <= MAXIMUM_MENUS_LENGTH)) {
+            throw new IllegalArgumentException(INVALID_MENU_COUNT.getMessage());
+        }
+    }
+
+    private void validateIsMenuExist(List<String> cantEatingMenus) {
+       if(!isMenuExist(cantEatingMenus)) {
+           throw new IllegalArgumentException(NOT_EXIST_MENU.getMessage());
+       }
+    }
+
+    public List<Menu> getCantEatingMenus() {
+        return cantEatingMenus;
+    }
+
+    private List<Menu> cantEatingMenus(List<String> cantEatingMenus) {
+        return cantEatingMenus.stream()
+            .map(Menu::new)
+            .collect(Collectors.toList());
+    }
+
+    private boolean isMenuExist(List<String> cantEatingMenus) {
+        List<String> allMenus = Arrays.stream(Category.values())
+            .flatMap(category -> category.getMenus().stream())
+            .collect(Collectors.toList());
+
+        return cantEatingMenus.stream().anyMatch(allMenus::contains);
+    }
+}

리뷰: 사용자가 입력한 못 먹는 메뉴가 현재 보관하고 있는 메뉴 정보에 존재하지 않을 경우를 고려하고 싶었습니다! 선권님의 말씀대로라면 Menu 클래스에서 이를 관리하는게 더 적절해보이시나요?
@@ -0,0 +1,43 @@
+package menu.domain;
+
+import java.util.Arrays;
+import java.util.List;
+
+import camp.nextstep.edu.missionutils.Randoms;
+
+public enum Category {
+    JAPANESE("일식", Arrays.asList("규동", "우동", "미소시루", "스시", "가츠동", "오니기리", "하이라이스", "라멘", "오코노미야끼")),
+    KOREAN("한식", Arrays.asList("김밥", "김치찌개", "쌈밥", "된장찌개", "비빔밥", "칼국수", "불고기", "떡볶이", "제육볶음")),
+    CHINESE("중식", Arrays.asList("깐풍기", "볶음면", "동파육", "짜장면", "짬뽕", "마파두부", "탕수육", "토마토 달걀볶음", "고추잡채")),
+    ASIAN("아시안", Arrays.asList("팟타이", "카오 팟", "나시고렝", "파인애플 볶음밥", "쌀국수", "똠얌꿍", "반미", "월남쌈", "분짜")),
+    WESTERN("양식", Arrays.asList("라자냐", "그라탱", "뇨끼", "끼슈", "프렌치 토스트", "바게트", "스파게티", "피자", "파니니"));;
+
+    private final String categoryName;
+    private final List<String> menus;
+
+    Category(String categoryName, List<String> menus) {
+        this.categoryName = categoryName;
+        this.menus = menus;
+    }
+
+    public String getCategoryName() {
+        return categoryName;
+    }
+
+    public List<String> getMenus() {
+        return menus;
+    }
+
+    public static Category randomCategory() {
+        int randomIndex = Randoms.pickNumberInRange(0, 4);
+        return Category.values()[randomIndex];
+    }
+
+    public static List<String> getMenusByCategory(Category category) {
+        return category.menus;
+    }
+
+    public static String getMenu(List<String> menus) {
+        return Randoms.shuffle(menus).get(0);
+    }
+}

리뷰: 말씀하신 부분에 대해 생각해보니 GenerateCategory 클래스에서 이를 관리해도 좋았을 것 같네요 피드백 감사합니다!!
@@ -0,0 +1,50 @@
+package menu.domain;
+
+import static menu.exception.ErrorMessage.*;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class CantEatingMenus {
+    private static final int MAXIMUM_MENUS_LENGTH = 2;
+
+    private List<Menu> cantEatingMenus = new ArrayList<>();
+
+    public CantEatingMenus(List<String> cantEatingMenus) {
+        validateMenusLength(cantEatingMenus);
+        validateIsMenuExist(cantEatingMenus);
+        this.cantEatingMenus = cantEatingMenus(cantEatingMenus);
+    }
+
+    private void validateMenusLength(List<String> cantEatingMenus) {
+        if(!(cantEatingMenus.size() <= MAXIMUM_MENUS_LENGTH)) {
+            throw new IllegalArgumentException(INVALID_MENU_COUNT.getMessage());
+        }
+    }
+
+    private void validateIsMenuExist(List<String> cantEatingMenus) {
+       if(!isMenuExist(cantEatingMenus)) {
+           throw new IllegalArgumentException(NOT_EXIST_MENU.getMessage());
+       }
+    }
+
+    public List<Menu> getCantEatingMenus() {
+        return cantEatingMenus;
+    }
+
+    private List<Menu> cantEatingMenus(List<String> cantEatingMenus) {
+        return cantEatingMenus.stream()
+            .map(Menu::new)
+            .collect(Collectors.toList());
+    }
+
+    private boolean isMenuExist(List<String> cantEatingMenus) {
+        List<String> allMenus = Arrays.stream(Category.values())
+            .flatMap(category -> category.getMenus().stream())
+            .collect(Collectors.toList());
+
+        return cantEatingMenus.stream().anyMatch(allMenus::contains);
+    }
+}

리뷰: 넵 저는 메뉴의 존재 유무는 메뉴에서 검증하는 것이 적절하다고 생각해요!
정적 팩토리 메서드를 활용하면 자연스러운 흐름이 짜여질 것 같습니다.
@@ -0,0 +1,43 @@
+package menu.domain;
+
+import java.util.Arrays;
+import java.util.List;
+
+import camp.nextstep.edu.missionutils.Randoms;
+
+public enum Category {
+    JAPANESE("일식", Arrays.asList("규동", "우동", "미소시루", "스시", "가츠동", "오니기리", "하이라이스", "라멘", "오코노미야끼")),
+    KOREAN("한식", Arrays.asList("김밥", "김치찌개", "쌈밥", "된장찌개", "비빔밥", "칼국수", "불고기", "떡볶이", "제육볶음")),
+    CHINESE("중식", Arrays.asList("깐풍기", "볶음면", "동파육", "짜장면", "짬뽕", "마파두부", "탕수육", "토마토 달걀볶음", "고추잡채")),
+    ASIAN("아시안", Arrays.asList("팟타이", "카오 팟", "나시고렝", "파인애플 볶음밥", "쌀국수", "똠얌꿍", "반미", "월남쌈", "분짜")),
+    WESTERN("양식", Arrays.asList("라자냐", "그라탱", "뇨끼", "끼슈", "프렌치 토스트", "바게트", "스파게티", "피자", "파니니"));;
+
+    private final String categoryName;
+    private final List<String> menus;
+
+    Category(String categoryName, List<String> menus) {
+        this.categoryName = categoryName;
+        this.menus = menus;
+    }
+
+    public String getCategoryName() {
+        return categoryName;
+    }
+
+    public List<String> getMenus() {
+        return menus;
+    }
+
+    public static Category randomCategory() {
+        int randomIndex = Randoms.pickNumberInRange(0, 4);
+        return Category.values()[randomIndex];
+    }
+
+    public static List<String> getMenusByCategory(Category category) {
+        return category.menus;
+    }
+
+    public static String getMenu(List<String> menus) {
+        return Randoms.shuffle(menus).get(0);
+    }
+}

리뷰: 음 41라인의 목적은 결국 메뉴를 뽑기 위함이라 생각해서 getMenu()로 네이밍 했습니다.
랜덤으로 메뉴를 선택한다는 사실을 직관적으로 표현함이 가독성 및 유지보수성 측면에서 더 좋을 것 같네요!:)
@@ -0,0 +1,22 @@
+package menu.exception;
+
+public enum ErrorMessage {
+    INVALID_COACH_NAME_LENGTH("코치의 이름은 최소 2글자, 최대 4글자여야 합니다."),
+    INVALID_COACHES_COUNT("코치는 최소 2명, 최대 5명여야 합니다."),
+    INVALID_MENU_COUNT("못 먹는 메뉴는 최소 0개, 최대 2개여야 합니다."),
+    NOT_EXIST_MENU("존재하지 않는 메뉴입니다."),
+    ;
+
+    private static final String PREFIX = "[ERROR] ";
+    private static final String POSTFIX = " 다시 입력해 주세요.";
+
+    private final String message;
+
+    ErrorMessage(String message) {
+        this.message = message;
+    }
+
+    public String getMessage(Object... args) {
+        return String.format(PREFIX + message + POSTFIX, args);
+    }
+}

리뷰: 시간이 부족해 리팩토링하지 못한 점이 아쉽네요ㅠ 피드백 감사합니다!
@@ -22,6 +22,7 @@ public ResponseEntity<Void> highlight(
             @Valid @RequestBody HighlightsRequest request,
             @ReviewGroupSession ReviewGroup reviewGroup
     ) {
+        // todo: session 에서 리뷰 그룹 직접 꺼내는 것이 아닌, request 에 있는 reviewGroupId 를 활용하도록 수정해야 한다.
         highlightService.editHighlight(request, reviewGroup);
         return ResponseEntity.ok().build();
     }

리뷰: 지금 대부분의 서비스 코드들이 ReviewGroupSession 으로 ReviewGroup 자체를 받아오고 있어요😓
이걸 바꾸는게 대공사가 될 것 같네요...
@@ -16,7 +16,7 @@
 import reviewme.reviewgroup.service.dto.ReviewGroupCreationRequest;
 import reviewme.reviewgroup.service.dto.ReviewGroupCreationResponse;
 import reviewme.reviewgroup.service.dto.ReviewGroupPageResponse;
-import reviewme.reviewgroup.service.dto.ReviewGroupResponse;
+import reviewme.reviewgroup.service.dto.ReviewGroupSummaryResponse;
 
 @RestController
 @RequiredArgsConstructor
@@ -26,8 +26,8 @@ public class ReviewGroupController {
     private final ReviewGroupLookupService reviewGroupLookupService;
 
     @GetMapping("/v2/groups/summary")
-    public ResponseEntity<ReviewGroupResponse> getReviewGroupSummary(@RequestParam String reviewRequestCode) {
-        ReviewGroupResponse response = reviewGroupLookupService.getReviewGroupSummary(reviewRequestCode);
+    public ResponseEntity<ReviewGroupSummaryResponse> getReviewGroupSummary(@RequestParam String reviewRequestCode) {
+        ReviewGroupSummaryResponse response = reviewGroupLookupService.getReviewGroupSummary(reviewRequestCode);
         return ResponseEntity.ok(response);
     }
 

리뷰: 회의에서 

- 이 api 도 reviewGroupId 로 판별할까?🤔
- 리리코로 리뷰 그룹 정보를 식별해오는게 통일도 안된 것 같고 애매하긴 한데,..
호환성의 문제도 있고 복잡하니 일단 api 는 그대로 두고 response 이름만 변경하자! 😇


라고 했어서 반영했습니다~