Usando buenos nombres

Actualmente me encuentro leyendo una joya de la literatura informática de los últimos años, Clean Code: A Handbook of Agile Software Craftsmanship, de Robert C. Martin (os recomiendo su lectura encarecidamente). Sin enrollarme demasiado, el libro muestra como mejorar nuestro código (y como no empeorarlo) siguiendo algunas técnicas que, en los dos últimas décadas, han demostrado ser efectivas para mantener un proyecto de software funcionando de la mejor manera posible durante el mayor tiempo posible.

Este artículo trata sobre una de esas técnicas, una tan sencilla como poderosa, y que podemos empezar a aplicar desde ya a todo el código que pase por nuestras manos: usar buenos nombres. Entremos en materia.

¿Por qué usar buenos nombres?

Usar buenos nombres es esencial para mantener el código legible. Como programadores, pasamos más tiempo leyendo código (propio y ajeno) que escribiéndolo (según algunos estudios ¡en un ratio de 10:1!); toda nueva funcionalidad se basa en funcionalidad anterior, y si esa funcionalidad anterior no es legible nuestra tarea se ralentizará y, en el peor de los casos, puede convertirse en una pesadilla. Más aún, prácticamente todo tiene un nombre (constantes, variables, métodos, clases, paquetes, ...) así que entidades bien definidas con un nombre apropiado y no ambiguo nos harán el trabajo mucho más sencillo. Usar buenos nombres nos va a ayudar a desarrollar más rápido, cometer/producir menos errores, y sobre todo a ser profesionales y sinceros con nuestro trabajo. Comencemos con las reglas.

Ten cuidado al elegir los nombres y cámbialos cuando encuentres uno mal definido

Sin llegar a convertir esta regla en una obsesión, pues puede haber más de una manera igual de adecuada de nombrar una entidad, es la base de todo lo que se va a exponer. Debes aceptar esta regla de manera natural y acometerla sin poner excusas. La forma de llevarla a cabo está explicada en el resto de reglas.

Nombrando clases

Clases y objetos deben ser nombrados usando sustantivos como Cliente, Usuario y Empleado.

Nombrando métodos

Métodos deben ser nombrados usando verbos o frases con verbos como pagarSalario, borrarArchivo, etc. Métodos que actúan como accesores según el estándar Javabean deben seguir las reglas típicas (getXxx, setXxx, isXxx).

Usa nombres que revelen una intención

El nombre de una variable, un método, o una clase debe decir porqué existe, qué hace, y qué representa, respectivamente. Si una variable, clase o método necesita un comentario que responda a una de esas tres cuestiones es que su nombre no está revelando su intención:

int dias; // dias transcurridos desde la última comprobación
	

El nombre de la variable anterior es ambiguo, y cuando sea usada 50 líneas después de ser declarado no nos estará diciendo quien es, porqué está ahí y porqué debe ser usada de la forma en que lo esté siendo. Lo más probable es que tengamos que retroceder buscando el lugar donde ha sido declarada (y tal vez viendo cómo ha sido usada anteriormente) para responder a las tres preguntas anteriores. Una manera más adecuada de nombrarla hubiera sido así:

int diasDesdeUltimaComprobacion;
	

Ahora, la misma variable leída las mismas 50 líneas más abajo expresa claramente su razón de ser y la del código que la está usando. Adicionalmente, hemos omitido el comentario asociado ya que la información que proporciona es ahora redundante.

Evita la desinformación

La desinformación oscurece el código, llevándote a seguir falsas pistas y a perderte entre ellas. No uses nombres del tipo usuariosList para un objeto que no represente realmente una colección de tipo List (mejor usa conjuntoDeUsuarios o aún mejor usuarios).

Otra fuente de desinformación resulta de nombres muy largos y muy similares:

public class ControladorParaManejoEficienteDeEntradas { }
public class ControladorParaGuardadoEficienteDeEntradas { }
	

Para este último caso no hay solución sencilla (ambas clases están nombradas de manera adecuada) y lo mejor es tener presente la similitud de sus nombres, prestando atención a si estamos usando la clase adecuada en cada momento.

Un caso extremo de desinformación se produce al usar los caracteres l (ele minúscula) y O (o mayúscula), que son fácilmente confundidos por 1 (uno) y 0 (cero).

Realiza distinciones de significado

Que el compilador entienda y compile todo el código que le proporcionamos no nos convierte en buenos programadores. Una de mis citas favoritas sobre programación (y que tengo siempre pinchada en la pared delante de mí) es de Martin Fowler y dice lo siguiente:

Any fool can write code that a computer can understand. Good programmers write code that humans can understand.

Que traducido viene a decir:

Cualquier tonto puede escribir código que un ordenador puede entender. Los buenos programadores escriben código que los seres humanos pueden entender.

Que nuestro compilador entienda la diferencia inherente entre dos variables array1 y array2 no le va a importar lo más mínimo a quien, seis meses después de haber sido escritas, tenga que mantenerlas (y ese alguien podríamos ser nosotros) y no sepa por donde empezar porque sus nombres no significan nada. Ese día seremos maldecidos:

void copiarCaracteres(char[] array1, char[] array2) {
    for(int i = 0; i < array1.length; i++) {
        array2[i] = array1[i];
    }
}
	

Aunque el método anterior tiene un nombre adecuado, el algoritmo que contiene en su interior es confuso. Y si en lugar de contener solamente 2 lineas de código tuviera 20, sería un caos. La solución es hacer una distinción de significado entre los nombres de los argumentos del método, renombrándolos para que tengan un significado explícito y claro:

void copiarCaracteres(char[] origen, char[] destino) {
    for(int i = 0; i < origen.length; i++) {
        destino[i] = origen[i];
    }
} 
	

Otra fuente de indistinción de significado nace del uso de ciertas palabras en los nombres que damos a una entidad. ¿Acaso no resultan tremendamente similares en significado las variables cliente y clienteInfo? ¿Que representa cada una de ellas cuando ambas aparecen en el mismo contexto? Por este motivo, palabras como info y datos deben ser evitadas.

Usa nombres pronunciables

Imagina el siguiente ejemplo:

Date genAmdhms() { }
	

El ejemplo anterior es un caso claro, tal vez un poco extremo (aunque recuerdo haber hecho cosas no mucho mejores en alguna ocasión...), de nombre impronunciable, y está relacionado con una de las primeras reglas que vimos: USA NOMBRES QUE REVELEN UNA INTENCIÓN. Probablemente el método vaya acompañado de un comentario que clarifique cual es su función, ya que de otra manera sería prácticamente imposible deducir cual es ésta (función que, dicho sea de paso, es generar el año, mes, día, hora, minuto, y segundo actual). ¿Acaso no habría sido más sencillo hacer lo siguiente?:

Date generarTimestamp() { }
	

Otro problema que surge al usar nombres impronunciables aparece cuando tenemos que comunicarnos verbalmente con un compañero o socio. Imagínate por un momento la siguiente conversación:

Durante la última batería de tests ha aparecido un bug que apunta al método gen a eme de hache eme ese, ¿puedes echarle un vistazo?

Creo que las palabras sobran.

Usa nombres que puedan ser buscados

Nombres con un solo carácter (o extremadamente cortos) y el uso de literales numéricos sufren de un mal común: son difíciles de buscar. El el primer caso (nombres con un solo carácter) su uso debería ser reducido a métodos más bien cortos y algunos casos especiales (por ejemplo la típica variable i dentro de un bucle for) y nunca en ningún otro lugar. Una variable llamada e para representar a un objeto de tipo Empleado es una atrocidad que no debes cometer jamás. En el segundo caso (literales numéricos) deberían ser sustituidos por constantes con un nombre bien definido. Veamos un ejemplo:

if (empleado.getHorasTrabajadasUltimaSemana() > 40) {
    pagarHorasExtra(empleado);
} 	
	

En el bloque condicional anterior se está usando un literal numérico (40) donde debería utilizarse una constante con un nombre bien definido. Si en el futuro decidimos que las horas base por semana deben ser 35 en lugar de 40, nos veremos obligados a buscar cada linea donde aparece el valor 40 (problema 1: es difícil de buscar) y determinar en cada una de ellas si dicho valor 40 hace referencia a las horas trabajadas, puesto que si hace referencia a otro contexto (por ejemplo el número máximo de empleados por departamento) y lo cambiamos por error, estaremos introduciendo un bug que será difícil de encontrar cuando la aplicación comience a funcionar mal (problema 2: propensión a introducir errores). La solución ya se ha mencionado dos veces: declarar una constante con un nombre adecuado:

final static int HORAS_BASE_POR_SEMANA = 40;

// ...

if (empleado.getHorasTrabajadasUltimaSemana() > HORAS_BASE_POR_SEMANA) {
    pagarHorasExtra(empleado);
} 
	

Ojalá algún día a todos nos paguen las horas extra con la misma facilidad :)

Elige una única palabra por cada concepto

Elige una palabra para cada concepto abstracto y úsala siempre. Imagina tres clases, cada una con un método que obtiene información de forma similar desde una base de datos. En la primera clase el método se llama leerDatosDesdeBBDD, en la segunda obtenerDatosDesdeBBDD y en la tercera recuperarDatosDesdeBBDD. Esto sólo puede crear confusión, además de ser muy poco elegante.

Es justo reconocer que este error (o más exactamente violación de buenas prácticas) lo he cometido a menudo en el pasado, por ejemplo diseñando una interface DAO con métodos llamados leerXxx, leerYyy, ... y una interface de servicio con métodos llamados obtenerXxx, obtenerYyy, ... . Esta última interface se limitaba a llamar a los métodos del DAO, y a pesar de trabajar conectadas, cada una utilizaba su propia nomenclatura, resultando en código confuso (y muy poco estético).

Quienquiera que lea código de este tipo se empezará a hacer preguntas totalmente innecesarias como ¿que diferencia hay entre leer datos y recuperar datos? ¿Lee desde un archivo y recupera desde una base de datos? ¿O lee desde una base de datos y recupera desde un archivo? Entonces tendrá que revisar código fuente, documentación y/o especificaciones para descubrir que ambos métodos hacen lo mismo, aunque han sido nombrados según convenciones diferentes. El código debe ser (dentro de sus/nuestras posibilidades) como la prosa, como un relato que cuenta una historia que puedes comprender frase a frase. Nunca como una adivinanza ni como un juego de palabras. No dejes nada al azar.

Evita usar la misma palabra para dos conceptos

Esta regla es hermana de la regla anterior. Imagina otras tres clases, cada una con un método llamado añadir. En la primera clase, dicho método añade un registro a una base de datos (insert), en la segunda añade un objeto a un array (push), y en la tercera añade una cadena de texto a otra previamente almacenada en una variable (concat). Esos tres métodos, si son utilizados "cara a cara" en una misma aplicación (como la interface de servicio y la interface DAO de la regla anterior) producirán confusión al lector, ya que los tres se llaman igual pero hacen cosas completamente distintas (ten presente que intento hacer una distinción sutil del qué hace y no del cómo lo hace, siguiendo el más que importante principio de encapsulación). La solución a este problema es ser consistente con los nombres que usamos, o en su defecto o imposibilidad, usar nombres apropiados:

class SimpleDao {
    void añadirRegistroEnBBDD(Registro registro) { }
} 
	

Fácil, ¿verdad?.

Usa nombres que reflejen la solución

Quien lee tu código es también programador, por lo debes usar términos que reflejen la solución que estás usando. Un ejemplo visto anteriormente es usuariosList, que rápidamente se interpreta como una colección de tipo List que contiene usuarios. Quienquiera que tenga que usar dicha variable tendrá conciencia inmediata de qué es y cómo debe usarla. Otro ejemplo de nombre que refleja la solución que está tratando sería UsuarioFacade: una clase que implementa el patrón de diseño Facade para realizar operaciones sobre objetos de tipo Usuario. Es más fácil nombrar bien una entidad que acompañarla con un comentario de 15 palabras.

Usa nombres que reflejen el dominio

Esta regla complementa a la anterior. Cuando tratamos problemas de un dominio especifico, debemos usar nombres que reflejen ese dominio. Un método actualizarInventario está hablando sobre algo llamado inventario, que es parte del dominio de la aplicación que estamos escribiendo. Esto nos va a ayudar tanto a escribir el código necesario para implementar el método (puesto que el nombre deja claro el qué) como a entender su función cuando sea usado en el futuro.

Añade un contexto

La última regla, añade un contexto, nos puede ayudar en ciertos momentos a evitar ambigüedades y por tanto confusión. Imagina que tenemos un conjunto de variables llamadas calle, numero, ciudad y codigoPostal. Todas ellas, cuando se usan conjuntamente, reflejan claramente que estamos tratando con una dirección postal y que sus nombres son perfectamente válidos. Sin embargo, ¿que ocurre si una de ellas, por ejemplo numero, es usada de manera independiente dentro de un método? Para el lector de dicho método, numero puede significar un montón de cosas, o tal vez no significar nada, convirtiendo el método o ciertas partes de él en (como dije unas reglas más arriba) una adivinanza: sabemos el qué, pero no sabemos el porqué. Esto no es código legible.

Existen dos soluciones a este problema. La primera es añadir un prefijo a las variables, lo que les proporciona un contexto y elimina de un plumazo cualquier ambigüedad presente y futura:

String direccionCalle;
String direccionNumero;
String direccionCiudad;
String direccionCodigoPostal;
	

La segunda solución, y en cualquier lenguaje orientado a objetos la ideal, es crear una clase que sea en si misma el contexto:

public class Direccion {
    String calle;
    String numero;
    String ciudad;
    String codigoPostal;
    
    // getters y setters
} 
	

De esta manera dejamos de llamar a numero para llamar a direccion.getNumero(), provocando que el significado de la antes ambigua variable se descubra ahora por si mismo.

Resumen

Todo el código que no es legible y claro en sus intenciones es dicifil de entender y de mantener. Programar debe ser un placer, dentro de su dificultad y otras peculiaridades que no vienen a cuento, y nunca una pesadilla. El código debe fluir, y ningún programador desea ahogarse en un rio desbordado de dificultades. Usar buenos nombres en clases, métodos, variables, etc. es una muy buena manera de empezar a mejorar nuestro código, ayudándonos a nosotros mismos y ayudando también a otros. Y además, es tremendamente sencillo (dentro de su dificultad y otras peculiaridades que no vienen a cuento, again). Te animo a que adoptes los principios que se han expuesto en este artículo; si no te gustan siempre puedes volver a tu estilo habitual, pero lo más posible es que una vez los pruebes y veas como crece la calidad de tu trabajo, nunca des marcha atrás.