El Programador Profesional, The Clean Coder (1)

Published on Author lopez

Cada tanto vuelvo a leer algunos libros clásicos de programación, como The Pragmatic Programmer, Programming Pearls, The C Programming Language, Code Complete, Clean Code… En estos días me reencuentro con el excelente The Clean Coder del bueno de @unclebobmartin (ver el blog http://blog.cleancoder.com/ y el sitio asociado https://cleancoders.com/). Me gustó darme cuenta que en estos años estuve mejorando en varios de los puntos que menciona para un programador profesional (en otros todavía estoy en deuda). Quisiera comentar en esta nueva serie de posts las cualidades y actitudes que menciona como importantes.

Primero, Martin se refiere a programadores PROFESIONALES: todo lo que expone es lo que él ve como deseable para un programador que quiera llamarse profesional, no solamente porque trabaja programando sino por el respeto y la posición que alcanza entre sus colegas y clientes.

La primera gran regla que da se basa en el juramento hipocrático:

First, Do Not Harm

Es decir, lo primero, al programar, es no hacer daño. Esto se traduce en NO INTRODUCIR ERRORES (bugs que le dicen). Cualquier software exitoso necesita ser modificado en el tiempo, y es signo de profesionalismo en un programador el no introducir errores en las modificaciones que haga. Uno podría decir: la programación es una actividad muy complicada, tanto que como seres humanos no podemos evitar introducir bugs. Pero Martin retruca: un médico también puede equivocarse, pero el juramento hipocrático lo lleva a esforzarse, a hacer todo lo posible que esté a su alcance y bajo su entendimiento para evitar lastimar a sus pacientes. Eso mismo tenemos que hacer nosotros como programadores: esforzarnos por no introducir bugs cuando corregimos alguno o agregamos nueva funcionalidad. Martin da algunos ejemplos de su propia historia profesional sobre las consecuencias nefastas que puede provocar el agregar bugs sin darnos cuenta.

Pienso que estamos todos de acuerdo en que necesitamos hacer ese esfuerzo, pero ¿cómo lograrlo? Una estrategia, desaconsejable, es hacer que los clientes finales sean los que reporten bugs. ¡Qué bueno! Entreguemos algo rápido, así nos destacamos en nuestro cumplimiento de los plazos y luego, reparemos lo que ellos detectaron, también rápido. Pero no: no es un camino a seguir en los tiempos que corren. Otra aproximación: hacer que el equipo de QA (Quality Assurance), se encargue de detectar los problemas. De nuevo, así podemos entregar algo rápido, avanzar, total, ya alguien va a reportar los errores. Tampoco este es el camino esperado para un programador profesional: malgastar el tiempo de otros cuando uno debería directamente entregar código que funciona, no es profesional, es chapucero.

Entonces ¿cómo hacemos que lo que codificamos no tenga errores? Una es aplicar las mejores prácticas, todos los patrones del mundo, y confiar en nuestra capacidad de escribir bien, de “compilar” los resultados de una función que escribimos en nuestra cabeza, sin nunca probarla. Esta estrategia sigue siendo tan común que me asombra. Aún cuando seamos los “grandes” programadores que toman todas sus precauciones para no introducir errores, los errores siguen apareciendo. ¿Qué propone Martin (y es agua para mi molino, si ya conocen mi postura sobre el tema)? Pues bien:

How can you know your code works? That’s easy. Test it. Test it again. Test it up. Test it down. Test it seven ways to Sunday!

Siempre probar el código. Siempre. ¿Sugiere entonces Martin tener un 100% de cubrimiento de código? No, no lo sugiere, lo DEMANDA. Cada línea de producción que uno escriba debe ser puesta a prueba.

Every single line of code that you write should be tested. Period.

Parece no realista. Pero no lo es. Cada línea que uno escribe es para ser ejecutada alguna vez, entonces, DEBEMOS asegurarnos que funciona como esperamos. No basta con leer el código y “compilar” el resultado en nuestros sesos, decir simplemente: “esto hace lo que se espera”. Martin (y permítanme que yo me sume) DEMANDA que sea probada con un test.

Una estrategia que veo seguir, entonces, es escribir las pruebas DESPUES de escribir el código. He escuchado cosas como: “con escribir código DESPUES, me aseguro que funcione, y el código que YA escribí tiene buena calidad”. Bueno, pocas veces he visto que se cumpliera. Lo que sucede muchas veces es que el código a probar a posteriori, es un código enrevesado, o al menos, difícil de ser probado. Es por eso que Martin escribe:

The solution to that is to design your code to be easy to test. And the best way to do that is to write your tests first, before you write the code that passes them.

¡¡SI!! Es tan simple como eso: escribir las pruebas ANTES del código. Esto nos lleva a TDD (Test-Driven Development), flujo de trabajo del que Martin es un “big supporter” (y yo también).

Por supuesto que esto no quita que probemos nuestro software con pruebas automáticas de integración, o con pruebas manuales de QA. Siempre probar, probar, probar… Hacer todo el esfuerzo como profesionales para quedar seguros de que nuestro software funciona como se espera. Pero el paso inicial es usar TDD: me temo que no encuentro todavía una disciplina o flujo de trabajo que reemplace a TDD para conseguir el “First, Do Not Harm”.

Alguno podrá decir que practicar TDD lleva “más tiempo”. Pues yo todavía tengo que ver eso en el contexto de un proyecto con lógica no trivial: el aparente más tiempo que involucra escribir los tests antes del código, y el trabajo de refactor, que permite hacer que el código evolucione, en vez de “escribirlo bien en la primera vez”, es menor que el tiempo tradicional si contamos el tiempo de depurar, los días y horas gastados en identificar las causas de errores, y el tiempo que se tarda en corregirlos.

Puedo dar como evidencia experiencia propia, lamentablemente sin poder mostrar código concreto por haber sidos proyectos privados. El cliente final fue el mismo, el dominio fue parecido en complejidad y alcance, y los equipos de programadores de muy buen y similar nivel.

Proyecto A: escrito sin TDD, con pruebas de QA, pruebas manuales, pruebas de usuarios en ambiente de test, y luego en stage, antes de llegar a producción.

Proyecto B: escrito con TDD, con tests automáticos de integración, pruebas de QA, pruebas manuales, pruebas de usuarios en ambiente de test y stage, antes de llegar a producción.

El segundo proyecto, escrito con TDD, se implementó evolutivamente, con TRES implementaciones esencialmente diferentes, en el TIEMPO que se había estimado hacerlo sin TDD: Luego, fue más fácil en este proyecto introducir pruebas automáticas de integración, incluso directamente en el código. Y cada vez que modificábamos código o agregamos algo nuevo, al quedar las pruebas de código en “verde” luego las pruebas de integración y manuales de QA eran “un trámite”: muchas muchas veces pasaban, al habernos asegurado el cumplimiento de las pruebas escritas con TDD.

El primer proyecto también tenia pruebas de código, con un cubrimiento de código mayor de ochenta por ciento, escritas DESPUES de escribir el código. Pero aún cuando esas pruebas pasaran a “verde”, eso NO ASEGURABA el buen funcionamiento del sistema final. Y había que dar muchas vueltas para probar un código que no había sido escrito como para ser probado fácilmente. Muchas veces las pruebas de QA fallaban. Y cada vez que se introducía un cambio o se arreglaba un bug conocido, aparecía algún otro problema detectado en QA. Aún dos semanas antes de la entrega final, se introdujo un problema que sólo fue detectado en producción: dos clicks en el browser, y el sistema explotaba en cuatro continentes.

Del segundo proyecto, no escuché nada por más de un año, luego de su entrega. Pregunto un día ¿lo están usando? Sí, lo estaba usando. Ah ¿lo están usando para probarlo cada tanto? No, lo usaban cada día YA EN producción. Se le había entregado el mantenimiento del sistema a otro equipo, lejano. Entonces pregunto; ¿cómo? ¿lo están usando desde hace meses? ¿cómo es que no nos llegan reportes de bugs? La respuesta: porque NO LOS ENCONTRARON (o por lo menos hasta ese momento). Todo había sido escrito ordenadamente, aplicando “baby steps”, programación evolutiva, escribiendo explícitamente los casos de uso esperados, tanto de funcionamiento correcto como de conducta ante entradas inválidas. Estaba armado de tal forma que, al tener ese tipo de pruebas, y código que fuera fácilmente probable, las modificaciones se podían implementar y enterarse inmediatamente si algo anterior se rompía o no. Porque eso es también nos da TDD: el facilitar la profesión de los que vengan después a modificar el software que estamos construyendo.

Por supuesto que TDD solo no asegura la AUSENCIA TOTAL de bugs. Pero su aplicación a conciencia, junto con refactor casi continuo, la aplicación CONCIENZUDA de patrones y otros conceptos (no solamente por que sí), el diseño evolutivo (que permite ir construyendo algo que no se aparta de la simplicidad sino hasta la llegada de un caso de uso que haga necesario agregar algo), todo eso, que llamo “TDD y cabeza”, es lo que permitió el resultado exitoso del proyecto B.

Como programadores que aspiramos a ser profesionales tenemos que poner todo nuestro esfuerzo en este tema, en escribir código sin introducir errores. Y para eso, veo como esencial el escribir las pruebas antes del código, y mantener el sistema en un estado simple, claro, entendible y mantenible. Más sobre el tema en próximo post.

Nos leemos!

Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez