Implementirajte statično analizo v proces, namesto da z njim iščete napake

K pisanju tega članka me je spodbudilo veliko število gradiv o statični analizi, ki jih je vedno več. Najprej to PVS-studio blog, ki se aktivno promovira na Habréju s pregledi napak, ki jih je njihovo orodje odkrilo v odprtokodnih projektih. Nedavno implementiran PVS-studio Podpora za Java, in seveda razvijalci IntelliJ IDEA, katerih vgrajeni analizator je verjetno najnaprednejši za Javo danes, ni mogel ostati stran.

Ko berete takšne kritike, dobite občutek, da govorimo o čarobnem eliksirju: pritisnite gumb in tukaj je - seznam napak pred vašimi očmi. Zdi se, da bo z izboljšanjem analizatorjev samodejno vedno več hroščev in izdelki, ki jih skenirajo ti roboti, bodo postajali vse boljši, brez kakršnega koli truda z naše strani.

Toda čarobnih eliksirjev ni. Rad bi govoril o tem, o čemer se običajno ne razpravlja v objavah, kot je "tukaj so stvari, ki jih lahko najde naš robot": česa analizatorji ne morejo, kakšna je njihova dejanska vloga in mesto v procesu dostave programske opreme in kako jih pravilno implementirati.

Implementirajte statično analizo v proces, namesto da z njim iščete napake
Raglja (vir: wikipedia).

Česa statični analizatorji nikoli ne zmorejo

Kaj je s praktičnega vidika analiza izvorne kode? Vnesemo nekaj virov in v kratkem času (veliko krajšem od izvajanja testov) dobimo nekaj informacij o našem sistemu. Temeljna in matematično nepremostljiva omejitev je, da lahko tako pridobimo le precej ozek razred informacij.

Najbolj znan primer problema, ki ga ni mogoče rešiti s statično analizo, je stop problem: to je izrek, ki dokazuje, da je nemogoče razviti splošen algoritem, ki bi iz izvorne kode programa določil, ali se bo zankal ali končal v končnem času. Razširitev tega izreka je Riceov izrek, ki pravi, da je za vsako netrivialno lastnost izračunljivih funkcij ugotavljanje, ali poljuben program ovrednoti funkcijo s tako lastnostjo, algoritemsko nerešljiv problem. Na primer, nemogoče je napisati analizator, ki lahko iz katere koli izvorne kode ugotovi, ali je analizirani program implementacija algoritma, ki izračunava, recimo, kvadriranje celega števila.

Tako ima funkcionalnost statičnih analizatorjev nepremostljive omejitve. Statični analizator nikoli ne bo mogel v vseh primerih določiti takšnih stvari, kot je na primer pojav "izjeme ničelnega kazalca" v jezikih z ničelnimi vrednostmi ali v vseh primerih določiti pojav "atributa ni bilo mogoče najti" v jezikih z dinamično tipkanje. Vse, kar zmore najnaprednejši statični analizator, je izpostaviti posebne primere, katerih število je med vsemi možnimi težavami z vašo izvorno kodo brez pretiravanja kaplja v morje.

Statična analiza ni iskanje napak

Iz zgoraj navedenega sledi sklep: statična analiza ni sredstvo za zmanjšanje števila napak v programu. Upal bi si trditi, da bo ob prvi uporabi v vašem projektu našel "zabavna" mesta v kodi, vendar najverjetneje ne bo našel nobenih napak, ki bi vplivale na kakovost vašega programa.

Primeri napak, ki jih samodejno najdejo analizatorji, so impresivni, vendar ne smemo pozabiti, da so bili ti primeri odkriti s skeniranjem velikega nabora velikih kodnih baz. Po istem principu krekerji, ki lahko preizkusijo več preprostih gesel na velikem številu računov, na koncu najdejo tiste račune, ki imajo preprosto geslo.

Ali to pomeni, da statične analize ne bi smeli uporabiti? Seveda ne! In natanko iz istega razloga, zakaj je vredno preveriti vsako novo geslo, da pride na stop seznam »preprostih« gesel.

Statična analiza je več kot iskanje napak

Pravzaprav so problemi, ki jih praktično rešuje analiza, veliko širši. Konec koncev je na splošno statična analiza kakršno koli preverjanje izvornih kod, ki se izvede pred njihovim zagonom. Tukaj je nekaj stvari, ki jih lahko storite:

  • Preverjanje sloga kodiranja v najširšem pomenu besede. To vključuje preverjanje oblikovanja in iskanje uporabe praznih/dodatnih oklepajev, nastavitev pragov za metrike, kot je število vrstic/kompleksnost ciklomatske metode itd. – vse, kar potencialno naredi kodo bolj berljivo in vzdržljivo. V Javi je to orodje Checkstyle, v Pythonu pa flake8. Programi tega razreda se običajno imenujejo "linters".
  • Analizirati ni mogoče samo izvedljive kode. Datoteke virov, kot so JSON, YAML, XML, .properties, se lahko (in bi morale!) samodejno preveriti glede veljavnosti. Ali ni bolje ugotoviti, da je zaradi nekaterih neparnih narekovajev struktura JSON pokvarjena v zgodnji fazi samodejnega potrjevanja zahteve za vleko kot pri izvajanju testov ali med izvajanjem? Na voljo so ustrezna orodja: npr. YAMLlint, JSONLint.
  • Prevajanje (ali razčlenjevanje za dinamične programske jezike) je tudi neke vrste statična analiza. Prevajalniki so praviloma sposobni izdati opozorila, ki nakazujejo težave s kakovostjo izvorne kode in jih ne smemo prezreti.
  • Včasih je prevajanje več kot le prevajanje izvršljive kode. Na primer, če imate dokumentacijo v obliki AsciiDoctor, nato pa v trenutku njegove pretvorbe v HTML/PDF obdelovalnik AsciiDoctor (Vtičnik Maven) lahko izda opozorila, na primer o nedelujočih notranjih povezavah. In to je dober razlog, da ne sprejmete zahteve za vlečenje s spremembami dokumentacije.
  • Preverjanje črkovanja je tudi vrsta statične analize. Pripomoček aspell je sposoben preveriti črkovanje ne samo v dokumentaciji, temveč tudi v izvornih kodah programa (komentarji in literali) v različnih programskih jezikih, vključno s C/C++, Javo in Python. Napaka je tudi črkovalna napaka v uporabniškem vmesniku ali dokumentaciji!
  • Konfiguracijski testi (kaj je, glejte ta и ta poročila), čeprav se izvajajo v izvajalnem okolju testa enote, kot je pytest, so pravzaprav tudi nekakšna statična analiza, saj med izvajanjem ne izvajajo izvorne kode.

Kot lahko vidite, ima iskanje hroščev na tem seznamu najmanj pomembno vlogo, vse ostalo pa je na voljo z uporabo brezplačnih odprtokodnih orodij.

Katero od teh vrst statične analize bi morali uporabiti v vašem projektu? Seveda, čim več, tem bolje! Glavna stvar je, da jo pravilno izvajamo, o čemer bomo še razpravljali.

Dostavni cevovod kot večstopenjski filter in statična analiza kot njegova prva kaskada

Klasična metafora za stalno integracijo je cevovod (cevovod), po katerem tečejo spremembe – od spreminjanja izvorne kode do dostave v proizvodnjo. Standardno zaporedje stopenj tega plinovoda izgleda takole:

  1. statična analiza
  2. kompilacija
  3. enotni testi
  4. integracijski testi
  5. testi uporabniškega vmesnika
  6. ročno preverjanje

Spremembe, zavrnjene v N-ti stopnji cevovoda, se ne prenesejo na stopnjo N+1.

Zakaj ravno tako in ne drugače? V testnem delu cevovoda bodo preizkuševalci prepoznali dobro znano testno piramido.

Implementirajte statično analizo v proces, namesto da z njim iščete napake
Testna piramida. Vir: članek Martin Fowler.

Na dnu te piramide so testi, ki jih je lažje napisati, tečejo hitreje in niso lažno pozitivni. Zato bi jih moralo biti več, zajemati bi morali več kode in se izvajati prvi. Na vrhu piramide je ravno nasprotno, zato je treba število testov integracije in uporabniškega vmesnika zmanjšati na potrebni minimum. Oseba v tej verigi je najdražji, najpočasnejši in najbolj nezanesljiv vir, zato je čisto na koncu in opravlja delo le, če predhodne faze niso odkrile nobenih napak. Vendar pa je po enakih principih cevovod zgrajen v delih, ki niso neposredno povezani s testiranjem!

Rad bi ponudil analogijo v obliki večstopenjskega sistema za filtriranje vode. Umazana voda se dovaja na vhod (spremembe z okvarami), na izhodu moramo dobiti čisto vodo, v kateri so izločene vse neželene nečistoče.

Implementirajte statično analizo v proces, namesto da z njim iščete napake
Večstopenjski filter. Vir: Wikimedia Commons

Kot veste, so čistilni filtri zasnovani tako, da lahko vsaka naslednja kaskada izfiltrira vedno manjši delež onesnaževal. Hkrati imajo grobe čistilne kaskade večjo prepustnost in nižje stroške. V naši analogiji to pomeni, da so vhodna kakovostna vrata hitrejša, zahtevajo manj truda za zagon in so sama po sebi bolj nezahtevna pri delovanju - in to je točno zaporedje, v katerega so vgrajena. Vloga statične analize, ki je, kot zdaj razumemo, sposobna izločiti le največje napake, je vloga rešetke-"blata" na samem začetku kaskade filtrov.

Statična analiza sama po sebi ne izboljša kakovosti končnega izdelka, tako kot »blatna past« ne naredi vode pitne. In vendar je, tako kot pri drugih elementih tekočega traku, njegov pomen očiten. Čeprav so v večstopenjskem filtru izhodne stopnje potencialno sposobne zajeti vse enako kot vhodne stopnje, je jasno, do kakšnih posledic bo pripeljal poskus obiti samo s finimi čistilnimi stopnjami, brez vhodnih stopenj.

Namen "zbiralnika blata" je razbremeniti poznejše kaskade od zajemanja zelo velikih napak. Pregledovalca kode na primer vsaj ne smejo motiti nepravilno oblikovana koda in kršitve uveljavljenih standardov kodiranja (kot so dodatni oklepaji ali pregloboko ugnezdene veje). Napake, kot je NPE, je treba ujeti z enotnimi testi, a če nam analizator še pred preizkusom nakaže, da se napaka neizogibno mora zgoditi, bo to bistveno pospešilo njeno odpravo.

Mislim, da je zdaj jasno, zakaj statična analiza ne izboljša kakovosti izdelka, če se uporablja občasno, in jo je treba nenehno uporabljati za filtriranje sprememb z velikimi napakami. Vprašanje, ali bo uporaba statičnega analizatorja izboljšalo kakovost vašega izdelka, je približno enakovredno vprašanju "Ali se bo pitna kakovost vode, vzete iz umazanega ribnika, izboljšala, če jo spustimo skozi cedilo?"

Izvedba v podedovanem projektu

Pomembno praktično vprašanje: kako uvesti statično analizo v proces kontinuirane integracije kot "vrata kakovosti"? V primeru samodejnih testov je vse očitno: obstaja niz testov, neuspeh katerega koli od njih je zadosten razlog za domnevo, da sklop ni prestal vrat kakovosti. Poskus namestitve vrat na enak način na podlagi rezultatov statične analize ne uspe: v podedovani kodi je preveč opozoril o analizi, ki jih ne želite popolnoma prezreti, vendar je tudi nemogoče ustaviti dostavo samo zato, ker vsebuje opozorila analizatorja.

Analizator ob prvi uporabi generira ogromno število opozoril na kateremkoli projektu, velika večina pa ni povezana s pravilnim delovanjem produkta. Nemogoče je popraviti vse te komentarje naenkrat in mnogi od njih niso potrebni. Navsezadnje vemo, da naš izdelek kot celota deluje, še pred uvedbo statične analize!

Posledično se mnogi omejijo na epizodno uporabo statične analize ali pa jo uporabljajo samo v načinu obveščanja, ko se poročilo analizatorja preprosto izda med montažo. To je enakovredno odsotnosti vsakršne analize, kajti če že imamo veliko opozoril, potem pojav še enega (ne glede na to, kako resen) ob spremembi kode ostane neopažen.

Znani so naslednji načini uvajanja kakovostnih vrat:

  • Nastavi omejitev skupnega števila opozoril ali števila opozoril, deljeno s številom vrstic kode. To ne deluje dobro, saj takšna vrata prosto preskočijo spremembe z novimi napakami, dokler njihova meja ni presežena.
  • Popravljanje na neki točki, da so vsa stara opozorila v kodi prezrta, in neuspešna izgradnja, ko se pojavijo nova opozorila. To funkcionalnost zagotavlja PVS-studio in nekateri spletni viri, kot je Codacy. Nisem imel priložnosti delati v PVS-studiu, kar zadeva moje izkušnje s Codacyjem, je njihova glavna težava ta, da je definicija, kaj je "stara" in kaj "nova" napaka, precej zapleten algoritem, ki ne vedno delujejo pravilno, zlasti če so datoteke močno spremenjene ali preimenovane. Po mojem spominu je Codacy lahko preskočil nova opozorila v zahtevi za vleko in hkrati ne bi preskočil zahteve za vleko zaradi opozoril, ki niso bila povezana s spremembami kode tega PR.
  • Po mojem mnenju je v knjigi opisana najučinkovitejša rešitev Nenehna dostava "ratchet" metoda. Glavna ideja je, da je lastnost vsake izdaje število opozoril statične analize, dovoljene pa so samo spremembe, ki ne povečajo skupnega števila opozoril.

Raglja

Deluje takole:

  1. Na začetni stopnji je implementiran zapis v metapodatke izdaje o številu opozoril v kodi, ki so jih našli analizatorji. Tako, ko gradite navzgor, vaš upravitelj repozitorija ni napisan le "izdaja 7.0.2", ampak "izdaja 7.0.2, ki vsebuje 100500 opozoril Checkstyle". Če uporabljate naprednega upravitelja repozitorija (kot je Artifactory), je takšne metapodatke o vaši izdaji preprosto shraniti.
  2. Zdaj vsaka zahteva za vlečenje pri gradnji primerja število opozoril, ki jih prejme, s številom v trenutni izdaji. Če PR povzroči povečanje tega števila, potem koda ne prestane vrat kakovosti v statični analizi. Če se število opozoril zmanjša ali ne spremeni, potem gre.
  3. Pri naslednji izdaji bo preračunano število opozoril zapisano nazaj v metapodatke izdaje.

Tako bo postopoma, a vztrajno (kot pri raglji), število opozoril težilo k ničli. Seveda lahko sistem preslepimo tako, da uvedemo novo opozorilo, a popravimo tuje. To je normalno, saj na dolgi rok daje rezultat: opozorila se praviloma ne odpravijo eno za drugim, ampak takoj po skupini določene vrste, vsa opozorila, ki jih je mogoče odpraviti, pa se hitro odpravijo.

Ta graf prikazuje skupno število opozoril Checkstyle za šest mesecev delovanja takšne "raglje". enega naših odprtokodnih projektov. Število opozoril se je zmanjšalo za red velikosti in to naravno, vzporedno z razvojem izdelka!

Implementirajte statično analizo v proces, namesto da z njim iščete napake

Uporabljam spremenjeno različico te metode, pri čemer ločeno štejem opozorila glede na projektni modul in orodje za analizo, rezultat pa je datoteka YAML z metapodatki o sestavu, ki je videti nekako takole:

celesta-sql:
  checkstyle: 434
  spotbugs: 45
celesta-core:
  checkstyle: 206
  spotbugs: 13
celesta-maven-plugin:
  checkstyle: 19
  spotbugs: 0
celesta-unit:
  checkstyle: 0
  spotbugs: 0

V katerem koli naprednem sistemu CI je mogoče implementirati ragljo za katero koli orodje za statično analizo, ne da bi se zanašali na vtičnike in orodja tretjih oseb. Vsak od analizatorjev izdela svoje poročilo v preprosti obliki besedila ali XML, ki jo je enostavno razčleniti. V skriptu CI je treba registrirati samo potrebno logiko. Ogledate si lahko, kako je to implementirano v naših odprtokodnih projektih, ki temeljijo na Jenkinsu in Artifactoryju tukaj ali tukaj. Oba primera sta odvisna od knjižnice ratchetlib: metoda countWarnings() šteje oznake xml v datotekah, ki jih ustvarita Checkstyle in Spotbugs na običajen način, in compareWarningMaps() izvaja isto ragljo in vrže napako, ko se število opozoril v kateri koli kategoriji poveča.

Za analizo črkovanja komentarjev, besedilnih literalov in dokumentacije z uporabo aspela je možna zanimiva izvedba zaskoka. Kot veste, pri preverjanju črkovanja vse besede, ki jih standardni slovar ne pozna, niso napačne, lahko jih dodate v uporabniški slovar. Če naredite uporabniški slovar del izvorne kode projekta, potem lahko vrata kakovosti črkovanja oblikujete takole: izvajanje aspell s standardnim in uporabniškim slovarjem ne bi smel najti nobenih črkovalnih napak.

O pomembnosti popravka različice analizatorja

Na koncu je treba opozoriti na naslednje: ne glede na to, kako implementirate analizo v svoj cevovod dostave, mora biti različica analizatorja fiksna. Če dovolite, da se analizator spontano posodablja, se lahko pri gradnji naslednje zahteve za vleko "pojavijo" nove napake, ki niso povezane s spremembami kode, ampak so povezane z dejstvom, da lahko novi analizator preprosto najde več napak - in to bo prekinilo vaš proces sprejemanja zahtev za vleko. Nadgradnja analizatorja mora biti zavestno dejanje. Vendar pa je trdo popravljanje različice vsake komponente sklopa na splošno nujna zahteva in tema za ločeno razpravo.

Ugotovitve

  • Statična analiza vam ne bo našla napak in ne bo izboljšala kakovosti vašega izdelka kot rezultat ene same aplikacije. Edini pozitiven učinek na kakovost je njegova stalna uporaba v procesu dostave.
  • Iskanje hroščev sploh ni glavna naloga analize, velika večina uporabnih funkcij je na voljo v odprtokodnih orodjih.
  • Implementirajte vrata kakovosti na podlagi rezultatov statične analize na prvi stopnji dostavnega cevovoda z uporabo zaskočne kode za podedovano kodo.

reference

  1. Nenehna dostava
  2. A. Kudryavtsev: Analiza programa: kako razumeti, da ste dober programer poročilo o različnih metodah analize kode (ne samo statične!)

Vir: www.habr.com

Dodaj komentar