이 가이드에서는 유지관리하기가 쉽고 일관된 코드를 작성하는 데 권장되는 표준을 제시합니다. 개발자는 이 가이드라인을 준수하여 코드 가독성을 향상하고, 오류를 줄이며, 협업을 촉진할 수 있습니다. 표준화된 코드 스타일은 프로젝트에 참여하는 현재 및 미래의 개발자 모두가 코드를 쉽게 이해하고 유지관리할 수 있게 하는 데 꼭 필요합니다.
이 가이드는 권장 사항을 제공하지만 선택은 팀에 달려 있습니다.
1. 일반적인 명명 패턴
명명 규칙은 가독성이 높고 유지관리하기 쉬운 코드를 작성하는 데 필수적입니다. 따라서 코드 전체의 명명 스타일에 일관성을 유지하는 것이 좋습니다.
1.1 권장하는 방식
IsX: 보통 질문을 위한 로직 변수를 명명하는 데 사용됩니다(예: IsEmpty).OnX: 프레임워크에서 호출하는 오버로드 가능한 함수입니다.SubscribeX: X로 명명된 프레임워크 이벤트에 등록하며, 보통 OnX 함수를 전달합니다.MakeC: C 생성자를 오버로드하지 않고 클래스 C의 인스턴스를 만듭니다.CreateC: 클래스 C의 인스턴스를 하나 생성하여 논리 수명을 시작합니다.DestroyC: 논리 수명을 종료합니다.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 := class2.2 인터페이스는 형용사 스타일 사용
인터페이스는 되도록 printable 또는 enumerable과 같이 형용사를 사용해야 합니다. 형용사가 어울리지 않는다고 생각되는 경우 이름에 _interface를 대신 추가합니다.
my_new_thing_interface := interface2.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 = 53.1 들여쓰기
들여쓰기에는 스페이스를 4번 사용하고, Tab은 절대 사용하면 안 됩니다.
코드 블록은 중괄호가 아니라 공백을 추가한 들여쓰기 블록을 사용해야 합니다.
Versemy_class := class: Foo():void = Print("Hello World")예외가 있다면
option{a},my_class{A := b}등의 한 줄 표현식을 사용하는 경우입니다.
3.2 스페이스
컨텍스트에 맞게 코드를 간소화하는 것이 힘든 경우 연산자 앞뒤에 공백을 사용합니다. 괄호를 사용하여 연산 순서를 명시적으로 정의합니다.
VerseMyNumber := 4 + (2 * (a + b))괄호의 시작과 끝에는 공백을 추가하면 안 됩니다. 괄호 내에 여러 개의 표현식이 있는 경우에는 단일 공백으로 구분해야 합니다.
VerseMyEnum := enum{Red, Blue, Green} MyObject:my_class = my_class{X := 1, Y := 2} Vector := vector3{Left := 1000.0, Up := -1000.0, Forward := 0.0} Foo(Num:int, Str:[]char)식별자와 타입은 함께 두고, 할당
=연산자 앞뒤에 공백을 추가합니다. 타입 정의 및 상수 초기화 연산자(:=) 앞뒤에 공백을 추가합니다.VerseMyVariable:int = 5 MyVariable := 5 my_type := class함수 시그니처의 괄호, 식별자 및 타입 공백 추가의 경우에도 동일한 권장 사항을 따릅니다.
VerseFoo(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 줄바꿈
줄바꿈을 삽입하려면 공백이 추가된 여러 줄 형식을 사용합니다.
권장하는 방식
VerseMyTransform := transform: Translation := vector3: Left := 100.0 Up := 200.0 Forward := 300.0 Rotation := rotation: Pitch := 0.0 Yaw := 0.0 Roll := 0.0가독성이 높고 편집하기 쉽습니다.
지양할 방식
VerseMyTransform := transform{Translation := vector3{Left := 100.0, Up := 200.0, Forward := 300.0}, Rotation := rotation{...}}한 줄에서는 가독성이 떨어집니다.
열거형별로 주석이 필요하거나 줄바꿈을 삽입해야 하는 경우에는 열거형을 공백이 추가된 여러 줄 형식으로 정의합니다.
Verseenum: Red, # Desc1 Blue, # Desc2
3.4 괄호
상속되지 않는 클래스 정의에는 괄호를 사용하지 마세요.
권장하는 방식 | Verse |
지양할 방식 | Verse |
3.5 점-공백 표기법 지양
소괄호 대신 '. '과 같이 점-공백 표기법을 사용하는 것은 피해야 합니다. 이렇게 하면 공백을 눈으로 확인하기가 힘들고 혼동하기도 쉬워집니다.
지양할 방식 | Verse |
지양할 방식 | Verse |
4. 함수
4.1 기본적으로 묵시적 반환
함수는 마지막 표현식 값을 반환합니다. 이것을 묵시적 반환으로 사용합니다.
Sqr(X:int):int =
X * X # Implicit return명시적 반환을 사용한다면 함수의 모든 반환이 명시적이어야 합니다.
4.2 GetX 함수가 갖춰야 할 특징
유효한 값을 반환하지 못할 수 있는 게터 또는 유사 시맨틱은 <decides><transacts>로 표시하여 옵션 이외의 타입을 반환해야 합니다. 호출자는 잠재적 실패를 처리해야 합니다.
GetX()<decides><transacts>:x예외가 있다면 조건 없이 var에 써야 하는 함수입니다. 실패 시 뮤테이션이 롤백되므로 반환 타입에 logic 또는 option을 사용해야 합니다.
4.3 단일 파라미터 함수보다 확장 메서드 사용
단일 타입 파라미터가 있는 함수 대신 확장 메서드를 사용합니다.
그러면 IntelliSense에 도움이 됩니다. Normalize(MyVector) 대신 MyVector.Normalize()를 입력하면 입력한 메서드 이름의 각 문자로 이름을 제안할 수 있습니다.
권장하는 방식 | Verse |
지양할 방식 | Verse |
5. 실패 체크
5.1 한 줄에서 실패 가능 표현식은 세 개로 제한
한 줄에서 사용할 수 있는 조건부 확인/실패 가능 표현식은 최대 세 개로 제한합니다.
Verseif (Damage > 10, Player := FindRandomPlayer[], Player.GetFortCharacter[].IsAlive[]): EliminatePlayer(Player)조건의 수가 세 개 미만인 경우에는
()와 함께if형식을 사용합니다.
권장하는 방식 | Verse | 코드를 간결하면서도 가독성 있게 유지할 수 있습니다. |
지양할 방식 | Verse | 가독성이 좋아지지 않는데 코드를 여러 줄로 불필요하게 분할하는 것은 피합니다. |
각 표현식에 단어를 두 개 이상 사용하는 경우, 한 줄에는 최대 두 개의 표현식을 사용하는 것이 가독성이 더 높습니다.
Verseif (Player := FindAlivePlayer[GetPlayspace().GetPlayers()], Team := FindEmptyTeam[GetPlayspace().GetTeamCollection().GetTeams()]): AddPlayerToTeam(Player, Team)이 규칙은 한 줄의 실패 컨텍스트에도 적용할 수 있으며, 아홉 단어 이상은 사용하지 않는 것이 좋습니다. 이 제한을 넘는 경우에는 공백이 추가된 여러 줄 형식을 사용해야 합니다.
권장하는 방식 | Verse | 여러 줄을 사용하여 텍스트 가독성과 컨텍스트 이해도를 높입니다. |
지양할 방식 | Verse | 텍스트가 해석하기 어렵습니다. |
여러 실패 가능 조건을 하나의
<decides>함수로 그룹화할지 여부를 평가하면 코드를 더 쉽게 읽고 재사용할 수 있게 됩니다. 코드가 한 곳에서만 사용되는 경우에는 임시 함수 없이 'section' 주석만으로 충분할 수 있습니다.Verseif: Player := FindRandomPlayer[] IsAlive[Player] not IsInvulnerable[Player] Character := Player.GetFortCharacter[] Character.GetHealth < 10 then: EliminatePlayer(Player)위의 코드는 다음과 같이 다시 쓸 수 있습니다.
VerseGetRandomPlayerToEliminate()<decides><transacts>:player= Player := FindRandomPlayer[] IsAlive[Player] not IsInvulnerable[Player] Character := Player.GetFortCharacter[] Character.GetHealth < 10 Player if (Player := GetRandomPlayerToEliminate[]): Eliminate(Player)for루프 표현식에도 동일한 가이드라인이 적용됩니다. 예를 들면 다음과 같습니다.Verseset 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위의 코드는 다음과 같이 더 개선할 수 있습니다.
Verseset 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을 따르는 것이 좋습니다.
이렇게 하면 코드 로컬리티가 향상되어 보다 손쉽게 로직을 이해하고 디버깅할 수 있습니다.
권장하는 방식 | Verse | 종속 또는 관련된 조건이 그룹화되어 있습니다. |
권장하는 방식 | Verse | 종속 또는 관련된 조건이 그룹화되어 있습니다. |
지양할 방식 | Verse | 불필요한 들여쓰기는 코드 플로를 따라가기 어렵게 만들 수 있습니다. |
지양할 방식 | Verse | 불필요한 들여쓰기는 코드 플로를 따라가기 어렵게 만들 수 있습니다. |
각 잠재적 실패 또는 실패 그룹을 개별적으로 처리하는 경우 실패 컨텍스트를 분할해도 좋습니다.
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나 이와 유사한 용어로 꾸미지 않아야 합니다.
권장하는 방식 | Verse |
지양할 방식 | Verse |
내부적으로 무엇인가가 발생하기를 기다리는 <suspends> 함수에는 Await 접두사 추가가 허용됩니다.
이렇게 하면 API가 어떻게 사용되어야 하는지 명확하게 알 수 있습니다.
AwaitGameEnd()<suspends>:void=
# Setup other things before awaiting game end…
GameEndEvent.Await()
OnBegin()<suspends>:void =
race:
RunGameLoop()
AwaitGameEnd()9. 어트리뷰트
9.1 어트리뷰트 분리
어트리뷰트는 다른 줄로 분리합니다. 이렇게 하면 가독성이 향상됩니다. 특히 동일한 식별자에 여러 어트리뷰트를 추가할 경우에 특히 그렇습니다.
권장하는 방식 | Verse |
지양할 방식 | Verse |
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 }