Guía de estilo para el desarrollo en GeneXus


Esta guía ha sido creada por varios desarrolladores GeneXus, la misma trata de:
  • Transmitir las mejores prácticas a la hora de desarrollar en GeneXus.
  • Estandarizar el código escrito. Ya que hay tantas formas de programar como programadores, con esta guía se intenta simplificar la lectura del código fuente.
  • Divulgar buenas prácticas de codificación y las novedades del lenguaje.
Empecemos:

1. Definición de nombres

1.1 Se debe ser descriptivo con los nombres.

Se intenta que el nombre sea auto-descriptivo.

// mal
Proc: CliCre

// bien
Proc: ClienteCrear

1.2 Utilizar PascalCase al nombrar objetos, atributos y variables.

// mal
clientecrear

// bien
ClienteCrear

1.3 No utilizar underscore al inicio o final en ningún tipo de objeto, atributo o variable.

Esto puede hacer suponer a un programador proveniente de otros lenguajes que tiene algún significado de privacidad.

// mal
&_CliNom = "John Doe"
&CliNom_ = "John Doe"
Proc: _ClienteCrear

// bien
&CliNom = "John Doe"

1.4 Nombrar los dominios enumerados sin abreviar, comenzando con la entidad en singular y siguiendo con el calificador enumerado también en singular. Los valores enumerados también se deben especificar en singular.

Esto es para facilitar la definición de atributos y variables basadas en un dominio (GeneXus lo hará automáticamente).

// mal
DocumentosTipo
DocumentosTipos
DocTipos

// bien
DocumentoTipo { Venta, Compra, etc}
DocumentoModo { Credito, Débito}

1.5 Nombrar procedimientos relacionados mediante Entidad + Atributo(depende el caso) + Complemento + Acción.

Esto permite agrupar los objetos de la misma entidad en la selección de objetos entre otros. Algunas acciones típicas son Get, Set, Load (para SDT), Insert, Utpdate, Delete, etc. La diferencia entre Set y Update es que Set refiere a un atributo y Update a una entidad.

// mal
CreCli
UpsertCliente
FechaCliente

// bien
ClienteUpsert
ClienteEliminar
ClienteFechaModificadoGet
ClienteFechaModificadoSet
DocumentoRecalculo

1.6 Utilizar nomenclatura GIK para nombrar atributos. Se pueden crear atributos sin el límite de los 3 caracteres si el nombre no supera los 20 caracteres y mejora la comprensión.

Estandard desde los inicios de GeneXus.

// mal
CreCliFch
FechaCreadoCliente

// bien
CliFchCre

// mejor
ClienteFechaCreado

1.7 Las transacciones deben tener el nombre de la entidad en singular.

Se define así porque en la comunidad GeneXus está claro que queda mejor a la hora de trabaja por ejmeplo con Business Component. También es requerimiento de algunos patterns GeneXus para su correcta visualización (ej.: K2BTools).

// mal
Trn:Articulos
Trn:Clientes

// bien
Trn:Cliente
Trn:Articulo
Volver al inicio

2. Identación y espaciado

2.1 Utilizar tabuladores (tab) en lugar de "espacios". De esta forma, cada uno puede visializar la cantidad de espacioes que prefiera, ya que se configura en GeneXus.

La identación ofrece a los desarrolladores una mejor lectura del código fuente. Si tomamos una identación estandard, facilitará al resto entednder el código fuente.

// mal
if &DocumentoTipo = DocumentoTipos.Venta
msg( "Venta")
endif

// mal
if &DocumentoTipo = DocumentoTipo.Venta
msg( "Venta")
endif

// bien
if &DocumentoTipo = DocumentoTipo.Venta
   msg( "Venta")
endif

2.2 Se deben identar las condiciónes y comandos dentro de un for each.

// mal
for each
where DocumentoTipo = DocumentoTipo.Venta
...
endfor

// mal
for each
defined by ClienteNombre
...
endfor

// bien
for each
   where DocumentoTipo = DocumentoTipo.Venta

   ...
endfor

2.3 Si en un for each se especifican where, defined by ú otros, dejar una línea en blanco antes del código.

// mal
for each
   where DocumentoTipo = DocumentoTipo.Venta
   if DocTot > LimCreMto
  ...
   endif
endfor

// mal
for each
   defined by ClienteNombre
   for each Documentos
  ...
   endfor
endfor

// bien
for each
   where DocumentoTipo = DocumentoTipos.Venta

   if DocTot > LimCreMto
  ...
   endif
endfor

// bien
for each
   defined by ClienteNombre

   for each Documentos
  ...
   endfor
endfor

2.4 Dejar un espacio antes de cada parámetro.

Hace a la sentencia más sencilla de leer.

// mal
parm(in:PaiId,out:&PaiNom);

// bien
parm( in:PaiId, out:&PaiNom);

// mal
&Fecha = ymdtod(2017,01,01)

// bien
&Fecha = ymdtod( 2017, 01, 01)
Volver al inicio

3. Dominios enumerados

3.1 Evitar la utilización de textos/números fijos cuando pueden existir multiples valores.

Simplificar la lectura y no necesitar recordar el texto específico de cada opción.

// mal
if &HttpResponse = "GET"

// bien
// Crear un dominio enumerado HTTPMethod con los posibles valores ( POST, GET)
if &HttpResponse = HTTPMethod.Get

3.2 Los dominios enumerados cuyo valor quedará registrado en la base de datos, deberán ser de tipo CHAR.

Es para facilitar la lectura de las consultas realizadas directamente a la base de datos por el usuario. Es preferible que se utilice CHAR(2 a 3) para optimizar búsqueda mediante índices pequeños.

// mal
MovimientoCuenta.Credito 1
MovimientoCuenta.Debito  2

// bien
MovimientoCuenta.Credito "CRE"
MovimientoCuenta.Debito  "DEB"

3.3 Evitar definir dominios enumerados con valores "Empty" (0 ó "").

Luego, por ejemplo, si los queremos desplegar en un combo, no va a funcionar "Empty item".

// mal
ModoLectura.Normal     ""
ModoLectura.Secuencial "S"

// bien
ModoLectura.Normal     "N"
ModoLectura.Secuencial "S"

4. Structured Data Types

4.1 Utilizar New() en la creación de SDT en lugar de Clone(). Incluso antes de utilizar el SDT por primera vez en lugar de al final (aunque GeneXus lo soporte).

Queda claro que se está trabajando con un nuevo item.

// &Cliente SDT:Cliente
// &Clientes lista de SDT:Cliente

// mal
for each Clientes
   &Cliente.CliNom = CliNom
   &Clientes.Add( &Cliente.Clone())
endfor

// bien
for each Clientes
   &Cliente = new()
   &Cliente.CliNom = CliNom
   &Clientes.Add( &Cliente)
endfor

4.2 Desde que GeneXus permite definir variables como listas, evitar crear SDT del tipo lista.

Al definir la variable del item particular, se lo marca como lista.

// mal
SDT:Clientes : Lista
ClienteItem
CliNom

// bien
SDT:Cliente
CliNom

5. Strings

5.1 Utilizar format para desplegar mensajes conteniendo datos y en llamadas a funciones javascript.

Si la aplicación se va a traducir en diferentes lenguajes no hay que re-programar los mensajes.

// mal
&Msg = "El cliente Nro." + &CliId.ToString() + " se llama " + &CliNom

// bien
&Msg = format( "El cliente Nro. %1 se llama %2", &CliId.ToString(), &CliNom)
Esto soluciona la traducción según contexto. Por ejemplo,

ingles: "The name of John's dog is Gandalf"

español: "El nombre del perro de John es Gandalf"
Lo anterior realizado mediante concatenación no quedaría correctamente traducido.

En el siguiente caso, podemos ver como podemos dejar para traducir solo el texto dentro de un método jsevent.

// mal
&Msg = "confirm('¿Está seguro de agregar excepción?')"
&LstExc.JSEvent( "onclick", &Msg)

// bien
&Msg = format( !"confirm('%1')", "¿Está seguro de agregar excepción?")
&LstExc.JSEvent( "onclick", &Msg)

5.2 Utilizar !"" para strings que no deben ser traducidos.

Un traductor puede modificar constantes o códigos específicos del sistema y pueden afectar el funcionamiento, por ejemplo parámetros.

// mal
&ParVal = ParamGet( "GLOBAL ENCRYPT KEY")

// bien
&ParVal = ParamGet( !"GLOBAL ENCRYPT KEY")

5.3 Utilizar comilla doble por defecto.

Estandarización de código para facilitar la lectura.

// mal
&Msg = 'Hola mundo!'

// bien
&Msg = "Hola mundo!"

6. Comentarios

6.1 Utilizar /** ... */ para comentarios multi-línea en descripciones de funcionamiento. Se puede seguir utilizando // ya que Genexus permite auto-comentar con Ctrl-Q | Ctrl-Shift-Q.

// mal
// CrearCliente crea una nuevo cliente
// según las variables:
// &CliNom
// &CliDir
sub 'CrearCliente'
  // ...
endsub

// bien
/**
* CrearCliente crea una nuevo cliente
* según las variables:
* &CliNom
* &CliDir
*/
sub 'CrearCliente'
   // ...
endsub

6.2 Utilizar // para comentarios de una sola línea. Estos comentarios deben estar una línea antes del sujeto a comentar. Dejar una línea en blanco antes del comentarios a no ser que sea la pimer línea del bloque o se esté comentando un where de for-each.

// mal
&CliNom = "John Doe" // Se asigna el nombre a la variable

// bien
// Se asigna el nombre a la variable
&CliNom = "John Doe"

// mal
sub 'CrearCliente'
   msg( "Creando cliente", status)
   // Se crea el cliente
   &ClienteBC = new()
   &ClienteBC.CliNom = "John Doe"
   &ClienteBC.Save()
endsub

// bien
sub 'CrearCliente'
   msg( "Creando cliente", status)

   // Se crea el cliente
   &ClienteBC = new()
   &ClienteBC.CliNom = "John Doe"
   &ClienteBC.Save()
endsub

// también está bien
sub 'CrearCliente'
   // Se crea el cliente
   &ClienteBC = new()
   &ClienteBC.CliNom = "John Doe"
   &ClienteBC.Save()
endsub

6.3 Comenzar todos los comentarios con un espacio para que sean sencillos de leer.

// mal
//Está activo
&IsActive = true

// bien
// Está activo
&IsActive = true

// mal
/**
*Se obtiene el nombre de la empresa
*para luego desplegarlo
*/
&EmpNom = EmpresaNombreGet( &EmpId)

// bien
/**
* Se obtiene el nombre de la empresa
* para luego desplegarlo
*/
&EmpNom = EmpresaNombreGet( &EmpId)

6.4 Agregar prefijos en los comentarios con FIXME o TODO ayudan a otros desarrolladores a entender rápidamente si se está ante un posible problema que necesita ser revisado o si se está sugiriendo una solución a un problema existente. 
Estos son diferentes a los comentarios regulares porque conllevan a acciones. 
Estas acciones son FIXME: -- necesita resolverse or TODO: -- necesita implementarse.

6.5 Usar // FIXME: para marcar problemas.

// FIXME: Revisar cuando &Divisor es 0
&Total = &Dividendo / &Divisor

6.6 Usar // TODO: para marcar implementaciones a realizar.

// TODO: Implementar la subrutina
sub "CrearCliente"
endsub

7. Comandos y funciones

7.1 Utilizar minúsculas al nombrar comandos y funciones del sistema.

Esto optimiza el desarrollo ya que los comandos y funciones provistas por el lenguaje se utilizan tan frecuentemente y no es necesario especificarlos en PascalCase.

// mal
For Each
   Where CliCod = &CliCod
   Msg( CliNom)
EndFor

// bien
for each
   where CliCod = &CliCod

   msg( CliNom)
endfor

// mal
&Fecha = YmdToD( 2017, 01, 01)

// bien
&Fecha = ymdtod( 2017, 01, 01)

7.2 Utilizar do case siempre que se pueda a fín de sustituir if anidados. Dejar un espacio entre cada bloque de case.

// mal
if &DocTipo = DocumentoTipos.Venta
   ...
else
   if &DocTipo = DocumentoTipos.Compra
  ...
   endif
endif

// también mal
do case
   case &DocTipo = DocumentoTipos.Venta
  ...
   case &DocTipo = DocumentoTipos.Compra
  ...

endcase

// bien
do case
   case &DocTipo = DocumentoTipos.Venta
  ...

   case &DocTipo = DocumentoTipos.Compra
  ...

   otherwise
  ...
endcase

// también está bien - Cuando existen múltiples case y la acción es de una sola línea.
> Esto facilita leer todas las opciones sin necesidad de scroll
do case
   case &Action = Action.Update      do 'DoUpdate'
   case &Action = Action.Insert      do 'DoInsert'
   case &Action = Action.Regenerate  do 'DoRegenerate'
   case &Action = Action.Clean       do 'DoClean'
   case &Action = Action.Refresh     do 'DoRefresh'
   case &Action = Action.Reload      do 'DoReload'
   otherwise do 'UnexpectedAction'
endcase

7.3 Utilizar clausula where en comandos for each en lugar de usar comandos "if", siempre que se trate de atributos de la tabla extendida.

Con esto logramos trasladar la condición al DBMS y hacer que forme parte de la query select evitando trabajar con grandes volúmenes de datos en el servidor de aplicación ó eventualmente en el cliente.

// mal
for each Documentos
   if DocTipo = DocumentoTipos.Ventas
  ...
   endif
endfor

// bien
for each
   where DocTipo = DocumentoTipos.Ventas
   ...
endfor

7.4 Utilizar "when" en comandos for each para simplificar la query enviada al DBMS.

// mal
for each Documentos
   where DocTipo = DocumentoTipos.Ventas
   where DocFch >= &FchIni or null(&FchIni)
   ...
endfor

// bien
for each
   where DocTipo = DocumentoTipos.Ventas
   where DocFch >= &FchIni when not &FchIni.IsEmpty()

   ...
endfor

7.5 Utilizar la última sintaxis siempre que la versión lo soporte.

// mal
&Name = udp( PNameGet, &Id)

// bien
&Name = PNameGet( &Id )

// mal
call( PNameSet, &Id, &Name)

// bien
PNameSet( &Id, &Name)

// mal
&Num = val( &NumChar)

// bien
&Num = &NumChar.ToNumeric()

8. Parámetros

8.1 Utilizar SDT en lugar de multiples parámetros.

Esto es importante en los casos de objetos con varios parámetros in o out, ya que la lectura queda confusa y si hay que modificar parámetros, se deberá revisar todos los llamadores. Si hay varios parámetros de salida, se deberán crear SDT por separado, tanto para entrada como para salida.

// mal
parm( in:&CliNom, in:&CliApe, in:&CliTel, in:&CliDir, in:&CliDOB);

// bien
parm( in:&sdtCliente);

// Ejemplo de un webservice
// mal
parm( in:&Nombre, in:&Edad, in:&EstadoCivil, out:&Id, out:&ErrorId);

// bien
parm( in:&PersonCreateRequest, out:&PersonCreateResponse);

9. Subrutinas

9.1 Al definir subrutinas agregar el nombre como comentario en la misma línea.

Con esto logramos ver el nombre de las sub-rutinas al momento de colapsar las mismas.

// mal
sub 'CrearCliente'
...
endsub

Resultado: "+Sub Block"

// bien
sub 'CrearCliente' // Crear Cliente
...
endsub

Resultado: "+Sub Block ('Crear Cliente')"

10. Buenas prácticas

10.1 Versionar el sistema según xx.yy.zz.

Donde: 
- xx: Cambios mayor de versión del sistema. Cambia con una frecuencia no menor a un año y generalmente implica un cambio mayor en el sistema. 
- yy: Incorpora cambios en base de datos. 
- zz: Incorpora solo cambios en los binarios.

10.2 Disponer de la versión actual de la aplicación dentro de los binarios.

Esto permite de forma inequívoca saber en que versión de la aplicación estamos trabajando. La versión se puede guardar también como un parámetro dentro de la base de datos, para poder obtener la diferencia con la versión de los binarios y así realizar la acción deseada.

Para lograr esto, se crea un procedimiento que retorna la versión en que estamos trabajando:

// Parameters
parm( out:&Version)

// Source
&Version = !"1.05.06"

10.3 Propiedades por defecto

Isolation level: Read commited
Generate prompt programs: No

10.4 No mostrar contraseñas en logs e información de debug

Esto obedece a mejorar la seguridad de los sistemas, evitando que queden credenciales en archivos y consolas con sus potenciales riesgos de seguridad


10.5 Establecer namespaces específicos en SDTs utilizados en webservices

Esto evita que se generen inconvenientes en producción si el environment cambia de namespace por defecto. Esto se define en la propiedad "name space" del SDT.


10.6 Evitar cargar grillas por defecto

En la mayoría de los casos el usuario va a aplicar algún filtro y al cargar por defecto se desperdician recursos del DBMS.


10.7 Evaluar si crear atributos nuevos como "null"

Ayuda a no re-crear la tabla ante una Reorg. Especialmente en tablas grandes donde el tiempo de migración de datos puede ser demasiado largo.


10.8 Evitar acceder a sesiones (websession) desde procedimientos con lógica de negocios.

El acceso a sesiones debe ser responsabilidad de la interfaz. Al trabajar con sesiones dentro de procedimientos estamos introduciendo lógica de la interfaz en el dominio del problema. Debido a esto, después podemos tener problemas si deseamos utilizar dichos procedimientos en ejecuciones por consola batch o win.

10.9 No mantener lógica del negocio en la interfaz.

Siguiendo con la idea anterior, se debe evitar incorporar lógica de negocios en la interfaz. El caso más claro en web es generar la exportación a excel y reportes en webpanels. Si en lugar de ello, los encapsulamos en procedimientos, eventualmente los podemos generar desde otras interfaces.

Comentarios