Ce guide fournit un ensemble de standards recommandés pour écrire un code cohérent et facile à maintenir. En adhérant à 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 fournit des recommandations, mais le choix revient au final à 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
EstX: Souvent utilisé pour nommer des variables logiques afin de poser une question (par exemple, EstVide).OnX: Fonction surchargeable appelée par le framework.SubscribeX: Abonnement à l'événement du framework nommé X, en transmettant 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: Mettre fin à 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
Décorer les noms de types. Appelez-le simplement
thing, pasthing_typeouthing_class.Décorer les valeurs d'énumération. Pas
color := enum{COLOR_Red, COLOR_Green}, utilisezcolor := enum{Red, Green}.
2. Noms
2.1 Les types utilisent lower_snake_case
Les noms de type doivent toujours être lower_snake_case. Cela inclut tous les types : les structures, les classes, les définitions de type, les traits/interfaces, les énumérations, etc.
my_new_type := class2.2 Les interfaces sont des adjectifs
Les interfaces devraient être des adjectifs lorsque c'est possible, comme imprimable ou énumérable. Lorsque les adjectifs semblent ne pas être appropriés, ajoutez plutôt _interface au nom.
my_new_thing_interface := interface2.3 PascalCase tout le reste
Tout le reste doit être en PascalCase. Les modules, les variables membres, les paramètres, les méthodes, et ainsi de suite.
MyNewVariable:my_new_type = …2.4 Types paramétriques
Nommez les types paramétriques t ou thing, 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 decharge utile.S'il existe plusieurs types paramétriques, évitez d'utiliser des lettres simples, telles que
t,u,gN'utilisez jamais le suffixe
_t.
3. Formatage
Il est important de rester cohérent dans le formatage de l'ensemble de votre codebase. Cela facilite 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 = 53.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) :
Versemy_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.
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 crochets pour définir explicitement l'ordre des opérations.
VerseMyNumber := 4 + (2 * (a + b))N'ajoutez pas d'espaces au début et à la fin des parenthèses. Les expressions multiples à l'intérieur des parenthèses doivent être séparées par un seul espace.
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)Conservez l'identificateur et le type ensemble ; ajoutez un espace autour de l'opérateur d'assignation
=. Ajoutez un espace autour des définitions de type et des opérateurs d'initialisation de constantes (:=).VerseMyVariable:int = 5 MyVariable := 5 my_type := classSuivez les mêmes recommandations pour l'espacement des parenthèses, des identificateurs et des types dans les signatures de fonction.
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 Sauts de ligne
Utilisez une forme multiligne espacée pour insérer un saut de ligne.
Faire
VerseMyTransform := transform: Translation := vector3: Left := 100.0 Up := 200.0 Forward := 300.0 Rotation := rotation: Pitch := 0.0 Yaw := 0.0 Roll := 0.0Plus lisible et plus facile à éditer.
Ne pas
VerseMyTransform := transform{Translation := vector3{Left := 100.0, Up := 200.0, Forward := 300.0}, Rotation := rotation{...}}Difficile à lire sur une seule ligne.
Définissez les énumérations sous forme espacée et multiligne si elles nécessitent des commentaires par énumération ou si vous devez insérer un saut de ligne.
Verseenum: Red, # Desc1 Blue, # Desc2
3.4 Parenthèses
N'utilisez pas de paranthèses pour les définitions de classe non héritées.
Faire | Verse |
Ne pas | Verse |
3.5 Évitez la notation point-espace
Évitez d'utiliser le point-espace ". Notation " à la place des accolades. Cela rend l’analyse d'espacement plus difficile visuellement et peut être source de confusion.
Ne pas | Verse |
Ne pas | Verse |
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 # Implicit returnDans 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 fonctions avec une sémantique similaire qui peuvent 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>:xUne exception concerne les fonctions qui doivent écrire inconditionnellement dans une variable. Le cas d'un échec annulerait la mutation, il est donc nécessaire d'utiliser logic ou option comme type de retour.
4.3 Préférez 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 | Verse |
Ne pas | Verse |
5. Vérifications des échecs
5.1 Limitez le nombre d'expressions faillibles sur une seule ligne à trois
Limitez les vérifications conditionnelles/expressions faillibles pouvant échouer sur une seule ligne à un maximum de trois.
Verseif (Damage > 10, Player := FindRandomPlayer[], Player.GetFortCharacter[].IsAlive[]): EliminatePlayer(Player)Utilisez la forme
ifavec des parenthèses()lorsque le nombre de conditions est inférieur à trois.
Faire | Verse | Permet de garder le code concis, mais lisible. |
Ne pas | Verse | Divise inutilement le code sur plusieurs lignes sans amélioration de la lisibilité. |
Si vous utilisez plus de deux mots pour chaque expression, un maximum de deux expressions sur une seule ligne est souvent plus lisible.
Verseif (Player := FindAlivePlayer[GetPlayspace().GetPlayers()], Team := FindEmptyTeam[GetPlayspace().GetTeamCollection().GetTeams()]): AddPlayerToTeam(Player, Team)Vous pouvez également appliquer la règle comme dans un contexte d'échec sur une seule ligne, n'utilisez pas plus de neuf mots. Lorsque vous dépassez la limite, utilisez la forme espacée et multiligne.
Faire | Verse | Le texte est plus lisible et le contexte est compréhensible sur plusieurs lignes. |
Ne pas | Verse | Le texte est difficile à analyser. |
Évaluez si le regroupement de 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.Verseif: Player := FindRandomPlayer[] IsAlive[Player] not IsInvulnerable[Player] Character := Player.GetFortCharacter[] Character.GetHealth < 10 then: EliminatePlayer(Player)Peut être réécrit comme :
VerseGetRandomPlayerToEliminate()<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 :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() LightDeviceMieux comme :
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 Regrouper les expressions d'échec dépendantes
Lorsqu'une condition dans un contexte d’échec dépend de la réussite d'un contexte d’échec précédent, gardez les deux conditions ensemble dans le même contexte d’échec lorsque c'est possible, et suivez le principe 5.1.
Cela améliore la localisation du code, ce qui facilite la compréhension logique et le débogage.
Faire | Verse | Les conditions dépendantes ou liées sont regroupées. |
Faire | Verse | Les conditions dépendantes ou liées sont regroupées. |
Ne pas | Verse | Une indentation inutile peut rendre le flux plus difficile à suivre. |
Ne pas | Verse | Une indentation inutile peut rendre le flux plus difficile à suivre. |
Il est acceptable de 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 Préférer les interfaces aux classes
Utilisez des interfaces au lieu de classes lorsque cela est raisonnable. 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 Préférer un accès privé et une restriction de l'étendue
Les membres de classe doivent être "privés" dans la plupart des cas.
Les méthodes de classe et de module doivent être limitées de manière aussi restrictive que possible : <internal> ou <private>, le cas échéant.
7. Événements
7.1 Événements de suffixe avec gestionnaires d'événements et de préfixes activés
Les noms des événements pouvant être abonnés ou des listes de délégués doivent être sous forme de suffixe avec 'Event', et les noms des gestionnaires d'événements doivent être sous forme de préfixe avec 'On'.
MyDevice.JumpEvent.Subscribe(OnJump)8. Concurrence
8.1 Ne pas décorer les fonctions avec Async
Évitez de décorer les fonctions <suspends> avec Async ou des termes similaires.
Faire | Verse |
Ne pas | Verse |
Il est acceptable d'ajouter le préfixe Await à une fonction <suspends> qui attend en interne que quelque chose se produise.
Cela peut clarifier la manière dont une API doit être utilisée.
AwaitGameEnd()<suspends>:void=
# Setup other things before awaiting game end…
GameEndEvent.Await()
OnBegin()<suspends>:void =
race:
RunGameLoop()
AwaitGameEnd()9. Attributs
9.1 Attributs séparés
Placez les attributs sur une ligne séparée. C'est plus lisible, en particulier si plusieurs attributs sont ajoutés au même identificateur.
Faire | Verse |
Ne pas | Verse |
10. Importer les expressions
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 }