Ten przewodnik zawiera zbiór zalecanych standardów dotyczących pisania spójnego i łatwego w utrzymaniu kodu. Przestrzegając zawartych w nim wytycznych, twórcy mogą zwiększyć czytelność swojego kodu, ograniczyć ilość błędów i ułatwić współpracę. Znormalizowany styl kodu jest konieczny, aby kod był zrozumiały i łatwy w utrzymaniu dla twórców pracujących przy projekcie zarówno w tej chwili, jak i w przyszłości.
W przewodniku zawarto jedynie zalecenia, jednak to zespół musi zadecydować, czy z nich skorzystać.
1. Wspólne konwencje nazewnictwa
Nazewnictwo ma kluczowe znaczenie dla czytelności kodu i łatwości jego utrzymania. Staraj się stosować jednakowy styl nazewnictwa w całym kodzie.
1.1 Rozwiązania zalecane
IsX: Często stosowane przy nadawaniu nazw zmiennym logicznym w celu zadania pytania (np. IsEmpty).OnX: Funkcja dopuszczająca możliwość przeciążenia wywoływana przez framework.SubscribeX: subskrybuje zdarzenie frameworku o nazwie X, często przekazując do niego funkcję OnX.MakeC: Utwórz instancję klasy c bez przeciążania konstruktora c.CreateC: Utworzenie instancji klasy c poprzez rozpoczęcie jej logicznego czasu życia.DestroyC: Zakończenie czasu życia logiki.C:c: Jeśli pracujesz z pojedynczą instancją klasy c, możesz nazwać ją C.
1.2 Rozwiązania, których należy unikać
Upiększanie nazw typów. Nazwij coś po prostu
thing, a niething_typelubthing_class.Udekoruj wartości wyliczeń. Zamiast
color := enum{COLOR_Red, COLOR_Green}, użyjcolor := enum{Red, Green}.
2. Nazwy
2.1 Typy zapisujemy jako ciągi małych liter typu lower_snake_case
Nazwy typów należy zawsze zapisywać w postaci ciągów słów pisanych małymi literami, np. lower_snake_case. Dotyczy to wszystkich typów: struktur, klas, definicji typów, cech/interfejsów, wyliczeń itp.
my_new_type := class2.2 Interfejsy są przymiotnikami
W miarę możliwości interfejsy powinny mieć postać przymiotników, np. printable czy enumerable oznaczających odpowiednio możliwość drukowania i utworzenia wyliczenia. Jeśli zastosowanie przymiotnika byłoby niewłaściwe, zamiast tego dodaj do nazwy przyrostek _interface.
my_new_thing_interface := interface2.3 Wszystko inne od wielkiej litery, bez spacji (PascalCase)
Wszystkie inne nazwy powinny być zapisywane od wielkiej litery bez spacji (PascalCase). Moduły, zmienne składowe, parametry, metody itd.
MyNewVariable:my_new_type = …2.4 Typy parametryczne
W nazwach typów parametrycznych stosuj t lub thing, gdzie thing objaśnia, co dany typ ma reprezentować. Na przykład:
Send(Payload:payload where payload:type)Wysyłasz sparametryzowane dane,Payload, dowolnegotypu ładunku.* Jeśli występuje więcej niż jeden typ parametryczny, unikaj stosowania pojedynczych liter, takich jak
t,u,gNigdy nie używaj przyrostka
_t.
3. Formatowanie
Ważne, aby w całej bazie kodu stosować jednakowe formatowanie. Dzięki temu kod będzie bardziej czytelny i zrozumiały dla ciebie oraz innych twórców. Styl formatowania należy dobrać pod kątem konkretnego projektu.
W ramach przykładu zachowania spójności można wybrać jeden z poniższych formatów użycia spacji i stosować go w całej bazie kodu:
MyVariable : int = 5
MyVariable:int = 53.1 Wcięcia
Wcięcia twórz za pomocą czterech spacji, nigdy znaków tabulacji.
Bloki kodu powinny wykorzystywać bloki z wcięciami (z odstępami) zamiast nawiasów klamrowych:
Versemy_class := class: Foo():void = Print("Hello World")Wyjątek stanowią wyrażenia jednowierszowe, takie jak
opcja{a},my_class{A := b}itp.
3.2 Spacje
Stosuj spacje wokół operatorów, chyba że ze względu na kontekst dobrym pomysłem jest zachowanie zwięzłości kodu. Dodawaj nawiasy, aby w jawny sposób definiować kolejność operacji.
VerseMyNumber := 4 + (2 * (a + b))Nie dodawaj spacji na początkach ani na końcach wyrażeń w nawiasach. Wiele wyrażeń w nawiasach powinno się rozdzielać pojedynczą spacją.
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)Nie rozdzielaj identyfikatora i typu; dodawaj spację wokół operatora przypisania
=. Dodawaj spacje wokół definicji typu i operatorów inicjowania stałych (:=).VerseMyVariable:int = 5 MyVariable := 5 my_type := classTe same zalecenia należy stosować w odniesieniu do nawiasów, identyfikatorów i odstępów między typami w sygnaturach funkcji.
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 Znaki podziału wiersza
Stosuj format wielowierszowy, z odstępami, aby wstawić znak podziału wiersza.
Rozwiązania zalecane
VerseMyTransform := transform: Translation := vector3: Left := 100.0 Up := 200.0 Forward := 300.0 Rotation := rotation: Pitch := 0.0 Yaw := 0.0 Roll := 0.0Większa czytelność i łatwiejsza edycja.
Rozwiązania, których należy unikać
VerseMyTransform := transform{Translation := vector3{Left := 100.0, Up := 200.0, Forward := 300.0}, Rotation := rotation{...}}Czytanie kodu jednowierszowego jest trudne.
Definiuj wyliczenia w formie wielowierszowej, z odstępami, jeśli wymagają one wstawienia komentarzy do poszczególnych pozycji lub jeśli musisz wstawić znak podziału wiersza.
Verseenum: Red, # Desc1 Blue, # Desc2
3.4 Nawiasy
Nie stosuj nawiasów w definicjach klas niedziedziczących.
Rozwiązania zalecane | Verse |
Rozwiązania, których należy unikać | Verse |
3.5 Unikanie notacji kropka-spacja
Unikaj używania notacji kropka-spacja ". " zamiast nawiasów. To utrudnia wizualnie analizowanie spacji i może stanowić potencjalne źródło nieporozumień.
Rozwiązania, których należy unikać | Verse |
Rozwiązania, których należy unikać | Verse |
4. Funkcje
4.1 Wywnioskowany zwracany wynik jako domyślny
Funkcje zwracają wartość ich ostatniego wyrażenia. Wykorzystaj ją jako wywnioskowany zwracany wynik.
Sqr(X:int):int =
X * X # Implicit returnW przypadku użycia dowolnych jawnych wartości zwracanych, wszystkie zwracane wartości w funkcji powinny być jawne.
4.2 Funkcje GetX powinny być następujące:
Gettery lub funkcje o podobnej semantyce, które mogą nie zwrócić prawidłowych wartości, powinny być oznaczone jako <decides><transacts> i zwracać typ niebędący opcją. Wywoływacz powinien obsługiwać potencjalne niepowodzenie.
GetX()<decides><transacts>:xWyjątek stanowią funkcje, które wymagają bezwarunkowego zapisu w var. Niepowodzenie spowoduje wycofanie mutacji, więc muszą użyć logic lub option dla swojego typu zwracanego.
4.3 Metody rozszerzające lepsze od funkcji pojedynczych parametrów
Zamiast funkcji z parametrem jednego typu lepiej stosować metody rozszerzające.
Taki sposób postępowania ułatwia korzystanie z funkcji IntelliSense. Wpisanie MyVector.Normalize() zamiast Normalize(MyVector) pozwala funkcji podpowiadać nazwy z każdym wpisywanym znakiem nazwy metody.
Rozwiązania zalecane | Verse |
Rozwiązania, których należy unikać | Verse |
5. Sprawdzanie niepowodzeń
5.1 Ograniczenie liczby wyrażeń zawodnych w jednym wierszu do trzech
Ograniczaj liczbę kontroli warunkowych / wyrażeń zawodnych w jednym wierszu do maksymalnie trzech.
Verseif (Damage > 10, Player := FindRandomPlayer[], Player.GetFortCharacter[].IsAlive[]): EliminatePlayer(Player)Stosuj wyrażenie
ifw postaci z nawiasami(), gdy liczba warunków jest mniejsza niż trzy warunki.
Rozwiązania zalecane | Verse | Dzięki temu kod jest zwięzły, ale czytelny. |
Rozwiązania, których należy unikać | Verse | Niepotrzebne dzielenie kodu na wiele wierszy w sposób, który nie zwiększa jego czytelności. |
W przypadku użycia więcej niż dwóch słów dla każdego wyrażenia, często bardziej czytelne będzie zastosowanie maksymalnie dwóch wyrażeń w jednym wierszu.
Verseif (Player := FindAlivePlayer[GetPlayspace().GetPlayers()], Team := FindEmptyTeam[GetPlayspace().GetTeamCollection().GetTeams()]): AddPlayerToTeam(Player, Team)Można również zastosować taką zasadę, jak w pojedynczym wierszu kontekstu niepowodzenia, aby nie używać więcej niż dziewięciu słów. Po przekroczeniu limitu stosuj zapisy wielowierszowe z odstępami.
Rozwiązania zalecane | Verse | Podział na wiele wierszy poprawia czytelność tekstu i ułatwia zrozumienie kontekstu. |
Rozwiązania, których należy unikać | Verse | Tekst jest trudny do sparsowania. |
Oceń, czy zgrupowanie wielu warunków zawodnych do pojedynczej funkcji
<decides>zwiększy czytelność kodu i ułatwi jego ponowne wykorzystanie. Zwróć uwagę, że jeśli kod jest używany wyłącznie w jednym miejscu, komentarz "section" bez funkcji ad hoc może wystarczyć.Verseif: Player := FindRandomPlayer[] IsAlive[Player] not IsInvulnerable[Player] Character := Player.GetFortCharacter[] Character.GetHealth < 10 then: EliminatePlayer(Player)Można zapisać jako:
VerseGetRandomPlayerToEliminate()<decides><transacts>:player= Player := FindRandomPlayer[] IsAlive[Player] not IsInvulnerable[Player] Character := Player.GetFortCharacter[] Character.GetHealth < 10 Player if (Player := GetRandomPlayerToEliminate[]): Eliminate(Player)Te same wytyczne obowiązują w przypadku wyrażeń w pętlach
for. Na przykład: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() LightDeviceLepiej jako:
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 Grupowanie ze sobą zależnych wyrażeń zawodnych
Gdy warunek w kontekście niepowodzenia zależy od powodzenia poprzedniego kontekstu niepowodzenia, w miarę możliwości połącz ze sobą dwa warunki w jednym kontekście niepowodzenia, korzystając przy tym z wytycznych w punkcie 5.1.
Poprawi to lokalność kodu, co ułatwi zrozumienie jego logiki oraz debugowanie.
Rozwiązania zalecane | Verse | Warunki zależne lub powiązane są zgrupowane. |
Rozwiązania zalecane | Verse | Warunki zależne lub powiązane są zgrupowane. |
Rozwiązania, których należy unikać | Verse | Niepotrzebne wcięcia mogą utrudnić śledzenie przepływu. |
Rozwiązania, których należy unikać | Verse | Niepotrzebne wcięcia mogą utrudnić śledzenie przepływu. |
Dzielenie kontekstów niepowodzenia jest dopuszczalne, jeśli zajmujesz się każdym potencjalnym niepowodzeniem (lub grupami niepowodzeń) oddzielnie.
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. Hermetyzacja
6.1 Preferowanie interfejsów zamiast klas
W uzasadnionych przypadkach stosuj interfejsy zamiast klas. Pomaga to ograniczyć zależności implementacji i umożliwia użytkownikom wprowadzanie implementacji, które mogą być wykorzystane przez framework.
6.2 Preferowanie dostępu prywatnego i ograniczania zakresu
W większości przypadków elementy członkowskie klas powinny być oznaczone jako 'private'.
Zakres metod modułów i klas powinien być ograniczony w możliwie jak najbardziej restrykcyjny sposób, w miarę możliwości za pomocą identyfikatorów <internal> lub <private>.
7. Zdarzenia
7.1 Przyrostek Event w zdarzeniach i przedrostek On w handlerach
Zdarzenia, które można subskrybować, lub nazwy list delegatów powinny mieć przyrostek 'Event', a nazwy handlerów zdarzeń powinno się poprzedzać przedrostkiem 'On'.
MyDevice.JumpEvent.Subscribe(OnJump)8. Współbieżność
8.1 Unikanie dekorowania funkcji dopiskiem Async
Unikaj dekorowania funkcji <suspends> dopiskiem Async lub podobnymi hasłami.
Rozwiązania zalecane | Verse |
Rozwiązania, których należy unikać | Verse |
Dopuszczalne jest dodawanie przedrostka Await do funkcji <suspends>, która wewnętrznie oczekuje na jakieś zdarzenie.
To może określić sposób, w jaki interfejs API ma być używany.
AwaitGameEnd()<suspends>:void=
# Setup other things before awaiting game end…
GameEndEvent.Await()
OnBegin()<suspends>:void =
race:
RunGameLoop()
AwaitGameEnd()9. Atrybuty
9.1 Oddzielanie atrybutów
Dodawaj atrybuty w oddzielnych wierszach. Dzięki temu kod jest bardziej czytelny, zwłaszcza jeśli doda się wiele atrybutów do tego samego identyfikatora.
Rozwiązania zalecane | Verse |
Rozwiązania, których należy unikać | Verse |
10. Importowanie wyrażeń
10.1 Alfabetyczne sortowanie wyrażeń importu
Na przykład:
using { /EpicGames.com/Temporary/Diagnostics }
using { /EpicGames.com/Temporary/SpatialMath }
using { /EpicGames.com/Temporary/UI }
using { /Fortnite.com/UI }
using { /Verse.org/Simulation }