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 infotMis 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õiste | Määratlus | Kohene ebaõnnestumine? | Kuidas leida |
|---|---|---|---|
| Bug | Kood, mis tekitab vale käitumise | Jah, testid ebaõnnestuvad, kasutajad teatavad vigadest | Testimine, jälgimine, vealogid |
| Koodi lõhn | Struktuuriline muster, mis ennustab tulevasi probleeme | Ei, kood töötab korrektselt | Koodi ülevaade, staatiline analüüs |
| Tehniline võlg | Varasemate otseteede ja halbade otsuste kogunenud hind | Ei, aga aja jooksul tekivad ühendid | Mõõ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.
| Kategooria | Kood lõhnab |
|---|---|
| Puhitajad, kood, mis on kasvanud kohmakaks | Pikk meetod, suur klass, primitiivne kinnisidee, pikk parameetrite loend, andmekogumid |
| Objektiorientatsiooni kuritarvitajad, objektikeskse esindatuse põhimõtete väärkasutamine | Lülituse laused, ajutine väli, keeldutud pärand, alternatiivsed klassid erinevate liidestega |
| Muutuste ennetajad, muuta muutused keeruliseks | Erinev muutus, haavlipüssi kirurgia, paralleelsed pärimishierarhiad |
| Vajalikud tooted, ebavajalik kood | Kommentaarid (liigsed), duplikaatkood, laisk klass, andmeklass, surnud kood, spekulatiivne üldistus |
| Ühendused, liigne sidumine | Kadedus 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õhn | Milline see välja näeb | Esmane risk |
|---|---|---|
| Duplikaatkood | Sama loogika esineb mitmes kohas | Veaparandusi tuleb rakendada kõikjal; koopiad erinevad aja jooksul |
| Pikk meetod | Meetodid, mis ületavad 20–30 rida ja millel on mitu vastutusala | Suur kognitiivne koormus; isoleeritud käitumist on raske testida |
| Jumala klass / suur klass | Üks klass, mis teeb kõike | Iga tunnuse muutus puudutab sama klassi; ühenda konflikte, haprust |
| Pikk parameetrite loend | Meetodid, mis võtavad 4+ parameetrit | Lihtne edastada valesid väärtusi; raskesti loetavad kutsekohad |
| Funktsioonide kadedus | Meetod, mis kasutab teise klassi andmeid rohkem kui enda omasid | Tihe sidestus; ühe klassi muutus lõhub teise |
| Erinev muutus | Üks klass muudeti mitmel erineval põhjusel | Rikub ainuvastutust; ettearvamatud kõrvalmõjud |
| Haavlipüssi kirurgia | Üks muudatus nõuab redigeerimist paljudes klassides | Kõrge muudatuskulu; lihtne eksemplar vahele jätta |
| Surnud kood | Kood, mida kunagi ei kutsuta ega saavutata | Ajab arendajaid segadusse; kuhjub aastate jooksul; raskendab migratsiooni |
| Primitiivne kinnisidee | Põhitüüpide (stringid, int-id) kasutamine domeeniobjektide asemel | Valideerimine on laiali pillutatud; halb väljendusrikkus |
| Andmete kogumid | Sama väljade rühm edastati korduvalt koos | Peaks olema domeeniobjekt; annab märku abstraktsiooni puudumisest |
| Spekulatiivne üldistus | Kujutatavate tulevikuvajaduste jaoks kirjutatud kood | Tarbetu keerukus; keegi ei saa aru, miks see seal on |
| Ebajärjekindel veakäsitlus | Vaiksed püüdmised, erinevad erandstrateegiad | Vead 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:
| meetriline | Mida see mõõdab | Hoiatuslävi | Kriitiline lävi |
|---|---|---|---|
| Tsüklomaatiline keerukus | Otsustusharude arv meetodis | 10i kohal | 20i kohal |
| Meetodi pikkus (read) | Ridade arv meetodis/funktsioonis | 20i kohal | 50i kohal |
| Parameetrite arv | Meetodi aktsepteeritavate parameetrite arv | 4i kohal | 7i kohal |
| Klassi pikkus | Ridade arv klassis | 200i kohal | 500i kohal |
| Paljundamise määr | Dubleeritud koodi protsent | 3% üle | 10% üle |
| Kognitiivne keerukus | Kui raske on koodist aru saada | 15i kohal | 25i kohal |
| Aferentne sidumine (Ca) | Sellest klassist sõltuvate klasside arv | 15i kohal | 30i kohal |
| Efferentne sidestus (Ce) | Klasside arv, millest see klass sõltub | 15i kohal | 30i 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.
| Vahend | Põhikeel | Mida see tuvastab |
|---|---|---|
| SonarQube / SonarCloud | Java, Python, JS/TS, C# ja palju muud | Täielik Fowleri lõhnade taksonoomia, turvaaukude levialad, dubleerimised |
| Checkstyle + PMD | Java | Stiilirikkumised, dubleerimised, keerukusnäitajad |
| ESLint + TypeScript-eslint | JavaScript, TypeScript | Pikad funktsioonid, keerukus, kasutamata kood |
| Pylint + Radoon | Python | Keerukuse, stiili ja hooldatavuse indeks |
| ReSharper / Rider | C# | Üleliigne kood, pikad meetodid, sidumisprobleemid |
| Clippy | Rust | Idiomaatilised rikkumised, tavalised mustrid, mis lõhnavad Rustis koodina |
| CodeClimate | Mitme keele | Keerukuse, dubleerimise ja hooldatavuse skoor |
| SMART TS XL | COBOL, JCL, Java, Python, RPG, SQL, .NET | Keeltevaheline 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.