Clean Code: Buenas Prácticas para Escribir Código Limpio y Eficiente
Escribir un código limpio además de una habilidad que todos debemos tener, es una práctica esencial para cualquier desarrollador que busque crear aplicaciones escalables, fáciles de mantener y legibles por otros.
Y no solo eso, sino que también te abrirá puertas, ya que si en tus proyectos públicos tu código es limpio y utilizas buenas prácticas, es un muy buen punto para tu próxima aventura laboral.
Este tutorial te guiará a través de los principios fundamentales de Clean Code, detallando buenas prácticas que transformarán la calidad de tu código. Aprenderás a organizar mejor tus proyectos, a estructurar tus funciones y variables de manera intuitiva, y a evitar errores comunes que afectan la claridad y eficiencia del software.
Deuda técnica
Probablemente, ya hayas oído hablar sobre la importancia de la calidad del código, y no es para menos: cuando no cuidamos detalles como refactorizar o añadir comentarios claros, siempre se paga un precio.
Las excusas más comunes suelen ser la falta de tiempo para el mantenimiento, la dificultad de entender un código complejo (porque si, incluso con mucha experiencia, un código denso puede ser un desafío), o la falta de disponibilidad de un compañero para transferir el conocimiento necesario.
Adoptar prácticas de Clean Code ayuda a evitar estos problemas y a construir una base de código más robusta y comprensible para todos.
Hay cuatro tipos de deuda técnica:
- Imprudente: Por ejemplo, copiar un código que te has encontrado por internet para salir del paso sin ni siquiera entenderlo.
- Imprudente e inadvertida: Cuando no tienes conocimientos sobre buenas prácticas o patrones de diseño.
- Prudente: Y digo prudente entre comillas porque en este caso sabemos que lo estamos haciendo mal, pero dejamos para más adelante una refactorización, separar en clases, etc.
- Prudente e inadvertida: Cuando después de terminar una tarea nos damos cuenta de como podríamos haberlo hecho mejor.
Cómo mejorar nuestro código
A continuación vamos a ver algunos consejos para mejorar nuestro código 🙌.
Refactorización
Refactorizar un código es el proceso por el cual el objetivo es mejorar un código existente para que sea más entendible y tolerante a cambios. Un ejemplo puede ser una clase inmensa que realiza varias acciones distintas, la típica clase Utils
, que puede tener por ejemplo un método para generar un UUID
, otro método que realiza peticiones CURL
, etc.
La idea sería separarla en distintas clases en las que cada una cumpla sus cometidos.
En este caso es importante contar con pruebas automáticas para que cuando realicemos la refactorización, nuestro proyecto siga manteniendo el comportamiento esperado.
Nombres pronunciables y expresivos a la hora de declarar variables, constantes y métodos
Es fundamental asignar nombres claros y comprensibles a variables, constantes y métodos. Aunque a veces puede ser tentador ahorrar caracteres, hacerlo suele perjudicar la legibilidad y dificulta el entendimiento de otros desarrolladores.
Lo que para ti puede ser obvio al abreviar, para un compañero podría ser confuso y propenso a malinterpretaciones.
Opta siempre por nombres que comuniquen la intención de forma directa. Si encuentras que necesitas añadir comentarios para aclarar el propósito de un nombre, es probable que el nombre no sea lo suficientemente explícito.
Evita redundancias: Ser expresivo no implica añadir palabras innecesarias. Por ejemplo, al nombrar una función, evita prefijos como fn
o function
. Estos elementos no aportan claridad y solo agregan ruido visual.
Nombrar las variables según el tipo de dato
Si es booleano
, podemos usar prefijos como is
, can
, has
y siempre positivos.
let isUserActive: boolean = true; // indica si el usuario está activo.
let isProcessComplete: boolean = false; // indica si el proceso está completado.
let canUserEdit: boolean = true; // indica si el usuario tiene permiso para editar.
let canSendNotification: boolean = false; // indica si se pueden enviar notificaciones.
let hasAccess: boolean = true; // indica si se tiene acceso.
let hasCompletedTask: boolean = false; // Correcto: indica si se ha completado la tarea.
En el caso de arrays
, tiene sentido que los nombres sean en plural, ya que pueden contener varios datos.
// Mal nombre, está en singular y no da mucha información
const country: string[] = ['Spain', 'Portugal', 'Italy']
// Un poco mejor pero cometemos el error de indicar el tipo de dato en el nombre de la variable
const countryArray: string[] = ['Spain', 'Portugal', 'Italy']
// Este caso ya es bueno, añadimos el nombre en plural, sin embargo, solo con countries,
// a simple vista no sabemos si es una lista de objetos, nombres, identificadores.
const countries: string[] = ['Spain', 'Portugal', 'Italy']
// El mejor nombre, muy explicito pero sin sobrepasarnos, indicamos que es un listado de nombres de paises
const countryNames: string[] = ['Spain', 'Portugal', 'Italy']
Si es un número, podemos usar prefijos como min
, max
y total
.
const minFlightsPerDay = 5
const maxFlightsPerDay = 10
let totalFlightsToday = 7
Al nombrar funciones, utiliza un verbo seguido de un sustantivo que describa lo que realiza la función. Sé específico, pero comedido, evitando detalles innecesarios en el nombre. El objetivo es que el nombre exprese claramente la acción que realiza la función sin introducir condiciones que pueden manejarse dentro del código.
// Malos nombres
function validateFormIfNotEmpty() { /* ... */ }
function checkPermissionIfUserLoggedIn() { /* ... */ }
// Buenos nombres
function validateForm() { /* ... */ }
function checkPermission() { /* ... */ }
// Malos nombres
function getUserDataIfAuthorized() { /* ... */ }
function fetchOrderIfExists() { /* ... */ }
// Buenos nombres
function getUserData() { /* ... */ }
function fetchOrder() { /* ... */ }
Nombres de clases
El nombre de una clase debe ser claro y específico, capturando su propósito sin ser genérico ni demasiado extenso. Dado que el nombre de la clase es lo primero que comunica su función, asegúrate de que describa con precisión lo que hace y su rol dentro del sistema.
Para encontrar el mejor nombre, considera estas preguntas:
- ¿Qué hace exactamente esta clase? Define el propósito principal de la clase y asegúrate de que el nombre lo refleje.
- ¿Cómo realiza esta clase su tarea? Si su funcionamiento o contexto es único, incluye detalles que lo hagan evidente.
- ¿Hay algo específico sobre su ubicación o contexto de uso? Si la clase pertenece a un módulo o área específica del sistema, eso puede ser útil para la claridad del nombre.
Orden y estructura recomendada para una clase
- Propiedades estáticas: Declara las propiedades de clase que no dependen de instancias.
- Propiedades de instancia: En el siguiente orden, declara primero las propiedades privadas, luego las protegidas y finalmente las públicas.
- Constructores estáticos: Define constructores estáticos, si son necesarios, para inicializar la clase sin instancias.
- Constructor de instancia: Incluye el constructor estándar que inicializa las instancias de la clase.
- Constructores privados: Añade cualquier constructor privado requerido para la creación controlada de instancias.
- Métodos estáticos: Declara los métodos que no dependen de instancias y que son de utilidad para la clase en general.
- Métodos privados: Coloca aquí los métodos internos que no deben ser accesibles desde fuera de la clase.
- Métodos de instancia: Organiza el resto de métodos de instancia en orden de importancia o frecuencia de uso, de mayor a menor.
- Getters y setters: Ubícalos al final, para gestionar el acceso y modificación de las propiedades privadas o protegidas.
Esta estructura ayuda a mantener el código limpio, organizado y facilita su comprensión.
Clean Code en funciones: argumentos y parámetros
Para escribir funciones claras y manejables, es recomendable limitar el número de parámetros a un máximo de tres. Si necesitas más, considera usar un objeto o, en algunos casos, un array
. También es útil ordenar los parámetros alfabéticamente para mejorar la claridad.
A continuación, te dejo unas buenas prácticas en el diseño de funciones:
- Mantén una sola responsabilidad: Cada función debe realizar solo una tarea específica. Si realiza varias tareas, divídela en funciones más pequeñas.
- Simplicidad ante todo: La función debe ser fácil de entender y libre de lógica innecesaria.
- Tamaño reducido: Es ideal que las funciones sean concisas; funciones de una sola línea están bien siempre que no aumenten la complejidad.
- Líneas limitadas: Para facilitar la lectura y mantenimiento, procura que una función tenga menos de 20 líneas, aunque esto no siempre va a ser posible.
- Evita el uso de
else
: Estructurasif
claras sinelse
suelen ser más legibles. En lugar deelse
, organiza el flujo de la función para que sea directo. - Usa el operador ternario para condiciones simples: El operador ternario (
condición ? valorSiVerdadero : valorSiFalso
) es una opción compacta y clara para condiciones que no requieren estructuras complejas.
Cómo usar comentarios
Evita usar comentarios en los casos en los que el código se ve claro y explícito. Si hay un código mal escrito, no añadas un comentario, reescríbelo para mejorarlo.
Uniformidad en el proyecto
Es importante mantener un estilo uniforme al nombrar métodos en nuestras clases para garantizar claridad y coherencia en el código. Consideremos este ejemplo:
// Clase 1
const createOrder = () => {};
const updateOrder = () => {};
const deleteOrder = () => {};
// Clase 2
const createNewUser = () => {};
const modifyUser = () => {};
const removeUser = () => {};
En la primera clase, los métodos utilizan los prefijos create
, update
y delete
, lo que establece un patrón claro y consistente. Sin embargo, en la segunda clase, aunque las funciones realizan tareas similares, se utilizan prefijos diferentes como createNew
, modify
y remove
. Este cambio rompe la uniformidad y dificulta la lectura y comprensión del código.
Recomendación: Usa los mismos prefijos y convenciones para tareas similares, independientemente de la clase o entidad. Por ejemplo, en lugar de modifyUser
y removeUser
, utiliza updateUser
y deleteUser
. Esto no solo mejora la consistencia del código, sino que también facilita la colaboración y el mantenimiento del proyecto.
También puedes definir esta información en el archivo README
de tu proyecto para tenerla siempre disponible y compartirla con los demás integrantes.
Code smells
Los code smells (o "malos olores de código") son indicadores de que el código puede tener problemas de diseño, estructura o mantenibilidad que podrían causar errores o hacer que el sistema sea difícil de entender y extender.
Aunque no son errores que impidan que el programa funcione, los code smells pueden llevar a problemas más graves a medida que el proyecto crece.
Te dejo algunos ejemplos comunes de code smells:
- Código duplicado: Fragmentos idénticos o muy similares en diferentes partes del código. Esto aumenta el riesgo de inconsistencias y hace el código más difícil de mantener.
- Métodos o clases demasiado largos: Funciones o clases que realizan múltiples tareas y tienen muchas líneas de código. Esto hace que el código sea complejo y difícil de entender.
- Código muerto: Variables, funciones o bloques de código que nunca se usan. Esto causa confusión y aumenta el tamaño del código innecesariamente.
- Excesiva complejidad: Código que utiliza estructuras o lógica innecesariamente complejas, cuando una solución más simple sería suficiente.
- Nombres ambiguos: Variables, funciones o clases con nombres poco claros o imprecisos que dificultan la comprensión de su propósito.
- Clases con demasiadas responsabilidades (God Class): Clases que tienen demasiadas responsabilidades y hacen demasiadas cosas. Esto viola el principio de responsabilidad única y hace que el código sea difícil de mantener y probar.
- Funciones con demasiados parámetros: Funciones o métodos con muchos parámetros, lo que hace que el código sea difícil de leer y propenso a errores.
- Condicionales excesivos: Uso excesivo de estructuras condicionales complejas (
if-else
oswitch
) que pueden reemplazarse con polimorfismo, patrones de diseño o estructuras más limpias.
Espero que este post te ayude y como siempre, te recomiendo seguirme en Twitter para estar al tanto de los nuevo contenido. Ahora también puedes seguirme en Instagram donde estoy subiendo tips, tutoriales en vídeo e información sobre herramientas para developers.
Por último os dejo mi guía para aprender a trabajar con APIs donde explico todo el funcionamiento de una API, el protocolo HTTP y veremos como construir una API con arquitectura REST.
Nos leemos 👋.