Önemli Testler Yazın: Önce En Karmaşık Kodu Ele Alın

Yayınlanan: 2022-03-11

Kod kalitesi konusunda pek çok tartışma, makale ve blog var. İnsanlar diyor ki - Test Odaklı teknikleri kullanın! Testler, herhangi bir yeniden düzenlemeyi başlatmak için "olmazsa olmazdır"! Hepsi harika, ancak 2016 ve hala üretimde olan ve on, on beş ve hatta yirmi yıl önce yaratılmış çok sayıda ürün ve kod tabanı var. Birçoğunun düşük test kapsamına sahip eski kodlara sahip olduğu bir sır değil.

Her ne kadar teknoloji dünyasının her zaman önde gelen, hatta en uç noktasında yer almak istesem de – yeni harika projeler ve teknolojilerle meşgul – ne yazık ki bu her zaman mümkün olmuyor ve çoğu zaman eski sistemlerle uğraşmak zorunda kalıyorum. Sıfırdan geliştiğinizde, yeni maddede ustalaşan bir yaratıcı olarak hareket ettiğinizi söylemeyi seviyorum. Ancak eski kod üzerinde çalışırken daha çok bir cerrah gibi olursunuz – sistemin genel olarak nasıl çalıştığını bilirsiniz, ancak hastanın “ameliyatınızdan” sağ çıkıp çıkmayacağından asla emin olamazsınız. Ve eski kod olduğundan, güvenebileceğiniz pek çok güncel test yoktur. Bu, çok sık olarak ilk adımlardan birinin bunu testlerle kapatmak olduğu anlamına gelir. Daha doğrusu, sadece kapsam sağlamak değil, aynı zamanda bir test kapsamı stratejisi geliştirmek.

Birleştirme ve Döngüsel Karmaşıklık: Daha Akıllı Test Kapsamı için Metrikler

%100 kapsama alanını unutun. Bozulma olasılığı daha yüksek olan sınıfları belirleyerek daha akıllıca test edin.
Cıvıldamak

Temel olarak, ilk etapta sistemin hangi bölümlerini (sınıflarını/paketlerini) testlerle kapsamamız gerektiğini, nerede birim testlerine ihtiyacımız olduğunu, entegrasyon testlerinin nerede daha yararlı olacağını vb. belirlemem gerekiyordu. Kuşkusuz bunun birçok yolu var. Bu tür bir analize yaklaşın ve benim kullandığım analiz en iyisi olmayabilir, ancak bu bir tür otomatik yaklaşım. Yaklaşımım uygulandığında, analizin kendisini gerçekten yapmak minimum zaman alır ve daha da önemlisi eski kod analizine biraz eğlence katar.

Buradaki ana fikir, iki ölçümü analiz etmektir – eşleşme (yani, afferent eşleşme veya CA) ve karmaşıklık (yani, döngüsel karmaşıklık).

İlki, sınıfımızı kaç sınıfın kullandığını ölçer, bu nedenle temel olarak bize belirli bir sınıfın sistemin kalbine ne kadar yakın olduğunu söyler; sınıfımızı kullanan sınıflar ne kadar fazlaysa, onu testlerle kaplamak o kadar önemlidir.

Öte yandan, bir sınıf çok basitse (örneğin yalnızca sabitleri içeriyorsa), sistemin diğer birçok parçası tarafından kullanılsa bile, bunun için bir test oluşturmak neredeyse o kadar önemli değildir. İkinci ölçümün yardımcı olabileceği yer burasıdır. Bir sınıf çok fazla mantık içeriyorsa, Döngüsel karmaşıklık yüksek olacaktır.

Aynı mantık tersten de uygulanabilir; yani, bir sınıf birçok sınıf tarafından kullanılmasa ve yalnızca belirli bir kullanım durumunu temsil etse bile, dahili mantığı karmaşıksa onu testlerle kapsamak yine de mantıklıdır.

Yine de bir uyarı var: diyelim ki iki sınıfımız var – biri CA 100 ve karmaşıklık 2 ile diğeri CA 60 ve karmaşıklık 20 ile. İlki için metriklerin toplamı daha yüksek olsa da, kesinlikle kapsamalıyız. önce ikincisi. Bunun nedeni, birinci sınıfın diğer birçok sınıf tarafından kullanılması, ancak çok karmaşık olmamasıdır. Öte yandan, ikinci sınıf diğer birçok sınıf tarafından da kullanılmaktadır ancak birinci sınıftan nispeten daha karmaşıktır.

Özetlemek gerekirse: Yüksek CA ve Siklomatik karmaşıklığa sahip sınıfları tanımlamamız gerekiyor. Matematiksel olarak, değerleri CA ve Karmaşıklık ile birlikte artan bir derecelendirme - f(CA,Karmaşıklık) olarak kullanılabilecek bir uygunluk fonksiyonuna ihtiyaç vardır.

Genel olarak konuşursak, iki metrik arasında en küçük farka sahip sınıflara test kapsamı için en yüksek öncelik verilmelidir.

Tüm kod tabanı için CA ve Karmaşıklığı hesaplamak ve bu bilgiyi CSV formatında çıkarmak için basit bir yol sağlamak için araçlar bulmak zor oldu. Araştırmam sırasında ücretsiz olan iki araca rastladım, bu yüzden onlardan bahsetmemek haksızlık olur:

  • Birleştirme metrikleri: www.spinellis.gr/sw/ckjm/
  • Karmaşıklık: cyvis.sourceforge.net/

Biraz Matematik

Buradaki ana sorun, iki kriterimiz var – CA ve Siklomatik karmaşıklık – bu yüzden bunları birleştirmemiz ve tek bir skaler değere dönüştürmemiz gerekiyor. Biraz farklı bir görevimiz olsaydı – örneğin, kriterlerimizin en kötü kombinasyonuna sahip bir sınıf bulmak için – klasik bir çok amaçlı optimizasyon problemimiz olurdu:

Pareto cephesinde (yukarıdaki resimde kırmızı) bir nokta bulmamız gerekecek. Pareto kümesiyle ilgili ilginç olan şey, kümedeki her noktanın optimizasyon görevine bir çözüm olmasıdır. Kırmızı çizgide ne zaman ilerlesek, kriterlerimiz arasında bir uzlaşmaya varmamız gerekiyor – biri iyileşirse diğeri daha da kötüleşir. Buna Skalarizasyon denir ve nihai sonuç bunu nasıl yaptığımıza bağlıdır.

Burada kullanabileceğimiz birçok teknik var. Her birinin kendi artıları ve eksileri vardır. Bununla birlikte, en popüler olanları doğrusal skalerizasyon ve bir referans noktasına dayalıdır. Lineer en kolayıdır. Uygunluk fonksiyonumuz, CA ve Karmaşıklığın doğrusal bir kombinasyonu gibi görünecektir:

f(CA, Karmaşıklık) = A×CA + B×Karmaşıklık

burada A ve B bazı katsayılardır.

Optimizasyon problemimizin çözümünü temsil eden nokta doğru üzerinde yer alacaktır (aşağıdaki resimde mavi). Daha doğrusu mavi çizgi ile kırmızı Pareto cephesinin kesiştiği noktada olacaktır. Asıl sorunumuz tam olarak bir optimizasyon sorunu değil. Bunun yerine, bir sıralama işlevi oluşturmamız gerekiyor. Sıralama fonksiyonumuzun iki değerini, temel olarak Sıralama sütunumuzdaki iki değeri ele alalım:

R1 = A∗CA + B∗Karmaşıklık ve R2 = A∗CA + B∗Karmaşıklık

Yukarıda yazılan formüllerin ikisi de doğruların denklemidir, üstelik bu doğrular paraleldir. Daha fazla sıra değerini dikkate alarak, daha fazla çizgi ve dolayısıyla Pareto çizgisinin (noktalı) mavi çizgilerle kesiştiği yerlerde daha fazla nokta elde edeceğiz. Bu noktalar, belirli bir sıralama değerine karşılık gelen sınıflar olacaktır.

Ne yazık ki, bu yaklaşımla ilgili bir sorun var. Herhangi bir satır için (Sıralama değeri), üzerinde çok küçük CA ve çok büyük Karmaşıklık (ve tam tersi) bulunan puanlarımız olacaktır. Bu, tam olarak kaçınmak istediğimiz şey olan, metrik değerler arasında büyük bir fark olan noktaları listenin en üstüne koyar.

Skalarizasyonu yapmanın diğer yolu referans noktasına dayanmaktadır. Referans noktası, her iki kriterin maksimum değerlerine sahip bir noktadır:

(max(CA), max(Karmaşıklık))

Uygunluk işlevi, Referans noktası ile veri noktaları arasındaki mesafe olacaktır:

f(CA,Karmaşıklık) = √((CA−CA ) 2 + (Karmaşıklık−Karmaşıklık) 2 )

Bu uygunluk fonksiyonunu, merkezi referans noktasında olan bir daire olarak düşünebiliriz. Bu durumda yarıçap, Derecenin değeridir. Optimizasyon probleminin çözümü, dairenin Pareto cephesine değdiği nokta olacaktır. Orijinal problemin çözümü, aşağıdaki resimde gösterildiği gibi farklı daire yarıçaplarına karşılık gelen nokta kümeleri olacaktır (farklı sıralar için dairelerin bölümleri noktalı mavi eğriler olarak gösterilmiştir):

Bu yaklaşım uç değerlerle daha iyi ilgilenir ancak yine de iki sorun vardır: Birincisi – Lineer kombinasyonla karşılaştığımız sorunu daha iyi aşmak için referans noktalarının yakınında daha fazla nokta olmasını istiyorum. İkincisi – CA ve Döngüsel karmaşıklık doğal olarak farklıdır ve farklı değerler kümesine sahiptir, bu nedenle bunları normalleştirmemiz gerekir (örneğin, her iki metriğin tüm değerlerinin 1'den 100'e kadar olması için).

İşte ilk sorunu çözmek için uygulayabileceğimiz küçük bir numara – CA ve Döngüsel Karmaşıklığa bakmak yerine, ters çevrilmiş değerlerine bakabiliriz. Bu durumda referans noktası (0,0) olacaktır. İkinci sorunu çözmek için, minimum değeri kullanarak metrikleri normalleştirebiliriz. İşte nasıl göründüğü:

Tersine çevrilmiş ve normalleştirilmiş karmaşıklık – NormComplexity:

(1 + dk(Karmaşıklık)) / (1 + Karmaşıklık)∗100

Ters çevrilmiş ve normalleştirilmiş CA – NormCA:

(1 + dk(CA)) / (1+CA)∗100

Not: 0'a bölme olmaması için 1 ekledim.

Aşağıdaki resim, ters çevrilmiş değerlere sahip bir grafiği göstermektedir:

Son Sıralama

Şimdi son adıma geliyoruz - sıralamayı hesaplamak. Bahsettiğim gibi, referans noktası yöntemini kullanıyorum, bu yüzden yapmamız gereken tek şey vektörün uzunluğunu hesaplamak, normalleştirmek ve bir sınıf için birim test oluşturmanın önemi ile onu yükseltmek. İşte son formül:

Sıra(NormKarmaşıklık , NormCA) = 100 − √(NormKarmaşıklık 2 + NormCA 2 ) / √2

Daha Fazla İstatistik

Eklemek istediğim bir düşünce daha var ama önce bazı istatistiklere göz atalım. Burada, Birleştirme metriklerinin bir histogramı yer almaktadır:

Bu resimle ilgili ilginç olan, düşük CA'lı (0-2) sınıfların sayısıdır. CA 0 olan sınıflar ya hiç kullanılmaz ya da üst düzey hizmetlerdir. Bunlar API uç noktalarını temsil eder, bu nedenle birçoğuna sahip olmamız sorun değil. Ancak CA 1'e sahip sınıflar, doğrudan uç noktalar tarafından kullanılan sınıflardır ve bu sınıflardan uç noktalardan daha fazlasına sahibiz. Bu, mimari/tasarım açısından ne anlama geliyor?

Genel olarak, bu, bir tür komut dosyası odaklı yaklaşımımız olduğu anlamına gelir - her iş vakasını ayrı ayrı yazarız (iş senaryoları çok çeşitli olduğu için kodu gerçekten yeniden kullanamayız). Eğer durum buysa, o zaman kesinlikle bir kod kokusudur ve yeniden düzenleme yapmamız gerekir. Aksi takdirde, sistemimizin uyumunun düşük olduğu anlamına gelir, bu durumda da yeniden düzenlemeye ihtiyacımız var, ancak bu sefer mimari yeniden düzenlemeye ihtiyacımız var.

Yukarıdaki histogramdan alabileceğimiz ek yararlı bilgiler, düşük eşleşmeli sınıfları ({0,1}'de CA) birim testleri ile kapsama için uygun sınıflar listesinden tamamen filtreleyebilmemizdir. Yine de aynı sınıflar entegrasyon/fonksiyonel testler için iyi adaylardır.

Bu GitHub deposunda kullandığım tüm komut dosyalarını ve kaynakları bulabilirsiniz: ashalitkin/code-base-stats.

Her Zaman Çalışır mı?

Şart değil. Her şeyden önce, çalışma zamanı değil, tamamen statik analiz ile ilgilidir. Bir sınıf diğer birçok sınıftan bağlantılıysa, bu onun yoğun olarak kullanıldığının bir işareti olabilir, ancak bu her zaman doğru değildir. Örneğin, işlevselliğin son kullanıcılar tarafından gerçekten yoğun bir şekilde kullanılıp kullanılmadığını bilmiyoruz. İkincisi, sistemin tasarımı ve kalitesi yeterince iyiyse, büyük olasılıkla farklı bölümleri / katmanları arayüzler aracılığıyla ayrıştırılır, bu nedenle CA'nın statik analizi bize gerçek bir resim vermez. Sanırım CA'nın Sonar gibi araçlarda bu kadar popüler olmamasının ana nedenlerinden biri bu. Neyse ki, bizim için tamamen iyi, çünkü hatırlarsanız, bunu özellikle eski çirkin kod tabanlarına uygulamakla ilgileniyoruz.

Genel olarak, çalışma zamanı analizinin çok daha iyi sonuçlar vereceğini söyleyebilirim, ancak ne yazık ki çok daha maliyetli, zaman alıcı ve karmaşıktır, bu nedenle yaklaşımımız potansiyel olarak yararlı ve daha düşük maliyetli bir alternatiftir.

İlgili: Tek Sorumluluk İlkesi: Büyük Kodun Tarifi