🫧Clean Code :: 클린 코드 가이드

2025. 3. 28. 13:25·Dev./기타 개발 관련

💭회고

개발에서 가장 중요한 역량 중 하나인 '클린 코드'에 대해 정리해보았다. 특히 언리얼 엔진 환경에서 어떻게 코드 품질을 높일 수 있는지, 실무에서 자주 만나는 문제점들과 해결 방법을 추가적으로 조사해서 업데이트하였다...!

이 내용은 이론에 그치지 않고, 실제 프로젝트에서 팀 생산성과 코드 안정성을 크게 향상시키는 핵심 요소이므로 유념해두자.

TLDR;
게임 개발에서는 여러 분야의 전문가가 협업하기 때문에 코드의 가독성과 유지보수가 더욱 중요하다.
24가지 나쁜 코드 유형을 인식하고 개선하는 것이 중요하며,
➡️ 이를 위해 명확한 이름 사용, 중복 코드 제거, 함수 단일 책임 원칙 적용, 긴 매개변수 목록 개선, 전역 변수 사용 최소화, 불변 데이터 활용 등의 방법을 활용한다. 언리얼 엔진에서는 컴포넌트 기반 아키텍처와 SOLID 원칙을 적용하여 코드의 품질을 높이고, 성능과 가독성의 균형을 유지하는 것이 중요하다. 또한, 지속적인 리팩토링과 테스트를 통해 코드를 단순하고 직관적으로 유지하는 것이 프로젝트 성공의 핵심 전략이다.

📒학습 내용

⭐클린 코드의 중요성: AI 시대에 더욱 필요한 개발자 역량

"좋은 코드는 추가 설명 없이도 이해하기 쉽고, 나쁜 코드는 많은 설명을 해도 이해하기 어렵다."

 

클린 코드는 단순한 코딩 스타일의 문제가 아닌, 프로젝트의 성패를 좌우하는 핵심 요소이다.

특히 AI 시대에는 복잡한 알고리즘 짜는 능력보다 깨끗하고 명확한 코드를 설계할 줄 아는 능력이 더 큰 경쟁력이 된다.

GPT와 같은 AI 도구들이 기본적인 코드 작성을 대체할 수 있지만, 코드 설계와 구조화는 여전히 개발자의 몫이다.

 

왜 게임 개발에서 클린 코드가 더 중요한가?

게임 개발은 다른 소프트웨어 개발과 달리 여러 분야의 전문가(아티스트, 기획자, 프로그래머 등)가 함께 작업한다.

코드가 지저분하면:

  • 다른 팀원들의 작업 진행이 지연된다
  • 버그 수정과 기능 추가에 더 많은 시간이 소요된다
  • 프로젝트 후반부에 기술적 부채가 누적되어 출시 일정이 위험해진다
  • 새로운 팀원의 적응 기간이 길어진다
야근시키게 만드는 동료가 나쁜 놈이다... 나쁜 놈이 되지 말자😂

 

💡 특히 코어 시스템과 기반 클래스들은 최대한 깔끔하게 설계하는 데 시간을 투자하자. 초기에 시간을 더 투자하면 나중에 많은 시간을 절약할 수 있다.


🚮24가지 나쁜 코드 유형과 개선 방법

1. 기이한 이름

모호한 이름은 코드 이해를 어렵게 만든다. 변수와 함수는 목적과 의도가 명확한 이름을 사용하자.

나쁜 예:

float a; // 플레이어 체력
void p(); // 데미지 처리 함수

좋은 예:

float playerHealth;
void ProcessDamage();
💡 실무 팁: 팀 프로젝트에서는 네이밍 컨벤션(명명 규칙)이 매우 중요하다. 예를 들어 변수는 소문자로 시작하는 등 (camelCase)의 규칙을 따르자.

2. 중복 코드

똑같은 코드가 여러 곳에 복사-붙여넣기되어 있으면 하나를 수정할 때 다른 곳도 모두 찾아 수정해야 한다...🤮

중복된 기능은 하나의 함수로 묶어서 관리하자. ( 동일한 로직이 필요하면 반드시 함수화하. )

💡실무 팁: 언리얼 엔진에서는 BlueprintFunctionLibrary 클래스를 활용해 자주 사용하는 기능을 모듈화할 수 있다. 이렇게 만든 함수는 C++과 블루프린트 양쪽에서 모두 활용 가능하다.

3. 긴 함수

하나의 함수는 하나의 일만 담당하도록 합니다(단일 책임 원칙).

나쁜 예:

void GameManager::Update()
{
    // 플레이어 입력 처리
    // ... 20줄의 코드
    
    // 적 AI 업데이트
    // ... 50줄의 코드
    
    // 물리 시스템 업데이트
    // ... 30줄의 코드
}
 

좋은 예:

void GameManager::Update()
{
    ProcessPlayerInput();
    UpdateEnemyAI();
    UpdatePhysicsSystem();
}
💡 실무 팁:  언리얼 엔진의 Tick 함수는 특히 짧게 유지해야 한다. 매 프레임마다 실행되므로 긴 Tick 함수는 성능에 직접적인 영향을 미친다... 가능하면 이벤트 기반 방식으로 코드를 구성하자.

즉, 실행되는 코드를 최소한으로 유지하고, 꼭 필요한 작업만 처리해야 한다는 뜻이다. 그리고 "이벤트 기반 방식"이라는 것은 특정 조건이나 상황이 발생했을 때만 코드를 실행하도록 설계하자는 의미...!

➡️ 예를 들어 델리게이트(Delegate)는 이벤트 기반 방식의 대표적인 구현 방법 중 하나.
캐릭터가 특정 트리거 영역에 들어갔을 때 어떤 동작을 실행하고 싶다면, Tick 함수에서 매번 거리 계산을 하는 대신 델리게이트를 이용해서 트리거 이벤트를 발생시키는 방식으로 처리하면 된다. 델리게이트는 블루프린트와도 연동이 잘되므로 비프로그래머도 활용하기 쉽다는 장점이 있다.

4. 긴 매개변수 목록

함수에 너무 많은 매개변수가 있으면 사용하기 어렵고 의도가 불분명해진다.

관련된 매개변수는 구조체나 클래스로 묶어서 전달하자.

개선 전:

void CreateEnemy(FString Name, float health, float damage, float speed, FVector location, FRotator rotation, bool isElite, int level);
 

개선 후:

// Enemy 데이터 구조체 정의
struct FEnemyData
{
    FString Name;
    float Health;
    float Damage;
    float Speed;
    bool bIsElite;
    int Level;
	
    // 생성자
    FEnemyData(FString InName, float InHealth, float InDamage, float InSpeed, bool InIsElite, int InLevel)
        : Name(InName), Health(InHealth), Damage(InDamage), Speed(InSpeed), bIsElite(InIsElite), Level(InLevel)
    {}
};

// ~

void CreateEnemy(FEnemyData enemyData, FTransform spawnTransform);
 

5. 전역 변수 남용

전역 변수는 편리하지만 코드의 의존성과 복잡성을 높인다. 전역 변수는 모든 곳에서 접근 가능하기에 프로젝트가 커질수록 관리가 어려워진다. 의도치 않은 곳에서 에러가 발생할 가능성이 커지기 때문이다. 특히 디버깅 중에 어떤 코드가 전역 변수를 변경했는지 추적하는 것은 굉장히 번거로울 수 있다.

➡️ 언리얼 엔진에서는 Subsystem을 활용해 전역 상태를 관리한다.

: Subsystem??

더보기

Subsystem은 언리얼 엔진의 특정 클래스에 기반한 시스템으로, 게임의 전역 상태나 동작을 체계적으로 관리할 수 있도록 설계되었다. Subsystem은 여러 종류가 있으며, 각각의 라이프사이클이 특정 게임 객체에 맞춰져 있다!!

즉, Subsystem은 엔진 객체의 라이프사이클에 맞게 자동으로 생성, 소멸되므로 메모리 관리가 용이하고 의존성을 줄일 수 있는 것이다.

 

주요 Subsystem 유형은 다음과 같다:

1) GameInstance Subsystem : 게임 전체에 걸쳐 지속되는 상태 관리. (예: 게임 설정, 사용자 데이터, 글로벌 상태)

2) GameState Subsystem : 현재 게임 세션의 상태 관리. (예: 점수판 데이터, 경기 진행 상태)

3) PlayerState Subsystem : 각 플레이어의 상태를 관리 (예: 플레이어 통계, 개인 설정)

4) LocalPlayer Subsystem : 로컬 플레이어의 상태와 입력을 관리(예: UI 상태, 입력 컨트롤)

💡 실무 팁: GameInstance, GameState, PlayerState 등 언리얼의 기본 클래스를 활용하거나, Subsystem을 생성하여 전역 변수 사용을 최소화하면 테스트와 디버깅이 훨씬 쉬워진다.
 

6. 가변 데이터

자주 변하는 데이터는 예측하기 어렵고 오류 발생 가능성이 높다.

가능하면 불변 데이터를 사용하고, 수정이 필요할 때만 새 객체를 생성하자.

💡 실무 팁: 언리얼 엔진에서 구조체(struct)는 값 복사 방식으로 작동하므로, 데이터 무결성 유지에 유용하다. 반면 클래스는 참조 방식이라 여러 곳에서 변경될 수 있어 주의가 필요하다. ➡️ 언리얼이 USTRUCT를 지원하는 이유

7-24. 그외 악취나는 코드와 개선 방법

7. 뒤엉킨 변경 : 하나의 모듈이 여러 이유로 수정되는 경우 ➡️ 책임에 따라 모듈을 명확히 분리하자.

8. 샷건 수술: 하나의 변경으로 여러 모듈을 수정해야 하는 경우 ➡️ 연관된 기능은 한 곳에 모아 응집도를 높이자.

9. 기능 편애 : 함수가 자신의 클래스보다 다른 클래스의 데이터를 더 많이 사용하는 경우 ➡️ 메서드를 적절한 클래스로 이동시키자.

10. 데이터 뭉치: 항상 같이 다니는 데이터는 클래스나 구조체로 묶어 관리하자

11. 기본형 집착: 모든 것을 int, float, string 등의 기본 타입으로만 해결하려는 경향 ➡️ 의미 있는 객체(클래스 또는 구조체)를 만들어 활용하자.

12. 반복되는 스위치문: 같은 조건의 스위치문이 여러 곳에 있다면...! 다형성(polymorphism)을 고려해서 설계해보자. 언리얼 엔진의 인터페이스 시스템(UInterface)은 이런 문제를 해결하기 좋다.

13. 반복문 속의 복잡한 로직 : 반복문 내부에 복잡한 로직이 있으면 성능 저하가 심해지고 코드 이해도 어려워진다.

14. 성의 없는 요소 : 불필요한 코드나 구조는 코드의 복잡성만 증가시킨다 ➡️ 사용되지 않는 변수, 함수, 클래스는 과감히 제거하자. 의미 없는 주석이나 빈 메서드도 삭제하자.

15. 추측성 일반화 : 미래에 필요할 것 같아 미리 만들어 놓은 기능은 대부분 쓸모없어진다... 

  • 현재 필요한 기능에 집중! (YAGNI - "You Ain't Gonna Need It").
  • 코드를 간단하게 유지! (KISS - "Keep It Simple, Stupid").
  • 중복을 피하자! (DRY - "Don't Repeat Yourself").

16. 임시 필드 : 클래스 내에 특정 상황에서만 사용되는 필드가 있으면 코드 이해가 어려워진다.

17. 메시지 체인 : 객체의 내부 구조가 외부로 너무 많이 노출되는 경우(디미터의 법칙 위반) ➡️ 델리게이트(Delegate)와 이벤트(Event) 시스템을 활용하여 객체 간 직접적인 참조를 줄일 수 있다.

18. 중재자 (Middle Man) : 클래스가 단순히 다른 클래스로 요청을 전달하는 역할만 한다면 불필요한 복잡성을 야기한다 ➡️ 언리얼 엔진의 액터 컴포넌트 시스템을 잘 활용하면 불필요한 중재자 없이 모듈화된 구조를 만들 수 있다.

19. 내부자 거래 : 모듈 간 데이터 교류가 너무 많으면 결합도가 높아진다 ➡️ 언리얼 엔진의 인터페이스(UInterface) 시스템을 활용하여 모듈 간 통신을 추상화하고 결합도를 낮출 수 있다.

20. 거대한 클래스 : 너무 많은 책임을 갖는 클래스는 이해와 유지보수가 어렵다 ➡️ 단일 책임 원칙(SRP)에 따라 클래스를 여러 개의 작은 클래스로 분할한다.

21. 서로 다른 인터페이스의 대안 클래스들 : ➡️ 유사한 기능을 가진 클래스들은 공통 인터페이스로 묶어 일관성을 유지하자

22. 데이터 클래스 : ➡️ 언리얼 엔진의 구조체(USTRUCT)를 활용하여 순수한 데이터 객체를 표현하고, 이를 처리하는 로직은 별도의 클래스나 컴포넌트에 구현한다.

23. 상속 포기 : 상속을 받았지만 부모 클래스의 기능을 사용하지 않는 경우 ➡️ 컴포넌트 시스템과 인터페이스를 조합하여 유연한 구조를 만들 수 있다.

24. 주석 : 과도한 주석은 코드 자체의 가독성 문제를 숨긴다 ➡️ 코드 자체로 의도가 명확히 드러나도록 작성하자.


💎언리얼 엔진 개발자를 위한 핵심 클린 코드 팁 

컴포넌트 기반 아키텍처 활용

언리얼 엔진은 컴포넌트 시스템을 제공한다. 이를 적극 활용하여 기능을 분리하고 재사용성을 높이는 것이 좋다!

// 캐릭터에 모든 기능을 넣는 대신 컴포넌트로 분리
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
UInventoryComponent* InventoryComponent;

UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
UCombatComponent* CombatComponent;

SOLID 원칙 적용

SOLID 원칙 언리얼 엔진 적용 방법
단일 책임 원칙 하나의 클래스/컴포넌트는 하나의 책임만 갖도록 설계
개방-폐쇄 원칙 블루프린트로 확장 가능한 C++ 기반 클래스 설계
리스코프 치환 원칙 상속 계층에서 자식 클래스가 부모의 동작을 보장
인터페이스 분리 원칙 UInterface를 활용한 작은 단위의 인터페이스 설계
의존성 역전 원칙 구체적 클래스보다 인터페이스에 의존하는 구조
💡 실무 팁:  경력이 쌓일수록 단순한 코딩보다 설계가 더 중요해진다. 핵심 시스템을 설계할 때는 반드시 클래스 다이어그램을 그려보고, 의존성과 확장성을 검토하자. 시간이 조금 더 걸리더라도 이 과정이 프로젝트 후반에 큰 차이를 만든다!

성능과 클린 코드의 균형

게임 개발에서는 코드 가독성과 성능 사이에 균형이 중요하다.

핫 패스(Hot Path, 자주 실행되는 경로)에서는 성능 최적화에 더 비중을 두고,

그 외 부분에서는 가독성과 유지보수성에 중점을 두면 좋다고 한다.

모든 코드를 최적화하려 하지 말자. 프로파일링을 통해 실제 병목 지점을 찾아 집중적으로 최적화하는 것이 효율적이다.

리팩토링을 두려워하지 말자

기능이 제대로 동작한다고 해서 코드가 완성된 것이 아니다.

💡실무 팁:  작은 기능 단위로 리팩토링하고, 테스트 코드나 테스트 레벨을 작성하여 기능이 정상 동작하는지 확인하자.

"코드를 단순하고 직관적으로 유지하는 것이 프로젝트 성공의 핵심 전략이다."


🟣오늘의 옵시디언 현황

 

'Dev. > 기타 개발 관련' 카테고리의 다른 글

GitHub Desktop 복구 과정에 대해...🥲  (0) 2025.02.21
[TIL_250218] Git LFS 사용 시 주의 사항  (0) 2025.02.18
[TIL_250217] Git LFS와 언리얼 엔진  (0) 2025.02.17
'Dev./기타 개발 관련' 카테고리의 다른 글
  • GitHub Desktop 복구 과정에 대해...🥲
  • [TIL_250218] Git LFS 사용 시 주의 사항
  • [TIL_250217] Git LFS와 언리얼 엔진
raindrovvv
raindrovvv
raindrovvv 님의 블로그 입니다.
  • raindrovvv
    raindrovvv 님의 블로그
    raindrovvv
  • 전체
    오늘
    어제
    • 분류 전체보기 (101)
      • Dev. (94)
        • UE 언리얼 엔진 (49)
        • Unity 유니티 (0)
        • Wwise 와이즈 (7)
        • 게임 네트워크 (8)
        • 그래픽스 Graphics (22)
        • 프로젝트 (4)
        • 기타 개발 관련 (4)
      • Computer Science (0)
        • 하드웨어 HW (0)
        • 소프트웨어 SW (0)
        • 통신 (0)
        • 데이터 (0)
      • 블로그 (3)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    게임네트워크
    게임음향
    unrealengine
    그래픽스
    언리얼
    게임개발
    UE
    네트워크
    Wwise
    게임
    Git
    AI
    머티리얼
    셰이더
    깃
    게임사운드
    언리얼엔진
    TA
    고라니
    Unreal
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
raindrovvv
🫧Clean Code :: 클린 코드 가이드
상단으로

티스토리툴바