Eu tenho um projeto. Neste projeto, eu queria refatorá-lo para adicionar um recurso, e refatorei o projeto para adicionar o recurso.
O problema é que, quando terminei, era necessário fazer uma pequena alteração na interface para acomodá-la. Então eu fiz a mudança. E então a classe consumidora não pode ser implementada com sua interface atual em termos da nova, por isso também precisa de uma nova interface. Agora, três meses depois, eu tive que corrigir inúmeros problemas virtualmente não relacionados, e estou tentando resolver problemas que foram roteados para daqui a um ano ou simplesmente listados como não serão corrigidos devido à dificuldade antes que a coisa seja compilada novamente.
Como posso evitar esse tipo de refatoração em cascata no futuro? É apenas um sintoma das minhas aulas anteriores que dependem muito um do outro?
Edição resumida: nesse caso, o refator era o recurso, pois o refator aumentava a extensibilidade de um pedaço de código específico e diminuía algum acoplamento. Isso significava que os desenvolvedores externos poderiam fazer mais, que era o recurso que eu queria oferecer. Portanto, o refator original em si não deveria ter sido uma mudança funcional.
Edição maior que prometi há cinco dias:
Antes de iniciar esse refator, eu tinha um sistema em que tinha uma interface, mas na implementação, simplesmente dynamic_cast
através de todas as implementações possíveis que eu enviei. Obviamente, isso significava que você não poderia simplesmente herdar da interface, por um lado, e em segundo lugar, que seria impossível para qualquer pessoa sem acesso à implementação implementar essa interface. Então, decidi que queria corrigir esse problema e abrir a interface para consumo público, para que qualquer pessoa pudesse implementá-lo e que a implementação da interface fosse o contrato inteiro necessário - obviamente uma melhoria.
Quando eu estava encontrando e matando com fogo todos os lugares que tinha feito isso, encontrei um lugar que provou ser um problema específico. Dependia dos detalhes de implementação de todas as várias classes derivadas e da funcionalidade duplicada que já estava implementada, mas melhor em outro lugar. Poderia ter sido implementado em termos da interface pública e reutilizado a implementação existente dessa funcionalidade. Descobri que era necessário um determinado contexto para funcionar corretamente. Grosso modo, a implementação anterior de chamada parecia um pouco com
for(auto&& a : as) {
f(a);
}
No entanto, para obter esse contexto, eu precisava transformá-lo em algo mais como
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);
Isso significa que, para todas as operações que costumavam fazer parte f
, algumas delas precisam fazer parte da nova função g
que opera sem um contexto, e algumas delas precisam ser feitas como parte do agora diferido f
. Mas nem todos os métodos f
chamam necessidade ou querem esse contexto - alguns deles precisam de um contexto distinto que obtêm por meios separados. Então, para tudo o que f
acaba chamando (que é, grosso modo, praticamente tudo ), eu tive que determinar o contexto necessário, se houver, de onde eles deveriam obtê-lo e como separá-los do antigo f
para o novo f
e o novo. g
.
E foi assim que acabei onde estou agora. A única razão pela qual eu continuei foi porque eu precisava dessa refatoração por outras razões de qualquer maneira.