Bölüm 10 / 12
Kod kalitesi
Analyzer, linter, kapsam (coverage), statik analiz.
13 dakikalık okuma
Test, kalitenin dinamik kanıtıydı — kod çalıştırılır, davranış doğrulanır. Bu ders madalyonun öbür yüzünü kurar: kodu hiç çalıştırmadan inceleyen statik analiz. .NET derleyicisinin içinde yaşayan Roslyn analizörleri, kuralların depoda yaşadığı .editorconfig, stil tartışmasını makineye devreden biçimlendiriciler, karmaşıklık metrikleri ve SonarQube'un kalite kapısı kavramı. İkinci yarıda 1. derste açtığımız senetler ödenir: kapsamanın araç zinciri — Coverlet + ReportGenerator — ve kapsamanın ölçemediğini ölçen mutasyon testi (Stryker.NET). Dersin tamamını tek ilke bağlar: makineye devredilebilen her kalite kontrolü makineye devredilir — insan gözü, makinenin göremediği şeye (tasarım, niyet, isabet) saklanır.
01Ne öğreneceksin
- Statik ↔ dinamik analiz ayrımı; her birinin gördüğü ve göremediği
- Roslyn analizörleri: SDK içindeki kural motoru; uyarıyı hataya çevirmek
- .editorconfig: kuralların depoda yaşaması;
dotnet format - Stil tartışmasının bitirilişi: biçimlendiriciler; ESLint ↔ Prettier iş bölümü
- Döngüsel karmaşıklık ve metriklerin sınırı (Goodhart, ikinci gelişi)
- SonarQube kavramları: issue türleri, kalite kapısı, yeni kod odağı
- Kapsama zinciri: Coverlet → ReportGenerator; satır ↔ dal raporu
- Stryker.NET ile mutasyon testi: mutation score, sağ kalan mutantın anlamı
02Statik ↔ dinamik: iki gözün iş bölümü
Dinamik analiz kodu çalıştırarak inceler: birim/entegrasyon testleri, profiler, 9. dersteki dotnet-trace, 14-09'daki DAST. Gördüğü şey gerçek davranıştır; göremediği şey, çalıştırılmayan yoldur — test etmediğin dal, dinamik analiz için yoktur. Statik analiz kodu çalıştırmadan, metin ve sözdizimi ağacı üzerinden inceler: derleyici (en eski statik analizör), tip denetimi (Hat 3'ün null uyarıları, Hat 9'un TypeScript'i), analizörler, linter'lar, 14-09'daki SAST. Gördüğü şey tüm koddur — çalıştırılmayan dal dahil; göremediği şey çalışma zamanı gerçeğidir: gerçek veri, gerçek zamanlama, gerçek yapılandırma.
Bu yüzden ikisi rakip değil katmandır ve ucuzluk sırası nettir (1. dersin maliyet eğrisi): önce derleyici/analizör (yazarken, saniyeler), sonra test (commit'te, dakikalar), sonra inceleme ve üretim gözlemi. Statik analizin süper gücü bedava ölçeklenmesidir: bir kural yazılır, milyon satıra aynı anda uygulanır, yorulmaz ve unutmaz. Sınırı da bilinir: kural genellemedir, yanlış pozitif üretir — yönetimi 07. bölümde.
03Roslyn analizörleri: derleyicinin içindeki denetçi
.NET derleyicisi (Roslyn) bir API'dir ve analizörler o API üzerinde çalışan
kural motorlarıdır: kod yazılırken sözdizimi ağacını inceler, ihlali IDE'de dalgalı
çizgiyle, derlemede uyarı/hata olarak bildirir. SDK'nın içinde yüzlerce kural hazır gelir
(CAxxxx — doğruluk, performans, güvenlik, kullanım; IDExxxx —
stil). Ayarların özü .csproj'da:
<PropertyGroup>
<AnalysisLevel>latest</AnalysisLevel> <!-- kural setinin sürümü -->
<AnalysisMode>Recommended</AnalysisMode> <!-- kaçı açık: Default/Recommended/All -->
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
Son satır bu dersin en önemli kararıdır: uyarı, görmezden gelinebilen şeydir; hata
gelinemeyen. Uyarılar birikmeye başladığı gün "100 uyarı var zaten, 101 olmuş ne fark
eder" kültürü doğar ve sinyal ölür (2. dersteki kararsız test normalleşmesiyle aynı
mekanizma). Yeni projede birinci günden uyarı = hata; mevcut projede ise kademeli sıkma —
önce mevcut ihlaller düzeltilir ya da bilinçli susturulur, sonra kapı kapanır. Susturmanın
da adabı vardır: #pragma warning disable ya da [SuppressMessage]
her zaman gerekçeyle — gerekçesiz susturma, 14-11'deki gerekçesiz hariç tutmanın
kod hâlidir. Üçüncü parti analizörler NuGet ile eklenir (ör. xUnit analizörleri — 3.
derste await'siz ThrowsAsync'i yakalayan kurallar tam bunlardı; Roslynator;
güvenlik analizörleri 14-09'un SAST katmanı olarak).
04.editorconfig: kural depoda yaşar
Analizör kurallarının hangi şiddette uygulanacağı kişiye ya da IDE ayarına bırakılırsa
ekip standardı olmaz. Çözüm, depo köküne konan .editorconfig: editörden
bağımsız bir standart dosyada hem biçim (girinti, satır sonu) hem analizör şiddetleri
tanımlanır — kural, kodla aynı yerde, aynı sürüm kontrolünde yaşar:
[*.cs]
indent_size = 4
csharp_style_var_when_type_is_apparent = true:suggestion
# analizör şiddeti: none | silent | suggestion | warning | error
dotnet_diagnostic.CA2007.severity = none # ConfigureAwait kuralı: uygulama kodunda kapalı
dotnet_diagnostic.CA1062.severity = warning
dotnet_diagnostic.xUnit1031.severity = error # testte senkron bekleme: hata
İki pratik sonucu: yeni gelen geliştirici hiçbir ayar yapmadan ekip kurallarını devralır;
ve kural değişikliği bir PR'dır — tartışılır, incelenir, geçmişi tutulur.
dotnet format komutu bu dosyaya göre tüm çözümü biçimlendirir ve stil
ihlallerini düzeltir; CI'da dotnet format --verify-no-changes ile "biçim
bozuk mu" sorusu bir kapıya dönüşür (18. hat).
05Stil tartışmasını makineye devretmek
Kod incelemelerinin en verimsiz türü stil münakaşasıdır: süslü parantez nereye, boşluk
kaç, var mı açık tip mi. Bunların çoğunun doğru cevabı yoktur —
önemli olan tek şeydir: herkesin aynısını yapması. Bu yüzden olgun ekipler kararı
bir kez verir, makineye gömer ve bir daha konuşmaz. .NET'te bu iş
.editorconfig + dotnet format (istersen StyleCop.Analyzers ile
daha sıkı kurallar); JS/TS dünyasında ikili iş bölümü vardır ve karıştırılır:
Prettier salt biçimlendiricidir — fikri yoktur, formatı dayatır;
ESLint ise linter'dır — hata kalıplarını yakalar (kullanılmayan değişken,
await'siz promise, tehlikeli eşitlik). Doğru kurulum ikisini birlikte, çakışan stil
kurallarını ESLint'ten kapatarak kullanır. Kayıt cihazı da editördür: kaydederken
biçimlendir (format on save) açıksa, biçim diye bir konu kalmaz — 0. hattaki editör
yatırımının geri dönüşü.
06Metrikler: döngüsel karmaşıklık ve sınırı
Döngüsel karmaşıklık (cyclomatic complexity), bir metottaki bağımsız yol
sayısıdır; pratik hesabı "karar noktası + 1"dir — her if, case,
döngü, &&/|| bir karar noktasıdır. Önemi doğrudan bu
hattın konusuna bağlanır: karmaşıklık N ise, dal kapsaması için en az N test
gerekir (2. dersteki karar tablosunun boyutu). Karmaşıklığı 25 olan metot, "25 testlik
metot" demektir — kimse yazmaz, metot test edilmez, hata orada birikir. Yaygın eşik
uyarısı ~10-15'tir; çare neredeyse her zaman Hat 4'tendir: metodu böl, koşul yığınını
polimorfizme ya da tablo/sözlüğe çevir, erken dönüşle iç içe geçmeyi düzleştir.
Diğer metrikler aynı ailedendir: bilişsel karmaşıklık (Sonar'ın insan-okuması odaklı türevi — iç içe geçmeyi daha sert cezalandırır), sınıf başına bağımlılık, metot uzunluğu, bakım endeksi (bileşik skor). Hepsi için 1. dersin uyarısı ikinci kez gelir: metrik sinyaldir, hedef değil. "Karmaşıklık 10'u geçmesin" kuralı hedefe dönüştüğünde Goodhart işler — metot ikiye bölünür ama mantık bölünmez, sayı düşer, karmaşıklık kalır. Metriğin doğru kullanımı sıralamadır: "en karmaşık + en sık değişen + en az test edilen" kesişimi, refactoring bütçesinin adresidir (11. derste teknik borç haritasına bağlanacak).
07SonarQube: kalite kapısı kavramı
SonarQube (bulut sürümü SonarCloud), statik analizi ekip ölçeğinde raporlayan platformdur: her analizde kodu tarar, bulguları sınıflandırır, eğilimi izler. Kavram seti sınav ve mülakat diline girmiştir:
| Kavram | Anlamı |
|---|---|
| Bug | Muhtemel çalışma zamanı hatası (her zaman null dönen karşılaştırma gibi) |
| Vulnerability / Security Hotspot | Güvenlik açığı / gözle doğrulanması gereken riskli desen — 14-09'un SAST'ı |
| Code smell | Çalışan ama bakımı zorlaştıran kod (Hat 4'ün kokuları: dev metot, kopyala-yapıştır, ölü kod) |
| Teknik borç oranı | Tahmini düzeltme süresinin geliştirme süresine oranı — kaba ama eğilim için işe yarar |
| Kalite kapısı (quality gate) | Eşikler kümesi: sağlanmazsa analiz "failed" — birleştirme engellenebilir |
En değerli fikri "yeni kod" odağıdır (Sonar'ın sloganıyla: temiz kalarak kodla — clean as you code): on yıllık bir kod tabanına "sıfır bulgu" dayatmak felçtir; kapı bunun yerine değişen ve yeni yazılan koda bakar — yeni kodda sıfır yeni bug, yeni kodda kapsama eşiği (1. dersteki diff coverage fikrinin platform hâli). Eski taban, dokunuldukça temizlenir. İkinci disiplin yanlış pozitif yönetimidir: kural genellemedir, bazen haksızdır; bulgu gerekçeyle "won't fix" işaretlenir — sessizce yok saymak ya da kuralı toptan kapatmak, sinyali öldürmenin iki yoludur. (Kapının CI hattına fiziken bağlanması — birleştirmeyi gerçekten engellemesi — 18. hattın işi; buradan oraya kavramlar hazır gidiyor.)
08Kapsama zinciri: Coverlet ve ReportGenerator
1. derste kapsamanın ne olmadığını kurduk; şimdi nasıl toplandığını kur. .NET'in fiilî standardı Coverlet'tir (xUnit şablonuyla zaten gelir):
dotnet test --collect:"XPlat Code Coverage"
# çıktı: TestResults/<guid>/coverage.cobertura.xml (makine formatı)
reportgenerator -reports:"**/coverage.cobertura.xml" -targetdir:rapor -reporttypes:Html
# rapor/index.html: satır + dal kapsaması, dosya dosya, satır satır boyalı
ReportGenerator ham XML'i insan diline çevirir: hangi dosyada hangi satırlar hiç
çalışmamış, hangi dallar yarım — kırmızı/yeşil boyalı kaynak görünümüyle. Okuma disiplini
1. dersten: toplam yüzdeye değil boşluk haritasına bak — "işlem iptal dalı hiç test
edilmemiş" bulgusu, "%84'müş" bilgisinden yüz kat değerlidir. Rapor türlerinden
Cobertura CI araçlarının ortak dili, HTML insanın; ikisi birlikte üretilir.
Eşik koymak istersen Coverlet'in Threshold parametresi var — ama eşiği koyacaksan
1. dersin tavsiyesiyle: mutlak toplam yerine, yeni/değişen kod üzerinde (07. bölümdeki yeni
kod odağı).
09Mutasyon: testlerin testi — Stryker.NET
Senet ödeme zamanı: kapsama "kod çalıştı" der, mutasyon testi "test yakalar mı"
diye sorar. Stryker.NET, üretim kodunda küçük anlamlı bozulmalar (mutantlar) üretir
— >= → >, + → -,
true → false, sabit değiştirme — ve her mutant için test paketini
koşturur:
dotnet tool install -g dotnet-stryker
dotnet stryker # test projesinin dizininde
# rapor özeti:
# Killed : mutantı bir test yakaladı (kırmızı yandı) → iyi
# Survived : hiçbir test fark etmedi → kapsanan ama doğrulanmayan kod
# No cov. : mutanta hiçbir test dokunmuyor → kapsama boşluğu
# Timeout : mutant sonsuz döngü yaptı, süre doldu → killed sayılır
Mutation score = öldürülen / toplam. Asıl okunacak şey skor değil, sağ kalan
mutantların listesidir: her biri "bu satırı böyle bozdum, testlerin yeşil kaldı"
cümlesidir — ya assert zayıftır (değer doğrulanmıyor), ya sınır testi eksiktir
(>=/> mutantının sağ kalması, 2. dersteki sınır değer
testinin yokluğunun kanıtıdır), ya da kod ölüdür. 3. dersin mini alıştırmasında bunu elle
yapmıştın; Stryker aynı deneyi binlerce kez, sistematik yapar.
Maliyet dürüstlüğü: mutasyon pahalıdır — her mutant için testler koşar; büyük çözümde saatler sürebilir. Bu yüzden doğru kullanım hedeflidir: kritik iş kuralı modüllerine (ücret hesabı, yetki kararları), gece/haftalık koşuda, tercihen yalnız değişen dosyalara. "Tüm çözüm, her commit" hedefi değildir. Kalite araçlarının bütünü de aynı akılla dizilir: her kayıtta analizör + test; her PR'da format doğrulama + kapsama raporu + Sonar kapısı; periyodik olarak mutasyon — piramidin araç zinciri hâli.
10Mini alıştırma — kalite zincirini kur
Yaklaşık 20 dakika · dotnet CLI (3-5. derslerdeki çözümün)
- Kapıyı kapat. Çözümüne
TreatWarningsAsErrors+AnalysisMode=Recommendedekle ve derle. Kaç ihlal çıktı? Üçünü düzelt; birini gerekçeli sustur (#pragma+ tek satır neden) — farkı hisset. - Kuralı depoya koy.
.editorconfig'e bir stil kuralı ve birdotnet_diagnosticşiddeti yaz;dotnet format --verify-no-changesile ihlali yakalat, sonradotnet formatile düzelttir. - Karmaşıklığı gör. En uzun metodunu bul, karar noktalarını sayarak döngüsel karmaşıklığını elle hesapla. 10'u aşıyorsa Hat 4 araçlarıyla (böl, erken dön, sözlüğe çevir) düşür ve testlerin hâlâ yeşil olduğunu doğrula — refactor'un hakemi onlar.
- Boşluk haritası. Coverlet + ReportGenerator ile HTML rapor üret. Toplam yüzdeyi görmezden gel; kırmızı kalan en kritik üç satırı bul ve her biri için karar ver: test mi eksik, kod mu ölü?
- Mutantları salıver.
dotnet strykerkoştur. Sağ kalan mutantlardan ikisini seç: her biri için ya assert'i güçlendir ya eksik sınır testini ekle; yeniden koşturup öldüklerini gör. Skorun kaç puan arttı — ve asıl önemlisi, hangi gerçek hata sınıfı artık yakalanıyor?
11Kendini sına
Statik ve dinamik analiz nedir, hangisi neyi görür? Kalite kontrollerinin doğru sıralaması neden "önce statik"tir?
Statik analiz kodu çalıştırmadan, metin ve sözdizimi ağacı üzerinden inceler: derleyici, tip denetimi, Roslyn analizörleri, linter'lar, güvenlik tarafında SAST. Tüm kodu görür — hiç çalıştırılmayan dal dahil — ama çalışma zamanı gerçeğini (gerçek veri, zamanlama, yapılandırma) göremez ve kuralları genelleme olduğundan yanlış pozitif üretir. Dinamik analiz kodu çalıştırarak inceler: testler, profiler, izleme araçları, DAST. Gerçek davranışı görür ama yalnız çalıştırılan yolu — test edilmeyen dal onun için yoktur. İkisi rakip değil katmandır; sıralamayı kusur maliyeti eğrisi belirler: statik analiz geri bildirimi yazarken, saniyeler içinde ve bedava ölçeklenerek verir (bir kural milyon satıra uygulanır, yorulmaz), test dakikalar, inceleme saatler, üretim günler mertebesindedir. Bu yüzden makineye devredilebilen her kontrol en ucuz katmana itilir: null güvenliği ve hata kalıpları analizöre, biçim biçimlendiriciye, davranış testlere; insan gözü makinenin göremediğine — tasarım, isabet, niyet — saklanır. Uyarıların hataya çevrilmesi de bu zincirin şartıdır: görmezden gelinebilen sinyal, birikip ölür.
Roslyn analizörleri ve .editorconfig birlikte nasıl bir kalite altyapısı kurar? Uyarı susturmanın adabı nedir?
Roslyn analizörleri derleyici API'si üzerinde çalışan kural motorlarıdır: SDK ile yüzlerce kural (CA doğruluk/performans/güvenlik, IDE stil) hazır gelir, NuGet ile üçüncü parti eklenir (xUnit analizörleri, Roslynator, güvenlik paketleri); ihlal IDE'de anında, derlemede uyarı/hata olarak görünür. csproj'da AnalysisLevel/AnalysisMode kural setini açar, TreatWarningsAsErrors uyarıyı görmezden gelinemez hâle getirir — "100 uyarı varken 101'i kimse görmez" çürümesinin panzehiri. .editorconfig bu kuralların şiddetini ve biçim standartlarını depo köküne, koddan ayrı değil kodla birlikte koyar: dotnet_diagnostic.CAxxxx.severity satırlarıyla kural başına none/suggestion/warning/error atanır; yeni geliştirici sıfır ayarla standardı devralır ve her kural değişikliği incelemeden geçen bir PR olur. dotnet format bu dosyaya göre biçimlendirir; --verify-no-changes CI kapısıdır. Susturma adabı: #pragma warning disable ya da [SuppressMessage] her zaman gerekçeyle yazılır — hangi kural, neden burada geçerli değil; gerekçesiz susturma, denetimdeki gerekçesiz hariç tutma gibi, kuralın değil disiplinin delindiğinin işaretidir. Mevcut projede geçiş kademelidir: önce ihlaller düzeltilir ya da bilinçli susturulur, sonra kapı kapanır.
Döngüsel karmaşıklık nedir, testle ilişkisi nedir? SonarQube'un kalite kapısı ve "yeni kod odağı" neden önemlidir?
Döngüsel karmaşıklık, metottaki bağımsız yol sayısıdır; pratik hesabı karar noktası + 1 — her if, case, döngü ve &&/|| bir karar noktası. Testle bağı doğrudandır: dal kapsaması için en az karmaşıklık kadar test gerekir; karmaşıklığı 25 olan metot 25 testlik metottur — yazılmaz, test edilmez, hata orada birikir. Çare Hat 4 araçlarıdır: bölme, erken dönüş, koşul yığınını polimorfizme ya da tabloya çevirme. Ama metrik sinyaldir, hedef değil: eşiğe hedef muamelesi yapılırsa Goodhart işler — metot mekanik ikiye bölünür, sayı düşer, gerçek karmaşıklık kalır; doğru kullanım "en karmaşık × en sık değişen × en az test edilen" kesişimini refactoring bütçesine adres göstermektir. SonarQube bulguları sınıflandırır (bug, vulnerability/hotspot, code smell) ve kalite kapısı bir eşikler kümesidir: sağlanmazsa analiz başarısızdır ve birleştirme engellenebilir. "Yeni kod odağı"nın önemi pratikliğindedir: on yıllık tabana sıfır bulgu dayatmak felç eder; kapı değişen ve yeni yazılan koda bakar — yeni kodda sıfır yeni bug, yeni kodda kapsama eşiği — eski taban dokunuldukça temizlenir. Yanlış pozitifler gerekçeyle kapatılır; kuralı toptan kapatmak ya da bulguları sessizce yok saymak sinyali öldürür.
Coverlet/ReportGenerator zinciri nasıl çalışır ve rapor nasıl okunur? Mutasyon testi kapsamanın ölçemediği neyi ölçer, Stryker çıktısı nasıl yorumlanır?
Coverlet, testler koşarken hangi satır ve dalların çalıştığını toplar: dotnet test --collect:"XPlat Code Coverage" makine formatında (Cobertura XML) çıktı verir; ReportGenerator bunu insan diline çevirir — dosya dosya, satır satır boyalı HTML: hangi satır hiç çalışmamış, hangi dal yarım. Okuma disiplini toplam yüzde değil boşluk haritasıdır: "iptal dalı hiç test edilmemiş" bulgusu "%84" bilgisinden değerlidir; eşik konacaksa mutlak toplama değil yeni/değişen koda konur. Kapsamanın ölçemediği şey doğrulama kalitesidir: assert'süz test de kapsama üretir. Mutasyon testi tam bunu ölçer — "testler, kodu bozduğumda fark eder mi?" Stryker.NET üretim kodunda küçük bozulmalar üretir (işleç değişimi, sabit oynatma, koşul tersleme) ve her mutant için paketi koşturur. Çıktı yorumu: Killed — bir test kırmızı yandı, iyi; Survived — hiçbir test fark etmedi: ya assert zayıf, ya sınır testi eksik (>= → > mutantının sağ kalması sınır değer testinin yokluğudur), ya kod ölü; No coverage — mutanta hiçbir test dokunmuyor; Timeout — killed sayılır. Asıl değer skor değil sağ kalan mutant listesidir: her biri somut bir test iyileştirme görevidir. Maliyeti yüksektir (her mutant × tüm testler), bu yüzden hedefli kullanılır: kritik iş kuralı modülleri, gece koşusu, değişen dosyalar — her commit'te tüm çözüm değil.
12Sınav köşesi
Yazılıda nasıl sorulur
(1) Statik kod analizi nedir, dinamik analizden farkı nedir? (2) Linter ve biçimlendirici ne işe yarar, farkları nelerdir? (3) Kod kapsaması (coverage) nasıl ölçülür? (4) Satır kapsaması ile dal kapsaması arasındaki fark nedir? (5) Döngüsel karmaşıklık nedir, nasıl hesaplanır? (6) Kod kalitesi metrikleri nelerdir? (7) SonarQube ne için kullanılır, kalite kapısı nedir? (8) Code smell nedir, örnek veriniz. (9) Mutasyon testi nedir? (10) Uyarıların hataya çevrilmesi neden önerilir?
Sözlüde nasıl anlatılır — 2 dakikalık taslak
Düzen: (1) İlke — "Makineye devredilebilen her kalite kontrolünü makineye devrederim: hata kalıpları analizöre, biçim biçimlendiriciye, davranış testlere; insan incelemesini tasarıma ve isabete saklarım." (2) Altyapı — ".NET'te Roslyn analizörlerini açar, uyarıyı hataya çeviririm — görmezden gelinebilen sinyal birikip ölür; kuralları .editorconfig'e koyarım ki standart depoda yaşasın ve kural değişikliği bir PR olsun. Susturmayı yalnız gerekçeyle kabul ederim." (3) Ölçüm — "Karmaşıklığı testle ilişkisinden okurum: dal kapsaması için karmaşıklık kadar test gerekir. Metrikleri hedef değil sinyal olarak kullanırım — en karmaşık, en sık değişen, en az test edilen kesişimi refactoring bütçemin adresidir." (4) Platform — "Sonar'da kalite kapısını yeni koda kurarım: eski tabana sıfır bulgu dayatmak felçtir, yeni kodda sıfır yeni bug + kapsama eşiği ilerletir." (5) Kıdem vuruşu — "Kapsamayı Coverlet + ReportGenerator ile boşluk haritası olarak okurum, yüzde hedefi koymam; doğrulama kalitesini ise Stryker ile ölçerim — sağ kalan her mutant, kapsanan ama test edilmeyen kodun kanıtıdır ve bana somut bir test görevi verir. Mutasyonu da hedefli koşarım: kritik modüller, gece koşusu."
