Cómo eliminar una función o característica cuando se usa TDD

20

En los textos sobre TDD, a menudo leo sobre "eliminar duplicación" o "mejorar la legibilidad" durante el paso de refactorización. Pero, ¿qué me hace eliminar una función no utilizada?

Por ejemplo, digamos que hay una clase Ccon métodos a()y b(). Ahora creo que sería bueno tener un método en el f()que entrar C. De hecho, f()reemplaza todas las llamadas a, b()con la excepción de las pruebas unitarias que definieron / describieron b(). Ya no es necesario, excepto para las pruebas.

¿Es seguro simplemente eliminar b()y todas las pruebas que lo usaron? ¿Es eso parte de "mejorar la legibilidad"?

TobiMcNamobi
fuente
3
Simplemente agregue otra prueba para verificar que la función no existe y luego repare los casos de prueba que fallan;)
Filip Haglund
@FilipHaglund Dependiendo del idioma, esto podría ser posible. Pero como documentación de código esto se vería raro.
TobiMcNamobi
1
Solo para cualquiera que no conozca mejor: el comentario de @ FilipHaglund es obviamente una broma. No hagas eso.
jhyot

Respuestas:

16

Sí, por supuesto. El código más fácil de leer es el que no está allí.

Dicho esto, la refactorización generalmente significa mejorar el código sin cambiar su comportamiento. Si piensa en algo que mejora el código, simplemente hágalo. No hay necesidad de meterlo en un agujero de paloma antes de que se te permita hacerlo.

Sebastian Redl
fuente
@ jpmc26 ¡Ágil, hombre, ágil! :-)
TobiMcNamobi
1
"El código más fácil de leer es el que no está allí". - Estoy colgando esa cita en la pared: D
winkbrace
27

Eliminar un método público no es "refactorizar": refactorizar es cambiar la implementación mientras se pasan las pruebas existentes.

Sin embargo, eliminar un método innecesario es un cambio de diseño perfectamente razonable.

TDD saca esto en cierta medida, porque al revisar las pruebas, puede observar que está probando un método innecesario. Las pruebas están impulsando tu diseño, porque puedes ir "Mira, esta prueba no tiene nada que ver con mi objetivo".

Puede revelarse más en niveles más altos de prueba, junto con herramientas de cobertura de código. Si ejecuta pruebas de integración con cobertura de código y ve que no se están llamando a los métodos, es una pista de que no se usa un método. El análisis de código estático también puede indicar que no se utilizan métodos.

Hay dos enfoques para eliminar un método; ambos trabajan en diferentes circunstancias:

  1. Eliminar el método. Siga los errores de compilación para eliminar cualquier código dependiente y pruebas. Si está satisfecho de que las pruebas afectadas son desechables, confirme sus cambios. Si no, retroceda.

  2. Elimine las pruebas que considere obsoletas. Ejecute todo su conjunto de pruebas con cobertura de código. Elimine los métodos que el conjunto de pruebas no ha ejercido.

(Esto presupone que su conjunto de pruebas tiene una buena cobertura para empezar).

Delgado
fuente
10

De hecho, f () reemplaza todas las llamadas a b () con la excepción de las pruebas unitarias que definieron / describieron b ()

En mi humilde opinión, el ciclo típico de TDD se verá así:

  • escribir pruebas fallidas para f () (probablemente basadas en las pruebas para b ()): las pruebas se vuelven rojas

  • implementar f () -> las pruebas se vuelven verdes

  • refactor : -> eliminar b () y todas las pruebas para b ()

Para el último paso, puede considerar eliminar b () primero y ver qué sucede (cuando se usa un lenguaje compilado, el compilador debe quejarse solo de las pruebas existentes, cuando no, las pruebas unitarias antiguas para b fallarán, por lo que es claro, también debes eliminarlos).

Doc Brown
fuente
4

Sí lo es.

El código mejor, más libre de errores y más legible es el código que no existe. Esfuércese por escribir la mayor cantidad de código que sea posible mientras cumple con sus requisitos.

Kilian Foth
fuente
99
Te perdiste la parte de "cómo".
JeffO
2

Es deseable eliminar b()una vez que ya no se usa, por la misma razón que es deseable no agregar funciones no utilizadas en primer lugar. Ya sea que lo llames "legibilidad" u otra cosa, si todo lo demás es igual, es una mejora para el código que no contiene nada para lo que no sirve. ¡En aras de tener al menos una medida específica por la cual es mejor no tenerla, eliminarla garantiza que su costo de mantenimiento futuro después de ese cambio sea cero!

No he encontrado ninguna técnica especial que sea necesaria para eliminarlo realmente con sus pruebas, ya que cualquier pensamiento de reemplazar b()por algo nuevo debe, por supuesto, ir acompañado de una consideración de todo el código que se llama actualmente b(), y las pruebas son un subconjunto de "todo el código ".

La línea de razonamiento que generalmente funciona para mí es que en el punto en que me doy cuenta de que se f()ha vuelto b()obsoleto, por b()lo tanto, debe ser al menos obsoleto, y estoy buscando encontrar todas las llamadas b()con la intención de reemplazarlas con llamadas a f(), yo Considere también el código de prueba . Específicamente, si b()ya no es necesario, entonces puedo y debo eliminar sus pruebas unitarias.

Tienes toda la razón de que nada me obliga a notar que b()ya no es necesario. Eso es una cuestión de habilidad (y, como dice Slim, los informes de cobertura de código en pruebas de nivel superior). Si solo se refieren a pruebas unitarias y no a pruebas funcionales b(), entonces puedo ser cautelosamente optimista de que no es parte de ninguna interfaz publicada y, por lo tanto, eliminarla no es un cambio importante para ningún código que no esté bajo mi control directo.

El ciclo rojo / verde / refactor no menciona explícitamente la eliminación de las pruebas. Además, la eliminación b()viola el principio abierto / cerrado, ya que claramente su componente está abierto a modificaciones. Entonces, si desea pensar en este paso como algo fuera de TDD simple, continúe. Por ejemplo, es posible que tenga algún proceso para declarar que una prueba es "incorrecta", que se puede aplicar en este caso para eliminar la prueba porque prueba algo que no debería estar allí (la función innecesaria b()).

Creo que, en la práctica, la mayoría de las personas probablemente permita que se realice una cierta cantidad de rediseño junto con un ciclo rojo / verde / refactorizador, o consideren que eliminar las pruebas unitarias redundantes es una parte válida de un "refactorizador" aunque sea estrictamente hablando No está refactorizando. Su equipo puede decidir cuánto drama y papeleo deben estar involucrados para justificar esta decisión.

De todos modos, si b()fuera importante, habría pruebas funcionales para ello, y no se eliminarían a la ligera, pero ya ha dicho que solo hay pruebas unitarias. Si no distingue adecuadamente entre las pruebas unitarias (escritas en el diseño interno actual del código, que ha cambiado) y las pruebas funcionales (escritas en las interfaces publicadas, que tal vez no desee cambiar), entonces debe ser más cauteloso sobre la eliminación de pruebas unitarias.

Steve Jessop
fuente
2

Una cosa que siempre debemos recordar es que ahora estamos usando REPOSITORIOS DE CÓDIGO con CONTROL DE VERSIÓN. Ese código eliminado no se ha ido realmente ... todavía está allí en algún lugar de una iteración anterior. ¡Así que sopla! Sea liberal con la tecla Eliminar, porque siempre puede regresar y recuperar ese precioso método elegante que pensó que podría ser útil algún día ... si algún día llega. Está allá.

Por supuesto, eso va junto con la advertencia de los males y el peligro de las versiones no compatibles con versiones anteriores ... aplicaciones externas que dependían de la implementación de su interfaz, que ahora están huérfanas por su código (de repente) en desuso.

dwoz
fuente
Eliminar código no es un problema en general. Me encanta eliminar para eliminar líneas, funciones o ... bueno, ¡módulos completos! Si es posible. Y hago todo esto con o sin TDD. Pero: ¿Hay algún punto en el flujo de trabajo TDD donde se elimine el código (no dupli)? De lo contrario, se eliminará de todos modos. Pero hay Esa fue mi pregunta.
TobiMcNamobi