Dieser Leitfaden enthält eine Reihe von empfohlenen Standards für das Schreiben von konsistentem Code, der leicht zu pflegen ist. Durch die Einhaltung dieser Richtlinien können Entwickler die Lesbarkeit des Codes verbessern, Fehler reduzieren und die Zusammenarbeit erleichtern. Ein standardisierter Codestil ist notwendig, um sicherzustellen, dass der Code sowohl für aktuelle als auch für zukünftige Entwickler, die an einem Projekt arbeiten, einfach zu verstehen und zu pflegen ist.
Dieser Leitfaden enthält Empfehlungen, aber letztlich liegt die Entscheidung bei deinem Team.
1. Gebräuchliche Benennungsschemata
Die Benennung ist entscheidend für lesbaren und wartbaren Code. Versuche, in deinem Code eine einheitliche Benennung zu verwenden.
1.1 Do‘s
IsX: Wird oft zur Benennung von Logikvariablen verwendet, um eine Frage zu stellen (z. B. IsEmpty).OnX: Eine überladbare Funktion, die vom Framework aufgerufen wird.SubscribeX: Abonniert ein Framework-Event mit dem Namen X, oft mit Übergabe einer OnX-Funktion an dieses Ereignis.MakeC: Erstellt eine Instanz der Klasse c, ohne den c-Constructor zu überladen.CreateC: Erstellt eine Instanz der Klasse c, die ihr logische Lebensdauer beginnt.DestroyC: Beendet die logische Lebensdauer.C:c: Wenn du mit einer einzelnen Instanz der Klasse c arbeitest, ist es in Ordnung, sie C zu nennen.
1.2 Don'ts
Anhängen von Typnamen. Nenne es einfach
thing, nichtthing_typeoderthing_class.Anhängen von Enum-Werten. Nicht
color := enum{COLOR_Red, COLOR_Green}, verwendecolor := enum{Red, Green}.
2. Namen
2.1 Typen verwenden lower_snake_case
Typnamen sollten immer lower_snake_case sein. Dazu gehören alle Typen: Structs, Klassen, Typedefs, Traits/Interfaces, Enums, usw.
my_new_type := class2.2 Interfaces sind Adjektive
Interfaces sollten wenn möglich Adjektive sein, wie z.B. printable, enumerable. Wenn Adjektive nicht geeignet erscheinen, füge stattdessen _interface an den Namen an.
my_new_thing_interface := interface2.3 PascalCase für alles andere
Alle anderen Namen sollten PascalCase sein. Module, Mitgliedsvariablen, Parameter, Methoden und so weiter.
MyNewVariable:my_new_type = …2.4 Parametrische Typen
Benenne parametrische Typen t oder thing, wobei thing erklärt, was der Typ darstellen soll. Beispiel:
Send(Payload:payload where payload:type)Du sendest einige parametrisierte Daten,Payload, von einem beliebigenpayload-Typ.Wenn es mehr als einen parametrischen Typ gibt, vermeide die Verwendung einzelner Buchstaben, wie
t,u,gVerwende niemals das Suffix
_t.
3. Formatierung
Es ist wichtig, dass du bei der Formatierung deiner gesamten Codebasis konsistent bleibst. Dadurch wird der Code für dich und andere Entwickler einfacher zu lesen und zu verstehen. Wähle einen Formatierungsstil, der für das Projekt geeignet ist.
Als Beispiel für eine konsistente Vorgehensweise könntest du eines der folgenden Abstandsformate wählen und es in der gesamten Codebasis verwenden:
MyVariable : int = 5
MyVariable:int = 53.1 Einrückung
Verwende vier Leerzeichen für die Einrückung, niemals Tabulatoren.
Codeblöcke sollten eingerückte Blöcke (spaced) und keine geschweiften Klammern (braced) verwenden:
Versemy_class := class: Foo():void = Print("Hello World")Eine Ausnahme ist das Schreiben von einzeiligen Ausdrücken wie
option{a},my_class{A := b}usw.
3.2 Leerzeichen
Verwende Leerzeichen um Operatoren herum, es sei denn, es ist sinnvoll, den Code für seinen Kontext kompakt zu halten. Füge Klammern hinzu, um die Reihenfolge der Operationen explizit festzulegen.
VerseMyNumber := 4 + (2 * (a + b))Füge keine Leerzeichen am Anfang und Ende von Klammern ein. Mehrere Ausdrücke innerhalb von Klammern sollten durch ein einzelnes Leerzeichen getrennt werden.
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)Halte Bezeichner und Typ zusammen. Füge um den Zuweisungsoperator
=herum ein Leerzeichen hinzu. Füge um Typdefinitionen und konstante Initialisierungsoperatoren herum (:=) ein Leerzeichen hinzu.VerseMyVariable:int = 5 MyVariable := 5 my_type := classBefolge die gleichen Empfehlungen für Klammern, Bezeichner und Typenabstände für Funktionssignaturen.
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 Zeilenumbrüche
Verwende eine beabstandete, mehrzeilige Form, um einen Zeilenumbruch einzufügen.
Do‘s
VerseMyTransform := transform: Translation := vector3: Left := 100.0 Up := 200.0 Forward := 300.0 Rotation := rotation: Pitch := 0.0 Yaw := 0.0 Roll := 0.0Besser lesbar und leichter zu bearbeiten.
Don‘t
VerseMyTransform := transform{Translation := vector3{Left := 100.0, Up := 200.0, Forward := 300.0}, Rotation := rotation{...}}Schwer zu lesen in einer einzigen Zeile
Definiere Enums in beabstandeter, mehrzeiliger Form, wenn sie Kommentare pro Enumeration benötigen oder wenn du einen Zeilenumbruch einfügen musst.
Verseenum: Red, # Desc1 Blue, # Desc2
3.4 Klammern
Verwende keine Klammern für nicht vererbende Klassendefinitionen.
Do‘s | Verse |
Don‘t | Verse |
3.5 Vermeide die Punkt-Leerzeichen-Schreibweise
Vermeide die Verwendung von Punkt-Leerzeichen „.“-Notation anstelle von Klammern. Dies macht es visuell schwieriger, Leerzeichen zu erkennen und ist eine potenzielle Quelle der Verwirrung.
Don‘t | Verse |
Don‘t | Verse |
4. Funktionen
4.1 Implizite Rückgabe als Standard
Funktionen geben ihren letzten Ausdruckswert zurück. Verwende das als implizite Rückgabe.
Sqr(X:int):int =
X * X # Implicit returnWenn explizite Rückgaben verwendet werden, sollten alle Rückgaben in der Funktion explizit sein.
4.2 GetX-Funktionen sollten sein
Getter oder Funktionen mit ähnlicher Semantik, die möglicherweise keine gültigen Werte zurückgeben, sollten mit <decides><transacts> gekennzeichnet werden und einen Typ ohne Option zurückgeben. Der Aufrufer sollte mit möglichen Fehlern umgehen.
GetX()<decides><transacts>:xEine Ausnahme bilden Funktionen, die bedingungslos in eine var schreiben müssen. Ein Fehlschlag würde die Mutation zurücksetzen, daher müssen sie als Rückgabe-Typ logic oder option verwenden.
4.3 Bevorzugung von Erweiterungsmethoden gegenüber Ein-Parameter-Funktionen
Verwende Erweiterungsmethoden anstelle einer Funktion mit einem einzigen typisierten Parameter.
Dies hilft Intellisense. Durch die Eingabe von MyVector.Normalize() anstelle von Normalize(MyVector) kann es mit jedem Zeichen des eingegebenen Methodennamens Namen vorschlagen.
Do‘s | Verse |
Don‘t | Verse |
5. Fehlerprüfungen
5.1 Begrenzung der Anzahl der einzeiligen fehlbaren Ausdrücke auf drei
Begrenze bedingte Prüfungen/fehlbare Ausdrücke in einer einzigen Zeile auf maximal drei.
Verseif (Damage > 10, Player := FindRandomPlayer[], Player.GetFortCharacter[].IsAlive[]): EliminatePlayer(Player)Verwende die Form
ifmit Klammern(), wenn die Anzahl der Bedingungen kleiner als drei ist.
Do‘s | Verse | Hält den Code kurz, aber lesbar. |
Don‘t | Verse | Unnötige Aufteilung des Codes auf mehrere Zeilen, ohne dass die Lesbarkeit verbessert wird. |
Wenn du mehr als zwei Wörter für jeden Ausdruck verwendest, sind maximal zwei Ausdrücke in einer Zeile oft besser lesbar.
Verseif (Player := FindAlivePlayer[GetPlayspace().GetPlayers()], Team := FindEmptyTeam[GetPlayspace().GetTeamCollection().GetTeams()]): AddPlayerToTeam(Player, Team)Du kannst die Regel auch in einem Fehlerkontext in einer einzigen Zeile anwenden, verwende nicht mehr als neun Wörter. Bei Überschreitung der Grenze ist die mehrzeilige Form mit Abstand zu verwenden.
Do‘s | Verse | Der Text liest sich besser und der Kontext ist über mehrere Zeilen verständlich. |
Don‘t | Verse | Der Text ist schwer zu lesen. |
Es ist zu prüfen, ob die Zusammenfassung mehrerer fehlbarer Bedingungen in einer einzigen
<decides>-Funktion die Lesbarkeit und Wiederverwendung des Codes erleichtern würde. Wenn der Code nur an einer Stelle verwendet wird, kann ein "section"-Kommentar ohne Ad-hoc-Funktion ausreichend sein.Verseif: Player := FindRandomPlayer[] IsAlive[Player] not IsInvulnerable[Player] Character := Player.GetFortCharacter[] Character.GetHealth < 10 then: EliminatePlayer(Player)Kann umgeschrieben werden als:
VerseGetRandomPlayerToEliminate()<decides><transacts>:player= Player := FindRandomPlayer[] IsAlive[Player] not IsInvulnerable[Player] Character := Player.GetFortCharacter[] Character.GetHealth < 10 Player if (Player := GetRandomPlayerToEliminate[]): Eliminate(Player)Derselbe Leitfaden gilt für Ausdrücke in
for-Schleifen. Beispiel: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() LightDeviceBesser als:
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 Gruppieren von abhängigen fehlbaren Ausdrücken
Wenn eine Bedingung in einem Fehlerkontext davon abhängt, dass ein vorhergehender Fehlerkontext erfolgreich ist, sollten die beiden Bedingungen, wenn möglich, im selben Fehlerkontext bleiben und die Richtlinie 5.1 befolgt werden.
Dadurch wird die Lokalität des Codes verbessert, was das logische Verständnis und die Fehlersuche vereinfacht.
Do‘s | Verse | Abhängige oder verwandte Bedingungen werden gruppiert. |
Do‘s | Verse | Abhängige oder verwandte Bedingungen werden gruppiert. |
Don‘t | Verse | Unnötige Einrückungen können die Übersichtlichkeit des Textes beeinträchtigen. |
Don‘t | Verse | Unnötige Einrückungen können die Übersichtlichkeit des Textes beeinträchtigen. |
Es ist akzeptabel, Fehlerkontexte aufzuteilen, wenn du jeden potenziellen Fehler (oder Fehlergruppen) separat behandelst.
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. Verkapselung
6.1 Bevorzuge Interfaces gegenüber Klassen
Verwende Interfaces anstelle von Klassen, wo es sinnvoll ist. Dies trägt zur Verringerung der Implementierungsabhängigkeiten bei und ermöglicht es den Benutzern, Implementierungen bereitzustellen, die vom Framework verwendet werden können.
6.2 Bevorzuge privaten Zugriff und schränke den Umfang ein
Klassenmitglieder sollten in den meisten Fällen "privat" sein.
Methoden von Klassen und Modulen sollten so restriktiv wie möglich skaliert werden – <internal> oder <private>, wo immer dies angebracht ist.
7. Events
7.1 Postfix-Events mit Event- und Präfix-Handlern mit On
Namen von abonnierbaren Events oder Delegatenlisten sollten mit dem Postfix `Event` versehen werden, und Namen von Event-Handlern sollten mit dem Präfix `On` versehen werden.
MyDevice.JumpEvent.Subscribe(OnJump)8. Parallelität
8.1 Verzichte darauf, Funktionen mit Async zu versehen
Vermeide es, <suspends>-Funktionen mit Async oder ähnlichen Begriffen zu versehen.
Do‘s | Verse |
Don‘t | Verse |
Es ist akzeptabel, das Präfix Await zu einer <suspends>-Funktion hinzuzufügen, die intern auf etwas wartet, das passieren soll.
Dies kann klären, wie eine API verwendet werden soll.
AwaitGameEnd()<suspends>:void=
# Setup other things before awaiting game end…
GameEndEvent.Await()
OnBegin()<suspends>:void =
race:
RunGameLoop()
AwaitGameEnd()9. Attribute
9.1 Attribute trennen
Setze die Attribute in eine eigene Zeile. Es ist besser lesbar, insbesondere wenn mehrere Attribute zur gleichen Kennung hinzugefügt werden.
Do‘s | Verse |
Don‘t | Verse |
10. Ausdrücke importieren
10.1 Sortiere Importausdrücke in alphabetischer Reihenfolge
Beispiel:
using { /EpicGames.com/Temporary/Diagnostics }
using { /EpicGames.com/Temporary/SpatialMath }
using { /EpicGames.com/Temporary/UI }
using { /Fortnite.com/UI }
using { /Verse.org/Simulation }