ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,43 @@
+package oncall.domain.date;
+
+import java.util.Arrays;
+import oncall.util.exception.ErrorMessage;
+import oncall.util.exception.MyIllegalArgumentException;
+
+public enum Month {
+    JAN(1, 31),
+    FEB(2, 28),
+    MAR(3, 31),
+    APR(4, 30),
+    MAY(5,31),
+    JUN(6, 30),
+    JUL(7, 31),
+    AUG(8, 31),
+    SEP(9, 30),
+    OCT(10, 31),
+    NOV(11, 30),
+    DEC(12, 31);
+    private final int month;
+    private final int totalDate;
+
+    Month(int month, int totalDate) {
+        this.month = month;
+        this.totalDate = totalDate;
+    }
+
+    public int getTotalDate() {
+        return totalDate;
+    }
+
+    public static Month getMonth(int month) {
+        return Arrays.stream(values())
+                .filter(m -> m.month == month)
+                .findAny()
+                .orElseThrow(() -> new MyIllegalArgumentException(ErrorMessage.WRONG_MONTH_VALUE));
+    }
+
+    @Override
+    public String toString() {
+        return month + "월";
+    }
+}

리뷰: 해당 클래스는 `java.time.Month` enum으로 완벽히 대체 가능할 것 같습니다!
(물론 저도 이 사실은 시험이 끝난 후 깨달았습니다...ㅎㅎ)
@@ -0,0 +1,43 @@
+package oncall.domain.date;
+
+import java.util.Arrays;
+import java.util.Objects;
+import oncall.util.exception.ErrorMessage;
+import oncall.util.exception.MyIllegalArgumentException;
+
+public enum DayOfWeek {
+    SUN(0, "일"), MON(1, "월"), TUE(2, "화"), WED(3, "수"),
+    THU(4, "목"), FRI(5, "금"), SAT(6, "토");
+    private static final int NUMBER_OF_DAY_OF_WEEK = 7;
+
+    private final int code;
+    private final String dayOfWeek;
+
+    DayOfWeek(int code, String dayOfWeek ) {
+        this.code = code;
+        this.dayOfWeek = dayOfWeek;
+    }
+
+    public static boolean checkWeekend(DayOfWeek dayOfWeek) {
+        return dayOfWeek.equals(SUN) || dayOfWeek.equals(SAT);
+    }
+
+    public DayOfWeek getNextDayOfWeek() {
+        int nextDayOfWeekCode = (this.code + 1) % NUMBER_OF_DAY_OF_WEEK;
+        return Arrays.stream(values()).filter(dayOfWeek -> dayOfWeek.code == nextDayOfWeekCode)
+                .findAny()
+                .orElseThrow(IllegalArgumentException::new);
+    }
+
+    public static DayOfWeek getDayOfWeek(String dayOfWeek) {
+        return Arrays.stream(values())
+                .filter(day -> Objects.equals(day.dayOfWeek, dayOfWeek))
+                .findAny()
+                .orElseThrow(() -> new MyIllegalArgumentException(ErrorMessage.NOT_DAY_OF_WEEK));
+    }
+
+    @Override
+    public String toString() {
+        return dayOfWeek;
+    }
+}

리뷰: `code`를 기준으로 정렬된 배열, 또는 리스트를 미리 캐싱해 둔다면 반복적인 `getNextDayOfWeek()` 호출 상황에서 매번 `Stream` 객체를 생성하지 않아도 되며 `code`를 사용해 인덱스로 접근만 하면 되기에 성능이 훨씬 좋아질 것 같습니다!
@@ -0,0 +1,32 @@
+package oncall.domain.employee;
+
+import java.util.Objects;
+import oncall.util.exception.ErrorMessage;
+import oncall.util.exception.MyIllegalArgumentException;
+
+public class Employee {
+    private final String name;
+
+    public Employee(String name) {
+        validateEmployeeName(name);
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    private void validateEmployeeName(String name) {
+        if (name.length() > 5) {
+            throw new MyIllegalArgumentException(ErrorMessage.TOO_LONG_EMPLOYEE_NAME);
+        }
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof Employee compare) {
+            return Objects.equals(this.name, compare.name);
+        }
+        return false;
+    }
+}

리뷰: 상수 추출이 가능할 것 같습니다!
@@ -0,0 +1,17 @@
+package oncall.domain.employee;
+
+import java.util.List;
+
+public class EmployeeGenerator {
+    public static List<WeekWorkEmployee> generateWeekWorkEmployees(List<String> input) {
+        return input.stream()
+                .map(WeekWorkEmployee::new)
+                .toList();
+    }
+
+    public static List<WeekendWorkEmployee> generateWeekendWorkEmployees(List<String> input) {
+        return input.stream()
+                .map(WeekendWorkEmployee::new)
+                .toList();
+    }
+}

리뷰: 어플리케이션에서 두 개의 서로 다른 클래스로 구분되는 `WeekWorkEmployee`와 `WeekendWorkEmployee`가 기능적인 차이가 없어 보이는데요. 
동일한 `Employee`를 변수 명으로 구분해 주는 대신 별도의 클래스로 구현하신 이유가 있을까요?
@@ -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();
+    }
+}

리뷰: getter를 통해 외부에 컬렉션의 크기를 노출시키는 대신, `getXXXEmployees(int index)` 메서드에서 % 나머지 연산을 함께 수행해 주는 것은 어떨까요?
외부에서는 이 클래스를 이용할 때 내부에서 어떤 식으로 순서를 유지시킨 채 반복적으로 멤버를 탐색하는지의 구현에 신경쓰지 않고 "`index` 번째의 근무자"를 구할 수 있을 것 같아요!

이렇게 되면 언젠가 내부 자료구조가 `List<>`에서 배열로 변경되는 등의 변화가 있더라도 외부 코드가 변경되지 않을 것 같습니다!
@@ -0,0 +1,11 @@
+package oncall.domain.date;
+
+public class DayGenerator {
+    public static Day generateFirstDay(Month month, DayOfWeek firstDayOfWeek) {
+        return new Day(month, new Date(1), firstDayOfWeek);
+    }
+
+    public static Day generateDay(Day day) {
+        return day.makeNextDay();
+    }
+}

리뷰: 날짜를 생성해 주는 역할을 수행하는 정적 유틸 클래스처럼 보이는데요. 
저는 "날짜 객체 생성" 역시 날짜 객체 스스로의 책임이라고 생각해요.
때문에 `generateFirstDay()` 메서드는 단순히 `Day.of(Month, DayOfWeek)`의 형태를 가지는 정적 팩토리 메서드로만 구현해도 괜찮지 않았을까 생각합니다. 
이렇게 구현해 준다면 `DayGenerator` 클래스를 `Day` 클래스에 병합해 줄 수 있을 것 같아요.
@@ -1,7 +1,97 @@
-# java-chess
+## 전체 시나리오
 
-체스 미션 저장소
+1. start를 입력 받으면 게임을 시작한다.
+    - [예외] 게임이 시작하지 않았는데 move를 입력하면 예외가 발생한다.
+2. move sourcePosition targetPosition을 입력 받아 기물을 움직인다.
+    - [예외] 게임 진행 중에 start를 입력하면 예외가 발생한다.
+3. status를 입력 받아 각 진영의 score를 출력하고 승패를 출력한다.
+4. 킹이 먼저 잡히는 진영이 패하고, 즉시 게임을 종료한다.
+5. end를 입력 받아 게임을 종료한다.
 
-## 우아한테크코스 코드리뷰
+### 체스 게임(ChessGame)
 
-- [온라인 코드 리뷰 과정](https://github.com/woowacourse/woowacourse-docs/blob/master/maincourse/README.md)
+- [x] 체스 게임은
+    - [x] 위치에 따른 기물 정보를 가지고 있다.
+    - [x] 현재 어느 진영의 턴인지 가지고 있다.
+    - [x] 각 진영의 점수를 계산할 수 있다.
+    - [x] 왕이 죽으면 게임이 끝난다.
+        - [x] 어떤 진영이 이겼는지 출력하고 게임을 끝낸다.
+
+## 기물(Piece)
+
+- [x] 기물은
+    - [x] 기물의 타입(PieceType)과 진영(Team), 이동 전략(MovingStrategies)을 가지고 있다.
+    - [x] 각각의 고유 타입으로 구분된다.
+    - [x] 움직이려한 기물이 초기 폰이라면 일반 폰으로 바꿔준다.
+    - [예외] 행마법상 움직이지 못하는 곳으로 이동하려고 하면 예외가 발생한다.
+    - [예외] 이동하고자 하는 지점에 아군 기물이 있으면 예외가 발생한다.
+    - [예외] 빈 기물(EMPTY)를 움직이려 하면 예외가 발생한다.
+
+### 슬라이딩 가능한 기물 (SlidingPiece)
+
+- [x] 룩(Rook)
+    - [x] 상, 하, 좌, 우로 움직일 수 있다.
+- [x] 비숍(Bishop)
+    - [x] 대각선으로 움직일 수 있다.
+- [x] 퀸(Queen)
+    - [x] 상, 하, 좌, 우, 대각선으로 움직일 수 있다.
+
+### 슬라이딩 불가한 기물 (NonSlidingPiece)
+
+- [x] 나이트(Knight)
+    - [x] 나이트 고유의 여덟 방향으로 움직일 수 있다.
+- [x] 킹(King)
+    - [x] 상, 하, 좌, 우로 움직일 수 있다.
+
+### Empty
+
+- [x] 빈 기물을 뜻하고, 움직일 수 없다.
+
+### 폰(Pawn)
+
+- [x] InitialWhitePawn
+    - [x] 이동 전략 : 위로 두 칸, 위로 한 칸 움직일 수 있다.
+    - [x] 공격 전략 : 왼쪽 위, 오른쪽 위로 한 칸 움직일 수 있다.
+- [x] InitialBlackPawn
+    - [x] 이동 전략 : 아래로 두 칸, 아래로 한 칸 움직일 수 있다.
+    - [x] 공격 전략 : 왼쪽 아래, 오른쪽 아래로 한 칸 움직일 수 있다.
+- [x] WhitePawn
+    - [x] 이동 전략 : 위로 한 칸 움직일 수 있다.
+    - [x] 공격 전략 : 왼쪽 위, 오른쪽 위로 한 칸 움직일 수 있다.
+- [x] BlackPawn
+    - [x] 이동 전략 : 아래로 한 칸 움직일 수 있다.
+    - [x] 공격 전략 : 왼쪽 아래, 오른쪽 아래로 한 칸 움직일 수 있다.
+
+### Turn
+
+- [x] 초기 턴은 WHITE부터 시작된다.
+- [x] WHITE에서 BLACK으로, BLACK에서 WHITE로 턴을 차례로 바꾼다.
+- [예외] 자신의 턴이 아님에도 자신의 말을 움직이려고 하면 예외가 발생한다.
+
+### Score
+
+- [x] 현재까지 남아 있는 말에 따라 점수를 계산할 수 있어야 한다.
+- [x] 각 말의 점수는 queen은 9점, rook은 5점, bishop은 3점, knight는 2.5점이다.
+- [x] pawn의 기본 점수는 1점이다.
+    - [x] 하지만 같은 세로줄에 같은 색의 폰이 있는 경우 1점이 아닌 0.5점을 준다.
+
+### DB 시나리오
+- [x] 게임 시작시 
+  - [x] 진행중이던 게임이 있다면 해당 게임을 로드해서 진행한다.
+  - [x] 진행중이던 게임이 없다면 새로운 게임을 시작한다.
+- [x] 게임 진행중
+  - [x] status를 입력하거나, 상대방의 킹을 잡으면 게임을 종료한다. 그리고 DB를 모두 지운다.
+  - [x] 그렇지 않고, end를 입력한다면 마지막 게임 상태를 DB에 저장한다.
+
+### DB DDL
+``` sql
+create table chess_game
+(
+    piece_id   int auto_increment primary key,
+    piece_file varchar(20) not null,
+    piece_rank varchar(20) not null,
+    piece_type varchar(20) not null,
+    piece_team varchar(20) not null,
+    last_turn  varchar(20) not null
+);
+```

리뷰: DDL 명시 좋아요~

저도 리뷰어 분께 리뷰받은 사항 중 하나를 말씀드리면 현재 chess_game 테이블에서 관리되는 데이터들을 보면 테이블 명이 chess_game이 아니라 piece가 되어야 하지 않을까 하는 것입니다.
@@ -43,7 +43,7 @@ set JAVA_EXE=java.exe
 if "%ERRORLEVEL%" == "0" goto execute
 
 echo.
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo ERROR: JAVA_HOME is not set and no 'java' gameCommand could be found in your PATH.
 echo.
 echo Please set the JAVA_HOME variable in your environment to match the
 echo location of your Java installation.
@@ -65,7 +65,7 @@ echo location of your Java installation.
 goto fail
 
 :execute
-@rem Setup the command line
+@rem Setup the gameCommand line
 
 set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
 

리뷰: 음...? 이 파일은 command 라는 단어를 전체 치환하다가 같이 수정되어 버린 것 같아 보여요~
@@ -0,0 +1,12 @@
+package chess.database;
+
+public final class DbInfo {
+
+    public static String service_url = "jdbc:mysql://127.0.0.1:13306/chess?useSSL=false&serverTimezone=UTC";
+    public static String service_user = "root";
+    public static String service_password = "root";
+
+    public static String test_url = "jdbc:mysql://127.0.0.1:13306/chess_test?useSSL=false&serverTimezone=UTC";
+    public static String test_user = "root";
+    public static String test_password = "root";
+}

리뷰: 이 클래스에서 connection을 만들어서 던져주는 건 어떨까요?
@@ -0,0 +1,21 @@
+package chess;
+
+import chess.controller.ChessController;
+import chess.view.IOViewResolver;
+import chess.view.InputView;
+import chess.view.OutputView;
+
+import java.util.Scanner;
+
+public final class MainApp {
+
+    public static void main(String[] args) {
+        try (Scanner scanner = new Scanner(System.in)) {
+            final ChessController chessController = new ChessController(
+                    new IOViewResolver(new InputView(scanner), new OutputView()));
+            chessController.process();
+        } catch (Exception exception) {
+            exception.printStackTrace();
+        }
+    }
+}

리뷰: 자원관리 쿠울 👍
@@ -0,0 +1,70 @@
+package chess.controller;
+
+import chess.domain.game.File;
+import chess.domain.game.Position;
+import chess.domain.game.Rank;
+
+import java.util.Arrays;
+import java.util.List;
+
+public enum GameCommand {
+    INIT,
+    START,
+    FIRST_MOVE,
+    MOVE,
+    STATUS,
+    END;
+
+    public static final String INVALID_COMMAND_MESSAGE = "잘못된 커맨드를 입력하였습니다.";
+    public static final int SOURCE_INDEX = 1;
+    public static final int TARGET_INDEX = 2;
+    public static final int SOURCE_AND_TARGET_LENGTH = 2;
+    public static final int INPUT_SIZE = 3;
+    private static final int DEFAULT_COMMAND_INDEX = 0;
+
+    public static GameCommand from(final List<String> input) {
+        GameCommand result = Arrays.stream(values())
+                .filter(value -> value.name().equalsIgnoreCase(input.get(DEFAULT_COMMAND_INDEX)))
+                .findFirst()
+                .orElseThrow(() -> new IllegalArgumentException(INVALID_COMMAND_MESSAGE));
+        if (result == MOVE) {
+            validateMoveCommand(input);
+        }
+        return result;
+    }
+
+    private static void validateMoveCommand(List<String> input) {
+        if (input.size() != INPUT_SIZE) {
+            throw new IllegalArgumentException(INVALID_COMMAND_MESSAGE);
+        }
+        if (input.get(SOURCE_INDEX).length() != SOURCE_AND_TARGET_LENGTH ||
+                input.get(TARGET_INDEX).length() != SOURCE_AND_TARGET_LENGTH) {
+            throw new IllegalArgumentException(INVALID_COMMAND_MESSAGE);
+        }
+    }
+
+    public static Position getPosition(final List<String> input, final int index) {
+        final String targetPosition = input.get(index);
+        final int fileOrder = getFileOrder(targetPosition);
+        final int rankOrder = getRankOrder(targetPosition);
+        return Position.of(File.of(fileOrder), Rank.of(rankOrder));
+    }
+
+    private static int getFileOrder(final String command) {
+        final int charToIntDifference = 96;
+        return command.charAt(0) - charToIntDifference;
+    }
+
+    private static int getRankOrder(final String command) {
+        final char charToIntDifference = '0';
+        return command.charAt(1) - charToIntDifference;
+    }
+
+    public boolean isEnd() {
+        return this == END;
+    }
+
+    public boolean isFirstMove() {
+        return this == FIRST_MOVE;
+    }
+}

리뷰: 처음 GameCommand 클래스를 보는데 Position을 구하는 코드들이 왜 여기에 들어가 있을까가 좀 궁금했었습니다. 아마 헙크가 input으로 들어온 command를 가지고 position을 구하는 기능이라고 생각해서 GameCommand로 분리하신 것 같아요.

저라면 해당 기능들을 file, rank, Position Enum에서 책임을 지도록 했을 것 같은데 헙크 의견은 어떠신지 궁금해요~
@@ -0,0 +1,31 @@
+//
+//  SceneDelegate.swift
+//  DrawingApp
+//
+//  Created by 김하림 on 2023/03/28.
+//
+
+import UIKit
+
+class SceneDelegate: UIResponder, UIWindowSceneDelegate {
+
+    var window: UIWindow?
+
+
+    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
+        guard let _ = (scene as? UIWindowScene) else { return }
+    }
+
+    func sceneDidDisconnect(_ scene: UIScene) { }
+
+    func sceneDidBecomeActive(_ scene: UIScene) { }
+
+    func sceneWillResignActive(_ scene: UIScene) { }
+
+    func sceneWillEnterForeground(_ scene: UIScene) { }
+
+    func sceneDidEnterBackground(_ scene: UIScene) { }
+
+
+}
+

리뷰: 필요없거나 사용하지 않는 메소드는 판단하여 삭제하는것도 좋을 거 같습니다
@@ -0,0 +1,91 @@
+//
+//  ViewController.swift
+//  DrawingApp
+//
+//  Created by 김하림 on 2023/03/28.
+//
+
+import UIKit
+import OSLog
+
+class ViewController: UIViewController {
+
+    override func viewDidLoad() {
+        super.viewDidLoad()
+     
+        let rect1 = RectangleFactory.createRectangle()
+        os_log("\(rect1.description)")
+    }
+}
+
+protocol RectangleComponent {
+    var id:String { get }
+    var size:RectangleSize { get }
+    var position:RectanglePosition { get }
+    var backGroundColor:(red:Int, green:Int, blue:Int) { get }
+    var alpha:Int { get }
+}
+
+class RectangleSize {
+    private var width:Double = 150
+    private var height:Double = 120
+    
+    func getWidth() -> Double {
+        return self.width
+    }
+    func getHeight() -> Double {
+        return self.height
+    }
+}
+class RectanglePosition {
+    private var X:Double
+    private var Y:Double
+    
+    init(X: Double, Y: Double) {
+        self.X = X
+        self.Y = Y
+    }
+    func randomPosition() -> RectanglePosition {
+        let x = Double.random(in: 0...UIScreen.main.bounds.width)
+        let y = Double.random(in: 0...UIScreen.main.bounds.height)
+        return RectanglePosition(X: x, Y: y)
+    }
+    func getPositionX() -> Double {
+        return self.X
+    }
+    func getPositionY() -> Double {
+        return self.Y
+    }
+}
+
+
+class IdRandomizer {
+    func randomString() -> String {
+        let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
+        var randomString = ""
+        for _ in 0..<9 {
+                let randomIndex = Int.random(in: 0..<letters.count)
+                let letter = letters[letters.index(letters.startIndex, offsetBy: randomIndex)]
+                randomString.append(letter)
+        }
+        return "\(randomString.prefix(3))-\(randomString.prefix(6).suffix(3))-\(randomString.suffix(3))"
+        }
+}
+
+class RectangleFactory {
+    static func createRectangle() -> Rectangle {
+        let id = IdRandomizer().randomString()
+        let size = RectangleSize()
+        let position = RectanglePosition(X: 0, Y: 0).randomPosition()
+        let backGroundColor = (red: Int.random(in: 0...255), green: Int.random(in: 0...255), blue: Int.random(in: 0...255))
+        let alpha = Int.random(in: 1...10)
+
+        return Rectangle(
+            id: id,
+            size: size,
+            position: position,
+            backGroundColor: backGroundColor,
+            alpha: alpha
+        )
+    }
+}

리뷰: 좌표 수정할 가능성이 없다면 상수로 선언하는 것도 좋아보입니다
@@ -0,0 +1,91 @@
+//
+//  ViewController.swift
+//  DrawingApp
+//
+//  Created by 김하림 on 2023/03/28.
+//
+
+import UIKit
+import OSLog
+
+class ViewController: UIViewController {
+
+    override func viewDidLoad() {
+        super.viewDidLoad()
+     
+        let rect1 = RectangleFactory.createRectangle()
+        os_log("\(rect1.description)")
+    }
+}
+
+protocol RectangleComponent {
+    var id:String { get }
+    var size:RectangleSize { get }
+    var position:RectanglePosition { get }
+    var backGroundColor:(red:Int, green:Int, blue:Int) { get }
+    var alpha:Int { get }
+}
+
+class RectangleSize {
+    private var width:Double = 150
+    private var height:Double = 120
+    
+    func getWidth() -> Double {
+        return self.width
+    }
+    func getHeight() -> Double {
+        return self.height
+    }
+}
+class RectanglePosition {
+    private var X:Double
+    private var Y:Double
+    
+    init(X: Double, Y: Double) {
+        self.X = X
+        self.Y = Y
+    }
+    func randomPosition() -> RectanglePosition {
+        let x = Double.random(in: 0...UIScreen.main.bounds.width)
+        let y = Double.random(in: 0...UIScreen.main.bounds.height)
+        return RectanglePosition(X: x, Y: y)
+    }
+    func getPositionX() -> Double {
+        return self.X
+    }
+    func getPositionY() -> Double {
+        return self.Y
+    }
+}
+
+
+class IdRandomizer {
+    func randomString() -> String {
+        let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
+        var randomString = ""
+        for _ in 0..<9 {
+                let randomIndex = Int.random(in: 0..<letters.count)
+                let letter = letters[letters.index(letters.startIndex, offsetBy: randomIndex)]
+                randomString.append(letter)
+        }
+        return "\(randomString.prefix(3))-\(randomString.prefix(6).suffix(3))-\(randomString.suffix(3))"
+        }
+}
+
+class RectangleFactory {
+    static func createRectangle() -> Rectangle {
+        let id = IdRandomizer().randomString()
+        let size = RectangleSize()
+        let position = RectanglePosition(X: 0, Y: 0).randomPosition()
+        let backGroundColor = (red: Int.random(in: 0...255), green: Int.random(in: 0...255), blue: Int.random(in: 0...255))
+        let alpha = Int.random(in: 1...10)
+
+        return Rectangle(
+            id: id,
+            size: size,
+            position: position,
+            backGroundColor: backGroundColor,
+            alpha: alpha
+        )
+    }
+}

리뷰: 개인적으로 하위 객체나 프로토콜들은 다른 파일로 분리하는 것도 좋을 것 같습니다
저는 분리했을 때 추후에 코드 수정시 찾아가기 쉬워서 좋았습니다
@@ -0,0 +1,91 @@
+//
+//  ViewController.swift
+//  DrawingApp
+//
+//  Created by 김하림 on 2023/03/28.
+//
+
+import UIKit
+import OSLog
+
+class ViewController: UIViewController {
+
+    override func viewDidLoad() {
+        super.viewDidLoad()
+     
+        let rect1 = RectangleFactory.createRectangle()
+        os_log("\(rect1.description)")
+    }
+}
+
+protocol RectangleComponent {
+    var id:String { get }
+    var size:RectangleSize { get }
+    var position:RectanglePosition { get }
+    var backGroundColor:(red:Int, green:Int, blue:Int) { get }
+    var alpha:Int { get }
+}
+
+class RectangleSize {
+    private var width:Double = 150
+    private var height:Double = 120
+    
+    func getWidth() -> Double {
+        return self.width
+    }
+    func getHeight() -> Double {
+        return self.height
+    }
+}
+class RectanglePosition {
+    private var X:Double
+    private var Y:Double
+    
+    init(X: Double, Y: Double) {
+        self.X = X
+        self.Y = Y
+    }
+    func randomPosition() -> RectanglePosition {
+        let x = Double.random(in: 0...UIScreen.main.bounds.width)
+        let y = Double.random(in: 0...UIScreen.main.bounds.height)
+        return RectanglePosition(X: x, Y: y)
+    }
+    func getPositionX() -> Double {
+        return self.X
+    }
+    func getPositionY() -> Double {
+        return self.Y
+    }
+}
+
+
+class IdRandomizer {
+    func randomString() -> String {
+        let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
+        var randomString = ""
+        for _ in 0..<9 {
+                let randomIndex = Int.random(in: 0..<letters.count)
+                let letter = letters[letters.index(letters.startIndex, offsetBy: randomIndex)]
+                randomString.append(letter)
+        }
+        return "\(randomString.prefix(3))-\(randomString.prefix(6).suffix(3))-\(randomString.suffix(3))"
+        }
+}
+
+class RectangleFactory {
+    static func createRectangle() -> Rectangle {
+        let id = IdRandomizer().randomString()
+        let size = RectangleSize()
+        let position = RectanglePosition(X: 0, Y: 0).randomPosition()
+        let backGroundColor = (red: Int.random(in: 0...255), green: Int.random(in: 0...255), blue: Int.random(in: 0...255))
+        let alpha = Int.random(in: 1...10)
+
+        return Rectangle(
+            id: id,
+            size: size,
+            position: position,
+            backGroundColor: backGroundColor,
+            alpha: alpha
+        )
+    }
+}

리뷰: 파라미터를 줄로 나눠주신 방식 읽기 편하고 깃 관리하기 수월하네요
저도 카피카피 해도될까요?
@@ -0,0 +1,32 @@
+//
+//  Rectangle.swift
+//  DrawingApp
+//
+//  Created by 김하림 on 2023/03/30.
+//
+
+import Foundation
+
+class Rectangle : RectangleComponent {
+    let id:String
+    let size:RectangleSize
+    let position:RectanglePosition
+    let backGroundColor:(red:Int, green:Int, blue:Int)
+    let alpha:Int
+    
+    init(id:String, size:RectangleSize, position:RectanglePosition, backGroundColor:(red:Int, green:Int, blue:Int), alpha:Int) {
+        
+        self.id = id
+        self.size = size
+        self.position = position
+        self.backGroundColor = backGroundColor
+        self.alpha = alpha
+    }
+    
+}
+
+extension Rectangle : CustomStringConvertible {
+    var description: String {
+        return "(\(id), X:\(position.getPositionX()), Y\(position.getPositionY()):, W\(size.getWidth()), H\(size.getHeight()), R:\(backGroundColor.red), G:\(backGroundColor.green), B:\(backGroundColor.blue), Alpha:\(alpha))"
+    }
+}

리뷰: Color 튜플형식으로 한번에 여러개의 파라미터를 받을 수 있는지 몰랐네요
한 수 배워가요