このガイドでは、メンテナンスが容易で一貫性のあるコードを書くうえで推奨される一連のスタンダードを提供します。 これらのガイドラインに準拠することで、コードの可読性を高めてエラーを減らし、コラボレーションを推進することができます。 プロジェクトに携わる現在および将来のすべてのデベロッパーが理解しやすく、メンテナンスも容易なコードを実現するには、標準化されたコード スタイルが欠かせません。
このガイドでは推奨事項を提供しますが、最終的には何を選ぶかはあなたのチームの判断です。
1. 共通する命名パターン
名前の付け方は、可読性が高いメンテナンス容易なコードを実現するうえで非常に重要です。 コード全体を通じて一貫した命名パターンを心がけてください。
1.1 すべきこと
IsX:質問を尋ねるロジック変数 (たとえば IsEmpty) によく使用されます。OnX:フレームワークで呼び出されるオーバーロード可能な関数です。SubscribeX:X という名前のフレームワーク イベントにサブスクライブして、多くの場合、それに OnX 関数を渡します。MakeC:c コンストラクタをオーバーロードせずに、クラス c のインスタンスを作成します。CreateC:クラス c のインスタンスを作成し、そのロジックのライフタイムを開始します。DestroyC:ロジックのライフタイムを終了します。C:c:クラス c の単一のインスタンスで作業している場合は、これを「C」と呼んでも差し支えありません。
1.2 すべきでないこと
タイプ名に装飾を施す。
thing_typeやthing_classではなく、シンプルにthingとします。列挙値に装飾を施す。
color := enum{COLOR_Red, COLOR_Green}ではなく、color := enum{Red, Green}を使用します。
2. 名前
2.1 タイプには lower_snake_case を使用する
型名は常に lower_snake_case とします。 これには、構造体、クラス、typedef、特性/インターフェース、列挙型など、すべてのタイプが含まれます。
my_new_type := class2.2 インターフェースは形容詞にする
可能な場合、インターフェースは形容詞にします。たとえば、「printable」や「enumerable」です。 形容詞がそぐわない場合は、代わりに名前の最後に _interface を付け足します。
my_new_thing_interface := interface2.3 その他のすべては PascalCase にする
その他の名前はすべて PascalCase (パスカル形式) にします。 モジュール、メンバー変数、パラメータ、メソッドなどです。
MyNewVariable:my_new_type = …2.4 パラメトリック型
パラメトリック型には、型が表現するものを表す thing を意味する「t」を名前に付けます。 次に例を示します。
Send(Payload:payload wherepayload:type)パラメータ化されたデータである、payload型のPayloadを送信します。複数のパラメトリック型が存在する場合は、
t、u、gなどの単一の文字を使うことは避けてください。_tサフィックスは使用しないでください。
3. フォーマット (書式)
コードベース全体を通じて一貫したフォーマットを維持することが大切です。 こうすることでコードが読みやすくなり、自身だけでなく他のデベロッパーにも理解しやすいものとなります。 プロジェクトに適したフォーマット スタイルを選んでください。
一貫性を保つための例として、スペースに関する次のフォーマットのいずれかを選択して、それをコードベース全体で使用します。
MyVariable : int = 5
MyVariable:int = 53.1 インデント
インデントには 4 つのスペースを使用します。タブは使用しないでください。
コード ブロックでは、中括弧/波括弧 ではなく、(スペースで) インデントされたブロックを使用します。
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> でマークして、非 option 型を返す必要があります。 潜在的な失敗は呼び出し元で処理する必要があります。
GetX()<decides><transacts>:x例外は無条件で var に書き込む必要がある関数です。 失敗すると変更がロールバックされるため、それらの関数では戻り型に logic または option を使用する必要があります。
4.3 単一パラメータ関数よりも拡張メソッドを優先する
型付けされた単一のパラメータを含む関数の代わりに、拡張メソッドを使用します。
こうすることでインテリセンスの機能に役立ちます。 Normalize(MyVector) ではなく MyVector.Normalize() と入力することで、インテリセンスでは、メソッド名の各文字を入力するごとに提案する名前が表示されます。
推奨 | Verse |
非推奨 | Verse |
5. 失敗チェック
5.1 単一行での「失敗する可能性がある式」の数は 3 つまでに抑える
単一行での条件付きのチェック/失敗する可能性がある式は 3 つまでに抑えます。
Verseif (Damage > 10, Player := FindRandomPlayer[], Player.GetFortCharacter[].IsAlive[]): EliminatePlayer(Player)条件の数が 3 未満の場合は、括弧
()を含むif形式を使用します。
推奨 | Verse | コードを簡潔に、読みやすさを維持します。 |
非推奨 | Verse | コードを複数行に分割しても、可読性が変わらないことがあります。 |
それぞれの式で 3 単語以上を使用する場合は、単一行での式の数を 2 つまでにすることで、読みやすくなることがよくあります。
Verseif (Player := FindAlivePlayer[GetPlayspace().GetPlayers()], Team := FindEmptyTeam[GetPlayspace().GetTeamCollection().GetTeams()]): AddPlayerToTeam(Player, Team)「9 単語より多くは使用しない」という、失敗コンテキストでのルールを単一行に適用することもできます。 この制限を超える場合は、スペースを加えた複数行の形式を使用します。
推奨 | Verse | 複数行に分けるとテキストが読みやすくなり、コンテキストも理解しやすくなります。 |
非推奨 | Verse | テキストはパースしづらくなります。 |
複数の失敗可能な条件を単一の
<decides>関数にグループ化することで、コードが読みやすく、再利用しやすくなるかどうかを評価します。 コードが一つの場所のみで使用される場合は、アドホック関数なしの「セクション」コメントで十分なこともあります。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 依存する「失敗する可能性がある式」はグループ化する
失敗コンテキストの条件が、以前の失敗コンテキストの成否に依存する場合は、可能であれば、それら 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 }