제가 평소에 코드를 짜면서 유의하면 좋을 것 같은 코딩 유의사항을 정리해보았습니다.
1. 긴 매개변수 목록 (Long Parameter List)
문제점: 너무 많은 매개변수가 있을 경우 함수의 가독성이 떨어지고, 관리가 어려워진다.
나쁜 예시:
void InitWeapon(FString Name, float Damage, float FireRate, int32 AmmoCount, float ReloadTime, USkeletalMesh* Mesh, USoundBase* Sound)
{
// 와, 많다 ...
}
좋은 예시: 구조체를 사용해 매개변수를 그룹화한다.
struct FWeaponData
{
FString Name;
float Damage;
float FireRate;
int32 AmmoCount;
};
struct FWeaponAssets
{
USkeletalMesh* Mesh;
USoundBase* Sound;
};
void InitWeapon(const FWeaponData& InData, const FWeaponAssets& InAssets)
{
// 훨씬 깔끔!
}
2. 전역 데이터 (Global Data)
문제점: 전역 데이터를 사용하면 예상치 못한 값 변경이 발생할 수 있으며, 디버깅이 어려워진다.
나쁜 예시:
UGameManager* GGameManager; // 전역!
void IncreaseScore()
{
GGameManager->Score += 10;
}
좋은 예시: Subsystem을 활용해 데이터의 접근을 제한한다.
UCLASS()
class UScoreSystem : public UGameInstanceSubsystem
{
GENERATED_BODY()
private:
int32 Score;
public:
void AddScore(int32 Amount)
{
Score += Amount;
}
int32 GetScore() const { return Score; }
};
3. 샷건 수술 (Shotgun Surgery)
문제점: 관련된 기능이 여러 클래스에 분산되어 있어 수정 시 여러 곳을 변경해야 한다.
나쁜 예시:
class APlayerCharacter : public ACharacter
{
public:
void TakeDamage(float Amount);
};
class AWeapon : public AActor
{
public:
float CalculateDamage();
};
class AMyGameMode : public AGameModeBase
{
public:
void UpdateDamageLeaderboard();
};
좋은 예시: 데미지 로직을 하나의 시스템으로 모은다.
class UDamageSystem : public UObject
{
public:
float CalculateDamage(AWeapon* Weapon, ACharacter* Target);
void ApplyDamage(AWeapon* Weapon, ACharacter* Target);
void UpdateDamageLeaderboard(ACharacter* Damager, ACharacter* Target, float Amount);
};
4. 기능 편애 (Feature Envy)
문제점: 특정 클래스가 다른 클래스의 데이터를 과도하게 사용하면 유지보수가 어려워진다.
나쁜 예시:
class UDamageCalculator : public UObject
{
public:
float CalculateDamageReduction(AMyCharacter* Character, float Damage);
};
좋은 예시: 필요한 연산을 해당 클래스 내부에서 처리하도록 한다.
class AMyCharacter : public ACharacter
{
public:
float CalculateDamageReduction(float Damage) const;
};
5. 데이터 뭉치 (Data Clumps)
문제점: 비슷한 데이터가 여러 곳에서 반복되면 유지보수가 어려워진다.
나쁜 예시:
void FireWeapon(float Damage, float Range, float Accuracy);
void ShowWeaponStats(float Damage, float Range, float Accuracy);
void UpgradeWeapon(float& Damage, float& Range, float& Accuracy);
좋은 예시: 공통 데이터를 구조체로 묶는다.
USTRUCT(BlueprintType)
struct FWeaponStats
{
GENERATED_BODY()
float Damage;
float Range;
float Accuracy;
};
6. 기본형 집착 (Primitive Obsession)
문제점: 단순한 데이터 타입을 과도하게 사용하면 데이터 무결성이 깨질 위험이 있다.
나쁜 예시:
float Health;
float MaxHealth;
좋은 예시: 전용 클래스를 만들어 로직을 포함시킨다.
class FHealth
{
public:
void ApplyDamage(float Amount);
private:
float Current;
float Max;
};
7. 게으른 요소 (Lazy Element)
문제점: 불필요한 중간 함수는 코드 복잡성을 증가시킨다.
나쁜 예시:
void FireProjectile(AProjectile* Projectile, FVector Direction, float Speed);
좋은 예시: 직접 핵심 로직을 실행한다.
class AProjectile : public AActor
{
public:
void Launch(const FVector& Dir, float Speed);
};
8. 추측성 일반화 (Speculative Generality)
문제점: 필요하지 않은 확장성을 고려하면 코드가 불필요하게 복잡해진다.
나쁜 예시:
class AWeaponBase : public AActor
{
virtual void SpecialAttack() = 0;
};
좋은 예시: 필요한 기능만 구현하고, 필요 시 확장한다.
class AWeapon : public AActor
{
public:
void Attack();
};
9. 메시지 체인 (Message Chains)
문제점: 객체 참조가 깊어질수록 유지보수가 어렵다.
나쁜 예시:
Player->GetInventory()->GetEquippedWeapon()->GetSound();
좋은 예시: 중간 객체가 직접 필요한 데이터를 제공하도록 한다.
class APlayer
{
public:
USoundBase* GetEquippedWeaponSound();
};
이와 같은 클린 코딩 원칙을 적용하면 유지보수성과 가독성이 높은 코드를 작성할 수 있다!