이 가이드에서는 유지관리하기가 쉽고 일관된 코드를 작성하는 데 권장되는 표준을 제시합니다. 개발자는 이 가이드라인을 준수하여 코드 가독성을 향상하고, 오류를 줄이며, 협업을 촉진할 수 있습니다. 표준화된 코드 스타일은 프로젝트에 참여하는 현재 및 미래의 개발자 모두가 코드를 쉽게 이해하고 유지관리할 수 있게 하는 데 꼭 필요합니다.
이 가이드는 권장 사항을 제공하지만 선택은 팀에 달려 있습니다.
1. 일반적인 명명 패턴
명명 규칙은 가독성이 높고 유지관리하기 쉬운 코드를 작성하는 데 필수적입니다. 따라서 코드 전체의 명명 스타일에 일관성을 유지하는 것이 좋습니다.
1.1 권장 사항
-
IsX
: 보통 질문을 위한 로직 변수를 명명하는 데 사용됩니다(예: IsEmpty). -
OnX
: 프레임워크에서 호출하는 오버로드 가능한 함수입니다. -
SubscribeX
: X로 명명된 프레임워크 이벤트에 등록하며, 보통 OnX 함수를 전달합니다. -
MakeC
: C 생성자를 오버로드하지 않고 클래스 C의 인스턴스를 만듭니다. -
CreateC
: 클래스 C의 인스턴스를 하나 생성하여 논리 수명을 시작합니다. -
DestroyC
: 클래스 C의 논리 수명을 종료합니다. -
C:c
: 클래스 C의 단일 인스턴스로 작업하는 경우 C라고 명명해도 됩니다.
1.2 비권장 사항
-
타입 이름을 꾸미는 것은 권장되지 않습니다. 예를 들어
thing
으로만 명명하는 것이 좋으며,thing_type
또는thing_class
로 명명하는 것은 바람직하지 않습니다. -
열거형 값을 꾸미는 것은 권장되지 않습니다. 예를 들어
color := enum{COLOR_Red, COLOR_Green}
대신color := enum{Red, Green}
으로 명명하는 것이 좋습니다.
2. 이름
2.1 타입의 경우 lower_snake_case 스타일 사용
타입 이름은 항상 lower_snake_case
여야 합니다. 여기에는 구조체, 클래스, 타입 정의, 특성/인터페이스, 열거형 등 모든 타입이 포함됩니다.
my_new_type := class
2.2 인터페이스는 형용사 스타일 사용
인터페이스는 되도록 printable 또는 enumerable과 같이 형용사를 사용해야 합니다. 형용사가 어울리지 않다고 생각되는 경우 이름에 _interface
를 추가합니다.
my_new_thing_interface := interface
2.3 이 외에는 모두 PascalCase 스타일 사용
이 외의 이름은 모두 PascalCase여야 합니다. 모듈, 멤버 변수, 파라미터, 메서드 등이 모두 여기에 해당됩니다.
MyNewVariable:my_new_type = …
2.4 파라미터 타입
-
파라미터 타입의 이름은 t 또는 thing으로 명명합니다. 여기서 thing은 해당 타입으로 나타내려는 내용을 설명합니다. 예를 들면 다음과 같습니다.
Send(Payload:payload where payload:type)
이 구문은 파라미터화된 데이터Payload
를payload
타입으로 전송하고 있는 것입니다. -
파라미터 타입이 두 개 이상 있는 경우에는
t
,u
,g
같은 단일 문자는 피해야 합니다. -
_t
접미사는 절대 사용하면 안 됩니다.
3. 포맷 지정
코드베이스 전체에서 일관된 포맷을 유지하는 것이 중요합니다. 이렇게 하면 자신과 다른 개발자가 코드를 더 쉽게 읽고 이해할 수 있습니다. 프로젝트에 적합한 포맷 스타일을 선택합니다.
일관성을 유지하는 예로는 다음 공백 추가 포맷 중 하나를 선택하여 코드베이스 전체에서 사용할 수 있습니다.
MyVariable : int = 5
MyVariable:int = 5
3.1 들여쓰기
-
들여쓰기에는 스페이스를 4번 사용하고, Tab은 절대 사용하면 안 됩니다.
-
코드 블록은 중괄호가 아니라 공백을 추가한 들여쓰기 블록을 사용해야 합니다.
my_class := class: Foo():void = Print("Hello World")
- 예외가 있다면
option{a}
,my_class{A := b}
같은 한 줄 표현식을 사용하는 경우입니다.
- 예외가 있다면
3.2 스페이스
- 컨텍스트에 맞게 코드를 간소화하는 것이 힘든 경우 연산자 앞뒤에 공백을 사용합니다. 이때, 소괄호를 사용하여 연산 순서를 명시적으로 정의합니다.
MyNumber := 4 + (2 * (a + b))
-
괄호의 시작과 끝에는 공백을 추가하면 안 됩니다. 괄호 내에 여러 개의 표현식이 있는 경우에는 단일 공백으로 구분해야 합니다.
enum{Red, Blue, Green} MyObject:my_class = my_class{X := 1, Y := 2} Vector := vector3{X := 1000.0, Y := -1000.0, Z := 0.0} Foo(Num:int, Str:[]char)
- 식별자와 타입은 함께 두고, 할당
=
연산자 앞뒤에 공백을 추가합니다. 타입 정의 및 상수 초기화 연산자(:=
) 앞뒤에 공백을 추가합니다.MyVariable:int = 5 MyVariable := 5 my_type := class
- 함수 시그니처의 괄호, 식별자 및 타입 공백 추가의 경우에도 동일한 권장 사항을 따릅니다.
Foo(X:t where t:subtype(class3)):tuple(t, int) = (X, X.Property) Foo(G(:t):void where t:type):void Const(X:t, :u where t:type, u:type):t = X
3.3 줄바꿈
-
줄바꿈을 삽입하려면 공백이 추가된 여러 줄 형식을 사용합니다.
권장 사항 MyTransform := transform: Translation := vector3: X := 100.0 Y := 200.0 Z := 300.0 Rotation := rotation: Pitch := 0.0 Yaw := 0.0 Roll := 0.0
가독성이 높고 편집하기 쉽습니다. 비권장 사항 MyTransform := transform{Translation := vector3{X := 100.0, Y := 200.0, Z := 300.0}, Rotation := rotation{...}}
한 줄에서는 가독성이 떨어집니다.
- 열거형별로 주석이 필요하거나 줄바꿈을 삽입해야 하는 경우에는 열거형을 공백이 추가된 여러 줄 형식으로 정의합니다.
enum: Red, # 설명 1 Blue, # 설명 2
3.4 괄호
상속되지 않는 클래스 정의에는 괄호를 사용하지 마세요.
권장 사항 |
|
비권장 사항 |
|
3.5 점-공백 표기법 지양
소괄호 대신 '. '과 같이 점-공백 표기법을 사용하는 것은 피해야 합니다. 이렇게 하면 공백을 눈으로 확인하기가 힘들고 혼동하기도 쉬워집니다.
비권장 사항 |
|
비권장 사항 |
|
4. 함수
4.1 기본적으로 묵시적 반환
함수는 마지막 표현식 값을 반환합니다. 이것을 묵시적 반환으로 사용합니다.
Sqr(X:int):int =
X * X # 묵시적 반환
명시적 반환을 사용한다면 함수의 모든 반환이 명시적이어야 합니다.
4.2 GetX 함수는 여야 함
유효한 값을 반환하지 못할 수 있는 게터 또는 유사 시맨틱은 <decides><transacts>
로 표시하여 옵션 이외의 타입을 반환해야 합니다. 호출자는 잠재적 실패를 처리해야 합니다.
GetX()<decides><transacts>:x
예외가 있다면 조건 없이 var
에 써야 하는 함수입니다. 실패 시 뮤테이션이 롤백되므로 반환 타입에 logic
또는 option
을 사용해야 합니다.
4.3 단일 파라미터 함수보다 확장 메서드 사용
단일 타입 파라미터를 입력하는 함수 대신 확장 메서드를 사용합니다.
그러면 IntelliSense에 도움이 됩니다. Normalize(MyVector)
대신 MyVector.Normalize()
를 입력하면 입력한 메서드 이름의 각 문자로 이름을 제안할 수 있습니다.
권장 사항 |
|
비권장 사항 |
|
5. 실패 체크
5.1 한 줄 실패 가능 표현식은 세 개로 제한
-
한 줄에서 사용할 수 있는 조건부 확인/실패 가능 표현식은 최대 세 개로 제한합니다.
if (Damage > 10, Player := FindRandomPlayer[], Player.GetFortCharacter[].IsAlive[]): EliminatePlayer(Player)
-
조건의 수가 3개 미만인 경우에는
()
와 함께if
형식을 사용합니다.
권장 사항 |
|
코드를 간결하면서도 가독성 있게 유지할 수 있습니다. |
비권장 사항 |
|
가독성 향상 없이 코드를 여러 줄로 불필요하게 분할하는 것은 피합니다. |
-
각 표현식에 단어를 두 개 이상 사용하는 경우, 한 줄에는 최대 두 개의 표현식을 사용하는 것이 가독성이 더 높습니다.
if (Player := FindAlivePlayer[GetPlayspace().GetPlayers()], Team := FindEmptyTeam[GetPlayspace().GetTeamCollection().GetTeams()]): AddPlayerToTeam(Player, Team)
-
이 규칙은 한 줄의 실패 컨텍스트에도 적용할 수 있으며, 아홉 단어 이상은 사용하지 않는 것이 좋습니다. 이 제한을 넘는 경우에는 공백이 추가된 여러 줄 형식을 사용해야 합니다.
권장 사항 |
|
여러 줄을 사용하여 텍스트 가독성과 컨텍스트 이해도를 높입니다. |
비권장 사항 |
|
텍스트를 파악하는 것이 힘들어집니다. |
-
여러 실패 가능 조건을 하나의
<decides>
함수로 그룹화할지 여부를 평가하면 코드를 더 쉽게 읽고 재사용할 수 있게 됩니다. 코드가 한 곳에서만 사용되는 경우에는 임시 함수 없이 'section' 주석만으로 충분할 수 있습니다.if: Player := FindRandomPlayer[] IsAlive[Player] not IsInvulnerable[Player] Character := Player.GetFortCharacter[] Character.GetHealth < 10 then: EliminatePlayer(Player)
-
위의 코드는 다음과 같이 다시 쓸 수 있습니다.
GetRandomPlayerToEliminate()<decides><transacts>:player= Player := FindRandomPlayer[] IsAlive[Player] not IsInvulnerable[Player] Character := Player.GetFortCharacter[] Character.GetHealth < 10 Player if (Player := GetRandomPlayerToEliminate[]): Eliminate(Player)
-
for
루프 표현식에도 동일한 가이드라인이 적용됩니다. 예를 들면 다음과 같습니다.set Lights = for (ActorIndex -> TaggedActor : TaggedActors, LightDevice := customizable_light_device[TaggedActor], ShouldLightBeOn := LightsState[ActorIndex]): Logger.Print("Adding Light at index {ActorIndex} with State:{if (ShouldLightBeOn?) then "On" else "Off"}") if (ShouldLightBeOn?) then LightDevice.TurnOn() else LightDevice.TurnOff() LightDevice
-
위의 코드는 다음과 같이 더 개선할 수 있습니다.
set Lights = for: ActorIndex -> TaggedActor : TaggedActors LightDevice := customizable_light_device[TaggedActor] ShouldLightBeOn := LightsState[ActorIndex] do: if (ShouldLightBeOn?) then LightDevice.TurnOn() else LightDevice.TurnOff() LightDevice
5.2 종속 실패 표현식 그룹화
실패 컨텍스트의 조건이 이전 실패 컨텍스트의 성공에 의존하는 경우, 되도록 두 조건을 같은 실패 컨텍스트에 유지하면서 가이드라인 5.1을 따르는 것이 좋습니다.
이렇게 하면 코드 로컬리티가 향상되어 보다 손쉽게 로직을 이해하고 디버깅할 수 있습니다.
권장 사항 |
|
종속 또는 관련된 조건이 그룹화되어 있습니다. |
권장 사항 |
|
종속 또는 관련된 조건이 그룹화되어 있습니다. |
비권장 사항 |
|
불필요한 들여쓰기는 코드 플로를 따라가기 어렵게 만들 수 있습니다. |
비권장 사항 |
|
불필요한 들여쓰기는 코드 플로를 따라가기 어렵게 만들 수 있습니다. |
각 잠재적 실패 또는 실패 그룹을 개별적으로 처리하는 경우 실패 컨텍스트를 분할해도 좋습니다.
if (Player := FindPlayer[]):
if (Player.IsVulnerable[]?):
EliminatePlayer(Player)
else:
Print("Player is invulnerable, can’t eliminate.")
else:
Print("Can’t find player. This is a setup error.")
6. 캡슐화
6.1 클래스보다 인터페이스 사용
타당한 이유가 있는 경우에는 클래스 대신 인터페이스를 사용합니다. 이렇게 하면 구현 종속성을 줄이고, 프레임워크에서 사용 가능한 구현을 사용자가 제공할 수 있습니다.
6.2 프라이빗 액세스 사용 및 범위 제한
대부분의 경우 클래스 멤버는 '프라이빗'이어야 합니다.
클래스 및 모듈 메서드는 적절한 경우 되도록 <internal>
또는 <private>
으로 범위가 제한되어야 합니다.
7. 이벤트
7.1 이벤트에는 접미사 Event를 붙이고 핸들러에는 접두사 On을 붙일 것
등록 가능 이벤트 또는 델리게이트 목록 이름에는 접미사 'Event'가 붙어야 하며, 이벤트 핸들러에는 접두사 'On'이 붙어야 합니다.
MyDevice.JumpEvent.Subscribe(OnJump)
8. 동시성
8.1 함수에는 Async로 꾸미지 말 것
<suspends>
함수는 Async
등의 용어로 꾸미지 않아야 합니다.
권장 사항 |
|
비권장 사항 |
|
내부적으로 무엇인가가 발생하기를 기다리는 <suspends>
함수에는 Await
접두사 추가가 허용됩니다.
이렇게 하면 API가 어떻게 사용되어야 하는지 명확하게 알 수 있습니다.
AwaitGameEnd()<suspends>:void=
# 게임이 종료되기를 기다릴 때까지 다른 요소를 구성합니다…
GameEndEvent.Await()
OnBegin()<suspends>:void =
race:
RunGameLoop()
AwaitGameEnd()
9. 어트리뷰트
9.1 어트리뷰트 분리
어트리뷰트는 다른 줄로 분리합니다. 이렇게 하면 가독성이 향상됩니다. 특히 동일한 식별자에 여러 어트리뷰트를 추가할 경우에 특히 그렇습니다.
권장 사항 |
|
비권장 사항 |
|
10. 임포트 표현식
10.1 임포트 표현식 알파벳 순서 정렬
예를 들면 다음과 같습니다.
using { /EpicGames.com/Temporary/Diagnostics }
using { /EpicGames.com/Temporary/SpatialMath }
using { /EpicGames.com/Temporary/UI }
using { /Fortnite.com/UI }
using { /Verse.org/Simulation }