Implementirajte statičku analizu u proces, umjesto da je koristite za pronalaženje grešaka

Na pisanje ovog članka potaknula me velika količina materijala o statičkoj analizi koji mi sve više privlače pažnju. Prvo, ovo PVS-studio blog, koji se aktivno promovira na Habréu uz pomoć pregleda grešaka koje je njihov alat pronašao u projektima otvorenog koda. Nedavno implementiran PVS-studio Java podrška, i, naravno, programeri IntelliJ IDEA, čiji je ugrađeni analizator vjerovatno najnapredniji za Javu danas, nije mogao ostati po strani.

Čitajući takve kritike, imate osjećaj da govorimo o čarobnom eliksiru: pritisnite dugme, i evo ga - lista nedostataka pred vašim očima. Čini se da kako se analizatori budu poboljšavali, automatski će se pronaći sve više grešaka, a proizvodi koje skeniraju ovi roboti će postajati sve bolji i bolji, bez ikakvog napora s naše strane.

Ali ne postoje magični eliksiri. Želio bih razgovarati o tome o čemu se obično ne govori u objavama poput "evo stvari koje naš robot može pronaći": šta analizatori ne mogu učiniti, koja je njihova stvarna uloga i mjesto u procesu isporuke softvera i kako ih pravilno implementirati .

Implementirajte statičku analizu u proces, umjesto da je koristite za pronalaženje grešaka
Ratchet (izvor: Wikipedia).

Ono što statički analizatori nikada ne mogu učiniti

Šta je analiza izvornog koda, sa praktične tačke gledišta? Dajemo neki izvorni kod kao ulaz, a kao izlaz, u kratkom vremenu (mnogo kraćem od pokretanja testova) dobijamo neke informacije o našem sistemu. Osnovno i matematički nepremostivo ograničenje je da na ovaj način možemo dobiti samo prilično usku klasu informacija.

Najpoznatiji primjer problema koji se ne može riješiti statičkom analizom je problem isključivanja: Ovo je teorema koja dokazuje da je nemoguće razviti opći algoritam koji može odrediti iz izvornog koda programa da li će se zapetljati ili završiti u konačnom vremenu. Proširenje ove teoreme je Rajsova teorema, koji kaže da je za bilo koje netrivijalno svojstvo izračunljivih funkcija određivanje da li proizvoljni program procjenjuje funkciju s takvim svojstvom algoritamski nerješiv problem. Na primjer, nemoguće je napisati analizator koji iz bilo kojeg izvornog koda može odrediti da li je program koji se analizira implementacija algoritma koji izračunava, recimo, kvadrat cijelog broja.

Dakle, funkcionalnost statičkih analizatora ima nepremostiva ograničenja. Statički analizator nikada neće moći otkriti u svim slučajevima takve stvari kao što je, na primjer, pojava "null pokazivača izuzetka" u jezicima koji dozvoljavaju vrijednost null, ili u svim slučajevima da odredi pojavu " atribut nije pronađen" u dinamički upisanim jezicima. Sve što najnapredniji statički analizator može je da istakne posebne slučajeve, čiji je broj, među svim mogućim problemima sa vašim izvornim kodom, bez pretjerivanja kap u moru.

Statička analiza nije u pronalaženju grešaka

Iz navedenog slijedi zaključak: statička analiza nije sredstvo za smanjenje broja nedostataka u programu. Usudio bih se reći: kada se prvi put primjenjuje na vaš projekat, naći će "zanimljiva" mjesta u kodu, ali, najvjerovatnije, neće pronaći nikakve nedostatke koji utiču na kvalitet vašeg programa.

Primeri grešaka koje automatski pronađu analizatori su impresivni, ali ne treba zaboraviti da su ovi primeri pronađeni skeniranjem velikog skupa velikih kodnih baza. Po istom principu, hakeri koji imaju priliku isprobati nekoliko jednostavnih lozinki na velikom broju naloga na kraju pronađu one naloge koji imaju jednostavnu lozinku.

Znači li to da se statička analiza ne smije koristiti? Naravno da ne! I iz potpuno istog razloga zbog kojeg je vrijedno provjeriti svaku novu lozinku kako biste bili sigurni da je uključena u stop listu „jednostavnih“ lozinki.

Statička analiza je više od pronalaženja grešaka

Zapravo, problemi koji se praktično rješavaju analizom su mnogo širi. Na kraju krajeva, općenito, statička analiza je svaka provjera izvornog koda prije nego što se pokrene. Evo nekoliko stvari koje možete učiniti:

  • Provjera stila kodiranja u najširem smislu riječi. Ovo uključuje i provjeru formatiranja, traženje upotrebe praznih/dodatnih zagrada, postavljanje pragova za metriku kao što je broj linija/ciklomatska složenost metode, itd. – sve što potencijalno ometa čitljivost i održivost koda. U Javi je takav alat Checkstyle, u Pythonu - flake8. Programi ove klase se obično nazivaju "linteri".
  • Ne može se analizirati samo izvršni kod. Datoteke resursa kao što su JSON, YAML, XML, .properties mogu (i treba!) biti automatski provjerene za validnost. Na kraju krajeva, bolje je otkriti da je JSON struktura pokvarena zbog nekih neuparenih citata u ranoj fazi automatske verifikacije Pull Request-a nego tokom izvršavanja testa ili vremena izvođenja? Dostupni su odgovarajući alati: npr. YAMLlint, JSONLint.
  • Kompilacija (ili raščlanjivanje za dinamičke programske jezike) je takođe vrsta statičke analize. Općenito, kompajleri su sposobni proizvesti upozorenja koja ukazuju na probleme s kvalitetom izvornog koda i ne treba ih zanemariti.
  • Ponekad je kompilacija više od samog kompajliranja izvršnog koda. Na primjer, ako imate dokumentaciju u formatu AsciiDoctor, a zatim u trenutku pretvaranja u HTML/PDF obrađivač AsciiDoctor (Maven dodatak) može izdati upozorenja, na primjer, o prekinutim internim vezama. I to je dobar razlog da ne prihvatite zahtjev za povlačenjem sa izmjenama dokumentacije.
  • Provjera pravopisa je također vrsta statičke analize. Utility aspell može provjeriti pravopis ne samo u dokumentaciji, već iu izvornim kodovima programa (komentari i literali) u različitim programskim jezicima, uključujući C/C++, Java i Python. Pravopisna greška u korisničkom interfejsu ili dokumentaciji je takođe nedostatak!
  • Testovi konfiguracije (o tome šta su - vidi. ovo и ovo izveštaji), iako se izvršavaju u jediničnom testu kao što je pytest, u stvari su takođe vrsta statičke analize, pošto ne izvršavaju izvorne kodove tokom njihovog izvršavanja.

Kao što vidite, traženje grešaka na ovoj listi igra najmanje važnu ulogu, a sve ostalo je dostupno korištenjem besplatnih alata otvorenog koda.

Koju od ovih vrsta statičke analize trebate koristiti u svom projektu? Naravno, što više to bolje! Glavna stvar je da se to ispravno implementira, o čemu će se dalje raspravljati.

Cjevovod isporuke kao višestepeni filter i statička analiza kao njegova prva faza

Klasična metafora za kontinuiranu integraciju je cjevovod kroz koji teku promjene, od promjena izvornog koda do isporuke do proizvodnje. Standardni slijed faza u ovom cevovodu izgleda ovako:

  1. statička analiza
  2. kompilâciâ
  3. jedinični testovi
  4. integracioni testovi
  5. UI testovi
  6. ručna provjera

Promjene odbijene u N-oj fazi cjevovoda ne prenose se u fazu N+1.

Zašto baš na ovaj način, a ne drugačije? U testnom dijelu cevovoda, testeri će prepoznati dobro poznatu piramidu testiranja.

Implementirajte statičku analizu u proces, umjesto da je koristite za pronalaženje grešaka
Test piramida. Izvor: članak Martin Fowler.

Na dnu ove piramide nalaze se testovi koji su lakši za pisanje, brži za izvođenje i koji nemaju tendenciju neuspjeha. Stoga bi ih trebalo biti više, trebalo bi da pokrivaju više koda i da se prvi izvrše. Na vrhu piramide je suprotno, pa broj integracijskih i UI testova treba svesti na potreban minimum. Osoba u ovom lancu je najskuplji, spori i najnepouzdaniji resurs, tako da je na samom kraju i obavlja posao samo ako u prethodnim fazama nisu pronađeni nedostaci. Međutim, isti principi se koriste za izgradnju cjevovoda u dijelovima koji nisu direktno povezani s testiranjem!

Želio bih ponuditi analogiju u obliku višestepenog sistema za filtriranje vode. Na ulaz se dovodi prljava voda (promjene s defektima), a na izlazu moramo dobiti čistu vodu u kojoj su eliminirane sve neželjene kontaminante.

Implementirajte statičku analizu u proces, umjesto da je koristite za pronalaženje grešaka
Višestepeni filter. Izvor: Wikimedia Commons

Kao što znate, filteri za čišćenje su dizajnirani tako da svaka naredna kaskada može filtrirati sve finiji dio zagađivača. U isto vrijeme, kaskade grubljeg prečišćavanja imaju veći protok i nižu cijenu. U našoj analogiji, to znači da su kapije ulaznog kvaliteta brže, zahtijevaju manje napora za pokretanje i same su nepretenciozne u radu - a to je redoslijed u kojem se grade. Uloga statičke analize, koja je, kako sada razumijemo, sposobna da otkloni samo najveće nedostatke, je uloga „blatne“ mreže na samom početku kaskade filtera.

Statička analiza sama po sebi ne poboljšava kvalitetu finalnog proizvoda, kao što „filter blata“ ne čini vodu pitkom. Pa ipak, u kombinaciji sa drugim elementima gasovoda, njegova važnost je očigledna. Iako su u višestepenom filteru izlazni stupnjevi potencijalno sposobni uhvatiti sve što ulazni stupnjevi rade, jasno je kakve će posljedice proizaći iz pokušaja da se zadovolji samo faze finog pročišćavanja, bez ulaznih stupnjeva.

Svrha "zamke za blato" je da se kaskadne kaskada oslobodi od hvatanja vrlo velikih defekata. Na primjer, u najmanju ruku, osoba koja radi pregled koda ne bi trebala biti ometana pogrešno formatiranim kodom i kršenjem utvrđenih standarda kodiranja (kao što su dodatne zagrade ili suviše duboko ugniježđene grane). Bugove poput NPE-a treba uhvatiti jediničnim testovima, ali ako nam analizator i prije testiranja ukaže da će se greška dogoditi, to će značajno ubrzati njeno popravljanje.

Vjerujem da je sada jasno zašto statička analiza ne poboljšava kvalitetu proizvoda ako se koristi povremeno, i treba je koristiti stalno za filtriranje promjena s grubim nedostacima. Pitanje da li će upotreba statičkog analizatora poboljšati kvalitet vašeg proizvoda otprilike je ekvivalentno postavljanju pitanja: „Hoće li voda uzeta iz prljavog ribnjaka biti poboljšana u kvalitetu pića ako se prođe kroz cjedilo?“

Implementacija u naslijeđeni projekat

Važno praktično pitanje: kako implementirati statičku analizu u kontinuirani proces integracije kao „kapija kvaliteta“? U slučaju automatskih testova, sve je očigledno: postoji skup testova, neuspjeh bilo kojeg od njih dovoljan je razlog da se vjeruje da sklop nije prošao kapiju kvalitete. Pokušaj instaliranja kapije na isti način na osnovu rezultata statičke analize ne uspijeva: previše je upozorenja analize u naslijeđenom kodu, ne želite ih potpuno zanemariti, ali je također nemoguće zaustaviti isporuku proizvoda samo zato što sadrži upozorenja analizatora.

Kada se koristi po prvi put, analizator proizvodi ogroman broj upozorenja na bilo koji projekt, od kojih velika većina nije povezana s ispravnim funkcioniranjem proizvoda. Nemoguće je ispraviti sve ove komentare odjednom, a mnogi nisu ni potrebni. Uostalom, znamo da naš proizvod u cjelini funkcionira, čak i prije uvođenja statičke analize!

Kao rezultat toga, mnogi su ograničeni na povremenu upotrebu statičke analize ili je koriste samo u informacionom režimu, kada se izveštaj analizatora jednostavno izdaje tokom sklapanja. Ovo je ekvivalentno izostanku bilo kakve analize, jer ako već imamo mnogo upozorenja, onda pojava još jednog (ma koliko ozbiljnog) prilikom promjene koda ostaje neprimijećena.

Poznate su sljedeće metode uvođenja kvalitetnih kapija:

  • Postavljanje ograničenja na ukupan broj upozorenja ili broj upozorenja podijeljen s brojem redova koda. Ovo loše funkcionira, jer takva kapija slobodno propušta promjene s novim defektima, sve dok njihova granica nije prekoračena.
  • Popravljanje, u određenom trenutku, sva stara upozorenja u kodu kao zanemarena, i odbijanje izgradnje kada se pojave nova upozorenja. Ovu funkcionalnost pruža PVS-studio i neki online resursi, na primjer, Codacy. Nisam imao priliku da radim u PVS-studio, što se tiče mog iskustva sa Codacyjem, njihov glavni problem je što je određivanje šta je "stara" a šta "nova" greška prilično složen algoritam koji ne radi uvek ispravno, posebno ako su datoteke jako modificirane ili preimenovane. Prema mom iskustvu, Codacy je mogao zanemariti nova upozorenja u zahtjevu za povlačenjem, a istovremeno ne proći pull zahtjev zbog upozorenja koja nisu bila vezana za promjene u kodu datog PR-a.
  • Po mom mišljenju, najefikasnije rješenje je ono opisano u knjizi Kontinuirana dostava “metoda čegrtanja”. Osnovna ideja je da je broj upozorenja statičke analize svojstvo svakog izdanja, a dozvoljene su samo promjene koje ne povećavaju ukupan broj upozorenja.

Ratchet

Funkcioniše ovako:

  1. U početnoj fazi se u metapodacima pravi zapis o izdavanju broja upozorenja u kodu koji su pronašli analizatori. Dakle, kada gradite uzvodno, vaš upravitelj spremišta ne piše samo "izdanje 7.0.2", već "izdanje 7.0.2 koje sadrži 100500 upozorenja o stilu provjere." Ako koristite napredni upravitelj spremišta (kao što je Artifactory), pohranjivanje takvih metapodataka o vašem izdanju je jednostavno.
  2. Sada svaki zahtjev za povlačenjem, kada je izgrađen, uspoređuje broj rezultirajućih upozorenja s brojem upozorenja dostupnih u trenutnom izdanju. Ako PR dovede do povećanja ovog broja, onda kod ne prolazi kapiju kvalitete za statičku analizu. Ako se broj upozorenja smanji ili se ne promijeni, onda prolazi.
  3. Prilikom sljedećeg izdanja, ponovo izračunati broj upozorenja će biti ponovo zabilježen u metapodacima izdanja.

Tako malo po malo, ali postojano (kao kada čegrtaljka radi), broj upozorenja će težiti nuli. Naravno, sistem se može prevariti uvođenjem novog upozorenja, ali ispravljanjem tuđeg. To je normalno, jer na velikoj udaljenosti daje rezultate: upozorenja se po pravilu ne ispravljaju pojedinačno, već u grupi određene vrste odjednom, a sva upozorenja koja se lako mogu ukloniti vrlo brzo se eliminiraju.

Ovaj grafikon prikazuje ukupan broj Checkstyle upozorenja za šest mjeseci rada takvog "čegrtača". jedan od naših OpenSource projekata. Broj upozorenja se smanjio za red veličine, a to se dogodilo prirodno, paralelno sa razvojem proizvoda!

Implementirajte statičku analizu u proces, umjesto da je koristite za pronalaženje grešaka

Koristim modificiranu verziju ove metode, odvojeno računajući upozorenja po modulu projekta i alatu za analizu, što rezultira YAML datotekom s metapodacima izgradnje koji izgleda otprilike ovako:

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

U svakom naprednom CI sistemu, ratchet se može implementirati za sve alate za statičku analizu bez oslanjanja na dodatke i alate trećih strana. Svaki analizator proizvodi vlastiti izvještaj u jednostavnom tekstualnom ili XML formatu koji je lako analizirati. Ostaje samo da upišete potrebnu logiku u CI skriptu. Možete vidjeti kako se to implementira u našim open source projektima baziranim na Jenkinsu i Artifactoryju ovdje ili ovdje. Oba primjera zavise od biblioteke ratchetlib: metod countWarnings() broji xml oznake u datotekama koje generiše Checkstyle i Spotbugs na uobičajen način, i compareWarningMaps() implementira istu zaporku, puštajući grešku kada se poveća broj upozorenja u bilo kojoj od kategorija.

Zanimljiva implementacija "ratchet" je moguća za analizu pravopisa komentara, tekstualnih literala i dokumentacije koristeći aspell. Kao što znate, prilikom provjere pravopisa, nisu sve riječi nepoznate standardnom rječniku netačne, mogu se dodati u korisnički rječnik. Ako napravite prilagođeni rječnik dio izvornog koda projekta, tada se kapija kvalitete pravopisa može formulirati na ovaj način: pokretanje aspell-a sa standardnim i prilagođenim rječnikom ne bi trebalo ne pronađite pravopisne greške.

O važnosti popravljanja verzije analizatora

U zaključku, poenta koju treba napomenuti je da bez obzira na to kako implementirate analizu u svoj cevovod isporuke, verzija analizatora mora biti fiksna. Ako dozvolite analizatoru da se spontano ažurira, tada pri sklapanju sljedećeg zahtjeva za povlačenjem mogu "iskočiti" novi nedostaci koji nisu povezani s promjenama koda, već su povezani s činjenicom da novi analizator jednostavno može pronaći više nedostataka - i to će prekinuti vaš proces prihvatanja zahtjeva za povlačenjem. Nadogradnja analizatora bi trebala biti svjesna akcija. Međutim, kruta fiksacija verzije svake komponente sklopa općenito je neophodan zahtjev i tema za posebnu raspravu.

nalazi

  • Statička analiza neće pronaći greške za vas i neće poboljšati kvalitet vašeg proizvoda kao rezultat jedne aplikacije. Pozitivan učinak na kvalitet može se postići jedino njegovom stalnom upotrebom u procesu isporuke.
  • Pronalaženje grešaka uopće nije glavni zadatak analize; velika većina korisnih funkcija dostupna je u alatima otvorenog koda.
  • Implementirajte kapije kvaliteta na osnovu rezultata statičke analize u samoj prvoj fazi cevovoda isporuke, koristeći „čegrtu“ za naslijeđeni kod.

reference

  1. Kontinuirana dostava
  2. A. Kudryavtsev: Analiza programa: kako shvatiti da ste dobar programer izvještaj o različitim metodama analize koda (ne samo statičke!)

izvor: www.habr.com

Dodajte komentar