Ce guide fournit un ensemble de standards recommandés pour écrire un code cohérent et facile à maintenir. En respectant ces lignes directrices, les développeurs peuvent améliorer la lisibilité du code, réduire les erreurs et faciliter la collaboration. Un style de code standardisé est nécessaire pour garantir que le code soit facile à comprendre et maintenir, pour les développeurs actuels et futurs travaillant sur un projet.
Ce guide ne fait qu'émettre des recommandations. La décision reviendra toujours à votre équipe.
1. Motifs typiques d'affectation de noms
L'affectation de noms est cruciale pour un code lisible et maintenable. Soyez cohérent dans le style d'affectation de noms tout au long de votre code.
1.1 À faire
-
IsX
: Souvent utilisé pour nommer des variables logiques afin de poser une question (par exemple, IsEmpty (EstVide)). -
OnX
: Fonction surchargeable appelée par le framework. -
SubscribeX
: Abonnement à l'événement du framework nommé X, en passant souvent une fonction OnX. -
MakeC
: Créer une instance de la classe c sans surcharger le constructeur c. -
CreateC
: Créer une instance de la classe c, en commençant sa durée de vie logique. -
DestroyC
: Terminer la durée de vie logique. -
C:c
: Si vous travaillez avec une seule instance de la classe c, il est correct de l'appeler C.
1.2 À ne pas faire
-
Surcharger les noms de types. Nommez-le simplement
thing
, et nonthing_type
outhing_class
. -
Surcharger la valeur d'énumération. Au lieu de
color := enum{COLOR_Red, COLOR_Green}
, utilisezcolor := enum{Red, Green}
.
2. Noms
2.1 Les types sont en lower_snake_case
Les noms de types doivent toujours être en lower_snake_case
, c'est-à-dire en minuscules et avec des traits de soulignement. Cela comprend tous les types : les structures, les classes, les définitions de type, les traits/interfaces, les énumérations, etc.
my_new_type := class
2.2 Les interfaces sont des adjectifs
Les interfaces devraient être des adjectifs lorsque c'est possible, comme affichable ou énumérable. Lorsque les adjectifs semblent ne pas être appropriés, ajoutez plutôt _interface
au nom.
my_new_thing_interface := interface
2.3 Le reste est en PascalCase
Tout le reste doit être en PascalCase. Les modules, les variables membres, les paramètres, les méthodes, etc.
MyNewVariable:my_new_type = …
2.4 Types paramétriques
-
Nommez les types paramétriques
t
outhing
, oùthing
explique ce que le type est censé représenter. Par exemple :Send(Payload:payload where payload:type)
Vous envoyez des données paramétrées,Payload
, de n'importe quel type depayload
. -
S'il y a plus d'un type paramétrique, évitez d'utiliser des lettres simples telles que
t
,u
oug
. -
N'utilisez jamais de suffixe
_t
.
3. Formatage
Il est important de rester cohérent dans le formatage de l'ensemble de votre codebase. Cela permet de faciliter la lecture et la compréhension du code pour vous-même et les autres développeurs. Choisissez un style de formatage qui convient au projet.
À titre d'exemple de cohérence, vous pouvez choisir l'un des formats d'espacement suivants et l'utiliser dans tout le codebase :
MyVariable : int = 5
MyVariable:int = 5
3.1 Indentation
-
Utilisez quatre espaces pour l'indentation, jamais des tabulations.
-
Les blocs de code doivent utiliser des blocs indentés (espacés) plutôt que des accolades (entre accolades) :
my_class := class: Foo():void = Print("Hello World")
- Sauf lors de l'écriture d'expressions d'une seule ligne telles que
option{a}
,my_class{A := b}
, etc.
- Sauf lors de l'écriture d'expressions d'une seule ligne telles que
3.2 Espaces
- Utilisez des espaces autour des opérateurs, sauf s'il est judicieux de maintenir le code compact pour son contexte. Ajoutez des parenthèses pour définir explicitement l'ordre des opérations.
MyNumber := 4 + (2 * (a + b))
-
N'ajoutez pas d'espaces au début et à la fin des parenthèses et accolades. Les expressions multiples à l'intérieur des parenthèses doivent être séparées par une seule espace.
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)
- Conservez l'identificateur et le type ensemble ; ajoutez une espace autour de l'opérateur
=
de l'assignation. Ajoutez également une espace autour des définitions de type et des opérateurs d'initialisation de constantes (:=
).MyVariable:int = 5 MyVariable := 5 my_type := class
- Suivez les mêmes recommandations pour l'espacement des parenthèses, des identificateurs et des types dans les signatures de fonction.
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 Sauts de ligne
-
Utilisez un format en plusieurs lignes, espacé, pour insérer un saut de ligne.
À faire MyTransform := transform: Translation := vector3: X := 100.0 Y := 200.0 Z := 300.0 Rotation := rotation: Tangage := 0.0 Lacet := 0.0 Roll := 0.0
Plus lisible et plus facile à modifier. À ne pas faire MyTransform := transform{Translation := vector3{X := 100.0, Y := 200.0, Z := 300.0}, Rotation := rotation{...}}
Difficile à lire sur une seule ligne.
- Définissez les énumérations sous un format espacé et en plusieurs lignes si elles nécessitent des commentaires spécifiques à chaque énumération ou si vous avez besoin d'insérer un saut de ligne.
enum: Red, # Desc1 Blue, # Desc2
3.4 Parenthèses
N'utilisez pas de parenthèses pour les définitions de classe non héritées.
À faire |
|
À ne pas faire |
|
3.5 Éviter la notation point-espace
Évitez d'utiliser la notation point-espace ". " à la place des accolades. Cela rend l'analyse d'espacement plus difficile visuellement et peut être source de confusion.
À ne pas faire |
|
À ne pas faire |
|
4. Fonctions
4.1 Retour implicite par défaut
Les fonctions renvoient la valeur de leur dernière expression. Utilisez cela comme un retour implicite.
Sqr(X:int):int =
X * X # Retour implicite
Dans le cas de l'utilisation des retours explicites, tous les retours dans la fonction doivent être explicites.
4.2 Les fonctions GetX doivent être
Les getters ou les fonctions ayant des sémantiques similaires qui pourraient ne pas renvoyer de valeurs valides doivent être marqués <decides><transacts>
et renvoyer un type non optionnel. L'appelant doit gérer les éventuels échecs.
GetX()<decides><transacts>:x
Une exception concerne les fonctions qui doivent écrire de manière inconditionnelle dans une var
. Le cas d'un échec pourrait annuler la mutation, il est donc nécessaire d'utiliser logic
ou option
comme type de retour.
4.3 Privilégier les méthodes d'extension aux fonctions à paramètre unique
Utilisez des méthodes d'extension au lieu d'une fonction avec un paramètre typé unique.
Cela facilite l'utilisation de Intellisense. En tapant MyVector.Normalize()
au lieu de Normalize(MyVector)
, cela peut suggérer des noms avec chaque caractère du nom de la méthode que vous tapez.
À faire |
|
À ne pas faire |
|
5. Vérifications des échecs
5.1 Limiter à trois le nombre d'expressions faillibles sur une seule ligne
-
Limitez à trois au maximum les vérifications conditionnelles/expressions faillibles sur une seule ligne.
if (Damage > 10, Player := FindRandomPlayer[], Player.GetFortCharacter[].IsAlive[]): EliminatePlayer(Player)
-
Utilisez la forme
if
avec des parenthèses()
lorsque le nombre de conditions est inférieur à trois.
À faire |
|
Permet de garder le code concis mais lisible. |
À ne pas faire |
|
Divise inutilement le code sur plusieurs lignes sans améliorer la lisibilité. |
-
Si chaque expression nécessite plus de deux mots, il est souvent préférable d'avoir un maximum de deux expressions sur une seule ligne pour une meilleure lisibilité.
if (Player := FindAlivePlayer[GetPlayspace().GetPlayers()], Team := FindEmptyTeam[GetPlayspace().GetTeamCollection().GetTeams()]): AddPlayerToTeam(Player, Team)
-
Vous pouvez également appliquer cette règle dans un contexte d'échec sur une seule ligne, mais sans dépasser neuf mots. Lorsque vous dépassez cette limite, utilisez la forme espacée et sur plusieurs lignes.
À faire |
|
Le texte est plus lisible et le contexte est compréhensible sur plusieurs lignes. |
À ne pas faire |
|
Le texte est difficile à analyser. |
-
Demandez-vous si regrouper plusieurs conditions faillibles dans une seule fonction
<decides>
rendrait le code plus facile à lire et à réutiliser. Notez que si le code est utilisé uniquement à un seul endroit, un commentaire de "section" sans fonction ad hoc peut suffire.if: Player := FindRandomPlayer[] IsAlive[Player] not IsInvulnerable[Player] Character := Player.GetFortCharacter[] Character.GetHealth < 10 then: EliminatePlayer(Player)
-
Ce code peut être réécrit comme ceci :
GetRandomPlayerToEliminate()<decides><transacts>:player= Player := FindRandomPlayer[] IsAlive[Player] not IsInvulnerable[Player] Character := Player.GetFortCharacter[] Character.GetHealth < 10 Player if (Player := GetRandomPlayerToEliminate[]): Eliminate(Player)
-
La même directive s'applique aux expressions dans les boucles
for
. Par exemple :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
-
Ce code peut être amélioré en :
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 Regrouper les expressions d'échec dépendantes ensemble
Lorsqu'une condition dans un contexte d'échec dépend de la réussite d'un contexte d'échec précédent, si possible, regroupez les deux conditions dans le même contexte d'échec et suivez les conseils de la section 5.1.
Cela améliore la localisation du code, ce qui facilite la compréhension logique et le débogage.
À faire |
|
Les conditions dépendantes ou liées sont regroupées. |
À faire |
|
Les conditions dépendantes ou liées sont regroupées. |
À ne pas faire |
|
Une indentation inutile peut rendre le flux plus difficile à suivre. |
À ne pas faire |
|
Une indentation inutile peut rendre le flux plus difficile à suivre. |
Vous pouvez également diviser les contextes d'échec si vous traitez chaque échec potentiel (ou groupes d'échecs) séparément.
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 Privilégier les interfaces aux classes
Dans la mesure du possible, utilisez des interfaces plutôt que des classes. Cela permet de réduire les dépendances d'implémentation et permet aux utilisateurs de fournir des implémentations qui peuvent être utilisées par le framework.
6.2 Privilégier un accès privé et une restriction de la portée
Les membres de classe doivent être "privés" dans la plupart des cas.
Les méthodes de classe et de module doivent être définies de la manière la plus restrictive possible (<internal>
ou <private>
si possible).
7. Événements
7.1 Événements avec le suffixe Event et gestionnaires avec le préfixe On
Les noms des événements pouvant être abonnés ou des listes de délégués doivent présenter le suffixe 'Event', et les noms des gestionnaires d'événements doivent comporter le préfixe 'On'.
MyDevice.JumpEvent.Subscribe(OnJump)
8. Concurrence
8.1 Ne pas surcharger les fonctions avec Async
Évitez de surcharger les fonctions <suspends>
avec Async
ou des termes similaires.
À faire |
|
À ne pas faire |
|
Vous pouvez ajouter le préfixe Await
à une fonction <suspends>
qui attend en interne que quelque chose se produise.
Cela peut permettre de clarifier la manière dont une API doit être utilisée.
AwaitGameEnd()<suspends>:void=
# Configurer d'autres choses avant d'attendre la fin du jeu...
GameEndEvent.Await()
OnBegin()<suspends>:void =
race:
RunGameLoop()
AwaitGameEnd()
9. Attributs
9.1 Isoler les attributs
Placez les attributs sur une ligne différente. Cela rend le tout plus lisible, en particulier si plusieurs attributs sont ajoutés au même identificateur.
À faire |
|
À ne pas faire |
|
10. Expressions d'importation
10.1 Trier les expressions d'importation par ordre alphabétique
Par exemple :
using { /EpicGames.com/Temporary/Diagnostics }
using { /EpicGames.com/Temporary/SpatialMath }
using { /EpicGames.com/Temporary/UI }
using { /Fortnite.com/UI }
using { /Verse.org/Simulation }