Parašyti šį straipsnį mane paskatino daugybė statinės analizės medžiagos, kuri vis dažniau sulaukia mano dėmesio. Pirma, tai , kuri aktyviai reklamuojasi „Habré“, remdamasi jų įrankio atvirojo kodo projektuose aptiktų klaidų apžvalgomis. Neseniai įdiegta PVS-studija , ir, žinoma, IntelliJ IDEA kūrėjai, kurių integruotas analizatorius šiandien yra turbūt pažangiausias Javai, .
Skaitant tokias apžvalgas apima jausmas, kad kalbame apie stebuklingą eliksyrą: paspauskite mygtuką, ir štai – prieš akis defektų sąrašas. Panašu, kad tobulėjant analizatoriams automatiškai bus randama vis daugiau klaidų, o šių robotų nuskaityti produktai taps vis geresni, be jokių mūsų pastangų.
Tačiau stebuklingų eliksyrų nėra. Norėčiau pakalbėti apie tai, apie ką dažniausiai nekalbama tokiuose įrašuose kaip „čia yra tai, ką gali rasti mūsų robotas“: ko negali padaryti analizatoriai, koks jų tikrasis vaidmuo ir vieta programinės įrangos pristatymo procese ir kaip juos teisingai įdiegti. .

Ratchet (šaltinis: ).
Ko niekada negali padaryti statiniai analizatoriai
Kas yra šaltinio kodo analizė praktiniu požiūriu? Pateikiame tam tikrą šaltinio kodą kaip įvestį, o kaip išvestį – per trumpą laiką (daug trumpiau nei atliekant bandymus) gauname tam tikrą informaciją apie mūsų sistemą. Esminis ir matematiškai neįveikiamas apribojimas yra tas, kad tokiu būdu galime gauti tik gana siaurą informacijos klasę.
Garsiausias problemos, kurios negalima išspręsti naudojant statinę analizę, pavyzdys : Tai yra teorema, kuri įrodo, kad neįmanoma sukurti bendro algoritmo, kuris pagal programos šaltinio kodą galėtų nustatyti, ar ji per ribotą laiką bus kilpa, ar baigsis. Šios teoremos išplėtimas yra , kuriame teigiama, kad bet kuriai ne trivialinei apskaičiuojamų funkcijų savybei nustatyti, ar savavališka programa įvertina funkciją su tokia savybe, yra algoritmiškai neišsprendžiama problema. Pavyzdžiui, neįmanoma parašyti analizatoriaus, kuris iš bet kurio šaltinio kodo galėtų nustatyti, ar analizuojama programa yra algoritmo, skaičiuojančio, tarkime, sveikojo skaičiaus kvadratą, įgyvendinimas.
Taigi statinių analizatorių funkcionalumas turi neįveikiamų apribojimų. Statinis analizatorius niekada negalės aptikti visais atvejais tokių dalykų, kaip, pavyzdžiui, „nulio rodyklės išimties“ atsiradimo kalbose, kurios leidžia įvesti nulinę reikšmę, arba visais atvejais nustatyti „nulio rodyklės išimties“ atsiradimą. atributas nerastas“ dinamiškai įvestomis kalbomis. Viskas, ką gali padaryti pažangiausias statinis analizatorius, yra išskirti ypatingus atvejus, kurių skaičius, be visų galimų šaltinio kodo problemų, be perdėto yra lašas jūroje.
Statinė analizė nėra skirta klaidų paieškai
Iš to, kas išdėstyta pirmiau, daroma išvada: statinė analizė nėra priemonė programoje esančių defektų skaičiui sumažinti. Drįsčiau teigti: pritaikius jūsų projektą pirmą kartą, jis suras „įdomių“ vietų kode, bet, greičiausiai, neras jokių defektų, turinčių įtakos jūsų programos kokybei.
Analizatorių automatiškai randamų defektų pavyzdžiai įspūdingi, tačiau nereikia pamiršti, kad šie pavyzdžiai buvo rasti nuskenavus didelį aibę didelių kodų bazių. Tuo pačiu principu įsilaužėliai, turintys galimybę išbandyti kelis paprastus slaptažodžius daugelyje paskyrų, galiausiai suranda tas paskyras, kuriose yra paprastas slaptažodis.
Ar tai reiškia, kad statinė analizė neturėtų būti naudojama? Žinoma ne! Ir lygiai dėl tos pačios priežasties, dėl kurios verta patikrinti kiekvieną naują slaptažodį, kad įsitikintumėte, jog jis įtrauktas į „paprastų“ slaptažodžių sąrašą.
Statinė analizė yra daugiau nei klaidų paieška
Iš tikrųjų analizės būdu praktiškai išsprendžiamos problemos yra daug platesnės. Galų gale, apskritai statinė analizė yra bet koks šaltinio kodų patikrinimas, atliktas prieš juos paleidžiant. Štai keletas dalykų, kuriuos galite padaryti:
- Kodavimo stiliaus tikrinimas plačiąja to žodžio prasme. Tai apima ir formatavimo tikrinimą, tuščių/papildomų skliaustų paiešką, metrikos slenksčių nustatymą, pvz., eilučių skaičių/metodo ciklomatinį sudėtingumą ir pan. – viską, kas gali trukdyti nuskaityti ir prižiūrėti kodą. Java programoje toks įrankis yra Checkstyle, Python – flake8. Šios klasės programos paprastai vadinamos „linters“.
- Galima analizuoti ne tik vykdomąjį kodą. Išteklių failų, tokių kaip JSON, YAML, XML, .properties, galiojimas gali būti (ir turėtų būti!) automatiškai tikrinamas. Galų gale, geriau sužinoti, kad JSON struktūra sugenda dėl kai kurių nesusietų kabučių ankstyvoje automatinio ištraukimo užklausos patvirtinimo stadijoje, nei atliekant bandymą ar vykdymo laiką? Galimos atitinkamos priemonės: pvz. , .
- Kompiliavimas (arba analizavimas dinaminėms programavimo kalboms) taip pat yra statinės analizės rūšis. Apskritai, kompiliatoriai gali pateikti įspėjimus, nurodančius šaltinio kodo kokybės problemas, todėl jų nereikėtų ignoruoti.
- Kartais kompiliavimas yra daugiau nei tik vykdomojo kodo kompiliavimas. Pavyzdžiui, jei turite tokio formato dokumentus , tada paverčiant jį į HTML/PDF, AsciiDoctor tvarkytoja () gali įspėti, pavyzdžiui, apie neveikiančias vidines nuorodas. Ir tai yra gera priežastis nepriimti ištraukimo užklausos su dokumentų pakeitimais.
- Rašybos tikrinimas taip pat yra statinės analizės rūšis. Naudingumas geba tikrinti rašybą ne tik dokumentacijoje, bet ir programų šaltinio koduose (komentaruose ir literaluose) įvairiomis programavimo kalbomis, įskaitant C/C++, Java ir Python. Rašybos klaida vartotojo sąsajoje ar dokumentacijoje taip pat yra defektas!
- Konfigūracijos testai (apie tai, kas jie yra - žr. и ataskaitos), nors ir vykdomos vieneto bandymo vykdymo metu, pvz., pytest, iš tikrųjų taip pat yra statinės analizės tipas, nes vykdymo metu jie nevykdo šaltinio kodų.
Kaip matote, klaidų paieška šiame sąraše atlieka mažiausiai svarbų vaidmenį, o visa kita pasiekiama naudojant nemokamus atvirojo kodo įrankius.
Kurią iš šių statinės analizės tipų turėtumėte naudoti savo projekte? Žinoma, kuo daugiau, tuo geriau! Svarbiausia tai teisingai įgyvendinti, apie tai bus kalbama toliau.
Pristatymo vamzdynas kaip kelių pakopų filtras ir statinė analizė kaip pirmasis etapas
Klasikinė nuolatinės integracijos metafora yra vamzdynas, kuriuo teka pokyčiai, nuo šaltinio kodo pakeitimų iki pristatymo į gamybą. Standartinė šio dujotiekio etapų seka atrodo taip:
- statinė analizė
- kompiliacija
- vienetiniai testai
- integracijos testai
- UI testai
- rankinis patikrinimas
Dujotiekio N etape atmesti pakeitimai neperkeliami į N+1 etapą.
Kodėl būtent taip, o ne kitaip? Dujotiekio bandomojoje dalyje bandytojai atpažins gerai žinomą testavimo piramidę.

Bandomoji piramidė. Šaltinis: Martinas Fowleris.
Šios piramidės apačioje yra testai, kuriuos lengviau parašyti, greičiau atlikti ir kurie nėra linkę žlugti. Todėl jų turėtų būti daugiau, jie turėtų apimti daugiau kodo ir būti vykdomi pirmiausia. Piramidės viršuje yra atvirkščiai, todėl integravimo ir vartotojo sąsajos testų skaičių reikėtų sumažinti iki būtino minimumo. Žmogus šioje grandinėje yra pats brangiausias, lėčiausias ir nepatikimiausias resursas, todėl jis yra pačioje pabaigoje ir atlieka tik tuos darbus, jei ankstesniuose etapuose nerado jokių defektų. Tačiau tie patys principai naudojami tiesiant dujotiekį tose dalyse, kurios nėra tiesiogiai susijusios su bandymais!
Norėčiau pasiūlyti analogiją daugiapakopės vandens filtravimo sistemos pavidalu. Į įvadą tiekiamas nešvarus vanduo (pakeitimai su defektais) turime gauti švarų vandenį, kuriame pašalinti visi nepageidaujami teršalai.

Daugiapakopis filtras. Šaltinis:
Kaip žinote, valymo filtrai sukurti taip, kad kiekviena sekanti kaskada galėtų išfiltruoti vis smulkesnę teršalų dalį. Tuo pačiu metu stambesnio valymo kaskados turi didesnį pralaidumą ir mažesnes sąnaudas. Pagal mūsų analogiją tai reiškia, kad įvesties kokybės vartai yra greitesni, jiems reikia mažiau pastangų, o juos paleisti ir jie yra nepretenzingi eksploatuoti – tokia seka jie yra statomi. Statinės analizės, kuri, kaip dabar suprantame, gali pašalinti tik didžiausius defektus, vaidmuo yra „purvo“ tinklelio vaidmuo pačioje filtro kaskados pradžioje.
Statinė analizė pati savaime nepagerina galutinio produkto kokybės, kaip ir „purvo filtras“ nepadaro vandens geru. Ir vis dėlto, kartu su kitais dujotiekio elementais, jo svarba yra akivaizdi. Nors daugiapakopiame filtre išvesties pakopos potencialiai gali užfiksuoti viską, ką daro įvesties etapai, aišku, kokios pasekmės bus bandymas apsieiti tik su smulkaus valymo etapais, be įvesties etapų.
„Purvo gaudyklės“ paskirtis – atleisti vėlesnes kaskadas nuo labai didelių defektų. Pavyzdžiui, bent jau kodo peržiūrą atliekantis asmuo neturėtų būti atitrauktas dėl netinkamai suformatuoto kodo ir nustatytų kodavimo standartų pažeidimų (pvz., papildomų skliaustų ar per giliai įdėtų šakų). Klaidos, tokios kaip NPE, turėtų būti sugautos atliekant vienetinius testus, tačiau jei dar prieš bandymą analizatorius mums praneš, kad klaida turi įvykti, tai žymiai pagreitins jos ištaisymą.
Manau, kad dabar aišku, kodėl statinė analizė nepagerina gaminio kokybės, jei ji naudojama retkarčiais, ir turėtų būti naudojama nuolat, siekiant išfiltruoti pokyčius su dideliais trūkumais. Klausimas, ar statinio analizatoriaus naudojimas pagerins jūsų produkto kokybę, yra maždaug tolygus klausimui: „Ar vandens, paimto iš nešvaraus tvenkinio, geriamojo kokybė pagerės, jei jis bus išleistas per kiaurasamtį?
Įdiegimas į seną projektą
Svarbus praktinis klausimas: kaip įdiegti statinę analizę į nuolatinį integracijos procesą kaip „kokybės vartus“? Automatinių testų atveju viskas akivaizdu: yra aibė testų, kurio nors iš jų gedimas yra pakankamas pagrindas manyti, kad surinkimas nepraėjo kokybės vartų. Bandymas sumontuoti vartus tokiu pačiu būdu, remiantis statinės analizės rezultatais, nepavyksta: sename kode yra per daug analizės įspėjimų, nenorite jų visiškai ignoruoti, tačiau taip pat neįmanoma sustabdyti produkto siuntimo. vien todėl, kad jame yra analizatoriaus įspėjimų.
Kai analizatorius naudojamas pirmą kartą, jis pateikia daugybę įspėjimų apie bet kurį projektą, kurių didžioji dauguma nėra susiję su tinkamu gaminio veikimu. Visų šių komentarų iš karto ištaisyti neįmanoma, o daugelis ir nebūtini. Juk žinome, kad visas mūsų produktas veikia, net prieš pradėdami taikyti statinę analizę!
Dėl to daugelis apsiriboja retkarčiais naudojamu statine analize arba naudoja ją tik informacijos režimu, kai surinkimo metu tiesiog išduodama analizatoriaus ataskaita. Tai tolygu jokios analizės nebuvimui, nes jei jau turime daug įspėjimų, tai kito (nesvarbu, koks rimtas) atsiradimas keičiant kodą lieka nepastebimas.
Yra žinomi šie kokybiškų vartų įvedimo būdai:
- Bendro įspėjimų skaičiaus ribos nustatymas arba įspėjimų skaičius, padalytas iš kodo eilučių skaičiaus. Tai veikia prastai, nes tokie vartai laisvai praleidžia pokyčius su naujais defektais, jei tik neviršijama jų riba.
- Tam tikru momentu pataisyti visus senus įspėjimus kode kaip ignoruojamus ir atsisakyti kurti, kai atsiranda naujų įspėjimų. Šią funkciją teikia PVS-studio ir kai kurie internetiniai ištekliai, pavyzdžiui, Codacy. Neturėjau galimybės dirbti PVS-studijoje, kadangi mano patirtis su Codacy, jų pagrindinė problema yra ta, kad nustatyti, kas yra „sena“, o kas „nauja“ yra gana sudėtingas algoritmas, kuris ne visada veikia. teisingai, ypač jei failai yra labai modifikuoti arba pervardyti. Mano patirtis rodo, kad „Codacy“ galėjo nepaisyti naujų įspėjimų ištraukimo užklausoje ir tuo pat metu nepraleisti ištraukimo užklausos dėl įspėjimų, nesusijusių su tam tikro PR kodo pakeitimais.
- Mano nuomone, pats efektyviausias sprendimas yra aprašytas knygoje „reketavimo metodas“. Pagrindinė idėja yra ta, kad statinės analizės įspėjimų skaičius yra kiekvieno leidimo savybė ir leidžiami tik tokie pakeitimai, kurie nepadidina bendro įspėjimų skaičiaus.
Terkšlė
Tai veikia taip:
- Pradiniame etape metaduomenyse daromas įrašas apie įspėjimų skaičiaus paskelbimą analizatorių surastame kode. Taigi, kai kuriate prieš srovę, jūsų saugyklos valdytojas rašo ne tik „7.0.2 leidimas“, bet ir „7.0.2 leidimas, kuriame yra 100500 XNUMX įspėjimų apie kontrolinį stilių“. Jei naudojate išplėstinę saugyklos tvarkyklę (pvz., „Artifactory“), tokius metaduomenis apie savo leidimą saugoti paprasta.
- Dabar kiekviena ištraukimo užklausa, kai ji sukurta, lygina gautų įspėjimų skaičių su esamo leidimo įspėjimų skaičiumi. Jei PR padidina šį skaičių, tada kodas nepraeina statinės analizės kokybės vartų. Jei įspėjimų skaičius sumažėja arba nekinta, tai praeina.
- Kito leidimo metu perskaičiuotas įspėjimų skaičius vėl bus įrašytas leidimo metaduomenyse.
Taigi po truputį, bet nuolat (kaip veikiant reketui), įspėjimų skaičius bus linkęs į nulį. Žinoma, sistemą galima apgauti įvedus naują įspėjimą, bet ištaisius kažkieno kitą. Tai normalu, nes per ilgą atstumą tai duoda rezultatų: įspėjimai koreguojami, kaip taisyklė, ne pavieniui, o iš karto tam tikro tipo grupėje, o visi lengvai pašalinami įspėjimai gana greitai pašalinami.
Šioje diagramoje parodytas bendras „Checkstyle“ įspėjimų skaičius per šešis tokio „reketo“ veikimo mėnesius . Įspėjimų skaičius sumažėjo dydžiu, ir tai įvyko natūraliai, lygiagrečiai su produkto kūrimu!

Naudoju modifikuotą šio metodo versiją, atskirai skaičiuodamas įspėjimus pagal projekto modulį ir analizės įrankį, todėl gaunamas YAML failas su kūrimo metaduomenimis, kuris atrodo maždaug taip:
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
Bet kurioje pažangioje CI sistemoje reketas gali būti įdiegtas bet kokiems statinės analizės įrankiams, nepasikliaujant papildiniais ir trečiųjų šalių įrankiais. Kiekvienas analizatorius sukuria savo ataskaitą paprastu tekstu arba XML formatu, kurį lengva analizuoti. Belieka įrašyti reikiamą logiką CI scenarijuje. Galite pamatyti, kaip tai įgyvendinama mūsų atvirojo kodo projektuose, paremtuose Jenkins ir Artifactory arba . Abu pavyzdžiai priklauso nuo bibliotekos : metodas countWarnings() įprastu būdu skaičiuoja xml žymas failuose, kuriuos sugeneravo Checkstyle ir Spotbugs, ir compareWarningMaps() įgyvendina tą patį reketą, išmesdama klaidą, kai padidėja įspėjimų skaičius kurioje nors iš kategorijų.
Galimas įdomus „reketo“ įgyvendinimas analizuojant komentarų rašybą, teksto žodžius ir dokumentaciją naudojant aspell. Kaip žinote, tikrinant rašybą, ne visi standartiniam žodynui nežinomi žodžiai gali būti įtraukti į vartotojo žodyną. Jei pasirinktinį žodyną padarote projekto šaltinio kodo dalimi, rašybos kokybės vartai gali būti suformuluoti taip: paleisti aspell su standartiniu ir pasirinktiniu žodynu. nerasti rašybos klaidų.
Apie analizatoriaus versijos taisymo svarbą
Apibendrinant reikia atkreipti dėmesį į tai, kad nesvarbu, kaip diegiate analizę savo pristatymo vamzdyne, analizatoriaus versija turi būti pataisyta. Jei leisite analizatoriui spontaniškai atnaujinti, tada surenkant kitą ištraukimo užklausą gali „iššokti“ nauji defektai, nesusiję su kodo pakeitimais, o susiję su tuo, kad naujasis analizatorius tiesiog gali rasti daugiau defektų - ir tai nutrauks jūsų ištraukimo užklausų priėmimo procesą. Analizatoriaus atnaujinimas turėtų būti sąmoningas veiksmas. Tačiau griežtas kiekvieno surinkimo komponento versijos fiksavimas paprastai yra būtinas reikalavimas ir atskiros diskusijos tema.
išvados
- Statinė analizė neras jums klaidų ir nepagerins jūsų produkto kokybės dėl vienos programos. Teigiamas poveikis kokybei gali būti pasiektas tik nuolat naudojant jį pristatymo metu.
- Klaidų paieška apskritai nėra pagrindinė analizės užduotis.
- Įdiekite kokybės vartus, pagrįstus statinės analizės rezultatais pačiame pirmajame tiekimo vamzdyno etape, naudodami senojo kodo „reketą“.
Nuorodos
- ataskaita apie skirtingus kodo analizės metodus (ne tik statinius!)
Šaltinis: www.habr.com
