Laksanakan analisis statik ke dalam proses, bukannya mencari pepijat dengannya

Saya mendapat inspirasi untuk menulis artikel ini dengan sejumlah besar bahan mengenai analisis statik yang semakin banyak ditemui. Pertama, ini Blog PVS-studio, yang secara aktif mempromosikan dirinya di HabrΓ© dengan ulasan tentang ralat yang ditemui oleh alat mereka dalam projek sumber terbuka. PVS-studio baru-baru ini dilaksanakan Sokongan Java, dan, sudah tentu, pembangun IntelliJ IDEA, yang penganalisis terbina dalam mungkin yang paling maju untuk Java hari ini, tidak dapat berjauhan.

Apabila membaca ulasan sedemikian, seseorang mendapat perasaan bahawa kita bercakap tentang elixir ajaib: tekan butang, dan inilah - senarai kecacatan di hadapan mata anda. Nampaknya apabila penganalisis bertambah baik, secara automatik akan terdapat lebih banyak pepijat, dan produk yang diimbas oleh robot ini akan menjadi lebih baik dan lebih baik, tanpa sebarang usaha daripada pihak kami.

Tetapi tidak ada elixir ajaib. Saya ingin bercakap tentang perkara yang biasanya tidak dibincangkan dalam siaran seperti "ini adalah perkara yang boleh ditemui oleh robot kami": perkara yang tidak dapat dilakukan oleh penganalisis, apakah peranan dan tempat sebenar mereka dalam proses penghantaran perisian, dan cara melaksanakannya dengan betul.

Laksanakan analisis statik ke dalam proses, bukannya mencari pepijat dengannya
Ratchet (sumber: wikipedia).

Perkara yang Tidak Boleh Dilakukan oleh Penganalisis Statik

Apakah, dari sudut pandangan praktikal, analisis kod sumber? Kami memberi suapan dalam beberapa sumber dan dalam masa yang singkat (lebih singkat daripada menjalankan ujian) kami mendapat beberapa maklumat tentang sistem kami. Batasan asas dan tidak dapat diatasi secara matematik ialah kita hanya boleh memperoleh kelas maklumat yang agak sempit.

Contoh paling terkenal masalah yang tidak dapat diselesaikan dengan analisis statik ialah hentikan masalah: ini adalah teorem yang membuktikan bahawa adalah mustahil untuk membangunkan algoritma umum yang akan menentukan daripada kod sumber program sama ada ia akan gelung atau ditamatkan dalam masa yang terhad. Lanjutan daripada teorem ini ialah Teorem berasmenyatakan bahawa untuk mana-mana sifat bukan remeh bagi fungsi boleh dikira, menentukan sama ada program arbitrari menilai fungsi dengan sifat sedemikian adalah masalah yang tidak boleh diselesaikan secara algoritma. Sebagai contoh, adalah mustahil untuk menulis penganalisis yang boleh menentukan daripada mana-mana kod sumber sama ada program yang dianalisis adalah pelaksanaan algoritma yang mengira, katakan, menduakan integer.

Oleh itu, kefungsian penganalisis statik mempunyai had yang tidak dapat diatasi. Penganalisis statik tidak akan dapat menentukan dalam semua kes perkara seperti, sebagai contoh, kejadian "pengecualian penuding nol" dalam bahasa yang boleh dibatalkan, atau dalam semua kes untuk menentukan kejadian "atribut tidak dijumpai" dalam bahasa dengan menaip dinamik. Apa yang boleh dilakukan oleh penganalisis statik tercanggih ialah menyerlahkan kes khas, bilangannya, antara semua kemungkinan masalah dengan kod sumber anda, adalah, tanpa keterlaluan, penurunan di lautan.

Analisis statik bukan mencari pepijat

Kesimpulan berikut dari yang di atas: analisis statik bukanlah satu cara untuk mengurangkan bilangan kecacatan dalam program. Saya akan berani mengatakan bahawa apabila ia mula-mula digunakan pada projek anda, ia akan menemui tempat "lucu" dalam kod, tetapi, kemungkinan besar, tidak akan menemui sebarang kecacatan yang menjejaskan kualiti program anda.

Contoh-contoh kecacatan yang ditemui secara automatik oleh penganalisis adalah mengagumkan, tetapi kita tidak boleh lupa bahawa contoh ini ditemui dengan mengimbas satu set besar pangkalan kod yang besar. Dengan prinsip yang sama, keropok yang dapat mencuba beberapa kata laluan mudah pada sejumlah besar akaun akhirnya menemui akaun tersebut yang mempunyai kata laluan mudah.

Adakah ini bermakna analisis statik tidak boleh digunakan? Sudah tentu tidak! Dan betul-betul atas sebab yang sama mengapa ia patut menyemak setiap kata laluan baharu untuk masuk ke senarai hentian kata laluan "mudah".

Analisis statik adalah lebih daripada mencari pepijat

Malah, masalah yang diselesaikan secara praktikal melalui analisis adalah lebih luas. Lagipun, secara amnya, analisis statik ialah sebarang semakan kod sumber yang dijalankan sebelum ia dilancarkan. Berikut ialah beberapa perkara yang boleh anda lakukan:

  • Menyemak gaya pengekodan dalam erti kata yang paling luas. Ini termasuk kedua-dua menyemak pemformatan dan mencari penggunaan kurungan kosong/tambahan, menetapkan ambang pada metrik seperti bilangan baris / kerumitan kaedah siklomatik, dsb. - segala-galanya yang berpotensi menjadikan kod lebih mudah dibaca dan diselenggara. Di Java alat ini ialah Checkstyle, dalam Python ia adalah flake8. Program kelas ini biasanya dipanggil "linters".
  • Bukan sahaja kod boleh laku boleh dianalisis. Fail sumber seperti JSON, YAML, XML, .properties boleh (dan harus!) disemak secara automatik untuk kesahihannya. Bukankah lebih baik untuk mengetahui bahawa disebabkan beberapa petikan yang tidak berpasangan, struktur JSON rosak pada peringkat awal pengesahan Permintaan Tarik automatik daripada semasa melaksanakan ujian atau pada masa Jalankan? Alat yang sesuai tersedia: contohnya, YAMLlint, JSONLint.
  • Penyusunan (atau penghuraian untuk bahasa pengaturcaraan dinamik) juga merupakan sejenis analisis statik. Sebagai peraturan, penyusun mampu mengeluarkan amaran yang menunjukkan masalah dengan kualiti kod sumber, dan ia tidak boleh diabaikan.
  • Kadangkala kompilasi bukan hanya tentang menyusun kod boleh laku. Sebagai contoh, jika anda mempunyai dokumentasi dalam format AsciiDoctor, kemudian pada saat transformasinya menjadi pengendali HTML/PDF AsciiDoctor (Pemalam Maven) boleh mengeluarkan amaran, contohnya, tentang pautan dalaman yang rosak. Dan ini adalah alasan yang baik untuk tidak menerima Permintaan Tarik dengan perubahan dokumentasi.
  • Semakan ejaan juga merupakan sejenis analisis statik. Utiliti aspell boleh menyemak ejaan bukan sahaja dalam dokumentasi, tetapi juga dalam kod sumber program (komen dan literal) dalam bahasa pengaturcaraan yang berbeza, termasuk C/C++, Java dan Python. Ralat ejaan dalam antara muka pengguna atau dokumentasi juga merupakan kecacatan!
  • Ujian konfigurasi (untuk apa itu, lihat ini ΠΈ ini reports), walaupun ia dilaksanakan dalam runtime ujian unit seperti pytest, ia sebenarnya juga sejenis analisis statik, kerana ia tidak melaksanakan kod sumber semasa pelaksanaannya.

Seperti yang anda lihat, mencari pepijat dalam senarai ini mengambil peranan paling tidak penting, dan segala-galanya tersedia melalui penggunaan alat sumber terbuka percuma.

Antara jenis analisis statik berikut, yang manakah harus digunakan dalam projek anda? Sudah tentu, lebih banyak, lebih baik! Perkara utama adalah untuk melaksanakannya dengan betul, yang akan dibincangkan lebih lanjut.

Saluran paip penghantaran sebagai penapis berbilang peringkat dan analisis statik sebagai lata pertamanya

Metafora klasik untuk penyepaduan berterusan ialah saluran paip (saluran paip) yang melaluinya perubahan mengalir - daripada menukar kod sumber kepada penghantaran kepada pengeluaran. Urutan standard peringkat saluran paip ini kelihatan seperti ini:

  1. analisis statik
  2. kompilasi
  3. ujian unit
  4. ujian integrasi
  5. ujian UI
  6. pemeriksaan manual

Perubahan yang ditolak pada peringkat N saluran paip tidak disebarkan ke peringkat N+1.

Kenapa betul-betul begini dan bukan sebaliknya? Dalam bahagian ujian saluran paip, penguji akan mengenali piramid ujian yang terkenal.

Laksanakan analisis statik ke dalam proses, bukannya mencari pepijat dengannya
Uji piramid. Sumber: artikel Martin Fowler.

Di bahagian bawah piramid ini terdapat ujian yang lebih mudah untuk ditulis, berjalan lebih pantas dan tidak cenderung kepada positif palsu. Oleh itu, perlu ada lebih banyak daripada mereka, mereka harus meliputi lebih banyak kod dan dilaksanakan terlebih dahulu. Di bahagian atas piramid, sebaliknya adalah benar, jadi bilangan integrasi dan ujian UI harus dikurangkan kepada minimum yang diperlukan. Orang dalam rantaian ini adalah sumber yang paling mahal, paling lambat dan paling tidak boleh dipercayai, jadi dia berada di penghujung dan melakukan kerja hanya jika peringkat sebelumnya tidak menemui sebarang kecacatan. Walau bagaimanapun, mengikut prinsip yang sama, saluran paip dibina di bahagian yang tidak berkaitan secara langsung dengan ujian!

Saya ingin menawarkan analogi dalam bentuk sistem penapisan air pelbagai peringkat. Air kotor dibekalkan kepada input (perubahan dengan kecacatan), pada output kita mesti mendapatkan air bersih, di mana semua bahan cemar yang tidak diingini dihapuskan.

Laksanakan analisis statik ke dalam proses, bukannya mencari pepijat dengannya
Penapis berbilang peringkat. Sumber: Wikimedia Commons

Seperti yang anda ketahui, penapis pembersihan direka sedemikian rupa sehingga setiap lata seterusnya boleh menapis pecahan bahan cemar yang semakin kecil. Pada masa yang sama, lata penulenan yang lebih kasar mempunyai daya pemprosesan yang lebih tinggi dan kos yang lebih rendah. Dalam analogi kami, ini bermakna bahawa pintu kualiti input lebih pantas, memerlukan sedikit usaha untuk dimulakan, dan ia sendiri lebih bersahaja dalam operasi - dan ini betul-betul urutan ia dibina. Peranan analisis statik, yang, seperti yang kita faham sekarang, mampu menghapuskan hanya kecacatan yang paling teruk, adalah peranan "lumpur" parut pada permulaan lata penapis.

Analisis statik dengan sendirinya tidak meningkatkan kualiti produk akhir, sama seperti "perangkap lumpur" tidak menjadikan air boleh diminum. Namun begitu, sama dengan elemen penghantar yang lain, kepentingannya adalah jelas. Walaupun dalam penapis berbilang peringkat, peringkat keluaran berkeupayaan untuk menangkap segala-galanya sama seperti peringkat input, adalah jelas akibat yang akan ditimbulkan oleh percubaan untuk bertahan dengan hanya peringkat penulenan halus, tanpa peringkat input.

Tujuan "pengumpul lumpur" adalah untuk memunggah lata berikutnya daripada menangkap kecacatan yang sangat teruk. Sebagai contoh, sekurang-kurangnya, penyemak kod tidak boleh terganggu oleh kod yang tidak diformatkan dengan betul dan pelanggaran norma pengekodan yang ditetapkan (seperti kurungan tambahan atau cawangan bersarang terlalu dalam). Pepijat seperti NPE harus ditangkap oleh ujian unit, tetapi jika sebelum ujian pun penganalisis menunjukkan kepada kita bahawa pepijat itu pasti akan berlaku, ini akan mempercepatkan pembetulannya dengan ketara.

Saya fikir kini jelas mengapa analisis statik tidak meningkatkan kualiti produk jika ia digunakan sekali-sekala, dan harus digunakan secara berterusan untuk menapis perubahan dengan kecacatan yang teruk. Bertanya sama ada menggunakan penganalisis statik akan meningkatkan kualiti produk anda secara kasarnya sama dengan bertanya "Adakah kualiti minuman air yang diambil dari kolam yang kotor akan bertambah baik jika ia disalurkan melalui colander?"

Pelaksanaan dalam projek warisan

Soalan praktikal yang penting: bagaimana untuk memperkenalkan analisis statik ke dalam proses penyepaduan berterusan sebagai "pintu kualiti"? Dalam kes ujian automatik, semuanya jelas: terdapat satu set ujian, kegagalan mana-mana daripada mereka adalah alasan yang mencukupi untuk mempercayai bahawa pemasangan tidak melepasi pintu kualiti. Percubaan untuk memasang get dengan cara yang sama berdasarkan hasil analisis statik gagal: terdapat terlalu banyak amaran analisis dalam kod warisan, anda tidak mahu mengabaikannya sepenuhnya, tetapi juga mustahil untuk menghentikan penghantaran a produk hanya kerana ia mengandungi amaran penganalisis.

Apabila digunakan buat kali pertama, penganalisis menghasilkan sejumlah besar amaran pada mana-mana projek, yang sebahagian besarnya tidak berkaitan dengan fungsi produk yang betul. Tidak mustahil untuk membetulkan semua komen ini sekaligus, dan kebanyakannya tidak diperlukan. Lagipun, kami tahu bahawa produk kami secara keseluruhannya berfungsi, walaupun sebelum pengenalan analisis statik!

Akibatnya, ramai orang mengehadkan diri mereka kepada penggunaan episodik analisis statik, atau menggunakannya hanya dalam mod makluman, apabila laporan penganalisis hanya dikeluarkan semasa pemasangan. Ini bersamaan dengan ketiadaan sebarang analisis, kerana jika kita sudah mempunyai banyak amaran, maka kejadian yang lain (tidak kira betapa seriusnya) apabila perubahan kod tidak disedari.

Cara berikut untuk memperkenalkan gerbang berkualiti diketahui:

  • Menetapkan had pada jumlah amaran, atau bilangan amaran dibahagikan dengan bilangan baris kod. Ini tidak berfungsi dengan baik, kerana gerbang sedemikian bebas melangkau perubahan dengan kecacatan baru sehingga hadnya melebihi.
  • Membetulkan, pada satu ketika, semua amaran lama dalam kod sebagai diabaikan, dan gagal membina apabila amaran baharu berlaku. Fungsi ini disediakan oleh PVS-studio dan beberapa sumber dalam talian, seperti Codacy. Saya tidak mempunyai peluang untuk bekerja di PVS-studio, bagi pengalaman saya dengan Codacy, masalah utama mereka ialah takrifan apa itu ralat "lama" dan apa itu "baru" adalah algoritma yang agak rumit yang tidak sentiasa berfungsi dengan betul, terutamanya jika fail banyak diubah suai atau dinamakan semula. Dalam ingatan saya, Codacy boleh melangkau amaran baharu dalam permintaan tarik, dan pada masa yang sama tidak melangkau permintaan tarik kerana amaran yang tidak berkaitan dengan perubahan dalam kod PR ini.
  • Pada pendapat saya, penyelesaian yang paling berkesan diterangkan dalam buku Penghantaran berterusan kaedah "ratcheting". Idea utama ialah sifat setiap keluaran ialah bilangan amaran analisis statik, dan hanya perubahan yang tidak meningkatkan jumlah amaran dibenarkan.

Ratchet

Ia berfungsi seperti ini:

  1. Pada peringkat awal, rekod dalam metadata keluaran bilangan amaran dalam kod yang ditemui oleh penganalisis dilaksanakan. Oleh itu, apabila anda membina huluan, pengurus repositori anda ditulis bukan sahaja "keluaran 7.0.2", tetapi "keluarkan 7.0.2 yang mengandungi amaran 100500 Checkstyle". Jika anda menggunakan pengurus repositori lanjutan (seperti Artifactory), adalah mudah untuk menyimpan metadata sedemikian tentang keluaran anda.
  2. Kini setiap permintaan tarik pada binaan membandingkan bilangan amaran yang diterima dengan nombor dalam keluaran semasa. Jika PR membawa kepada peningkatan dalam bilangan ini, maka kod itu tidak melepasi pintu kualiti dalam analisis statik. Jika bilangan amaran berkurangan atau tidak berubah, maka ia berlalu.
  3. Pada keluaran seterusnya, bilangan amaran yang dikira semula akan ditulis kembali ke metadata keluaran.

Jadi sedikit demi sedikit, tetapi secara berterusan (seperti dengan ratchet), bilangan amaran akan cenderung kepada sifar. Sudah tentu, sistem boleh tertipu dengan memperkenalkan amaran baharu, tetapi membetulkan amaran orang lain. Ini adalah perkara biasa, kerana dalam jangka panjang ia memberikan hasil: amaran ditetapkan, sebagai peraturan, bukan satu demi satu, tetapi serta-merta oleh sekumpulan jenis tertentu, dan semua amaran yang mudah dihapuskan dengan cepat dihapuskan.

Graf ini menunjukkan jumlah amaran Gaya Semak selama enam bulan operasi "ratchet" sedemikian salah satu projek sumber terbuka kami. Bilangan amaran telah berkurangan mengikut urutan magnitud, dan ini berlaku secara semula jadi, selari dengan pembangunan produk!

Laksanakan analisis statik ke dalam proses, bukannya mencari pepijat dengannya

Saya menggunakan versi kaedah ini yang diubah suai, mengira amaran secara berasingan mengikut modul projek dan alat analisis, menghasilkan fail YAML dengan metadata pemasangan yang kelihatan seperti ini:

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

Dalam mana-mana sistem CI lanjutan, ratchet boleh dilaksanakan untuk sebarang alat analisis statik tanpa bergantung pada pemalam dan alat pihak ketiga. Setiap penganalisis menghasilkan laporannya dalam teks ringkas atau format XML yang mudah dihuraikan. Ia kekal untuk mendaftar hanya logik yang diperlukan dalam skrip CI. Anda boleh melihat cara ini dilaksanakan dalam projek sumber terbuka kami berdasarkan Jenkins dan Artifactory, anda boleh di sini atau di sini. Kedua-dua contoh adalah bergantung kepada perpustakaan ratchetlib: kaedah countWarnings() mengira teg xml dalam fail yang dijana oleh Checkstyle dan Spotbugs dengan cara biasa, dan compareWarningMaps() melaksanakan ratchet yang sama, melontar ralat apabila bilangan amaran dalam mana-mana kategori meningkat.

Pelaksanaan ratchet yang menarik adalah mungkin untuk analisis ejaan komen, literal teks dan dokumentasi menggunakan aspell. Seperti yang anda ketahui, semasa menyemak ejaan, tidak semua perkataan yang tidak diketahui dalam kamus standard adalah tidak betul, ia boleh ditambah pada kamus pengguna. Jika anda menjadikan kamus pengguna sebahagian daripada kod sumber projek, maka gerbang kualiti ejaan boleh dirumuskan seperti berikut: pelaksanaan aspell dengan standard dan kamus pengguna tidak boleh tidak menemui kesilapan ejaan.

Mengenai kepentingan membetulkan versi penganalisis

Sebagai kesimpulan, perkara berikut harus diperhatikan: tidak kira bagaimana anda melaksanakan analisis ke dalam saluran penghantaran anda, versi penganalisis mesti diperbaiki. Jika anda membenarkan penganalisis mengemas kini secara spontan, maka apabila membina permintaan tarik seterusnya, kecacatan baharu mungkin "muncul" yang tidak berkaitan dengan perubahan kod, tetapi berkaitan dengan fakta bahawa penganalisis baharu hanya dapat mencari lebih banyak kecacatan - dan ini akan memecahkan proses menerima permintaan tarik . Menaik taraf penganalisis harus menjadi tindakan sedar. Walau bagaimanapun, penetapan keras versi setiap komponen pemasangan adalah keperluan yang diperlukan secara umum dan topik untuk perbincangan yang berasingan.

Penemuan

  • Analisis statik tidak akan menemui pepijat untuk anda dan tidak akan meningkatkan kualiti produk anda hasil daripada satu aplikasi. Satu-satunya kesan positif terhadap kualiti adalah penggunaan berterusan semasa proses penghantaran.
  • Mencari pepijat bukanlah tugas utama analisis sama sekali, sebahagian besar fungsi berguna tersedia dalam alat sumber terbuka.
  • Laksanakan gerbang kualiti berdasarkan hasil analisis statik pada peringkat pertama saluran paip penghantaran, menggunakan ratchet untuk kod warisan.

rujukan

  1. Penghantaran berterusan
  2. A. Kudryavtsev: Analisis program: bagaimana untuk memahami bahawa anda adalah seorang pengaturcara yang baik melaporkan kaedah analisis kod yang berbeza (bukan sahaja statik!)

Sumber: www.habr.com

Tambah komen