14 marzo 2015

Clean Code

A lo largo de la vida no hay muchos libros que cambien realmente tu manera de pensar y hacer las cosas. En mi caso puedo contar con los dedos de una mano los libros de ese tipo que me he encontrado: "Computer Networking: a Top Down Approach" de Kurose y Ross, "Security Engineering: A Guide to Building Dependable Distributed systems" de Ross J. Anderson, y el libro del que trata este artículo "Clean Code: a Handbook of agile Software Craftmanship" de Robert C. Martin.

Supe de este libro en una de las conferencias de la PyConEs de 2013. En aquella conferencia hablaron de como hacer código sostenible en el tiempo. El tema me interesaba porque estaba preocupado por un fenómeno que todo programados se encuentra tarde o temprano: incluso con Python, cuando tu código crece se hace más difícil de mantener. Había programado aplicaciones que me resultaban difíciles de entender cuando tenía que revisarlas apenas unos meses después. Muchos años antes me había pasado a Python para evitar ese mismo problema en Java y Perl. En la conferencia aseguraban que los principios explicados en el libro ayudaban a prevenir ese problema. Esa es la razón por la que lo leí y he de admitir que tenían razón.

La lectura de este libro puede ser chocante. Hay tantas prácticas que sumimos correctas pero que sin embargo está terriblemente equivocadas que lo normal es leer por primera vez ciertos pasajes con una mezcla de sorpresa e incredulidad. Cosas como afirmar que los comentarios en el código son un reconocimiento de tu fracaso para hacer comprensible tu código suenan extrañas la primera vez que se leen pero luego se acaba comprendiendo que el autor está en lo cierto.

Los ejemplos del libro no están en Python sino en Java, sin embargo no creo que ningún programador de Python tenga problema alguno en comprender los conceptos que allí se explican. Algunos de los conceptos son un poco "javeros" pero muchos otros son útiles para desarrolladores en Python. Algunos de los conceptos principales son:

  • Los nombres de tus funciones deberían explicar claramente lo que hace la función. No se permiten abreviaturas en dichos nombres.
  • Una función sólo debería hacer una única cosa. Una función sólo debería tener un propósito. Por supuesto, una función puede tener muchos pasos pero el conjunto de ellos debería estar enfocado a conseguir el objetivo de la función y cada paso debería implementarse en su propia función. Una consecuencia de este enfoque es que es más fácil desarrollar tests unitarios para este tipo de funciones.
  • Las funciones deberían ser cortas: 2 líneas es estupendo, 5 está bien, 10 regular y 15 es muy pobre.
  • Los comentarios de código debería utilizarse únicamente para explicar decisiones de diseño pero nunca para explicar lo que hace el código.
  • No se deben mezclar niveles de abstracción en la misma función, lo que significa que no se debe llamar directamente a la API de Python mientras que otros pasos de la función llaman a tus propias funciones. En vez de eso es mejor meter tu llamada a la API dentro de una de tus propias funciones.
  • Ordena el código de manera que se pueda leer el conjunto de las implementaciones desde arriba en sentido descendente.
  • Hay que reducir todo lo pasible la cantidad de argumentos que se pasan a las funciones. Funciones con 1 argumento están bien, con 2 regular y con 3 probablemente deberías darle otra vuelta.
  • No te repitas (DRY, Don't Repeat Yourself, aunque este concepto ya lo conocía antes de leer este libro).
  • Las clases deberían ser pequeñas.
  • Las clases sólo deberían tener una razón para cambiar (Principio de Responsabilidad Única). En mi opinión este principio es una extensión lógica del de "un único propósito" de las funciones.
  • Idealmente, los atributos de las clases deberían ser utilizados por todos los métodos de la clase. Si nos encontramos atributos que sólo utilicen un pequeño subconjunto de métodos, deberíamos preguntarnos si esos atributos y esos métodos podrían ir en una clase separada.
  • Las clases deberían estar abiertas a extensiones pero cerradas para modificaciones. Eso significa que deberíamos incorporar nuevas funcionalidades heredando las clases existentes no modificándolas. De esa manera se reduce el riesgo de romper las programas cuando incluimos nuevas funcionalidades.
  • Haz TDD o condénate a ti mismo al infierno de meter modificaciones en tu código con la incertidumbre de si no romperás algo sin darte cuenta.
Hay muchos más conceptos, todos completamente explicados con ejemplos, pero estos que he explicado son los que tengo en la cabeza cuando escribo código.

Para probar si los principios de este libro estaban en lo cierto, desarrollé una aplicación llamada Geolocate, siguiendo estos conceptos y los de TDD. Al principio fue difícil cambiar mis hábitos a la horas de escribir código, pero conforme mi código fue creciendo me fui dando cuenta resultaba mucho más fácil encontrar errores y corregirlos de lo que me había resultado en proyectos anteriores. Además, cuando mi aplicación adquirió un tamaño respetable, la dejé aparte durante cinco meses para ver cómo de fácil resultaba retomar el desarrollo después de que pasase tanto tiempo. Fue asombroso. Aunque en mis anteriores proyectos habría necesitado varios días para comprender del todo un código tan grande, esta vez comprendí el funcionamiento del código en apenas una hora.

Mi conclusión es que este libro es imprescindible, dado que te permite mejorar dramáticamente la calidad de tu código y tu paz mental a la hora de mantenerlo después.