Kodelugt: Hvad de er, og hvordan de forbinder sig med teknisk gæld

Kodelugt: Hvad de er, og hvordan de forbinder sig med teknisk gæld

Kodelugt er ikke fejl. Et program med fejl går ned, returnerer forkerte resultater eller fejler en test. Et program med kodelugt kan køre perfekt i årevis, og alligevel koster hver ændring mere end den burde, hver ny funktion medfører uventede risici, og hvert forsøg på refactoring afslører afhængigheder, som ingen vidste eksisterede. Kodelugt er de strukturelle egenskaber ved kode, der forudsiger fremtidige problemer: de forårsager ikke en øjeblikkelig fejl, men de gør enhver fremtidig ændring sværere, langsommere og farligere end den behøver at være.

Udtrykket blev populariseret af Martin Fowler og Kent Beck i Fowlers bog *Refactoring: Improving the Design of Existing Code * (1999), som katalogiserede 22 navngivne kodelugte og matchede hver enkelt med en tilsvarende refactoringteknik. Dette katalog er fortsat den kanoniske reference, og de lugte, som Fowler navngav, Long Method, God Class, Duplicated Code, Feature Envy, Divergent Change, Shotgun Surgery og andre, optræder i SonarQube-regelsæt, statiske analyseværktøjer og kodegennemgangstjeklister på tværs af branchen i dag.

Oprydningskode lugter

SMART TS XL hjælper med at kortlægge og reparere dem på tværs af komplekse systemer.

Mere info

Hvad er en kodelugt?

En kodelugt er en overfladeegenskab ved kildekode, der antyder et dybere strukturelt eller designmæssigt problem. Koden kompileres, består tests og producerer korrekt output, men noget ved dens struktur gør den sværere at læse, udvide eller modificere sikkert, end den burde være. Fowlers definition: "en overfladeindikation, der normalt svarer til et dybere problem i systemet."

Kodelugt er ikke overtrædelser i samme forstand som en syntaksfejl eller en mislykket assertion. De er indikatorer, mønstre som erfarne udviklere genkender som advarselstegn, selv når der ikke er nogen umiddelbar fejl synlig. Faren er, at de er kumulative: en enkelt lang metode i en kodebase på 10,000 linjer er en mindre ulempe. Hundredvis af lange metoder, duplikeret logik spredt over snesevis af moduler, og God Classes i midten af ​​afhængighedsgrafen er et system, der er blevet virkelig vanskeligt at ændre sikkert.

Kodelugt vs. fejl vs. teknisk gæld

Disse tre begreber er relaterede, men forskellige, og at forveksle dem fører til dårlig prioritering:

ConceptDefinitionØjeblikkelig fiasko?Sådan Find
BugKode, der producerer forkert adfærdJa, test mislykkes, brugere rapporterer fejlTest, overvågning, fejllogfiler
Kode lugtStrukturmønster, der forudsiger fremtidige problemerNej, koden kører korrektKodegennemgang, statisk analyse
Teknisk gældDe akkumulerede omkostninger ved tidligere genveje og dårlige beslutningerNej, men forværres over tidMetrikker, kompleksitetsanalyse, estimater af refactoringindsats

Kodelugt er den mekanisme, hvorigennem teknisk gæld akkumuleres. Hver Long Method, der tilføjes til kodebasen, er en enhed af påløben teknisk gæld; dens rentebetaling er den ekstra tid, enhver fremtidig udvikler bruger på at forstå den, og enhver fremtidig ændring bruger på at undgå bivirkningerne af dens størrelse.

Hvad er en kodelugt i SonarQube?

SonarQube klassificerer kodeproblemer i tre kategorier: fejl (helt sikkert forkerte), sårbarheder (sikkerhedsproblemer) og kodelugt (vedligeholdelsesproblemer). SonarQubes kodelugt knyttes direkte til Fowlers katalog og inkluderer regler for lange metoder (over konfigurerbare linjegrænser), duplikerede blokke, for mange parametre, komplekse kognitive kompleksitetsscorer, manglende fejlhåndtering og overtrædelser af arkitektonisk kobling. Kodelugtreglerne i SonarQube er branchens mest anvendte automatiserede operationalisering af Fowlers originale taksonomi.

Martin Fowlers kode lugter: Den klassiske taksonomi

Fowlers originale 22 kode-lugts, organiseret efter kategori, forbliver standardreferencen. Alle større statiske analyseværktøjers regelsæt er afledt af denne taksonomi.

KategoriKode lugter
Oppustethed, kode der er vokset til en uhåndterlig størrelseLang metode, stor klasse, primitiv besættelse, lang parameterliste, dataklumper
Objektorienteringsmisbrugere, misbrug af OO-principperSkift-erklæringer, midlertidigt felt, afvist testamente, alternative klasser med forskellige grænseflader
Forebyggende faktorer, gør forandring vanskeligDivergent ændring, haglgeværkirurgi, parallelle arvshierarkier
Undværlige, unødvendig kodeKommentarer (overdrevne), Duplikatkode, Lazy Class, Dataklasse, Død kode, Spekulativ generalitet
koblinger, overdreven koblingMisundelse, upassende intimitet, beskedkæder, mellemmand

At forstå, hvilken kategori en lugt tilhører, hjælper med at prioritere afhjælpning: Oppustede lugte og forandringsforebyggende faktorer korrelerer direkte med høje refactoring-omkostninger; koblere korrelerer direkte med arkitektonisk skørhed; og engangslugte er de sikreste at fjerne.

Den mest almindelige kode lugter: Hurtig reference

Kode LugtHvad det lignerPrimær risiko
Duplikeret kodeDen samme logik optræder flere stederFejlrettelser skal implementeres overalt; kopier afviger over tid
Lang metodeMetoder på over 20-30 linjer med flere ansvarsområderHøj kognitiv belastning; vanskelig at teste isoleret adfærd
Gudklasse / StorklasseEn klasse, der gør altEnhver ændring af funktioner berører den samme klasse; fusionskonflikter, skrøbelighed
Lang parameterlisteMetoder der tager 4+ parametreLet at angive forkerte værdier; svært at læse opkaldssider
FunktionsmisundelseEn metode, der bruger en anden klasses data mere end sine egneTæt kobling; ændringer i én klasse bryder den anden
Divergerende forandringEn klasse ændret af mange forskellige årsagerOvertræder enkeltansvar; uforudsigelige bivirkninger
HaglgeværkirurgiÉn ændring kræver redigeringer på tværs af mange klasserHøje ændringsomkostninger; let at overse en instans
Død kodeKode, der aldrig kaldes eller nåsForvirrer udviklere; akkumuleres over år; komplicerer migrering
Primitiv besættelseBrug af grundlæggende typer (strenge, heltaller) i stedet for domæneobjekterValidering spredt overalt; dårlig udtryksevne
DataklumperDen samme gruppe af felter sendes sammen gentagne gangeBør være et domæneobjekt; signalerer manglende abstraktion
Spekulativ generalitetKode skrevet til forestillede fremtidige behovUnødvendig kompleksitet; ingen forstår hvorfor den er der
Inkonsekvent fejlhåndteringTavse fangster, varierende undtagelsesstrategierFejl opdages ikke; fejlfinding tager meget længere tid

Definitioner og eksempler på kodelugt

Duplikeret kode

Den mest almindelige og dyreste kodelugt i store systemer. Duplikering opstår ved copy-paste-udvikling, tidspres og ved at teams arbejder i siloer, der uafhængigt løser det samme problem. Den umiddelbare konsekvens er en vedligeholdelsesafgift: enhver ændring af delt logik skal anvendes på hver kopi.

Java

// 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;
}

Når forretningsreglen ændres (tærsklen bliver 1500, hastigheden bliver 12%), opdateres den ene kopi, og den anden gør ikke. To moduler er nu uenige om grundlæggende forretningslogik, og uoverensstemmelsen dukker op i produktionen under en revision snarere end under testning.

Rettelse : Udtræk den delte logik til en enkelt funktion, hjælpeklasse eller et delt bibliotek, som begge kaldere refererer til.

Lang metode

En metode, der er vokset ud over sit oprindelige formål ved at absorbere yderligere ansvar over tid. Den kognitive belastning ved at læse en metode på 200 linjer er kvalitativt forskellig fra at læse tyve metoder på 10 linjer, ikke kun kvantitativt. Lange metoder er svære at teste, fordi de udfører for mange ting til at teste isoleret, og svære at forstå, fordi læseren skal holde hele udførelseskonteksten i arbejdshukommelsen.

Detektionstærskel : Metoder over 20-30 linjer kræver gennemgang; over 50 linjer er refaktorering næsten altid berettiget. I COBOL er afsnit på over 100 sætninger det samme.

python

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

Hvert ansvar i denne metode bør være en separat klasse eller funktion. At samle dem betyder, at enhver fremtidig opdatering af fakturering, lager eller notifikationer risikerer at destabilisere hele ordrebehandlingsflowet.

Gud Klasse

En klasse, der har akkumuleret ansvar på tværs af flere domæner og dermed krænker princippet om enkeltansvar i så alvorlig grad, at den bliver centrum for en kodebases tyngdekraft: alt afhænger af den, og at ændre noget i den kræver forståelse af alt om den.

Detektionssignal : En klasse med mere end 20-30 offentlige metoder, eller en hvis navn indeholder "Manager", "Processor", "Handler", "Utils" eller "Helper", der anvendes på flere uafhængige domæner.

Divergerende forandring

En klasse, der ændres af mange forskellige, uafhængige årsager. Hver gang databaseskemaet ændres, redigerer du denne klasse. Hver gang prisreglerne ændres, redigerer du denne klasse. Hver gang notifikationsformatet ændres, redigerer du denne klasse. Denne klasse har for mange ansvarsområder og bør opdeles.

Definition : En klasse, der konstant ændrer sig af forskellige årsager. Det modsatte af haglgeværkirurgi.

Haglgeværkirurgi

En enkelt konceptuel ændring, der kræver redigeringer på tværs af mange forskellige klasser. Ændring af en skattesats kræver ændring af en backend-beregning, en frontend-validering, en databaseudløser, et batchjob og en rapporteringsforespørgsel på fem forskellige steder. Hvis en af ​​dem mangler, produceres der inkonsekvent adfærd.

sql

-- Tax logic duplicated across queries
SELECT amount * 0.05 FROM invoices;
SELECT amount * 0.05 FROM payments;
SELECT amount * 0.05 FROM reports;

Ændring af 0.05 til 0.07 kræver nu, at alle forekomster på tværs af SQL-filer, lagrede procedurer og applikationskode findes.

Funktionsmisundelse

En metode, der bruger mere tid på at bruge data og metoder fra en anden klasse end sin egen. Dette signalerer, at adfærden sandsynligvis hører hjemme i den anden klasse.

Java

// 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

Død kode

Kode, der findes i repository'et, men aldrig kaldes af nogen udførelsessti i produktion. Død kode akkumuleres over år, efterhånden som funktioner fjernes, erstattes eller omstruktureres uden at slette den gamle kode. Det tilføjer støj til kodegennemgang, forvirrer udvikleres onboarding til kodebasen, komplicerer migreringsanalyse og bliver lejlighedsvis genaktiveret ved et uheld.

DetektionStatiske analyseværktøjer, herunder SonarQube, Knip (til TypeScript/JavaScript) og SMART TS XL Identificer utilgængelige funktioner, ikke-kaldte metoder og ubrugte variabler på tværs af kodebasen.

Overtrædelser af DRY-princippet

Princippet om "Don't Repeat Yourself" (DRY) siger, at al viden skal have en enkelt, utvetydig repræsentation i et system. DRY-overtrædelser er roden til duplikeret kode, dataklumper og mange "shotgun surgery"-scenarier. Når forretningslogik er repræsenteret flere steder, afviger disse repræsentationer uundgåeligt. DRY er princippet; duplikeret kode er lugten, der indikerer overtrædelsen.

python

# 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

Detektionstærskler: Hvornår bliver kode til en lugt?

Detektion af lugt i kode kræver målbare tærskler. Nedenfor er de almindeligt anvendte målinger og de værdier, der indikerer en lugt, der kræver opmærksomhed:

metricHvad det målerAdvarselstærskelKritisk tærskel
Cyklomatisk kompleksitetAntal beslutningsgrene i en metodeOver 10Over 20
Metodelængde (linjer)Antal linjer i en metode/funktionOver 20Over 50
ParametertællingAntal parametre, som en metode acceptererOver 4Over 7
Klassens længdeAntal linjer i en klasseOver 200Over 500
DuplikeringsrateProcentdel af kode, der er duplikeretOver 3%Over 10%
Kognitiv kompleksitetHvor svær koden er at forståOver 15Over 25
Afferent kobling (Ca)Antal klasser, der er afhængige af denne klasseOver 15Over 30
Efferent kobling (Ce)Antallet af klasser, som denne klasse afhænger afOver 15Over 30

Disse tærskler kan konfigureres i SonarQube, og de fleste statiske analyseplatforme tillader brugerdefinerede regler baseret på disse metrikker. Klasserne og metoderne med kritisk tærskel er de refaktoreringsmål med højest prioritet: de er de mest sandsynlige kilder til fremtidige defekter og de dyreste komponenter at vedligeholde.

Værktøjer til detektion af kodelugt

Automatiseret detektion er den eneste skalerbare tilgang til at identificere kodelugt på tværs af store kodebaser. Manuel gennemgang fanger kun en brøkdel af, hvad automatiserede værktøjer finder, og manuel gennemgang skalerer ikke til ældre systemer med millioner af linjer.

VærktøjPrimært sprogHvad det registrerer
SonarQube / SonarCloudJava, Python, JS/TS, C# og mereFuld Fowler lugttaksonomi, sikkerhedshotspots, duplikationer
Checkstyle + PMDJavaStilbrud, duplikationer, kompleksitetsmålinger
ESLint + typescript-eslintJavaScript, TypeScriptLange funktioner, kompleksitet, ubrugt kode
Pylint + RadonPythonKompleksitet, stil, vedligeholdelsesindeks
ReSharper / RytterC#Redundant kode, lange metoder, koblingsproblemer
ClippyRustIdiomatiske overtrædelser, almindelige mønstre, der lugter kode i Rust
KodeklimaMulti-sprogKompleksitet, duplikering, vedligeholdelsesscore
SMART TS XLCOBOL, JCL, Java, Python, RPG, SQL, .NETTværsproget duplikering, død kode, kobling, afhængighedsdrift

Kode lugter i rust fanges primært af Clippy, som håndhæver idiomatiske rustmønstre. De mest almindelige rustspecifikke lugte omfatter unødvendig kloning, misbrug af unwrap() i produktionsstier, overindlejrede matchudtryk og funktioner, der skal returneres Result men brug panik i stedet.

Kodelugt og teknisk gæld: Forbindelsen

Teknisk gæld er de akkumulerede omkostninger ved tidligere beslutninger, der favoriserede hastighed frem for kvalitet. Kodelugt er den mekanisme, hvorved denne gæld manifesterer sig i kodestrukturen. Forholdet er direkte: hver uadresseret kodelugt er en enhed af teknisk gæld, og renten er den ekstra tid, som enhver fremtidig ændring skal bruge på at arbejde omkring den.

Som beskrevet i forbindelse med konsekvensanalyse for softwareændringsstyring , øger de strukturelle problemer, som kodelugt indikerer, overdreven kobling, duplikeret logik og ophobning af død kode, direkte omfanget af enhver ændring, fordi de gør det sværere at isolere, hvad en given ændring vil påvirke.

Forklar teknisk gæld i form af kodelugt : Hvis en kodebase har 40% duplikering, koster hver fejlrettelse 1.4 gange så meget som den burde. Hvis kernebehandlingsklassen er en God-klasse, som alt afhænger af, kræver hver funktionstilføjelse forståelse og test af hele klassen. Hvis fejlhåndteringen er inkonsekvent, kræver hver produktionshændelse mere undersøgelsestid, fordi fejlsignalerne er upålidelige. Teknisk gæld er ikke abstrakt, det er summen af ​​disse sammensatte ineffektiviteter.

CISQ-undersøgelsen viser konsekvent, at udviklere bruger 30-40% af deres tid på at arbejde med teknisk gæld i stedet for at bygge ny funktionalitet. Kodens lugtdensitet er den mest direkte måling af, hvor meget gæld der er akkumuleret.

Hvordan SMART TS XL Registrerer kodelugt i virksomhedsskala

Individuelle værktøjer som SonarQube og Clippy fungerer i et enkelt sprog. I virksomhedsmiljøer, hvor COBOL-programmer skriver til datasæt, som Java-tjenester læser, hvor JCL-jobstrømme kalder programmer på flere sprog, og hvor den samme forretningslogik er blevet uafhængigt duplikeret på tværs af tre forskellige systemer skrevet i tre forskellige årtier, kan enkeltsprogede værktøjer ikke se det fulde billede.

SMART TS XL's statisk kodeanalyse registrerer kodelugt på tværs af alle sprog i miljøet samtidigt: duplikeret logik mellem en COBOL-kopibog og en Java-værktøjsklasse, død kode i RPG-programmer, som intet JCL-job kalder, God Class-mønstre i COBOL-programmer, hvor et enkelt afsnit udfører arbejdet for halvtreds, og inkonsistente fejlhåndteringsmønstre på tværs af grænsen mellem sprog.

Funktionen til kortlægning af applikationsafhængigheder identificerer de arkitektoniske lugte, som individuelle værktøjer på filniveau ikke kan se: hvilke komponenter har den højeste afferente kobling (mest afhængige, højeste risiko for at ødelægge ting, når de ændres), hvor der findes cirkulære afhængigheder mellem moduler, der burde være uafhængige, og hvor duplikeret forretningslogik er blevet uafhængigt vedligeholdt i forskellige systemer uden at nogen af ​​kopierne er opmærksomme på den anden.

Effektanalysefunktionen gør lugte handlingsrettede: Før refaktorering af en komponent med høj kobling opregner effektanalysen alle afhængige komponenter, der skal testes, valideres eller opdateres. Dette omdanner den "refaktoreringslammelse", som teams oplever i store, lugtende kodebaser, til et struktureret, afgrænset afhjælpningsprogram, hvor hver ændring har et defineret omfang snarere end en ukendt risiko.

For teams, der udfører ældre moderniseringsprogrammer, er kodesmugningsanalyse fundamentet for moderniseringsplanen: den døde kode elimineres, før migreringen begynder (hvilket reducerer omfanget), den duplikerede logik konsolideres i kanoniske implementeringer, de komponenter med den højeste kobling moderniseres sidst (efter at alt, der afhænger af dem, er blevet adresseret), og God Classes dekomponeres, før de konverteres til et nyt sprog, fordi konvertering af en God Class til Java producerer en God Class i Java.

Håndtering af kodelugt: En prioriteringsramme

Ikke alle lugte af kode berettiger øjeblikkelig refactoring. Den rigtige tilgang er risikobaseret prioritering:

Prioritet 1, Lugt i komponenter med høj ændringshastighed. Kode, der ændres ofte og har høj kompleksitet eller kobling, producerer flest defekter. Disse komponenter koster mest pr. ændring og genererer flest produktionshændelser. Ret disse først.

Prioritet 2, Lugter ved arkitektoniske grænser. Godklasser og komponenter med høj kobling, som alt afhænger af, er de farligste at ændre, men også de dyreste at lade være ufikserede. Disse kræver den mest omhyggelige konsekvensanalyse før refaktorering.

Prioritet 3, Duplikeret kode på tværs af systemgrænser. Når den samme forretningslogik findes i flere systemer, skal ændringer koordineres på tværs af alle kopier samtidigt. Konsolidering af denne duplikering reducerer koordineringsomkostninger og forhindrer divergens.

Prioritet 4, Fjernelse af død kode. Død kode er den sikreste kategori at håndtere: fjernelse af den kan ikke ødelægge adfærden, men afslører kun tidligere skjulte afhængigheder. Den bør fjernes før enhver migrering eller konvertering for at undgå spild af kræfter på at konvertere kode, der aldrig vil blive kaldt.

Prioritet 5, Stil- og strukturlugte i områder med lav risiko. Lange metoder og parameterlister i stabil kode med lav ændringshastighed kan håndteres opportunistisk, når nærliggende kode skal ændres af andre årsager, og samtidig omstruktureres de omgivende lugte.

Disciplinen med at detektere, måle og håndtere kodelugt systematisk, snarere end reaktivt, når en lugt allerede har forårsaget en produktionsfejl, er det, der adskiller udviklingsteams, der opretholder leveringshastigheden over tid, fra dem, der gradvist sænker tempoet, efterhånden som deres systemer vokser.