martes 27 de febrero de 2007

Editorial: Recomendaciones de estilo

No basta con escribir un programa que funcione. El código tiene que estar bien escrito. El problema del estilo es muy recurrente en el desarrollo de software. Muchas veces se escribe el código pensando que la única persona que lo modificará es el mismo programador. Y cuando llega alguien más, y comienza a revisar el código, comienzan los problemas.

Peor aún es cuando se mezclan estilos de programación. Me ha tocado revisar códigos donde hay hasta cinco estilos diferentes de programación. Que si uno usa notación húngara, que si otro emplea camelCase, que si otro prefija las variables con el alcance de la variable, que si para las variables miembro se les prefija con una m_, o simplemente con el guión bajo, o no se les prefija... un reto leer esos códigos.

¿Por qué es importante? A continuación te presento un código escrito en C en 1988. ¿Podrías decirme qué es lo que hace el programa?


#define _ -F<00||--F-OO--;
int F=00,OO=00;main(){F_OO();printf("%1.3f\n",4.*-F/OO/OO);}F_OO()
{
_-_-_-_
_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_
_-_-_-_
}


Aunque es divertido ver este código, apuesto a que pocos sabrán que hace el programa. Lo que hace es calcular el valor de Pi viendo su propia área. De hecho, este programa ganó en 1988 el International Obfuscated C Code Contest. Interesante, ¿no? Evidentemente no queremos que nuestro código sea igual de intentendible que el anterior. Bueno, al menos no deberíamos querer. Pues bien, hay algunas reglas estándares que podríamos seguir para hacer de este mundo uno más feliz, para los programadores al menos. Y también escribo a continuación mis sugerencias.

1. Comentar el código.

No hay absolutamente ninguna excusa para omitir comentar tu código. Pero sí hay límites. Cuando comentas una clase, debes decir cuál es el propósito de la misma, resaltar algunos métodos importantes y sus relaciones con otras clases.

Cuando comentes un método o una función, describe qué hace, no cómo lo hace. Información sobre los parámetros y el valor que la función regrese siempre es útil. Si el método cambia el estado de la clase, sería bueno que lo indicaras.

Al comentar variables, ten en cuenta que en primera instancia el nombre de la misma debería ser lo suficientemente clara. Si por algún motivo no lo es (i.e. la variable se usa para más de lo que su nombre indica) es recomendable poner un comentario breve al respecto.

Con respecto al código empotrado dentro de un método/función, es bueno que se comente algunos pasos importantes, o el por qué se tomó tal o cuál desición de realizar el algoritmo de la forma en que se lleva a cabo. A veces, para métodos especialmente largos, es bueno comentar por bloques los pasos que va haciendo la función.

Finalmente, pero no menos importante: no insultes la inteligencia de los demás. Digo, es bueno poner comentarios, sin excederse claro, pero por favor, omite hacer comentarios obvios que no hacen otra cosa que insultar a quien lo lee. Un ejemplo que encontré en un código hace poco:

// almacena el valor de retorno de la función.
int iValorRetorno;
...
// se asigna 0 al valor de retorno de la función.
iValorRetorno = 0;


Por favor. Si algún día me entero quién escribió eso, seguro le doy un buen golpe en la nariz. Así que sería bueno omitir este tipo de comentarios.

2. Convención de nombres.

Lo voy a poner de forma clara y directa. Dado el contexto (es decir, sabiendo de qué trata la clase), yo debería saber, por el puro nombre, qué hace el método, o para qué se emplea la variable. Los nombres tienen que ser claros e intuitivos, pero tampoco hay que caer en exageraciones. Por ejemplo:


class CProducto { ... };

class CListaProductos
{
public:
CProducto BuscarEnLaListaDeProductos(const std::string& objIdDeProductoABuscar);
};


Lo anterior es un ejemplo de un método cuyo nombre describe de más. Dada la clase CListaProductos es claro que estamos tratando sobre una lista, y por ende, sobre varios elementos (tentativamente relacionados a CProducto). Es decir, tenemos un contexto claro. Un método que busque es obvio que lo hará en la propia lista, así que el nombre se podría abreviar sin que se pierda claridad. El nombre del parámetro, por supuesto, podría quedar simplemente como objId o a lo más, objIdProducto.

3. Convención de nombres.

Elige bien la convención de nombres. Hay diversas. Desde el estándar de C y C++ que consiste en nombrar clases y variables en minúsculas y de forma abreviada, la notación húngara -mi favorita-, el tipo de nombres de Pascal y la notación camello. Yo prefiero la notación húngara, para obtener información sobre el tipo de dato de la variable. Pero esto es muy a gusto personal.

Muchas personas deciden añadir prefijos también dependiendo del alcance de la variable. Por ejemplo, l_ cuando es una variable local, p_ cuando es un parámetro de una función, m_ cuando es una variable miembro, s_ cuando la variable miembro es estática y g_ cuando se trata de una variable global. En lo personal, yo solo empleo los prefijos m_ y g_. No uso las demás, porque me parecen un poco redundantes. Además, luego el código puede quedar muy confuso entre tantos guiones bajos: algo no muy agradable a la vista, en mi opinión.

Una cosa es cierta: cuando se elija un estilo, este se queda para toda la aplicación. No hay nada más fastidioso que tener código que mezcla notación hungara con notación de C, con notación de camello. Si tienes que modificar un código, trata de respetar el estilo, o bien, cambia todo el código. Pero recuerda que cada quién tiene su forma de trabajar.

4. Sangrado y espaciado

A veces me he encontrado con código como el siguiente:


void foo()
{
...linea 1...
...linea 2...
for (...)
{
...linea 3...
...linea 4...
}
}


¿Acaso es mucho pedir que se formatée el código de forma legible? Al parecer para mucha gente sí lo es. No hay nada como un sangrado que permita leer bien el flujo de la aplicación. De esta forma uno sabe en qué momento terminan bloques de código sin tener que revisar todo el bloque. Y es mejor si es uniforme, y no que el primer sangrado es un tab, el segundo dos espacios, el tercero cinco, etc.

5. Bloques de código

Esto también es importante. En el cuerpo de una función es conveniente agrupar los procesos similares en un bloque, es decir, que las sentencias terminen en un solo retorno de línea, y que cuando el bloque acabe, incluír dos retornos de línea. Esto ayuda a que el código no se amontone.

6. Números mágicos

Evita los números mágicos. Nada de:


int foo()
{
return 7;
}


¿Por qué 7 y no 32? En todo caso, es mejor tener un símbolo definido:


#define DIAS_DE_LA_SEMANA 7

int foo()
{
return DIAS_DE_LA_SEMANA;
}


Esto ya es más legible. Para el caso en que haya varios valores relacionados, usualmente es mejor emplear una enumeración (enum). Y si es dentro de una clase, mejor (claro, en caso de que aplique).

7. Variables globales

En general, las variables globales deberían evitarse. En su lugar considera crear una clase Singleton. Por ejemplo:


class Configuración
{
private:
Configuracion() { ... }
...

public:
static Configuracion* Instancia()
{
static Configuracion configuracion;
return &configuracion;
}
};

void foo()
{
Configuracion* pConfig = Configuracion::Instancia();
pConfig->HacerAlgo();
}


Es mejor que tener una variable global, sobre todo si ésta solo tendrá una instancia. Si por algún motivo es necesario emplear variables globales, se recomienda entonces prefijarlas con el operador de alcance de resolución, como en:


int g_iVariable;

void foo()
{
::g_iVariable = 5;
}


Esto puede ser una buena alternativa a prefijar las variables con g_.

8. Nombre de archivos

En general siempre es una buena idea que la declaración de una clase -y solo su declaración y símbolos relacionados a ésta- se incluyan en un archivo de encabezado, mientras que la implementación se lleve a cabo en el archivo *.cpp correspondiente. Además, es buena idea darle el mismo nombre que la clase. Por ejemplo, para la clase MiClase, tendríamos el archivo MiClase.h donde viene la declaración, y MiClase.cpp donde viene la implementación de la misma, nada más. Esto establece y garantiza un orden en todo nuestro código. En el caso particular de que los nombres de clases los prefijes con alguna letra (como CObject, por ejemplo) es conveniente entonces que el nombre de los archivos sea Object.h y Object.cpp.

Incluir definiciones en los archivos de implementación es mala idea porque si esa clase es requerida en otro lado, ya no se podrá emplear ahí, y habrá que redefinir la clase. Por otro lado, es inconveniente que la implementación se de en el archivo encabezado. Si el archivo se incluye en más de dos implementaciones, tendríamos un error de redefinición.

Un caso excepcional puede hacerse en el caso de las plantillas. Esto, porque en muchas ocasiones una plantilla se empleará como librería. Debido a que la plantilla no se compila sino hasta que se instancia con un tipo de dato específico, es imposible exportarlas desde una librería de enlace dinámico. Entonces, usualmente éstas se distribuyen en código fuente, y pues es mejor solamente incluir el encabezado (con su definición) que también tener que incluir los códigos fuentes.

Bueno, estas son algunas de las recomendaciones que puedo hacer. Con el tiempo, espero agregar más. Cada programador tiene su forma peculiar de programar. Pero podemos poner un poquito de nosotros para hacer de nuestro código más legible. Algún día, cuando leas un código heredado, agradecerás que el programador que lo desarrolló haya seguido lineamientos estándares sobre el estilo de programar... y lo maldecirás en caso contrario.