언리얼 코딩표준

후이재·2021년 6월 27일
1

언리얼에서 개발을 진행중이다보니 언리얼 코딩표준을 따라야한다.
일찍이 정리해두고 알아뒀어야하는데 이제 정리하게 된다. 시니어개발자님이 문서를 알려주셨다 ㅎㅎ
나는 쓰면서 알아가는 공부가 잘 되니 내용이 거의 고대로 있더라도 적어내려가보겠다.

작명규칙

  • 첫 글자는 대문자, 단어사이 공백 없이
  • 유형이름은 변수 이름과 구분을 위해 접두사를 붙임
    - UObject 상속은 U로 시작
    • AActor 상속은 A로 시작
    • SWidget 상속은 S로 시작
    • 추상 인터페이스는 I로 시작
    • Enum 은 E
    • Boolean 은 b
    • 그 외 클래스는 대부분 F로 시작
    • Typedef 시에는 적당한 유형을 붙이자
  • 유형과 변수명은 명사
  • 메서드 이름은 동사로 설명(하는 일 또는 반환값)
    - CheckValidity, GetMyScore
  • 명확하고 애매하지 않게! 과도한 축약을 피하자
  • 변수는 한 줄에 하나만.// 설명하기 좋도록
  • bool 반환하는 함수의 경우, true/false 질문을 한다.
    - IsVisible() SouldClearBuffer()
  • 프로시져(반환값이 없는 함수)는 강한 동사 뒤에 오브젝트를 붙여쓴다.
  • 파라미터
    - 출력할 것으로 기대되는 경우 Out 접두사 붙임
    • 입력으로 기대되는 경우 In 접두사 붙임
    • Boolean 인데 In/Out 이라면 bOutResult
  • 반환값
    - 반환값이 있는 경우, 반환값에 대한 설명이 필요함
float TeaWeight;
int32 TeaCount;
bool bDoesTeaStink;
FName TeaName;
FString TeaFriendlyName;
UClass* TeaClass;
USoundCue* TeaSound;
UTexture* TeaTexture;

코멘트

  • 가장 중요한 것은 코멘트가 없어도 설명이 되는 코드 작성하기
    - 변수명, 함수명 잘 작성하라는 뜻
    - 축약해서 코드를 작성하고 코멘트를 상세하게 작성하지 말라는 뜻
  • 설명을 위한 코멘트를 작성하자. 보이는 그대로를 알려주는 코멘트를 작성은 할 필요없음
  • 그렇다고 모순되는 코드를 작성하고 코멘트를 달 생각은 말자. 코드를 모순되게 짜면 안됨

Const 정확도를 맞추자

  • 함수 인수가 수정되지않는 경우 -> 함수 인자에 const
  • 함수가 오브젝트 수정하지않는 경우 -> 함수 자체에 const
  • 루프에서 컨테이너에 대한 수정하지 않아 const 사용해서 반복처리 -> for(const Fstring ...)
  • 포인터에 const: T* const ptr;
  • 포인터가 아닌 자체에 const: const T* ptr;
  • 반환형에 const 를 붙이지 마라
    - const 배열로의 레퍼런스, const 배열로의 포인터는 괜찮음
    • const 배열, const 배열로의 const 반환은 안됨

코맨트 포맷

  • JavaDoc 기반 시스템 따라 문서 제작하는 Unreal Engine(Doxygen이 아닐까?)
  • 포맷에 따라 주석 작성하면 문서화에 도움이 됨
  • 간단한 설명, param, return 등을 적는다.
  • // Todo: Doxygen과 관련된 글 쓰기

최신 C++ 언어 문법

C++14 언어 기능을 활용
지원되는 C++컴파일러 기능이 존재하니, 그 외에는 사용을 삼가/조심 해야한다.

static_assert

: 컴파일 시간 assert 가 필요한 경우 사용.

  • assert 는 프로그램 수행 중 유지되어야 하는 부분을 검증할 때 사용된다.(절대 음수면 안되는 경우)
  • 그 중 static_assert는 컴파일 타임에서의 검증을 위해 사용된다.
template <class T>
void my_assert(T& a)
{
    static_assert(std::is_copy_constructible<T>::value,
        "Copy constructure is unavailable");
}

override 및 final

: 사용을 권장하는 키워드!

  • override 는 하위 클래스에서 override한 가상함수에 붙인다. 이것은 오버라이딩한 가상함수다! 알려주는 것
  • fianl 은 더이상 가상함수의 override를 허용하지 않겠다는 뜻. 그 함수를 재정의하는 마지막 하위 클래스에서 사용!

nullptr

: 모든 경우 사용!

  • NULL 매크로 대신 nullptr을 사용해야한다.
  • 한 가지 예외: C++/CX 빌드의 nullptr은 managed null 레퍼런스 유형
  • 그 외에는 거의 호환되긴 하지만 호환성을 위해서는 더 일반적인 decltype(nulltype) 대신 TYPE_OF_NULLPTR 사용!

auto 키워드

: 몇 가지 예외를 제외하고 C++에서 auto는.. 안된다. 초기화 유형은 명시!

  • 괜찮은 경우
    1. 변수에 람다를 바인딩 해아 하는 경우
    2. 이터레이터 변수의 경우(가독성을 위해)
    3. 템플릿 코드에서 표현식 유형 쉽게 식별하기 위해
  • 반드시, 읽는 사람이 무슨 유형인지 명확하게 알 수 있어야한다!
  • auto 를 쓰더라도 const, &, * 를 정확하게 사용해야한다.

범위 기반 for

: 유지보수 향상에 도움되므로 추천!

for(TPair<FString, int32>& Kvp: MyMap)
{
	// do something
}

람다 및 무명 함수

: 자유롭게 사용 가능!

  • 범용 알고리즘의 술부에서 많이 사용됨.
  • 람다의 문서화는 일반 함수와 같은 방식으로 문서화 필요
  • 자동캡처보다 수동캡처가 좋음. 퍼포먼스를 위해
  • 자동캡처는 묵시적으로 this를 맵처
  • 반환형을 명시해주는 것이 좋음

Enum

: 구식 네임스페이스 enum 을 대체하여 사용해야 함

UENUM()
enum class ECocoFood : unit8
{
	duck,
    lamb
}
UPROPERTY()
ECocoFood CocoFood;
  • 플래그로 사용되는 Enum 클래스는 새로운 ENUM_CLASS_FLAGS(EnumType) 매크로 사용 필요

이동 시맨틱

: 최적화를 위해 MoveTemp 사용 가능

  • 컨테이너 유형 TArray, TMap, TSet, FString 에는 move생성자와 move 할당 연산자가 있음(lvalue <-> rvalue)
  • UE4에서는 MoveTemp를 통해 명시적으로 실행 가능
  • 이는 임시 복사하는 비용이 없어 표현상의 이득이 됨

디폴트 멤버 이니셜라이저

: 클래스 자체 내 클래스 기본 값을 정의하는 데 사용 가능

  • 클래스의 멤버변수를 선언하는 동시에 초기화 하는 경우를 말함
  • 엔진 코드 보다는 게임 코드 쪽에 적합
UPROPERTY()
int32 CocoAge = 13;
  • 장점
    - 생성자에서 이니셜라이저 복제할 필요 없음
    - 초기화 순서 선언순서 섞이지않음
    - 가독성 유지보수성에 좋음
  • 단점:
    - 기본 값 변경 시 종속 파일 리빌드 필요
    • 헤더는 엔진 패치 릴리즈에서 변경 불가.. 수정 제한될 수 있음
    • 모든것을 초기화할 수 없음. 베이스클래스라던가..
    • 여기저기 나눠서 초기화 하면 오히려 가독성 유지보수성이 좋지 않을 수 있음

서드 파티 코드

: 엔진에서 사용하는 라이브러리에 코드를 수정할 때 //@UE4 코멘트로 변경한 이유에 대해 태그 달기

코드 포맷

대괄호

  • 새 줄에 대괄호 넣는 것이 관행임!!!

If-Else

  • 각 실행 블록은 대괄호로 묶어야함
  • 안 묶었다가 의도치않은 실수를 만들 수 있음..

탭 및 들여쓰기

  • 실행 블록별로 들여쓴다.
  • 줄 시작부분 공백은 스페이스 아닌 탭으로!

Switch 문

  • 다음 케이스로 넘어가는 지 명시적으로 밝혀주기! 각 경우마다 break넣기
  • break 넣지 않으면, fall through 코멘트 달기 (// falls through)
  • default case 는 항상! 그 뒤에 case를 추가할 때를 대비하여 break도 넣는다.

네임스페이스

  • 언리얼 코드는 글로벌 네임스페이스에 둘러싸여 있지 않으니, 서드파티 코드 사용 시 전역 범위에서 충돌 일어나지 않게 주의!
  • Using 선언
    - 전역 범위에서는 안됨. 다른 네임스페이스 안 또는 함수 안에서는 괜찮음
  • 언리얼 헤더 툴에는 네임스페이스가 지원되지 않음 -> UCLASS, USTRUCT 등 정의 시에는 사용 불가

물리적 종속성

  • 파일 이름은 접두사 붙이지 말기 Scene.cpp
  • 모든 헤더는 #pragma once 디렉티브 (지시자)로 복수의 include 방지
  • include 시에는 전부 include 보다는 특정 부분을 inlcude 하는것이 좋음.
  • 모듈에는 Private 와 Public 소스 디렉터리가 있음.
    - 다른 모듈이 필요로 하는 정의는 Public
    • 그 외의 모든 것은 Private
  • 인라인 함수 너무 많이 사용하지 말자. 사용하지 않는 파일도 강제로 리빌드 시킴. 프로파일링 이득이 있는 경우에만 쓰자.
  • FORCEINLINE 사용에 있어서는 보수적이어야 한다. // Todo: ForceInline 조사
    - 모든 코드와 로컬 변수는 호출하는 함수로 확장 -> 큰 함수에서 발생하는 것과 동일한 빌드 시간 문제 발생

캡슐화

  • 보호 키워드로 캡슐화
  • 맴버는 public/protected 인터페이스 일부아니면 private!
  • 더이상 파생을 원하지 않으면 final 사용

일반적인 스타일 문제

  • 종속성은 최소화
  • 메서드는 가급적 하위 메서드로 분할. 이름 잘 지어둔 다수의 하위 메서드를 연속적으로 호출하는것이 더 이해하기 쉬움
  • 함수 선언이나 함수 호출 위치에서 함수 이름과 인수 목록 선행되는 괄호 사이 공백 금지
  • 파일 끝에 빈 줄 하나
  • 스트링 리터럴 주변에는 항상 TEXT 매크로 사용!- // Todo: TEXT 매크로 설명
  • 루프에서 동일 적업 반복 피하기
  • 핫 리로드 기능 염두. 리로드동안 그대로 남아있을 것에만 statics 사용 // Todo: 핫 리로드 조사
  • 복잡한 표현식은 중간변수를 사용하여 단순화! (바로 bool 리턴하는 함수를 표현식에 넣는 것이 아니라 변수에 저장한 후 표현식에 넣기)
  • 포인터와 레퍼런스 공백은 딱 한칸만 FShaderType Ptr (O)/ FShaderType Ptr (X)
  • 변수 음영(shadowed) 허용안됨(멤버변수와 같은 이름인 변수를 사용하는 것)
  • 함수 호출에서 익명 리터럴 피하기
    - 리터럴이란, 정해진 값을 말하는 것
    • 함수호출에 값을 바로 넣는지 않아야한다는 것(FeedCoco(1, TEXT("코코야 밥먹자"))안된다는 것)

API 디자인 지침

  • bool 함수 파라미터가 많아지면, enum을 사용하자
  • 너무 긴 함수 파라미터는 struct 사용을 고려해보자
  • bool 및 FString 사용한 함수 오버로드는 피하자
  • I 접두사를 가진 클래스는 항상 추상형이어야한다. 순수가상함수만 있어야하며, 멤버변수 있음 안됨.
  • 오버라이딩 메서드 선언시에는 virtual, override 키워드 사용 (virtual void F() override;)

플랫폼별 코드

  • 플랫폼별 코드는 적합한 이름의 하위 디렉터리 아래 플랫폼별 소스 파일에 추상화 및 구현하야함
Source/Runtime/Core/Private/[Platform]/[Platform]Memory.cpp
  • 하드웨어 추상 층 확장해서 static 함수 추가
profile
공부를 위한 벨로그

0개의 댓글