Translated from Version in Spanish
Good Programming practices in GeneXus serve to enhance understanding and clarity of the code, aside from allowing unified criteria between different programmers of the community.
Good programming practices in GeneXus based on the assumption that the code is the best documentation that may have a system, this is also the best tool of a developer to communicate their work with other programmers.
By using the Good programming practices in GeneXus KB code gets an added value because it acquires:
- Easy integration and reuse
- Easy comprehension by the programmer
- Unification of criteria
- Elimination of the dark zone of the code
- Easy comunication between programmers
- Clarity and correctness in the code
- Increasing significance in mantaining of Software
(See also Design Tips for recommendations of design).
Good programming practices in GeneXus are composed of rules and recommendations.
Rules
- Naming of attributes should use nomenclature GIK
- Attributes must have description and Help
Name: CliCod
Description: Client Code
Help: Identification of the Client in the System
- Tables should have names that represent reality and not the name inherited by the transaction that creates them.
Trn: Cli2SisPro
Table: Clients
- The variables that refer to an attribute should be based on the same and have the same name attribute, if logic allows.
Attribute: CliCod - C(20) - Code of Client
Variable: &CliCod - C(20) - Code of Client
- The variables that refer to an attribute should be based on the same and have the same name attribute, adding one more suffixes to qualify if necessary.(Agregado AdeL 05jul08)
Attribute: CliCod - C(20) - Code of Client
Variable: &CliCodOri - C(20) - Code of Client of Origin <----- Correct
Variable: &CliCodDst - C(20) - Code of Client of Destination <----- Correct
Variable: &CliDstCod - C(20) - Code of Client of Destination <----- Incorrecto
It is recommended that in defining these subtypes belong to a specific group and not be defined in the "None" group
Each subtype groups should take Subtype name attribute that identifies the group (primary) or a concatenation of names if multiple primary, as in the following example (Added AdeL 05jul08)
Name of Group: BankCodOrigin
Subtype Supertype
BankCodOrigin BankCod
BankNameOrigin BankName
It is considered good practice for the GeneXus Analyst carefully review the report specification as it is it is the main tool for detecting errors in the code.
In occasion no existence of criteria within the community to define the Rules thus the code is quite "hard" to understand and difficult to search for the behavior that was scheduled for a particular attribute.
Ej:
Parm(in:EmpCod, in:&User, in:&CliCod, in:&Mode);
noaccept(CiuCod);
&CliResidueAux = udp(PcalcResidue, EmpCod, &CliCod, CliResidue);
error('Message') If Null(&User);
allownulls(EmpCod, LocCod ) ;
Call(PActInfo, EmpCod, CliCod) if <cond>;
error('Message') IF CliDir = nullvalue(CliDir ) and after(CliDir) ;
prompt(Wclients, EmpCod, CliCod);
default(CliDatCrea, Now() ) ;
noaccept(EmpCod);
Call(PInfoUsr, EmpCod,&User) if <cond>;
nocheck(EmpCod, LocCod);
msg('Residue less than zero') if CliResidue < 0;
Refcall(Wclients, EmpCod, CliCod);
Call(Pprocedure, EmpCod, CliCod) if <cond>;
default(CliArea, 'A' ) ;
De está manera podemos seguir el comportamiento que se programo para un atributo o variable en particular.
By observing the code we realize that we have to search for something till the end and showing no criteria to follow. There are many ways to define the rules to make them easier to understand, but we will take two criteria to be seen as good practice.
Criterion no. 1
Define the rules grouped by attribute:
if so we can continue the behavior that was scheduled for a particular attribute or variable.
Example:
Parm(in:EmpCod, in:&User, in:&CliCod, in:&Mode);
error('Message') If Null(&User);
Call(PInfoUsr, EmpCod, &User) if <cond>;
allownulls(EmpCod, LocCod ) ;
nocheck(EmpCod, LocCod);
Call(PActInfo, EmpCod, CliCod) if <cond>;
Call(Pprocedure, EmpCod, CliCod) if <cond>;
prompt(Wclients, EmpCod, CliCod);
Refcall(Wclients, EmpCod, CliCod);
error('Message') IF CliDir = nullvalue(CliDir ) and after(CliDir) ;
default(CliDatCrea, Now() ) ;
default(CliArea, 'A' ) ;
noaccept(EmpCod);
noaccept(CiuCod);
msg('Residue less than zero') if CliResidue < 0;
&CliResidueAux = udp(PcalcResidue, EmpCod, &CliCod, CliResidue);
Criterio Nro. 2 (Recommended)
Grouping Rules for conduct
On this way we can go directly to the sector in which depends on code is the error or to add behavior.
Example:
Parm(in:EmpCod, in:&User, in:&CliCod, in:&Mode);
noaccept(EmpCod);
noaccept(CiuCod);
allownulls(EmpCod, LocCod ) ;
nocheck(EmpCod, LocCod);
default(CliDatCrea, Now() ) ;
default(CliArea, 'A' ) ;
error('Message') If Null(&User);
error('Message') IF CliDir = nullvalue(CliDir ) and after(CliDir) ;
msg('Residue less than zero') if CliResidue < 0;
Call(PInfoUsr, EmpCod,&Usuario) if <cond>;
Call(PActInfo, EmpCod, CliCod) if <cond>;
Call(Pprocedure, EmpCod, CliCod) if <cond>;
&CliResidueAux = udp(PcalcResidue, EmpCod, &CliCod, CliResidue);
prompt(Wclients, EmpCod, CliCod);
Refcall(Wclients, EmpCod, CliCod);
/* Comment: Andrés Cuñarro
In the case of grouping rules by behavior practical results indicate we sometimes must use different rules to accomplish the task. If these (or other) sections include a style, a criterion standard can be defined for each Kb.*/
For example:
parm( parm1, parm2, ...);
//INSTANTIATE REGISTER (PARAMETER)
//VALUES FOR DEFAULTS
default(CliDatCrea, Now() ) ;
//INTERFACE
noaccept(EmpCod);
//FUNCTIONAL DEPENDENCIES
CliAge = Age(CliDatBirth);
//REFERENTIAL INTEGRITY
allownulls(EmpCod, LocCod ) ;
nocheck(EmpCod, LocCod);
//VALIDATION OF FIELD
error("Name of client incorrect.") if null(CliNam);
//VALIDATION OF REGISTER
error("The date of expiration can not be smaller than the document.") if DocDatExp < DocDat;
//PROMPTS
prompt(WClients, CliCod);
//ACTIONS
Call(PInfoUsr, EmpCod, &User) if <cond>;
Recommendations
- The objects description of a KB should be clear regardless of the name.
Name: ModCliDebt
Description: Modification of Client with Debt
- They must have a method to name objects. This depends greatly on the size (s) of KB (s), type of facility, inherited criteria and other number of factors, but some things to consider would be (Added by AdeL 05jul08).
- At first the object, a couple of letters that identify the application (eg 'PE' for an application of Personnel, 'CO' for an application Purchasing). This could be optional, depending on the environment.
- An identifier of the entity on which the object works (eg Customers, Invoices, Tax), which could also be mnemonic (eg Cli, Invoice, Tax)
- One or more verbs, combined with words or abbreviations identification briefly what the object does (Compute, Delete, Edit, Create).
- The description of the objects must first contain the object and then the action is executed.
Name Description
CliDebtModify Client with Debt - Modify
InvoiceLogin Invoice - Login
COOrderPurchaseTaxCalculate Order of Purchase - Tax - Calculate (This would be an object of the KB Purchasing, e.g. a distributed development environment)
VEClientsWorkWith Clients - Work with (This would be an object of the KB Sales, e.g. a distributed development environment.)
- Use mnemonic names for variables that no corresponding system attribute.
To load into a variable the existence of a client.
Correct form: ExistClient
Incorrect form: Flag
- Use mnemonic names for objects in the KB
/*
Author: Cristhián Gómez (urulinux@adinet.com.uy)
Date of Creation: 26-06-2004
Last modification: 27-06-2004
Versión: 1.2
Description: Cambia el estado de los movimientos luego de la autorización del Usuario
*/
- Place a blank line between the definitions of events or subroutines to separate them and make programs more understandable.
- Within the event you should start writing code after a tab., This facilitates the visualization of code.
// Incorrect form:
Event 'NewCli'
If &CliCod = &Client
//Code
Endif
EndEvent
// Correct Form:
Event 'NewCli'
If &CliCod = &Client
//Code
Endif
EndEvent
- For the ForEach remain clear and easy to identify in events or code it's generally recommended to write as follows:
// Incorrect form:
Event 'NewCli'
For Each
where CliCod = &CliCod
//Code
EndFor
EndEvent
// Correct form:
Event 'NewCli'
For Each
where CliCod = &CliCod
//Code
EndFor
EndEvent
- For the ForEach filters remain clearer it is recommended that a where for each condition and not using AND.
//Incorrect form:
For Each
where CliCod = &CliCod and CliStatus = &CliStatus and CliTipo = &CliTipo
//Code
EndFor
// Correct from:
For Each
where CliCod = &CliCod
where CliStatus = &CliStatus
where CliTipo = &CliTipo
//Code
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
- Put a space after each comma (,) in the rules, call, udp, etc. to make programs easier to understand.
// Incorrect form:
parm(&CliCod,&UsuCod,&Tipo);
call(MiObjeto,CliCod,UsuCod,&Tipo)
// Correct form:
parm(&CliCod, &UsuCod, &Tipo);
call(MiObjeto, CliCod, UsuCod, &Tipo)
The names of variables, subroutines, objects, etc should be as clear as possible because if someone outside work with the code he must also understand the code by generally deciphering the names of each variable, etc.
Example: You want to load into a variable client by provider
//Incorrect form:
&CPProv
//Correct form:
&ClientByProvider
The clarity of the code is also considered good programming practice, in many cases programmers abuse habit of using "if" forgetting that there is "Do Case" command. Many times this is because early versions of GeneXus did not support this command and the habit is stronger than change.
- Attributes must be based on Domains
We must treat all attributes are always based on a domain, so it is easier to adapt to changes in rates or longer.
It is recommended that all web application uses Patterns, the patterns of GeneXus offer us an ideal tool to create web applications. Facilitate migration to win a web environment and offer a practical way to solve problems we had long before. For more information read Patterns.
- Avoid constant in the code
Using enumerators instead of constants in the code. That way if the constant is change it need not be changed on all sides used.
&Type = "CR" // BAD
&Type = BalanceType.Credit // GOOD
The nature of most projects leads us to make changes constantly on the initial knowledge we have stored in a KB. Changes in customer requirements shoot a lot of actions that sometimes makes us to change much of our business logic.
This leads to the existence of KBs with many objects, attributes and tables that are not used or are no longer used by any change or refactoring the code. This means that there is duplication or unnecessary knowledge in the KB and as a KB grows production times also grow.
There are tasks that are often made without realizing we optimized by maintaining the existing knowledge in a KB. Doing good we can lower maintenance times:
-
- Publication of Information with GXPublic
It would be good if we take time occasionally to delete all objects, attributes, domains, subtypes, and tables that do not use. This will improve production times and help the KB have the knowledge you need.
One of the things that makes a KB grow is to have models unused, in many cases no choice but to have several prototype and production models. A good recommendation is to eliminate all models that are not used in a KB.
- Encapsulate code using Formula Attributes
A very important aspect in maintaining the software is to centralize the definition of the various calculations performed on the data. It is recommended to incorporate these calculations as Formula Attributes. In this way we ensure that when calculating required to perform for a given information changes, the formula attribute is changed and this is on ALL the system.
Finally a nomenclature proposal for cases where the definition of a Att formula is based on a procedure Ex: CliSdoRes = udp (P ...) Name of Procedure must match the name of the attribute. For the above example: CliSdoRes = udp (PCliSdoRes, ...), another option is to put in the procedure name the particle "Frm" in the above example: CliSdoRes = udp (PFrmCliSdoRes, ...)