Tengo un proyecto. En este proyecto, quise refactorizarlo para agregar una característica, y he refactorizado el proyecto para agregar la característica.
El problema es que cuando terminé, resultó que tenía que hacer un cambio de interfaz menor para acomodarlo. Así que hice el cambio. Y luego, la clase consumidora no se puede implementar con su interfaz actual en términos de la nueva, por lo que también necesita una nueva interfaz. Ahora han pasado tres meses, y he tenido que solucionar innumerables problemas virtualmente no relacionados, y estoy resolviendo problemas que se planearon en un año o simplemente no se solucionarán debido a dificultades antes de que se compile. de nuevo.
¿Cómo puedo evitar este tipo de refactorizaciones en cascada en el futuro? ¿Es solo un síntoma de mis clases anteriores dependiendo demasiado el uno del otro?
Edición breve: en este caso, el refactor era la característica, ya que el refactor aumentó la extensibilidad de una pieza particular de código y disminuyó algunos acoplamientos. Esto significaba que los desarrolladores externos podían hacer más, que era la característica que quería ofrecer. Por lo tanto, el refactor original en sí no debería haber sido un cambio funcional.
Edición más grande que prometí hace cinco días:
Antes de comenzar este refactor, tenía un sistema donde tenía una interfaz, pero en la implementación, simplemente dynamic_cast
a través de todas las implementaciones posibles que envié. Obviamente, esto significaba que no podía simplemente heredar de la interfaz, por una cosa, y en segundo lugar, que sería imposible que alguien sin acceso a la implementación implementara esta interfaz. Así que decidí que quería solucionar este problema y abrir la interfaz para el consumo público para que cualquiera pudiera implementarlo y que la implementación de la interfaz fuera todo el contrato requerido, obviamente una mejora.
Cuando estaba encontrando y matando con fuego todos los lugares donde había hecho esto, encontré un lugar que resultó ser un problema particular. Dependía de los detalles de implementación de todas las diversas clases derivadas y la funcionalidad duplicada que ya estaba implementada pero mejor en otro lugar. Podría haberse implementado en términos de la interfaz pública en lugar de reutilizar la implementación existente de esa funcionalidad. Descubrí que se requería una pieza particular de contexto para funcionar correctamente. En términos generales, la implementación previa de la llamada se parecía un poco a
for(auto&& a : as) {
f(a);
}
Sin embargo, para obtener este contexto, necesitaba cambiarlo por algo más parecido
std::vector<Context> contexts;
for(auto&& a : as)
contexts.push_back(g(a));
do_thing_now_we_have_contexts();
for(auto&& con : contexts)
f(con);
Esto significa que para todas las operaciones que solían ser parte de f
, algunas de ellas deben formar parte de la nueva función g
que opera sin contexto, y algunas de ellas deben hacerse de una parte del f
ahora diferido. Pero no todos los métodos f
call necesitan o desean este contexto, algunos de ellos necesitan un contexto distinto que obtengan por medios separados. Así que para todo lo que f
termina llamando (lo cual es, en términos generales, bastante todo ), tuve que determinar qué contexto, si lo hubo, necesitaban, de dónde deberían obtenerlo, y cómo dividirlos del antiguo f
en nuevo f
y nuevo g
.
Y así es como terminé donde estoy ahora. La única razón por la que seguí adelante es porque necesitaba esta refactorización por otras razones de todos modos.