# Unity 코드 리뷰 체크리스트 ## 📋 개요 Unity MVVM 프로젝트에서 일관된 코드 품질을 위한 체계적인 코드 리뷰 체크리스트입니다. project rule과 완전히 일치하는 검증 기준을 제시합니다. --- ## 🏷️ 기본 코딩 표준 검토 ### ✅ 네이밍 규칙 (Naming Conventions) - [ ] **클래스명**: PascalCase 사용 (`PlayerController`, `InventoryModel`) - [ ] **메서드명**: PascalCase 사용 (`GetHealth()`, `UpdatePosition()`) - [ ] **프로퍼티명**: PascalCase 사용 (`MaxHealth`, `IsActive`) - [ ] **변수/매개변수**: camelCase 사용 (`moveSpeed`, `targetPosition`) - [ ] **Private 필드**: _camelCase 사용 (`_playerData`, `_isInitialized`) - [ ] **상수**: UPPER_SNAKE_CASE 사용 (`MAX_HEALTH`, `DEFAULT_SPEED`) - [ ] **SerializeField**: private + camelCase with underscore (`[SerializeField] private float _moveSpeed`) - [ ] **Interface**: I + PascalCase 사용 (`IPlayerService`, `IDataManager`) ### ✅ 파일 및 구조 - [ ] **파일명**: 클래스명과 일치 - [ ] **네임스페이스**: 적절한 네임스페이스 사용 - [ ] **Using 문**: 불필요한 using 제거, 알파벳 순 정렬 - [ ] **폴더 구조**: 기능별 모듈화 구조 준수 (`Features/[ModuleName]/Models/`) --- ## 🏗️ Unity 특화 검토 ### ✅ MonoBehaviour 규칙 - [ ] **생명주기 순서**: Awake → Start → Update → FixedUpdate → LateUpdate - [ ] **함수 간 간격**: 2줄 띄우기 적용 - [ ] **컴포넌트 캐싱**: GetComponent를 Awake에서 한 번만 호출 - [ ] **SerializeField 활용**: public 필드 대신 [SerializeField] private 사용 - [ ] **Inspector 참조**: public 함수/변수 변경 시 씬/프리팹 영향도 검토 ```csharp // ✅ 좋은 예시 public class PlayerController : MonoBehaviour { [SerializeField] private float _moveSpeed = 5f; [SerializeField] private Transform _playerTransform; private Rigidbody _rigidbody; private void Awake() { _rigidbody = GetComponent<Rigidbody>(); // 컴포넌트 캐싱 } private void Start() { InitializePlayer(); } private void Update() { HandleMovement(); } } ``` ### ✅ 성능 최적화 - [ ] **Update 최적화**: Update에서 GetComponent 직접 호출 금지 - [ ] **GC 최소화**: StringBuilder 사용, 불필요한 string 연산 제거 - [ ] **오브젝트 풀링**: 자주 생성/삭제되는 오브젝트에 풀링 적용 - [ ] **캐싱 활용**: 반복 계산 결과 캐싱 사용 --- ## 🎯 MVVM 패턴 검토 ### ✅ Model 구현 - [ ] **ScriptableObject 사용**: 데이터 모델은 ScriptableObject 기반 - [ ] **ReactiveProperty 활용**: UI 바인딩을 위한 ReactiveProperty 사용 - [ ] **비즈니스 로직**: 모델에 게임 규칙과 데이터 조작 로직 포함 - [ ] **인터페이스 정의**: 의존성 역전을 위한 인터페이스 제공 ```csharp // ✅ Model 검토 포인트 [CreateAssetMenu(fileName = "PlayerModel", menuName = "Game/Player")] public class PlayerModel : ScriptableObject, IPlayerModel { [SerializeField] private int _level = 1; public ReactiveProperty<int> Level { get; private set; } public void Initialize() { Level = new ReactiveProperty<int>(_level); } } ``` ### ✅ ViewModel 구현 - [ ] **Pure C# 클래스**: Unity 의존성 없는 순수 C# 클래스 - [ ] **IDisposable 구현**: 리소스 해제를 위한 Dispose 패턴 - [ ] **의존성 주입**: [Inject] 특성으로 의존성 주입 - [ ] **Command 패턴**: ReactiveCommand 사용한 사용자 액션 처리 - [ ] **데이터 변환**: Model 데이터를 UI에 맞게 변환 ```csharp // ✅ ViewModel 검토 포인트 public class PlayerViewModel : IDisposable { [Inject] private IPlayerModel _playerModel; public ReadOnlyReactiveProperty<string> LevelText { get; private set; } public ReactiveCommand HealCommand { get; private set; } private readonly CompositeDisposable _disposables = new(); public void Dispose() { _disposables?.Dispose(); // 필수! } } ``` ### ✅ View 구현 - [ ] **MonoBehaviour 상속**: Unity UI 컴포넌트 제어 - [ ] **ViewModel만 참조**: Model 직접 참조 금지 - [ ] **데이터 바인딩**: Observable Subscribe를 통한 UI 업데이트 - [ ] **리소스 해제**: OnDestroy에서 disposables 해제 --- ## 🔧 의존성 주입 (VContainer) 검토 ### ✅ DI Container 설정 - [ ] **LifetimeScope 구성**: 적절한 스코프 설정 - [ ] **라이프타임 관리**: Singleton/Transient/Scoped 적절한 선택 - [ ] **인터페이스 등록**: 구현체가 아닌 인터페이스로 등록 - [ ] **순환 의존성**: 순환 참조 없음 확인 ```csharp // ✅ DI 설정 검토 protected override void Configure(IContainerBuilder builder) { // 인터페이스로 등록 builder.RegisterInstance(_playerModel).As<IPlayerModel>(); // 적절한 라이프타임 설정 builder.Register<PlayerViewModel>(Lifetime.Scoped); builder.Register<GameManager>(Lifetime.Singleton); } ``` ### ✅ 의존성 주입 사용 - [ ] **생성자 주입**: Pure C# 클래스는 생성자 주입 우선 - [ ] **프로퍼티 주입**: MonoBehaviour는 [Inject] 프로퍼티/필드 사용 - [ ] **Null 체크**: 주입된 의존성 null 체크 수행 --- ## 📡 메시징 시스템 (MessagePipe) 검토 ### ✅ 메시지 정의 - [ ] **readonly struct**: 메시지는 변경 불가능한 구조체로 정의 - [ ] **의미 있는 이름**: 메시지 의도가 명확한 네이밍 - [ ] **필요한 데이터만**: 메시지에 불필요한 데이터 포함하지 않음 ```csharp // ✅ 메시지 정의 검토 public readonly struct PlayerLevelUpMessage { public readonly int NewLevel; public readonly int PreviousLevel; public PlayerLevelUpMessage(int newLevel, int previousLevel) { NewLevel = newLevel; PreviousLevel = previousLevel; } } ``` ### ✅ 메시지 사용 - [ ] **적절한 이벤트 선택**: Unity Event vs C# Event vs Observable vs MessagePipe - [ ] **구독 해제**: 구독한 메시지는 반드시 해제 (OnDestroy, Dispose) - [ ] **성능 고려**: 고빈도 메시지는 성능 영향 검토 --- ## 🛡️ 에러 핸들링 검토 ### ✅ Null 안전성 - [ ] **외부 참조 체크**: 외부에서 받은 모든 참조 null 체크 - [ ] **Unity Object 체크**: Unity 오브젝트 파괴 여부 확인 - [ ] **컬렉션 안전성**: 배열/리스트 인덱스 범위 체크 ### ✅ 예외 처리 - [ ] **Try-Catch 사용**: 파일 I/O, 네트워크, JSON 파싱에 예외 처리 - [ ] **구체적 예외**: Exception보다는 구체적 예외 타입 catch - [ ] **로깅**: Debug.LogError로 오류 상황 기록 ```csharp // ✅ 에러 핸들링 검토 public async UniTask<PlayerData> LoadPlayerDataAsync(string path) { try { // null 체크 if (string.IsNullOrEmpty(path)) { Debug.LogError("Path is null or empty"); return null; } var jsonData = await File.ReadAllTextAsync(path); return JsonUtility.FromJson<PlayerData>(jsonData); } catch (FileNotFoundException) { Debug.LogWarning(quot;File not found: {path}"); return CreateDefaultData(); } catch (JsonException ex) { Debug.LogError(quot;JSON parse error: {ex.Message}"); throw; } } ``` --- ## 🧪 테스트 코드 검토 ### ✅ 테스트 작성 - [ ] **단위 테스트 존재**: 새로운 public 메서드에 대한 테스트 작성 - [ ] **AAA 패턴**: Arrange-Act-Assert 구조 준수 - [ ] **의미 있는 테스트명**: 한글 사용하여 의도 명확히 표현 - [ ] **Edge Case 테스트**: 경계값, 예외 상황 테스트 포함 ### ✅ 테스트 품질 - [ ] **테스트 독립성**: 다른 테스트에 영향받지 않음 - [ ] **Mock 사용**: 외부 의존성은 Mock으로 대체 - [ ] **적절한 범위**: 테스트가 너무 크거나 작지 않음 --- ## 📊 성능 및 메모리 검토 ### ✅ 성능 최적화 - [ ] **프로파일링**: 성능이 중요한 코드는 Profiler로 측정 - [ ] **알고리즘 복잡도**: O(n²) 이상의 복잡도 피하기 - [ ] **불필요한 연산**: 매 프레임 실행되는 무거운 로직 최적화 ### ✅ 메모리 관리 - [ ] **메모리 누수**: 이벤트 구독 해제, IDisposable 구현 - [ ] **GC 압박**: string 연산, boxing 최소화 - [ ] **오브젝트 생성**: 불필요한 new 연산 줄이기 --- ## 📝 코드 품질 검토 ### ✅ 코드 구조 - [ ] **함수 길이**: 20줄 이하 권장, 30줄 초과 금지 - [ ] **클래스 크기**: 단일 책임 원칙 준수, 200줄 이하 권장 - [ ] **중첩 깊이**: 3단계 이하, Guard Clause 활용 - [ ] **매직 넘버**: 상수나 enum으로 대체 ### ✅ 가독성 - [ ] **주석 품질**: 한글 주석으로 의도 명확히 설명 - [ ] **TODO/FIXME**: 임시 코드나 알려진 이슈 표시 - [ ] **코드 일관성**: 팀 내 코딩 스타일 일관성 유지 ```csharp // ✅ 가독성 검토 예시 /// <summary> /// 플레이어의 체력을 회복시킵니다. /// </summary> /// <param name="amount">회복할 체력량</param> /// <returns>실제 회복된 체력량</returns> public float HealPlayer(float amount) { // 입력값 유효성 검사 if (amount <= 0) { Debug.LogWarning("회복량이 0 이하입니다."); return 0f; } // TODO: 회복 효과 애니메이션 추가 필요 (2024-01-15) float actualHeal = Mathf.Min(amount, _maxHealth - _currentHealth); _currentHealth += actualHeal; return actualHeal; } ``` --- ## 🔍 리뷰 프로세스 ### 1️⃣ 자동 검사 (Pre-Commit) - [ ] **컴파일 에러**: 모든 플랫폼에서 컴파일 성공 - [ ] **경고 메시지**: 컴파일러 경고 0개 유지 - [ ] **정적 분석**: SonarQube 등 도구로 코드 품질 검사 - [ ] **테스트 실행**: 모든 단위 테스트 통과 ### 2️⃣ 리뷰어 검토 - [ ] **기능 동작**: 요구사항에 맞게 동작하는지 확인 - [ ] **코드 설계**: 아키텍처 원칙 준수 여부 - [ ] **성능 영향**: 성능에 부정적 영향 없는지 확인 - [ ] **보안 이슈**: 잠재적 보안 문제 검토 ### 3️⃣ Unity 특화 검토 - [ ] **Scene/Prefab 변경**: 의도한 변경인지 확인 - [ ] **Missing Reference**: Inspector에서 참조 누락 없음 - [ ] **빌드 테스트**: 대상 플랫폼에서 빌드 성공 - [ ] **런타임 테스트**: 실제 게임에서 동작 확인 --- ## 📋 리뷰 완료 체크리스트 ### ✅ 승인 전 최종 확인 - [ ] 모든 체크리스트 항목 통과 - [ ] 최소 2명의 리뷰어 승인 - [ ] CI/CD 파이프라인 모든 단계 통과 - [ ] 관련 문서 업데이트 완료 - [ ] Breaking Change 영향도 분석 완료 ### ✅ 머지 후 확인 - [ ] 개발/스테이징 환경에서 정상 동작 확인 - [ ] 성능 지표 이상 없음 확인 - [ ] 관련 이슈/스토리 상태 업데이트 --- ## 🎯 리뷰 품질 향상 팁 ### 리뷰어를 위한 팁 1. **긍정적 피드백**: 좋은 코드에 대해서도 적극적 피드백 2. **구체적 제안**: "이렇게 해보세요" 보다는 구체적 대안 제시 3. **학습 기회**: 왜 그렇게 해야 하는지 이유 설명 4. **우선순위**: Critical > Major > Minor 순으로 피드백 분류 ### 작성자를 위한 팁 1. **셀프 리뷰**: PR 생성 전 스스로 한번 검토 2. **작은 PR**: 300줄 이하의 작은 단위로 PR 생성 3. **명확한 설명**: PR 설명에 변경 의도와 테스트 방법 포함 4. **테스트 포함**: 새로운 기능은 반드시 테스트와 함께 제출 --- ## 📊 리뷰 메트릭스 ### 추적할 지표 - **리뷰 소요 시간**: PR 생성부터 머지까지 평균 시간 - **결함 발견율**: 리뷰에서 발견된 버그 수 - **리뷰 커버리지**: 코드 라인 대비 리뷰된 비율 - **재작업율**: 리뷰 후 수정이 필요한 PR 비율 ### 개선 목표 - 리뷰 소요 시간: 24시간 이내 - 결함 발견율: 라인당 0.1개 이하 - 리뷰 커버리지: 95% 이상 - 재작업율: 30% 이하 --- *체계적인 코드 리뷰로 높은 품질의 Unity 게임 코드를 만들어가세요!* --- *최종 업데이트: 2024-01-03* *작성자: Unity Code Review Team*