Emilio Devesa

Escribir buen código

Uno debe adquirir buenas costumbres a la hora de programar. Son muchas y algunas se refieren a como distribuir el código, otras a como documentarlo, otras a qué orden seguir al escribirlo, otras a cómo indentarlo y dividirlo en partes más simples, etc. De las conclusiones sacadas, aquí están unas pocas sugerencias y un ejemplo práctico. Me centraré en Pascal por ser este uno de los primeros lenguajes de nuestra carrera y el que mucha gente aprende por su cuenta para iniciarse en la programación estructurada, pero en general todo consejo puede ser bueno independientemente de qué lenguaje estemos utilizando.

También voy a comentar justo lo contrario, los errores más graves que creo que se pueden cometer a la hora de abordar un desarrollo.

Distribuir el código
Hoy en día la gran mayoría de lenguajes de programación existentes, y por supuesto los más conocidos, aceptan la distribución del código fuente de un programa en bloques que se conectan al programa principal o que se encuentran interconectados entre sí. Ya se llamen módulos, librerías, units, bibliotecas… al final todos estos nombres se refieren a un fichero que proporciona una determinada utilidad. En nuestra carrera, el ejemplo más evidente son las units de Pascal (concretamente de la implementación FreePascal) donde todas las funciones y procedimientos de uno de estos módulos se crean para proporcionar a un programa la habilidad de crear, modificar y consultar listas, colas, pilas, etc. Es importante por eso darle también un nombre apropiado a estas librerías: “lista.pas” o “juego.pas”, por ejemplo. En otras ocasiones, un programa puede necesitar de forma recurrente operaciones mucho más simples y que no están relacionadas entre sí. Pongo por ejemplo la función que convierta una letra mayúscula en minúscula, que enlace ficheros, o el procedimiento que centre un texto en pantalla, o tal vez el algoritmo que nos devuelva un número generado aleatoriamente. Estas operaciones no tienen nada que ver entre sí y no nos proporcionarán un nuevo tipo de dato abstracto, pero son necesarias y se utilizan de forma común. Más evidente aún es la necesidad de importar en todos los módulos de un programa los tipos de datos que se emplean. En lugar de definir los tipos en cada fichero, podemos escribirlos una vez en un módulo separado y exportarlo. En este caso, a mi me gusta emplear una denominación tomada de la denominación de paquetes de software en sistemas Debian. Igual que a una unit que proporciona una lista podríamos llamarla “lista.pas”, podríamos hablar de “general.pas” o, mi preferido, “base.pas” pues sí que contiene información y operaciones básicas comunes al resto del programa y que, por su naturaleza, no están directamente relacionadas entre sí.

Documentar el código
Es muy importante aprender cuando y como escribir comentarios en medio del código fuente de cualquier programa. Por ejemplo, en la siguiente sentencia no hay nada que comentar:

x := 1;

Sería inútil comentar esta instrucción pues cuando un programador abre un programa de pascal (y suponemos por tanto, que entiende fundamentalmente el lenguaje) sabrá que se trata de una asignación y que se graba el número 1 en la variable x. Los comentarios han de escribirse para otro programador, no para cualquier persona, a no ser que esa sea explícitamente nuestra intención. ¿Cuantas líneas de sintaxis igualmente sencilla hay en cualquier código fuente? Exacto, muchas. Llenar de comentarios redundantes o que explican algo tremendamente obvio solo incomodará a quien quiera leerlo y entenderlo. No obstante, sí que resulta útil comentar fragmentos mayores, como una función, indicando sus entradas, salidas, precondiciones y poscondiciones, objetivo y quizás, alguna aclaración sobre su funcionamiento.

function esListaVacia (L: tLista): boolean;
{Objetivo: Devuelve TRUE si la lista L es una lista vacia, o FALSE en caso contrario
PreCD: La lista esta inicializada}

Tampoco estaría de más comentar las condiciones que se dan para detener un bucle cuya lógica sea un poco enrevesada o no demasiado auto-explicativa. Un ejemplo de esto puede ser:

while not (p = nil) and not (p^.num = n) do p := p^.sig;
{Este bucle se detiene si:
  - Se llega al final de la lista
  - Si el dato num de la posicion p es igual a n
}

Otra posibilidad que nos brindan los comentarios es hacer de delimitadores. No hay problema en dejar tres lineas en blanco de separación entre unas funciones con una ligera relación y otras de carácter distinto, como tampoco importará incluir un comentario como el siguiente:

{ ############################## }

Esta linea sin importancia resulta una gran ayuda visual para organizar de forma eficiente el código, algo de lo que también hablaré más adelante. No puedo terminar el apartado dedicado a la documentación sin hablar de la función principal de los comentarios. Es precisamente comentar una determinada instrucción o una porción de nuestro trabajo y a eso mismo ha de ceñirse, y no se han de utilizar para escribir grandes explicaciones o hablar de aspectos que no son los propios de ese código. Para eso podría recurrirse a la documentación externa, un archivo independiente donde comentar más profundamente aspectos teóricos, decisiones tomadas o desarrollar razonamientos que ayuden a entender el planteamiento general (y más abstraído del código) del programa.

Seguir un orden al escribir código
En casi cualquier lenguaje de programación existen sentencias o palabras de la sintaxis del lenguaje que requieren una apertura y un cierre, o el uso de más de una palabra. Por ejemplo, las palabras BEGIN y END de Pascal. Por cada BEGIN que escribamos, deberá haber otro END, así que no es descabellada la idea de que nada más escribir el primero, colocar ya el segundo y luego escribir el código en medio de ambos. Esto ayudará a no olvidarnos de ningún cierre. Lo mismo se aplica a las aperturas y cierres de paréntesis, llaves, corchetes, etc. Incluso sentencias selectoras como IF requieren otras palabras: THEN y ELSE, por lo que nada más escribir uno, estaría bien escribir los otros dos. Lo mismo para CASE, WHILE, FOR, etc.

Cómo indentar (tabular) el código
Para que cualquier fragmento resulte más legible se puede decir que es necesario tabular las sentencias según su jerarquía en el algoritmo, y emplear también de forma inteligente los saltos de línea. Un ejemplo de lo que NO hay que hacer:

write ('escribe n: ');  
readln (n);  
if n=1  
then begin  
write ('hola');
writeln ('aqui n es 1');  
end  
else writeln ('hola, aqui n es otra cosa')
end; 
write ('escribe i: ');  
readln (i);

Es mucho más claro:

write ('escribe n: ');
readln (n);

if n = 1  
then begin  
	write ('hola');  
	writeln ('aqui n es 1');  
end  
else writeln ('hola, aqui n es otra cosa');  
  
write ('escribe i: ');  
readln (i);  

La ventaja de tener un código escrito de forma bien clara es que su lectura es mucho más sencilla y parece más fácil de depurar o mejorar. Seguramente por eso tampoco te habrás dado cuenta de que en el primer caso sobra el “end;” que hay al principio de la tercera línea 😉

Dividir el código en partes más simples
Nuestro profesor de programación repite año tras año la siguiente máxima:

Si ocupa más de lo que un editor de texto puede mostrar en una sola pantalla, es demasiado largo

Razón no le falta en absoluto. Quiere decir que no hay que hacer larguísimos procedimientos de 100 líneas. Ni tan siquiera de 80, 70, 60 o 50. Utilizar unidades tan largas de código es una de las mejores maneras de cometer muchos errores: usar identificadores que ya se han usado, no cerrar parejas tipo begin-end, escribir condiciones con una lógica enrevesada… Vale la pena hacer trozos de 20 o 30 lineas y saber que ese trozo está bien que no tener que andar mirando y remirando el código fuente por n-ésima vez para que al final el error sea un punto y coma no puesto, un paréntesis no cerrado, un nombre reutilizado para algo con lo que no se puede reutilizar, etc. Además, hacer algoritmos más pequeños facilita la corrección de los mismos.

A conitnuación adjunto un pequeño código fuente escrito en Pascal. Es una función que inserta un dato de forma ordenada en una lista dinámica. En la parte superior del fichero se encuentra la función (y la función auxiliar “CrearNodo”) ya terminada. Luego, tras una linea de comentarios a modo de separador se encuentra la función hecha paso a paso hasta llegar al resultado final, para así apreciar cómo yo escribo mi código de forma ordenada.

buen código

Aprovecharé ahora para referirme a algunas convenciones respecto a nombres identificadores de variables, constantes, etc. y a los editores con los que trabajar. De nuevo emplearé el lenguaje Pascal como ejemplo, pero estas directrices se pueden seguir prácticamente en cualquier lenguaje.

Convenciones
Por enumerarlas de una forma rápida:

...
procedure DibujarCuadrado (lado: integer);
...
...
var
	n_entero: integer;
	letra: char;
...
...
const
	NULO = nil;
	MAX = 100;
...
...
type
	tListaOrdenada = array [1..MAX] of integer;
...

Así vemos claramente que se trata de una lista ordenada.

...
function RaizCuadrada (numero: integer): real;
...
procedure MostrarTextoCentrado (texto: string);
...
...
function RaizCuadrada (numero: integer): real;
...

Es evidente que calcula una raíz cuadrada del entero recibido en la variable “numero”. Sería inútil nombrarla de la siguiente forma:

...
function RaizCuadradaDeUnNumero (numero: integer): real;
...

Este nombre tan largo solo nos causaría incomodidades escribiendo el código fuente y provocará que las líneas del mismo se vuelvan demasiado largas, algo de lo que hablaré más adelante.

Editores
Que yo sepa, todo código fuente debe ser guardado como texto plano sin ningún tipo de formato. Esto ya descarta como posibles editores a muchos procesadores de texto (Microsoft Word, OpenOffice.org Writer, Abiword, etc.). Son excelentes herramientas y por supuesto que pueden trabajar con texto plano, pero lo hacen de una forma muy engorrosa y son aplicaciones muy pesadas y que por lo general no facilitan mucho la tarea al programador, porque no han sido creadas para programar. Un editor de texto plano puede ser desde el viejo editor de MS-DOS (que se puede lanzar en la consola con el comando “edit”) o los más básicos presentes en sistemas GNU/Linux (pico, nano, emacs, vi, etc.).

edit

nano

Desgraciadamente, no pueden competir con las nuevas generaciones, que han evolucionado claramente hacia no solo la creación de pequeños documentos sino la creación de código fuente en muchos lenguajes. Habitualmente yo utilizo gEdit, presente como editor de texto por defecto en Ubuntu y que cumple mis necesidades sin problemas. Las características que más demando en un editor son:

gedit

Los errores

Errores en el análisis del problema

Errores en el diseño de la solución

Errores durante la programación

Algoritmo para hacer cafe:
	1. Preparar la cafetera
	2. Poner al fuego
	3. Servir cuando este listo

A su vez podemos hacer descomposiciones de estos:

1. Preparar la cafetera: 
	1.1 Abrir cafetera 
	1.2 Abrir paquete de cafe
	1.3 Poner X gramos de café en el compartimento de cafe de la cafetera

Llegará un momento en que nuestro algoritmo contenga muchos pasos, pero todos serán operaciones “atómicas” o suficientemente sencillas como para programarlas sin dificultad.

Errores en la depuración

Otros errores

En definitiva, programar bien es una tarea que requiere mucha práctica pero que, como he descrito en algunos de los ejemplos anteriores, puede ser más sencilla si contamos con herramientas apropiadas y adoptamos algunas “buenas prácticas”. Según tu experiencia encontrarás más útiles unos u otros consejos. Por ejemplo en el caso de un estudiante de primero de carrera, quizás los más interesantes sean los consejos de programación “pura y dura” mientras que uno de segundo quizás ya pueda preocuparse del análisis y el diseño.