Código limpio: usa nombres que revelen su intención
Muchas veces estoy trabajando y le pregunto a mi mujer (compartimos despacho): ¿tú cómo llamarías a algo que hace X en un contexto Y?
Poner un nombre en programación es un proceso que hay que tomarse seriamente y cualquier ayuda, punto de vista o idea es bienvenida para seleccionar el más adecuado.
"En software los nombres están en todas partes. Ponemos nombre a las variables, a las funciones, a los argumentos, a las clases y a los paquetes. Ponemos nombre a los archivos de código fuente y a las carpetas que los contienen. Ponemos nombre a nuestros ficheros jar, war y ear. Ponemos nombres y nombres y más nombres. Lo hacemos tantas veces que sería deseable hacerlo bien” Robert C. Martin
En este post voy a recordar la primera regla que Robert C.Martin propone para generar buenos nombres y comentar algunas de las experiencias que he tenido al aplicarla.
La intención de los nombres es lo que cuenta
Usa nombres que revelen su intencionalidad. Tómatelo en serio, dedícale el tiempo necesario para escoger uno y cámbialo tantas veces como sea preciso. El tiempo que le dediques, aunque parezca perdido, más tarde lo vas a recuperar con creces.
Si un nombre requiere un comentario, entonces ese nombre no revela su intención.
int d; // tiempo transcurrido en días
El nombre d no revela nada. No evoca un sentido de tiempo transcurrido, ni de días.
Deberíamos escoger un nombre que especifique lo que se está midiendo y la unidad de esa medida:
int tiempoTranscurridoEnDias; int diasDesdeLaCreacion; int diasDesdeLaModificacion; int antiguedadArchivoEnDias;
Escoger estos nombres en lugar de d facilita la lectura del código y el hecho de poder realizar cambios sin temor.
¿Cuál es el propósito del siguiente código?
public List getThem() { List<int[]> list1 = new ArrayList(); for (int[] x : theList) if (x[0] == 4) list1.add(x); return list1; }
¿Por qué es difícil saber qué está haciendo este código? No hay expresiones complejas. El espaciado y la sangría son razonables. Solo hay tres variables y dos constantes. Ni siquiera hay clases sofisticadas o métodos polimórficos, solo una lista de arrays (o eso parece).
El problema no es la simplicidad del código, sino su carácter implícito: el grado en que el contexto no es explícito en el propio código. Implícitamente, el código requiere que sepamos las respuestas a preguntas tales como:
- ¿Qué contiene theList?
- ¿Cuál es el significado del subíndice cero de un elemento de theList?
- ¿Cuál es el significado del valor 4?
- ¿Cómo usaré la lista que devuelve?
Las respuestas a estas preguntas no se encuentran en la muestra del código, pero podrían haber estado ahí. Supongamos que estamos trabajando en un juego de buscar minas. El tablero es una lista de celdas llamadas theList. Cambiemos el nombre por gameBoard
Cada celda en el tablero está representada por un array. Además, el subíndice cero es la ubicación de un valor de estado, que cuando vale 4 significa "marcado" . Tan solo dando nombres relacionados con el contexto mejoramos considerablemente el código:
public List getFlaggedCells() { List flaggedCells = new ArrayList(); for (int[] cell : gameBoard) if (cell[STATUS_VALUE] == FLAGGED) flaggedCells.add(cell); return flaggedCells; }
La simplicidad del código no ha cambiado. Sigue teniendo exactamente el mismo número de operadores y constantes, con exactamente el mismo número de niveles de anidamiento. Pero el código se ha vuelto mucho más explícito. Podemos ir más allá y escribir una clase simple para las celdas en lugar de usar una array de enteros. Podemos incluir una función que revele la intención (con el nombre isFlagged) para ocultar los números "mágicos". El resultado es una una nueva versión de la función:
public List getFlaggedCells() { List flaggedCells = new ArrayList(); for (Cell cell : gameBoard) if (cell.isFlagged()) flaggedCells.add(cell); return flaggedCells; }
Con estos sencillos cambios de nombre, no es difícil entender lo que está sucediendo. Este es el poder de escoger buenos nombres.
Mi experiencia con esta Regla
Esta idea fue para mí una auténtica revelación. Cuando empecé a aplicarla noté una gran mejoría en mi código. De repente, todo parecía más fácil.
Uno de los mayores obstáculos que me he encontrado al aplicar esta regla es la resistencia al cambio. ¿Cuántas veces después de asignar un nombre a una variable o función has vuelto a renombrarlo? ¿Lo has llegado a renombrar más de tres veces? Si no es así, lo más probable es que con el paso del tiempo el código vaya perdiendo legibilidad.
Ante la duda siempre me hago esta pregunta:
¿Eres capaz de leer el nombre de una función de tu código y saber lo que hace sin mirar su cuerpo?
Si tienes que hacer click en F12 para navegar al cuerpo de la función, mal asunto. Escoge un nombre que describa cada una de las responsabilidades de la variable o función. Con solo leerla ya deberías saber de qué se trata y qué va hacer.
Al intentar aplicar esta regla me he encontrado a mí mismo generando nombres extremadamente largos:
EstablecerCentroCosteImputacionParaSolicitudesQueDebenSerImputadasContraCentroCosteDelSolicitante();
ConfigurarParaQueEnCasoDeRetramitacionSeCalculeDeNuevoElPresupuesto();
Evidentemente estos nombres tan largos no son lo ideal, pero sí son mejores que abreviaturas o nombres genéricos que no evocan nada. En caso de no encontrar nada mejor, no tengo inconveniente en dejarlos así. Con ello consigo que, al vorlver al mismo código meses después, tan solo leyendo los nombres entro en contexto más rápidamente.
Otra ventaja que me encontrado es que, en algunos casos, me empuja a cumplir con el principio de responsabilidad única. Por ejemplo, cuando me encuentro nombrando una función así:
EliminaGeneraInsertaAsientoGastoTrasnferencia();
Que debería separar así:
EliminarAsientoGastoTransferencia(diario); var asiento = GenerarAsientoGastoTransferencia(parámetros,…) InsertarAsiento (asiento);
Como ves, todo son ventajas. :)
Pregunta extra
Yendo más allá, también intento responder a esta otra pregunta:
¿Eres capaz de leer el nombre de una función de tu código a un usuario y que sepa lo que significa?
Esta ya es más difícil pero en lo posible intento que se cumpla.