Döngüsel Karmaşıklık: Kodda Neyi Düzelt, Neyi Yeniden Yaz?

Summary

Döngüsel karmaşıklık, bir fonksiyondaki bağımsız yürütme yollarının sayısıdır. Thomas McCabe'in 1976'da tanımladığı bu metrik, yazılımın test edilebilirliğini ve bakım maliyetini doğrudan etkiler. CC değeri 10'u geçtiğinde hata oranı belirgin biçimde artar, refactoring ertelenir ve yeni özellik eklemek giderek güçleşir. SonarQube, CodeScene ve Code Climate gibi araçlar bu ölçümü otomatikleştirir ve CI/CD süreçlerine entegre eder.

Döngüsel karmaşıklık kavramını gösteren kod akış diyagramı

Döngüsel Karmaşıklık: Kodda Neyi Düzelt, Neyi Yeniden Yaz?

Saat 23.00. Kodu teslim etmen gerekiyor ama içinde bulunduğun fonksiyon 200 satır uzunluğunda, 14 iç içe if bloğu ve gömülü bir switch var. Döngüsel karmaşıklık değeri 28. Testi yazmaya kalkıştığında fark ediyorsun: her dalı kapsamak için 28 farklı senaryo gerekiyor. Şimdi projenin teknik borcunu ölçen bir sayıya bakıyorsun.

Bu yazı, döngüsel karmaşıklığın ne olduğunu, hangi değerden itibaren ciddi sorun yarattığını ve nasıl düşürüleceğini gösteriyor. Teori değil, çalışan bir pipeline'a nasıl entegre edildiği. Araç seçimi, eşik belirleme, refactoring teknikleri: bunların hepsini somut örneklerle ele alacağız.

Döngüsel Karmaşıklık Tam Olarak Neyi Ölçer?

Thomas J. McCabe'in 1976'da tanımladığı döngüsel karmaşıklık, bir programın kontrol akış grafiğindeki bağımsız yolların sayısıdır. Matematiksel tanım şudur:

CC = E - N + 2P

Burada E kenar sayısı, N düğüm sayısı, P bağlantılı bileşen sayısıdır. Pratikte her if, else if, for, while, case, && ve || ifadesi bu sayacı bir artırır. Başlangıç değeri 1'dir; her koşul eklendikçe artar.

Bunun pratik karşılığı nettir: bir fonksiyon için yazılması gereken minimum birim testi sayısı, döngüsel karmaşıklık değerine eşittir. CC = 15 ise en az 15 farklı test senaryosu gerekiyor. Bu sayıyı küçümseme. Her test senaryosu yazılmadığında dalların bir kısmı test dışı kalır ve üretim ortamında hata üretir.

Şu örneği düşün: ödeme işleyen bir fonksiyon, kart türünü kontrol ediyor, bakiyeyi doğruluyor, ülke kısıtlamalarını ele alıyor ve son olarak fraud skoruna göre işlemi onaylıyor. Her koşul dal ekler. Dört koşul grubu varsa ve her birinde iki-üç seçenek mevcutsa, CC kolayca 12-15 aralığına taşır. O fonksiyonu değiştirirken her dalı aklında tutmak zorundaysın. Hata kaçınılmaz hale gelir.

Önemli bir ayrıntı: döngüsel karmaşıklık, fonksiyon içindeki koşul sayısını ölçer, fonksiyon uzunluğunu değil. Kısa ama koşul dolu bir fonksiyon, uzun ama düz akan bir fonksiyondan çok daha yüksek CC taşıyabilir. Bu yüzden sadece satır sayısına bakarak kalite değerlendirmesi yapamazsın.

Hangi Eşik Değeri Kabul Edilebilir?

Sektörün yerleşik referans noktaları şöyle:

NASA'nın geliştirme standartları ve havacılık sertifikasyonu DO-178C, 10'u mutlak sınır olarak kabul eder. Web uygulamalarında bazı ekipler 15'e kadar tolerans gösterir ama bu bir tavan olarak görülmeli, hedef olarak değil.

Dil ve bağlam eşiği etkiler. Python'da list comprehension'lar CC'yi düşük gösterebilir ama okunabilirliği artırmaz. TypeScript'te generic tür koşulları CC'ye yansımaz ama zihinsel yük ekler. Sayı tek başına yeterli bir rehber değildir; bağlamla birlikte değerlendirilmelidir.

Eşik belirlerken ekip büyüklüğü ve kod değişim hızı da hesaba katılmalı. İki kişilik bir indie hacker ekibi 10'dan yüksek tolerans gösterebilir çünkü tüm kod tabanını birkaç saatte gözden geçirebilirler. On kişilik bir ekipte aynı fonksiyona farklı kişiler dokunsun, o zaman 10'un üzeri hızla kontrolden çıkar.

Döngüsel karmaşıklık kontrol akış grafiği ve dal sayımı örneği

Yüksek Döngüsel Karmaşıklık Neden Tehlikeli?

Capers Jones'un çalışmalarına göre CC > 10 olan modüllerde hata yoğunluğu, CC < 5 olan modüllere kıyasla ortalama 4 kat daha yüksektir. Bu sadece soyut bir istatistik değil.

Pratikte şu örüntüyü görüyoruz: açık kaynak projelerdeki güvenlik açıklarının büyük çoğunluğu, CC değeri 15'in üzerinde olan fonksiyonlarda tespit edilmiştir. Yüksek karmaşıklık sadece test maliyetini artırmaz; saldırı yüzeyini de genişletir çünkü test dışı kalan dallar doğrulama eksikliği taşır. Giriş doğrulaması gereken bir dal hiç test edilmediyse, orada bir güvenlik açığı olduğunu üretim ortamı bulmadan bilemezsin.

Ekip dinamiklerine etkisi ise daha az konuşulan bir boyuttur. Bir fonksiyona dokunmaktan korkulduğunda, o fonksiyonun CC değeri genellikle 20'nin üzerindedir. "Çalışıyor, elleme" kültürünü besleyen şey tam olarak budur. Ekip yeni özellikler üzerine yığmaya devam eder, mevcut yapı bozulmaya devam eder ve teknik borç katlanır. Bu döngüyü kırmak için metriği görünür kılmak gerekir.

Bir başka gerçek: yüksek CC olan bir fonksiyon, code review sürecini de zehirler. Reviewer 200 satır koda bakar, hangi dalın ne yaptığını anlamakta zorlanır ve sonunda beklenmedik bir onay verir. Hatalar burada sızmaya devam eder. Karmaşık fonksiyonlar, iyi niyetli reviewerları bile etkisiz kılar.

Ayrıca onboarding maliyetini de göz önünde bulundur. Yeni bir geliştirici projeye katıldığında, CC değeri yüksek dosyaları anlamak için çok daha fazla zaman harcar. Bu, doğrudan sprint hızını etkiler ve yeni ekip üyelerinin katkı süresini uzatır.

Döngüsel Karmaşıklığı CI/CD Pipeline'ına Nasıl Entegre Edersin?

Ölçmek yetmez. Ölçümü zorunlu hale getirmek gerekir. Bunun için birkaç yaklaşım var.

Python projeleri için radon ile basit bir gate:

- name: Dongusel karmasiklik kontrolu
  run: |
    pip install radon
    radon cc src/ -s -n C --total-average
    if radon cc src/ -s -n C | grep -q '^'; then
      echo 'Kritik karmasiklik tespit edildi' && exit 1
    fi

Bu adım, CC > 10 olan her fonksiyon için CI'ı kırar. Yeni kod bu eşiği geçemez. Eski kodu hemen düzeltmek zorunda değilsin; ama yeni eklenen her şey temiz olmak zorundadır. Bu, borcu dondurur ve yeni borca izin vermez.

JavaScript/TypeScript için:

npx complexity-report-html --format json src/ > complexity.json
jq '.functions[] | select(.complexity.cyclomatic > 10)' complexity.json

Önemli bir strateji: tüm mevcut koda gate uygulamak yerine, delta kontrolü yap. Yani sadece PR'ın değiştirdiği fonksiyonları kontrol et. Bu şekilde eski borcun varlığı yeni kodu bloke etmez ama kötüleşmeyi durdurursun. SonarQube bu yaklaşımı "new code definition" özelliğiyle kutudan çıkar şekilde destekler.

SonarQube bu süreci daha da ileri taşır: her PR'de hangi fonksiyonların eşiği aştığını, önceki versiyona göre neyin kötüleştiğini gösterir. Quality gate tanımlarsın, yeni kod gate'i geçmeden merge edilemez. Küçük ekipler için SonarCloud, self-hosted kurulum gerektirmeden aynı işlevi yerine getirir.

CI/CD pipeline'da döngüsel karmaşıklık eşiği kontrolü ekran görüntüsü

Refactoring Stratejileri: CC'yi Nasıl Düşürürsün?

Yüksek döngüsel karmaşıklığı azaltmanın birkaç kanıtlanmış yolu var. Bunları sırayla uygulayabilirsin.

Guard clause ile erken dönüş:

İç içe geçmiş koşulları düzleştirmenin en hızlı yolu budur.

# Önce (CC = 4)
def process_order(user, order):
    if user:
        if order:
            if order.is_valid():
                if not order.is_duplicate():
                    execute(order)

# Sonra (CC = 4, ama test edilebilir ve okunabilir)
def process_order(user, order):
    if not user:
        return
    if not order:
        return
    if not order.is_valid():
        return
    if order.is_duplicate():
        return
    execute(order)

CC değişmedi ama her koşul artık bağımsız test edilebilir. İç içe yapıyı çözdüğünde bir sonraki refactoring adımı için zemin hazırlanmış olur. Ölçülebilir kazanım: her koşul için ayrı test yazabilir, test süitini izole şekilde çalıştırabilirsin.

Polimorfizm ile uzun if-elif zincirlerini kırmak:

Bir fonksiyon if type == "A", elif type == "B", elif type == "C" zincirleri taşıyorsa, strategy pattern veya dictionary dispatch düşün. Yeni tür eklendiğinde mevcut kodu değiştirmiyorsun; sadece yeni bir sınıf veya handler ekliyorsun. Açık-kapalı prensibinin pratik karşılığı budur.

# Dictionary dispatch - uzun elif zinciri yerine
handlers = {
    "A": handle_type_a,
    "B": handle_type_b,
    "C": handle_type_c,
}
result = handlers.get(event_type, default_handler)(payload)

Bu yaklaşımda her handler fonksiyonu ayrı ayrı test edilebilir ve yeni bir tür eklemek mevcut koda dokunmayı gerektirmez.

Fonksiyon çıkarma:

CC = 22 olan bir fonksiyon varsa, içindeki mantıksal blokları 4-6 satırlık adlandırılmış fonksiyonlara çıkar. Her alt fonksiyon CC = 3-4 olur, ana fonksiyon CC = 5-6 civarına düşer. Toplam kod miktarı çok az artar ama yapı belirgin biçimde netleşir. Refactoring'i son haline getirmeden önce mevcut testlerin hâlâ geçtiğinden emin ol.

Adlandırma burada kritik: process_payment_step_1 gibi anlamlı olmayan isimler yerine validate_card_limit veya apply_fraud_score gibi niyeti açıklayan isimler seç. İyi adlandırılmış küçük fonksiyonlar, belge ihtiyacını azaltır.

Bu İşi Kolaylaştıran Araçlar

Hangi aracı seçeceğin, neyi çözmek istediğine bağlı.

Sadece ölçmek istiyorsan:

Bu araçların çoğu JSON çıktı üretebilir. JSON çıktıyı CI loglarına veya bir dashboard'a bağlamak görece basittir. Küçük projelerde haftalık bir rapor bile yeterli olabilir.

Takım genelinde eşik uygulamak istiyorsan:

SonarQube Community Edition ücretsiz ve self-hosted çalışır. Her commit'te CC raporunu gösterir, quality gate tanımlamana izin verir. Pull request merge edilmeden önce ekip liderinin onayını zorunlu kılabilir ya da belirli bir eşiği geçen her kod için otomatik blok koyabilirsin. Self-hosted kurulum gerektirmeden denemek istiyorsan SonarCloud, küçük açık kaynak projelerde tamamen ücretsizdir.

Tarihsel trend ve gerçek teknik borcu görmek istiyorsan:

CodeScene, kodu sadece ölçmez. Git geçmişiyle birleştirerek hangi dosyaların hem en sık değiştiğini hem de yüksek CC taşıdığını gösterir. Yüksek değişim sıklığı ve yüksek karmaşıklık birlikte en riskli kombinasyonu oluşturur. Sessiz ama karmaşık bir dosyadan çok daha tehlikelidir bu kombinasyon. Aynı zamanda "hotspot" analizi ile gerçekten refactoring önceliği verilmesi gereken dosyaları listeler.

Döngüsel Karmaşıklık Tek Başına Yeterli Bir Rehber mi?

Hayır. Bu noktayı açıkça söylemek gerekiyor.

CC değeri 3 olan ama okunması neredeyse imkânsız fonksiyonlar var: derin iç içe promis zinciri, yan etkileri gizlenmiş fonksiyonlar, karmaşık generic tür kısıtları. Bunları CC yakalamaz. Tersine, gerçek bir iş mantığı için CC = 12 son derece mantıklı olabilir çünkü her dal gerçek bir iş senaryosunu temsil eder ve bu senaryolar test edilmiştir.

Bu yüzden döngüsel karmaşıklığı şu ölçümlerle birlikte izlemek daha sağlıklıdır:

Döngüsel karmaşıklık, "buraya bakmalıyız" diyen ekipler için iyi bir pusulajır. Hangi fonksiyona refactoring zamanı harcayacağını bilmek istiyorsun? CC listesinin üst sıralarından başla, değişim sıklığıyla çakıştır ve öncelik sıranı oluştur. Bu kombinasyon, sezgiye dayalı kararlara göre çok daha güvenilir bir yol haritası verir.

Başlamak için pratik bir öneri: bu hafta radon cc . -s -n B komutunu çalıştır ve CC > 10 olan fonksiyon listesine bak. Üç ayda bir değişen ve yüksek CC taşıyan ilk beş dosyadan başla. Buradan küçük adımlarla ilerlemek, her şeyi aynı anda düzeltmeye çalışmaktan çok daha sürdürülebilirdir.

Refactoring'i sprint'e almadan önce mevcut test kapsamını kontrol et. Testlerin olmadığı bir fonksiyona güvenle dokunmak zordur. Önce kritik dalları kapsayan basit testler yaz, sonra yapıyı bozmadan CC'yi düşür. Bu sırayı tersine çevirmek, üretim ortamında istemediğin sürprizlere yol açar. Metrik bir araçtır; karar mekanizması değil. Doğru kullanıldığında hangi koda önce bakman gerektiğini açıkça gösterir. Bunu ekip ritüeline dahil et ve refactoring kararlarını tartışmalı hale getirmekten kurtulursun.

Frequently asked questions

Döngüsel karmaşıklık için hangi eşik değeri kullanılmalı?
Sektör standardı olarak CC <= 10 makul bir hedef. NASA ve DO-178C gibi kritik sistemler bu değeri mutlak sınır olarak kabul eder. Web uygulamalarında 15'e kadar tolerans gösterilir ama bu bir hedef değil, acil müdahale eşiği olarak düşünülmeli.
Döngüsel karmaşıklığı nasıl hesaplayabilirim?
Formül CC = E - N + 2P'dir. Pratik yol: fonksiyondaki her if, else if, for, while, case, &&, || ifadesini say ve 1 ekle. Python için radon, JavaScript için ESLint complexity kuralı veya lizard bu hesaplamayı otomatik yapar.
Yüksek döngüsel karmaşıklık güvenlik açıklarına neden olabilir mi?
Evet. Test dışı kalan dallar doğrulama eksikliği taşır. Açık kaynak projelerdeki güvenlik açıklarının büyük çoğunluğu yüksek CC'li fonksiyonlarda tespit edilmiştir. Karmaşıklık hem hata riskini hem de saldırı yüzeyini artırır.
Döngüsel karmaşıklık ile bilişsel karmaşıklık arasındaki fark nedir?
Döngüsel karmaşıklık bağımsız yürütme yollarını sayar; bilişsel karmaşıklık ise iç içe geçme derinliğini ve düşünsel kırılmaları ağırlıklandırarak insanın kodu okuma güçlüğünü ölçer. SonarQube her ikisini de raporlar.
Hangi diller için döngüsel karmaşıklık araçları var?
Çok geniş bir yelpaze: Python için radon, Go için gocyclo, JavaScript/TypeScript için ESLint complexity kuralı, C/C++/Java/Go/Swift dahil 10+ dil için lizard, Java için PMD. SonarQube neredeyse tüm yaygın dilleri destekler.
CI/CD'ye döngüsel karmaşıklık kontrolü nasıl eklenir?
radon (Python) veya lizard (çok dil) ile CLI üzerinden bir threshold check yaz ve CI adımına ekle. Eşiği geçen fonksiyon bulunursa exit 1 dönsün. SonarQube veya Code Climate'i projeye bağlarsan quality gate'ler bunu otomatik yönetir.
Döngüsel karmaşıklığı düşürmenin en hızlı yolu nedir?
Guard clause ile erken dönüş en hızlı kazanım sağlar: iç içe koşul zincirlerini düzleştirir. Ardından uzun fonksiyonları adlandırılmış alt fonksiyonlara böl. Her adımdan sonra testlerin hâlâ geçtiğini doğrula.