Koodilõhnad: mis need on ja kuidas need on seotud tehnilise võlaga

Koodilõhnad: mis need on ja kuidas need on seotud tehnilise võlaga

Koodilõhnad ei ole vead. Vigane programm jookseb kokku, annab valesid tulemusi või ei läbi testi. Programm, millel on koodilõhnad, võib aastaid suurepäraselt töötada, kuid iga muudatus selles maksab rohkem kui peaks, iga uus funktsioon kannab ootamatut riski ja iga refaktoriseerimiskatse paljastab sõltuvusi, mille olemasolust keegi ei teadnud. Koodilõhnad on koodi struktuurilised omadused, mis ennustavad tulevasi probleeme: need ei põhjusta kohest riket, kuid muudavad iga tulevase muudatuse raskemaks, aeglasemaks ja ohtlikumaks, kui see peaks olema.

Selle termini tegid populaarseks Martin Fowler ja Kent Beck oma 1999. aasta teoses „Refactoring: Improving the Design of Existing Code “, milles kataloogiti 22 nimetatud koodilõhna ja sobitati igaüks neist vastava refaktorimistehnikaga. See kataloog on endiselt kanooniline viide ning lõhnad, mida Fowler nimetas – Long Method, God Class, Duplicated Code, Feature Envy, Divergent Change, Shotgun Surgery ja teised – esinevad tänapäeval SonarQube'i reeglistikes, staatilise analüüsi tööriistades ja koodiülevaatuse kontrollnimekirjades kogu tööstuses.

Puhastuskood lõhnab

SMART TS XL aitab neid keerukates süsteemides kaardistada ja parandada.

Rohkem infot

Mis on koodilõhn?

Koodi lõhn on lähtekoodi pinnapealne omadus, mis viitab sügavamale struktuurilisele või disainiprobleemile. Kood kompileerub, läbib testid ja annab korrektse väljundi, kuid miski selle struktuuris muudab selle lugemise, laiendamise või ohutu muutmise raskemaks, kui peaks. Fowleri definitsioon: „pinnapealne märge, mis tavaliselt vastab süsteemi sügavamale probleemile“.

Koodilõhnad ei ole samasugused rikkumised nagu süntaksiviga või ebaõnnestunud väide. Need on indikaatorid, mustrid, mida kogenud arendajad tunnevad ära hoiatusmärkidena isegi siis, kui otsest tõrget pole näha. Oht seisneb selles, et need on kumulatiivsed: üks pikk meetod 10 000-realises koodibaasis on väike ebamugavus. Sajad pikad meetodid, dubleeritud loogika kümnete moodulite vahel ja sõltuvusgraafiku keskel olevad jumalaklassid on süsteem, mida on muutunud tõeliselt raskeks ohutult muuta.

Koodilõhn vs. Vead vs. Tehniline võlg

Need kolm mõistet on omavahel seotud, kuid erinevad ning nende segi ajamine viib halva prioriseerimiseni:

MõisteMääratlusKohene ebaõnnestumine?Kuidas leida
BugKood, mis tekitab vale käitumiseJah, testid ebaõnnestuvad, kasutajad teatavad vigadestTestimine, jälgimine, vealogid
Koodi lõhnStruktuuriline muster, mis ennustab tulevasi probleemeEi, kood töötab korrektseltKoodi ülevaade, staatiline analüüs
Tehniline võlgVarasemate otseteede ja halbade otsuste kogunenud hindEi, aga aja jooksul tekivad ühendidMõõdikud, keerukusanalüüs, refaktoreerimise töömahu hinnangud

Koodilõhnad on mehhanism, mille kaudu tehniline võlg koguneb. Iga koodibaasile lisatud Long Method on tehnilise võla ühik; selle intressimakse on lisaaeg, mille iga tulevane arendaja kulutab selle mõistmisele ja iga tulevane muudatus kulutab selle suuruse kõrvalmõjude vältimisele.

Mis on koodilõhn SonarQube'is?

SonarQube liigitab koodiprobleemid kolme kategooriasse: vead (kindlasti valed), haavatavused (turvaprobleemid) ja koodilõhnad (hooldatavuse probleemid). SonarQube'i koodilõhnad on otse seotud Fowleri kataloogiga ja sisaldavad reegleid pikkade meetodite (üle konfigureeritavate rea lävede), dubleeritud plokkide, liiga paljude parameetrite, keerukate kognitiivse keerukusastmete, puuduva veakäsitluse ja arhitektuuriliste sidestusrikkumiste kohta. SonarQube'i koodilõhnareeglid on tööstuses kõige laialdasemalt kasutatav Fowleri algse taksonoomia automatiseeritud operationalisatsioon.

Martin Fowleri kood lõhnab: klassikaline taksonoomia

Fowleri algsed 22 koodilõhna, mis on kategooriatesse jaotatud, on endiselt standardviide. Iga suurema staatilise analüüsi tööriista reeglistik on tuletatud sellest taksonoomiast.

KategooriaKood lõhnab
Puhitajad, kood, mis on kasvanud kohmakaksPikk meetod, suur klass, primitiivne kinnisidee, pikk parameetrite loend, andmekogumid
Objektiorientatsiooni kuritarvitajad, objektikeskse esindatuse põhimõtete väärkasutamineLülituse laused, ajutine väli, keeldutud pärand, alternatiivsed klassid erinevate liidestega
Muutuste ennetajad, muuta muutused keeruliseksErinev muutus, haavlipüssi kirurgia, paralleelsed pärimishierarhiad
Vajalikud tooted, ebavajalik koodKommentaarid (liigsed), duplikaatkood, laisk klass, andmeklass, surnud kood, spekulatiivne üldistus
Ühendused, liigne sidumineKadedus funktsioonide üle, sobimatu intiimsus, sõnumiahelad, vahendaja

Lõhna kategooria mõistmine aitab seada tähtsuse järjekorda parandusmeetmetele: paisujad ja muutuste ennetajad on otseselt seotud kõrgete ümberehituskuludega; sidurid on otseselt seotud arhitektuurilise haprusega; mittevajalikud on kõige ohutumad eemaldada.

Kõige levinumad koodilõhnad: kiirjuhend

Koodi lõhnMilline see välja näebEsmane risk
DuplikaatkoodSama loogika esineb mitmes kohasVeaparandusi tuleb rakendada kõikjal; koopiad erinevad aja jooksul
Pikk meetodMeetodid, mis ületavad 20–30 rida ja millel on mitu vastutusalaSuur kognitiivne koormus; isoleeritud käitumist on raske testida
Jumala klass / suur klassÜks klass, mis teeb kõikeIga tunnuse muutus puudutab sama klassi; ühenda konflikte, haprust
Pikk parameetrite loendMeetodid, mis võtavad 4+ parameetritLihtne edastada valesid väärtusi; raskesti loetavad kutsekohad
Funktsioonide kadedusMeetod, mis kasutab teise klassi andmeid rohkem kui enda omasidTihe sidestus; ühe klassi muutus lõhub teise
Erinev muutusÜks klass muudeti mitmel erineval põhjuselRikub ainuvastutust; ettearvamatud kõrvalmõjud
Haavlipüssi kirurgiaÜks muudatus nõuab redigeerimist paljudes klassidesKõrge muudatuskulu; lihtne eksemplar vahele jätta
Surnud koodKood, mida kunagi ei kutsuta ega saavutataAjab arendajaid segadusse; kuhjub aastate jooksul; raskendab migratsiooni
Primitiivne kinnisideePõhitüüpide (stringid, int-id) kasutamine domeeniobjektide asemelValideerimine on laiali pillutatud; halb väljendusrikkus
Andmete kogumidSama väljade rühm edastati korduvalt koosPeaks olema domeeniobjekt; annab märku abstraktsiooni puudumisest
Spekulatiivne üldistusKujutatavate tulevikuvajaduste jaoks kirjutatud koodTarbetu keerukus; keegi ei saa aru, miks see seal on
Ebajärjekindel veakäsitlusVaiksed püüdmised, erinevad erandstrateegiadVead jäävad avastamata; veaotsing võtab palju kauem aega

Koodilõhna definitsioonid ja näited

Duplikaatkood

Kõige levinum ja kallim koodilõhn suurtes süsteemides. Dubleerimine tekib kopeerimise ja kleepimise meetodil arendamisel, ajapuuduse tõttu ja meeskondade eraldatuses töötamisel, kes lahendavad sama probleemi iseseisvalt. Selle otsene tagajärg on hooldusmaks: iga jagatud loogika muudatus tuleb rakendada igale koopiale.

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

Kui ärireegel muutub (lävi muutub 1500-ks, määr muutub 12%-ks), uuendatakse ühte eksemplari ja teist mitte. Kaks moodulit ei ole nüüd põhilise äriloogika osas kooskõlas ja lahknevus ilmneb tootmises auditi, mitte testimise käigus.

Parandus : Ekstraheeri jagatud loogika ühte funktsiooni, utiliidiklassi või jagatud teeki, millele mõlemad kutsujad viitavad.

Pikk meetod

Meetod, mis on aja jooksul lisandunud kohustuste tõttu oma algsest eesmärgist välja kasvanud. 200-realise meetodi lugemise kognitiivne koormus erineb kvalitatiivselt kahekümne kümnerealise meetodi lugemisest, mitte ainult kvantitatiivselt. Pikki meetodeid on raske testida, kuna need täidavad liiga palju asju, et neid eraldi testida, ja neid on raske mõista, kuna lugeja peab kogu teostuskonteksti töömälus hoidma.

Tuvastamislävi : Meetodid, mis on pikemad kui 20–30 rida, väärivad ülevaatamist; üle 50 rea puhul on refaktoreerimine peaaegu alati õigustatud. COBOL-is on üle 100 lausega lõigud samaväärsed.

püüton

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

Selle meetodi iga vastutusala peaks olema eraldi klass või funktsioon. Nende koondamine tähendab, et iga tulevane arvelduse, laoseisu või teadete värskendus võib destabiliseerida kogu tellimuste töötlemise voo.

Jumala klass

Klass, millel on kogunenud vastutus mitmes domeenis, rikkudes ühtse vastutuse põhimõtet nii rängalt, et sellest saab koodibaasi raskuskese: kõik sõltub sellest ja selles millegi muutmiseks on vaja kõige selle kohta aru saada.

Tuvastussignaal : klass, millel on rohkem kui 20–30 avalikku meetodit või mille nimi sisaldab terminit „Manager”, „Processor”, „Handler”, „Utils” või „Helper”, mida rakendatakse mitmele omavahel mitteseotud domeenile.

Erinev muutus

Klass, mida muudetakse paljudel erinevatel, omavahel mitteseotud põhjustel. Iga kord, kui andmebaasi skeem muutub, muudate seda klassi. Iga kord, kui hinnakujundusreeglid muutuvad, muudate seda klassi. Iga kord, kui teavitusvorming muutub, muudate seda klassi. Sellel klassil on liiga palju kohustusi ja see tuleks jagada.

Definitsioon : Üks klass, mis erinevatel põhjustel pidevalt muutub. Haavlikirurgia vastand.

Haavlipüssi kirurgia

Üks kontseptuaalne muudatus, mis nõuab redigeerimist paljudes erinevates klassides. Maksumäära muutmine nõuab tagaserveri arvutuse, esiserveri valideerimise, andmebaasi päästiku, pakktöötluse ja aruandluspäringu muutmist viies erinevas kohas. Mõne puudumine põhjustab ebajärjekindlat käitumist.

SQL

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

0.05 muutmine väärtuseks 0.07 nõuab nüüd iga esinemise leidmist SQL-failides, salvestatud protseduurides ja rakenduskoodis.

Funktsioonide kadedus

Meetod, mis kulutab rohkem aega teise klassi andmete ja meetodite kasutamisele kui oma klassi oma. See annab märku, et käitumine kuulub tõenäoliselt teise klassi.

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

Surnud kood

Kood, mis on repositooriumis olemas, kuid mida ükski teostusrada tootmises kunagi ei kutsu. Surnud kood koguneb aastate jooksul, kuna funktsioone eemaldatakse, asendatakse või restruktureeritakse ilma vana koodi kustutamata. See lisab koodi läbivaatamisele müra, ajab arendajaid koodibaasiga liitudes segadusse, raskendab migratsioonianalüüsi ja aeg-ajalt taasaktiveeritakse kogemata.

DetectionStaatilise analüüsi tööriistad, sh SonarQube, Knip (TypeScripti/JavaScripti jaoks) ja SMART TS XL Tuvastage kättesaamatud funktsioonid, kutsumata meetodid ja kasutamata muutujad kogu koodibaasis.

DRY põhimõtte rikkumised

„Ära korda ennast” (Don't Repeat Yourself ehk DRY) printsiip väidab, et igal teadmisel peab olema süsteemis üksainus ja üheselt mõistetav esitus. DRY rikkumised on dubleeritud koodi, andmekogumite ja paljude nn haavlipüssioperatsioonide stsenaariumide algpõhjuseks. Kui äriloogikat esitatakse mitmes kohas, siis need esitused lahknevad paratamatult. DRY on printsiip; dubleeritud kood on lõhn, mis viitab rikkumisele.

püüton

# 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

Tuvastusläved: millal muutub kood lõhnaks?

Koodilõhna tuvastamiseks on vaja mõõdetavaid läviväärtusi. Allpool on toodud tavaliselt kasutatavad mõõdikud ja väärtused, mis näitavad tähelepanu vajavat lõhna:

meetrilineMida see mõõdabHoiatusläviKriitiline lävi
Tsüklomaatiline keerukusOtsustusharude arv meetodis10i kohal20i kohal
Meetodi pikkus (read)Ridade arv meetodis/funktsioonis20i kohal50i kohal
Parameetrite arvMeetodi aktsepteeritavate parameetrite arv4i kohal7i kohal
Klassi pikkusRidade arv klassis200i kohal500i kohal
Paljundamise määrDubleeritud koodi protsent3% üle10% üle
Kognitiivne keerukusKui raske on koodist aru saada15i kohal25i kohal
Aferentne sidumine (Ca)Sellest klassist sõltuvate klasside arv15i kohal30i kohal
Efferentne sidestus (Ce)Klasside arv, millest see klass sõltub15i kohal30i kohal

Neid lävendeid saab SonarQube'is konfigureerida ja enamik staatilise analüüsi platvorme lubab nendel mõõdikutel põhinevaid kohandatud reegleid. Kriitilise läve klassid ja meetodid on kõrgeima prioriteediga refaktoriseerimise sihtmärgid: need on tulevaste defektide kõige tõenäolisemad allikad ja kõige kallimad komponendid hooldamiseks.

Koodilõhna tuvastamise tööriistad

Automaatne tuvastamine on ainus skaleeritav lähenemisviis koodilõhna tuvastamiseks suurtes koodibaasides. Manuaalne ülevaatus tabab vaid murdosa sellest, mida automatiseeritud tööriistad leiavad, ja käsitsi ülevaatus ei ole skaleeritav miljonite ridadega pärandsüsteemidele.

VahendPõhikeelMida see tuvastab
SonarQube / SonarCloudJava, Python, JS/TS, C# ja palju muudTäielik Fowleri lõhnade taksonoomia, turvaaukude levialad, dubleerimised
Checkstyle + PMDJavaStiilirikkumised, dubleerimised, keerukusnäitajad
ESLint + TypeScript-eslintJavaScript, TypeScriptPikad funktsioonid, keerukus, kasutamata kood
Pylint + RadoonPythonKeerukuse, stiili ja hooldatavuse indeks
ReSharper / RiderC#Üleliigne kood, pikad meetodid, sidumisprobleemid
ClippyRustIdiomaatilised rikkumised, tavalised mustrid, mis lõhnavad Rustis koodina
CodeClimateMitme keeleKeerukuse, dubleerimise ja hooldatavuse skoor
SMART TS XLCOBOL, JCL, Java, Python, RPG, SQL, .NETKeeltevaheline dubleerimine, surnud kood, sidestamine, sõltuvuste triiv

Kood lõhnab roostes püütakse kinni peamiselt Clippy abil, mis sunnib peale idioomaatseid roostemustreid. Kõige levinumad roostele omased lõhnad hõlmavad tarbetut kloonimist, väärkasutamist unwrap() tootmisradadel, üleliia pesastatud vasteavaldised ja funktsioonid, mis peaksid tagastama Result aga kasutage selle asemel paanikat.

Koodilõhnad ja tehniline võlg: seos

Tehniline võlg on varasemate otsuste akumuleerunud kulu, mis eelistasid kiirust kvaliteedile. Koodilõhnad on mehhanism, mille abil see võlg koodistruktuuris avaldub. Seos on otsene: iga lahendamata koodilõhn on tehnilise võla ühik ja intressimäär on lisaaeg, mille iga tulevane muudatus peab selle ümber töötamiseks kulutama.

Nagu tarkvaramuudatuste haldamise mõjuanalüüsi kontekstis kirjeldatud , suurendavad koodilõhnaga seotud struktuuriprobleemid – liigne sidumine, dubleeritud loogika ja surnud koodi kuhjumine – otseselt iga muudatuse ulatust, kuna need raskendavad iga muudatuse mõju eristamist.

Selgitage tehnilist võlga koodilõhnade abil : kui koodibaasis on 40% dubleerimist, maksab iga veaparandus 1.4 korda rohkem, kui see peaks maksma. Kui põhitöötlusklass on jumalklass, millest kõik sõltub, nõuab iga funktsiooni lisamine kogu klassi mõistmist ja testimist. Kui veakäsitlus on ebajärjekindel, nõuab iga tootmisjuhtum rohkem uurimisaega, kuna rikkesignaalid on ebausaldusväärsed. Tehniline võlg ei ole abstraktne, vaid nende liiteefektiivsuse summa.

CISQ uuring näitab järjepidevalt, et arendajad kulutavad 30–40% oma ajast tehnilise võla kallal töötamisele, mitte uue funktsionaalsuse loomisele. Koodi lõhna tihedus on kõige otsesem mõõt kogunenud võla suuruse kohta.

Kuidas SMART TS XL Tuvastab koodilõhnad ettevõtte tasandil

Üksikud tööriistad, näiteks SonarQube ja Clippy, töötavad ühe keele piires. Ettevõttekeskkondades, kus COBOL-programmid kirjutavad andmekogumitesse, mida Java-teenused loevad, kus JCL-töövood käivitavad programme mitmes keeles ja kus sama äriloogika on iseseisvalt dubleeritud kolmes erinevas süsteemis, mis on kirjutatud kolmel erineval aastakümnel, ei näe ühekeelsed tööriistad täit pilti.

SMART TS XL'S staatilise koodi analüüs tuvastab koodilõhnasid samaaegselt kõigis keskkonnas olevates keeltes: dubleeritud loogika COBOL-i käsiraamatu ja Java utiliidiklassi vahel, surnud kood RPG-programmides, mida ükski JCL-töö ei kutsu esile, jumalaklassi mustrid COBOL-programmides, kus üks lõik teeb ära viiekümne töö, ja ebajärjekindlad veakäsitlusmustrid keeltevahelises piiris.

Rakenduse sõltuvuste kaardistamise funktsioon tuvastab arhitektuurilised lõhnad, mida üksikud failitaseme tööriistad ei näe: millistel komponentidel on suurim aferentne seos (kõige enam sõltuvad, suurim purunemisrisk muutmisel), kus moodulite vahel, mis peaksid olema sõltumatud, esinevad ringsõltuvused ja kus dubleeritud äriloogikat on erinevates süsteemides iseseisvalt hoitud ilma, et kumbki koopia oleks teisest teadlik.

Mõjuanalüüsi võimalus muudab lõhnad rakendatavaks: enne mis tahes suure sidestusteguriga komponendi refaktoriseerimist loetleb mõjuanalüüs kõik sõltuva komponendi, mida tuleb testida, valideerida või uuendada. See muudab suurtes haisvates koodibaasides meeskondade kogetava „refaktoriseerimisparalüüsi“ struktureeritud ja ulatusega parandusprogrammiks, kus igal muudatusel on määratletud ulatus, mitte tundmatu risk.

Meeskondade jaoks, kes viivad läbi päranduseks muutunud moderniseerimisprogramme, on koodi lõhnaanalüüs moderniseerimisplaani aluseks: surnud kood elimineeritakse enne migratsiooni algust (vähendades ulatust), dubleeritud loogika koondatakse kanoonilisteks implementatsioonideks, kõige suurema sidumisega komponendid moderniseeritakse viimasena (pärast seda, kui kõik neist sõltuv on lahendatud) ja jumalaklassid lagundatakse enne uude keelde teisendamist, sest jumalaklassi Java keelde teisendamine loob jumalaklassi Javas.

Koodilõhnadega tegelemine: prioriseerimise raamistik

Mitte kõik koodilõhnad ei õigusta kohest refaktoreerimist. Õige lähenemisviis on riskipõhine prioriseerimine:

Prioriteet 1, lõhn kiirelt muutuvate komponentide järele. Sageli muutuv ja keerukas või sidestusega kood tekitab kõige rohkem defekte. Need komponendid maksavad iga muudatuse kohta kõige rohkem ja tekitavad kõige rohkem tootmisintsidente. Parandage need kõigepealt.

Prioriteet 2, haiseb arhitektuuripiiridel. Kõige ohtlikumad on muuta jumalklassid ja suure seosega komponendid, millest kõik sõltub, aga ka kõige kallimad on parandamata jätta. Need nõuavad enne refaktoreerimist kõige hoolikamat mõjuanalüüsi.

Prioriteet 3, dubleeritud kood üle süsteemipiiride. Kui sama äriloogika eksisteerib mitmes süsteemis, tuleb muudatusi kõigis koopiates samaaegselt koordineerida. Selle dubleerimise konsolideerimine vähendab koordineerimiskulusid ja hoiab ära lahknevused.

Prioriteet 4, surnud koodi eemaldamine. Surnud kood on kõige turvalisem kategooria, millega tegeleda: selle eemaldamine ei saa käitumist muuta, vaid ainult paljastab eelnevalt peidetud sõltuvused. See tuleks enne mis tahes migreerimist või teisendamist eemaldada, et vältida raisatud pingutust koodi teisendamisel, mida kunagi ei kutsuta.

Prioriteet 5, stiili ja struktuuri lõhnad madala riskiga piirkondades. Stabiilse ja madala muutumiskiirusega koodi pikki meetodeid ja parameetrite loendeid saab lahendada oportunistlikult, kui lähedalasuvat koodi on vaja muudel põhjustel muuta, ja samal ajal ümbritsevaid lõhnu ümber faktoriseerida.

Koodilõhnade süstemaatiline tuvastamine, mõõtmine ja käsitlemine, mitte reageeriv tegutsemine, kui lõhn on juba tootmistõrke põhjustanud, eristab arendusmeeskondi, kes säilitavad edastuskiiruse aja jooksul, neist, kelle süsteemide kasvades järk-järgult aeglustub.