Implementer statisk analyse i prosessen, i stedet for å bruke den til å finne feil

Jeg ble bedt om å skrive denne artikkelen av den store mengden materiale om statisk analyse som i økende grad kommer til min oppmerksomhet. For det første dette PVS-studioblogg, som aktivt promoterer seg selv på Habré ved hjelp av anmeldelser av feil funnet av verktøyet deres i åpen kildekode-prosjekter. Nylig implementert PVS-studio Java-støtte, og selvfølgelig utviklerne av IntelliJ IDEA, hvis innebygde analysator sannsynligvis er den mest avanserte for Java i dag, klarte ikke holde seg unna.

Når du leser slike anmeldelser, får du følelsen av at vi snakker om en magisk eliksir: trykk på knappen, og her er den - en liste over defekter foran øynene dine. Det ser ut til at etter hvert som analysatorene forbedres, vil flere og flere feil automatisk bli funnet, og produktene som skannes av disse robotene vil bli bedre og bedre, uten noen innsats fra vår side.

Men det er ingen magiske eliksirer. Jeg vil gjerne snakke om det som vanligvis ikke snakkes om i innlegg som "her er tingene vår robot kan finne": hva analysatorer ikke kan gjøre, hva er deres virkelige rolle og plass i programvareleveringsprosessen, og hvordan implementere dem riktig .

Implementer statisk analyse i prosessen, i stedet for å bruke den til å finne feil
Ratchet (kilde: wikipedia).

Hva statiske analysatorer aldri kan gjøre

Hva er kildekodeanalyse, fra et praktisk synspunkt? Vi leverer noe kildekode som input, og som utdata får vi på kort tid (mye kortere enn å kjøre tester) litt informasjon om systemet vårt. Den grunnleggende og matematisk uoverkommelige begrensningen er at vi på denne måten bare kan få tak i en ganske smal informasjonsklasse.

Det mest kjente eksemplet på et problem som ikke kan løses ved hjelp av statisk analyse er avslutningsproblem: Dette er et teorem som beviser at det er umulig å utvikle en generell algoritme som kan bestemme ut fra kildekoden til et program om det vil sløyfes eller avsluttes på en begrenset tid. En utvidelse av denne teoremet er Rice sin teorem, som sier at for enhver ikke-triviell egenskap til beregnelige funksjoner, er det et algoritmisk vanskelig problem å bestemme om et vilkårlig program evaluerer en funksjon med en slik egenskap. For eksempel er det umulig å skrive en analysator som kan avgjøre fra hvilken som helst kildekode om programmet som analyseres er en implementering av en algoritme som beregner for eksempel kvadreringen av et heltall.

Dermed har funksjonaliteten til statiske analysatorer uoverstigelige begrensninger. En statisk analysator vil aldri i alle tilfeller kunne oppdage slike ting som for eksempel forekomsten av et "nullpekerunntak" på språk som tillater verdien av null, eller i alle tilfeller bestemme forekomsten av en " attributt ikke funnet" på dynamisk skrevet språk. Alt den mest avanserte statiske analysatoren kan gjøre er å fremheve spesielle tilfeller, hvor antallet, blant alle mulige problemer med kildekoden din, uten overdrivelse er en dråpe i havet.

Statisk analyse handler ikke om å finne feil

Fra ovenstående følger konklusjonen: statisk analyse er ikke et middel for å redusere antall defekter i et program. Jeg vil våge å si: når det brukes på prosjektet ditt for første gang, vil det finne "interessante" steder i koden, men mest sannsynlig vil det ikke finne noen defekter som påvirker kvaliteten på programmet ditt.

Eksemplene på defekter som automatisk blir funnet av analysatorer er imponerende, men vi bør ikke glemme at disse eksemplene ble funnet ved å skanne et stort sett med store kodebaser. Etter samme prinsipp finner hackere som har muligheten til å prøve flere enkle passord på et stort antall kontoer til slutt de kontoene som har et enkelt passord.

Betyr dette at statisk analyse ikke skal brukes? Selvfølgelig ikke! Og av nøyaktig samme grunn som det er verdt å sjekke hvert nytt passord for å sikre at det er inkludert i stopplisten over "enkle" passord.

Statisk analyse er mer enn å finne feil

Faktisk er problemene som praktisk talt løses ved analyse mye bredere. Tross alt, generelt sett er statisk analyse enhver verifisering av kildekoder utført før de lanseres. Her er noen ting du kan gjøre:

  • Sjekke kodestil i ordets videste forstand. Dette inkluderer både å sjekke formatering, lete etter bruk av tomme/ekstra parenteser, sette terskler på beregninger som antall linjer/syklomatisk kompleksitet til en metode, etc. - alt som potensielt hindrer lesbarheten og vedlikeholdbarheten til koden. I Java er et slikt verktøy Checkstyle, i Python - flake8. Programmer i denne klassen kalles vanligvis "linters".
  • Ikke bare kjørbar kode kan analyseres. Ressursfiler som JSON, YAML, XML, .properties kan (og bør!) sjekkes automatisk for gyldighet. Tross alt er det bedre å finne ut at JSON-strukturen er ødelagt på grunn av noen uparrede sitater på et tidlig stadium av automatisk Pull Request-verifisering enn under testkjøring eller kjøretid? Egnede verktøy er tilgjengelige: f.eks. YAMLlint, JSONLint.
  • Kompilering (eller parsing for dynamiske programmeringsspråk) er også en type statisk analyse. Generelt er kompilatorer i stand til å produsere advarsler som indikerer problemer med kildekodekvaliteten og bør ikke ignoreres.
  • Noen ganger er kompilering mer enn bare å kompilere kjørbar kode. For eksempel hvis du har dokumentasjon i formatet AsciiDoctor, så i øyeblikket du gjør den om til HTML/PDF, er AsciiDoctor-behandleren (Maven-plugin) kan utstede advarsler, for eksempel om ødelagte interne lenker. Og dette er en god grunn til ikke å godta Pull-forespørselen med dokumentasjonsendringer.
  • Stavekontroll er også en type statisk analyse. Nytte en formel er i stand til å kontrollere stavemåten ikke bare i dokumentasjon, men også i programkildekoder (kommentarer og bokstaver) på forskjellige programmeringsspråk, inkludert C/C++, Java og Python. En stavefeil i brukergrensesnittet eller dokumentasjonen er også en feil!
  • Konfigurasjonstester (om hva de er - se. dette и dette rapporter), selv om de utføres i en enhetstestkjøring som pytest, er faktisk også en type statisk analyse, siden de ikke kjører kildekoder under utførelse.

Som du kan se, spiller søk etter feil i denne listen den minst viktige rollen, og alt annet er tilgjengelig ved å bruke gratis åpen kildekode-verktøy.

Hvilken av disse typene statisk analyse bør du bruke i prosjektet ditt? Selvfølgelig, jo flere jo bedre! Det viktigste er å implementere det riktig, som vil bli diskutert videre.

Leveringsrørledning som flertrinnsfilter og statisk analyse som første trinn

Den klassiske metaforen for kontinuerlig integrasjon er en pipeline som endrer flyt gjennom, fra kildekodeendringer til levering til produksjon. Standardsekvensen av stadier i denne rørledningen ser slik ut:

  1. statisk analyse
  2. samling
  3. enhetstester
  4. integrasjonstester
  5. UI-tester
  6. manuell sjekk

Endringer som avvises på N. trinn av rørledningen overføres ikke til trinn N+1.

Hvorfor akkurat på denne måten og ikke på annen måte? I testdelen av rørledningen vil testerne gjenkjenne den velkjente testpyramiden.

Implementer statisk analyse i prosessen, i stedet for å bruke den til å finne feil
Testpyramide. Kilde: artikkel Martin Fowler.

I bunnen av denne pyramiden er tester som er lettere å skrive, raskere å utføre, og som ikke har en tendens til å mislykkes. Derfor bør det være flere av dem, de bør dekke mer kode og kjøres først. På toppen av pyramiden er det motsatt, så antall integrasjon og UI-tester bør reduseres til det nødvendige minimum. Personen i denne kjeden er den dyreste, langsomme og upålitelige ressursen, så han er helt på slutten og utfører bare arbeidet hvis de forrige stadiene ikke fant noen feil. Imidlertid brukes de samme prinsippene for å bygge en rørledning i deler som ikke er direkte relatert til testing!

Jeg vil gjerne tilby en analogi i form av et flertrinns vannfiltreringssystem. Skittent vann (endringer med defekter) tilføres inngangen, ved utgangen må vi motta rent vann, der alle uønskede forurensninger er eliminert.

Implementer statisk analyse i prosessen, i stedet for å bruke den til å finne feil
Flertrinns filter. Kilde: Wikimedia Commons

Som du vet, er rengjøringsfiltre utformet slik at hver påfølgende kaskade kan filtrere ut en stadig finere brøkdel av forurensninger. Samtidig har grovere rensekaskader høyere gjennomstrømning og lavere kostnad. I vår analogi betyr dette at inngangskvalitetsporter er raskere, krever mindre innsats for å starte og er i seg selv mer upretensiøse i drift - og dette er sekvensen de er bygget i. Rollen til statisk analyse, som, som vi nå forstår, er i stand til å luke ut bare de groveste defektene, er rollen til "slam"-gitteret helt i begynnelsen av filterkaskaden.

Statisk analyse i seg selv forbedrer ikke kvaliteten på sluttproduktet, akkurat som et "slamfilter" ikke gjør vann drikkebart. Og likevel, i forbindelse med andre elementer i rørledningen, er dens betydning åpenbar. Selv om utgangstrinnene i et flertrinnsfilter potensielt er i stand til å fange opp alt inngangstrinnene gjør, er det klart hvilke konsekvenser som vil få av et forsøk på å nøye seg med finrensetrinn alene, uten inngangstrinn.

Hensikten med "slamfellen" er å avlaste påfølgende kaskader fra å fange opp svært grove feil. For eksempel, som et minimum, bør personen som utfører kodegjennomgangen ikke bli distrahert av feil formatert kode og brudd på etablerte kodestandarder (som ekstra parenteser eller for dypt nestede grener). Feil som NPE-er bør fanges opp av enhetstester, men hvis analysatoren allerede før testen indikerer for oss at en feil er bundet til å skje, vil dette fremskynde fiksingen betydelig.

Jeg tror det nå er klart hvorfor statisk analyse ikke forbedrer kvaliteten på produktet hvis det brukes av og til, og bør brukes konstant for å filtrere ut endringer med grove defekter. Spørsmålet om hvorvidt bruk av en statisk analysator vil forbedre kvaliteten på produktet ditt tilsvarer omtrent å spørre: "Vil vann tatt fra en skitten dam bli forbedret i drikkekvalitet hvis det føres gjennom et dørslag?"

Implementering i et arvprosjekt

Et viktig praktisk spørsmål: hvordan implementere statisk analyse i den kontinuerlige integrasjonsprosessen som en "kvalitetsport"? Når det gjelder automatiske tester, er alt åpenbart: det er et sett med tester, svikt i noen av dem er tilstrekkelig grunn til å tro at forsamlingen ikke bestod kvalitetsporten. Et forsøk på å installere en port på samme måte basert på resultatene av en statisk analyse mislykkes: det er for mange analyseadvarsler i den eldre koden, du vil ikke ignorere dem fullstendig, men det er også umulig å stoppe levering av et produkt bare fordi den inneholder analysatoradvarsler.

Når den brukes for første gang, produserer analysatoren et stort antall advarsler på ethvert prosjekt, hvorav de aller fleste ikke er relatert til riktig funksjon av produktet. Det er umulig å rette opp alle disse kommentarene på en gang, og mange er ikke nødvendige. Tross alt vet vi at produktet vårt som helhet fungerer, selv før vi introduserer statisk analyse!

Som et resultat er mange begrenset til sporadisk bruk av statisk analyse, eller bruker den bare i informasjonsmodus, når en analysatorrapport ganske enkelt utstedes under montering. Dette tilsvarer fraværet av noen analyse, fordi hvis vi allerede har mange advarsler, så går forekomsten av en annen (uansett hvor alvorlig) når du endrer koden, ubemerket.

Følgende metoder for å introdusere kvalitetsporter er kjent:

  • Sette en grense for totalt antall advarsler eller antall advarsler delt på antall kodelinjer. Dette fungerer dårlig, fordi en slik port fritt lar endringer med nye defekter passere, så lenge grensen deres ikke overskrides.
  • Retter, på et bestemt tidspunkt, alle gamle advarsler i koden som ignorert, og nekter å bygge når nye advarsler oppstår. Denne funksjonaliteten leveres av PVS-studio og noen nettressurser, for eksempel Codacy. Jeg hadde ikke muligheten til å jobbe i PVS-studio, når det gjelder min erfaring med Codacy, er hovedproblemet deres at å bestemme hva som er en "gammel" og hva som er en "ny" feil er en ganske kompleks algoritme som ikke alltid fungerer riktig, spesielt hvis filer er kraftig modifisert eller omdøpt. Etter min erfaring kunne Codacy ignorere nye advarsler i en pull-forespørsel, samtidig som den ikke passerte en pull-forespørsel på grunn av advarsler som ikke var relatert til endringer i koden til en gitt PR.
  • Etter min mening er den mest effektive løsningen den som er beskrevet i boken Kontinuerlig Levering "skrattemetode". Den grunnleggende ideen er at antallet statiske analyseadvarsler er en egenskap for hver utgivelse, og kun endringer er tillatt som ikke øker det totale antallet advarsler.

Ratchet

Det fungerer slik:

  1. I det innledende stadiet registreres det i metadataene om utgivelsen av antall advarsler i koden funnet av analysatorene. Så når du bygger oppstrøms, skriver depotadministratoren din ikke bare "utgivelse 7.0.2", men "utgivelse 7.0.2 som inneholder 100500 XNUMX sjekkstil-advarsler." Hvis du bruker en avansert repository manager (som Artifactory), er det enkelt å lagre slike metadata om utgivelsen.
  2. Nå sammenligner hver pull-forespørsel, når den er bygget, antallet resulterende advarsler med antallet tilgjengelige advarsler i den gjeldende utgivelsen. Hvis PR fører til en økning i dette tallet, passerer ikke koden kvalitetsporten for statisk analyse. Hvis antallet advarsler reduseres eller ikke endres, går det over.
  3. Ved neste utgivelse vil det omkalkulerte antallet advarsler registreres igjen i utgivelsesmetadataene.

Så litt etter litt, men jevnt og trutt (som når en skralle fungerer), vil antallet advarsler ha en tendens til null. Selvsagt kan systemet lures ved å innføre en ny advarsel, men korrigere andres. Dette er normalt, fordi det gir resultater over lang avstand: advarsler korrigeres som regel ikke individuelt, men i en gruppe av en bestemt type på en gang, og alle lett fjernbare advarsler elimineres ganske raskt.

Denne grafen viser det totale antallet Checkstyle-advarsler for seks måneders drift av en slik "ratchet" på et av våre OpenSource-prosjekter. Antall advarsler har gått ned med en størrelsesorden, og dette skjedde naturlig, parallelt med produktutviklingen!

Implementer statisk analyse i prosessen, i stedet for å bruke den til å finne feil

Jeg bruker en modifisert versjon av denne metoden, og teller advarsler separat etter prosjektmodul og analyseverktøy, noe som resulterer i en YAML-fil med byggemetadata som ser omtrent slik ut:

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 ethvert avansert CI-system kan ratchet implementeres for alle statiske analyseverktøy uten å stole på plugins og tredjepartsverktøy. Hver analysator produserer sin egen rapport i et enkelt tekst- eller XML-format som er enkelt å analysere. Alt som gjenstår er å skrive den nødvendige logikken i CI-skriptet. Du kan se hvordan dette implementeres i våre åpen kildekode-prosjekter basert på Jenkins og Artifactory her eller her. Begge eksemplene avhenger av biblioteket ratchetlib: metode countWarnings() teller xml-koder i filer generert av Checkstyle og Spotbugs på vanlig måte, og compareWarningMaps() implementerer samme skralle, og gir en feil når antallet advarsler i noen av kategoriene øker.

En interessant implementering av "ratchet" er mulig for å analysere stavemåten til kommentarer, bokstavelige tekster og dokumentasjon ved hjelp av aspell. Som du vet, når du kontrollerer stavemåten, er ikke alle ord ukjent for standardordboken feil, de kan legges til brukerordboken. Hvis du gjør en egendefinert ordbok til en del av prosjektets kildekode, så kan stavekvalitetsporten formuleres på denne måten: kjører aspell med en standard og tilpasset ordbok bør ikke finner ingen stavefeil.

Om viktigheten av å fikse analysatorversjonen

Avslutningsvis er poenget å merke seg at uansett hvordan du implementerer analyse i leveringspipeline, må versjonen av analysatoren fikses. Hvis du lar analysatoren oppdateres spontant, kan nye defekter "dukke opp" når du setter sammen neste pull-forespørsel, som ikke er relatert til kodeendringer, men er relatert til det faktum at den nye analysatoren ganske enkelt er i stand til å finne flere defekter - og dette vil bryte prosessen med å akseptere pull-forespørsler. Oppgradering av en analysator bør være en bevisst handling. Imidlertid er stiv fiksering av versjonen av hver monteringskomponent generelt et nødvendig krav og et tema for en separat diskusjon.

Funn

  • Statisk analyse vil ikke finne feil for deg og vil ikke forbedre kvaliteten på produktet ditt som et resultat av en enkelt applikasjon. En positiv effekt på kvaliteten kan bare oppnås gjennom konstant bruk under leveringsprosessen.
  • Å finne feil er ikke hovedoppgaven med analyse i det hele tatt; de aller fleste nyttige funksjoner er tilgjengelige i opensource-verktøy.
  • Implementer kvalitetsporter basert på resultatene av statisk analyse i det aller første stadiet av leveringsrørledningen, ved å bruke en "skralle" for eldre kode.

referanser

  1. Kontinuerlig Levering
  2. A. Kudryavtsev: Programanalyse: hvordan forstå at du er en god programmerer rapporter om forskjellige metoder for kodeanalyse (ikke bare statiske!)

Kilde: www.habr.com

Legg til en kommentar