# 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*