Técnicas para una Programación de Calidad.
Moisés Daniel Díaz
Toledano. www.moisesdaniel.com
Introducción.
¿Qué es un error? Podríamos decir a priori que un error es un fallo que se incluye en un programa estropeando el producto y afectando al cliente.
Podemos verlo también desde otra óptica. Cuando desarrollamos software intentamos proveer de cierta funcionalidad al usuario, si esta funcionalidad no se cumple, podemos decir que el software tiene errores.
Estos errores de los que estamos hablando, son errores cuantificables (detectables) por el usuario, lo que nos lleva al concepto de calidad externa del software.
Sin embargo, aún no hemos justificado el propósito de este artículo, ¿por qué hacer una programación de calidad? Porque la calidad externa del software guarda una relación directa con la calidad interna del mismo, es decir con su estructura y codificación.
Todos queremos construir software correcto, robusto, extensible y reusable. Debemos ser conscientes que el mejor camino para lograrlo, está precisamente en hacer una programación de calidad.
Existen además otras razones de mucha importancia para aplicar técnicas de buena codificación. Como desarrolladores no podemos olvidar que el código fuente se mantiene y reutiliza, con lo que para facilitar estas tareas debemos cuidar nuestra programación.
El objetivo del presente artículo es presentar de una forma simple, informal y no demasiada ordenada, algunas técnicas particulares, así como otras generales, para el ejercicio de una buena codificación. Quizás no se esté de acuerdo con algunos de estos consejos, pero al menos espero hacer reflexionar al lector sobre ellos, lo cual sin duda, traerá beneficios cuantificables a nuestros desarrollos.
1.- Nomenclatura
Los identificadores tanto de variables como de clases, funciones y procedimientos constituyen una buena parte del código. Su elección es muy importante ya que normalizan el código, y ayudan a entender el significado real de las acciones que realizan.
Es muy importante usar una nomenclatura bien definida en nuestra código, ya que aumenta enormemente la legibilidad y la semántica del mismo y hace más sencillo el trabajo en equipo y la supervisión por parte de otras personas . Tener en cuenta que los excesos son negativos y que por tanto especificar reglas para absolutamente todo trae más problemas de los que soluciona.
La nomenclatura más ampliamente extendida es la ‘Notación Húngara’, con numerosísimos defensores y detractores, cada uno bien armado de argumentos. Nos guste o no la notación húngara, siempre podemos sacar provechosas enseñanzas de esta propuesta que presupone que es mucho más importante dar información en los nombres de los identificadores que poder leer el código en voz alta.
Una de las ideas centrales en esta notación es la del uso de prefijos que incluyan información sobre el tipo de los identificadores. Esta propuesta en concreto tiene ciertas desventajas en los IDE (Entornos Integrados de Desarrollo) actuales debido sobre todo a la existencia de herramientas que nos informan conforme vamos tecleando de los diferentes métodos, atributos, variables, etc disponibles en ese contexto particular.
Sin embargo, sí que podemos usar postfijos para especificar cierta información, en concreto información relacionada con el ámbito de validez de las variables, tema que hasta ahora ha sido poco tratado.
Una posible recomendación sería la de usar como postfijos en los identificadores para indicar el ámbito de validez de ciertas variables los siguientes caracteres: 'C' para las variables de clase y 'G' para los objetos o variables de ámbito global.
modMain
Public
oCnnG As ADODB.Connection ‘Esto es solo un ejemplo!! Solo usar objetos globales
cuando es estrictamente ‘necesario
Public
Sub main()
' algo de código
Set oCnnG = New ADODB.Connection
oCnnG.Open "DSN=XX;SERVER=XX",
"XX", "XX"
'más
código
End Sub
Un pequeño ejemplo de clase desarrollada según las normas que se esbozan a lo largo de este artículo sería:
Option
Explicit
'Comentarios
de Clase.
Dim bConsistC As Boolean
Dim sFilePathC As String
Public Sub Init(psFilePath As
String)
Debug.Assert (Dir(psFilePath, vbArchive)
<> "")
sFilePathC = psFilePath
bConsistC = True
End Sub
Public Function sGetFileText()
As String
On Error GoTo errCatch
Debug.Assert (bConsistC = True)
Dim iFile As Integer
Dim sTempText As String
iFile = FreeFile
Open sFilePathC For Input As #iFile
sTempText = input$(LOF(iFile), #iFile)
Close #iFile
sGetFileText = sTempText
Exit Function
errCatch:
'Error code
End Function
2.-
Uso profuso de Aserciones/Validaciones.
Uno de los pilares de una programación sólida es conseguir detectar rápidamente los errores durante el desarrollo para evitar que estos pasen al producto que entregamos al cliente.
El mecanismo principal para lograr este objetivo es el uso generalizado y profuso de validaciones en el código para detectar cualquier uso no válido de nuestros métodos, clases, funciones y procedimientos. A esto lo denomino ‘Blindaje de Clases y Bloques’.
Se recomienda como mínimo usarlas como precondiciones en nuestras funciones y procedimientos para validar: parámetros, estado del objeto, estados contextuales, etc. Un ejemplo general y abstracto podría ser el siguiente:
Option Explicit
‘Comentarios sobre la clase
'Declaraciones a nivel de clase (recuerda el último carácter con 'C')
Public Sub
SomeProcedure ( parameters list )
On error errCatch
'Validación
del estado de la clase (variables de clase y objetos relacionados con esta
clase!!).
Debug.Assert
(bIamConsist() = true)
Debug.Assert (oClass2C.State = adStateOpen)
'Validación
de parámetros, por ejemplo:
Debug.Assert (
bIsValidParam1(param1) = true)
'Si
es necesario Validación de estados globales, por ejemplo:
Debug.Assert
(DatabaseConnectionG.State = adStateOpen)
'algo de código
Exit Sub
errCatch:
'Tratamiento de errores.
End Sub
Una posible clasificación de las validaciones es la siguiente:
· · Validaciones Internas. Se escriben para capturar estados erróneas durante el desarrollo.
· · Validaciones Paramétricas.
· · Validaciones Contextuales a nivel de Clase.
· · Validaciones Contextuales a nivel de Aplicación.
· · Validaciones Externas. Se escriben para evitar que se produzcan estados erróneos en la aplicación debidos a entradas no válidas por parte de los componentes externos a la aplicación (usuarios, ficheros, etc).
Las únicas validaciones que permanecerán en el producto final son las validaciones externas (sobre todo aquellas concernientes a la interacción con el usuario), las internas tienen como propósito principal el ayudarnos durante el desarrollo del código.
De todas formas, me encuentro con cierta frecuencia con la pregunta de ¿por qué no dejar las validaciones internas? Decir que suponen un alto coste en cuanto a rendimiento, y que no suponen ninguna ventaja para el usuario. Nos ayudan enormemente a desarrollar un código de calidad ya que detectan estados y usos inconsistentes, indicándonos que existe un problema y no teniendo que descubrirlo el usuario.
El tema de las validaciones es tan importante que si es necesario deberíamos mantener información adicional en nuestras clases, módulos, etc, para poder hacer una comprobación más estricta de errores.
3.- Reutilización de
arquitecturas.
La estructura de la mayoría de las aplicaciones es muy similar. Podemos observar que existen ciertos objetos que usamos en casi todos nuestros programas. Por ello es importante desarrollar una arquitectura clara y sencilla en nuestra aplicación, ayudándonos de desarrollos anteriores, y patrones de diseño.
Haciendo un pequeño apunte sobre una cuestión específica, podemos ver que una categoría importante dentro de los objetos comúnmente usados, se refiere a aquellos que van registrando información que se origina en cualquier parte de nuestra aplicación mientras esta se ejecuta: los objetos Log, los Reports, e incluso algunos objetos de tratamiento global de errores. Estos han de declararse globales para que puedan ser usados desde cualquier parte de nuestra aplicación. Se recomienda además que tengan una interfaz pequeña y sencilla para evitar dependencias con el resto del código.
modMain
Public oLogG As
CLogObject
Public oReportG as
CReportObject
'....Ser cuidadoso, no abusar de los objetos
globales!!!!!
Public Sub main()
' Algo de
código
'Inicializar oLogG y oReportG
'más código
End Sub
Es muy importante no abusar de los objetos globales en nuestras aplicaciones ya que disminuyen la reusabilidad de nuestras clases (las hacemos mucho más interdependientes), de hecho el número de objetos globales debe ser siempre muy reducido. En cualquier diseño uno de los mandamientos a seguir es desarrollar módulos, clases, componentes, etc altamente desacoplados con el resto, por lo que vuelvo a repetir, hacer que estas clases globales tengan un interfaz pequeño, y sólo sean llamadas desde puntos específicos del resto del código.
4.- Algunos consejos específicos.
Existen multitud de cuestiones a tener en cuenta a la hora de hacer una programación de calidad, aquí esbozo algunos temas concretos que he encontrado importantes:
q q Uso de manejadores de errores. En cualquier lugar de un programa se pueden producir errores, pero es evidente que hay lugares más propicios para que estos ocurran. En un principio deberíamos de hacer un amplio uso de los manejadores de errores, pero si no es así, no olvidemos implementarlos en el código que haga uso de recursos ajenos a la propia aplicación en sí: interacción con bases de datos, protocolos de comunicaciones, ficheros, etc. De esta forma evitaremos los famosos “Runtime Errors” que hacen que ‘desaparezca’ nuestra aplicación.
q q Controlar las dependencias de cada clase. Un inconveniente bastante grande que nos encontramos a la hora de reutilizar una clase en otro proyecto, es que con demasiada frecuencia desconocimos los recursos necesarios para hacerla funcionar, es decir qué componentes utiliza, qué otras clases instancia, etc. Un mecanismo sencillo para controlar esta información consiste en incluir en el comentario cabecera de cada clase una línea en la que especifiquemos claramente todas sus dependencias (componentes que usa, clases que instancia, objetos o variables globales que utiliza, etc).
q q Validar la consistencia de clase. Es importante que nos aseguremos que una clase sólo se use si está en un estado consistente. En VB no es obligatorio llamar a un constructor con lo que debemos asegurarnos no solo de tener uno (aunque esté casi vacío) sino obligar a usarlo. Podemos también encontrarnos con clases cuyo proceso de instanciación esté gradado o se da en varios pasos. Tener una variable de clase que nos indique si la clase se haya en un estado consistente (es decir si ha sido iniciada con todos los datos y referencias que necesita) es algo que puede ahorrarnos muchos problemas. Por lo general yo denomino a esta variable ‘bConsistC’.
q
q Mantener la
unicidad funcional. Hacer que las funciones que construyamos sirvan para una única
cosa. Esto hace más fácil reutilizarlas, depurarlas, construir aserciones para
ellas, entenderlas y comunicarlas.
q q No incurrir en el desnivel semántico. El código básicamente se estructura en bloques de dos tipos: funciones y procedimientos. Debemos de procurar que el nivel de las abstracción que manipulemos estén en un nivel de abstracción similar. Esto favorece la creación de código claro, muy autoexpresivo y libre de errores, además de que es más fácil de entender y depurar. Lo contrario, es incurrir en lo que llamo desnivel semántico.
5.- Algunos otras reglas generales.
Para finalizar este repaso informal sobre la práctica de la programación apuntamos algunos consejos generales sobre el desarrollo de código de calidad, que no debemos perder de vista.
q q Seguimientos de código. No escatimemos esfuerzos en contemplar como funciona nuestro código. Hoy en día la mayoría de los entornos de desarrollos permiten hacer tareas de seguimiento de una forma sencilla y rica. Es increíble la cantidad de errores que uno es capaz de encontrar viendo funcionar el código paso a paso, ya que en esta situación se invierte la relación entre el programador y el ordenador, siendo el cerebro capaz de pensar más rápido que él, y ver más allá de lo que con anterioridad hemos escrito.
q q Conseguir que el código tenga una visualización correcta. Hacerlo más legible. Esto se consigue formateando bien el código: sangrado, colores, estructuración, tamaño adecuado de líneas, extensión correcta en funciones, procedimientos, clases, etc.
¿Cuál es más fácil de leer?
1.-
Public Sub
Procedure(parameters list)
On Error GoTo
errCatch
Debug.Assert
(ValidateSomething())
If bSomeCondition =
False Then
DoSomething
Else
For each object in
colObjects
If
bSomeOtherCondition = false then
DoOtherThings
End If
Next
End If
Exit Sub
errCatch:
MsgBox
Err.Description, .......
End Sub
2.-
Public Sub
Procedure(parameters list)
On Error GoTo
errCatch
Debug.Assert (ValidateSomething())
If bSomeCondition = False Then
DoSomething
Else
For each object in colObjects
If bSomeOtherCondition = false then
DoOtherThings
End If
Next
End If
Exit Sub
errCatch:
MsgBox Err.Description, .......
End Sub
q
q Construir
interfaces gráficos consistentes. Es importante situar al usuario ante lo
conocido, y por ello no debemos temer el ‘copiar’ el estilo de aplicaciones
comúnmente usadas que sitúan rápidamente a nuestros usuarios sobre la pista de
cómo deben de interaccionar con la aplicación. Es importante usar estructuras
visuales que reflejen aquello que se está manejando (las estructuras que están
por abajo). Esto hace que el usuario pueda olvidarse que existe una aplicación
entre él y sus datos.
q q Centrarse en lo importante. No todas las opciones de un programa tienen igual importancia, con lo que no debemos de olvidar asegurarnos que aquello que de verdad tiene que hacer una aplicación lo haga bien.
q q De forma general, no sacrificar claridad por velocidad. La velocidad depende básicamente de la arquitectura y tecnología usada en una aplicación y no de detalles concretos que hayamos desarrollado en nuestro programa.
Bibliografía:
Moisés Daniel Díaz, Ingeniero en
Informática.
Web:
www.moisesdaniel.com