Introducción
Toda la información que encontrarás aquí está basada en el libro de Carlos Blé “Código sostenible”. En este post hablaré sobre lo que he aprendido con la lectura de este libro. Serán unas breves nociones lo que destacaré, por lo que para obtener la mejor experiencia te recomiendo que leas el libro original, te será muy útil el detalle con el que se explican los conceptos y los ejemplos usados en esta guía para realizar código limpio y fácil de mantener.
¿Qué es código sostenible?
El código debe ser funcionalmente perfecto, debe cumplir todos los criterios especificados, pero además debe ser fácil de entender, mantener y escalar.
Si tu código no es sostenible será una principal fuente de fracaso en tu empresa, ya que con el tiempo será cada vez más difícil de modificar.
Y no confundamos que sostenible va relacionado con futurista, ya que precisamente intentar realizar código que cubra los escenarios de hoy, pero también los de mañana, complica mucho el trabajo que tenemos que hacer y en la mayoría de ocasiones nos induce a que no podamos tener un código sostenible.
En resumen, el código es sostenible cuando es simple y conciso, y está respaldado por test automáticos.
¿Cómo consigo un código sostenible?
Como vimos en el punto anterior, tenemos que lograr un código sencillo y concreto. Para entender cómo logramos esto, veamos qué está en nuestra mano.
No caigas en la reutilización de código excesiva sin control que, por ejemplo, intenta hacer que una misma función sirva para todo, ya que esto hará que uses menos abstracciones. Las abstracciones son la semántica de tu aplicación. Cada vez que extraes un objeto o una función y le das un nombre estás añadiendo una abstracción que define la misión de esa parte del código. El código es mucho más sencillo de entender cuando es rico en el uso de lenguaje humano. Es por eso que debemos cuidar los nombres que le asignamos a las variables, métodos y clases, ya que determinarán la dificultad para entender tu código. Buscamos nombres coherentes con el contexto, pronunciables, que denoten claramente su comportamiento.
Como bien indica el concepto anterior, crear abstracciones aporta semántica a nuestra aplicación y denota la intención de lo que programamos, podemos destacar también evitar caer en primitive obsession, la cual nos habla de evitar el uso excesivo de tipos primitivos (como String, Integer…) y en su lugar favorecer nuestros tipos propios, que serían esas clases que envuelven a los primitivos y les da una misión y un sentido, ya que tendrán un contexto basado en el nombre de la clase y el comportamiento (métodos) estará guiado por el mismo contexto. La utilización de tipos propios ayuda a que podamos desarrollar esa nueva feature más rápido, porque con su utilización conseguimos un código fácil de comprender e intuitivo.
El código que usa correctamente los tipos propios habla el lenguaje del negocio para el que se está desarrollando la aplicación, es decir, un lenguaje común para todos en la empresa, y favorecerá la comunicación entre desarrolladores y otros cargos de negocio. Por supuesto evita los modelos anémicos, que serían aquellos objetos que no tienen datos o que los tienen, pero no los utilizan directamente porque exponen getters y setters y dejan que los demás artefactos decidan qué debe hacer. En su lugar, pídele al objeto que es el que tiene la responsabilidad de resolver ese problema que haga lo que quieres hacer, favorece el principio Tell don’t ask.
Para tener un código simple, un buen ejemplo a seguir serían las cuatro reglas del diseño simple definidas por Kent Beck:
- Pasar los test
- Revelar la intención
- No contener duplicidad (no repetir la implementación de un concepto)
- Tener el menor número de elementos posible (no hacer más de lo necesario)
Procura diseñar software con alta cohesión y bajo acoplamiento, es decir, busca crear artefactos independientes, que no dependan de otros para funcionar. Siempre existirá un mínimo de acoplamiento, ya que hay que conectar las distintas piezas de nuestro código, pero evitar el acoplamiento excesivo hará escalable nuestro código. Implementar una arquitectura hexagonal puede ser muy beneficioso para que el dominio de nuestra aplicación no tenga dependencias externas. La ley de demeter también puede ser aplicada para evitar el acoplamiento. Para diseñar aplicaciones con alta cohesión y bajo acoplamiento es recomendable conocer los principios SOLID. También deberíamos limitar la visibilidad de métodos y clases, deberíamos ser conscientes de que aquello que marcamos como público, solo debería ser lo realmente necesario.
El código atractivo nos llama y nos resulta placentero a la hora de leerlo. Indentaciones bien realizadas, saltos de líneas justos, mismo nivel de abstracción del código dentro de un mismo bloque, y más, son ejemplos de lo que podemos hacer para que nuestro código se vea bonito y anime a interpretarlo. Usar cláusulas guarda es tan beneficioso para el rendimiento como para el aspecto visual, ya que denota claramente la intención de un método para forzar la detención de su ejecución y devolver algo cuando se da alguna condición.
Evita las sorpresas, piensa detenidamente cada decisión de diseño que tomes, ya que tendrá un impacto en la persona que interprete después tu código, que podrías ser tú mismo. Ten constructores que solo construyen, es decir, que no ejecuten ninguna lógica compleja más allá de crear una instancia de la clase, si necesitas hacer validaciones delégalas a métodos de factoría y cierra la visibilidad del constructor. Favorece las funciones puras, aquellas que siempre retornan el mismo resultado para la misma entrada. Favorece el uso de las funciones propias de lenguaje, que en el caso de lenguajes modernos o de los más veteranos, pero que suelen traer mejoras con cada actualización, muchas veces incluyen funcionalidades que facilitan la vida del programador y reducen la cantidad de trabajo que tiene que realizar para resolver un problema, al mismo tiempo que reducen la probabilidad de introducir un error, ya que evitan la complejidad accidental incluida por el programador. Si trabajas con lenguajes modernos aprovecha todas sus ventajas y no caigas en viejos principios divulgados por limitaciones del lenguaje o porque simplemente responden a un contexto del software en una época donde lo correcto era eso, pero como en todo, siempre hay una forma de mejorar y surgen nuevos principios y patrones. Por ejemplo, hoy en día no se recomiendan que las funciones solo deban tener un único return. En realidad, si sabemos que nuestra función ya tiene un valor para retornar y esperamos hasta el final de la ejecución para retornarlo, nos estamos arriesgando a que mute su valor por el camino y no tengamos el resultado esperado, además aumentamos la probabilidad de un bug sin necesidad, ya que se ejecutan más líneas para obtener el mismo resultado. En su lugar, utilizar un return temprano será mucho más claro para los que lean el código y será más seguro, un concepto que se conoce como cláusula guarda. Como este hay muchos otros principios que no aplican para el desarrollo de software más habitual, solo es cuestión de detectarlos y aplicar la lógica, y como no, nuestros conocimientos de código sostenible, para evitarlos y tener un código más fácil de interpretar y de manejar.
Aunque tengamos una buena batería de test es necesario tener también un buen manejo y prevención de errores, algo que ocurre cuando tenemos en cuenta las posibles excepciones de nuestra aplicación y las manejas de una forma inteligente, notificándolas en un log de errores u otros sistemas de monitorización. Cuanto más entiendes el lenguaje de programación que está usando es más complicado que puedas introducir un error, ya que eres más consciente de cada cosa que realizas y qué riesgos puede tener. Utilizar un IDE para programar es lo mejor que puedes hacer, te resaltan zonas donde puedes mejorar el código o zonas donde puede haber riesgo de error, además te autocompletan código o lo sugieren para esos momentos en los que no tienes claro algo o simplemente te sirve para aumentar tu productividad. Para poder decir que tenemos una buena batería de test es necesario que pensemos en multitud de casos, todos los que se nos puedan ocurrir, para poder cubrir todos los escenarios posibles y que tengamos un código resiliente. Para evitar el uso de excepciones a lo loco, tenemos disponibles lenguajes null safe como Kotlin, pero si tú necesitas Java, por ejemplo, siempre tienes herramientas para usar en lugar de las excepciones, como los tipos Either y Try o el patrón notificación. Captúralas y envuélvelas en las tuyas propias para poder proporcionar información extendida y dar más contexto de por qué se pudo dar ese error. Nunca añadas un ’throws’ a la firma de tus métodos, es ignorar totalmente la excepción y no tiene sentido.
Cuando queremos código sostenible no todo es código, ya que hay que cuidar otros aspectos que mejoran la calidad de nuestro trabajo, como puede ser la posibilidad de replicar los entornos productivos en uno local para poder solucionar rápidamente los bugs, hacer pairing para resolverlos, o incluso como forma de trabajo estándar, por el hecho de que dos cabezas pueden pensar una solución mejor y estudiar los riesgos minuciosamente.
Conclusión
Hacer código sostenible no es una tarea compleja, pero tampoco es fácil, es el fruto del cuidado diario de nuestro código tomando buenas decisiones en cada momento, huyendo de la complejidad y la sorpresa. Por experiencia propia, lo realmente complicado es no seguir las guías de código sostenible y tener que mantener ese código, es un quebradero de cabeza más grande que invertir nuestros esfuerzos en la aplicación de código sostenible. En definitiva, la fórmula perfecta para que un código sea mantenible, legible y escalable pasa por tener test suficientes y de calidad que cubran el código productivo, y que tanto los test como el código productivo sigan estos consejos que hemos aprendido con la lectura del libro y de este post.