miércoles, 5 de septiembre de 2012

Imposible revertir el efecto de la entropía...

...causada por la falta de test cases.

Es un poco mi conclusión. O bueno, la palabra conclusión suena muy ambiciosa. Porque tampoco es que pretendo concluir, no seguir reflexionando al respecto, o no cambiar mi opinión, en un futuro.
Pero es la sensación que me da, por el momento.

Que qué ?

Que es muy muy muy dificil, casi imposible, revertir la situación en la que un equipo de desarrollo se hace cargo de un producto mediano-grande/grande, el cual fue construido sin test unitarios, y llevarlo a un nivel de cobertura coherente.

Muchas veces escucho a la gente decir, en tales situaciones, u opinar desde afuera: "ah, pero entonces si hay código malo, hay que hacerle testcases". Y algunos agregan, ".. para luego refactorizarlo".
Suena muchísimo más simple de lo que parece.

Y mi conclusión, supongo que sonará algo controversial en un principio, hasta que me explique, es que es imposible, e impráctico hacer testcases en retrospectiva.

En primer instancia, porque, por naturaleza, el test asegura cierto comportamiento "esperado" del sistema. Entonces, generalmente uno parte del test, y luego implementa la lógica (como en TDD).
Pero si parto de la implementación, cómo se que lo que está haciendo es un verde ? Cómo se que es lo esperado ?
Ok, debería tener una especificación de requerimientos para eso. Pero también se da la correlación de que un sistema sin test unitarios, tampoco tiene especificación de requerimientos, o al menos una útil a tal efecto.

En segundo lugar, porque dicho sistema, suele tener un nivel de acoplamiento infernal, lo cual lo convierte en imposible de testear.
Pasa que uno nunca sabe por donde empezar. Es como cuando se te enreda uno de esos juegos que venden en retiro de piecitas de alambre. Y lo ponés sobre la mesa, y decís, por donde diablos tiro para deshacer esta maraña.

Porque, además, hay que entender que, por más que existen herramientas para encarar el testeo unitario en forma de concern transversal, es decir con aspectos, con frameworks de mocks. Se hace mucho más simple, si uno ya diseña el software, teniendo en cuenta la facilidad para testearlo.

El tercer punto, es que, un sistema caótico, codificado proceduralmente y con baja calidad, suele tener una cantidad de código mucho mayor a la realmente necesaria para implementar las mismas funcionalidades pero bajo las buenas prácticas (por ejemplo el principio DRY, el uso del polimorfismo, composición, herencia, etc).
Entonces, por qué querría perder tiempo testeando 80.000 lineas de código, de las cuales se que, al menos 60.000 están de más, y que, eventualmente van a desaparecer ? Porque, por ejemplo voy a reemplazar miles de lineas que parsean XML "a mano", por el uso de un fwk declarativo, como digester, o xstream.

Obviamente que entiendo la idea de que, el test me permitirá validar, al reemplazar ese código "feo", por el nuevo "prolijo". Verificar que el nuevo produce los mismos resultados que el viejo. Que es equivalente. Definición de refactor. Solo cambié la implementación.

Pero eso, nuevamente es teoría, fácil de decir, o repetir, simple y correcta en el caso ideal, pero que es muy dificil de que suceda en la realidad.

Porque, cuarto, un sistema de este tipo, justamente no tiene abstracciones, no tiene interfaces bien pensadas y diseñadas para separar sus módulos. De modo de que yo pueda hacer el test, y poder correrlo contra la implementación vieja, y la nueva.
Porque ahí sí, el test es todo ganancia. Luego de refactorizar, me seguiría sirviendo exáctamente así como está, para tener cobertura sobre la nueva impl.

Pero no, en la práctica, como decía, no se puede hacer un tests sobre el código viejo, que sirva para el código nuevo.

Entonces, es inevitable, hay que arrancar por refactorizar.
Yo no digo que no haya que hacer ningún tests. Lo que digo, es que la premisa, o el objetivo principal del programador, no debe ser el de hacer testscases. Debe ser el de refactorizar.
En el medio, en el trayecto, va haciendo testcases. Los que ameriten.
Cuando se pueda, empezará por un test, y luego refactorizará.
Pero cuando no, habrá que refactorizar, y luego pensar en el test.

Y en especial, dentro de la idea de refactorizar, me parece que el objetivo más importante del desarrollador, tiene que ser el de reducir la cantidad del código al mínimo.
Si logro bajar de 80.000 a 60.000 o a 40.000 lineas de código,  me resultará mucho más simple trabajar en la cobertura.

Obviamente que esta tarea es complicada, y creo que me gustaría reflexionar un poco más, para luego hacer otro posts específico dedicado a ese proceso de refactorizar un sistema que no conocemos.

Pero volviendo, creo que ese es el camino.
Al menos, de las experiencias de laburo que tuve, nunca vi que se haya podido revertir el nivel de cobertura, cuando este es muy bajo.
  • En un laburo partimos de un framework web con cero tests cases. Trabajamos casi 3 años en eso. Lo cambiamos rotúndamente, hacia un fwk mucho más declarativo, que generaba el html automáticamente, con aspectos para manejar transacciones a nivel de objetos, etc. Se intentaron varias estratégias para testearlo. Por ejemplo con tests automáticos con herramientas como selenium. Lo que pasó es que ante cambios mínimos, esos tests se rompían. Además, eran tests que requerian muchísimo esfuerzo de mantenimiento. Que además, tenían complejidad accidental, como que necesitaban abrir un browser iexplorer, etc. etc.. Eventualmente, el costo hizo que nadie le de bola. 
  • En otro laburo, trabajaba sobre un producto muy muy grande, que ya tenía como unos 6 años o más. Donde muchos de los desarrolladores originales ya no estaban. Una cantidad de código monstruosa. Lo mismo. Hubo muchos esfuerzos individuales de ir agregando un testcito por aquí, otro por allá. Luego, cuanto más trataba de abarcar el test, más era susceptible ante cambios, y más complejo de configurar, y mantener. A lo que, quizás el creador se iba a otro módulo o de la empresa, y el que venía atrás miraba el test rojo, y terminaba por comentarlo, borrarlo, blah.
  • En otro proyecto de investigación sucedía algo parecido. Incluso, no solo con la ausencia de tests, sino también por ejemplo, con la ausencia de un framework de dependecy injection. Es muy dificil cambiar una arquitectura así.
Ahora, nuevamente estoy trabajando en un producto de unas 80K lineas con 2.3% de cobertura. Y de nuevo, veo que intentar hacer tests es imposible:
  • los tests de aceptación o de integración, que por un lado tienen la ventaja de abarcar más, se tornan inmantenibles, y el impacto de esfuerzo ante refactors (que siempre van a suceder) es muy grande.
  • los tests bien unitarios, creo que representan algo así como intentar matar un elefante con una gomera y arbejas.
Como conclusión, el objetivo principal del programador debería ser, primeramente, el llevar el código a una unidad maleable, controlable. Luego eso, sí va a ser mucho más simple de testear y mantener.

Salió un posts mucho más largo del que esperaba.

Saludines !


No hay comentarios:

Publicar un comentario