Magpatupad ng static na pagsusuri sa proseso, sa halip na gamitin ito upang maghanap ng mga bug

Na-prompt akong isulat ang artikulong ito sa pamamagitan ng malaking dami ng mga materyales sa static na pagsusuri na lalong dumarating sa aking atensyon. Una, ito PVS-studio blog, na aktibong nagpo-promote ng sarili sa HabrΓ© sa tulong ng mga pagsusuri ng mga error na natagpuan ng kanilang tool sa mga open source na proyekto. Kamakailan ay ipinatupad ang PVS-studio Suporta sa Java, at, siyempre, ang mga developer ng IntelliJ IDEA, na ang built-in na analyzer ay marahil ang pinaka-advanced para sa Java ngayon, hindi makalayo.

Kapag nagbabasa ng mga naturang review, naramdaman mo na pinag-uusapan natin ang tungkol sa isang magic elixir: pindutin ang pindutan, at narito ito - isang listahan ng mga depekto sa harap ng iyong mga mata. Mukhang habang umuunlad ang mga analyzer, mas maraming bug ang awtomatikong makikita, at ang mga produktong na-scan ng mga robot na ito ay magiging mas mahusay at mas mahusay, nang walang anumang pagsisikap sa aming bahagi.

Ngunit walang mga magic elixir. Gusto kong pag-usapan kung ano ang karaniwang hindi pinag-uusapan sa mga post tulad ng "narito ang mga bagay na mahahanap ng ating robot": kung ano ang hindi magagawa ng mga analyzer, ano ang kanilang tunay na tungkulin at lugar sa proseso ng paghahatid ng software, at kung paano ipatupad ang mga ito nang tama .

Magpatupad ng static na pagsusuri sa proseso, sa halip na gamitin ito upang maghanap ng mga bug
Ratchet (pinagmulan: Wikipedia).

Ano ang hindi kailanman magagawa ng mga static analyzer

Ano ang source code analysis, mula sa praktikal na pananaw? Nagbibigay kami ng ilang source code bilang input, at bilang output, sa maikling panahon (mas maikli kaysa sa pagpapatakbo ng mga pagsubok) nakakakuha kami ng ilang impormasyon tungkol sa aming system. Ang pangunahing at hindi malulutas na limitasyon sa matematika ay ang makakakuha lamang tayo ng isang medyo makitid na klase ng impormasyon sa ganitong paraan.

Ang pinakatanyag na halimbawa ng isang problema na hindi malulutas gamit ang static na pagsusuri ay problema sa shutdown: Ito ay isang theorem na nagpapatunay na imposibleng bumuo ng isang pangkalahatang algorithm na maaaring matukoy mula sa source code ng isang programa kung ito ay mag-loop o magwawakas sa isang takdang panahon. Ang isang extension ng teorama na ito ay Ang teorama ni Rice, na nagsasaad na para sa anumang di-maliit na pag-aari ng mga computable na function, ang pagtukoy kung ang isang arbitrary na programa ay nagsusuri ng isang function na may ganoong katangian ay isang algorithmically intractable na problema. Halimbawa, imposibleng magsulat ng isang analyzer na maaaring matukoy mula sa anumang source code kung ang program na sinusuri ay isang pagpapatupad ng isang algorithm na kinakalkula, halimbawa, ang squaring ng isang integer.

Kaya, ang pag-andar ng mga static analyzer ay may hindi malulutas na mga limitasyon. Ang isang static na analyzer ay hindi kailanman makaka-detect sa lahat ng mga kaso tulad ng, halimbawa, ang paglitaw ng isang "null pointer exception" sa mga wikang nagpapahintulot sa halaga ng null, o sa lahat ng mga kaso upang matukoy ang paglitaw ng isang " attribute not found" sa mga dynamic na na-type na wika. Ang magagawa lang ng pinaka-advanced na static analyzer ay i-highlight ang mga espesyal na kaso, ang bilang nito, sa lahat ng posibleng problema sa iyong source code, ay, nang walang pagmamalabis, isang drop sa bucket.

Ang static na pagsusuri ay hindi tungkol sa paghahanap ng mga bug

Mula sa itaas, ang konklusyon ay sumusunod: ang static na pagsusuri ay hindi isang paraan ng pagbabawas ng bilang ng mga depekto sa isang programa. Gusto kong sabihin na: kapag inilapat sa iyong proyekto sa unang pagkakataon, makakahanap ito ng "kawili-wiling" mga lugar sa code, ngunit, malamang, hindi ito makakahanap ng anumang mga depekto na makakaapekto sa kalidad ng iyong programa.

Ang mga halimbawa ng mga depekto na awtomatikong natagpuan ng mga analyzer ay kahanga-hanga, ngunit hindi natin dapat kalimutan na ang mga halimbawang ito ay natagpuan sa pamamagitan ng pag-scan sa isang malaking hanay ng malalaking codebase. Sa parehong prinsipyo, ang mga hacker na may pagkakataong sumubok ng ilang simpleng password sa malaking bilang ng mga account sa kalaunan ay mahahanap ang mga account na iyon na may simpleng password.

Nangangahulugan ba ito na hindi dapat gamitin ang static na pagsusuri? Syempre hindi! At para sa eksaktong parehong dahilan na ito ay nagkakahalaga ng pagsuri sa bawat bagong password upang matiyak na ito ay kasama sa stop list ng "simple" na mga password.

Ang static na pagsusuri ay higit pa sa paghahanap ng mga bug

Sa katunayan, ang mga problema na praktikal na nalutas sa pamamagitan ng pagsusuri ay mas malawak. Pagkatapos ng lahat, sa pangkalahatan, ang static na pagsusuri ay anumang pag-verify ng mga source code na isinasagawa bago sila ilunsad. Narito ang ilang bagay na maaari mong gawin:

  • Sinusuri ang istilo ng coding sa pinakamalawak na kahulugan ng salita. Kabilang dito ang parehong pagsuri sa pag-format, paghahanap para sa paggamit ng mga walang laman/dagdag na panaklong, pagtatakda ng mga threshold sa mga sukatan tulad ng bilang ng mga linya/cyclomatic complexity ng isang pamamaraan, atbp. - anumang bagay na posibleng humadlang sa pagiging madaling mabasa at mapanatili ng code. Sa Java, ang naturang tool ay Checkstyle, sa Python - flake8. Ang mga programa ng klase na ito ay karaniwang tinatawag na "linters."
  • Hindi lamang executable code ang maaaring masuri. Ang mga mapagkukunang file gaya ng JSON, YAML, XML, .properties ay maaaring (at dapat!) awtomatikong suriin para sa bisa. Pagkatapos ng lahat, mas mahusay na malaman na ang istraktura ng JSON ay nasira dahil sa ilang hindi ipinares na mga quote sa isang maagang yugto ng awtomatikong pag-verify ng Pull Request kaysa sa panahon ng pagpapatupad ng pagsubok o oras ng pagtakbo? Available ang mga naaangkop na tool: hal. YAMLlint, JSONLint.
  • Ang compilation (o pag-parse para sa mga dynamic na programming language) ay isa ring uri ng static analysis. Sa pangkalahatan, ang mga compiler ay may kakayahang gumawa ng mga babala na nagpapahiwatig ng mga problema sa kalidad ng source code at hindi dapat balewalain.
  • Minsan ang compilation ay higit pa sa pag-compile ng executable code. Halimbawa, kung mayroon kang dokumentasyon sa format AsciiDoctor, pagkatapos ay sa sandaling gawin itong HTML/PDF ang handler ng AsciiDoctor (Maven plugin) ay maaaring magbigay ng mga babala, halimbawa, tungkol sa mga sirang panloob na link. At ito ay isang magandang dahilan upang hindi tanggapin ang Pull Request na may mga pagbabago sa dokumentasyon.
  • Ang spell checking ay isa ring uri ng static analysis. Kagamitan aspell ay nasusuri ang spelling hindi lamang sa dokumentasyon, kundi pati na rin sa mga source code ng programa (mga komento at literal) sa iba't ibang mga programming language, kabilang ang C/C++, Java at Python. Ang isang error sa spelling sa user interface o dokumentasyon ay isa ring depekto!
  • Mga pagsubok sa pagsasaayos (tungkol sa kung ano ang mga ito - tingnan. ito ΠΈ ito mga ulat), bagama't naisakatuparan sa isang unit test runtime gaya ng pytest, sa katunayan ay isa ring uri ng static na pagsusuri, dahil hindi sila nagpapatupad ng mga source code sa panahon ng kanilang pagpapatupad.

Tulad ng nakikita mo, ang paghahanap ng mga bug sa listahang ito ay gumaganap ng hindi gaanong mahalagang papel, at lahat ng iba ay magagamit sa pamamagitan ng paggamit ng mga libreng open source na tool.

Alin sa mga ganitong uri ng static na pagsusuri ang dapat mong gamitin sa iyong proyekto? Siyempre, mas marami ang mas mahusay! Ang pangunahing bagay ay upang ipatupad ito nang tama, na tatalakayin pa.

Ang pipeline ng paghahatid bilang isang multi-stage na filter at static na pagsusuri bilang unang yugto nito

Ang klasikong metapora para sa tuluy-tuloy na pagsasama ay isang pipeline kung saan dumadaloy ang mga pagbabago, mula sa mga pagbabago sa source code hanggang sa paghahatid sa produksyon. Ang karaniwang pagkakasunud-sunod ng mga yugto sa pipeline na ito ay ganito ang hitsura:

  1. static na pagsusuri
  2. compilation
  3. mga pagsubok sa yunit
  4. mga pagsubok sa pagsasama-sama
  5. Mga pagsubok sa UI
  6. manu-manong pagsusuri

Ang mga pagbabagong tinanggihan sa Nth stage ng pipeline ay hindi ililipat sa stage N+1.

Bakit eksakto sa ganitong paraan at hindi kung hindi man? Sa bahagi ng pagsubok ng pipeline, makikilala ng mga tagasubok ang kilalang testing pyramid.

Magpatupad ng static na pagsusuri sa proseso, sa halip na gamitin ito upang maghanap ng mga bug
Subukan ang pyramid. Pinagmulan: artikulo Martin Fowler.

Sa ilalim ng pyramid na ito ay mga pagsubok na mas madaling isulat, mas mabilis na maisagawa, at walang posibilidad na mabigo. Samakatuwid, dapat magkaroon ng higit pa sa kanila, dapat nilang saklawin ang higit pang code at i-execute muna. Sa tuktok ng pyramid, ang kabaligtaran ay totoo, kaya ang bilang ng integration at UI test ay dapat bawasan sa kinakailangang minimum. Ang tao sa kadena na ito ay ang pinakamahal, mabagal at hindi mapagkakatiwalaang mapagkukunan, kaya siya ay nasa pinakadulo at nagsasagawa lamang ng trabaho kung ang mga nakaraang yugto ay walang nakitang anumang mga depekto. Gayunpaman, ang parehong mga prinsipyo ay ginagamit upang bumuo ng isang pipeline sa mga bahagi na hindi direktang nauugnay sa pagsubok!

Gusto kong mag-alok ng isang pagkakatulad sa anyo ng isang multi-stage na sistema ng pagsasala ng tubig. Ang maruming tubig (nagbabago na may mga depekto) ay ibinibigay sa input; sa output dapat tayong tumanggap ng malinis na tubig, kung saan ang lahat ng hindi gustong mga kontaminante ay naalis na.

Magpatupad ng static na pagsusuri sa proseso, sa halip na gamitin ito upang maghanap ng mga bug
Multi-stage na filter. Pinagmulan: Wikimedia Commons

Tulad ng alam mo, ang mga filter ng paglilinis ay idinisenyo upang ang bawat kasunod na kaskad ay makapag-filter ng mas pinong bahagi ng mga kontaminant. Kasabay nito, ang mga coarser purification cascades ay may mas mataas na throughput at mas mababang gastos. Sa aming pagkakatulad, nangangahulugan ito na ang mga gate ng kalidad ng pag-input ay mas mabilis, nangangailangan ng mas kaunting pagsisikap upang magsimula, at sila mismo ay mas hindi mapagpanggap sa pagpapatakbo - at ito ang pagkakasunud-sunod kung saan itinayo ang mga ito. Ang papel na ginagampanan ng static analysis, na, tulad ng naiintindihan na natin ngayon, ay may kakayahang alisin lamang ang mga pinakamalalaking depekto, ay ang papel ng grid ng "putik" sa pinakadulo simula ng filter cascade.

Ang static na pagsusuri mismo ay hindi nagpapabuti sa kalidad ng panghuling produkto, tulad ng isang "mud filter" na hindi ginagawang maiinom ang tubig. Gayunpaman, kasabay ng iba pang mga elemento ng pipeline, ang kahalagahan nito ay halata. Bagama't sa isang multistage na filter, ang mga yugto ng output ay potensyal na may kakayahang makuha ang lahat ng ginagawa ng mga yugto ng pag-input, malinaw kung anong mga kahihinatnan ang magreresulta mula sa pagtatangkang gawin ang mga yugto ng fine-purification nang nag-iisa, nang walang mga yugto ng pag-input.

Ang layunin ng "mud trap" ay upang mapawi ang mga kasunod na cascades mula sa paghuli ng napakalaking depekto. Halimbawa, sa pinakamababa, ang taong gumagawa ng pagsusuri ng code ay hindi dapat magambala ng maling pagkaka-format ng code at mga paglabag sa mga itinatag na pamantayan ng coding (tulad ng mga karagdagang panaklong o masyadong malalim na nested na sangay). Ang mga bug tulad ng mga NPE ay dapat mahuli sa pamamagitan ng mga unit test, ngunit kung bago pa man ang pagsubok ay ipahiwatig sa amin ng analyzer na ang isang bug ay tiyak na mangyayari, ito ay makabuluhang mapabilis ang pag-aayos nito.

Naniniwala ako na malinaw na ngayon kung bakit ang static na pagsusuri ay hindi nagpapabuti sa kalidad ng produkto kung ginagamit paminsan-minsan, at dapat na patuloy na gamitin upang i-filter ang mga pagbabago na may malalaking depekto. Ang tanong kung ang paggamit ng static analyzer ay mapapabuti ang kalidad ng iyong produkto ay halos katumbas ng pagtatanong, "Mapapabuti ba ang tubig na kinuha mula sa isang maruming pond sa kalidad ng pag-inom kung ito ay ipapasa sa isang colander?"

Pagpapatupad sa isang legacy na proyekto

Isang mahalagang praktikal na tanong: kung paano ipatupad ang static na pagsusuri sa tuluy-tuloy na proseso ng pagsasama bilang isang "gate ng kalidad"? Sa kaso ng mga awtomatikong pagsubok, ang lahat ay halata: mayroong isang hanay ng mga pagsubok, ang kabiguan ng alinman sa mga ito ay sapat na dahilan upang maniwala na ang pagpupulong ay hindi pumasa sa kalidad ng gate. Nabigo ang pagtatangkang mag-install ng gate sa parehong paraan batay sa mga resulta ng isang static na pagsusuri: masyadong maraming babala sa pagsusuri sa legacy code, hindi mo nais na ganap na balewalain ang mga ito, ngunit imposible ring ihinto ang pagpapadala ng produkto dahil lang naglalaman ito ng mga babala ng analyzer.

Kapag ginamit sa unang pagkakataon, ang analyzer ay gumagawa ng isang malaking bilang ng mga babala sa anumang proyekto, ang karamihan sa mga ito ay hindi nauugnay sa wastong paggana ng produkto. Imposibleng itama ang lahat ng mga komentong ito nang sabay-sabay, at marami ang hindi kinakailangan. Pagkatapos ng lahat, alam namin na ang aming produkto sa kabuuan ay gumagana, bago pa man ipakilala ang static na pagsusuri!

Bilang resulta, marami ang limitado sa paminsan-minsang paggamit ng static na pagsusuri, o ginagamit lamang ito sa mode ng impormasyon, kapag ang ulat ng analyzer ay inilabas lamang sa panahon ng pagpupulong. Ito ay katumbas ng kawalan ng anumang pagsusuri, dahil kung marami na tayong babala, kung gayon ang paglitaw ng isa pa (kahit gaano kalubha) kapag binabago ang code ay hindi napapansin.

Ang mga sumusunod na paraan ng pagpapakilala ng mga gate ng kalidad ay kilala:

  • Pagtatakda ng limitasyon sa kabuuang bilang ng mga babala o ang bilang ng mga babala na hinati sa bilang ng mga linya ng code. Hindi ito gumagana, dahil ang gayong gate ay malayang nagpapahintulot sa mga pagbabago na may mga bagong depekto na dumaan, hangga't ang kanilang limitasyon ay hindi lalampas.
  • Ang pag-aayos, sa isang tiyak na sandali, ang lahat ng mga lumang babala sa code bilang hindi pinansin, at pagtanggi na bumuo kapag may mga bagong babala. Ang functionality na ito ay ibinibigay ng PVS-studio at ilang online na mapagkukunan, halimbawa, Codacy. Hindi ako nagkaroon ng pagkakataon na magtrabaho sa PVS-studio, tulad ng para sa aking karanasan sa Codacy, ang kanilang pangunahing problema ay ang pagtukoy kung ano ang "luma" at kung ano ang isang "bago" na error ay isang medyo kumplikadong algorithm na hindi palaging gumagana. nang tama, lalo na kung ang mga file ay binago o pinalitan ng pangalan. Sa aking karanasan, maaaring balewalain ng Codacy ang mga bagong babala sa isang pull request, habang hindi pumasa sa pull request dahil sa mga babala na walang kaugnayan sa mga pagbabago sa code ng isang partikular na PR.
  • Sa aking palagay, ang pinakamabisang solusyon ay ang inilarawan sa aklat Patuloy na Paghahatid "paraan ng ratcheting". Ang pangunahing ideya ay ang bilang ng mga static na babala sa pagsusuri ay isang pag-aari ng bawat release, at mga pagbabago lang ang pinapayagan na hindi nagpapataas sa kabuuang bilang ng mga babala.

Ratchet

Gumagana ito sa ganitong paraan:

  1. Sa paunang yugto, isang talaan ang ginawa sa metadata tungkol sa pagpapalabas ng bilang ng mga babala sa code na natagpuan ng mga analyzer. Kaya, kapag bumuo ka ng upstream, ang iyong repository manager ay nagsusulat hindi lamang ng "release 7.0.2", ngunit "release 7.0.2 containing 100500 checkstyle warnings." Kung gumagamit ka ng advanced na repository manager (gaya ng Artifactory), ang pag-iimbak ng naturang metadata tungkol sa iyong release ay madali.
  2. Ngayon, ang bawat pull request, kapag binuo, ay inihahambing ang bilang ng mga nagresultang babala sa bilang ng mga babala na available sa kasalukuyang release. Kung humahantong ang PR sa isang pagtaas sa bilang na ito, kung gayon ang code ay hindi pumasa sa kalidad ng gate para sa static na pagsusuri. Kung ang bilang ng mga babala ay bumaba o hindi nagbabago, pagkatapos ito ay pumasa.
  3. Sa susunod na release, ang muling kalkuladong bilang ng mga babala ay ire-record muli sa release metadata.

Kaya unti-unti ngunit tuluy-tuloy (tulad ng kapag gumagana ang isang ratchet), ang bilang ng mga babala ay magiging zero. Siyempre, ang sistema ay maaaring malinlang sa pamamagitan ng pagpapakilala ng isang bagong babala, ngunit pagwawasto sa ibang tao. Ito ay normal, dahil sa isang mahabang distansya ay nagbibigay ito ng mga resulta: ang mga babala ay naitama, bilang isang panuntunan, hindi isa-isa, ngunit sa isang grupo ng isang tiyak na uri nang sabay-sabay, at ang lahat ng madaling maalis na mga babala ay mabilis na tinanggal.

Ipinapakita ng graph na ito ang kabuuang bilang ng mga babala ng Checkstyle para sa anim na buwang pagpapatakbo ng naturang "ratchet" sa isa sa aming mga proyekto sa OpenSource. Ang bilang ng mga babala ay nabawasan ng isang order ng magnitude, at ito ay natural na nangyari, kasabay ng pagbuo ng produkto!

Magpatupad ng static na pagsusuri sa proseso, sa halip na gamitin ito upang maghanap ng mga bug

Gumagamit ako ng binagong bersyon ng pamamaraang ito, hiwalay na nagbibilang ng mga babala sa pamamagitan ng module ng proyekto at tool sa pagsusuri, na nagreresulta sa isang YAML file na may build metadata na mukhang ganito:

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

Sa anumang advanced na CI system, maaaring ipatupad ang ratchet para sa anumang static na tool sa pagsusuri nang hindi umaasa sa mga plugin at third-party na tool. Ang bawat analyzer ay gumagawa ng sarili nitong ulat sa isang simpleng text o XML na format na madaling suriin. Ang natitira na lang ay isulat ang kinakailangang lohika sa script ng CI. Makikita mo kung paano ito ipinapatupad sa aming mga open source na proyekto batay sa Jenkins at Artifactory dito o dito. Ang parehong mga halimbawa ay nakasalalay sa silid-aklatan ratchetlib: paraan countWarnings() binibilang ang mga xml tag sa mga file na nabuo ng Checkstyle at Spotbugs sa karaniwang paraan, at compareWarningMaps() nagpapatupad ng parehong ratchet, naghahagis ng error kapag tumaas ang bilang ng mga babala sa alinman sa mga kategorya.

Ang isang kawili-wiling pagpapatupad ng "ratchet" ay posible para sa pagsusuri sa pagbabaybay ng mga komento, literal ng teksto at dokumentasyon gamit ang aspell. Tulad ng alam mo, kapag sinusuri ang spelling, hindi lahat ng mga salitang hindi alam sa karaniwang diksyunaryo ay mali; maaari silang idagdag sa diksyunaryo ng gumagamit. Kung gagawin mo ang isang custom na diksyunaryo na bahagi ng source code ng proyekto, ang gate ng kalidad ng spelling ay maaaring mabuo sa ganitong paraan: pagpapatakbo ng aspell na may karaniwan at custom na diksyunaryo hindi dapat walang mahanap na mga error sa spelling.

Tungkol sa kahalagahan ng pag-aayos ng bersyon ng analyzer

Sa konklusyon, ang puntong dapat tandaan ay kahit paano mo ipatupad ang pagsusuri sa iyong pipeline ng paghahatid, dapat ayusin ang bersyon ng analyzer. Kung pinapayagan mo ang analyzer na kusang mag-update, pagkatapos ay kapag nag-assemble ng susunod na pull request, ang mga bagong depekto ay maaaring "mag-pop up" na hindi nauugnay sa mga pagbabago sa code, ngunit nauugnay sa katotohanan na ang bagong analyzer ay madaling makahanap ng higit pang mga depekto - at ito ay sisira sa iyong proseso ng pagtanggap ng mga pull request. Ang pag-upgrade ng isang analyzer ay dapat na isang malay na aksyon. Gayunpaman, ang mahigpit na pag-aayos ng bersyon ng bawat bahagi ng pagpupulong ay karaniwang isang kinakailangang kinakailangan at isang paksa para sa isang hiwalay na talakayan.

Natuklasan

  • Ang static na pagsusuri ay hindi makakahanap ng mga bug para sa iyo at hindi mapapabuti ang kalidad ng iyong produkto bilang resulta ng isang application. Ang isang positibong epekto sa kalidad ay makakamit lamang sa pamamagitan ng patuloy na paggamit nito sa panahon ng proseso ng paghahatid.
  • Ang paghahanap ng mga bug ay hindi ang pangunahing gawain ng pagsusuri; ang karamihan sa mga kapaki-pakinabang na pag-andar ay magagamit sa mga tool na opensource.
  • Magpatupad ng mga de-kalidad na gate batay sa mga resulta ng static na pagsusuri sa pinakaunang yugto ng pipeline ng paghahatid, gamit ang "ratchet" para sa legacy code.

sanggunian

  1. Patuloy na Paghahatid
  2. A. Kudryavtsev: Pagsusuri ng programa: kung paano maunawaan na ikaw ay isang mahusay na programmer ulat sa iba't ibang paraan ng pagsusuri ng code (hindi lamang static!)

Pinagmulan: www.habr.com

Magdagdag ng komento