¿Qué es el código limpio?
El código limpio es código escrito no solo para compilar y ejecutarse, sino para que otros desarrolladores puedan entender su intención y posteriormente modificarlo, ampliarlo y verificarlo de forma segura. No es un concepto definido por un único estándar o puntuación internacional estricta. Más bien, es un término práctico que abarca objetivos de calidad como legibilidad, comprensibilidad, mantenibilidad, coherencia y seguridad de los cambios. google.github.io
Al principio, es fácil pensar que se trata simplemente de «código que se ve bien». Sin embargo, en la práctica, los momentos importantes llegan después de escribir el código por primera vez: al corregir una funcionalidad, encontrar un error, añadir un requisito o revisar el trabajo de un compañero. El código limpio consiste más en reducir el tiempo y la probabilidad de errores en esos momentos. Por ello, la clave no es memorizar técnicas sintácticas concretas, sino considerar qué necesita saber quien lee el código y dónde tendrá impacto un cambio.
¿Qué significa exactamente código limpio?
El software no es un documento que se escribe una vez y luego queda terminado. El código existente se vuelve a leer al añadir un estado de pedido, cambiar una regla de precios o investigar un error. Quien lo lee puede ser el desarrollador original, pero a menudo es otro integrante del equipo o nuestro yo futuro. El código limpio se refiere a un estado en el que esa persona puede entender con relativa rapidez la función del código, sus entradas y salidas, las condiciones importantes y los puntos donde probablemente habrá cambios.
Aquí, «limpio» no significa solamente una valoración estética. Por ejemplo, incluso un código bien formateado es arriesgado de modificar si sus nombres son ambiguos, varias responsabilidades se mezclan en una función y no hay forma de verificarlo. Por el contrario, un código puede ser mejor desde la perspectiva del mantenimiento si su función está clara, se ajusta a las convenciones del equipo y tiene pruebas que permiten confirmar los cambios, aunque no use un estilo especialmente distintivo. La revisión de código examina no solo el estilo, sino también el diseño, la corrección funcional, la complejidad, las pruebas y la documentación. google.github.io
El término código limpio se hizo ampliamente conocido a través del libro de 2008 Clean Code, de Robert C. Martin. Sin embargo, las recomendaciones del libro se sitúan en el contexto de determinados lenguajes y prácticas de desarrollo orientado a objetos. En lugar de aplicar sin cambios un libro o una regla conocida a todos los lenguajes y tamaños de programa, es más adecuado evaluar si resuelve un problema en la base de código y el equipo actuales. www.informit.com
¿Por qué no basta con que el código se ejecute?
Producir el resultado deseado para las entradas actuales es el requisito más básico de un programa. Pero incluso una funcionalidad correcta resulta difícil de gestionar a largo plazo si se rompe fácilmente con el siguiente cambio. Por ejemplo, una función larga puede contener el cálculo de descuentos, comprobaciones de permisos, renderizado de la interfaz y almacenamiento de datos. Puede funcionar ahora, pero quien intente cambiar solo la política de descuentos tendrá más probabilidades de afectar también la gestión de permisos o el orden de almacenamiento.
El código difícil de leer no es solo una cuestión de tardar más en leerlo. Sin confianza en la intención, los desarrolladores pueden copiar lógica similar, modificar un área más amplia de la necesaria o recrear reglas que ya existen. Los revisores también tienen dificultades para evaluar el impacto de un cambio. La mantenibilidad es la propiedad de no obstaculizar cambios futuros, y el código limpio se centra en mejorar esa mantenibilidad.
Aun así, nadie puede eliminar de antemano todos los costes de cambios futuros. Cuando los requisitos son complejos o los sistemas externos imponen restricciones importantes, el código también tendrá cierta complejidad. El mejor objetivo no es fingir que la realidad es simple, sino distinguir la complejidad evitable de la inevitable. Si la complejidad es necesaria, su motivo debe hacerse visible mediante la estructura, los nombres, las pruebas y la documentación.
¿Cómo revelan los buenos nombres la intención del código?
Los nombres son la información con la que los lectores se encuentran más a menudo cuando entienden el código por primera vez. Los nombres genéricos como x, data, process y flag pueden resultar familiares para quien los escribió, pero no indican a los demás qué representan. En cambio, nombres como expiredCouponCount, isEligibleForRefund y calculateShippingFee comunican de manera relativamente directa el propósito de un valor u operación. Los nombres significativos también permiten trasladar al propio código información que, de otro modo, tendría que explicarse en comentarios. google.github.io
Una buena denominación es una cuestión de especificidad, no de longitud. Un concepto ampliamente acordado dentro de un ámbito pequeño puede tener un nombre corto, mientras que un valor utilizado en un ámbito más amplio puede necesitar más contexto. Por ejemplo, el índice de bucle i puede entenderse dentro de un bucle muy corto. Pero si el valor de retorno de una función o un campo de objeto se llama solo result, es difícil saber si representa éxito, una cantidad o el resultado de una consulta.
También es útil distinguir los verbos de los sustantivos. La lectura suele fluir de manera natural cuando las funciones usan nombres basados en verbos que revelan qué hacen, mientras que los valores y objetos usan nombres basados en sustantivos que revelan qué son. sendReceipt() es una acción, mientras que receiptEmail es un dato. Sin embargo, alargar un nombre no elimina automáticamente la ambigüedad. handleUserData es más largo, pero sigue sin estar claro qué gestiona.
// Example with unclear intent
if (a) {
doIt(b);
}
// Example where the purpose of the condition and action is visible
if (isPaymentApproved) {
sendOrderConfirmation(order);
}
Los nombres del segundo ejemplo aún deben ajustarse al contexto real. La idea es que los lectores entiendan la decisión importante sin tener que buscar lejos las definiciones de a y b. Frente a una estructura donde los comentarios repiten lo que los nombres ya explican, conseguir que los nombres y la composición del código se expliquen por sí mismos reduce el riesgo de que la explicación quede desactualizada después de un cambio.
¿Cuánto deben dividirse las funciones y la estructura?
Cuando una función o módulo hace demasiadas cosas, quienes lo leen deben mantener varias reglas en mente al mismo tiempo. Si la validación de entrada, el cálculo, las llamadas externas, el manejo de errores y el formato del resultado se mezclan en un bloque, cambiar una parte puede exigir entender todo el flujo. Separar los pasos relacionados en unidades con nombre puede hacer que el flujo de alto nivel sea más fácil de leer.
Por ejemplo, un proceso de confirmación de pedido puede mostrarse con pasos como validateOrder, calculateTotal, reserveInventory y createPayment, que expresan el flujo de negocio. El propósito de separar no es aumentar el número de funciones, sino hacer más fácil de leer la responsabilidad y el orden de cada paso. Si una función extraída tiene solo una línea y su nombre es menos claro que la expresión original, es difícil concluir que la extracción mejora la comprensión.
La división excesiva crea el problema opuesto. Para entender una acción, los lectores pueden tener que desplazarse continuamente entre muchos archivos y funciones delgadas. Las abstracciones, como las interfaces o los tipos, tienen la ventaja de ocultar detalles de implementación, pero también pueden ocultar contexto necesario. La abstracción debe utilizarse cuando ofrece un beneficio claro, no con la premisa de que «más abstracción siempre significa mejor diseño». google.github.io
Por tanto, la decisión de dividir puede evaluarse con preguntas como estas:
- ¿Esta parte tiene una función que pueda explicarse de forma independiente?
- ¿Su nombre explica la intención mejor que leer el código interno?
- ¿La misma regla se repite en varios lugares, lo que justifica reunirla en uno solo?
- ¿Crea un límite donde, al hacer un cambio, solo sea necesario examinar esta parte?
- Tras la separación, ¿seguir las llamadas hace menos claro el flujo general?
Estas preguntas no generan una respuesta automática. Pero centran la atención en el coste real para los lectores de entender el código, en lugar de en reglas superficiales como «funciones cortas».
¿La simplicidad equivale a tener menos funcionalidades?
En el código limpio, simplicidad no significa renunciar a funcionalidades necesarias. Se parece más a evitar estructuras innecesarias, puntos de extensión sin uso y desvíos difíciles de entender que los requisitos actuales no exigen. Si se generaliza basándose solo en suposiciones sobre necesidades futuras, los lectores actuales deben entender casos que aún no existen.
Por ejemplo, crear por adelantado un sistema de complementos multicapa para una funcionalidad pequeña con un único método de pago puede dejar espacio para una futura ampliación. Pero también aumenta de inmediato las rutas de código, la configuración y las combinaciones que deben probarse. Por el contrario, si ya está confirmada la incorporación de métodos de pago y sus reglas difieren considerablemente, crear un límite común puede reducir cambios futuros. Ninguna de las dos decisiones es siempre mejor de antemano.
La simplicidad tampoco significa «el menor número de líneas de código». Comprimir varias condiciones y transformaciones en una línea puede parecer ingenioso para quien lo escribió, pero la persona que lo modifique deberá interpretar precedencias y excepciones. En cambio, usar valores intermedios con nombres apropiados y separar las condiciones puede aumentar el número de líneas y, al mismo tiempo, simplificar el proceso de razonamiento. Las guías de revisión de código también enfatizan que los futuros desarrolladores deben poder leer, entender y modificar el código. google.github.io
En la práctica, es útil considerar conjuntamente dos clases de simplicidad. La primera es la simplicidad de la implementación en sí: si hay pocos estados, ramas, dependencias y duplicaciones innecesarios. La segunda es la simplicidad de uso y cambio: si quienes llaman al código pueden usarlo correctamente con facilidad y si está claro dónde modificarlo cuando cambian las reglas. Una decisión que simplifica el uso externo a veces puede ser mejor, aunque el interior sea algo más complejo.
¿Por qué es necesario un estilo coherente y por qué no es suficiente?
Cuando varían la sangría, los saltos de línea, la organización de archivos y las convenciones de nombres, quienes leen el código deben interpretar el formato cada vez. Usar de forma coherente un estilo acordado por el equipo puede reducir la atención dedicada a diferencias superficiales del código. Las herramientas que verifican reglas mecánicamente, como los formateadores automáticos y los linters, pueden ser especialmente útiles para este trabajo repetitivo.
Sin embargo, seguir únicamente el estilo no vuelve limpio al código. Aunque todos los nombres sigan la misma convención, las responsabilidades pueden seguir siendo ambiguas; aunque la longitud de línea sea correcta, el diseño puede continuar excesivamente enmarañado. La revisión de la calidad del código sostiene que, además del estilo, deben considerarse el diseño, la funcionalidad, la complejidad, las pruebas y la documentación. google.github.io
Al aplicar reglas de estilo, normalmente es práctico respetar las convenciones existentes del equipo. Probar una notación preferida en un único archivo nuevo puede parecer insignificante, pero puede debilitar la coherencia de todo el proyecto. A la inversa, una convención existente puede discutirse y cambiarse si una mejora incrementa significativamente la claridad. Lo importante no es competir por cuál regla es más elegante, sino si el equipo puede leer y cambiar el código de forma coherente.
La revisión de código también requiere distinguir las diferencias menores de preferencia de los problemas que afectan a la mantenibilidad. Exigir perfección en cada cambio puede ralentizar la propia mejora. Si un cambio mejora en conjunto la mantenibilidad, la legibilidad y la comprensibilidad, aceptarlo de forma incremental puede ser más realista. google.github.io
¿Cuál es la relación entre las pruebas y el código limpio?
Las pruebas son medios ejecutables para verificar el comportamiento que el código promete. Aquí, una promesa significa comportamiento observable como «solo se pagan los pedidos válidos», «un pedido que ya ha sido cancelado no se cancela de nuevo» o «se descuenta el importe especificado cuando se cumplen las condiciones de descuento». Las pruebas proporcionan una base para comprobar si se rompió un comportamiento crítico después de un cambio.
Si el código limpio se considera solo código que se ve bien, las pruebas pueden parecer algo separado. Pero bajo una definición que incluye la modificación segura, las pruebas son fundamentales. Durante una limpieza estructural, se debe poder confirmar que se conservó el comportamiento externo y, al añadir una regla nueva, comprobar que las reglas anteriores no se hayan roto accidentalmente. El código mantenible debe tener pruebas que verifiquen la lógica principal y el comportamiento prometido, y que ayuden a identificar la causa de los fallos. google.github.io
Tener muchas pruebas por sí solo no garantiza la calidad. Las pruebas demasiado acopladas a un orden interno menor pueden dificultar incluso mejoras estructurales legítimas. Por el contrario, las pruebas que omiten condiciones de límite y reglas de negocio importantes pueden no contribuir suficientemente a la seguridad de los cambios, aunque sean numerosas. Los nombres de las pruebas y la estructura preparar-actuar-verificar también deben escribirse con claridad para que los lectores sepan qué se garantiza.
Por ejemplo, si una lógica calcula un período de elegibilidad para reembolso, es más significativo probar los límites de la regla real —como la fecha límite propiamente dicha, justo después de la fecha límite y una entrada ausente— que comprobar únicamente fechas normales. Los casos que deben probarse dependen de los requisitos y el riesgo del producto. La clave es conseguir que las pruebas comuniquen no solo que «existe código», sino «qué comportamiento debe seguir preservándose».
¿Cuándo son necesarios los comentarios y la documentación?
Los comentarios no son malos. Son especialmente valiosos para transmitir contexto que el código tiene dificultades para expresar. Por ejemplo, los nombres por sí solos pueden no comunicar adecuadamente una solución alternativa para un comportamiento anómalo en un servicio externo, restricciones legales o contractuales, una decisión basada en mediciones de rendimiento o el motivo de un código de compatibilidad temporal que se eliminará después de una fecha concreta. Esta información ayuda a los futuros mantenedores a entender por qué no deberían sustituirlo por un enfoque más simple. google.github.io
Por el contrario, los comentarios que simplemente traducen lo que el código ya dice pueden desincronizarse del código con el tiempo. Un comentario que dice «incrementar el contador en 1» junto a count = count + 1 no aporta información nueva. En ese caso, puede tener prioridad un nombre mejor o una estructura más directa. Cuanto más largos sean los comentarios, más conviene comprobar si indican que la intención del código no está clara.
La ubicación apropiada de la documentación también puede variar. Un motivo local dentro de una función puede encajar en un comentario cercano. Las reglas de uso, los métodos de configuración y las condiciones de compatibilidad compartidas entre varios módulos pueden ser más fáciles de encontrar en documentación independiente o descripciones de interfaz. Esté donde esté, lo importante es proporcionar a los lectores el contexto necesario para tomar decisiones y actualizarlo junto con el código cuando este cambie.
¿En qué se diferencian el código limpio, la refactorización y el estilo de programación?
Estos tres términos suelen mencionarse juntos, pero desempeñan funciones distintas. El código limpio es un estado de calidad o una perspectiva orientada a que el código sea fácil de entender y cambiar. La refactorización es la actividad de mejorar la estructura interna preservando el comportamiento observable externamente. El estilo de programación es una convención para expresar código, como la sangría, la notación de nombres y el espaciado.
| Categoría | Pregunta clave | Alcance |
|---|---|---|
| Código limpio | ¿Se puede entender y modificar este código de forma segura? | Nombres, estructura, complejidad, pruebas, documentación, coherencia |
| Refactorización | ¿Cómo se puede mejorar la estructura preservando el comportamiento? | Una actividad de mejora estructural |
| Estilo de programación | ¿En qué formato expresa el equipo el código? | Convenciones de notación y formato |
La refactorización es una forma de crear o mantener código limpio. Por ejemplo, los cálculos de precio duplicados pueden reunirse en un solo lugar, los nombres ambiguos pueden cambiarse y las condiciones pueden organizarse en unidades más fáciles de entender. Pero los cambios estructurales realizados sin confirmar que se preserva el comportamiento pueden ser arriesgados, por lo que las pruebas y la revisión son importantes.
El estilo reduce la fricción de la colaboración, pero no resuelve automáticamente los problemas de diseño. Por el contrario, un código claro con una estructura funcional no es automáticamente malo solo porque su estilo difiera ligeramente. Entender esta distinción reduce el error de tratar los problemas de formato y los riesgos reales de mantenimiento con el mismo peso en las revisiones. google.github.io
¿Qué debe tener prioridad cuando hay restricciones de rendimiento y seguridad?
El énfasis del código limpio en la simplicidad y la claridad no significa sacrificar el rendimiento, la seguridad, la compatibilidad ni la fiabilidad operativa. Por ejemplo, una caché necesaria para el rendimiento, pasos de validación requeridos por seguridad o una gestión de compatibilidad para un sistema externo antiguo pueden hacer más complejo el código. Si esa complejidad se basa en requisitos reales y resultados de mediciones, puede ser más adecuada que una alternativa que simplemente parezca más simple.
La actitud importante en esta situación no es ocultar la complejidad. Las restricciones, el comportamiento que debe garantizarse y los motivos para no usar una implementación convencional pueden hacerse visibles mediante nombres, estructura, pruebas y los comentarios necesarios. El principio de priorizar los hechos técnicos y los datos sobre las preferencias personales se aplica a estas decisiones. google.github.io
Por ejemplo, si una implementación fácil de leer no cumple los requisitos de tiempo de respuesta en el entorno real de producción, hay motivos para elegir una implementación más compleja. Sin embargo, tampoco es deseable hacer complejo todo el código basándose únicamente en la suposición de que es «por rendimiento». Tras medir el problema y confirmar los requisitos, deben compararse tanto los costes como los beneficios de la complejidad.
Lo mismo se aplica a la seguridad. Pasos como la validación de entradas, las comprobaciones de autorización y el manejo de errores pueden alargar el flujo del código. Eso no significa que puedan omitirse para acortar el código. Una buena estructura sitúa estos pasos necesarios donde sean fáciles de reconocer y ayuda a evitar que las reglas sensibles queden dispersas arbitrariamente por toda la base de código.
¿Cuáles son los malentendidos comunes sobre el código limpio?
El primero es el malentendido de que «más corto siempre es mejor». Las funciones cortas y las expresiones concisas pueden ayudar, pero el número de líneas no es el criterio. La división y la abstracción excesivas pueden alargar las rutas de llamadas y ocultar el contexto. En vez de preguntar si el código se ha acortado, pregunte si los lectores pueden entender con más facilidad el flujo principal y sus motivos. google.github.io
El segundo es el malentendido de que «menos comentarios siempre es mejor». La idea de expresar mediante nombres y estructura el contenido que el código puede explicar por sí mismo no significa eliminar información de contexto útil. En particular, los motivos de las decisiones y las restricciones externas pueden necesitar permanecer en comentarios o documentación. Los buenos comentarios no repiten el código; proporcionan contexto que es difícil conocer solo a partir del código. google.github.io
El tercero es el malentendido de que «el código solo es bueno si sigue todas las reglas». Las recomendaciones son herramientas para juzgar, no un código legal aplicable a todas las situaciones. Las prioridades varían según las características del lenguaje, las convenciones existentes del proyecto, los requisitos de rendimiento y seguridad, y la experiencia del equipo. Es más importante comprobar si aplicar una regla realmente hace que el código sea más claro.
El cuarto es el malentendido de que «el diseño debe ser perfecto desde el principio». Los requisitos cambian y cierta información no puede conocerse inicialmente. En lugar de retrasar los cambios persiguiendo solo la perfección, es más realista seguir realizando pequeñas mejoras que hagan que el sistema actual sea, en conjunto, más fácil de leer y mantener. google.github.io
¿Cómo se puede evaluar el código limpio en la práctica?
Es difícil evaluarlo solo con una lista de verificación absoluta, pero se pueden plantear varias preguntas al afrontar un cambio. Primero, considere si alguien que ve el código por primera vez puede explicar su propósito principal. Después, al cambiar una regla, compruebe si el lugar que debe modificarse está relativamente claro o si también deben cambiarse áreas no relacionadas. Por último, confirme si existen pruebas o métodos de revisión para verificar el comportamiento esencial después del cambio.
Estas son preguntas prácticas que puede utilizar al escribir o revisar una funcionalidad:
- ¿Se puede entender aproximadamente la función de un valor, una función o un módulo solo por su nombre?
- ¿Una función mezcla innecesariamente distintas reglas de negocio u operaciones externas?
- ¿La misma regla importante está copiada en varios lugares?
- ¿Encaja de manera natural en las convenciones del equipo sobre nombres, formato y organización de archivos?
- ¿Se han registrado cuando es necesario los motivos de las decisiones o las restricciones que el código no puede expresar?
- ¿Hay una manera de verificar el comportamiento esencial y las condiciones de límite arriesgadas?
- ¿La simplificación ha pasado por alto requisitos de rendimiento, seguridad o compatibilidad?
- ¿La abstracción o separación realmente reduce el coste de comprensión, o solo alarga el camino que deben seguir los lectores?
No es necesario responder a todas estas preguntas inmediatamente. Intentar resolver todos los problemas de diseño en un cambio pequeño puede paralizar la revisión. Es práctico corregir primero los problemas de mayor impacto y orientar el resto en una mejor dirección en cambios posteriores. El objetivo de la revisión de código también puede ser la mejora continua de la mantenibilidad, la legibilidad y la comprensibilidad del sistema, en lugar de producir código perfecto. google.github.io
Conclusión: el código limpio es calidad para el cambio, no un formato fijo
El código limpio no significa solo una lista de reglas de un libro concreto o un formato ordenado. Es una perspectiva de calidad que hace visible la intención del código mediante nombres y estructura, reduce la complejidad innecesaria, permite una lectura coherente dentro de un equipo y hace posible verificar el comportamiento después de los cambios. Los comentarios se utilizan para transmitir contexto, las pruebas respaldan la seguridad de los cambios y las abstracciones se usan cuando realmente facilitan la comprensión y la modificación.
La forma del buen código puede variar de un proyecto a otro. Lo importante no es si parece corto o si sigue una regla famosa, sino si el siguiente desarrollador puede entenderlo y cambiarlo correctamente bajo los requisitos y restricciones actuales. Mejorar continuamente pequeños nombres, condiciones, pruebas y estructuras desde esa perspectiva es el punto de partida práctico del código limpio. google.github.iogoogle.github.io