Jag blev uppmanad att skriva den hÀr artikeln av den stora mÀngd material om statisk analys som alltmer kommer till min uppmÀrksamhet. För det första, detta , 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 implementerad PVS-studio , och, naturligtvis, utvecklarna av IntelliJ IDEA, vars inbyggda analysator förmodligen Àr den mest avancerade för Java idag, .
NÀr du lÀser sÄdana recensioner fÄr du kÀnslan av att vi pratar om ett magiskt elixir: tryck pÄ knappen, och hÀr Àr den - en lista över defekter framför dina ögon. Det verkar som att allt eftersom analysatorerna förbÀttras kommer fler och fler buggar att hittas automatiskt, och produkterna som skannas av dessa robotar kommer att bli bÀttre och bÀttre, utan nÄgon anstrÀngning frÄn vÄr sida.
Men det finns inga magiska elixirer. Jag skulle vilja prata om det som vanligtvis inte pratas om i inlÀgg som "hÀr Àr de saker som vÄr robot kan hitta": vad analysatorer inte kan göra, vad Àr deras verkliga roll och plats i mjukvaruleveransprocessen och hur man implementerar dem korrekt .

Ratchet (kÀlla: ).
Vad statiska analysatorer aldrig kan göra
Vad Àr kÀllkodsanalys ur en praktisk synvinkel? Vi tillhandahÄller en del kÀllkod som indata, och som utdata fÄr vi pÄ kort tid (mycket kortare Àn pÄgÄende tester) lite information om vÄrt system. Den grundlÀggande och matematiskt oöverstigliga begrÀnsningen Àr att vi bara kan fÄ en ganska snÀv klass av information pÄ detta sÀtt.
Det mest kÀnda exemplet pÄ ett problem som inte kan lösas med statisk analys Àr : Detta Àr ett teorem som bevisar att det Àr omöjligt att utveckla en generell algoritm som kan avgöra frÄn kÀllkoden för ett program om det kommer att loopa eller avslutas inom en begrÀnsad tid. En förlÀngning av detta teorem Àr , som anger att för alla icke-triviala egenskaper hos berÀkningsbara funktioner, Àr det ett algoritmiskt svÄrlöst problem att avgöra om ett godtyckligt program utvÀrderar en funktion med en sÄdan egenskap. Det Àr till exempel omöjligt att skriva en analysator som kan avgöra frÄn vilken kÀllkod som helst om programmet som analyseras Àr en implementering av en algoritm som berÀknar, till exempel, kvadreringen av ett heltal.
Funktionaliteten hos statiska analysatorer har sÄledes oöverstigliga begrÀnsningar. En statisk analysator kommer aldrig i alla fall att kunna upptÀcka sÄdant som till exempel förekomsten av ett "nullpekareundantag" pÄ sprÄk som tillÄter vÀrdet av null, eller i alla fall att faststÀlla förekomsten av ett " attribut inte hittat" pÄ dynamiskt skrivna sprÄk. Allt som den mest avancerade statiska analysatorn kan göra Àr att markera speciella fall, vars antal, bland alla möjliga problem med din kÀllkod, utan att överdriva Àr en droppe i hinken.
Statisk analys handlar inte om att hitta buggar
Av ovanstÄende följer slutsatsen: statisk analys Àr inte ett sÀtt att minska antalet defekter i ett program. Jag skulle vÄga sÀga: nÀr det appliceras pÄ ditt projekt för första gÄngen, kommer det att hitta "intressanta" platser i koden, men troligtvis kommer det inte att hitta nÄgra defekter som pÄverkar kvaliteten pÄ ditt program.
Exemplen pÄ defekter som automatiskt hittas av analysatorer Àr imponerande, men vi bör inte glömma att dessa exempel hittades genom att skanna en stor uppsÀttning stora kodbaser. Enligt samma princip hittar hackare som har möjlighet att prova flera enkla lösenord pÄ ett stort antal konton sÄ smÄningom de konton som har ett enkelt lösenord.
Betyder detta att statisk analys inte bör anvÀndas? SjÀlvklart inte! Och av exakt samma anledning som det Àr vÀrt att kontrollera varje nytt lösenord för att se till att det finns med i stopplistan över "enkla" lösenord.
Statisk analys Àr mer Àn att hitta buggar
Faktum Àr att de problem som praktiskt löses genom analys Àr mycket bredare. NÀr allt kommer omkring, i allmÀnhet Àr statisk analys varje verifiering av kÀllkoder som utförs innan de lanseras. HÀr Àr nÄgra saker du kan göra:
- Kontrollera kodningsstil i ordets vidaste bemÀrkelse. Detta inkluderar bÄde att kontrollera formatering, leta efter anvÀndningen av tomma/extra parenteser, stÀlla in tröskelvÀrden för mÄtt som antal rader/cyklomatisk komplexitet för en metod, etc. - allt som potentiellt hindrar lÀsbarheten och underhÄllbarheten av koden. I Java Àr ett sÄdant verktyg Checkstyle, i Python - flake8. Program i den hÀr klassen kallas vanligtvis "linters".
- Inte bara körbar kod kan analyseras. Resursfiler som JSON, YAML, XML, .properties kan (och bör!) automatiskt kontrolleras för giltighet. NÀr allt kommer omkring Àr det bÀttre att ta reda pÄ att JSON-strukturen Àr trasig pÄ grund av nÄgra oparade citat i ett tidigt skede av automatisk Pull Request-verifiering Àn under testkörning eller körtid? LÀmpliga verktyg finns: t.ex. , .
- Kompilering (eller analys för dynamiska programmeringssprÄk) Àr ocksÄ en typ av statisk analys. I allmÀnhet kan kompilatorer producera varningar som indikerar problem med kÀllkodens kvalitet och bör inte ignoreras.
- Ibland Àr kompilering mer Àn att bara kompilera körbar kod. Till exempel om du har dokumentation i formatet , i det ögonblick dÄ den omvandlas till HTML/PDF, hanterar AsciiDoctor () kan utfÀrda varningar, till exempel om trasiga interna lÀnkar. Och detta Àr en bra anledning att inte acceptera Pull-förfrÄgan med dokumentationsÀndringar.
- Stavningskontroll Àr ocksÄ en typ av statisk analys. Verktyg kan kontrollera stavning inte bara i dokumentationen, utan Àven i programkÀllkoder (kommentarer och bokstavliga ord) i olika programmeringssprÄk, inklusive C/C++, Java och Python. Ett stavfel i anvÀndargrÀnssnittet eller dokumentationen Àr ocksÄ ett fel!
- Konfigurationstester (om vad de Àr - se. О rapporter), Àven om de körs i en enhetstestkörning sÄsom pytest, Àr de i sjÀlva verket ocksÄ en typ av statisk analys, eftersom de inte exekverar kÀllkoder under exekveringen.
Som du kan se spelar sökning efter buggar i den hÀr listan den minst viktiga rollen, och allt annat Àr tillgÀngligt genom att anvÀnda gratis verktyg med öppen kÀllkod.
Vilken av dessa typer av statisk analys ska du anvÀnda i ditt projekt? SjÀlvklart, ju fler desto bÀttre! Det viktigaste Àr att implementera det korrekt, vilket kommer att diskuteras vidare.
Leveranspipeline som ett flerstegsfilter och statisk analys som första steg
Den klassiska metaforen för kontinuerlig integration Àr en pipeline genom vilken förÀndringar flödet, frÄn kÀllkodsÀndringar till leverans till produktion. Standardsekvensen av steg i denna pipeline ser ut sÄ hÀr:
- statisk analys
- kompilering
- enhetstester
- integrationstester
- UI-tester
- manuell kontroll
Ăndringar som avvisats i det N:e steget av pipelinen överförs inte till steg N+1.
Varför just pÄ detta sÀtt och inte pÄ annat sÀtt? I testdelen av pipelinen kommer testare att kÀnna igen den vÀlkÀnda testpyramiden.

Testa pyramid. KĂ€lla: Martin Fowler.
LÀngst ner i denna pyramid finns test som Àr lÀttare att skriva, snabbare att utföra och som inte har en tendens att misslyckas. DÀrför borde det finnas fler av dem, de borde tÀcka mer kod och exekveras först. I toppen av pyramiden Àr det tvÀrtom, sÄ antalet integrations- och UI-tester bör reduceras till det nödvÀndiga minimum. Personen i denna kedja Àr den dyraste, lÄngsamma och otillförlitliga resursen, sÄ han Àr i slutet och utför bara arbetet om de tidigare stegen inte hittade nÄgra defekter. Samma principer anvÀnds dock för att bygga en pipeline i delar som inte Àr direkt relaterade till testning!
Jag skulle vilja erbjuda en analogi i form av ett flerstegs vattenfiltreringssystem. Smutsigt vatten (förÀndringar med defekter) tillförs ingÄngen, vid utgÄngen mÄste vi fÄ rent vatten, i vilket alla oönskade föroreningar har eliminerats.

Flerstegsfilter. KĂ€lla:
Som ni vet Àr rengöringsfilter utformade sÄ att varje efterföljande kaskad kan filtrera bort en allt finare del av föroreningar. Samtidigt har grövre reningskaskader högre genomströmning och lÀgre kostnad. I vÄr analogi betyder detta att grindar av ingÄngskvalitet Àr snabbare, krÀver mindre anstrÀngning för att starta och Àr i sig mer opretentiösa i drift - och det Àr den sekvens i vilken de Àr byggda. Rollen för statisk analys, som, som vi nu förstÄr, kan rensa bort endast de grövre defekterna, Àr rollen som "lera"-nÀtet i början av filterkaskaden.
Statisk analys i sig förbĂ€ttrar inte kvaliteten pĂ„ slutprodukten, precis som ett "lerfilter" inte gör vatten drickbart. Och Ă€ndĂ„, i samband med andra delar av pipelinen, Ă€r dess betydelse uppenbar. Ăven om utgĂ„ngsstegen i ett flerstegsfilter potentiellt Ă€r kapabla att fĂ„nga allt som ingĂ„ngsstegen gör, Ă€r det tydligt vilka konsekvenser som kommer att bli av ett försök att nöja sig med enbart finreningssteg, utan ingĂ„ngssteg.
Syftet med "lerfÀllan" Àr att befria efterföljande kaskader frÄn att fÄnga upp mycket grova defekter. Till exempel bör Ätminstone personen som gör kodgranskningen inte distraheras av felaktigt formaterad kod och brott mot etablerade kodningsstandarder (som extra parenteser eller för djupt kapslade grenar). Buggar som NPE bör fÄngas upp av enhetstester, men om analysatorn redan före testet indikerar för oss att en bugg kommer att hÀnda, kommer detta att avsevÀrt pÄskynda ÄtgÀrden.
Jag tror att det nu Àr klart varför statisk analys inte förbÀttrar kvaliteten pÄ produkten om den anvÀnds ibland, och bör anvÀndas stÀndigt för att filtrera bort förÀndringar med grova defekter. FrÄgan om huruvida anvÀndning av en statisk analysator kommer att förbÀttra kvaliteten pÄ din produkt motsvarar ungefÀr att frÄga: "Kommer vatten som tas frÄn en smutsig damm att förbÀttras i drickskvalitet om det passerar genom ett durkslag?"
Implementering i ett Àldre projekt
En viktig praktisk frÄga: hur implementerar man statisk analys i den kontinuerliga integrationsprocessen som en "kvalitetsport"? NÀr det gÀller automatiska tester Àr allt uppenbart: det finns en uppsÀttning tester, ett misslyckande av nÄgon av dem Àr tillrÀcklig anledning att tro att monteringen inte klarade kvalitetsporten. Ett försök att installera en grind pÄ samma sÀtt baserat pÄ resultaten av en statisk analys misslyckas: det finns för mÄnga analysvarningar i den Àldre koden, du vill inte ignorera dem helt, men det Àr ocksÄ omöjligt att sluta skicka en produkt bara för att den innehÄller analysatorvarningar.
NÀr den anvÀnds för första gÄngen producerar analysatorn ett stort antal varningar pÄ alla projekt, varav de allra flesta inte Àr relaterade till produktens korrekta funktion. Det Àr omöjligt att korrigera alla dessa kommentarer pÄ en gÄng, och mÄnga Àr inte nödvÀndiga. NÀr allt kommer omkring vet vi att vÄr produkt som helhet fungerar, Àven innan vi införde statisk analys!
Som ett resultat Àr mÄnga begrÀnsade till tillfÀllig anvÀndning av statisk analys, eller anvÀnder den bara i informationslÀge, nÀr en analysatorrapport helt enkelt utfÀrdas under montering. Detta motsvarar frÄnvaron av nÄgon analys, för om vi redan har mÄnga varningar, sÄ gÄr förekomsten av en annan (oavsett hur allvarlig) nÀr du Àndrar koden obemÀrkt.
Följande metoder för att introducera kvalitetsgrindar Àr kÀnda:
- StÀlla in en grÀns för det totala antalet varningar eller antalet varningar dividerat med antalet kodrader. Detta fungerar dÄligt, eftersom en sÄdan grind fritt tillÄter förÀndringar med nya defekter att passera, sÄ lÀnge deras grÀns inte överskrids.
- Fixar, vid ett visst tillfÀlle, alla gamla varningar i koden som ignorerade, och vÀgrar bygga nÀr nya varningar uppstÄr. Denna funktion tillhandahÄlls av PVS-studio och vissa onlineresurser, till exempel Codacy. Jag hade inte möjligheten att arbeta i PVS-studio, för min erfarenhet av Codacy Àr deras huvudproblem att bestÀmma vad som Àr ett "gammalt" och vad som Àr ett "nytt" fel Àr en ganska komplex algoritm som inte alltid fungerar korrekt, sÀrskilt om filer Àr kraftigt modifierade eller bytt namn. Enligt min erfarenhet kunde Codacy ignorera nya varningar i en pull-förfrÄgan, samtidigt som den inte godkÀnde en pull-förfrÄgan pÄ grund av varningar som inte var relaterade till Àndringar i koden för en given PR.
- Enligt min mening Àr den mest effektiva lösningen den som beskrivs i boken "spÀrrmetoden". Grundtanken Àr att antalet statiska analysvarningar Àr en egenskap för varje version, och endast Àndringar Àr tillÄtna som inte ökar det totala antalet varningar.
Ratchet
Det fungerar sÄ hÀr:
- I det inledande skedet görs en registrering i metadata om frislÀppandet av antalet varningar i koden som analyserarna hittat. SÄ nÀr du bygger uppströms skriver din förvarshanterare inte bara "release 7.0.2", utan "release 7.0.2 som innehÄller 100500 XNUMX checkstyle-varningar." Om du anvÀnder en avancerad repository manager (som Artifactory) Àr det enkelt att lagra sÄdan metadata om din release.
- Nu jÀmför varje pull-begÀran, nÀr den Àr byggd, antalet resulterande varningar med antalet tillgÀngliga varningar i den aktuella versionen. Om PR leder till en ökning av detta antal, passerar koden inte kvalitetsgrinden för statisk analys. Om antalet varningar minskar eller inte Àndras gÄr det över.
- Vid nÀsta release kommer det omrÀknade antalet varningar att registreras igen i releasemetadata.
SÄ lite i taget men stadigt (som nÀr en spÀrrhake fungerar) kommer antalet varningar att tendera till noll. SjÀlvklart kan systemet luras genom att införa en ny varning, men korrigera nÄgon annans. Detta Àr normalt, eftersom det över en lÄng strÀcka ger resultat: varningar korrigeras, som regel, inte individuellt, utan i en grupp av en viss typ pÄ en gÄng, och alla lÀtt borttagbara varningar elimineras ganska snabbt.
Denna graf visar det totala antalet Checkstyle-varningar för sex mÄnaders drift av en sÄdan "spÀrr" pÄ . Antalet varningar har minskat med en storleksordning, och detta skedde naturligt, parallellt med produktutvecklingen!

Jag anvÀnder en modifierad version av den hÀr metoden, som separat rÀknar varningar av projektmodul och analysverktyg, vilket resulterar i en YAML-fil med byggmetadata som ser ut ungefÀr 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 alla avancerade CI-system kan ratchet implementeras för alla statiska analysverktyg utan att förlita sig pÄ plugins och tredjepartsverktyg. Varje analysator producerar sin egen rapport i ett enkelt text- eller XML-format som Àr lÀtt att analysera. Allt som ÄterstÄr Àr att skriva den nödvÀndiga logiken i CI-skriptet. Du kan se hur detta implementeras i vÄra open source-projekt baserade pÄ Jenkins och Artifactory eller . BÄda exemplen beror pÄ biblioteket : metod countWarnings() rÀknar xml-taggar i filer som genereras av Checkstyle och Spotbugs pÄ vanligt sÀtt, och compareWarningMaps() implementerar samma spÀrrhake, vilket ger ett fel nÀr antalet varningar i nÄgon av kategorierna ökar.
En intressant implementering av "spÀrren" Àr möjlig för att analysera stavningen av kommentarer, bokstavliga texter och dokumentation med aspell. Som du vet Àr inte alla ord som Àr okÀnda i standardlexikonet felaktiga nÀr du kontrollerar stavningen, de kan lÀggas till i anvÀndarlexikonet. Om du gör en anpassad ordbok till en del av projektets kÀllkod, sÄ kan stavningskvalitetsporten formuleras sÄ hÀr: köra aspell med en standard och anpassad ordbok hittar inga stavfel.
Om vikten av att fixa analysatorversionen
Sammanfattningsvis Àr poÀngen att notera att oavsett hur du implementerar analys i din leveranspipeline, mÄste versionen av analysatorn fixas. Om du tillÄter att analysatorn uppdateras spontant, kan nya defekter "dyka upp" vid montering av nÀsta pull-begÀran som inte Àr relaterade till kodÀndringar, utan Àr relaterade till det faktum att den nya analysatorn helt enkelt kan hitta fler defekter - och detta kommer att bryta din process för att acceptera pull-förfrÄgningar. Att uppgradera en analysator bör vara en medveten ÄtgÀrd. Fast fixering av versionen av varje monteringskomponent Àr dock i allmÀnhet ett nödvÀndigt krav och ett Àmne för en separat diskussion.
Resultat
- Statisk analys kommer inte att hitta buggar för dig och kommer inte att förbÀttra kvaliteten pÄ din produkt som ett resultat av en enda applikation. En positiv effekt pÄ kvaliteten kan endast uppnÄs genom att den stÀndigt anvÀnds under leveransprocessen.
- Att hitta buggar Àr inte alls huvuduppgiften för analys, de allra flesta anvÀndbara funktioner Àr tillgÀngliga i opensource-verktyg.
- Implementera kvalitetsgrindar baserat pÄ resultaten av statisk analys i det allra första steget av leveranspipelinen, med hjÀlp av en "spÀrr" för Àldre kod.
referenser
- rapportera om olika metoder för kodanalys (inte bara statisk!)
KĂ€lla: will.com
