반응형
[Unreal/C++] 코딩 표준 지침
이 글은 언리얼 엔진 문서 기반으로 작성되었습니다.
코딩 규칙의 중요성
- 소프트웨어가 수명을 지속하는 동안 들어가는 경비의 80%는 유지 보수비이다.
- 원저자(초기 개발자)가 소프트웨어의 수명이 다할 때까지 관리하는 일은 거의 없다.
- 코딩 규칙을 따랐을 경우 한층 읽기 쉽도록 해주므로, 보다 빨리 그리고 철저하게 이해할 수 있다.
이에 따라 작성하는 사람 보다 읽는 사람을 염두에 두고 체계를 잡아야 한다.
읽는 사람 대부분은 클래스의 공용 인터페이스를 사용하기 때문에,
- 읽기 쉬운 public을 상단에 선언하고 private를 하단에 작성한다.
작명 규칙
- 각 단어의 첫 글자는 대문자로 작성 한다.
- 단어 사이에 (보통)공백을 띄우지 않는다.
- 유형과 변수명은 명사로 작성한다.
- 메서드 이름은 동사로 작성한다. (메서드가 하는 일이나 반환값을 설명한다.)
- 변수 이름과 구분하기 위해 유형 이름을 대문자 한 글자로 나타내는 접두사를 붙인다. (C# 접두사 생략)
- 이름은 명확하고 서술적으로 표현한다.
- 과도한 축약은 피한다.
- 모든 변수에 대한 설명을 코멘트로 붙일 수 있도록 한 번에 하나씩 선언한다.
- 함수 파라미터 중 레퍼런스로 전달된 이후 출력될 경우 이름 앞에 "Out" 접두사를 붙이는 것을 권고한다.
접두사
클래스 | 접두사 |
템플릿(Template) | T |
UObject 상속 | U |
AActor 상속 | A |
SWidget 상속 | S |
추상 인터페이스 | I |
Enum (열거형) | E |
Boolean | b |
그 외 대부분 | F |
일부 서브 시스템 | 예외 접두사 |
구조체 Typedef | F |
UObject Typedef | U |
템플릿 Typedef | 적절하게 |
이식 가능한 (Portable) C++ 코드
- bool for boolean values (NEVER assume the size of bool). BOOL will not compile.
- TCHAR for a character (NEVER assume the size of TCHAR).
- uint8 for unsigned bytes (1 byte).
- int8 for signed bytes (1 byte).
- uint16 for unsigned "shorts" (2 bytes).
- int16 for signed "shorts" (2 bytes).
- uint32 for unsigned ints (4 bytes).
- int32 for signed ints (4 bytes).
- uint64 for unsigned "quad words" (8 bytes).
- int64 for signed "quad words" (8 bytes).
- float for single precision floating point (4 bytes).
- double for double precision floating point (8 bytes).
- PTRINT for an integer that may hold a pointer (NEVER assume the size of PTRINT).
C++의 int와 unsigned int 유형은 플랫폼에 따라 크기가 변할 수 있다.
정수 범위가 중요하지 않은 경우 사용해도 무방하지만, 명시적으로 크기가 정해진 경우 직렬화(Serialized) 또는 복제 형식(Replicated Formats)으로 사용해야 한다.
코멘트 지침
- 자체적으로 설명이 되는 코드를 작성한다.
- 사용자가 읽었을 경우 이해할 수 있게 작성한다.
- 나쁜 코드에 코멘트를 자세하게 달지 말고 이해하기 쉬운 코드로 다시 작성한다.
- 모순된 코멘트를 작성하지 않는다.
- 코드 변경시 주석도 함께 업데이트 한다.
- 함수의 코멘트는 공개적으로 선언되는 곳에 포함시킨다.
Const 정확도
Const는 컴파일러 지시어인 만큼 문서화되어 있으므로 모든 코드는 const가 정확하도록 노력해야 한다.
- 함수에 의해 인수가 수정되지 않는 경우 Const로 전달한다.
- 객체를 수정하지 않는 경우 함수를 Const로 지정한다.
- 반복문에서 컨테이너를 수정하지 않는 경우 Const로 반복한다.
- 포인터가 변경되지 않는 경우 Const로 지정한다. (T* const Ptr = ...;)
- 반환형에는 Const를 사용하지 않는다.
C++ 언어 구문
Unreal에서 지원하는 C++ 구문은 이식성을 고려하여 컴파일러의 호환성을 위해 대부분 C++14 기능을 활용하고 있다.
그러므로 모든 컴파일러가 최신 구문을 지원하기 전까지는 업데이트 될 가능성이 적다.
아래에 나열한 지원 되고 있는 C++ 언어 구문 외 최신 C++ 언어 구문은 사용을 지양해야한다.
- static_assert : 컴파일 시간 어설션이 필요한 경우 사용
- override 및 final : 사용을 권고한다.
- nullptr : 모든 경우 NULL 매크로 대신 nullptr을 사용한다.
- auto : 명확성을 위해 최대한 사용을 하지 않는다.
- 범위 기반 for : 코드의 가독성과 유지보수에 도움된다.
- 람다 및 무명 함수 : 람다(Lamda)는 자유롭게 사용 가능하다.
- enum class : enum을 대체하여 사용해야 한다.
- MoveTemp : 주요 컨테이너 유형에는 Move 할당 연산자가 있지만, MoveTemp를 통해 명시적으로 실행 가능하다.
- Initializers : 선언과 동시에 초기화 하는 것은 장점과 단점을 고려해 사용한다.
auto 키워드를 사용해도 괜찮은 경우
- 변수에 람다를 바인딩해야 하는 경우
- Iterator 변수의 경우
- 템플릿 코드에서 표현식의 유형을 쉽게 식별할 수 없는 경우
람다 및 무명함수 사용시 주의할 점
- 스테이트풀 람다는 함수 포인터에 할당될 수 없다.
- 일반 함수와 같은 방식으로 문서화를 진행한다.
- 자동 캡처보다 명시적 캡처가 좋다. ([&] 및 [=]) (가독성, 유지보수, 성능의 이유로 중요하다.)
- 포인터 참조 캡처와 값 캡처가 때때로 허상 참조를 유발할 수 있다.
- 값 캡처는 유예식이 아닌 람다에 사본을 만드는 경우 성능 문제가 있을 수 있다.
- 잘못 캡처된 UObject 포인터는 가비지 콜렉터에 보이지 않는다.
- 자동 캡처는 멤버 변수가 참조된 경우, 묵시적으로 this를 캡처한다.
- 결과를 반환할 경우 명시적으로 표현한다.
기본 멤버 초기화 장단점
장점
- 여러 생성자에 걸쳐 이니셜라이저를 복제할 필요가 없다.
- 초기화 순서와 선언 순서가 섞일 일이 없다.
- 멤버 유형, 프로퍼티 플래그, 기본 값이 모두 한 곳에 있어, 가독성과 유지보수성에 좋다.
단점
- 기본 값을 변경하면 모든 종속 파일을 리빌드해야 한다.
- 헤더는 엔진 패치 릴리즈에서 변경할 수 없으므로, 가능한 픽스 종류가 제한될 수 있다.
- 모든 것을 초기화시킬 수는 없다.
- 여기저기 나눠서 초기화 시 가독성과 유지보수성에 좋지 않다.
서드 파티 코드
엔진에서 사용하는 라이브러리의 코드를 수정할 때 변경 사항에
//@UE4 주석과 함께 변경 이유에 대한 설명을 태그해야 한다.
코드 포맷
대괄호 : 줄바꿈을 한 후 대괄호를 넣는다.
If-Else : 각 블록은 대괄호로 묶는다.
들여쓰기 : 실행 블록 별로 코드를 들여쓴다.
Tab : 공백을 사용하기 보다 Tab을 주로 이용한다.
Switch : 다음 케이스로 넘어가는지 명시적으로 밝히고, break를 넣거나 fall through 코멘트를 작성한다.
네임 스페이스 규칙
- 언리얼 코드는 현재 글로벌 네임스페이스에 둘러싸여 있지 않다.
- 서드 파티 코드를 사용하거나 포함할 때는 전역 범위에서 충돌이 일어나지 않도록 주의를 기울여야 한다.
- Using 선언은:
- cpp 파일에서도 전역 범위에 선언을 넣지 않는다. ("unity" 빌드 시스템에 문제가 발생할 수 있다.)
- 다른 네임스페이스 안이나 함수 본문 안에서는 using 선언을 넣어도 괜찮다.
- 네임스페이스 안에 using 선언을 넣는 경우, 해당 네임스페이스 다른 항목에도 적용된다.
- 오직 위 규칙을 따를 때 using 선언을 사용해도 안전합니다.
- 참고로 앞서 선언된 형은 각각의 네임스페이스 안에서 선언해 줘야 합니다. 그렇게 하지 않으면 링크 오류가 납니다.
- 한 네임스페이스 안에 다수의 클래스/유형을 선언하면, 다른 전역 범위의 클래스에서 사용하기가 어려울 수 있다.
- using 선언을 사용해서 네임스페이스 안의 특정 변수만 범위에 대한 별칭을 지정할 수 있다.
- 언리얼 헤더 툴에는 네임스페이스가 지원되지 않으므로, UCLASS, USTRUCT 등을 정의할 때는 사용할 수 없다.
물리적 종속성
- 파일 이름은 가급적 접두사를 붙이지 않는 것이 좋다.
- 모든 헤더는 #pragma once 디렉티브(지시자)로 복수의 include 를 방지해야 한다.
- 되도록 물리적 결합을 최소화한다.
- 다른 헤더의 표준 라이브러리 헤더를 포함하지 않는다.
- 헤더를 포함하는 대신 전방 선언을 사용할 수 있다면 사용한다.
- 포함할 때는 최대한 세밀하게 작성하세요. 전체 헤더 대신 필요한 기능 위주의 헤더 파일을 포함시킨다.
- 세부적인 포함을 더 쉽게 만들기 위해 필요한 모든 헤더를 직접 포함시킨다.
- 포함하는 다른 헤더에 의해 간접적으로 포함되는 헤더에 의존하지 않는다.
캡슐화
- 접근 지정자를 적극적으로 활용한다.
- 일부를 제외하고 대부분 private로 보호한다.
- 더 이상 파생시킬 클래스가 아닌 경우 final을 사용한다.
일반적인 스타일 문제
- 종속성을 최소화한다.
- 함수는 가급적 하위 함수로 분할한다. 다수의 하위 메서드를 이해하기 더 수월하다.
- 함수 선언이나 호출 위치에서 함수의 이름과 인수 목록 앞의 괄호 사이에 공백을 두지 않는다.
- 컴파일러 경고에 주의를 기울인다. #pragma 로 억제시킬 수는 있지만, 이는 정말 마지막 수단이어야 한다.
- 파일 끝에 빈 줄을 하나 추가한다. 모든 .cpp 와 .h 파일은 빈 줄이 있어야 gcc 에 제대로 돌아간다.
- 디버그 코드는 일반적으로 유용하고 잘 다듬어진 상태가 아니라면 체크인하지 말아야 한다.
- 스트링 리터럴 주변에는 항상 TEXT() 매크로를 사용해야 한다.
- 공통된 하위 표현식은 루프 밖으로 빼서 중복 계산을 피한다.
- 핫 리로드 기능을 주의한다. 반복처리 시간을 줄이기 위해 종속성을 최소화한다.
- 복잡한 표현식은 중간 변수를 사용하여 단순화한다.
- 포인터와 레퍼런스의 공백은 그 오른쪽에 딱 한 칸만 둬야한다.
- 변수 음영(shadowed)은 허용되지 않는다.
- 함수 호출에서 익명 리터럴 사용은 피한다.
API 디자인 지침
- bool 함수 파라미터는 피해야 한다. enum class 사용을 권장한다.
- 너무 긴 함수 파라미터 리스트는 피한다. 함수가 파라미터를 많이 받는 경우 전용 구조체 전달을 고려한다.
- bool 및 FString 을 사용한 함수 오버로드는 피한다.
- 인터페이스 (접두사가 "I" 인) 클래스는 항상 추상형이어야 하며, 멤버 변수가 있어서는 안된다.
- 오버라이딩 메서드를 선언할 때는 virtual 및 override 키워드를 사용한다.
플랫폼별 코드
플랫폼별 코드는 항상 적합한 이름의 하위 디렉터리 아래 플랫폼별 소스 파일에 추상화 및 구현해야 한다.
반응형
'Unreal > Manual' 카테고리의 다른 글
Unreal 권장하는 에셋 명명 규칙 (0) | 2023.10.25 |
---|---|
Unreal UPROPERTY 종류 (0) | 2023.10.25 |
Unreal C++ 컴포넌트 추가하기 (0) | 2023.10.24 |
Unreal C++ 헤더 포함 Header Include (0) | 2023.10.24 |
Unreal C++, 라이브 코딩 컴파일 (0) | 2023.10.23 |