Att skriva den hÀr artikeln inspirerades av den stora mÀngden material om statisk analys som allt oftare dyker upp. För det första Àr det , som aktivt marknadsför sig pÄ Habr med hjÀlp av recensioner av fel som hittats av deras verktyg i projekt med öppen kÀllkod. Nyligen implementerade PVS-studio , och sjÀlvklart kunde utvecklarna av IntelliJ IDEA, vars inbyggda analysator idag Àr den mest avancerade för Java, .
Vid lÀsningen av sÄdana recensioner fÄr man kÀnslan av att det handlar om ett magiskt elixir: tryck pÄ en knapp, och hÀr Àr listan över defekter framför ögonen. Det verkar som att ju mer avancerade analysatorerna blir, desto fler buggar kommer automatiskt att upptÀckas, och produkter som skannats av dessa robotar kommer att bli allt bÀttre utan nÄgra anstrÀngningar frÄn vÄr sida.
Men det finns inga magiska elixir. Jag skulle vilja prata om vad som vanligtvis inte nÀmns i inlÀgg som "det hÀr Àr vad vÄr robot kan hitta": vad analysatorer aldrig kan göra, vilken deras verkliga roll och plats Àr i mjukvaruleveransprocessen, och hur man implementerar dem pÄ rÀtt sÀtt.

Hraavoqik (kÀlla: ).
Vad statiska analysatorer aldrig kommer att kunna
Vad innebÀr det, ur ett praktiskt perspektiv, att analysera kÀllkoden? Vi matar in vissa kÀllfiler, och pÄ kort tid (mycket kortare Àn att köra tester) fÄr vi viss information om vÄrt system. En grundlÀggande och matematiskt oöverkomlig begrÀnsning Àr att vi pÄ detta sÀtt endast kan fÄ en ganska snÀv klass av information.
Det mest kÀnda exemplet pÄ ett problem som inte kan lösas med statisk analys Àr : detta Àr en teorem som bevisar att det Àr omöjligt att utveckla en allmÀn algoritm som utifrÄn kÀllkoden till ett program kan avgöra om det kommer att gÄ i en oÀndlig loop eller avslutas inom en Àndlig tid. En utvidgning av denna teorem Àr , som hÀvdar att för varje icke-trivial egenskap av berÀkningsbara funktioner Àr definitionen av huruvida ett godtyckligt program berÀknar en funktion med en sÄdan egenskap en algoritmiskt olöslig uppgift. Till exempel, det Àr omöjligt att skriva en analysator som, för vilken kÀllkod som helst, avgör om det analyserade programmet Àr en implementering av en algoritm som berÀknar, lÄt oss sÀga, kvadraten av ett heltal.
DÀrför har funktionaliteten hos statiska analysatorer oöverkomliga begrÀnsningar. En statisk analysator kommer aldrig att kunna i alla fall identifiera sÄdana saker som till exempel uppkomsten av "null pointer exception" i sprÄk som tillÄter null-vÀrden, eller i alla fall identifiera förekomsten av "attribute not found" i sprÄk med dynamisk typning. Allt som den mest avancerade statiska analysatorn kan göra Àr att lyfta fram exceptionella fall, vars antal bland alla möjliga problem med din kÀllkod utan överdrift Àr en droppe i havet.
Statisk analys Àr inte buggsökning
Av det ovanstÄende framgÄr slutsatsen: statisk analys Àr inte ett verktyg för att minska antalet defekter i programmet. Jag vÄgar pÄstÄ att vid första tillÀmpningen pÄ ditt projekt kommer den att hitta "intressanta" stÀllen i koden, men mycket troligt kommer den inte att hitta nÄgra defekter som pÄverkar din programs prestanda.
Exempel pÄ defekter som automatiskt upptÀckts av analysatorer Àr imponerande, men man bör inte glömma att dessa exempel hittades genom att skanna ett stort antal omfattande kodbaser. PÄ samma sÀtt som hackare, som har möjlighet att försöka nÄgra enkla lösenord pÄ mÄnga konton, sÄ smÄningom hittar de konton som har ett enkelt lösenord.
Betyder detta att statisk analys inte ska tillÀmpas? SjÀlvklart inte! Och av exakt samma anledning som man bör kontrollera varje nytt lösenord mot en lista över "enkla" lösenord.
Statiska analyser Àr mer Àn buggsökning
I sjÀlva verket Àr de praktiskt lösbara uppgifterna som analyseras mycket bredare. Eftersom statisk analys överlag Àr vilken kontroll av koden som helst som utförs före dess körning. HÀr Àr nÄgra saker man kan göra:
- Kodgranskning i vid bemĂ€rkelse av ordet. Detta inkluderar bĂ„de formateringskontroll och sökning efter anvĂ€ndning av överflödiga/överblivna parenteser, instĂ€llning av tröskelvĂ€rden för metrik som antal rader/cykelmatisk komplexitet för metoder etc. â allt som potentiellt försvĂ„rar lĂ€sbarheten och underhĂ„llbarheten av koden. I Java Ă€r ett sĂ„dant verktyg Checkstyle, i Python â flake8. Program av denna typ kallas vanligtvis "linters".
- Analys kan göras inte bara pÄ körbar kod. Resursfiler sÄsom JSON, YAML, XML, .properties kan (och bör!) automatiskt verifieras för giltighet. Det Àr bÀttre att upptÀcka att strukturen för JSON har brutits pÄ grund av ojÀmna citationstecken i ett tidigt skede av automatisk kontroll av Pull Request, Àn vid körning av tester eller under körtid? Relevanta verktyg finns: exempelvis, , .
- Kompilering (eller parsing för dynamiska programmeringssprÄk) Àr ocksÄ en form av statisk analys. Generellt kan kompilatorer ge varningar som signalerar problem med kvaliteten pÄ kÀllkoden, och dessa bör inte ignoreras.
- Ibland Àr kompilering inte bara kompilering av körbar kod. Om du till exempel har dokumentation i formatet , sÄ kan i samband med att den omvandlas till HTML/PDF, AsciiDoctor () ge varningar, exempelvis om brutna interna lÀnkar. Och detta Àr en stark anledning att inte godkÀnna en Pull Request med Àndringar i dokumentationen.
- Stavningskontroll Àr ocksÄ en form av statisk analys. Verktyget kan kontrollera stavning inte bara i dokumentation utan ocksÄ i kÀllkoder (kommentarer och litteraler) pÄ olika programmeringssprÄk, inklusive C/C++, Java och Python. Stavfel i anvÀndargrÀnssnittet eller dokumentationen Àr ocksÄ en defekt!
- Konfigurationstester (om vad det Ă€r â se och föredrag), Ă€ven om de körs i miljön för modulĂ€ra testtyper som pytest, Ă€r faktiskt ocksĂ„ en typ av statisk analys, eftersom de inte kör kĂ€llkoden under sin körning.
Som vi ser spelar felletande i denna lista den minst viktiga rollen, och allt annat Àr tillgÀngligt genom att anvÀnda gratis open source-verktyg.
Vilka av dessa typer av statisk analys bör anvÀndas i ditt projekt? SjÀlvklart, ju fler desto bÀttre! Det viktigaste Àr att implementera det korrekt, vilket vi kommer att diskutera nÀsta.
Leveranspipeline som en flertrinsfilter och statisk analys som dess första steg
Den klassiska metaforen för kontinuerlig integration Ă€r en pipeline dĂ€r Ă€ndringar flödar â frĂ„n Ă€ndring av kĂ€llkoden till leverans i produktion. Den standardiserade sekvensen av steg i denna pipeline ser ut sĂ„ hĂ€r:
- statisk analys
- kompilering
- modultester
- integreringstester
- UI-tester
- manuell kontroll
Ăndringar som avvisas vid steg N i pipelinen gĂ„r inte vidare till steg N+1.
Varför just sÄ, och inte pÄ nÄgot annat sÀtt? I den del av pipelinen som rör tester fÄr testare lÀra sig om den vÀlkÀnda testpyramiden.

Testpyramiden. KĂ€lla: Martin Fowler.
I den nedre delen av denna pyramid finns tester som Àr lÀttare att skriva, som körs snabbare och som inte tenderar att ge falska positiva resultat. DÀrför bör det finnas fler av dem, de bör tÀcka mer kod och köras först. I den övre delen av pyramiden Àr det tvÀrtom, varför antalet integreringstester och UI-tester bör minskas till ett absolut minimum. MÀnniskan i denna kedja Àr den dyraste, lÄngsammaste och mest opÄlitliga resursen, dÀrför befinner den sig i slutet och utför arbete endast om de föregÄende stegen inte har upptÀckt nÄgra defekter. Men samma principer gÀller Àven för pipeline i delar som inte direkt rör testning!
Jag skulle vilja föreslÄ en analogi med ett flertrins vattenfilter. PÄ ingÄngen matas smutsigt vatten (Àndringar med defekter) och pÄ utgÄngen bör vi fÄ rent vatten, dÀr alla oönskade föroreningar Àr borttagna.

Flertrinsfilter. KĂ€lla:
Som bekant Ă€r rengörande filter utformade sĂ„ att varje följande steg kan filtrera bort allt finare fraktioner av föroreningar. I detta sammanhang innebĂ€r det att de grova reningsstegen har högre kapacitet och lĂ€gre kostnad. I vĂ„r analogi betyder detta att de ingĂ„ende kvalitetsportarna har bĂ€ttre prestanda, krĂ€ver mindre anstrĂ€ngning för att starta och Ă€r mindre krĂ€vande i anvĂ€ndningen â och just i den sekvensen Ă€r de organiserade. Rollen av statisk analys, som vi nu förstĂ„r kan fĂ„nga upp endast de mest grova felen â Ă€r som ett
Statisk analys förbĂ€ttrar i sig sjĂ€lvt inte kvaliteten pĂ„ den slutliga produkten, precis som en grov filtrering inte gör vattnet drickbart. ĂndĂ„ Ă€r dess betydelse uppenbar i den övergripande kedjan av andra element i processen. Ăven om utgĂ„ngsstegen i ett flerstegsf filter potentiellt kan fĂ„nga upp samma saker som ingĂ„ngsstegen â Ă€r det tydligt vilka konsekvenser det fĂ„r att utesluta ingĂ„ngssteget och bara förlita sig pĂ„ fina reningssteg.
MĂ„let med den grova filtreringen Ă€r att avlasta efterföljande steg frĂ„n att fĂ„nga grova defekter. Till exempel, i varje fall, bör personen som gör kodgranskning inte behöva distraheras av felaktigt formaterad kod och brott mot faststĂ€llda kodningsnormer (som överflödiga parenteser eller för djupt nĂ€stlade strukturer). Buggar som NPE bör fĂ„ngas av enhetstester, men om analysverktyget redan före testen pĂ„pekar att en bugg oundvikligen kommer att intrĂ€ffa â kommer detta att pĂ„skynda dess rĂ€ttelse betydligt.
Jag antar att det nu Àr klart varför statisk analys inte förbÀttrar produktens kvalitet om den tillÀmpas sporadiskt, och att den bör tillÀmpas kontinuerligt för att avvisa förÀndringar med grova defekter. FrÄgan om huruvida anvÀndningen av statisk analysator förbÀttrar kvaliteten pÄ din produkt Àr ungefÀr densamma som att frÄga "kommer dricksvattnet, taget frÄn en smutsig vattenkÀlla, förbÀttras om det passerar genom en sikt?"
Implementering i ett legacy-projekt
Ett viktigt praktiskt spörsmÄl: hur implementerar man statisk analys i processen för kontinuerlig integration som en "quality gate"? NÀr det gÀller automatiska tester Àr allt uppenbart: det finns en uppsÀttning tester, misslyckande av vilket som helst av dem Àr en tillrÀcklig grund för att anse att bygget inte har passerat quality gate. Att försöka pÄ samma sÀtt sÀtta en gate baserat pÄ resultaten av statisk analys misslyckas: pÄ legacy-kod finns det alldeles för mÄnga varningar frÄn analysen, man vill inte helt ignorera dem, men det gÄr inte heller att stoppa produkten bara för att det finns varningar frÄn analysverktyget.
NÀr det appliceras för första gÄngen pÄ vilket projekt som helst ger analysverktyget en enorm mÀngd varningar, dÀr den absoluta majoriteten inte har nÄgot att göra med produktens korrekta funktion. Att omedelbart ÄtgÀrda alla dessa anmÀrkningar Àr inte möjligt, och mÄnga av dem behöver inte ÄtgÀrdas. Vi vet ju att vÄr produkt fungerar i stort, Àven innan införandet av statisk analys!
Som ett resultat begrÀnsar mÄnga sig till sporadisk anvÀndning av statisk analys, eller anvÀnder den bara i informationslÀge, dÀr det bara ges en rapport frÄn analysverktyget vid bygget. Detta Àr likvÀrdigt med avsaknaden av nÄgon analys, eftersom om vi redan har ett antal varningar sÄ förblir det ett förbisett problem om det dyker upp ytterligare en (oavsett hur allvarlig) vid kodÀndring.
Följande metoder för att införa quality gates Àr kÀnda:
- Att sÀtta en grÀns för det totala antalet varningar eller antalet varningar delat med antalet kodrader. Detta fungerar dÄligt, eftersom en sÄdan gate lÀtt slÀpper igenom Àndringar med nya defekter sÄ lÀnge deras grÀns inte överskrids.
- Vid en viss tidpunkt kan alla gamla varningar i koden hanteras som ignorerade, vilket gör att byggprocessen misslyckas vid nya varningar. Denna funktionalitet erbjuds av PVS-studio och vissa onlinetjĂ€nster, som Codacy. Jag har inte arbetat med PVS-studio, men nĂ€r det gĂ€ller min erfarenhet av Codacy ligger deras största problem i att avgöra vad som Ă€r ett "gammalt" respektive "nytt" fel â en ganska svĂ„r och inte alltid korrekt fungerande algoritm, speciellt om filer har förĂ€ndrats kraftigt eller fĂ„tt nya namn. FrĂ„n min erfarenhet har Codacy kunnat hoppa över nya varningar i en pull-request samtidigt som den kan avvisa en pull request pĂ„ grund av varningar som inte relaterar till Ă€ndringarna i koden för denna PR.
- Enligt min mening Àr den mest effektiva lösningen den som beskrivs i boken »ratcheting»-metoden. Huvudidén Àr att varje version har ett antal varningar frÄn statisk analys, och endast sÄdana Àndringar tillÄts som inte ökar det totala antalet varningar.
Ratcheting
Det fungerar sÄ hÀr:
- I det första steget registreras antalet varningar i koden som hittas av analysatorerna i metadata om versionen. DÀrmed registreras vid bygget av huvudgrenen i din repository manager inte bara "version 7.0.2", utan "version 7.0.2 med 100500 Checkstyle-varningar". Om du anvÀnder en avancerad repository manager (som Artifactory) Àr det enkelt att spara sÄdan metadata om din version.
- Nu jĂ€mför varje pull request under bygget det antal varningar som skapas med det antal som finns i den aktuella versionen. Om PR:n ökar detta antal passerar koden inte kvalitetstestet för statisk analys. Om antalet varningar minskar eller förblir oförĂ€ndrat â dĂ„ passerar det.
- Vid nÀsta version kommer det omrÀknade antalet varningar att skrivas in i metadata för versionen igen.
SÄ smÄningom, men stadigt (som nÀr man arbetar med en spÀrr), kommer antalet varningar att strÀva mot noll. Naturligtvis kan systemet manipuleras genom att införa en ny varning, men genom att ÄtgÀrda nÄgon annans. Detta Àr okej, eftersom det ger resultat pÄ lÄng sikt: varningar ÄtgÀrdas oftast inte en i taget, utan i en grupp av ett visst slag, och alla lÀttÄtgÀrdade varningar ÄtgÀrdas ganska snabbt.
I denna graf visas det totala antalet Checkstyle-varningar under ett halvÄr av arbete med en sÄdan "spÀrr" pÄ . Antalet varningar har minskat med en ordning, och detta har skett pÄ ett naturligt sÀtt, parallellt med produktutvecklingen!

Jag anvÀnder en modifierad version av denna metod, dÀr jag separat rÀknar varningar uppdelat efter projektmoduler och analysverktyg, den genererade YAML-filen med metadata om bygget ser ungefÀr ut sÄ hÀr:
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
I varje avancerad CI-system kan en "spÀrr" implementeras för alla statiska analysverktyg, utan att förlita sig pÄ plugins och tredjepartsverktyg. Varje analysverktyg ger sin rapport i ett enkelt text- eller XML-format, som enkelt kan analyseras. Det ÄterstÄr bara att ange den nödvÀndiga logiken i CI-skriptet. Du kan se hur detta har implementerats i vÄra open source-projekt baserat pÄ Jenkins och Artifactory eller . BÄda exemplen beror pÄ biblioteket : metoden countWarnings() rÀknar normalt xml-taggar i filer som genereras av Checkstyle och Spotbugs, och compareWarningMaps() implementerar just den spÀrren, och kastar ett fel om antalet varningar i nÄgon av kategorierna ökar.
En intressant implementering av "ratchet" Àr möjlig för att analysera stavningen av kommentarer, textstrÀngar och dokumentation med hjÀlp av aspell. Som bekant Àr inte alla ord som Àr okÀnda för den standardordbok som anvÀnds felaktiga nÀr man kontrollerar stavningen; de kan lÀggas till i en anvÀndardictionary. Om man gör anvÀndardictionaryn till en del av projektets kÀllkod kan kvalitetsgrÀnsen för stavning formuleras pÄ följande sÀtt: att köra aspell med standard- och anvÀndardictionary. hitta nÄgra stavfel.
Om betydelsen av att faststÀlla versionen av analyzern
Avslutningsvis Ă€r det viktigt att notera följande: oavsett hur ni implementerar analysen i er leveranspipeline, bör versionen av analyzern alltid faststĂ€llas. Om analyzern tillĂ„ts uppgraderas av sig sjĂ€lv kan nya defekter "dyka upp" vid byggandet av nĂ€sta pull request, vilket inte Ă€r relaterat till förĂ€ndringar i koden, utan beror pĂ„ att den nya analyzern helt enkelt kan hitta fler defekter â vilket kan förstöra er process för godkĂ€nnande av pull requests. Uppgradering av analyzern ska vara en medveten Ă„tgĂ€rd. I allmĂ€nhet Ă€r strikt faststĂ€llande av version för varje komponent i bygget ett nödvĂ€ndigt krav och ett Ă€mne för en separat diskussion.
Slutsatser
- Statisk analys kommer inte att hitta nÄgra buggar eller förbÀttra kvaliteten pÄ er produkt som resultat av en engÄngsapplikation. Den positiva effekten pÄ kvaliteten ges endast genom kontinuerlig anvÀndning av den i leveransprocessen.
- Att hitta buggar Àr överhuvudtaget inte huvudsyftet med analysen; den övervÀldigande majoriteten av de anvÀndbara funktionerna Àr tillgÀngliga i opensource-verktyg.
- Implementera kvalitetsgrÀnser baserat pÄ resultaten av statisk analys redan i det första steget av leveranspipen, anvÀndande "ratchet" för legacy-kod.
LĂ€nkar
- en presentation om olika kodanalysmetoder (inte bara statisk!)
KĂ€lla: habr.com
