Zapachy kodu to nie błędy. Program z błędami ulega awarii, zwraca błędne wyniki lub nie przechodzi testów. Program z zapachami kodu może działać bez zarzutu przez lata, a jednak każda zmiana kosztuje więcej niż powinna, każda nowa funkcja niesie ze sobą nieoczekiwane ryzyko, a każda próba refaktoryzacji ujawnia zależności, o których istnieniu nikt nie wiedział. Zapachy kodu to strukturalne cechy kodu, które przewidują przyszłe problemy: nie powodują natychmiastowej awarii, ale sprawiają, że każda przyszła zmiana jest trudniejsza, wolniejsza i bardziej niebezpieczna niż to konieczne.
Termin ten został spopularyzowany przez Martina Fowlera i Kenta Becka w książce Fowlera „Refactoring: Improving the Design of Existing Code ” (1999), w której skatalogowano 22 nazwane „smug kodu” i przypisano każdemu z nich odpowiednią technikę refaktoryzacji. Katalog ten pozostaje kanonicznym odniesieniem, a „smug” wymienione przez Fowlera, takie jak „Long Method”, „God Class”, „Duplicated Code”, „Feature Envy”, „Divergent Change”, „Shotgun Surgery” i inne, pojawiają się dziś w zestawach reguł SonarQube, narzędziach do analizy statycznej i listach kontrolnych do przeglądu kodu w całej branży.
Wyczyść zapachy kodu
SMART TS XL pomaga je mapować i naprawiać w złożonych systemach.
Więcej informacjiCzym jest zapach kodu?
Zapach kodu to powierzchowna cecha kodu źródłowego, która sugeruje głębszy problem strukturalny lub projektowy. Kod kompiluje się, przechodzi testy i generuje poprawne wyniki, ale coś w jego strukturze sprawia, że jest trudniejszy do odczytania, rozszerzenia lub bezpiecznej modyfikacji niż powinien. Definicja Fowlera: „powierzchowna oznaka, która zazwyczaj odpowiada głębszemu problemowi w systemie”.
Zapachy kodu nie są naruszeniami w tym samym sensie co błąd składniowy czy nieudana asercja. Są to wskaźniki, wzorce, które doświadczeni programiści rozpoznają jako sygnały ostrzegawcze, nawet gdy nie widać natychmiastowego błędu. Niebezpieczeństwo polega na tym, że się kumulują: pojedyncza, długa metoda w 10 000-linijkowej bazie kodu to drobna niedogodność. Setki długich metod, zduplikowana logika rozproszona w dziesiątkach modułów i klasy God Classes w centrum grafu zależności to system, który stał się naprawdę trudny do bezpiecznej zmiany.
Zapachy kodu kontra błędy kontra dług techniczny
Te trzy koncepcje są ze sobą powiązane, ale odrębne, a ich mylenie prowadzi do niewłaściwego ustalenia priorytetów:
| Pojęcie | Definicja | Natychmiastowa porażka? | Jak znaleźć |
|---|---|---|---|
| Bug | Kod, który powoduje nieprawidłowe zachowanie | Tak, testy kończą się niepowodzeniem, użytkownicy zgłaszają błędy | Testowanie, monitorowanie, dzienniki błędów |
| Zapach kodu | Wzorzec strukturalny przewidujący przyszłe problemy | Nie, kod działa poprawnie | Przegląd kodu, analiza statyczna |
| Dług techniczny | Skumulowany koszt przeszłych skrótów i złych decyzji | Nie, ale z czasem się to łączy | Metryki, analiza złożoności, szacunki nakładu pracy na refaktoryzację |
Zapachy kodu to mechanizm akumulacji długu technicznego. Każda długa metoda dodana do bazy kodu to jednostka zaciągniętego długu technicznego; jej odsetki to dodatkowy czas, jaki każdy przyszły programista poświęca na jej zrozumienie, oraz każda przyszła zmiana, której celem jest uniknięcie skutków ubocznych wynikających z jej rozmiaru.
Czym jest kod zapachowy w SonarQube?
SonarQube klasyfikuje problemy z kodem na trzy kategorie: błędy (zdecydowanie błędne), luki w zabezpieczeniach (problemy bezpieczeństwa) i zapachy kodu (problemy z utrzymaniem). Zapachy kodu w SonarQube są bezpośrednio odwzorowywane na katalog Fowlera i obejmują reguły dotyczące długich metod (powyżej konfigurowalnych progów wierszy), zduplikowanych bloków, zbyt dużej liczby parametrów, złożonych wskaźników złożoności poznawczej, brakującej obsługi błędów oraz naruszeń sprzężenia architektonicznego. Reguły zapachu kodu w SonarQube to najszerzej stosowana w branży zautomatyzowana operacjonalizacja oryginalnej taksonomii Fowlera.
Zapachy kodu Martina Fowlera: klasyczna taksonomia
Oryginalne 22 zapachy kodu Fowlera, uporządkowane według kategorii, pozostają standardowym punktem odniesienia. Zestaw reguł każdego głównego narzędzia do analizy statycznej pochodzi z tej taksonomii.
| Kategoria | Zapachy kodu |
|---|---|
| Wzdęciakod, który osiągnął nieporęczny rozmiar | Długa metoda, duża klasa, obsesja prymitywna, długa lista parametrów, skupiska danych |
| Osoby nadużywające orientacji obiektowej, niewłaściwe wykorzystanie zasad OO | Instrukcje Switch, Pole tymczasowe, Odrzucony zapis, Klasy alternatywne z różnymi interfejsami |
| Czynniki zapobiegające zmianom, utrudniać zmianę | Zmiana rozbieżna, operacja typu shotgun, równoległe hierarchie dziedziczenia |
| Niezbędne, niepotrzebny kod | Komentarze (nadmierne), Duplikat kodu, Leniwa klasa, Klasa danych, Martwy kod, Ogólność spekulatywna |
| Sprzęgła, nadmierne sprzężenie | Zazdrość, nieodpowiednia intymność, łańcuchy wiadomości, pośrednik |
Zrozumienie, do której kategorii należy dany zapach, pomaga ustalić priorytety działań naprawczych: czynniki wzmagające i uniemożliwiające zmiany bezpośrednio korelują z wysokimi kosztami refaktoryzacji; czynniki sprzęgające bezpośrednio korelują z kruchością architektury; czynniki zbędne są najbezpieczniejsze do usunięcia.
Najczęstsze zapachy kodów: krótki przewodnik
| Zapach kodu | Jak to wygląda | Ryzyko pierwotne |
|---|---|---|
| Zduplikowany kod | Ta sama logika pojawia się w wielu miejscach | Poprawki błędów muszą być stosowane wszędzie, kopie różnią się z czasem |
| Długa metoda | Metody przekraczające 20-30 wierszy z wieloma obowiązkami | Duże obciążenie poznawcze; trudno testować izolowane zachowania |
| Klasa Boga / Duża klasa | Jedna klasa, która robi wszystko | Każda zmiana funkcji dotyczy tej samej klasy: konfliktów scalania, kruchości |
| Długa lista parametrów | Metody przyjmujące 4+ parametrów | Łatwo przekazać błędne wartości, trudno odczytać miejsca wywołań |
| Zazdrość o funkcję | Metoda wykorzystująca dane innej klasy w większym stopniu niż własne | Ścisłe sprzężenie; zmiana w jednej klasie powoduje przerwanie drugiej |
| Zmiana rozbieżna | Jedna klasa zmodyfikowana z wielu różnych powodów | Narusza zasadę odpowiedzialności indywidualnej; nieprzewidywalne skutki uboczne |
| Operacja strzelbowa | Jedna zmiana wymaga edycji w wielu klasach | Wysokie koszty zmian; łatwo przeoczyć wystąpienie |
| Martwy kod | Kod, który nigdy nie jest wywoływany ani osiągany | Dezorientuje programistów, gromadzi się przez lata, komplikuje migrację |
| Pierwotna obsesja | Korzystanie z typów podstawowych (ciągi znaków, inty) zamiast obiektów domeny | Walidacja rozproszona wszędzie; słaba ekspresja |
| Grupy danych | Ta sama grupa pól przekazywana wielokrotnie razem | Powinien być obiektem domeny; sygnały nie posiadają abstrakcji |
| Ogólność spekulatywna | Kod napisany z myślą o wyobrażonych przyszłych potrzebach | Niepotrzebna złożoność; nikt nie rozumie, dlaczego tam jest |
| Niespójna obsługa błędów | Ciche łapanie, różne strategie wyjątków | Awarie nie są wykrywane, debugowanie trwa znacznie dłużej |
Definicje i przykłady zapachu kodu
Zduplikowany kod
Najczęstszy i najdroższy problem z kodem w dużych systemach. Duplikacja powstaje w wyniku kopiowania i wklejania, presji czasu oraz pracy zespołów w silosach, które niezależnie rozwiązują ten sam problem. Bezpośrednią konsekwencją jest podatek konserwacyjny: każda zmiana współdzielonej logiki musi zostać zastosowana do każdej kopii.
Jawa
// ServiceA -- discount calculation
double calculateDiscount(double amount) {
if (amount > 1000) return amount * 0.1;
return 0;
}
// ServiceB -- same logic, copied and forgotten
double computeDiscount(double value) {
if (value > 1000) return value * 0.1;
return 0;
}
Gdy reguła biznesowa ulega zmianie (próg wzrasta do 1500, a stawka do 12%), jedna kopia zostaje zaktualizowana, a druga nie. Dwa moduły nie zgadzają się teraz co do fundamentalnej logiki biznesowej, a rozbieżność ujawnia się w środowisku produkcyjnym podczas audytu, a nie podczas testów.
Poprawka : Wyodrębnij logikę współdzieloną do pojedynczej funkcji, klasy narzędziowej lub biblioteki współdzielonej, do której odwołują się obaj wywołujący.
Długa metoda
Metoda, która z czasem rozrosła się poza swoje pierwotne przeznaczenie, absorbując dodatkowe obowiązki. Obciążenie poznawcze związane z czytaniem metody składającej się z 200 wierszy kodu różni się jakościowo od czytania dwudziestu metod składających się z 10 wierszy kodu, a nie tylko ilościowo. Długie metody są trudne do przetestowania, ponieważ wykonują zbyt wiele czynności, aby testować je w izolacji, i trudne do zrozumienia, ponieważ czytelnik musi zachować cały kontekst wykonania w pamięci roboczej.
Próg wykrywalności : Metody powyżej 20-30 wierszy wymagają weryfikacji; powyżej 50 wierszy refaktoryzacja jest prawie zawsze uzasadniona. W COBOL-u akapity przekraczające 100 poleceń są równoważne.
pyton
class OrderProcessor:
def process_order(self, order):
# Validate order -- 40 lines
# Calculate discounts -- 30 lines
# Update inventory -- 25 lines
# Send notification emails -- 20 lines
# Generate invoice -- 35 lines
# 150+ lines total
pass
Każda odpowiedzialność w tej metodzie powinna być oddzielną klasą lub funkcją. Ich połączenie oznacza, że każda przyszła aktualizacja fakturowania, stanu zapasów lub powiadomień grozi destabilizacją całego procesu przetwarzania zamówień.
Klasa Boga
Klasa, która skumulowała obowiązki w wielu domenach, naruszając w tak poważny sposób zasadę pojedynczej odpowiedzialności, że staje się centrum ciężkości bazy kodu: wszystko od niej zależy, a wprowadzenie w niej jakichkolwiek zmian wymaga zrozumienia wszystkich jej aspektów.
Sygnał wykrycia : Klasa zawierająca ponad 20–30 metod publicznych lub taka, której nazwa zawiera słowa kluczowe „Manager”, „Processor”, „Handler”, „Utils” lub „Helper” stosowane do wielu niezwiązanych ze sobą domen.
Zmiana rozbieżna
Klasa, która jest modyfikowana z wielu różnych, niezwiązanych ze sobą powodów. Za każdym razem, gdy zmienia się schemat bazy danych, edytujesz tę klasę. Za każdym razem, gdy zmieniają się zasady cenowe, edytujesz tę klasę. Za każdym razem, gdy zmienia się format powiadomień, edytujesz tę klasę. Ta klasa ma zbyt wiele obowiązków i powinna zostać podzielona.
Definicja : Jedna klasa, która ciągle się zmienia z różnych powodów. Odwrotność chirurgii strzelbowej.
Operacja strzelbowa
Pojedyncza zmiana koncepcyjna, która wymaga edycji w wielu różnych klasach. Zmiana stawki podatkowej wymaga modyfikacji obliczeń back-endowych, walidacji front-endowych, wyzwalacza bazy danych, zadania wsadowego i zapytania raportowego w pięciu różnych miejscach. Pominięcie któregokolwiek z nich powoduje niespójne działanie.
sql
-- Tax logic duplicated across queries
SELECT amount * 0.05 FROM invoices;
SELECT amount * 0.05 FROM payments;
SELECT amount * 0.05 FROM reports;
Zmiana z 0.05 na 0.07 wymaga teraz znalezienia każdego wystąpienia w plikach SQL, procedurach składowanych i kodzie aplikacji.
Zazdrość o funkcję
Metoda, która spędza więcej czasu korzystając z danych i metod innej klasy niż z własnych. Sygnalizuje to, że zachowanie prawdopodobnie należy do innej klasy.
Jawa
// In ReportGenerator -- envious of Customer's data
double calculateCustomerRating(Customer customer) {
return customer.getOrderCount() * customer.getAverageOrderValue()
/ customer.getDaysSinceRegistration();
}
// This logic belongs in Customer, not ReportGenerator
Martwy kod
Kod, który istnieje w repozytorium, ale nigdy nie jest wywoływany przez żadną ścieżkę wykonania w środowisku produkcyjnym. Martwy kod gromadzi się latami, ponieważ funkcje są usuwane, zastępowane lub restrukturyzowane bez usuwania starego kodu. Powoduje to zakłócenia w przeglądzie kodu, dezorientuje programistów w procesie wdrażania do bazy kodu, komplikuje analizę migracji i czasami jest przypadkowo reaktywowany.
Wykrywanie:Narzędzia do analizy statycznej, w tym SonarQube, Knip (dla TypeScript/JavaScript) i SMART TS XL zidentyfikuj niedostępne funkcje, niewywołane metody i nieużywane zmienne w całej bazie kodu.
Naruszenia zasady DRY
Zasada DRY (Don't Repeat Yourself) głosi, że każdy element wiedzy musi mieć pojedynczą, jednoznaczną reprezentację w systemie. Naruszenia DRY są główną przyczyną duplikacji kodu, skupisk danych i wielu scenariuszy operacji typu shotgun. Gdy logika biznesowa jest reprezentowana w wielu miejscach, reprezentacje te nieuchronnie się rozchodzą. DRY to zasada; duplikacja kodu to zapach wskazujący na naruszenie.
pyton
# DRY violation: same validation logic in three places
def validate_email_in_registration(email):
return "@" in email and "." in email
def validate_email_in_profile_update(email):
return "@" in email and "." in email
def validate_email_in_checkout(email):
return "@" in email and "." in email
# DRY-compliant: one function, three callers
def is_valid_email(email):
return "@" in email and "." in email
Progi detekcji: kiedy kod staje się zapachem?
Wykrywanie zapachu kodu wymaga mierzalnych progów. Poniżej przedstawiono powszechnie używane wskaźniki i wartości wskazujące na zapach wymagający uwagi:
| metryczny | Co mierzy | Próg ostrzegawczy | Próg krytyczny |
|---|---|---|---|
| Złożoność cyklomatyczna | Liczba gałęzi decyzyjnych w metodzie | Przede 10 | Przede 20 |
| Metoda Długość (linie) | Liczba wierszy w metodzie/funkcji | Przede 20 | Przede 50 |
| Liczba parametrów | Liczba parametrów akceptowanych przez metodę | Przede 4 | Przede 7 |
| Długość zajęć | Liczba wierszy w klasie | Przede 200 | Przede 500 |
| Współczynnik duplikacji | Procent kodu, który jest zduplikowany | Powyżej 3% | Powyżej 10% |
| Złożoność poznawcza | Jak trudny jest do zrozumienia kod | Przede 15 | Przede 25 |
| Sprzężenie aferentne (Ca) | Liczba klas zależnych od tej klasy | Przede 15 | Przede 30 |
| Sprzęgło eferentne (Ce) | Liczba zajęć w tej klasie zależy od | Przede 15 | Przede 30 |
Progi te można konfigurować w SonarQube, a większość platform do analizy statycznej umożliwia tworzenie niestandardowych reguł opartych na tych metrykach. Klasy i metody progów krytycznych to cele refaktoryzacji o najwyższym priorytecie: są najbardziej prawdopodobnymi źródłami przyszłych defektów i najbardziej kosztownymi w utrzymaniu komponentami.
Narzędzia do wykrywania zapachu kodu
Automatyczne wykrywanie to jedyne skalowalne podejście do identyfikacji podejrzanych kodów w dużych bazach kodu. Ręczny przegląd wychwytuje jedynie ułamek tego, co wykrywają narzędzia automatyczne, a ręczny przegląd nie jest skalowalny do starszych systemów z milionami wierszy kodu.
| Narzędzie | Podstawowy język | Co wykrywa |
|---|---|---|
| SonarQube / SonarCloud | Java, Python, JS/TS, C# i inne | Pełna taksonomia zapachów Fowlera, punkty zapalne bezpieczeństwa, duplikacje |
| Checkstyle + PMD | Java | Naruszenia stylu, duplikaty, metryki złożoności |
| ESLint + typescript-eslint | JavaScript, TypeScript | Długie funkcje, złożoność, nieużywany kod |
| Pylint + Radon | Python | Wskaźnik złożoności, stylu i łatwości utrzymania |
| ReSharper / Rider | C# | Nadmiarowy kod, długie metody, problemy ze sprzężeniem |
| Clippy | Rdza | Naruszenia idiomatyczne, typowe wzorce będące zapachami kodu w Rust |
| kodKlimat | Wielojęzyczne | Złożoność, duplikacja, wynik łatwości utrzymania |
| SMART TS XL | COBOL, JCL, Java, Python, RPG, SQL, .NET | Duplikacja międzyjęzykowa, martwy kod, sprzężenie, dryf zależności |
W Rust kod śmierdzi są wychwytywane głównie przez Clippy, który wymusza idiomatyczne wzorce Rust. Do najczęstszych zapachów specyficznych dla Rust należą: niepotrzebne klonowanie, niewłaściwe użycie unwrap() w ścieżkach produkcyjnych, nadmiernie zagnieżdżonych wyrażeniach dopasowania i funkcjach, które powinny zwracać Result ale zamiast tego użyj paniki.
Zapachy kodu i dług techniczny: powiązanie
Dług techniczny to skumulowany koszt wcześniejszych decyzji, które faworyzowały szybkość nad jakością. Błędy w kodzie to mechanizm, poprzez który ten dług manifestuje się w strukturze kodu. Zależność jest bezpośrednia: każdy nieadresowany błąd w kodzie to jednostka długu technicznego, a stopa procentowa to dodatkowy czas, jaki każda przyszła zmiana musi poświęcić na jego obejście.
Jak opisano w kontekście analizy wpływu na zarządzanie zmianą oprogramowania , problemy strukturalne, o których wspomina zapach kodu, takie jak nadmierne powiązania, zduplikowana logika, akumulacja martwego kodu, bezpośrednio zwiększają zakres każdej zmiany, ponieważ utrudniają wyizolowanie wpływu danej zmiany.
Wyjaśnij dług techniczny w kategoriach „smrodu kodu” : jeśli baza kodu ma 40% duplikacji, każda poprawka błędu kosztuje 1.4 raza więcej niż powinna. Jeśli podstawowa klasa przetwarzania jest klasą boską, od której wszystko zależy, każde dodanie funkcji wymaga zrozumienia i przetestowania całej klasy. Jeśli obsługa błędów jest niespójna, każdy incydent produkcyjny wymaga więcej czasu na analizę, ponieważ sygnały awarii są zawodne. Dług techniczny nie jest abstrakcyjny, lecz sumą tych narastających nieefektywności.
Badania CISQ konsekwentnie wskazują, że programiści spędzają 30-40% swojego czasu na pracy nad długiem technicznym, zamiast tworzyć nowe funkcjonalności. Gęstość zapachu kodu jest najbardziej bezpośrednim miernikiem tego, ile długu się nagromadziło.
W jaki sposób SMART TS XL Wykrywa zapachy kodu w skali przedsiębiorstwa
Poszczególne narzędzia, takie jak SonarQube i Clippy, działają w ramach jednego języka. W środowiskach korporacyjnych, gdzie programy COBOL zapisują dane do zestawów danych odczytywanych przez usługi Java, gdzie strumienie zadań JCL wywołują programy w wielu językach, a ta sama logika biznesowa została niezależnie zduplikowana w trzech różnych systemach napisanych w ciągu trzech różnych dekad, narzędzia jednojęzyczne nie są w stanie zapewnić pełnego obrazu.
SMART TS XL'S statyczna analiza kodu wykrywa jednocześnie nieprawidłowe kody we wszystkich językach w środowisku: zduplikowaną logikę między kopią COBOL a klasą narzędziową Java, martwy kod w programach RPG, którego nie wywołuje żadne zadanie JCL, wzorce God Class w programach COBOL, gdzie pojedynczy akapit wykonuje pracę pięćdziesięciu, oraz niespójne wzorce obsługi błędów w różnych językach.
Możliwość mapowania zależności aplikacji identyfikuje architektoniczne problemy, których poszczególne narzędzia na poziomie plików nie widzą: które komponenty mają najsilniejsze sprzężenie aferentne (od których jest najwięcej zależności, najwyższe ryzyko uszkodzenia czegoś w przypadku wprowadzenia zmian), gdzie występują zależności cykliczne między modułami, które powinny być niezależne, oraz gdzie zduplikowana logika biznesowa była niezależnie utrzymywana w różnych systemach, bez świadomości istnienia drugiej kopii.
Funkcja analizy wpływu umożliwia wdrożenie działań w przypadku wystąpienia nieprzyjemnych zapachów: przed refaktoryzacją jakiegokolwiek komponentu o wysokim stopniu sprzężenia, analiza wpływu wylicza każdy zależny komponent, który wymaga przetestowania, walidacji lub aktualizacji. To przekształca „paraliż refaktoryzacji”, którego zespoły doświadczają w dużych, „śmierdzących” bazach kodu, w ustrukturyzowany, zakresowy program naprawczy, w którym każda zmiana ma zdefiniowany zakres, a nie nieznane ryzyko.
W przypadku zespołów realizujących programy modernizacji starszych wersji oprogramowania, analiza zapachu kodu stanowi podstawę planu modernizacji: martwy kod jest eliminowany przed rozpoczęciem migracji (zmniejszając jej zakres), zduplikowana logika jest konsolidowana w kanoniczne implementacje, komponenty o największym powiązaniu są modernizowane na końcu (po zajęciu się wszystkim, co od nich zależy), a klasy Boga są rozkładane przed konwersją na nowy język, ponieważ konwersja klasy Boga do języka Java powoduje utworzenie klasy Boga w języku Java.
Rozwiązywanie problemów z zapachami kodu: Struktura priorytetyzacji
Nie wszystkie smrody kodu wymagają natychmiastowej refaktoryzacji. Właściwym podejściem jest priorytetyzacja oparta na ryzyku:
Priorytet 1: Błędy w komponentach o wysokiej częstotliwości zmian. Kod, który często się zmienia i charakteryzuje się wysoką złożonością lub sprzężeniem, generuje najwięcej defektów. Te komponenty generują najwyższy koszt w przeliczeniu na zmianę i generują najwięcej incydentów produkcyjnych. Napraw je w pierwszej kolejności.
Priorytet 2: Zapachy na granicach architektonicznych. Klasy Boga i komponenty o wysokim stopniu sprzężenia, od których wszystko zależy, są najbardziej niebezpieczne do zmiany, ale też najdroższe do pozostawienia bez zmian. Wymagają one najdokładniejszej analizy wpływu przed refaktoryzacją.
Priorytet 3: Duplikacja kodu w różnych systemach. Gdy ta sama logika biznesowa istnieje w wielu systemach, zmiany muszą być skoordynowane we wszystkich kopiach jednocześnie. Konsolidacja tej duplikacji zmniejsza obciążenie związane z koordynacją i zapobiega rozbieżnościom.
Priorytet 4, Usuwanie martwego kodu. Martwy kod to najbezpieczniejsza kategoria, którą należy uwzględnić: jego usunięcie nie może zakłócić działania, a jedynie ujawnić wcześniej ukryte zależności. Należy go usunąć przed jakąkolwiek migracją lub konwersją, aby uniknąć marnowania czasu na konwersję kodu, który nigdy nie zostanie wywołany.
Priorytet 5, Styl i zapachy strukturalne w obszarach niskiego ryzyka. Długie metody i listy parametrów w stabilnym kodzie o niskim współczynniku zmian można rozwiązać w sposób doraźny, gdy sąsiedni kod wymaga zmiany z innych powodów, jednocześnie refaktoryzując otaczające zapachy.
Dyscyplina polegająca na systematycznym wykrywaniu, mierzeniu i reagowaniu na problemy z kodem, które już spowodowały awarię produkcji, a nie na reaktywnym ich wykrywaniu, odróżnia zespoły programistyczne, które utrzymują stałe tempo dostarczania oprogramowania, od tych, które stopniowo zwalniają wraz z rozwojem systemów.