Este guia apresenta um conjunto de padrões recomendados para escrever um código consistente e de fácil manutenção. Seguindo essas diretrizes, os desenvolvedores podem melhorar a legibilidade do código, reduzir erros e facilitar a colaboração. É necessário ter um estilo de código padronizado para garantir que os desenvolvedores atuais e futuros que trabalhem em um projeto possam facilmente entender e manter.
Este guia apresenta recomendações, mas, em última análise, a escolha é da sua equipe.
1. Padrões de nomenclatura comuns
A nomenclatura é essencial para um código legível e sustentável. Tente ser consistente no estilo de nomenclatura em todo o código.
1.1 O que fazer
-
IsX
: Usada frequentemente para nomear variáveis lógicas ao fazer uma pergunta (por exemplo, "IsEmpty"). -
OnX
: Uma função sobrecarregável chamada pela framework. -
SubscribeX
: Inscrever-se no evento de framework chamado "X", geralmente passando uma função "OnX" para ele. -
MakeC
: Criar uma instância da classe C sem sobrecarregar o construtor C. -
CreateC
: Criar uma instância da classe C, iniciando seu tempo de vida lógico. -
DestroyC
: Encerrar a vida útil da lógica. -
C:c
: Se estiver trabalhando com uma única instância da classe C, não há problema em chamá-la de C.
1.2 O que não fazer
-
Decorar nomes de tipos. Basta chamá-la de
thing
, nãothing_type
outhing_class
. -
Decorar valores de enumeração. Em vez de
color := enum{COLOR_Red, COLOR_Green}
, usarcolor := enum{Red, Green}
.
2. Nomes
2.1 Tipos usam "lower_snake_case"
Nomes de tipos devem ser sempre lower_snake_case
. Isso inclui todos os tipos: structs, classes, typedefs, traits/interfaces, enums, etc.
my_new_type := class
2.2 Interfaces são adjetivos
Interfaces devem ser adjetivos sempre que possível, como imprimível, enumerável. Quando adjetivos não parecerem corretos, acrescente _interface
ao nome.
my_new_thing_interface := interface
2.3 "PascalCase" para todo o restante
Todos os outros nomes devem ser usar o formato "PascalCase". Módulos, variáveis de membros, parâmetros, métodos e assim por diante.
MyNewVariable:my_new_type = …
2.4 Tipos paramétricos
-
Nomeie tipos paramétricos "t" ou "thing", em que "thing" explica o que o tipo deve representar. Por exemplo:
Send(Payload:payload where payload:type)
Você está enviando alguns dados parametrizados,Payload
, de qualquer tipopayload
. -
Se houver mais de um tipo paramétrico, evite usar letras únicas, como
t
,u
,g
-
Nunca use o sufixo
_t
.
3. Formatação
É importante manter-se consistente com a formatação em toda a base do código. Assim, o código fica mais fácil para você e outros desenvolvedores lerem e entenderem. Escolha um estilo de formatação que funcione para o projeto.
Como exemplo de consistência, você pode escolher um dos seguintes formatos de espaçamento e usá-lo em toda a base do código:
MyVariable : int = 5
MyVariable:int = 5
3.1 Recuo
-
Use quatro espaços para o recuo, nunca tabulações.
-
Os blocos de código devem usar blocos recuados (espaçados) em vez de colchetes (chaves):
my_class := class: Foo():void = Print("Hello World")
- Exceto ao escrever expressões de linha única como
option{a}
,my_class{A := b}
, etc.
- Exceto ao escrever expressões de linha única como
3.2 Espaços
- Use espaços ao redor dos operadores, a menos que faça sentido manter o código compacto para o seu contexto. Adicione chaves para definir explicitamente a ordem das operações.
MyNumber := 4 + (2 * (a + b))
-
Não adicione espaços ao início nem ao final dos colchetes. Várias expressões dentro de colchetes devem ser separadas por um único espaço.
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)
- Mantenha o identificador e o tipo juntos e adicione um espaço ao redor do operador
=
de atribuição. Adicione um espaço ao redor de definições de tipos e operadores de inicialização constante (:=
).MyVariable:int = 5 MyVariable := 5 my_type := class
- Siga as mesmas recomendações para colchetes, identificadores e espaçamento de tipos para assinaturas de função.
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 Quebras de linha
-
Use um formulário espaçado de várias linhas para inserir uma quebra de linha.
O que fazer MyTransform := transform: Translation := vector3: X := 100.0 Y := 200.0 Z := 300.0 Rotation := rotation: Pitch := 0.0 Yaw := 0.0 Roll := 0.0
Mais legível e fácil de editar. O que não fazer MyTransform := transform{Translation := vector3{X := 100.0, Y := 200.0, Z := 300.0}, Rotation := rotation{...}}
Difícil de ler em uma única linha.
- Defina enumerações em formato multilinha espaçado se elas precisarem de comentários por enumeração ou se você precisar inserir uma quebra de linha.
enum: Red, # Desc1 Blue, # Desc2
3.4 Parênteses
Não use colchetes para definições de classes não herdadas.
O que fazer |
|
O que não fazer |
|
3.5 Evite a notação de ponto-espaço
Evite usar a notação de ponto-espaço ". " no lugar de chaves. Ela dificulta a análise visual de espaços em branco e é uma fonte potencial de confusão.
O que não fazer |
|
O que não fazer |
|
4. Funções
4.1 Retorno implícito por padrão
Funções retornam o valor de sua última expressão. Use isso como um retorno implícito.
Sqr(X:int):int =
X * X # Retorno implícito
Se estiver usando qualquer retorno explícito, todos os retornos na função deverão ser explícitos.
4.2 As funções "GetX" devem ser ""
Getters ou funções com semântica semelhante que podem falhar ao retornar valores válidos devem ser marcadas como <decides><transacts>
e retornam um tipo que não é opção. O chamador deve lidar com possíveis falhas.
GetX()<decides><transacts>:x
Uma exceção são as funções que precisam gravar incondicionalmente em uma var
. A falha reverteria a mutação, então é necessário usar logic
ou option
para seu tipo de retorno.
4.3 Prefira métodos de extensão a funções de parâmetros únicos
Use métodos de extensão em vez de uma função com um único parâmetro digitado.
Fazer isso ajuda o IntelliSense. Ao digitar MyVector.Normalize()
em vez de Normalize(MyVector)
, pode sugerir nomes com cada caractere do nome do método que você digitar.
O que fazer |
|
O que não fazer |
|
5. Verificações de falha
5.1 Limite a contagem de expressões falíveis de linha única a três
-
Limite as verificações condicionais/expressões passíveis de falha em uma única linha a um máximo de três.
if (Damage > 10, Player := FindRandomPlayer[], Player.GetFortCharacter[].IsAlive[]): EliminatePlayer(Player)
-
Use a forma de
if
com parênteses()
quando o número de condições for menor que três.
O que fazer |
|
Mantém o código conciso, mas legível. |
O que não fazer |
|
Divide desnecessariamente o código em várias linhas sem melhorias de legibilidade. |
-
Se estiver usando mais de duas palavras para cada expressão, um máximo de duas expressões em uma única linha geralmente será mais legível.
if (Player := FindAlivePlayer[GetPlayspace().GetPlayers()], Team := FindEmptyTeam[GetPlayspace().GetTeamCollection().GetTeams()]): AddPlayerToTeam(Player, Team)
-
Você também pode aplicar a regra como em um contexto de falha em uma única linha. Não use mais de nove palavras. Quando ultrapassar o limite, use o formulário multilinha espaçado.
O que fazer |
|
A leitura do texto é melhor, e o contexto é compreensível em várias linhas. |
O que não fazer |
|
O texto é difícil de analisar. |
-
Avalie se o agrupamento de várias condições falíveis em uma única função
<decides>
tornaria o código mais fácil de ler e reutilizar. Observe que, se o código for usado apenas em um local, um comentário e "seção" sem uma função específica pode ser suficiente.if: Player := FindRandomPlayer[] IsAlive[Player] not IsInvulnerable[Player] Character := Player.GetFortCharacter[] Character.GetHealth < 10 then: EliminatePlayer(Player)
-
Pode ser reescrita como:
GetRandomPlayerToEliminate()<decides><transacts>:player= Player := FindRandomPlayer[] IsAlive[Player] not IsInvulnerable[Player] Character := Player.GetFortCharacter[] Character.GetHealth < 10 Jogador if (Player := GetRandomPlayerToEliminate[]): Eliminate(Player)
-
A mesma diretriz se aplica a expressões em loops
for
. Por exemplo:set Lights = for (ActorIndex -> TaggedActor : TaggedActors, LightDevice := customizable_light_device[TaggedActor], ShouldLightBeOn := LightsState[ActorIndex]): Logger.Print("Adicionando luz no índice {ActorIndex} com State:{if (ShouldLightBeOn?) then "On" else "Off"}") if (ShouldLightBeOn?) then LightDevice.TurnOn() else LightDevice.TurnOff() LightDevice
-
Fica melhor assim:
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 Agrupe expressões de falha dependentes
Quando uma condição em um contexto de falha depende do êxito de um contexto de falha anterior, mantenha as duas condições juntas no mesmo contexto de falha, quando possível, e siga a diretriz 5.1.
Assim, você melhora a localidade do código, o que simplifica a compreensão lógica e a depuração.
O que fazer |
|
Condições dependentes ou relacionadas são agrupadas. |
O que fazer |
|
Condições dependentes ou relacionadas são agrupadas. |
O que não fazer |
|
A endentação desnecessária pode dificultar o acompanhamento do fluxo. |
O que não fazer |
|
A endentação desnecessária pode dificultar o acompanhamento do fluxo. |
É aceitável dividir contextos de falha quando você lida com cada falha potencial (ou grupos de falha) separadamente.
if (Player := FindPlayer[]):
if (Player.IsVulnerable[]?):
EliminatePlayer(Player)
else:
Print("O jogador é invulnerável, não pode ser eliminado.")
else:
Print("Não é possível encontrar o jogador. Este é um erro de configuração.")
6. Encapsulamento
6.1 Prefira interfaces a classes
Use interfaces em vez de classes onde for razoável. Isso ajuda a reduzir as dependências de implementação e permite que os usuários forneçam implementações que podem ser usadas pela framework.
6.2 Prefira o acesso privado e restrinja o escopo
Os membros da classe devem ser "private" na maioria dos casos.
Métodos de classe e módulo devem ter o escopo o mais restritivo possível (<internal>
ou <private>
) quando apropriado.
7. Eventos
7.1 Eventos de sufixo com manipuladores de eventos e prefixos com "On"
Eventos assináveis ou nomes de listas de delegados devem ser sufixados com "Event", e nomes de manipuladores de eventos devem ser prefixados com "On".
MyDevice.JumpEvent.Subscribe(OnJump)
8. Simultaneidade
8.1 Não decorar funções " com Async
Evite decorar funções <suspends>
com Async
ou termos semelhantes.
O que fazer |
|
O que não fazer |
|
É aceitável adicionar o prefixo Await
a uma função <suspends>
que espera internamente que algo aconteça.
Isso pode esclarecer como uma API deve ser usada.
AwaitGameEnd()<suspends>:void=
# Configurar outras ações antes de aguardar o final do jogo…
GameEndEvent.Await()
OnBegin()<suspends>:void =
race:
RunGameLoop()
AwaitGameEnd()
9. Atributos
9.1 Separe atributos
Coloque os atributos em uma linha separada. O código fica mais legível, especialmente se vários atributos são adicionados ao mesmo identificador.
O que fazer |
|
O que não fazer |
|
10. Expressões de importação
10.1 Classifique expressões de importação em ordem alfabética
Por exemplo:
using { /EpicGames.com/Temporary/Diagnostics }
using { /EpicGames.com/Temporary/SpatialMath }
using { /EpicGames.com/Temporary/UI }
using { /Fortnite.com/UI }
using { /Verse.org/Simulation }