Hataları bulmak için kullanmak yerine, sürece statik analiz uygulayın

Statik analizle ilgili giderek dikkatimi çeken çok sayıda materyal beni bu makaleyi yazmaya yöneltti. Öncelikle bu PVS stüdyosu bloguAçık kaynaklı projelerde araçları tarafından bulunan hataların incelenmesi sayesinde Habré'de aktif olarak tanıtımını yapan . Son zamanlarda PVS stüdyosu uygulandı Java desteğive elbette, yerleşik analizörü muhtemelen bugün Java için en gelişmiş çözümleyici olan IntelliJ IDEA'nın geliştiricileri, uzak duramadım.

Bu tür incelemeleri okurken, sihirli bir iksirden bahsettiğimiz hissine kapılıyorsunuz: düğmeye basın ve işte burada - gözlerinizin önünde kusurların bir listesi. Öyle görünüyor ki, analizörler geliştikçe, giderek daha fazla hata otomatik olarak bulunacak ve bu robotlar tarafından taranan ürünler, bizim hiçbir çabamız olmadan, giderek daha iyi hale gelecektir.

Ama sihirli iksirler yok. “İşte robotumuzun bulabileceği şeyler” gibi yazılarda genelde konuşulmayan şeylerden bahsetmek istiyorum: analizörlerin yapamadığı şeyler, yazılım teslim sürecindeki gerçek rolleri ve yerleri nedir ve bunların doğru şekilde nasıl uygulanacağı. .

Hataları bulmak için kullanmak yerine, sürece statik analiz uygulayın
Cırcır (kaynak: wikipedia).

Statik analizörlerin asla yapamayacağı şey

Pratik açıdan kaynak kodu analizi nedir? Bazı kaynak kodlarını girdi olarak veriyoruz ve çıktı olarak kısa sürede (test çalıştırmaktan çok daha kısa sürede) sistemimiz hakkında bazı bilgiler elde ediyoruz. Temel ve matematiksel olarak aşılamaz sınırlama, bu şekilde yalnızca oldukça dar bir bilgi sınıfı elde edebilmemizdir.

Statik analiz kullanılarak çözülemeyen bir problemin en ünlü örneği kapanma sorunu: Bu, bir programın kaynak kodundan sonlu bir zamanda döngü yapıp yapmayacağını veya sonlanacağını belirleyebilecek genel bir algoritma geliştirmenin imkansız olduğunu kanıtlayan bir teoremdir. Bu teoremin bir uzantısı Rice'ın teoremiHesaplanabilir işlevlerin önemsiz olmayan herhangi bir özelliği için, rastgele bir programın böyle bir özelliğe sahip bir işlevi değerlendirip değerlendirmediğini belirlemenin algoritmik olarak çözülmesi zor bir sorun olduğunu belirtir. Örneğin, analiz edilen programın, örneğin bir tamsayının karesini hesaplayan bir algoritmanın uygulaması olup olmadığını herhangi bir kaynak kodundan belirleyebilecek bir analizör yazmak imkansızdır.

Bu nedenle, statik analizörlerin işlevselliği aşılmaz sınırlamalara sahiptir. Bir statik analizci, örneğin null değerine izin veren dillerde "boş işaretçi istisnası"nın oluşması veya her durumda bir "boş değerin varlığını belirlemesi gibi şeyleri her durumda tespit edemeyecektir. dinamik olarak yazılan dillerde özellik bulunamadı" hatası alıyorum. En gelişmiş statik analiz cihazının yapabileceği tek şey, kaynak kodunuzla ilgili tüm olası sorunlar arasında, abartısız, okyanusta bir damla olan özel durumları vurgulamaktır.

Statik analiz hataları bulmakla ilgili değildir

Yukarıdakilerden şu sonuç çıkar: Statik analiz, bir programdaki kusurların sayısını azaltmanın bir yolu değildir. Şunu söylemek isterim: Projenize ilk kez uygulandığında kodda "ilginç" yerler bulacaktır, ancak büyük olasılıkla programınızın kalitesini etkileyecek herhangi bir kusur bulacaktır.

Analizörlerin otomatik olarak bulduğu kusur örnekleri etkileyicidir ancak bu örneklerin çok sayıda büyük kod tabanı taranarak bulunduğunu unutmamalıyız. Aynı prensibe göre, çok sayıda hesapta birkaç basit şifreyi deneme fırsatı bulan bilgisayar korsanları, sonunda basit şifreye sahip hesapları bulur.

Bu, statik analizin kullanılmaması gerektiği anlamına mı geliyor? Tabii ki değil! Ve tam olarak aynı nedenden dolayı, her yeni şifrenin "basit" şifrelerin durma listesine dahil edildiğinden emin olmak için kontrol edilmesi önemlidir.

Statik analiz hataları bulmaktan daha fazlasıdır

Aslında analizle pratik olarak çözülen problemler çok daha geniştir. Sonuçta, genel olarak statik analiz, kaynak kodlarının başlatılmadan önce gerçekleştirilen herhangi bir doğrulamasıdır. İşte yapabileceğiniz bazı şeyler:

  • Kelimenin en geniş anlamıyla kodlama stilinin kontrol edilmesi. Buna, biçimlendirmenin kontrol edilmesi, boş/ekstra parantezlerin kullanımının aranması, satır sayısı/bir yöntemin döngüsel karmaşıklığı gibi metrikler üzerinde eşiklerin ayarlanması vb. dahildir. Bu, kodun okunabilirliğini ve sürdürülebilirliğini potansiyel olarak engelleyen herhangi bir şeydir. Java'da böyle bir araç Checkstyle, Python'da ise flake8'dir. Bu sınıftaki programlara genellikle “linters” adı verilir.
  • Yalnızca yürütülebilir kod analiz edilemez. JSON, YAML, XML, .properties gibi kaynak dosyaları geçerlilik açısından otomatik olarak kontrol edilebilir (ve edilmelidir!). Sonuçta, JSON yapısının bazı eşleştirilmemiş teklifler nedeniyle bozulduğunu, test yürütme veya çalışma süresinden ziyade otomatik Çekme İsteği doğrulamasının erken bir aşamasında bulmak daha mı iyidir? Uygun araçlar mevcuttur: ör. YAMLlint, JSONLint.
  • Derleme (veya dinamik programlama dilleri için ayrıştırma) da bir tür statik analizdir. Genel olarak derleyiciler, kaynak kodu kalitesiyle ilgili sorunları belirten ve göz ardı edilmemesi gereken uyarılar üretme yeteneğine sahiptir.
  • Bazen derleme, yürütülebilir kodu derlemekten daha fazlasıdır. Örneğin, şu formatta belgeleriniz varsa AsciiDoktor, ardından HTML/PDF'ye dönüştürme anında AsciiDoctor işleyicisi (Maven eklentisi) örneğin bozuk dahili bağlantılar hakkında uyarı verebilir. Bu da belge değişiklikleriyle birlikte Çekme İsteğini kabul etmemek için iyi bir nedendir.
  • Yazım denetimi de bir tür statik analizdir. Yarar bir büyü yalnızca belgelerde değil, aynı zamanda C/C++, Java ve Python dahil olmak üzere çeşitli programlama dillerindeki program kaynak kodlarında (yorumlar ve sabit bilgiler) yazım denetimi yapabilir. Kullanıcı arayüzündeki veya belgelerdeki yazım hatası da bir kusurdur!
  • Yapılandırma testleri (ne oldukları hakkında - bkz. bu и bu raporlar), her ne kadar pytest gibi bir birim test çalışma zamanında yürütülse de aslında bir tür statik analizdir, çünkü yürütülmeleri sırasında kaynak kodları yürütmezler.

Gördüğünüz gibi, bu listedeki hataları aramak en az önemli rolü oynuyor ve diğer her şeye ücretsiz açık kaynak araçları kullanılarak ulaşılabilir.

Projenizde bu statik analiz türlerinden hangisini kullanmalısınız? Elbette ne kadar çoksa o kadar iyi! Önemli olan, daha fazla tartışılacak olan onu doğru bir şekilde uygulamaktır.

Çok aşamalı bir filtre olarak teslimat hattı ve ilk aşaması olarak statik analiz

Sürekli entegrasyonun klasik metaforu, kaynak kodu değişikliklerinden teslimata ve üretime kadar değişikliklerin aktığı bir boru hattıdır. Bu boru hattındaki standart aşama sırası şuna benzer:

  1. statik analiz
  2. derleme
  3. birim testleri
  4. entegrasyon testleri
  5. Kullanıcı arayüzü testleri
  6. manuel kontrol

İşlem hattının N. aşamasında reddedilen değişiklikler N+1 aşamasına aktarılmaz.

Neden tam olarak bu şekilde, başka türlü değil? Boru hattının test kısmında, test uzmanları iyi bilinen test piramidini tanıyacaklardır.

Hataları bulmak için kullanmak yerine, sürece statik analiz uygulayın
Piramidi test edin. Kaynak: makale Martin Fowler.

Bu piramidin en altında yazılması daha kolay, yürütülmesi daha hızlı ve başarısız olma eğilimi olmayan testler bulunur. Bu nedenle, daha fazla sayıda olmalı, daha fazla kodu kapsamalı ve ilk önce çalıştırılmalıdırlar. Piramidin tepesinde bunun tersi doğrudur, dolayısıyla entegrasyon ve kullanıcı arayüzü testlerinin sayısı gereken minimum seviyeye indirilmelidir. Bu zincirdeki kişi en pahalı, yavaş ve güvenilmez kaynaktır, dolayısıyla en sonda yer alır ve ancak önceki aşamalarda herhangi bir kusur bulunmazsa işi gerçekleştirir. Bununla birlikte, doğrudan testle ilgili olmayan parçalarda bir boru hattı oluşturmak için aynı prensipler kullanılır!

Çok kademeli su filtreleme sistemi şeklinde bir benzetme sunmak istiyorum. Girişe kirli su (kusurlu değişiklikler) verilir, çıkışta tüm istenmeyen kirletici maddelerin giderildiği temiz su almalıyız.

Hataları bulmak için kullanmak yerine, sürece statik analiz uygulayın
Çok aşamalı filtre. Kaynak: Vikipedi

Bildiğiniz gibi temizleme filtreleri, her bir ardışık kademenin kirletici maddelerin giderek daha ince bir kısmını filtreleyebileceği şekilde tasarlanmıştır. Aynı zamanda, daha kaba arıtma basamakları daha yüksek verim ve daha düşük maliyete sahiptir. Bizim benzetmemizde bu, giriş kalitesi kapılarının daha hızlı olduğu, başlamak için daha az çaba gerektirdiği ve operasyonda kendilerinin daha iddiasız olduğu anlamına gelir - ve bu onların inşa edilme sırasıdır. Artık anladığımız kadarıyla yalnızca en büyük kusurları ayıklayabilen statik analizin rolü, filtre kademesinin en başındaki "çamur" ızgarasının rolüdür.

Statik analiz tek başına nihai ürünün kalitesini artırmaz, tıpkı bir “çamur filtresinin” suyu içilebilir hale getirmemesi gibi. Yine de boru hattının diğer unsurlarıyla birlikte önemi açıktır. Her ne kadar çok aşamalı bir filtrede çıkış aşamaları potansiyel olarak giriş aşamalarının yaptığı her şeyi yakalama kapasitesine sahip olsa da, giriş aşamaları olmadan yalnızca ince arıtma aşamalarıyla idare etme girişiminin ne gibi sonuçlar doğuracağı açıktır.

"Çamur tuzağının" amacı, daha sonraki kademelerin çok büyük kusurları yakalamasını engellemektir. Örneğin, kod incelemesini yapan kişinin en azından, yanlış biçimlendirilmiş kod ve yerleşik kodlama standartlarının ihlalleri (ekstra parantez veya çok derin iç içe geçmiş dallar gibi) nedeniyle dikkati dağılmamalıdır. NPE'ler gibi hatalar birim testleriyle yakalanmalıdır, ancak testten önce bile analizör bize bir hatanın kaçınılmaz olduğunu belirtirse, bu, sorunun düzeltilmesini önemli ölçüde hızlandıracaktır.

Statik analizin ara sıra kullanıldığında ürünün kalitesini neden iyileştirmediğinin ve büyük kusurlu değişiklikleri filtrelemek için sürekli kullanılması gerektiğinin artık açık olduğuna inanıyorum. Statik analizör kullanmanın ürününüzün kalitesini artırıp artırmayacağı sorusu kabaca şu soruyu sormakla eşdeğerdir: "Kirli bir havuzdan alınan su, süzgeçten geçirilirse içme kalitesi artar mı?"

Eski bir projeye uygulama

Önemli bir pratik soru: Bir “kalite kapısı” olarak sürekli entegrasyon sürecine statik analiz nasıl uygulanır? Otomatik testler söz konusu olduğunda her şey açıktır: bir dizi test vardır ve bunlardan herhangi birinin başarısızlığı, montajın kalite kapısından geçmediğine inanmak için yeterli nedendir. Statik analizin sonuçlarına dayanarak aynı şekilde bir kapı kurma girişimi başarısız olur: eski kodda çok fazla analiz uyarısı vardır, bunları tamamen göz ardı etmek istemezsiniz, ancak aynı zamanda bir ürünü göndermeyi durdurmak da imkansızdır. sırf analizör uyarıları içerdiği için.

Analizör ilk kez kullanıldığında herhangi bir projede çok sayıda uyarı üretir ve bunların büyük çoğunluğu ürünün düzgün çalışmasıyla ilgili değildir. Bu yorumların tamamını bir anda düzeltmek imkansızdır ve çoğuna da gerek yoktur. Sonuçta, statik analize geçmeden önce bile ürünümüzün bir bütün olarak çalıştığını biliyoruz!

Sonuç olarak, birçoğu statik analizin ara sıra kullanımıyla sınırlıdır veya bir analizör raporu montaj sırasında basitçe yayınlandığında bunu yalnızca bilgi modunda kullanır. Bu, herhangi bir analizin olmamasına eşdeğerdir, çünkü zaten çok sayıda uyarımız varsa, kodu değiştirirken bir başkasının (ne kadar ciddi olursa olsun) ortaya çıkması fark edilmeden kalır.

Kalite kapılarını tanıtmanın aşağıdaki yöntemleri bilinmektedir:

  • Toplam uyarı sayısına veya uyarı sayısının kod satırı sayısına bölünmesine ilişkin bir sınır belirlemek. Bu pek işe yaramıyor çünkü böyle bir kapı, limitleri aşılmadığı sürece yeni kusurlu değişikliklerin geçmesine serbestçe izin veriyor.
  • Belirli bir anda koddaki tüm eski uyarıların göz ardı edilmesi ve yeni uyarılar oluştuğunda derlemenin reddedilmesi. Bu işlevsellik PVS-studio ve Codacy gibi bazı çevrimiçi kaynaklar tarafından sağlanır. PVS stüdyosunda çalışma fırsatım olmadı, Codacy deneyimime göre, onların asıl sorunu neyin "eski" ve neyin "yeni" hata olduğunu belirlemenin oldukça karmaşık bir algoritma olması ve her zaman işe yaramaması. özellikle dosyalar büyük ölçüde değiştirilmiş veya yeniden adlandırılmışsa. Deneyimlerime göre Codacy, bir çekme isteğindeki yeni uyarıları göz ardı edebilirken aynı zamanda belirli bir PR'nin kodundaki değişikliklerle ilgili olmayan uyarılar nedeniyle bir çekme isteğini iletemeyebilir.
  • Bana göre en etkili çözüm kitapta anlatılan çözümdür. Sürekli Teslim “cırcır yöntemi”. Temel fikir, statik analiz uyarılarının sayısının her sürümün bir özelliği olması ve yalnızca toplam uyarı sayısını artırmayan değişikliklere izin verilmesidir.

cırcır

Bu şekilde çalışır:

  1. İlk aşamada, analizörlerin bulduğu koddaki uyarı sayısının yayımlanmasına ilişkin meta verilerde bir kayıt yapılır. Dolayısıyla, yukarı yönde derleme yaptığınızda, depo yöneticiniz yalnızca "sürüm 7.0.2" değil, aynı zamanda "7.0.2 kontrol stili uyarısı içeren sürüm 100500" de yazar. Gelişmiş bir veri havuzu yöneticisi kullanıyorsanız (Artifactory gibi), sürümünüzle ilgili bu tür meta verileri depolamak kolaydır.
  2. Artık her çekme isteği, oluşturulduğunda, sonuçta ortaya çıkan uyarıların sayısını mevcut sürümdeki mevcut uyarıların sayısıyla karşılaştırır. PR bu sayının artmasına neden oluyorsa kod statik analiz için kalite kapısından geçemiyor demektir. Uyarı sayısı azalırsa veya değişmezse geçer.
  3. Bir sonraki sürümde, yeniden hesaplanan uyarı sayısı sürüm meta verilerine yeniden kaydedilecektir.

Böylece yavaş yavaş ama istikrarlı bir şekilde (tıpkı bir mandalın çalıştığı zamanki gibi), uyarıların sayısı sıfıra doğru yönelecektir. Elbette yeni bir uyarı getirilerek, ancak başkasının uyarısı düzeltilerek sistem aldatılabilir. Bu normaldir, çünkü uzun mesafelerde sonuç verir: Uyarılar, kural olarak, tek tek değil, belirli türden bir grup halinde aynı anda düzeltilir ve kolayca kaldırılabilen tüm uyarılar oldukça hızlı bir şekilde ortadan kaldırılır.

Bu grafik, böyle bir "mandalın" altı aylık çalışması için toplam Checkstyle uyarı sayısını gösterir. Açık Kaynak projelerimizden biri. Uyarıların sayısı büyük ölçüde azaldı ve bu, ürünün geliştirilmesine paralel olarak doğal olarak gerçekleşti!

Hataları bulmak için kullanmak yerine, sürece statik analiz uygulayın

Bu yöntemin değiştirilmiş bir sürümünü kullanıyorum, uyarıları proje modülü ve analiz aracına göre ayrı ayrı sayıyorum ve sonuçta şuna benzeyen derleme meta verileri içeren bir YAML dosyası elde ediyorum:

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

Herhangi bir gelişmiş CI sisteminde, eklentilere ve üçüncü taraf araçlara ihtiyaç duymadan herhangi bir statik analiz aracı için mandal uygulanabilir. Her analizci, analiz edilmesi kolay, basit bir metin veya XML formatında kendi raporunu üretir. Geriye kalan tek şey CI betiğine gerekli mantığı yazmaktır. Jenkins ve Artifactory'yi temel alan açık kaynak projelerimizde bunun nasıl uygulandığını görebilirsiniz. burada veya burada. Her iki örnek de kütüphaneye bağlıdır mandallı kitap: yöntem countWarnings() Checkstyle ve Spotbugs tarafından oluşturulan dosyalardaki xml etiketlerini olağan şekilde sayar ve compareWarningMaps() aynı mandalı uygular ve herhangi bir kategorideki uyarı sayısı arttığında hata verir.

Aspell kullanarak yorumların, metin değişmezlerinin ve belgelerin yazımını analiz etmek için "mandalın" ilginç bir uygulaması mümkündür. Bildiğiniz gibi yazım denetimi yaparken standart sözlükte bilinmeyen tüm kelimeler hatalı değildir; bunlar kullanıcı sözlüğüne eklenebilir. Projenin kaynak kodunun özel bir sözlük kısmını yaparsanız, yazım kalitesi geçidi şu şekilde formüle edilebilir: aspell'i standart ve özel bir sözlükle çalıştırmak yapmamalı yazım hatası bulamıyorum.

Analizör versiyonunu düzeltmenin önemi hakkında

Sonuç olarak dikkat edilmesi gereken nokta, analizi teslimat hattınıza nasıl uygularsanız uygulayın, analizörün sürümünün sabit olması gerektiğidir. Analizörün kendiliğinden güncellenmesine izin verirseniz, bir sonraki çekme isteğini derlerken, kod değişiklikleriyle ilgili olmayan ancak yeni analizörün daha fazla kusur bulabilmesiyle ilgili olan yeni hatalar "ortaya çıkabilir" - bu da çekme isteklerini kabul etme sürecinizi bozacaktır. Bir analizörün yükseltilmesi bilinçli bir eylem olmalıdır. Bununla birlikte, her bir montaj bileşeninin versiyonunun sağlam bir şekilde sabitlenmesi genellikle gerekli bir gerekliliktir ve ayrı bir tartışma konusudur.

Bulgular

  • Statik analiz sizin için hata bulmayacak ve tek bir uygulama sonucunda ürününüzün kalitesini artırmayacaktır. Kalite üzerinde olumlu bir etki ancak teslimat sürecinde sürekli kullanılmasıyla sağlanabilir.
  • Hata bulmak analizin ana görevi değildir; yararlı işlevlerin büyük çoğunluğu açık kaynak araçlarda mevcuttur.
  • Eski kod için bir "mandal" kullanarak teslimat hattının ilk aşamasında statik analizin sonuçlarına dayalı kalite geçitlerini uygulayın.

referanslar

  1. Sürekli Teslim
  2. A. Kudryavtsev: Program analizi: iyi bir programcı olduğunuzu nasıl anlayabilirsiniz? Farklı kod analizi yöntemleri hakkında rapor (yalnızca statik değil!)

Kaynak: habr.com

Yorum ekle