Integrera statisk analys i processen istÀllet för att anvÀnda den för att leta efter buggar

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 PVS-studio bloggen, 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 stöd för Java, och sjÀlvklart kunde utvecklarna av IntelliJ IDEA, vars inbyggda analysator idag Àr den mest avancerade för Java, inte stÄ vid sidan av.

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.

Integrera statisk analys i processen istÀllet för att anvÀnda den för att leta efter buggar
Hraavoqik (kÀlla: wikipedia).

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 stoppproblemet: 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 Rays teorem, 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, YAMLlint, JSONLint.
  • 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 AsciiDoctor, sĂ„ kan i samband med att den omvandlas till HTML/PDF, AsciiDoctor (Maven-plugin) 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 aspell 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 det hĂ€r och det hĂ€r 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:

  1. statisk analys
  2. kompilering
  3. modultester
  4. integreringstester
  5. UI-tester
  6. 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.

Integrera statisk analys i processen istÀllet för att anvÀnda den för att leta efter buggar
Testpyramiden. KĂ€lla: artikeln 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.

Integrera statisk analys i processen istÀllet för att anvÀnda den för att leta efter buggar
Flertrinsfilter. KĂ€lla: Wikimedia Commons

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 Kontinuerlig leverans »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:

  1. 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.
  2. 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.
  3. 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Ä ett av vÄra OpenSource-projekt. Antalet varningar har minskat med en ordning, och detta har skett pÄ ett naturligt sÀtt, parallellt med produktutvecklingen!

Integrera statisk analys i processen istÀllet för att anvÀnda den för att leta efter buggar

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 hÀr eller hÀr. BÄda exemplen beror pÄ biblioteket ratchetlib: 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. fÄr inte 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

  1. Kontinuerlig leverans
  2. A. Kudryavtsev: Programanalys: hur man förstÄr att man Àr en bra programmerare en presentation om olika kodanalysmetoder (inte bara statisk!)

KĂ€lla: habr.com

Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar đŸ”„ Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar | ProHoster