Puntos clave
1. Escribe código limpio que sea legible y mantenible
La única medida válida de la calidad del código: WTFs/minuto
La legibilidad es primordial. El código limpio debe ser fácilmente comprendido por otros desarrolladores. Debe ser simple, elegante y libre de desorden. Esfuérzate por escribir código que exprese claramente su intención sin necesidad de comentarios extensos. Usa nombres significativos para variables y funciones, mantén las funciones pequeñas y enfocadas, y organiza el código de manera lógica.
La mantenibilidad permite la evolución. El código que es difícil de cambiar se convierte en un pasivo. Diseña tu código para que sea flexible y modular, de modo que pueda adaptarse a los requisitos cambiantes. Sigue principios como DRY (No te repitas) y SOLID para crear sistemas poco acoplados y altamente cohesivos. Refactoriza sin piedad para mejorar la estructura del código sin cambiar su comportamiento.
El código limpio vale la pena. Aunque escribir código limpio requiere más esfuerzo inicial, ahorra tiempo y dolores de cabeza a largo plazo. El código limpio es más fácil de depurar, extender y mantener. Permite a los desarrolladores trabajar de manera más eficiente y reduce el riesgo de introducir errores durante los cambios. Haz del código limpio una parte central de tu práctica de desarrollo.
2. Sigue convenciones de nombres significativos
El nombre de una variable, función o clase debe responder a todas las grandes preguntas. Debe decirte por qué existe, qué hace y cómo se usa.
Usa nombres que revelen la intención. Elige nombres que transmitan claramente el propósito y el comportamiento de variables, funciones y clases. Evita nombres de una sola letra o abreviaturas crípticas. Usa nombres pronunciables que puedan buscarse fácilmente. Por ejemplo:
- Malo: d (tiempo transcurrido en días)
- Bueno: tiempoTranscurridoEnDías
Sé consistente y preciso. Usa convenciones de nombres consistentes en todo tu código. Sé preciso para evitar ambigüedades; por ejemplo, usa distinciones significativas como obtenerCuentasActivas() y obtenerInfoCuentaActiva(). Evita codificaciones o prefijos que añadan ruido sin valor. Los nombres de las clases deben ser sustantivos, los nombres de los métodos deben ser verbos.
La longitud del nombre debe coincidir con el alcance. Usa nombres más largos y descriptivos para variables y funciones con alcances más grandes. Los nombres cortos son aceptables para alcances pequeños y locales. La longitud de un nombre debe ser proporcional a su ámbito de uso. Optimiza para la legibilidad y comprensión dentro del contexto donde se usa el nombre.
3. Mantén las funciones pequeñas y enfocadas
Las funciones deben hacer una cosa. Deben hacerlo bien. Deben hacerlo solo.
Lo pequeño es hermoso. Las funciones deben ser pequeñas, típicamente de 5 a 10 líneas de largo. Deben caber en una pantalla y ser comprensibles al instante. Extrae el código en funciones auxiliares bien nombradas en lugar de escribir funciones largas y complejas. Las funciones pequeñas son más fáciles de entender, probar y mantener.
Haz una cosa bien. Cada función debe tener un propósito único y claro. Si una función está haciendo múltiples cosas, extrae esas en funciones separadas. Señales de que una función está haciendo demasiado incluyen:
- Múltiples niveles de abstracción
- Múltiples secciones o bloques de código
- Numerosos parámetros
Mantén un nivel de abstracción. Las declaraciones dentro de una función deben estar todas al mismo nivel de abstracción. No mezcles lógica de alto nivel con detalles de bajo nivel. Extrae operaciones de bajo nivel en funciones separadas. Esto mejora la legibilidad al mantener las funciones enfocadas y conceptualmente simples.
4. Practica un formato y organización adecuados
El formato del código es sobre comunicación, y la comunicación es la primera tarea del desarrollador profesional.
El formato consistente importa. Usa una indentación, saltos de línea y espaciado consistentes en todo tu código. Esto mejora la legibilidad y reduce la carga cognitiva. Acuerda estándares de formato con tu equipo y usa herramientas automatizadas para hacerlos cumplir. Las pautas clave de formato incluyen:
- Indentación adecuada
- Colocación consistente de llaves
- Saltos de línea lógicos
- Espaciado apropiado
Organiza el código lógicamente. Agrupa el código relacionado y separa el código no relacionado. Usa líneas en blanco para crear "párrafos" entre secciones lógicas. Coloca funciones relacionadas cerca unas de otras. Mantén los archivos enfocados en un solo concepto o componente. Divide archivos grandes en otros más pequeños y enfocados cuando sea apropiado.
Sigue convenciones estándar. Adhiérete a las convenciones estándar para tu lenguaje y comunidad. Esto hace que tu código sea más familiar y accesible para otros desarrolladores. Por ejemplo, en Java:
- Los nombres de las clases usan PascalCase
- Los nombres de los métodos usan camelCase
- Las constantes usan ALL_CAPS
5. Gestiona dependencias y evita la duplicación
La duplicación puede ser la raíz de todos los males en el software.
Elimina la duplicación. El código duplicado es una oportunidad perdida para la abstracción. Cuando veas duplicación, extrae el código común en una función o clase reutilizable. Esto mejora la mantenibilidad al centralizar la lógica y reducir el riesgo de cambios inconsistentes. Tipos de duplicación a tener en cuenta:
- Bloques de código idénticos
- Algoritmos similares con ligeras variaciones
- Cadenas repetidas de switch/case o if/else
Gestiona las dependencias cuidadosamente. Minimiza las dependencias entre módulos para reducir el acoplamiento. Usa inyección de dependencias e inversión de control para hacer el código más modular y comprobable. Sigue el Principio de Inversión de Dependencias: depende de abstracciones, no de concreciones. Esto hace que tu código sea más flexible y fácil de cambiar.
Usa el principio del menor conocimiento. Un módulo no debe conocer los detalles internos de los objetos que manipula. Esto reduce el acoplamiento entre módulos. Por ejemplo, usa la Ley de Demeter: un método solo debe llamar a métodos en:
- Su propio objeto
- Objetos pasados como parámetros
- Objetos que crea
- Sus objetos componentes directos
6. Maneja los errores con gracia
El manejo de errores es importante, pero si oscurece la lógica, está mal.
Usa excepciones en lugar de códigos de error. Las excepciones son más limpias y no desordenan la lógica principal de tu código. Permiten que el manejo de errores se separe del camino feliz. Al usar excepciones:
- Crea mensajes de error informativos
- Proporciona contexto con excepciones
- Define clases de excepciones basadas en las necesidades del llamador
No devuelvas null. Devolver null lleva a excepciones de puntero nulo y desordena el código con verificaciones de null. En su lugar:
- Devuelve colecciones vacías en lugar de null para listas
- Usa el patrón de Objeto Nulo
- Usa Optional en Java o Maybe en lenguajes funcionales
Escribe declaraciones try-catch-finally primero. Comienza con el try-catch-finally al escribir código que podría lanzar excepciones. Esto ayuda a definir el alcance y las expectativas para el código que llama. Asegura que los recursos se gestionen y liberen adecuadamente, incluso en escenarios de error.
7. Escribe pruebas unitarias exhaustivas
El código de prueba es tan importante como el código de producción.
Sigue las tres leyes de TDD. El Desarrollo Guiado por Pruebas (TDD) mejora la calidad y el diseño del código:
- Escribe una prueba fallida antes de escribir cualquier código de producción
- Escribe solo lo suficiente de una prueba para demostrar un fallo
- Escribe solo el código de producción necesario para pasar la prueba
Mantén las pruebas limpias y mantenibles. Aplica los mismos estándares de calidad de código a tus pruebas que a tu código de producción. Refactoriza y mejora el código de prueba regularmente. Las pruebas bien estructuradas sirven como documentación y permiten la refactorización sin miedo del código de producción.
Apunta a una cobertura de prueba integral. Escribe pruebas que cubran casos extremos, condiciones de borde y escenarios de error, no solo el camino feliz. Usa herramientas de cobertura de código para identificar brechas en la cobertura de pruebas. Recuerda que una cobertura del 100% no garantiza un código libre de errores, pero proporciona confianza en la refactorización y los cambios.
8. Refactoriza el código continuamente
Deja el campamento más limpio de lo que lo encontraste.
Refactoriza oportunistamente. Mejora la estructura del código siempre que trabajes en una pieza de código. Sigue la Regla del Boy Scout: deja el código mejor de lo que lo encontraste. Las mejoras pequeñas e incrementales se acumulan con el tiempo y previenen la degradación del código. Las técnicas comunes de refactorización incluyen:
- Extraer métodos o clases
- Renombrar para mayor claridad
- Simplificar condicionales complejos
- Eliminar duplicación
Refactoriza de manera segura con pruebas. Siempre ten un conjunto sólido de pruebas antes de refactorizar. Haz cambios pequeños e incrementales y ejecuta pruebas con frecuencia. Esto te da confianza de que tus cambios no están rompiendo la funcionalidad existente. Usa herramientas de refactorización automatizadas cuando estén disponibles para reducir el riesgo de introducir errores.
Equilibra la refactorización con la entrega de valor. Aunque la refactorización continua es importante, no dejes que paralice el progreso. Apunta a "suficientemente bueno" en lugar de la perfección. Enfoca los esfuerzos de refactorización en las áreas de código más problemáticas o frecuentemente cambiadas. Comunica el valor de la refactorización a las partes interesadas para asegurar el apoyo a la mejora continua del código.
9. Aplica principios de programación orientada a objetos y funcional
Los objetos ocultan sus datos detrás de abstracciones y exponen funciones que operan sobre esos datos. Las estructuras de datos exponen sus datos y no tienen funciones significativas.
Usa sabiamente los principios orientados a objetos. Aplica principios como encapsulación, herencia y polimorfismo para crear diseños flexibles y modulares. Sigue los principios SOLID:
- Principio de Responsabilidad Única
- Principio Abierto-Cerrado
- Principio de Sustitución de Liskov
- Principio de Segregación de Interfaces
- Principio de Inversión de Dependencias
Aprovecha los conceptos de programación funcional. Incluso en lenguajes orientados a objetos, las técnicas de programación funcional pueden llevar a un código más limpio:
- Funciones puras sin efectos secundarios
- Datos inmutables
- Funciones de orden superior
- Composición de funciones
Elige el enfoque adecuado para el problema. Los paradigmas orientado a objetos y funcional tienen cada uno sus fortalezas y debilidades. Usa diseño orientado a objetos cuando necesites modelar dominios complejos con comportamiento. Usa enfoques funcionales para la transformación de datos y tuberías de procesamiento. Muchos lenguajes modernos soportan un enfoque híbrido, permitiéndote usar la mejor herramienta para cada parte de tu sistema.
10. Considera la concurrencia cuidadosamente
La concurrencia es una estrategia de desacoplamiento. Nos ayuda a desacoplar lo que se hace de cuándo se hace.
Entiende los desafíos de la concurrencia. La programación concurrente introduce complejidad y potencial para errores sutiles. Los problemas comunes incluyen:
- Condiciones de carrera
- Bloqueos
- Señales perdidas
- Problemas de visibilidad de memoria
Separa las preocupaciones de concurrencia. Mantén tu código relacionado con la concurrencia separado del resto del código. Esto lo hace más fácil de razonar y probar. Usa abstracciones como Ejecutores, Futuros y Actores para gestionar la concurrencia en lugar de trabajar con hilos en bruto.
Prefiere la inmutabilidad y las funciones puras. Los objetos inmutables y las funciones puras son inherentemente seguros para hilos. Eliminan muchos problemas de concurrencia al evitar el estado mutable compartido. Cuando el estado mutable es necesario, usa técnicas de sincronización adecuadas y considera usar variables atómicas o colecciones concurrentes.
Última actualización:
Reseñas
Código Limpio recibe en su mayoría críticas positivas por sus principios sobre cómo escribir código legible y mantenible. Los lectores aprecian los consejos prácticos sobre nombramiento, funciones y pruebas. El enfoque en Java del libro y algunas pautas excesivamente estrictas son críticas comunes. Muchos lo consideran una lectura esencial para desarrolladores, aunque algunos lo encuentran menos útil para programadores experimentados. Los estudios de caso y ejemplos de refactorización son elogiados por algunos, pero criticados por otros por ser excesivos. En general, los críticos coinciden en que el libro ofrece valiosas ideas sobre la calidad del código, incluso si no todas las sugerencias son universalmente aplicables.