Mam projekt. W tym projekcie chciałem przefaktoryzować go, aby dodać funkcję, i przebudowałem projekt, aby dodać funkcję.
Problem polega na tym, że kiedy skończyłem, okazało się, że muszę wprowadzić niewielką zmianę interfejsu, aby to uwzględnić. Więc dokonałem zmiany. I wtedy klasa konsumująca nie może zostać zaimplementowana z obecnym interfejsem pod względem nowego, więc potrzebuje również nowego interfejsu. Teraz minęły trzy miesiące i musiałem naprawić niezliczone, praktycznie niezwiązane ze sobą problemy, i patrzę na rozwiązywanie problemów, które zostały zaplanowane na rok od teraz lub po prostu wymienione jako nie naprawione z powodu trudności, zanim rzecz się skompiluje jeszcze raz.
Jak mogę uniknąć tego rodzaju refaktoryzacji kaskadowej w przyszłości? Czy to tylko symptom moich poprzednich zajęć, które zbyt mocno od siebie zależą?
Krótka edycja: w tym przypadku refaktorem była cecha, ponieważ refaktor zwiększył rozszerzalność określonego fragmentu kodu i zmniejszył pewne sprzężenie. Oznaczało to, że zewnętrzni programiści mogli zrobić więcej, co było funkcją, którą chciałem dostarczyć. Tak więc sam pierwotny refaktor nie powinien być zmianą funkcjonalną.
Większa edycja, którą obiecałem pięć dni temu:
Zanim zacząłem ten refaktor, miałem system, w którym miałem interfejs, ale w implementacji po prostu dynamic_cast
przechodziłem przez wszystkie możliwe implementacje, które wysłałem. To oczywiście oznaczało, że nie można po prostu odziedziczyć po interfejsie, po drugie, a po drugie, nie byłoby możliwe, aby ktokolwiek bez dostępu do implementacji implementował ten interfejs. Zdecydowałem więc, że chcę rozwiązać ten problem i otworzyć interfejs do publicznego użytku, aby każdy mógł go wdrożyć, a wdrożenie interfejsu było wymaganiem całej umowy - oczywiście poprawa.
Kiedy znajdowałem i zabijałem ogniem wszystkie miejsca, które to zrobiłem, znalazłem jedno miejsce, które okazało się być szczególnym problemem. Zależało to od szczegółów implementacji wszystkich różnych klas pochodnych i zduplikowanych funkcji, które zostały już zaimplementowane, ale lepiej gdzie indziej. Zamiast tego mógł zostać zaimplementowany w postaci interfejsu publicznego i ponownie wykorzystać istniejącą implementację tej funkcjonalności. Odkryłem, że do poprawnego działania wymagał określonego kontekstu. Z grubsza mówiąc, wywołanie poprzedniej implementacji wyglądało trochę jak
for(auto&& a : as) {
f(a);
}
Jednak, aby uzyskać ten kontekst, musiałem zmienić go na coś bardziej podobnego
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);
Oznacza to, że dla wszystkich operacji, które kiedyś były częścią f
, niektóre z nich muszą stać się częścią nowej funkcji, g
która działa bez kontekstu, a niektóre z nich muszą być częścią części odroczonej f
. Ale nie wszystkie metody f
nazywają potrzebę lub chcą tego kontekstu - niektóre z nich potrzebują odrębnego kontekstu, który uzyskują osobnymi środkami. Więc dla wszystkiego, co f
kończy się dzwonieniem (czyli, mówiąc z grubsza, prawie wszystko ), musiałem ustalić, jaki, jeśli w ogóle, potrzebny im kontekst, skąd go wziąć i jak podzielić je ze starego f
na nowe f
i nowe g
.
I tak skończyłem tam, gdzie teraz jestem. Jedynym powodem, dla którego kontynuowałem, jest to, że i tak potrzebowałem tego refaktoryzacji z innych powodów.