Keep (잘한 점)
- 기능의 완결성: 코드는 의도한 대로 동작하며, 게임의 루프(Loop)가 끊기지 않고 순환하는 구조를 잘 구현했다.
- 책임의 분리 시도: 최소한
BattleSystem과ShopSystem,UIManager를 파일 단위로 분리하려고 노력했다.
Problem (문제점 - 코드 기반 구체화)
- God Class (
GameManager):GameManager가 모든 시스템의 포인터를 소유하고 관리하는 '신(God)' 객체가 되어버려, 유지보수 시 사이드 이펙트가 우려된다. - 하드코딩된 로직:
BattleSystem::SpawnMonster의switch문과UseItem의if (HP < 0.5)같은 로직이 시스템 내부에 박혀 있어, 기획 데이터 변경 시 코드 수정이 불가피하다. - 확장성 부족: 새로운 게임 모드나 몬스터를 추가할 때마다 기존 코드를 뜯어고쳐야 하는 구조다. (OCP 위반)
Try (시도할 점 - 구체적 액션 아이템)
- Factory 패턴 적용:
BattleSystem::SpawnMonster의switch문을 제거하고, 몬스터 생성을 전담하는MonsterFactory클래스를 만든다. - State 패턴 적용:
GameManager의switch(Mode_)를 제거하고,IGameState인터페이스를 상속받은BattleState,ShopState클래스로 나누어 상태를 관리한다. - Component 패턴 도입:
GameManager가 모든 것을new하는 대신, 필요한 시스템을 컴포넌트 형태로 부착하거나 의존성 주입(DI)을 받도록 구조를 변경한다. - 데이터 주도 설계(Data-Driven): 몬스터 스폰 확률이나 아이템 사용 조건(50%) 등을 코드에서 빼내어 XML/JSON 혹은 별도의 설정 테이블로 관리하도록 구상해본다.
스파게티 코드를 막는 설계 원칙 (Factory & State Pattern)
1. switch 문의 지옥
프로젝트를 진행하며 BattleSystem과 GameManager를 구현했다. 기능은 잘 돌아가지만, 코드를 다시 보니 확장성에 심각한 문제가 있음을 발견했다.
❌ 나쁜 코드 예시 (내 코드)
// BattleSystem.cpp - 몬스터가 추가될 때마다 이 함수를 수정해야 함 (OCP 위반)
void BattleSystem::SpawnMonster(bool isBoss) {
switch (RandomUtil::GetRandomInt(1, 10)) {
case 1: monster_ = new BenediktKiss(...); break;
case 2: monster_ = new DominikYork(...); break;
// ... 몬스터가 100마리면 case가 100개?
}
}
이 방식은 OCP(Open-Closed Principle, 개방-폐쇄 원칙)를 위반한다. 기능을 확장(몬스터 추가)하려면 기존 코드(BattleSystem)를 수정(Open)해야 하기 때문이다.
2. 해결책 1) 팩토리 패턴 (Factory Pattern)
객체 생성을 전담하는 공장(Factory)을 만들어 BattleSystem이 구체적인 몬스터 클래스(BenediktKiss 등)를 모르게 해야 한다.
- Before:
BattleSystem이 직접new Monster()를 호출. - After:
BattleSystem은MonsterFactory::CreateRandomMonster()만 호출.
이렇게 하면 BattleSystem은 전투 로직에만 집중할 수 있고(응집도 상승), 몬스터 종류가 늘어나도 Factory만 수정하면 된다.
3. 해결책 2) 상태 패턴 (State Pattern)
GameManager에서 게임의 흐름을 제어하기 위해 switch (Mode_)를 사용했다.
// GameManager.cpp
while (bCanGameRunning) {
switch (Mode_) {
case BATTLE_MODE: ... break;
case SHOP_MODE: ... break;
// 모드가 10개면 GameManager는 수천 줄이 된다.
}
}
이것 역시 상태가 늘어날수록 GameManager가 비대해지는 원인이 된다. State 패턴을 적용하여 각 상태(BattleState, ShopState)를 클래스로 나누고, GameManager는 현재 상태의 Execute()만 호출하도록 변경해야 한다.
4. 결론 (Action Plan)
- 응집도(Cohesion):
BattleSystem에서 '보상'과 '생성' 로직을 분리하자. - 결합도(Coupling):
GameManager가 모든 객체를 알 필요가 없도록 추상화하자. - SOLID: OCP를 지키기 위해
if-else나switch도배를 멈추고 다형성(Polymorphism)을 활용하자.
'프로그래밍 > 개발일지' 카테고리의 다른 글
| [UnrealEngine] 멀티플레이어 디펜스 `SagoMagic` 프로젝트 KPT 회고 (0) | 2026.04.24 |
|---|---|
| [개발전반] Ureal Engine 5를 이용해 한 달동안 진행했던 팀프로젝트 TPS게임 개발 KRP (0) | 2026.03.05 |
| [개발전반] Best Practice란? (0) | 2026.02.03 |