Stabilność zamiast usuwania: strategie współistnienia kodu legacy w dużych projektach
W długowiecznych systemach o wysokim obciążeniu usunięcie kodu legacy wiąże się z ryzykiem regresji i utraty stabilności. Zamiast kompletnego refaktoryzowania lepiej stosować współistnienie starego i nowego kodu za pomocą feature toggles i fasad. Pozwala to rozwijać system bez przestojów, zachowując sprawdzone komponenty jako zapasowe.
Ryzyko agresywnego usuwania kodu
Usunięcie nawet martwego kodu może powodować awarie z powodu ukrytych zależności: reflection, dynamiczne ładowanie, wywołania RPC. Testy w systemach legacy rzadko obejmują przypadki graniczne, a dane produkcyjne aktywują rzadkie gałęzie. Utrata pamięci instytucjonalnej pogłębia problem — odchodząca drużyna zostawia kod krytyczny dla rzadkich scenariuszy, takich jak migracje czy zgodność z regulacjami.
W środowiskach enterprise niestandardowe moduły dostawców stają się narażone: zmiany blokują wsparcie producenta.
Zasada "nie ruszaj tego, co działa" w praktyce
Klasyczny przykład – NASA z sondą Deep Space 1: kod z lat 70. pozostał niezmieniony, otoczony adapterami. Stabilne komponenty nie są dotykane, jeśli spełniają swoje zadanie bez zakłóceń. To dojrzała strategia dla systemów starszych niż 5 lat.
Architektura współistnienia: Strangler Fig wstecz
Nie niszcz kodu legacy – buduj nowe równolegle, stopniowo przekazując ruch. Dwie strategie:
| Strategia | Działania | Warunki zastosowania |
|-----------|----------|-------------------|
| Usunięcie | Przepisać na nowym stacku | Prosta logika, 100% pokrycie testów, brak zależności |
| Współistnienie | Nowy kod równolegle, stopniowy routing | Krytyczna funkcjonalność, słabe testy, wysokie ryzyko |
Feature toggles zapewniają bezpieczeństwo:
- Opracuj nową implementację.
- Otocz w sprawdzanie flagi.
- Uruchom na 1% ruchu.
- Skaluj przy sukcesie.
- Cofnij przez flagę przy awarii.
Kod legacy pozostaje w repozytorium 2–3 lata dla statystyki niezawodności.
Realizacja w Spring Boot
Otocz kod legacy interfejsem, nie zmieniając oryginalnego kodu.
// Legacy-serwis (nie dotykamy)
@Service
public class LegacyDiscountService {
public double calculateDiscount(Order order) {
// Logika z pięciu lat temu
return order.getTotal() * 0.1;
}
}
// Nowy serwis
@Service
public class NewDiscountService {
public double calculateDiscount(Order order) {
return applyComplexRules(order);
}
}
Fasadę z flagą:
@Component
public class DiscountServiceFacade {
private final LegacyDiscountService legacyService;
private final NewDiscountService newService;
@Value("${feature.discount.new.enabled:false}")
private boolean useNewService;
public DiscountServiceFacade(LegacyDiscountService legacyService,
NewDiscountService newService) {
this.legacyService = legacyService;
this.newService = newService;
}
public double calculateDiscount(Order order) {
if (useNewService) {
return newService.calculateDiscount(order);
}
return legacyService.calculateDiscount(order);
}
}
Izolacja przez profile:
@Configuration
public class DiscountConfiguration {
@Bean
@ConditionalOnProperty(name = "feature.discount.new", havingValue = "false", matchIfMissing = true)
public DiscountService legacyDiscountService() {
return new LegacyDiscountService();
}
@Bean
@ConditionalOnProperty(name = "feature.discount.new", havingValue = "true")
public DiscountService newDiscountService() {
return new NewDiscountService();
}
}
Testy pomijają legacy:
@Test
@DisabledIf("!${feature.discount.new.enabled}")
void testNewDiscountLogic() {
// Tylko nowa logika
}
Schemat przełączania ruchu
API Gateway routuje według flag: monolit jako fallback, mikroserwis stopniowo zwiększa obciążenie. Service zarządzający feature toggle kontroluje procenty w czasie rzeczywistym. Kod legacy w produkcji przez 2–3 lata.
Kiedy usunięcie jest nieuniknione
- Bezpieczeństwo: CVE w usterzonych bibliotekach.
- Zgodność: Naruszenia GDPR.
- EOL technologii: Java bez aktualizacji.
- Brak ekspertyzy: Brak specjalistów.
Nawet wtedy – stopniowy rollout z możliwością cofnięcia.
Co jest ważne
- Stabilność systemu przeważa nad estetyką kodu: dostępność ma większą wartość niż usunięte linie legacy.
- Ukryte zależności wymagają monitoringu wywołań w środowisku produkcyjnym przed usunięciem.
- Feature toggles minimalizują ryzyko podczas migracji.
- Dług techniczny się opłaca, jeśli utrzymanie jest tańsze niż refaktoryzacja.
- Pamięć instytucjonalna w kodzie: nie usuwaj bez 100% pewności.
— Editorial Team
Brak komentarzy.