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

Na pisanje ovog članka ponukala me velika količina materijala o statičkoj analizi koji mi sve više privlače pozornost. Prvo, ovo PVS-studio blog, koji se aktivno promovira na Habréu uz pomoć recenzija 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 vjerojatno najnapredniji za Javu danas, nije mogao ostati podalje.

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

Ali nema čarobnih eliksira. Želio bih razgovarati o onome o čemu se obično ne govori u postovima poput "evo stvari koje naš robot može pronaći": što analizatori ne mogu, 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

Što je analiza izvornog koda, s praktičnog gledišta? Dajemo izvorni kod kao ulaz, a kao izlaz, u kratkom vremenu (mnogo kraćem od izvođenja testova) dobivamo neke informacije o našem sustavu. Temeljno 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 pomoću statičke analize je problem isključivanja: Ovo je teorem koji dokazuje da je nemoguće razviti opći algoritam koji može odrediti iz izvornog koda programa hoće li se on petljati ili prekinuti u konačnom vremenu. Proširenje ovog teoreme je Riceov teorem, koji navodi da je za bilo koje netrivijalno svojstvo izračunljivih funkcija, određivanje da li proizvoljni program vrednuje funkciju s takvim svojstvom algoritamski nerješiv problem. Na primjer, nemoguće je napisati analizator koji iz bilo kojeg izvornog koda može odrediti je li program koji se analizira implementacija algoritma koji izračunava, recimo, kvadriranje cijelog broja.

Dakle, funkcionalnost statičkih analizatora ima nepremostiva ograničenja. Statički analizator nikada neće moći otkriti u svim slučajevima stvari kao što je, na primjer, pojava "iznimke nultog pokazivača" u jezicima koji dopuštaju vrijednost null, ili u svim slučajevima utvrditi pojavu " atribut nije pronađen" u dinamički upisanim jezicima. Sve što najnapredniji statički analizator može učiniti je istaknuti posebne slučajeve, čiji je broj, među svim mogućim problemima s vašim izvornim kodom, bez pretjerivanja kap prelila čašu.

Statička analiza nije pronalaženje 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 primijeni na vaš projekt, pronaći će "zanimljiva" mjesta u kodu, ali najvjerojatnije neće pronaći nikakve nedostatke koji utječu na kvalitetu vašeg programa.

Primjeri nedostataka koje analizatori automatski pronalaze su impresivni, ali ne treba zaboraviti da su ti primjeri pronađeni skeniranjem velikog skupa velikih baza kodova. Po istom principu, hakeri koji imaju priliku isprobati nekoliko jednostavnih lozinki na velikom broju računa na kraju pronađu one račune koji imaju jednostavnu lozinku.

Znači li to da se statička analiza ne smije koristiti? Naravno da ne! I upravo iz istog razloga vrijedi provjeriti svaku novu zaporku kako biste bili sigurni da je uključena u stop listu "jednostavnih" zaporki.

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

Zapravo, problemi koji se praktično rješavaju analizom mnogo su širi. Uostalom, općenito, statička analiza je svaka provjera izvornih kodova koja se provodi prije njihovog pokretanja. Evo nekih stvari koje možete učiniti:

  • Provjera stila kodiranja u najširem smislu riječi. To uključuje i provjeru formatiranja, traženje korištenja praznih/dodatnih zagrada, postavljanje pragova za metriku kao što je broj redaka/ciklomatska složenost metode itd. - sve što potencijalno ometa čitljivost i mogućnost održavanja koda. U Javi je takav alat Checkstyle, u Pythonu - flake8. Programi ove klase obično se nazivaju "linters".
  • Ne može se analizirati samo izvršni kod. Datoteke resursa kao što su JSON, YAML, XML, .properties mogu se (i trebaju!) automatski provjeravati na valjanost. Uostalom, bolje je otkriti da je JSON struktura pokvarena zbog nekih neuparenih navodnika u ranoj fazi automatske provjere Pull Requesta nego tijekom izvođenja testa ili vremena izvođenja? Dostupni su odgovarajući alati: npr. YAMLlint, JSONLint.
  • Kompilacija (ili raščlanjivanje za dinamičke programske jezike) također je vrsta statičke analize. Općenito, prevoditelji su sposobni proizvesti upozorenja koja ukazuju na probleme s kvalitetom izvornog koda i ne bi ih se trebalo zanemariti.
  • Ponekad je kompilacija više od pukog kompajliranja izvršnog koda. Na primjer, ako imate dokumentaciju u formatu AsciiDoctor, zatim u trenutku pretvaranja u HTML/PDF rukovatelj AsciiDoctor (Dodatak Maven) može izdati upozorenja, na primjer, o neispravnim internim poveznicama. A ovo je dobar razlog da ne prihvatite Zahtjev za povlačenje s izmjenama dokumentacije.
  • Provjera pravopisa također je vrsta statičke analize. Korisnost čarolija 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++, Javu i Python. Pravopisna pogreška u korisničkom sučelju ili dokumentaciji također je nedostatak!
  • Konfiguracijski testovi (o tome što jesu - pogledajte. ovo и ovo izvješća), iako se izvode u runtimeu jediničnog testiranja kao što je pytest, zapravo su također vrsta statičke analize, budući da ne izvršavaju izvorne kodove tijekom svog izvođenja.

Kao što vidite, traženje bugova na ovom popisu 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 pravilno ga implementirati, o čemu će se dalje raspravljati.

Isporučni cjevovod kao višestupanjski filter i statička analiza kao njegov prvi stupanj

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 cjevovodu izgleda ovako:

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

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

Zašto baš ovako, a ne drugačije? U ispitnom dijelu cjevovoda, ispitivači će prepoznati dobro poznatu piramidu testiranja.

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

Na dnu ove piramide su testovi koje je lakše napisati, brže izvršiti i koji nemaju tendenciju pada. Stoga bi ih trebalo biti više, trebali bi pokrivati ​​više koda i prvi se izvršavati. Na vrhu piramide je suprotno, pa broj integracijskih i UI testova treba svesti na nužni minimum. Osoba u tom lancu je najskuplji, spor i nepouzdan resurs, pa je na samom kraju i obavlja posao samo ako prethodnim fazama nisu pronađeni nedostaci. Međutim, isti principi se koriste za izgradnju cjevovoda u dijelovima koji nisu izravno povezani s testiranjem!

Želio bih ponuditi analogiju u obliku višestupanjskog sustava za filtriranje vode. Prljava voda (promjene s nedostacima) dovodi se na ulaz, na izlazu moramo dobiti čistu vodu, u kojoj su eliminirani svi nepoželjni kontaminanti.

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

Kao što znate, filtri za čišćenje dizajnirani su tako da svaka sljedeća kaskada može filtrirati sve finiji dio onečišćenja. Istodobno, kaskade grubljeg pročišćavanja imaju veću propusnost i nižu cijenu. U našoj analogiji, to znači da su vrata ulazne kvalitete brža, zahtijevaju manje napora za pokretanje i da su sama po sebi nepretencioznija u radu - a to je redoslijed kojim se izgrađuju. Uloga statičke analize, koja je, kako sada razumijemo, sposobna ukloniti samo najveće nedostatke, uloga je rešetke "blata" na samom početku kaskade filtera.

Statička analiza sama po sebi ne poboljšava kvalitetu konačnog proizvoda, kao što ni "filtar za blato" ne čini vodu pitkom. Pa ipak, u kombinaciji s drugim elementima cjevovoda, njegova je važnost očita. Iako su u višestupanjskom filtru izlazni stupnjevi potencijalno sposobni uhvatiti sve što rade ulazni stupnjevi, jasno je kakve će posljedice imati pokušaj da se zadovolji samo sa stupnjevima finog pročišćavanja, bez ulaznih stupnjeva.

Svrha "zamke za blato" je osloboditi sljedeće kaskade od hvatanja vrlo velikih nedostataka. Na primjer, u najmanju ruku, osoba koja radi pregled koda ne bi trebala biti ometana neispravno oblikovanim kodom i kršenjem utvrđenih standarda kodiranja (poput dodatnih zagrada ili preduboko ugniježđenih grana). Greške poput NPE-a trebale bi biti uhvaćene jediničnim testovima, ali ako nam i prije testa analizator naznači da će se greška sigurno dogoditi, to će značajno ubrzati njezino 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 velikim nedostacima. Pitanje hoće li upotreba statičkog analizatora poboljšati kvalitetu vašeg proizvoda otprilike je jednako pitanju: "Hoće li voda uzeta iz prljavog ribnjaka biti poboljšana u kvaliteti za piće ako se propusti kroz cjedilo?"

Implementacija u naslijeđeni projekt

Važno praktično pitanje: kako implementirati statičku analizu u proces kontinuirane integracije kao "vrata kvalitete"? U slučaju automatskih testova, sve je očito: postoji niz testova, neuspjeh bilo kojeg od njih dovoljan je razlog za vjerovanje da sklop nije prošao vrata kvalitete. Pokušaj instaliranja vrata na isti način na temelju rezultata statičke analize ne uspijeva: postoji previše upozorenja analize u naslijeđenom kodu, ne želite ih potpuno zanemariti, ali također je nemoguće zaustaviti isporuku proizvoda samo zato što sadrži upozorenja analizatora.

Kada se koristi prvi put, analizator proizvodi veliki broj upozorenja na bilo kojem projektu, od kojih velika većina nije povezana s ispravnim radom proizvoda. Nemoguće je ispraviti sve te komentare odjednom, a mnogi nisu ni potrebni. Uostalom, znamo da naš proizvod u cjelini radi, č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 informacijskom načinu rada, kada se izvješće analizatora jednostavno izdaje tijekom sastavljanja. To je jednako izostanku bilo kakve analize, jer ako već imamo mnogo upozorenja, onda pojava još jednog (ma koliko ozbiljnog) kod promjene koda prolazi nezapaženo.

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

  • Postavljanje ograničenja ukupnog broja upozorenja ili broja upozorenja podijeljenog s brojem redaka koda. Ovo loše funkcionira jer takva vrata slobodno propuštaju promjene s novim nedostacima, sve dok njihova granica nije prekoračena.
  • Popravljanje, u određenom trenutku, svih starih upozorenja u kodu kao zanemarenih i odbijanja izgradnje kada se pojave nova upozorenja. Ovu funkcionalnost osigurava PVS-studio i neki internetski resursi, na primjer, Codacy. Nisam imao priliku raditi u PVS studiju, što se tiče mog iskustva s Codacyjem, njihov glavni problem je što je određivanje što je "stara", a što "nova" pogreška prilično složen algoritam koji ne radi uvijek ispravno, osobito ako su datoteke jako izmijenjene ili preimenovane. Prema mom iskustvu, Codacy bi mogao ignorirati nova upozorenja u zahtjevu za povlačenjem, dok u isto vrijeme ne bi proslijedio zahtjev za povlačenje zbog upozorenja koja nisu bila povezana s promjenama u kodu određenog PR-a.
  • Po mom mišljenju, najučinkovitije rješenje je ono opisano u knjizi Kontinuirano isporuke “metoda ratchetinga”. Osnovna ideja je da je broj upozorenja statičke analize svojstvo svakog izdanja, a dopuštene su samo promjene koje ne povećavaju ukupan broj upozorenja.

Ratchet

Radi na sljedeći način:

  1. U početnoj fazi u metapodacima se bilježi broj upozorenja u kodu koje su pronašli analizatori. Dakle, kada gradite uzvodno, vaš upravitelj repozitorija 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 repozitorija (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 dovodi do povećanja ovog broja, tada kod ne prolazi vrata kvalitete za statičku analizu. Ako se broj upozorenja smanji ili se ne promijeni, onda prolazi.
  3. U sljedećem izdanju, ponovno izračunati broj upozorenja bit će ponovno zabilježen u metapodacima izdanja.

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

Ovaj grafikon prikazuje ukupan broj Checkstyle upozorenja za šest mjeseci rada takve "čegrtaljke". jedan od naših OpenSource projekata. Broj upozorenja se smanjio za red veličine, i to prirodno, paralelno s razvojem proizvoda!

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

Koristim modificiranu verziju ove metode, zasebno računajući upozorenja po projektnom modulu i alatu za analizu, što rezultira YAML datotekom s metapodacima o izgradnji koja 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 bilo kojem naprednom CI sustavu, Ratchet se može implementirati za bilo koji alat za statičku analizu bez oslanjanja na dodatke i alate trećih strana. Svaki analizator proizvodi vlastito izvješće u jednostavnom tekstualnom ili XML formatu koji je lako analizirati. Sve što preostaje je napisati potrebnu logiku u CI skriptu. Možete vidjeti kako je to implementirano u našim projektima otvorenog koda temeljenim na Jenkinsu i Artifactoryju здесь ili здесь. Oba primjera ovise o knjižnici ratchetlib: metoda countWarnings() broji xml oznake u datotekama koje generiraju Checkstyle i Spotbugs na uobičajeni način, i compareWarningMaps() implementira istu zaporku, izbacujući pogrešku kada se broj upozorenja u bilo kojoj od kategorija poveća.

Zanimljiva implementacija "ratchet" moguća je za analizu pravopisa komentara, tekstualnih literala i dokumentacije pomoću aspela. Kao što znate, prilikom provjere pravopisa nisu sve riječi nepoznate standardnom rječniku netočne; mogu se dodati u korisnički rječnik. Ako napravite prilagođeni rječnik dijelom izvornog koda projekta, tada se vrata kvalitete pravopisa mogu formulirati na sljedeći način: pokretanje aspela sa standardnim i prilagođenim rječnikom ne smije pronaći pravopisne pogreške.

O važnosti popravljanja verzije analizatora

Zaključno, važno je napomenuti da bez obzira na to kako implementirate analizu u svoj cjevovod isporuke, verzija analizatora mora biti fiksna. Ako dopustite analizatoru da se spontano ažurira, tada se prilikom sastavljanja 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 - a to će prekinuti vaš proces prihvaćanja zahtjeva za povlačenjem. Nadogradnja analizatora trebala bi biti svjesna radnja. Međutim, kruta fiksacija verzije svake komponente sklopa općenito je nužan zahtjev i tema za zasebnu raspravu.

Zaključci

  • Statička analiza neće vam pronaći greške i neće poboljšati kvalitetu vašeg proizvoda kao rezultat jedne aplikacije. Pozitivan učinak na kvalitetu može se postići samo njegovom stalnom upotrebom tijekom procesa isporuke.
  • Pronalaženje grešaka uopće nije glavni zadatak analize; velika većina korisnih funkcija dostupna je u alatima otvorenog koda.
  • Implementirajte vrata kvalitete na temelju rezultata statičke analize u samoj prvoj fazi cjevovoda isporuke, koristeći "ratchet" za naslijeđeni kod.

reference

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

Izvor: www.habr.com

Dodajte komentar