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
: Suscríbete al evento marco llamado X, a menudo pasándole 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 lógica. -
DestroyC
: Finaliza la vida lógica. -
C:c
: Si trabajas con una sola 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 llámalo
cosa
, notipo_cosa
niclase_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 deben ir siempre escritos con_guion_bajo
. Esto incluye todos los tipos: estructuras, clases, definiciones, trazos/interfaces, enumeraciones, etc.
my_new_type := class
2.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 := interface
2.3 Todo lo demás DebeIrConcatenado
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
ocosa
, dondecosa
explica lo que se supone que representa el tipo. Por ejemplo:Send(Payload:payload where payload:type)
Estás enviando unos datos parametrizados,Payload
, de cualquier tipopayload
. -
Si hay más de un tipo paramétrico, evita usar letras sueltas, como
t
,u
,g
-
No uses nunca 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 = 5
3.1 Sangría
-
Utiliza cuatro espacios para la sangría, nunca tabuladores.
-
Los bloques de código deben utilizar bloques sangrados (espaciados) en lugar de corchetes (entre corchetes):
my_class := class: Foo():void = Print("Hello World")
- Excepto al escribir expresiones de una sola línea como
option{a}
,my_class{A := b}
, etc.
- Excepto al escribir expresiones de una sola línea como
3.2 Espacios
- Utiliza espacios alrededor de los operadores, a menos que tenga sentido mantener el código compacto para su contexto. Añade llaves para definir explícitamente el orden de las operaciones.
MyNumber := 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.
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)
- 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 (:=
).MyVariable:int = 5 MyVariable := 5 my_type := class
- Sigue las mismas recomendaciones sobre corchetes, identificadores y espaciado entre tipos para las firmas de funciones.
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 Saltos de línea
-
Utiliza un espaciado multilínea para insertar un salto de línea.
Recomendable MyTransform := transform: Translation := vector3: X := 100.0 Y := 200.0 Z := 300.0 Rotation := rotation: Pitch := 0.0 Yaw := 0.0 Roll := 0.0
Más legible y fácil de editar. No recomendable MyTransform := transform{Translation := vector3{X := 100.0, Y := 200.0, Z := 300.0}, Rotation := rotation{...}}
Difícil de leer en una sola línea.
- Define las enumeraciones de forma espaciada y multilínea si necesitan comentarios por enumeración o si necesitas insertar un salto de línea.
enum: Red, # Desc1 Blue, # Desc2
3.4 Corchetes
No utilices corchetes para las definiciones de clases no heredadas.
Recomendable |
|
No recomendable |
|
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 |
|
No recomendable |
|
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 # Retorno implícito
Si utilizas retornos explícitos, todos los retornos de la función deben ser explícitos.
4.2 Las funciones GetX deben ser
Los getters o funciones con semántica similar que puedan no devolver 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>:x
Una 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 |
|
No recomendable |
|
5. Comprobación de fallos
5.1 Limitar 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.
if (Damage > 10, Player := FindRandomPlayer[], Player.GetFortCharacter[].IsAlive[]): EliminatePlayer(Player)
-
Utiliza la forma
if
con paréntesis()
cuando el número de condiciones sea inferior a tres.
Recomendable |
|
Mantiene el código conciso pero legible. |
No recomendable |
|
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.
if (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 |
|
El texto se lee mejor y el contexto es comprensible en varias líneas. |
No recomendable |
|
El texto es difícil de analizar. |
-
Evalúa si agrupar varias condiciones de fallo en una única 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.if: Player := FindRandomPlayer[] IsAlive[Player] not IsInvulnerable[Player] Character := Player.GetFortCharacter[] Character.GetHealth < 10 then: EliminatePlayer(Player)
-
Puede reescribirse como:
GetRandomPlayerToEliminate()<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:set Lights = for (ActorIndex -> TaggedActor : TaggedActors, LightDevice := customizable_light_device[TaggedActor], ShouldLightBeOn := LightsState[ActorIndex]): Logger.Print("Añadiendo luz en el índice {ActorIndex} con estado:{if (ShouldLightBeOn?) then "On" else "Off"}") if (ShouldLightBeOn?) then LightDevice.TurnOn() else LightDevice.TurnOff() LightDevice
-
Mejor así:
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 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 |
|
Las condiciones dependientes o relacionadas se agrupan. |
Recomendable |
|
Las condiciones dependientes o relacionadas se agrupan. |
No recomendable |
|
Las sangrías innecesarias pueden hacer que el flujo sea más difícil de seguir. |
No recomendable |
|
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("El jugador es invulnerable, no se puede eliminar.")
else:
Print("No se puede encontrar al jugador. Se trata de un error de configuración.")
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 |
|
No recomendable |
|
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=
# Prepara otras cosas antes de esperar el final de la partida...
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 |
|
No recomendable |
|
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 }