This guide provides a set of recommended standards for writing consistent code that's easy to maintain. By adhering to these guidelines, developers can improve code readability, reduce errors, and facilitate collaboration. A standardized code style is necessary to ensure code is easy to understand and maintain by both current and future developers working on a project.
This guide provides recommendations, but ultimately the choice is up to your team.
1. Common Naming Patterns
Naming is crucial for readable and maintainable code. Try to be consistent in the naming style throughout your code.
1.1 Do
-
IsX
: Often used for naming logic variables to ask a question (for example, IsEmpty). -
OnX
: An overloadable function called by the framework. -
SubscribeX
: Subscribe to framework event named X, often passing an OnX function to it. -
MakeC
: Make an instance of class c without overloading the c constructor. -
CreateC
: Create an instance of class c, beginning its logical lifetime. -
DestroyC
: End the logic lifetime. -
C:c
: If you’re working with a single instance of class c, it’s fine to call it C.
1.2 Don’t
-
Decorate type names. Just call it
thing
, notthing_type
orthing_class
. -
Decorate enumeration values. Not
color := enum{COLOR_Red, COLOR_Green}
, usecolor := enum{Red, Green}
.
2. Names
2.1 Types use lower_snake_case
Type names should always be lower_snake_case
. This includes all types: structs, classes, typedefs, traits/interfaces, enums, etc.
my_new_type := class
2.2 Interfaces are adjectives
Interfaces should be adjectives where possible, such as printable, enumerable. Where adjectives don’t seem right, append _interface
to the name instead.
my_new_thing_interface := interface
2.3 PascalCase everything else
All other names should be PascalCase. Modules, member variables, parameters, methods, and so on.
MyNewVariable:my_new_type = …
2.4 Parametric Types
-
Name parametric types t or thing, where thing explains what the type is supposed to represent. For example:
Send(Payload:payload where payload:type)
You’re sending some parameterized data,Payload
, of anypayload
type. -
If there’s more than one parametric type, avoid using single letters, such as
t
,u
,g
-
Never use
_t
suffix.
3. Formatting
It is important to stay consistent with formatting throughout your codebase. This makes the code easier to read and understand for yourself and other developers. Choose a formatting style that works for the project.
As an example of staying consistent, you could choose one of the following spacing formats and use it throughout the codebase:
MyVariable : int = 5
MyVariable:int = 5
3.1 Indentation
-
Use four spaces for indentation, never tabs.
-
Code blocks should use indented blocks (spaced) rather than curly brackets (braced):
my_class := class: Foo():void = Print("Hello World")
- Except when writing single line expressions like
option{a}
,my_class{A := b}
, etc.
- Except when writing single line expressions like
3.2 Spaces
- Use spaces around operators, unless it makes sense to keep the code compact for its context. Add braces to explicitly define the order of operations.
MyNumber := 4 + (2 * (a + b))
-
Don’t add spaces at the beginnings and ends of brackets. Multiple expressions inside brackets should be separated by a single space.
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)
- Keep identifier and type together; add a space around the assignment
=
operator. Add a space around type definitions and constant initialization operators (:=
).MyVariable:int = 5 MyVariable := 5 my_type := class
- Follow the same recommendations for brackets, identifiers, and types spacing for function signatures.
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 Line Breaks
-
Use a spaced, multiline form to insert a line break.
Do MyTransform := transform: Translation := vector3: X := 100.0 Y := 200.0 Z := 300.0 Rotation := rotation: Pitch := 0.0 Yaw := 0.0 Roll := 0.0
More readable and easier to edit. Don't MyTransform := transform{Translation := vector3{X := 100.0, Y := 200.0, Z := 300.0}, Rotation := rotation{...}}
Hard to read on a single line.
- Define enums in spaced, multiline form if they need per-enumeration comments or if you need to insert a line break.
enum: Red, # Desc1 Blue, # Desc2
3.4 Brackets
Don’t use brackets for non-inheriting class definitions.
Do |
|
Don't |
|
3.5 Avoid Dot-Space Notation
Avoid using dot-space ". " notation in place of braces. This makes it visually harder to parse whitespace and is a potential source of confusion.
Don't |
|
Don't |
|
4. Functions
4.1 Implicit return by default
Functions return their last expression value. Use that as an implicit return.
Sqr(X:int):int =
X * X # Implicit return
If using any explicit returns, all returns in the function should be explicit.
4.2 GetX functions should be
Getters or functions with similar semantics that may fail to return valid values should be marked <decides><transacts>
and return a non-option type. The caller should handle potential failure.
GetX()<decides><transacts>:x
An exception is functions that need to unconditionally write to a var
. Failure would roll the mutation back, so they need to use logic
or option
for their return type.
4.3 Prefer Extension Methods to Single Parameter Functions
Use extension methods instead of a function with a single typed parameter.
Doing this helps Intellisense. By typing MyVector.Normalize()
instead of Normalize(MyVector)
it can suggest names with each character of the method name you type.
Do |
|
Don't |
|
5. Failure Checks
5.1 Limit single line Failable Expressions count to three
-
Limit conditional checks/failable expressions on a single line to a maximum of three.
if (Damage > 10, Player := FindRandomPlayer[], Player.GetFortCharacter[].IsAlive[]): EliminatePlayer(Player)
-
Use the form of
if
with parentheses()
when the number of conditions is less than three.
Do |
|
Keeps code concise but readable. |
Don't |
|
Unnecessarily splits code over multiple lines with no readability improvements. |
-
If using more than two words for each expression, a maximum of two expressions on a single line is often more readable.
if (Player := FindAlivePlayer[GetPlayspace().GetPlayers()], Team := FindEmptyTeam[GetPlayspace().GetTeamCollection().GetTeams()]): AddPlayerToTeam(Player, Team)
-
You can also apply the rule as in a failure context on a single line, don't use more than nine words. When over the limit, use the spaced, multiline form.
Do |
|
The text reads better and the context is understandable over multiple lines. |
Don't |
|
The text is hard to parse. |
-
Evaluate whether grouping multiple failable conditions into a single
<decides>
function would make the code easier to read and reuse. Note that if the code is only ever used in one place, a "section" comment without an ad hoc function can suffice.if: Player := FindRandomPlayer[] IsAlive[Player] not IsInvulnerable[Player] Character := Player.GetFortCharacter[] Character.GetHealth < 10 then: EliminatePlayer(Player)
-
Can be rewritten as:
GetRandomPlayerToEliminate()<decides><transacts>:player= Player := FindRandomPlayer[] IsAlive[Player] not IsInvulnerable[Player] Character := Player.GetFortCharacter[] Character.GetHealth < 10 Player if (Player := GetRandomPlayerToEliminate[]): Eliminate(Player)
-
The same guideline applies to expressions in
for
loops. For example: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
-
Better as:
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 Group Dependent Failure Expressions together
When a condition in a failure context depends on a previous failure context succeeding, keep the two conditions together in the same failure context when possible, and follow guideline 5.1.
This improves code locality, which simplifies logical understanding and debugging.
Do |
|
Dependent or related conditions are grouped. |
Do |
|
Dependent or related conditions are grouped. |
Don't |
|
Unnecessary indentation can make the flow harder to follow. |
Don't |
|
Unnecessary indentation can make the flow harder to follow. |
It’s acceptable to split failure contexts if you handle each potential failure (or failure groups) separately.
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. Encapsulation
6.1 Prefer Interfaces to Classes
Use interfaces instead of classes where reasonable. This helps reduce implementation dependencies and allows users to provide implementations that can be used by the framework.
6.2 Prefer Private Access and Restrict Scope
Class members should be 'private' in most cases.
Class and module methods should be scoped as restrictively as possible - <internal>
or <private>
where appropriate.
7. Events
7.1 Postfix Events with Event and Prefix Handlers with On
Subscribable events or delegate list names should be postfixed with 'Event', and event handler names should be prefixed with 'On'.
MyDevice.JumpEvent.Subscribe(OnJump)
8. Concurrency
8.1 Don’t Decorate Functions with Async
Avoid decorating <suspends>
functions with Async
or similar terms.
Do |
|
Don't |
|
It’s acceptable to add the Await
prefix to a <suspends>
function that internally waits on something to happen.
This can clarify how an API is supposed to be used.
AwaitGameEnd()<suspends>:void=
# Setup other things before awaiting game end…
GameEndEvent.Await()
OnBegin()<suspends>:void =
race:
RunGameLoop()
AwaitGameEnd()
9. Attributes
9.1 Separate Attributes
Put attributes on a separate line. It’s more readable, especially if multiple attributes are added to the same identifier.
Do |
|
Don't |
|
10. Import Expressions
10.1 Sort Import Expressions Alphabetically
For example:
using { /EpicGames.com/Temporary/Diagnostics }
using { /EpicGames.com/Temporary/SpatialMath }
using { /EpicGames.com/Temporary/UI }
using { /Fortnite.com/UI }
using { /Verse.org/Simulation }