728x90
반응형
프로그래밍을 하다 보면 유지보수가 어렵고 가독성이 떨어지는 코드를 마주할 때가 있습니다. 이러한 문제를 "코드 악취"라고 부르며, Unreal 엔진 프로젝트에서도 흔히 발견됩니다. 이 글에서는 24가지 코드 악취의 예시와 이를 개선하는 방법을 소개합니다.
1. 기이한 이름 (Mysterious Name)
- 문제점: 변수나 함수의 이름이 모호해 의도를 파악하기 어려움.
- 해결책: 역할이 명확한 이름 사용.
-
cppCopy
// 나쁜 예 float AAA; void DoIt(int x); // 좋은 예 float CurrentHealth; void AttackEnemy(int DamageAmount);
2. 중복 코드 (Duplicated Code)
- 문제점: 유사한 로직이 반복되어 수정이 어려움.
- 해결책: 상속 또는 컴포넌트로 통합.
-
cppCopy
// 공통 부모 클래스에서 데미지 처리 로직 통합 class ACharacterBase { void TakeDamage(float Amount) { Health -= Amount; if (Health <= 0) OnDeath(); } virtual void OnDeath() {} };
3. 긴 함수 (Long Function)
- 문제점: 한 함수가 너무 많은 역할을 수행.
- 해결책: 작은 단위의 함수로 분리.
-
cppCopy
void AMyCharacter::Tick(float DeltaTime) { HandleMovement(DeltaTime); HandleJump(); HandleAttack(); }
4. 긴 매개변수 목록 (Long Parameter List)
- 문제점: 매개변수가 많아 가독성 저하.
- 해결책: 구조체로 그룹화.
-
cppCopy
struct FWeaponData { float Damage, FireRate; }; void InitWeapon(const FWeaponData& Data);
5. 전역 데이터 (Global Data)
- 문제점: 전역 변수로 인한 부작용 발생.
- 해결책: 서브시스템 활용.
-
cppCopy
class UScoreSystem : public UGameInstanceSubsystem { void AddScore(int32 Amount); };
6. 가변 데이터 (Mutable Data)
- 문제점: 외부에서 직접 필드 수정.
- 해결책: 캡슐화와 Getter/Setter 사용.
-
cppCopy
class APlayerCharacter { private: float Health; public: void TakeDamage(float Amount); };
7. 뒤엉킨 변경 (Divergent Change)
- 문제점: 한 클래스가 여러 이유로 자주 수정됨.
- 해결책: 단일 책임 원칙 적용.
-
cppCopy
class UPlayerDataManager; // 데이터 처리 class UGameplayManager; // 게임플레이 처리
8. 샷건 수술 (Shotgun Surgery)
- 문제점: 한 기능 변경에 여러 클래스 수정 필요.
- 해결책: 관련 로직을 한 시스템으로 중앙화.
-
cppCopy
class UDamageSystem { void ApplyDamage(AWeapon* Weapon, ACharacter* Target); };
9. 기능 편애 (Feature Envy)
- 문제점: 다른 클래스의 데이터를 과도하게 참조.
- 해결책: 해당 기능을 데이터 소유 클래스로 이전.
-
cppCopy
float AMyCharacter::CalculateDamageReduction(float Damage);
10. 데이터 뭉치 (Data Clumps)
- 문제점: 연관된 데이터가 흩어져 전달.
- 해결책: 구조체로 묶어 관리.
-
cppCopy
struct FWeaponStats { float Damage, Range, Accuracy; };
11. 기본형 집착 (Primitive Obsession)
- 문제점: 기본 타입으로만 데이터 표현.
- 해결책: 커스텀 타입 생성.
-
cppCopy
class FHealth { void ApplyDamage(float Amount); };
12. 반복되는 스위치문 (Repeated Switches)
- 문제점: 타입 확인을 위한 조건문 반복.
- 해결책: 다형성 활용.
-
cppCopy
class AWeapon { virtual void Attack(); }; class ASword : public AWeapon { void Attack() override; };
13. 반복문 (Loops)
- 문제점: 중첩 루프와 복잡한 조건.
- 해결책: 알고리즘 함수 활용.
-
cppCopy
auto HeavyItems = Items.FilterByPredicate(...);
14. 게으른 요소 (Lazy Element)
- 문제점: 불필요한 중간 계층 함수.
- 해결책: 직접적인 구현으로 단순화.
-
cppCopy
void AProjectile::Launch() { /* 직접 구현 */ }
15. 추측성 일반화 (Speculative Generality)
- 문제점: 사용되지 않는 확장성 코드.
- 해결책: 실제 필요에 맞춰 최소화.
-
cppCopy
class AWeapon { void Attack(); }; // 기본 기능만
16. 임시 필드 (Temporary Field)
- 문제점: 특정 상황에서만 사용되는 필드.
- 해결책: 컴포넌트로 분리.
-
cppCopy
class URangedAttackComponent : public UActorComponent;
17. 메시지 체인 (Message Chains)
- 문제점: 과도한 점 표기법으로 객체 탐색.
- 해결책: 캡슐화로 체인 단축.
-
cppCopy
USoundBase* AWeapon::GetAttackSound();
18. 중재자 (Middle Man)
- 문제점: 중개자가 불필요한 호출만 전달.
- 해결책: 직접 바인딩으로 우회.
-
cppCopy
MyChar->SetupPlayerInput(InputComponent);
19. 내부자 거래 (Insider Trading)
- 문제점: 다른 객체의 내부 상태 직접 조작.
- 해결책: 공개 인터페이스만 사용.
-
cppCopy
Player->ReceiveDamage(AttackDamage);
20. 거대한 클래스 (Large Class)
- 문제점: 하나의 클래스가 너무 많은 책임.
- 해결책: 컴포넌트 기반 설계.
-
cppCopy
UPROPERTY() UMovementComponent* MovementComp;
21. 서로 다른 인터페이스의 대안 클래스들
- 문제점: 유사 기능이지만 인터페이스 불일치.
- 해결책: 공통 인터페이스 정의.
-
cppCopy
class AWeapon { virtual void Attack(); };
22. 데이터 클래스 (Data Class)
- 문제점: 데이터 저장만 하는 수동적 클래스.
- 해결책: 로직을 포함한 능동적 클래스로 변경.
-
cppCopy
class FPlayerStats { void ApplyDamage(float Damage); };
23. 상속 포기 (Refused Bequest)
- 문제점: 부모 클래스의 메서드를 사용하지 않음.
- 해결책: 인터페이스 분리.
-
cppCopy
class ABaseWeapon { virtual void Attack(); };
24. 주석 (Comments)
- 문제점: 복잡한 코드를 설명하는 과도한 주석.
- 해결책: 함수 분리와 의미 있는 이름 사용.
-
cppCopy
bool AEnemy::CanSeePlayer();
결론
코드 악취는 프로젝트의 유지보수성을 크게 저하시킵니다. Unreal 엔진의 기능(서브시스템, 컴포넌트, 다형성 등)을 활용해 가독성과 확장성이 높은 코드를 작성합시다. 리팩토링은 지속적인 과정이므로, 코드 리뷰와 리팩토링을 습관화하는 것이 중요합니다
728x90
반응형