Implementujte statickú analýzu do procesu a nepoužívajte ju na hľadanie chýb

K napísaniu tohto článku ma podnietilo veľké množstvo materiálov o statickej analýze, ktoré sa čoraz viac dostávajú do mojej pozornosti. Po prvé, toto PVS-studio blog, ktorá sa na Habré aktívne propaguje pomocou recenzií chýb nájdených ich nástrojom v open source projektoch. Nedávno implementované PVS-studio podpora Javaa, samozrejme, vývojári IntelliJ IDEA, ktorých vstavaný analyzátor je dnes pravdepodobne najpokročilejší pre Javu, nemohol zostať preč.

Pri čítaní takýchto recenzií máte pocit, že hovoríme o magickom elixíre: stlačte tlačidlo a tu je - zoznam defektov pred vašimi očami. Zdá sa, že so zdokonaľovaním analyzátorov sa bude automaticky nachádzať stále viac chýb a produkty naskenované týmito robotmi budú stále lepšie a lepšie, bez akéhokoľvek úsilia z našej strany.

Ale neexistujú žiadne magické elixíry. Chcel by som hovoriť o tom, o čom sa zvyčajne nehovorí v príspevkoch typu „tu sú veci, ktoré môže náš robot nájsť“: čo analyzátory nedokážu, aká je ich skutočná úloha a miesto v procese dodávania softvéru a ako ich správne implementovať .

Implementujte statickú analýzu do procesu a nepoužívajte ju na hľadanie chýb
Ratchet (zdroj: wikipedia).

Čo statické analyzátory nikdy nedokážu

Čo je analýza zdrojového kódu z praktického hľadiska? Poskytujeme nejaký zdrojový kód ako vstup a ako výstup v krátkom čase (oveľa kratšom ako spustenie testov) získame nejaké informácie o našom systéme. Základným a matematicky neprekonateľným obmedzením je, že takto môžeme získať len dosť úzku triedu informácií.

Najznámejším príkladom problému, ktorý nemožno vyriešiť pomocou statickej analýzy, je problém s vypínaním: Toto je teorém, ktorý dokazuje, že nie je možné vyvinúť všeobecný algoritmus, ktorý by dokázal zo zdrojového kódu programu určiť, či sa program zacyklí alebo skončí v konečnom čase. Rozšírením tejto vety je Riceova veta, ktorý uvádza, že pre akúkoľvek netriviálnu vlastnosť vypočítateľných funkcií je určenie, či ľubovoľný program vyhodnocuje funkciu s takouto vlastnosťou, algoritmicky neriešiteľný problém. Napríklad nie je možné napísať analyzátor, ktorý dokáže z akéhokoľvek zdrojového kódu určiť, či je analyzovaný program implementáciou algoritmu, ktorý počíta, povedzme, druhú mocninu celého čísla.

Funkčnosť statických analyzátorov má teda neprekonateľné obmedzenia. Statický analyzátor nikdy nebude schopný vo všetkých prípadoch zistiť také veci, ako je napríklad výskyt „výnimky ukazovateľa nula“ v jazykoch, ktoré umožňujú hodnotu null, alebo vo všetkých prípadoch určiť výskyt „ atribút nenájdený" v dynamicky zadávaných jazykoch. Najpokročilejší statický analyzátor dokáže iba upozorniť na špeciálne prípady, ktorých počet spomedzi všetkých možných problémov s vaším zdrojovým kódom je bez preháňania kvapkou v mori.

Statická analýza nie je o hľadaní chýb

Z vyššie uvedeného vyplýva záver: statická analýza nie je prostriedkom na zníženie počtu defektov v programe. Dovolím si povedať: pri prvom použití vo vašom projekte nájde „zaujímavé“ miesta v kóde, ale s najväčšou pravdepodobnosťou nenájde žiadne chyby, ktoré by ovplyvnili kvalitu vášho programu.

Príklady defektov automaticky nájdených analyzátormi sú pôsobivé, ale nemali by sme zabúdať, že tieto príklady boli nájdené skenovaním veľkého súboru veľkých kódových báz. Podľa rovnakého princípu hackeri, ktorí majú možnosť vyskúšať niekoľko jednoduchých hesiel na veľkom počte účtov, nakoniec nájdu tie účty, ktoré majú jednoduché heslo.

Znamená to, že by sa nemala používať statická analýza? Samozrejme, že nie! A presne z rovnakého dôvodu, pre ktorý stojí za to skontrolovať každé nové heslo, aby ste sa uistili, že je zahrnuté v zozname „jednoduchých“ hesiel.

Statická analýza je viac ako hľadanie chýb

V skutočnosti sú problémy prakticky riešené analýzou oveľa širšie. Vo všeobecnosti je statická analýza akékoľvek overenie zdrojových kódov vykonávané pred ich spustením. Tu je niekoľko vecí, ktoré môžete urobiť:

  • Kontrola štýlu kódovania v najširšom zmysle slova. To zahŕňa kontrolu formátovania, hľadanie použitia prázdnych/nadbytočných zátvoriek, nastavenie prahových hodnôt pre metriky, ako je počet riadkov/cyklomatická zložitosť metódy atď. – čokoľvek, čo potenciálne bráni čitateľnosti a udržiavateľnosti kódu. V Jave je takýto nástroj Checkstyle, v Pythone - flake8. Programy tejto triedy sa zvyčajne nazývajú „linters“.
  • Nie je možné analyzovať len spustiteľný kód. Súbory zdrojov ako JSON, YAML, XML, .properties môžu (a mali by!) byť automaticky kontrolované na platnosť. Koniec koncov, je lepšie zistiť, že štruktúra JSON je narušená kvôli niektorým nespárovaným úvodzovkám v počiatočnom štádiu automatického overenia žiadosti o stiahnutie, než počas vykonávania testu alebo behu? K dispozícii sú vhodné nástroje: napr. YAMLlint, JSONLint.
  • Kompilácia (alebo analýza pre dynamické programovacie jazyky) je tiež typom statickej analýzy. Vo všeobecnosti sú kompilátory schopné produkovať varovania, ktoré naznačujú problémy s kvalitou zdrojového kódu a nemali by sa ignorovať.
  • Niekedy je kompilácia viac než len kompilácia spustiteľného kódu. Napríklad, ak máte dokumentáciu vo formáte AsciiDoctor, potom v momente premeny na HTML/PDF obslužný program AsciiDoctor (Doplnok Maven) môže vydávať upozornenia napríklad na nefunkčné interné odkazy. A to je dobrý dôvod, prečo neprijať žiadosť o stiahnutie so zmenami v dokumentácii.
  • Kontrola pravopisu je tiež typom statickej analýzy. Utility kúzlo je schopný kontrolovať pravopis nielen v dokumentácii, ale aj v zdrojových kódoch programu (komentáre a literály) v rôznych programovacích jazykoch vrátane C/C++, Java a Python. Chybou je aj pravopisná chyba v používateľskom rozhraní alebo dokumentácii!
  • Konfiguračné testy (o tom, čo sú - pozri. toto и toto reporty), hoci sú vykonávané v runtime jednotkového testu, ako je pytest, sú v skutočnosti tiež typom statickej analýzy, pretože počas svojho vykonávania nespúšťajú zdrojové kódy.

Ako vidíte, hľadanie chýb v tomto zozname hrá najmenej dôležitú úlohu a všetko ostatné je dostupné pomocou bezplatných nástrojov s otvoreným zdrojovým kódom.

Ktoré z týchto typov statickej analýzy by ste mali použiť vo svojom projekte? Samozrejme, čím viac, tým lepšie! Hlavná vec je správne implementovať, o čom sa bude ďalej diskutovať.

Dodávkové potrubie ako viacstupňový filter a statická analýza ako jeho prvý stupeň

Klasická metafora pre kontinuálnu integráciu je kanál, cez ktorý prúdia zmeny, od zmien zdrojového kódu cez dodávku až po produkciu. Štandardná postupnosť etáp v tomto potrubí vyzerá takto:

  1. statická analýza
  2. kompilácia
  3. jednotkové testy
  4. integračné testy
  5. Testy používateľského rozhrania
  6. manuálna kontrola

Zmeny zamietnuté na N-tej etape plynovodu sa neprenášajú do etapy N+1.

Prečo práve takto a nie inak? V testovacej časti potrubia, testeri spoznajú dobre známu testovaciu pyramídu.

Implementujte statickú analýzu do procesu a nepoužívajte ju na hľadanie chýb
Skúšobná pyramída. Zdroj: článok Martin Fowler.

V spodnej časti tejto pyramídy sú testy, ktoré sa ľahšie píšu, rýchlejšie vykonávajú a nemajú tendenciu zlyhať. Preto by ich malo byť viac, mali by pokrývať viac kódu a mali by byť spustené ako prvé. Na vrchole pyramídy je opak pravdou, takže počet integračných a UI testov by sa mal znížiť na nevyhnutné minimum. Osoba v tomto reťazci je najdrahším, najpomalším a nespoľahlivým zdrojom, takže je na samom konci a prácu vykonáva len vtedy, ak predchádzajúce etapy nezistili žiadne nedostatky. Rovnaké princípy sa však používajú na stavbu potrubia v častiach, ktoré priamo nesúvisia s testovaním!

Chcel by som ponúknuť analógiu v podobe viacstupňového systému filtrácie vody. Na vstup sa privádza špinavá voda (mení sa poruchami), na výstupe musíme prijímať čistú vodu, v ktorej sú odstránené všetky nežiaduce nečistoty.

Implementujte statickú analýzu do procesu a nepoužívajte ju na hľadanie chýb
Viacstupňový filter. Zdroj: Wikimedia Commons

Ako viete, čistiace filtre sú navrhnuté tak, aby každá nasledujúca kaskáda dokázala odfiltrovať čoraz jemnejšiu frakciu kontaminantov. Kaskády hrubšieho čistenia majú zároveň vyššiu priepustnosť a nižšie náklady. V našej analógii to znamená, že brány kvality vstupu sú rýchlejšie, vyžadujú menšiu námahu na spustenie a samy o sebe sú nenáročnejšie v prevádzke – a to je postupnosť, v ktorej sa stavajú. Úlohou statickej analýzy, ktorá, ako teraz chápeme, je schopná odstrániť len tie najhrubšie chyby, je úloha „bahennej“ mriežky na samom začiatku filtračnej kaskády.

Statická analýza sama o sebe nezlepšuje kvalitu konečného produktu, rovnako ako „bahenný filter“ nerobí vodu pitnou. A predsa v spojení s ďalšími prvkami potrubia je jeho dôležitosť zrejmá. Hoci vo viacstupňovom filtri sú výstupné stupne potenciálne schopné zachytiť všetko, čo robia vstupné stupne, je jasné, aké dôsledky bude mať snaha vystačiť si so samotnými stupňami jemného čistenia bez vstupných stupňov.

Účelom „lapača bahna“ je odľahčiť následné kaskády od zachytenia veľmi hrubých defektov. Osoba, ktorá kontroluje kód, by sa napríklad nemala nechať rozptyľovať nesprávne naformátovaným kódom a porušením zavedených štandardov kódovania (ako sú zátvorky navyše alebo príliš hlboko vnorené vetvy). Chyby ako NPE by mali byť zachytené testami jednotiek, ale ak nám ešte pred testom analyzátor naznačí, že sa chyba určite vyskytne, výrazne to urýchli jej opravu.

Domnievam sa, že je teraz jasné, prečo statická analýza nezlepší kvalitu produktu, ak sa používa príležitostne, a mala by sa používať neustále na odfiltrovanie zmien s hrubými chybami. Otázka, či použitie statického analyzátora zlepší kvalitu vášho produktu, je zhruba ekvivalentná otázke: „Zlepší sa kvalita pitia vody odobratej zo špinavého rybníka, ak prejde cez cedník?“

Implementácia do staršieho projektu

Dôležitá praktická otázka: ako implementovať statickú analýzu do kontinuálneho integračného procesu ako „bránu kvality“? V prípade automatických testov je všetko zrejmé: existuje súbor testov, zlyhanie ktoréhokoľvek z nich je dostatočným dôvodom domnievať sa, že montáž neprešla bránou kvality. Pokus o inštaláciu brány rovnakým spôsobom na základe výsledkov statickej analýzy zlyhá: v starom kóde je príliš veľa varovaní analýzy, nechcete ich úplne ignorovať, ale je tiež nemožné zastaviť odosielanie produktu len preto, že obsahuje varovania analyzátora.

Pri prvom použití analyzátor vygeneruje obrovské množstvo varovaní pri akomkoľvek projekte, z ktorých veľká väčšina nesúvisí so správnou funkciou produktu. Nie je možné opraviť všetky tieto komentáre naraz a mnohé z nich nie sú potrebné. Koniec koncov, vieme, že náš produkt ako celok funguje ešte pred zavedením statickej analýzy!

Výsledkom je, že mnohé sú obmedzené na príležitostné použitie statickej analýzy alebo ju používajú iba v informačnom režime, keď sa správa analyzátora jednoducho vydá počas montáže. To je ekvivalentné absencii akejkoľvek analýzy, pretože ak už máme veľa varovaní, výskyt ďalšieho (bez ohľadu na to, aký závažný) pri zmene kódu zostane bez povšimnutia.

Sú známe nasledujúce spôsoby zavádzania kvalitných brán:

  • Nastavenie limitu celkového počtu upozornení alebo počtu upozornení vydeleného počtom riadkov kódu. Toto funguje zle, pretože takáto brána voľne umožňuje prechod zmien s novými defektmi, pokiaľ nie je prekročený ich limit.
  • Oprava v určitom okamihu všetkých starých upozornení v kóde ako ignorovaných a odmietnutie zostavenia, keď sa objavia nové upozornenia. Túto funkciu poskytuje PVS-studio a niektoré online zdroje, napríklad Codacy. Nemal som možnosť pracovať v PVS-studio, pokiaľ ide o moje skúsenosti s Codacy, ich hlavným problémom je, že určiť, čo je „stará“ a čo je „nová“ chyba, je pomerne zložitý algoritmus, ktorý nie vždy funguje správne, najmä ak sú súbory výrazne upravené alebo premenované. Podľa mojich skúseností mohol Codacy ignorovať nové upozornenia v žiadosti o stiahnutie, pričom zároveň neprešiel žiadosťou o stiahnutie kvôli upozorneniam, ktoré nesúviseli so zmenami v kóde daného PR.
  • Podľa mňa je najefektívnejšie riešenie opísané v knihe Nepretržité dodávanie „ratchetovacia metóda“. Základnou myšlienkou je, že počet varovaní statickej analýzy je vlastnosťou každého vydania a povolené sú len zmeny, ktoré nezvýšia celkový počet varovaní.

Ratchet

Funguje to takto:

  1. V počiatočnej fáze sa v metaúdajoch vytvorí záznam o počte varovaní v kóde nájdenom analyzátormi. Takže keď vytvárate upstream, váš správca úložiska nezapíše len „vydanie 7.0.2“, ale „vydanie 7.0.2 obsahujúce 100500 XNUMX upozornení na kontrolný štýl“. Ak používate pokročilého správcu úložiska (napríklad Artifactory), ukladanie takýchto metadát o vašom vydaní je jednoduché.
  2. Teraz každá požiadavka na stiahnutie, keď je zostavená, porovnáva počet výsledných varovaní s počtom varovaní dostupných v aktuálnom vydaní. Ak PR vedie k zvýšeniu tohto počtu, potom kód neprejde bránou kvality pre statickú analýzu. Ak sa počet varovaní zníži alebo sa nezmení, potom to prejde.
  3. Pri ďalšom vydaní sa prepočítaný počet varovaní opäť zaznamená do metadát vydania.

Postupne, ale neustále (ako keď funguje rohatka), počet varovaní bude mať tendenciu k nule. Samozrejme, systém sa dá oklamať zavedením nového varovania, ale opravou niekoho iného. To je normálne, pretože na veľkú vzdialenosť to dáva výsledky: varovania sa spravidla opravujú nie jednotlivo, ale v skupine určitého typu naraz a všetky ľahko odstrániteľné varovania sa celkom rýchlo odstránia.

Tento graf zobrazuje celkový počet varovaní Checkstyle za šesť mesiacov prevádzky takejto „račny“. jeden z našich OpenSource projektov. Počet upozornení sa rádovo znížil a stalo sa tak prirodzene súbežne s vývojom produktu!

Implementujte statickú analýzu do procesu a nepoužívajte ju na hľadanie chýb

Používam upravenú verziu tejto metódy, ktorá samostatne počíta upozornenia podľa projektového modulu a analytického nástroja, výsledkom čoho je súbor YAML s metadátami zostavy, ktorý vyzerá asi takto:

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 akomkoľvek pokročilom systéme CI môže byť račňa implementovaná pre akékoľvek nástroje statickej analýzy bez spoliehania sa na doplnky a nástroje tretích strán. Každý analyzátor vytvára svoju vlastnú správu v jednoduchom textovom alebo XML formáte, ktorý sa ľahko analyzuje. Zostáva už len napísať potrebnú logiku do CI skriptu. Môžete vidieť, ako je to implementované v našich open source projektoch založených na Jenkins a Artifactory tu alebo tu. Oba príklady závisia od knižnice ratchetlib: metóda countWarnings() počíta xml tagy v súboroch generovaných Checkstyle a Spotbugs bežným spôsobom a compareWarningMaps() implementuje rovnakú račňu, ktorá spôsobí chybu, keď sa počet varovaní v ktorejkoľvek kategórii zvýši.

Zaujímavou implementáciou "ratchet" je analýza pravopisu komentárov, textových literálov a dokumentácie pomocou aspell. Ako viete, pri kontrole pravopisu nie sú všetky slová neznáme štandardnému slovníku nesprávne, možno ich pridať do používateľského slovníka. Ak vytvoríte vlastný slovník súčasťou zdrojového kódu projektu, brána kvality pravopisu môže byť formulovaná týmto spôsobom: spustenie aspellu so štandardným a vlastným slovníkom by nemal nájsť žiadne pravopisné chyby.

O dôležitosti opravy verzie analyzátora

Na záver je potrebné poznamenať, že bez ohľadu na to, ako implementujete analýzu do svojho dodávateľského potrubia, verzia analyzátora musí byť opravená. Ak povolíte spontánnu aktualizáciu analyzátora, potom sa pri zostavovaní ďalšej požiadavky na stiahnutie môžu „objaviť“ nové chyby, ktoré nesúvisia so zmenami kódu, ale súvisia s tým, že nový analyzátor jednoducho dokáže nájsť viac chýb - a to preruší váš proces prijímania žiadostí o stiahnutie. Aktualizácia analyzátora by mala byť vedomá akcia. Pevná fixácia verzie každého montážneho komponentu je však vo všeobecnosti nevyhnutnou požiadavkou a témou na samostatnú diskusiu.

Závery

  • Statická analýza za vás nenájde chyby a nezlepší kvalitu vášho produktu v dôsledku jedinej aplikácie. Pozitívny vplyv na kvalitu je možné dosiahnuť iba jeho neustálym používaním počas procesu dodávky.
  • Hľadanie chýb vôbec nie je hlavnou úlohou analýzy, drvivá väčšina užitočných funkcií je dostupná v opensource nástrojoch.
  • Implementujte brány kvality na základe výsledkov statickej analýzy v úplne prvej fáze doručovacieho potrubia pomocou „račne“ pre starý kód.

referencie

  1. Nepretržité dodávanie
  2. A. Kudryavtsev: Analýza programu: ako pochopiť, že ste dobrý programátor správa o rôznych metódach analýzy kódu (nielen statickej!)

Zdroj: hab.com

Pridať komentár