Buenas Prácticas de programación en GeneXus

Tomado de: Buenas Practicas

Las Buenas prácticas de programación en GeneXus sirven para mejorar la comprensión y claridad del código, aparte de permitir unificar criterios entre los distintos programadores de la comunidad.

Las Buenas prácticas de programación en GeneXus parten de la base que el código es la mejor documentación que puede tener un sistema, por esto también es la mejor herramienta que tiene un programador de comunicar su trabajo con el resto de los programadores.

Al utilizar las Buenas prácticas de programación en GeneXus el código de la KB obtiene un valor agregado ya que adquiere:

  • Fácil integración y reutilización
  • Fácil comprensión por parte del programador
  • Unificación de criterios
  • Eliminación de zonas oscuras de código
  • Fácil comunicación entre programadores
  • Claridad y correctitud en el código
  • Incremento significable en la mantención del Software

(Ver tambien Design Tips por recomendaciones de design).

Las Buenas prácticas de programación en GeneXus están compuestas por reglas y recomendaciones.

Reglas

  • Al nombrar atributos se debe utilizar la nomenclatura GIK

  • Los atributos deben tener descripción y Help
Nombre: CliCod      Descripción: Código de Cliente      Help: Identificador del Cliente en el Sistema

  • Las Tablas deben tener nombres que representen la realidad y no el nombre heredado por la transacción que las crea.
Trn:  Cli2SisPro      Tabla: Clientes
  • Las variables que hagan referencia a un atributo deben ser basadas en el mismo y tener el mismo nombre del Atributo, si la lógica lo permite.

Atributo: CliCod - C(20)  - Código de Cliente          Variable: &CliCod - C(20)  - Código de Cliente
  • Las variables que hagan referencia a un atributo deben ser basadas en el mismo y tener el mismo nombre del Atributo, agregando uno más sufijos para calificarla en el caso que sea necesario (Agregado AdeL 05jul08).
     Atributo: CliCod – C(20) – Código de Cliente
     Variable: &CliCodOri – C(20) – Código de Cliente de Origen      <-----  Correcto
     Variable: &CliCodDst – C(20) – Código de Cliente de Destino     <-----  Correcto
     Variable: &CliDstCod – C(20) – Código de Cliente de Destino     <-----  Incorrecto

  • Definición de Subtipos

Se recomienda que al definir subtipos estos pertenezcan a un grupo especifico y no sean definidos en el grupo «None»

Cada grupos de subtipos debería tomar el nombre del Atributo Subtipo que identifica al Grupo (primario), o una concatenación de nombres si hay varios primarios, como en el siguiente ejemplo (Agregado AdeL 05jul08)

Nombre del Grupo:  BancoCodOrigen

Subtipos                                Supertipo       BancoCodOrigen                          BancoCod      BancoNombreOrigen                       BancoNombre  
  • Reporte de Especificación

Se considera una buena práctica que el Analista GeneXus revise con atención el reporte de especificación ya que está es la principal herramienta que tiene para detectar errores en el código.

  • Definición de Reglas

En ocasiones al no existir un criterio dentro de la comunidad para definir las Rules el código queda bastante «duro» para entender y difícil de buscar el comportamiento que se programo para un atributo en particular.

Ej: Parm(in:EmpCod, in:&Usuario, in:&CliCod, in:&Mode);  noaccept(CiuCod);  &CliSaldoAux = udp(PcalcSaldo, EmpCod, &CliCod, CliSaldo);  error('Mensaje') If Null(&Usuario);  allownulls(EmpCod, LocCod ) ;  Call(PActInfo, EmpCod, CliCod) if ;  error('Mensaje') IF CliDir = nullvalue(CliDir ) and after(CliDir) ;  prompt(Wclientes, EmpCod, CliCod);  default(CliFchCrea, Now() ) ;  noaccept(EmpCod);  Call(PInfoUsr, EmpCod,&Usuario) if ;  nocheck(EmpCod, LocCod);  msg('Saldo menor que cero') if CliSaldo < 0;  Refcall(Wclientes, EmpCod, CliCod);  Call(Pprocedure, EmpCod, CliCod) if ;  default(CliArea, 'A' ) ;

Si observamos este código nos damos cuenta que para buscar algo tenemos que recorrer hasta el final ya que no muestra ningún criterio a seguir. Existen muchas maneras de definir las rules para que sean fáciles de entender, pero vamos a tomar dos criterios que serán vistos como buenas prácticas.

Criterio Nro. 1

Definir las rules agrupadas por atributo:
De está manera podemos seguir el comportamiento que se programo para un atributo o variable en particular.

Ej: Parm(in:EmpCod, in:&Usuario, in:&CliCod, in:&Mode);  error('Mensaje') If Null(&Usuario); Call(PInfoUsr, EmpCod,&Usuario) if ;  allownulls(EmpCod, LocCod ) ; nocheck(EmpCod, LocCod);  Call(PActInfo, EmpCod, CliCod) if ; Call(Pprocedure, EmpCod, CliCod) if ; prompt(Wclientes, EmpCod, CliCod); Refcall(Wclientes, EmpCod, CliCod);  error('Mensaje') IF CliDir = nullvalue(CliDir ) and after(CliDir) ;  default(CliFchCrea, Now() ) ; default(CliArea, 'A' ) ;  noaccept(EmpCod); noaccept(CiuCod);  msg('Saldo menor que cero') if CliSaldo < 0; &CliSaldoAux = udp(PcalcSaldo, EmpCod, &CliCod, CliSaldo);

Criterio Nro. 2 (Recomendado)

Agrupar las Rules por comportamiento

De está manera podemos ir directamente al sector del código en donde detectamos que está el error o para agregar comportamiento.

Ej: Parm(in:EmpCod, in:&Usuario, in:&CliCod, in:&Mode); noaccept(EmpCod); noaccept(CiuCod);  allownulls(EmpCod, LocCod ) ; nocheck(EmpCod, LocCod);  default(CliFchCrea, Now() ) ; default(CliArea, 'A' ) ;  error('Mensaje') If Null(&Usuario); error('Mensaje') IF CliDir = nullvalue(CliDir ) and after(CliDir) ; msg('Saldo menor que cero') if CliSaldo < 0;   Call(PInfoUsr, EmpCod,&Usuario) if ; Call(PActInfo, EmpCod, CliCod) if ; Call(Pprocedure, EmpCod, CliCod) if ; &CliSaldoAux = udp(PcalcSaldo, EmpCod, &CliCod, CliSaldo);  prompt(Wclientes, EmpCod, CliCod); Refcall(Wclientes, EmpCod, CliCod);

/* Comentario: Andrés Cuñarro
Para el caso de agrupar las reglas por comportamiento resulta práctico indicar el cometido ya que algunas veces se deben utilizar rules distintas para cumplir un cometido.
Si se incluyen éstas (u otras) secciones en un style, se define un criterio standard para una Kb.*/

Por ejemplo:

parm( parm1, parm2, ...);  //INSTANCIAR REGISTRO (PARAMETRO)  //VALORES POR DEFECTO default(CliFchCrea, Now() ) ;  //INTERFASE noaccept(EmpCod);  //DEPENDENCIAS FUNCIONALES CliEdad = Age(CliFchNac);  //INTEGRIDAD REFERENCIAL allownulls(EmpCod, LocCod ) ; nocheck(EmpCod, LocCod);  //VALIDACION DE CAMPO error("Nombre de cliente incorrecto.") if null(CliNom);  //VALIDACION DE REGISTRO error("La fecha de vencimiento no puede ser menor a la del documento.") if DocFchVenc < DocFch;  //PROMPTS prompt(WClientes, CliCod);  //ACCIONES Call(PInfoUsr, EmpCod,&Usuario) if ; 

Recomendaciones

  • La descripción de los objetos de la KB debe ser clara independientemente del nombre del mismo.
Nombre: ModCliDeuda      Descripción: Modificación de Cliente con Deuda
  • Se debe tener un método para dar nombre a los objetos. Esto depende mucho del tamaño de la(s) KB(s), tipo de instalación, criterios heredados y otra cantidad de factores, pero algunas cosas a tener en cuenta serían (Agregado por AdeL 05jul08).
    • Al principio del objeto, un par de letras que identificara la aplicación (p.e. 'PE' para una aplicación de Personal, 'CO' para una aplicación de Compras). Esto podría ser opcional, dependiendo del ambiente.
    • Un identificador de la Entidad sobre la cual trabaja el objeto (p.e. Clientes, Facturas, Impuestos), la cual también podría ser nemotécnica (p.e. Cli, Fact, Imp)
    • Uno o más verbos, combinados con palabras o abreviaturas que identiquen someramente qué hace el objeto (Calcular, Borrar, Modificar, Crear).
  • La descripción de los objetos debe contener primero el objeto y después la acción que se ejecuta.
Nombre                      Descripción      CliDeudaModificar           Cliente con Deuda - Modificar FactIngresar                Factura - Ingresar COOrdenCompraImpCalcular    Orden de Compra - Impuestos - Calcular     (este sería un objeto de la KB de Compras, p.e. en un ambiente de desarrollo distribuido) VEClientesTrabCon           Clientes - Trabajar con                    (este sería un objeto de la KB de Ventas, p.e. en un ambiente de desarrollo distribuido)
  • Utilizar nombres nemotécnicos para las variables que no correspondan a ningún atributo del sistema.
Se quiere cargar en una variable la existencia de un cliente.      Forma correcta: ExisteCliente      Forma Incorrecta: Flag
  • Utilizar nombres nemotécnicos para los objetos de la KB

TrabajarConClientes
  • En el encabezado de los programas se debe hacer un cuadro con una pequeña descripción del propósito del mismo y datos útiles.

/*         Autor: Cristhián Gómez ([email protected])        Fecha de Creación: 26-06-2004        Ultima modificación:  27-06-2004        Versión: 1.2        Descripción: Cambia el estado de los movimientos luego de la autorización del Usuario      */

  • Colocar una línea en blanco entre las definiciones de eventos o subrutinas para separar los mismos y hacer más comprensibles los programas.

  • Dentro de los eventos se debe comenzar a escribir código luego de hacer un tab., esto facilita la visualización del código
// Forma incorrecta:      Event 'NuevoCli'      If &CliCod = &Cliente          //Codigo      Endif      EndEvent       // Forma Correcta:      Event 'NuevoCli'          If &CliCod = &Cliente              //Codigo          Endif      EndEvent
  • Para que los ForEach queden más claros y fáciles de identificar dentro de los eventos o del código en general, se recomienda que se escriban de la siguiente manera:

// Forma Incorrecta:      Event 'NuevoCli'      For Each      where CliCod = &CliCod      //Còdigo      EndFor      EndEvent        // Forma correcta:      Event 'NuevoCli'          For Each              where CliCod = &CliCod              //Còdigo          EndFor      EndEvent 

  • Para que los filtros de los ForEach queden más claros se recomienda tener un where para cada condición y no utilizar AND.

//Forma incorrecta:      For Each          where CliCod = &CliCod and CliStatus = &CliStatus and CliTipo = &CliTipo          //Còdigo      EndFor       // Forma correcta:      For Each          where CliCod = &CliCod          where CliStatus = &CliStatus          where CliTipo = &CliTipo          //Código      EndFor  

/* Comentario de Gabriel Icasuriaga
Otra opcion es definir toda la declaracion del for each sin indentar y recien indentar cuando comienza el codigo
aparte de eso, prefiero poner todos los atributos aunque esto sea redundante, asi de esta forma, cuando otra persona ve el codigo
la comprension es mas rapida. Tambien me gusta como quedan los "=" a la misma altura.

For Each Clicod, CliStatus Where CliCod    = &CliCod Where CliStatus = &CliStatus Where CliTipo    = &CliTipo              //Codigo    EndFor

/* Comentario de Nicolas Jodal: me gusta mas la version original */

/* Comentario de: Jorge Ronald Cribb - Vikam Corporation
Sugiero esta otra forma:

  • Las palabras reservadas de GENEXUS con MAYUSCULAS
  • Indentación de 4 caracteres para las opciones del FOR EACH.
  • Los conectores AND e OR indentados dentro de la opción WHERE.
FOR EACH Clicod, CliStatus      WHERE CliCod    = &CliCod          AND CliStatus = &CliStatus          AND CliTipo    = &CliTipo              //Codigo    ENDFOR

/* Comentario de Edson Geovane - Vikam Corporation
Eu só acrescentaria o uso da clausula 'defined by' após as sentenças WHERE. Acho importante porque define a tabela que será lida e ajuda ao programador identificar rapidamente esta tabela. Vamos incentivar os programadores ler os programas e entender como se fosse um idioma.

/* Comentario de: Jorge Ronald Cribb - Vikam Corporation
Para mejorar la compresión del programa sin embargo yo preferiría que el ESPECIFICADOR de Genexus escribiese al lado de cada FOR EACH (como comentario) la relación de TABLAS que está usando ese FOR EACH con toda la secuencia de JOINS e OPCIONES DE ORDER que será efectuada.
Ejemplo:

FOR EACH Clicod, CliStatus   // GX: CLIENTES      WHERE CliCod    = &CliCod          AND CliStatus = &CliStatus          AND CliTipo    = &CliTipo          //Codigo   ENDFOR

Nota: En este caso el comentario que dice "// GX: CLIENTES" seria colocado automaticamente por Genexus después de la especificación del objeto.
Aclarando un poco mas todavía: La información que sería colocada por Genexus en el fuente sería del mismo tipo que coloca en el informe/listado de navegación del objeto (pero un poco mas resumida).

/* Comentario de Demetrio Toledo.
Generalmente acostumbro identificar siempre a la tabla base donde voy a trabajar con el For Each utilizando la Sentencia Defined By, de la siguiente manera.

FOR EACH Clicod, CliStatus   // GX: CLIENTES        Where CliCod    = &CliCod        Where CliStatus = &CliStatus        Where CliTipo    = &CliTipo        Defined By CliEstReg          //Codigo   ENDFOR

De esta manera se identifica correctamente la tabla base.
Lo cual definitivamente da la posibilidad de que cada tabla tenga por definicion un atributo que contemple la situacion del registro, en una especie de auditoria, en este caso el CliEstReg, no indicara la situacion del registro 2=MODIFICADO 4=ELIMINADO O 1=CREADO...

/* Comentario de Adilson Costa.
Aproveitando o comentário anterior onde é identificada a tabela base, além de informar o nome da tabela, identifico também a sua descrição.
Utilizo também a mesma informação no fechamento do For Each para facilitar quando estamos utilizando For Each aninhados.

For Each Order Clicod, CliStatus        // Clientes -> Tabela de Clientes     Where CliCod    = &CliCod     Where CliStatus = &CliStatus     Where CliTipo    = &CliTipo      //Codigo   EndFor // Clientes -> Tabela de Clientes

/* Comentário de Fabiano Gorziza Me parece que a boa prática aqui é, independente da forma escolhida, definir um padrão para toda a empresa.

/* Comentario de Andrés Rodríguez
Mi forma es parecida a estas últimas pero mientras genexus no describa automáticamente la tabla que se recorre, yo lo escribo manualmente para ayudar a otros que entiendan el código y para que, en el futuro al colocar cosas dentro del for each sea posible verificar que siga recorriendo lo mismo que antes. Estoy de acuerdo en poner los = a la misma altura y a indentar los where. En lo personal coloco un espacio en blanco luego de los where o defined by.

For Each Order Clicod, CliStatus        // TBL: CLIENTES     Where CliCod    = &CliCod     Where CliStatus = &CliStatus     Where CliTipo   = &CliTipo      //Codigo  EndFor // TBL: CLIENTES 

  • Colocar un espacio después de cada coma(,) en las rules, call, udp,etc. para hacer los programas más fáciles de entender.
// Forma incorrecta:      parm(&CliCod,&UsuCod,&Tipo);      call(MiObjeto,CliCod,UsuCod,&Tipo)      // Forma correcta:      parm(&CliCod, &UsuCod, &Tipo);      call(MiObjeto, CliCod, UsuCod, &Tipo)

  • Evitar abreviar excesivamente

Los nombres de las variables, subrutinas, objetos, etc deben de ser lo más claro posibles ya que si alguien externo debe trabajar con el código deberá, además de entender el código en general, deberá estar descifrando los nombres de cada variable, etc.
Ej: Se quiere cargar en una variable el cliente por proveedor

//Forma Incorrecta:    &CPProv     //Forma correcta:    &ClientePorProveedor 

  • Claridad en el código

La claridad en el código también se considera una buena práctica de programación, en muchos casos por costumbre los programadores abusan del uso del "if" olvidando que existe el comando "Do Case". En muchas ocasiones esto se debe a que las primeras versiones de GeneXus no soportaban este comando y la costumbre es más fuerte que el cambio.

  • Los atributos deben estar basados en Domains

Hay que tratar que todos los atributos estén siempre basados en algún Domain, de esta manera es más fácil adaptarse a los cambios de tipos o largos.

  • Utilización de Patterns

Se recomienda que toda aplicación web utilice Patterns, los patterns de GeneXus nos ofrecen una herramienta ideal para crear aplicaciones web. Facilitan la migración de ambiente win a web y nos ofrecen una forma práctica de solucionar problemas que antes nos llevaban mucho tiempo. Por más información leer Patterns.

  • Evitar constante en el codigo

Usar los enumerators en vez de constantes en el codigo. De esa manera si se cambia la constante no es necesario cambiar en todos los lados que se usa.

&Type = "CR" // BAD  &Type = BalanceType.Credit // GOOD
  • Mantenimiento de Kb´s

La naturaleza de la mayoría de los proyectos nos lleva a que estemos haciendo cambios constantemente en el conocimiento inicial que tenemos almacenado en una KB. Los cambios en los requerimientos de los clientes disparan un montón de acciones que en ocasiones hace que modifiquemos gran parte de nuestra lógica de negocio.

Esto lleva a que existan KB´s que tienen muchos objetos, atributos y tablas que no se utilizan o que se dejaron de utilizar por algún cambio o refactoring en el código. Esto hace que en la KB exista conocimiento duplicado o innecesario y a medida que una KB crece también crecen los tiempos de producción.

Existen tareas que se hacen a menudo que sin darnos cuenta podemos optimizar haciendo un mantenimiento del conocimiento existente en una KB. Haciendo un buen mantenimiento podemos bajar los tiempos de:

    • Build All

    • Copy Model

    • Update Model

    • Generate Help

    • Publicación de Información con GXPublic

    • Respaldos

Seria bueno si en ocasiones nos tomamos un tiempo para borrar todos los objetos, atributos, dominios, subtipos y tablas que no utilicemos. Esto mejorara los tiempos de producción y ayudara a que mi KB tenga el conocimiento que necesita para responder a mis necesidades.

Una de las cosas que hace que una KB crezca es tener modelos sin utilizar, en muchos casos no queda más remedio que tener varios
modelos de prototipo y producción. Como recomendación seria bueno eliminar todos los modelos que no se utilicen en una KB.

  • Encapsular código mediante el uso de Atributos Fórmula

Un aspecto muy relevante a la hora de mantener el software es tener centralizado la definición de los distintos cálculos que se realizan sobre los datos. Para ello lo recomendado es tener incorporado estos cálculos como Atributos Fórmula. De esta manera nos aseguramos que cuando cambia el cálculo que se requiere realizar para obtener una determinada información, se cambia el atributo fórmula y esto tiene incidiencia sobre TODO el sistema.

Por último una nomenclatura Propuesta para los casos en que la definición de un Att fórmula esté basada en un procedimiento Ej: CliSdoRes = udp(P...) el Nombre del Procedimiento debe ser igual al nombre del Atributo. Para el Ejemplo anterior quedaría CliSdoRes = udp(PCliSdoRes,...), otra opción es anteponer en el nombre del procedimiento la partícula "Frm", en el ejemplo citado quedaría CliSdoRes = udp(PFrmCliSdoRes,...)

Deja un comentario

Tu dirección de correo electrónico no será publicada.