Esta guía brinda un conjunto de estándares recomendados para escribir un código coherente y fácil de mantener. Si siguen estas pautas, los desarrolladores pueden mejorar la legibilidad del código, reducir los errores y facilitar la colaboración. Un estilo de código estandarizado es necesario para garantizar que sea comprensible y fácil de mantener tanto para los desarrolladores actuales que trabajen en un proyecto como para los que se vayan a incorporar a él.
Esta guía ofrece recomendaciones, pero en última instancia la elección depende de tu equipo.
1. Patrones comunes de denominación
La denominación es crucial para que el código sea legible y fácil de mantener. Intenta ser coherente en el estilo de denominación a través de todo tu código.
1.1 Qué hacer
IsX: a menudo se usa para nombrar variables lógicas para hacer una pregunta (por ejemplo, IsEmpty).OnX: una función sobrecargable llamada por el marco de referencia.SubscribeX: se suscribe al evento del marco de referencia denominado X, que suele pasarle una función OnX.MakeC: Crea una instancia de la clase c sin sobrecargar el constructor c.CreateC: crea una instancia de la clase c, comenzando su vida útil lógica.DestroyC: finaliza el tiempo de vida de la lógica.C:c: si trabajas con una única instancia de la clase c, está bien llamarla C.
1.2 Qué no hacer
Decora los nombres de los tipos. Llámalo
thing, nothing_typenithing_class.Decorar los valores de enumeración. No
color := enum{COLOR_Red, COLOR_Green}, usacolor := enum{Red, Green}.
2. Nombres
2.1 Los tipos use lower_snake_case
Los nombres de tipo siempre deben escribirse en lower_snake_case. Esto incluye todos los tipos: estructuras, clases, definiciones de tipo, atributos/interfaces, enumeradores, etc.
my_new_type := class2.2 Las interfaces son adjetivos
Las interfaces deben ser adjetivos siempre que se pueda, como por ejemplo: printable, enumerable. Cuando los adjetivos no parezcan correctos, añade _interface al nombre.
my_new_thing_interface := interface2.3 Usa PascalCase en todo lo demás
Todos los demás nombres deben escribirse en PascalCase (palabras juntas que inician con mayúscula sin espacio entre ellas). Módulos, variables de miembro, parámetros, métodos, etc.
MyNewVariable:my_new_type = …2.4 Tipos paramétricos
Nombra los tipos paramétricos como t o thing, donde thing explica lo que se supone que representa el tipo. Por ejemplo:
Send(Payload:payload where payload:type)Estás enviando algunos datos parametrizados,Carga útil, de cualquier tipo decarga útil.Si hay más de un tipo paramétrico, evita utilizar letras únicas, como
t,u,g.Nunca uses el sufijo
_t.
3. Formato
Es importante mantener un formato coherente en todo el código. De este modo, el código será más fácil de leer y comprender, tanto para ti como para otros desarrolladores. Elige un estilo de formato que funcione para el proyecto.
Como ejemplo de coherencia, puedes elegir uno de los siguientes formatos de espaciado y utilizarlo en todo el código:
MyVariable : int = 5
MyVariable:int = 53.1 Indentación
Usa cuatro espacios para la sangría, nunca tabulaciones.
Los bloques de código deben usar bloques con sangría (con espacios) en lugar de llaves (con llaves):
Versemy_class := class: Foo():void = Print("Hello World")Excepto al escribir expresiones de una sola línea como
option{a},my_class{A := b}, etc.
3.2 Espacios
Utiliza espacios alrededor de los operadores, a menos que tenga sentido mantener el código compacto para su contexto. Añade corchetes para definir de forma explícita el orden de las operaciones.
VerseMyNumber := 4 + (2 * (a + b))No agregues espacios al principio ni al final de los corchetes. Las expresiones múltiples entre paréntesis deben separarse con un solo espacio.
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)Mantén el identificador y el tipo juntos; agrega un espacio alrededor del operador de asignación
=. Agrega un espacio alrededor de las definiciones de tipo y los operadores de inicialización de constantes (:=).VerseMyVariable:int = 5 MyVariable := 5 my_type := classSigue las mismas recomendaciones para paréntesis, identificadores y espaciado de tipos para las firmas de función.
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 Saltos de línea
Utiliza un formulario multilínea espaciado para insertar un salto de línea.
Qué hacer
VerseMyTransform := transform: Translation := vector3: Left := 100.0 Up := 200.0 Forward := 300.0 Rotation := rotation: Pitch := 0.0 Yaw := 0.0 Roll := 0.0Más legible y fácil de editar.
Qué no hacer
VerseMyTransform := transform{Translation := vector3{Left := 100.0, Up := 200.0, Forward := 300.0}, Rotation := rotation{...}}Difícil de leer en una sola línea.
Define las enumeraciones en formato multilínea con espacios si necesitan comentarios por enumeración o si necesitas insertar un salto de línea.
Verseenum: Red, # Desc1 Blue, # Desc2
3.4 Paréntesis
No utilices corchetes para las definiciones de clase no heredadas.
Qué hacer | Verse |
Qué no hacer | Verse |
3.5 Evita la notación con punto y espacio
Evita utilizar el punto y el espacio ". la notación " en vez de utilizar llaves. Esto dificulta visualmente el análisis de los espacios en blanco y facilita la confusión
Qué no hacer | Verse |
Qué no hacer | Verse |
4. Funciones
4.1 Retorno implícito por defecto
Las funciones devuelven el valor de su última expresión. Utilízalo como retorno implícito.
Sqr(X:int):int =
X * X # Implicit returnSi se utilizan retornos explícitos, todos los retornos de la función deben ser explícitos.
4.2 Las funciones GetX deberían ser
Los captadores o funciones con semántica similar que pueden no devolver valores válidos deben marcarse como <decides><transacts> y devolver un tipo que no sea de opción. El captador debe manejar el posible fallo.
GetX()<decides><transacts>:xUna excepción son las funciones que necesitan escribir incondicionalmente en una var. Un error revertiría la mutación, por lo que deben usar logic u option para su tipo de retorno.
4.3 Prefiere los métodos de extensión a las funciones de un solo parámetro
Utiliza métodos de extensión en lugar de una función con un único parámetro escrito.
Esto ayuda a Intellisense. Al escribir MyVector.Normalize() en lugar de Normalize(MyVector), puede sugerir nombres con cada personaje del nombre del método que escribas.
Qué hacer | Verse |
Qué no hacer | Verse |
5. Comprobación de fallo
5.1 Limita el recuento de expresiones falibles en una sola línea a un máximo de tres
Limita las comprobaciones condicionales/expresiones falibles en una sola línea a un máximo de tres.
Verseif (Damage > 10, Player := FindRandomPlayer[], Player.GetFortCharacter[].IsAlive[]): EliminatePlayer(Player)Utiliza la forma
ifcon paréntesis()cuando el número de condiciones sea inferior a tres.
Qué hacer | Verse | Mantiene el código conciso pero legible. |
Qué no hacer | Verse | Divide innecesariamente el código en varias líneas sin mejorar la legibilidad. |
Si utilizas más de dos palabras para cada expresión, un máximo de dos expresiones en una sola línea suele ser más legible.
Verseif (Player := FindAlivePlayer[GetPlayspace().GetPlayers()], Team := FindEmptyTeam[GetPlayspace().GetTeamCollection().GetTeams()]): AddPlayerToTeam(Player, Team)También puedes aplicar la regla como en un contexto de fallo en una sola línea: no utilices más de nueve palabras. Cuando supere el límite, utiliza la fórmula multilínea espaciada.
Qué hacer | Verse | El texto se lee mejor y el contexto es comprensible en varias líneas. |
Qué no hacer | Verse | El texto es difícil de analizar. |
Evalúa si agrupar varias condiciones de fallo en una sola función
<decides>facilitaría la lectura y la reutilización del código. Ten en cuenta que si el código solo se utiliza en un lugar, un comentario de "sección" sin una función ad hoc puede ser suficiente.Verseif: Player := FindRandomPlayer[] IsAlive[Player] not IsInvulnerable[Player] Character := Player.GetFortCharacter[] Character.GetHealth < 10 then: EliminatePlayer(Player)Puede reescribirse como:
VerseGetRandomPlayerToEliminate()<decides><transacts>:player= Player := FindRandomPlayer[] IsAlive[Player] not IsInvulnerable[Player] Character := Player.GetFortCharacter[] Character.GetHealth < 10 Player if (Player := GetRandomPlayerToEliminate[]): Eliminate(Player)La misma pauta se aplica a las expresiones en bucles
for. Por ejemplo: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() LightDeviceMejor como:
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 Agrupar expresiones de fallo dependientes
Cuando una condición de un contexto de fallo depende del éxito de un contexto de fallo anterior, mantén las dos condiciones juntas en el mismo contexto de fallo siempre que sea posible, y sigue la pauta 5.1.
Esto mejora la localización del código, lo que simplifica la comprensión lógica y la depuración.
Qué hacer | Verse | Se agrupan las condiciones dependientes o relacionadas. |
Qué hacer | Verse | Se agrupan las condiciones dependientes o relacionadas. |
Qué no hacer | Verse | La sangría innecesaria puede hacer que el flujo sea más difícil de seguir. |
Qué no hacer | Verse | La sangría innecesaria puede hacer que el flujo sea más difícil de seguir. |
Se acepta la división de los contextos de fallo si se gestiona cada fallo potencial (o grupos de fallos) por separado.
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. Encapsulación
6.1 Preferir interfaces a clases
Utiliza interfaces en lugar de clases cuando corresponda. Esto ayuda a reducir las dependencias de implementación y permite a los usuarios brindar implementaciones que el marco de referencia puede utilizar.
6.2 Prefiere el acceso privado y la restricción del alcance
Los miembros de la clase deben ser "privados" en la mayoría de los casos.
El alcance de los métodos de clases y módulos debe ser lo más restrictivo posible: <internal> o <private> cuando corresponda.
7. Eventos
7.1 Eventos posfijo con controladores de evento y prefijo con On
Los nombres de eventos suscribibles o de listas de delegados deben posfijarse con 'Event', y los nombres de controladores de eventos deben prefijarse con 'On'.
MyDevice.JumpEvent.Subscribe(OnJump)8. Simultaneidad
8.1 No decores funciones con Async
Evita decorar las funciones <suspends> con términos Async o similares.
Qué hacer | Verse |
Qué no hacer | Verse |
Es aceptable añadir el prefijo Await a una función <suspends> que espera a que ocurra algo en el interior.
Esto puede clarificar cómo se supone que debe utilizarse una API.
AwaitGameEnd()<suspends>:void=
# Setup other things before awaiting game end…
GameEndEvent.Await()
OnBegin()<suspends>:void =
race:
RunGameLoop()
AwaitGameEnd()9. Atributos
9.1 Atributos separados
Coloca los atributos en una línea separada. Es más legible, en especial si se añaden varios atributos al mismo identificador.
Qué hacer | Verse |
Qué no hacer | Verse |
10. Expresiones de importación
10.1 Ordenar alfabéticamente las expresiones de importación
Por ejemplo:
using { /EpicGames.com/Temporary/Diagnostics }
using { /EpicGames.com/Temporary/SpatialMath }
using { /EpicGames.com/Temporary/UI }
using { /Fortnite.com/UI }
using { /Verse.org/Simulation }