Esta guía proporciona un conjunto de normas recomendadas para escribir un código coherente y fácil de mantener. Al seguir estas directrices, los desarrolladores pueden mejorar la legibilidad del código, reducir los errores y agilizar la colaboración. Un estilo de código estandarizado es necesario para garantizar que el código sea fácil de entender y mantener tanto por los desarrolladores actuales como por los futuros que trabajen en un proyecto.
Esta guía ofrece recomendaciones, pero en última instancia la elección depende de tu equipo.
1. Patrones comunes de nomenclatura
La nomenclatura es crucial para un código legible y sostenible. Intenta ser coherente en el estilo de nomenclatura en todo tu código.
1.1 Usa lo siguiente
IsX: suele utilizarse para nombrar variables lógicas que formulan una pregunta (por ejemplo, IsEmpty).OnX: una función sobrecargable llamada por el marco.SubscribeX: se suscribe al evento del marco denominado X, a menudo pasándole una función OnX.MakeC: crea una instancia de la clase c sin sobrecargar el constructor c.DestroyC: crea una instancia de la clase c, comenzando su vida 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 No hagas lo siguiente
Añadir detalles innecesarios a nombres de los tipos. Solo denomínalo
cosa, notipo_cosaniclase_cosa.Añadir detalles innecesarios a valores de enumeración. Incorrecto:
color := enum{COLOR_Red, COLOR_Green}; correcto:color := enum{Red, Green}.
2. Nombres
2.1 Los tipos se escriben con_guion_bajo
Los nombres de los tipos siempre deben ir siempre escritos con_guion_bajo. Esto incluye todos los tipos: estructuras, clases, definiciones, trazos/interfaces, enums, etc.
my_new_type := class2.2 Las interfaces son adjetivos
Las interfaces deben ser adjetivos siempre que sea posible, como imprimible, enumerable. Cuando los adjetivos no parezcan adecuados, añade _interface al nombre en su lugar.
my_new_thing_interface := interface2.3 Todo lo demás debe ir en formato PascalCase
El resto de nombres DebenIrConcatenados. Módulos, variables miembro, parámetros, métodos, etc.
MyNewVariable:my_new_type = …2.4 Tipos paramétricos
Nombra tipos paramétricos c o cosa: «cosa» explicará lo que se supone que representa el tipo. Por ejemplo:
Send(Payload:payload where payload:type)Enviarás datos parametrizados,Payload, de cualquier tipopayload.Si hay más de un tipo paramétrico, evita utilizar letras sueltas, como
t,uog.Nunca utilices el sufijo
_t.
3. Formato
Es importante ser coherente con el formato en toda tu base de código. Esto hace que el código sea más fácil de leer y entender para ti y 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 base:
MyVariable : int = 5
MyVariable:int = 53.1 Sangría
Utiliza cuatro espacios para la sangría, nunca tabuladores.
Los bloques de código deben utilizar bloques con sangría (espaciados) en lugar de llaves (entre 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 explícitamente el orden de las operaciones.
VerseMyNumber := 4 + (2 * (a + b))No añadas espacios al principio y al final de los paréntesis. Las expresiones múltiples entre paréntesis deben separarse con un 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 juntos el identificador y el tipo; añade un espacio alrededor del operador de asignación
=. Añade un espacio alrededor de las definiciones de tipos y de los operadores de inicialización de constantes (:=).VerseMyVariable:int = 5 MyVariable := 5 my_type := classSigue las mismas recomendaciones sobre corchetes, identificadores y espaciado entre tipos para las firmas de funciones.
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 espaciado multilínea para insertar un salto de línea.
Recomendable
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.
No recomendable
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 enums de forma espaciada y multilínea si necesitan comentarios por enum o si necesitas insertar un salto de línea.
Verseenum: Red, # Desc1 Blue, # Desc2
3.4 Corchetes
No utilices corchetes para las definiciones de clases no heredadas.
Recomendable | Verse |
No recomendable | Verse |
3.5 Evita la notación punto-espacio
Evita utilizar la notación «. » en lugar de las llaves. Esto dificulta visualmente el análisis de los espacios en blanco y es una fuente potencial de confusión.
No recomendable | Verse |
No recomendable | Verse |
4. Funciones
4.1 Retorno implícito predeterminado
Las funciones devuelven el valor de su última expresión. Utilízalo como un retorno implícito.
Sqr(X:int):int =
X * X # Implicit returnSi utilizas retornos explícitos, todos los retornos de la función deben ser explícitos.
4.2 Las funciones GetX deben
Los getters o funciones con semántica similar que no devuelvan valores válidos deben marcarse como <decides><transacts> y devolver un tipo no opcional. El autor de la llamada debe gestionar el posible fallo.
GetX()<decides><transacts>:xUna excepción son las funciones que necesitan escribir incondicionalmente en una var. Un fallo haría retroceder la mutación, por lo que deben utilizar logic u option como tipo de retorno.
4.3 Preferencia por los métodos de extensión frente 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 tipado.
Hacerlo ayuda a Intellisense. Si escribes MyVector.Normalize() en lugar de Normalize(MyVector), puede sugerirte nombres con cada carácter del nombre del método que escribas.
Recomendable | Verse |
No recomendable | Verse |
5. Comprobación de fallos
5.1 Limita a tres el recuento de expresiones falibles de una sola línea
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.
Recomendable | Verse | Mantiene el código conciso pero legible. |
No recomendable | 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, suele ser más legible un máximo de dos expresiones en una sola línea.
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 superes el límite, utiliza la forma multilínea espaciada.
Recomendable | Verse | El texto se lee mejor y el contexto es comprensible en varias líneas. |
No recomendable | 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 reutilización del código. Ten en cuenta que si el código solo se utiliza en un lugar, puede bastar con un comentario de "sección" sin función específica.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 de los 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 así:
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 Agrupa las expresiones falibles dependientes
Cuando una condición de un contexto de fallo dependa 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 directriz 5.1.
Esto mejora la localización del código, lo que simplifica la comprensión lógica y la depuración.
Recomendable | Verse | Las condiciones dependientes o relacionadas se agrupan. |
Recomendable | Verse | Las condiciones dependientes o relacionadas se agrupan. |
No recomendable | Verse | Las sangrías innecesarias pueden hacer que el flujo sea más difícil de seguir. |
No recomendable | Verse | Las sangrías innecesarias pueden hacer que el flujo sea más difícil de seguir. |
Es aceptable dividir los contextos de fallo si manejas 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 Preferencia de interfaces a clases
Utiliza interfaces en lugar de clases cuando sea razonable. Esto ayuda a reducir las dependencias de implementación y permite a los usuarios proporcionar implementaciones que puedan ser utilizadas por el marco.
6.2 Preferencia por el acceso privado y ámbito restringido
Los miembros de la clase deben ser 'private' en la mayoría de los casos.
Los métodos de clases y módulos deben tener un ámbito lo más restrictivo posible: <internal> o <private> cuando proceda.
7. Eventos
7.1 Eventos de posfijo con controladores de eventos y prefijos con On
Los nombres de los eventos suscribibles o de las listas de delegados deben llevar el posfijo `Event y los nombres de los controladores de eventos el prefijo `On`.
MyDevice.JumpEvent.Subscribe(OnJump)8. Simultaneidad
8.1 No adornes las funciones con Async
Evita adornar las funciones <suspends> con términos Async o similares.
Recomendable | Verse |
No recomendable | Verse |
Es aceptable añadir el prefijo Await a una función <suspends> que internamente espera a que ocurra algo.
Esto puede aclarar cómo 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
Pon los atributos en una línea aparte. Resulta más legible, sobre todo si se añaden varios atributos al mismo identificador.
Recomendable | Verse |
No recomendable | Verse |
10. Expresiones de importación
10.1 Ordena las expresiones de importación alfabéticamente
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 }