Derinlemesine Cognitive Complexity

İsa ÖZGÜR
8 min readSep 9, 2023

--

Görsel kaynağı: https://www.nutraingredients-usa.com/Article/2019/08/29/Study-strengthens-supports-for-lutein-zeaxanthin-for-cognitive-boosts

Merhabalar 🖐🏻

Bugün kodlarımızdaki “bilişsel karmaşıklık” probleminin adım adım oluşturduğum bir algoritma üzerinden nasıl çözümlendiğini göreceğiz. Öncesinde konuya ait tanımlarla, ardından da en kalıcı öğrenim çözümü olarak gördüğüm örneklerle konumuzu pekiştireceğiz. Hazırsanız başlayalım!

Tanım

  1. Cognitive Complexity Nedir?
  • Bir kod parçasının(metot, fonksiyon) bilişsel olarak anlaşılma düzeyini ölçmeye yarayan bir kavramdır.

2. Sonarqube Nedir?

  • SonarQube, yazılım geliştirme süreçlerinde kod kalitesini, güvenliğini ve sürdürülebilirliğini değerlendirmek ve iyileştirmek için kullanılan bir açık kaynaklı platform ve araç setidir.
  • NOT: Projelerimizdeki cognitive complexity değerlerini ölçmek için Sonarqube kullanacağız.

3. Docker Container Nedir?

  • Uygulama ve tüm bağımlılıklarını (kod, çalışma zamanı, sistem araçları, kütüphaneler ve ayarlar gibi) tek bir pakette bir araya getiren ve bu paketi taşınabilir ve tekrarlanabilir hale getiren bir yazılım paketleme teknolojisidir.
  • NOT: Locale’imizdeki bir projede cognitive complexity analizi yapabilmek için Sonarqube’ü Docker Container üzerinden ayağa kaldıracağız.

Locale’de Sonarqube Ayağa Kaldırma

  • Yukarıda Docker Container sayesinde Sonarqube tool’unu ayağa kaldıracağımızdan söz etmiştim. Şimdi de adım adım bu entegrasyonu gerçekleştirelim.
  1. Docker Container’ımıza Sonarqube Ekleyelim:
  • Docker üzerinden Sonarqube İmajını Çekelim:
Detaylı Bilgi: https://hub.docker.com/_/sonarqube
  • Sonarqube Container’ı Yaratalım:

Tüm bunları yaparken Docker’ımızın da ayakta olduğundan emin olalım.

Bu işlemlerden sonra;

  • http://localhost:9000/ adresi üzerinde bir Sonarqube container yaratılmış olur.
  • Bu adrese gittikten sonra karşımıza bir login ekranı çıkar.
  • Bu default değerlerle login olduktan sonra sistem karşımıza şifremizi değiştirmemiz gerektiğiyle ilgili bir uyarı çıkarır. Dilediğiniz şekilde şifrenizi değiştirebilirsiniz, kullanıcı adınız ise “admin” olarak kalmaya devam eder.
  • Locale’deki bir projemizi Sonarqube’e entegre etmek için aşağıdaki adımları sırasıyla uygulayalım.
  • NOT: .NET projesi için kurulumlar yapılacaktır.
Manuel şekilde proje ekleyeceğimizi belritelim
Kendi seçtiğimiz bir isimle proje oluşturalım
Önerilen global ayarları işaretleyelim
Projemizi locale olarak analiz edeceğimizi belirtelim
Analiz edeceğimiz projemizi ayırt edebilmek için token generate edelim
Projemizde kullanılacak token örneği oluşturuldu
Analiz edeceğimiz projemizin tipini seçelim
  • Scanner .NET Core Global Tool kısmındaki analiz işlemlerimizi yapacak “sonarscanner” kurulur.
  • Ardından analiz işlemini yapacağımız projemizin dosya dizinine gittikten sonra bu dizin içerisinde Execute Scanner bölümündeki 3 komut sırasıyla çalıştırılır.
  • Bu komutlar proje token’ı yaratıldıktan sonra otomatik olarak bize oluşturularak verilir.
  • NOT: Analiz etmeden önce projedeki değişikliklerden sonra bu değişiklikleri kaydederek Execute Scanner bölümündeki 3 komut çalıştırılmalıdır. Bu sayede projenin son sürümü üzerinden analizler yapılmış olur.

Senaryo

  • Tüm kurulumlarımız tamamlandıktan sonra artık Cognitive Complexity örneğimize geçebiliriz.
  • Bir kullanıcı kayıt işlemi üzerinden öncesinde komplekslik derecesi yüksek şekilde kodumuzu yazıp, ardından da bu dereceyi nasıl düşürebildiğimizi açıklayacağım.
  • Bunu yaparken kendi oluşturmuş olduğum algoritmayı sizlerle paylaşıp adım adım nasıl buna uyarak metotlarımızı daha sade ve anlaşılır hale getirdiğimizi anlatacağım.
  • NOT: Sonarqube’ün de belirlediği varsayılan “cognitive complexity düzeyi 15'tir.”

Algoritma

Cognitive Complexity Ölçüm Kuralları

  1. İç içe geçmiş olan koşul veya döngü ifadeleri karmaşıklık seviyesini 1 arttırır.

ÖR: İç içe geçmiş şekilde 3 tane if’imiz olsun. Her iç içe geçmiş if seviyeyi 1 arttırır. Yani,

  • En dışarıdaki if seviyesi -> 1
  • Bir içindeki -> 2
  • En içerideki -> 3

Toplamda cognitive complexity değerleri: 1 + 2 + 3 = 6 olur.

2. İç içe geçmiş olan koşul veya döngü ifadelerinden aynı seviyede içe geçmişlik bulunanların değerleri artmaz, 1 olurlar.

ÖR: Bir for döngüsü içerisinde iç içe geçmemiş şekilde aynı düzeyde 2 tane if koşulumuz olduğunu düşünelim.

  • for döngüsü için -> 1
  • for içerisindeki ilk if için -> 2
  • for içerisindeki ikinci if için -> 1

Bir seviye bir kere aşıldıktan sonra aynı seviyede bu koşullar yazılmaya devam ediyorsa, artık o seviyede tekrar edenler yalnızca “1” değerinde bir karmaşıklık ifade ederler.

3. switch-case ifadesinin tamamı 1 karmaşıklıktadır. case içerisinde koşul ifadeleri olursa toplam karmaşıklık değişebilir.

NOT:

  • Seçim yapılan durumlarda(ÖR: kullanıcının hesap tipi seçimi) bu şekilde if’ler ile birlikte “hangi hesap tipi seçilmiş” kontrolü yapmak yerine switch-case ile değerlendirmek yalnızca 1 complexity puanı arttırdığından dolayı da best practise’dir.
  • Çünkü bir seçim durumu birden fazla gerçekleşebilecek durumu değil yalnızca tek bir durumun gerçekleşmesi halinde olacakları ifade eder.

4. Mantıksal operatörler(&& ve ||) karmaşıklık seviyesini mantık şekli değişmediği sürece 1 arttırır.

ÖR:

  • (a == 3 && b == 4 && c == 5) ifadesinde karmaşıklık -> 1'dir. Çünkü bilişsel olarak her biri aynı mantığa(AND) işaret ettiği için 1 kere sayılmaları yeterlidir.
  • (a == 3 || b == 4 || c == 5) karmaşıklık -> 1'dir. Yukarıdaki örnekle aynı sebepten.
  • (a == 3 || b == 4 || c == 5 && d == 6) karmaşıklık -> 2'dir. OR şeklinde giden mantığımıza AND işlemi de eklendiği için farklı bir mantık tipi olduğundan dolayı karmaşıklık düzeyi bu AND için de 1 artmış olur.
  • (a == 3 && b == 4 && c == 5 || d == 6 || e == 7 && f == 8) karmaşıklık -> 3
  • try-catch blokları 1 düzeyinde karmaşıklığa sahiptir.

Örneğimiz ile birlikte daha güzel şekilde bu durumları pekiştirelim :)

Kullanıcı Register İşlemi

  1. Kompleks Hali:
  • Cognitive Complexity kavramımızı anlatabilmek için olabildiğince bu işlemi kompleks hale getirmeye çalıştım. Örnek üzerinden giderken bu ölçümü nasıl yaptığımızı detaylı şekilde anlatıyor olacağım.
  • CQRS pattern üzerinden implemente edilen kullanıcı kayıt işlemi için oluşturduğumuz Handler sınıfındaki Handle metodunun karmaşıklık düzeyini birlikte düşürelim.
  • Önce karmaşıklıkları inceledikten sonra çözümüyle devam edeceğiz.
  • Sonar’ın vermiş olduğu uyarı şu şekilde(Issues sekmesinden ulaşılabilir) ;
En fazla 15 olması gerekirken 39 karmaşıklık düzeyimiz bulunuyor
  • 3 parça şeklinde Handle metodunun karmaşıklığını inceleyelim.
  1. Parça: Name ve Surname Alanları
  • Yukarıda bahsetmiş olduğum algoritmaya dönecek olursak, öncesinde işlemleri bölümlememiz gerektiğini ifade etmiştim.
  • Bu parçada da kullanıcı için Name ve Surname alanları kaydetmek üzere 2 bölüm bulunuyor.
  • Cognitive Complexity kurallarında da belirttiğim gibi karmaşıklık ifade eden yerleri yuvarlak içine alarak yanlarına karmaşıklık düzeyini yazdım.
  • İç içe olan if’lerde seviyenin 1 arttığını, aynı düzeyde olanlarda ise 1 olarak kaldıklarını gözlemleyebiliyoruz.

2. Parça: Email ve Password Alanları

  • Burada da Email ve Password olmak üzere 2 bölüme ayrılır işlemimiz.
  • 1. parçadaki işlemlerden farklı olarak Password kısmında && operatörünün birden fazla olmasına rağmen art arda aynı mantık olmasından dolayı 1 seviye artışına sebep olmasıdır. Onun dışındaki işlemler iç içe if’lerden doğan seviye ölçümleridir.

3. Parça: AccountType ve Roles Alanları

  • Bu parçada da farklı olan, diğer tüm alanlar için ilgili koşullar sağlanmadığı taktirde geriye bir değer dönmesi durumu varken Roles alanı için yalnızca gönderildiyse kayıtlanması sağlanıyor.
  • Bunun dışındaki komplekslik hesaplamaları kurallarda belirttiğim gibidir, yuvarlaklar içerisinde belirtilmişlerdir.
  • Son olarak kullanıcı veritabanına kaydedilerek success response dönülür ve işlem sonlanır.

Toplamda Handle metodu için Sonarqube’de verilen hatadaki gibi 39 komplekslik seviyesine ulaşıldığını görüyoruz. Şimdi oluşturduğum algoritmaya göre bu durumu çözelim :)

2. Sadeleştirilmiş Hali:

  • Yukarıda anlatmış olduğum her bir User entity’si üzerindeki alanı bölümler olarak kabul ederiz. Bu bizim algoritmamızın 3. adımındaki “İç içe işlevleri bağımlılıklarıyla birlikte bölümle” kuralına karşılık gelir.
  • Bu kurala uygun olarak 6 tane bölümümüz ortaya çıkar. Şimdi bu bölümleri teker teker detaylı bir şekilde sadeleştirip, son durumda Handle metodumuzun komplekslik düzeyi kaça düşmüş görelim.
  1. Bölüm: Name Alanı
  • Tüm iç içe işlevleri bölümledikten sonra sırasıyla Name alanından başladık. Hata veya değer dönülmesi gereken bir durum var mı diye baktık ve Name alanı boşsa veya 20 ile 2 uzunlukları arasında değilse hata dönüldüğünü gördük.
  • Bu durumu sadeleştirmeyi “Guard Clause ve Fast Fail” kavramıyla yaptık. Kısaca şu şekilde tanımlıyorum:

Olumsuzlukları eğer başta ele alıp çözümleyerek yolumuza devam edersek, elimizdeki olumlu durumları işlemek daha kolay ve rahat olur.

  • Biraz dramatik şekilde konuları açıklamayı her ne kadar sevsem de özünde kullanıcı kaynaklı hataları da daha iyi ele alıp bizi “Spaghetti Code” şeklinde tabir edilen kod biçimlerinden ve karmaşıklıktan kurtarmaya yarar.
  • Eski halinde 9 karmaşıklığa sahip olan Name alanı şu an yalnızca 2 karmaşıklığa sahiptir.
  • Algoritmamızın bir sonraki aşaması olan “Bölümü metot olarak parçala” sayesinde yazılımdaki “Seperation of Concerns” yani bağımlılıkları işlevine göre ayırarak Handle metodu içerisinden ayırdık.
  • Son olarak “Tüm bölümler metotlara parçalandı mı?” diye bakıyoruz ve henüz daha 5 tane bölüm olduğu için bitene kadar döngüye devam ediyoruz.

2. Bölüm: Surname Alanı

  • Name alanı değerlendirilmesi ile aynı yapıdadır. Orası tekrar incelenerek buradaki karmaşıklığın da 9'dan 2'ye düştüğü gözlemlenebilir.

3. Bölüm: Email Alanı

  • İlk başta Email alanının boş gönderilip gönderilmediğine baktık, eğer boş olsaydı aşağıda devam edildiği gibi “geçerli bir mail mi?” kontrolü EmailAddressAttribute sınıfı ile birlikte yapılmayacaktı.
  • Eğer boş gönderilmediyse geçerli bir mail mi kontrolü yapılır.
  • Guard Clause ve Fast Fail yaklaşımıyla öncesinde 5 olan Email karmaşıklığını 2'ye düşürebildik.

4. Bölüm: Password Alanı

  • İlk başta Password alanı boş mu dolu mu kontrolü yaptık.
  • Sonrasında Password alanı 6 karakterden küçükse, herhangi bir küçük/büyük harf veya sayı içermiyorsa geçersizdi.
  • Herhangi bir tanesinin oluşması durumunda bile geçersiz bir şifre girildiğinden dolayı tüm hepsini aynı koşula topladık.
  • Sonuç olarak eskiden 10 karmaşıklığa sahipken şu an yalnızca 3 karmaşıklığa sahip duruma geldi.

5. Bölüm: AccountType Alanı

  • Öncesinde geçerli değerlerin girilip girilmediğini sayısal aralık ile kontrol ettiğimiz AccountType enum’ı için Enum sınıfının IsDefined() metodundan yararlanarak gönderilen değerin geçerli bir değer olup olmadığını kontrol ediyoruz.
  • Sonuç olarak öncesinde 3 karmaşıklığa sahip olan bu bölüm şu anda 1 karmaşıklığa inmiş bulunuyor.

6. Bölüm: Roles Alanı

  • Şu ana kadar algoritmamızda hata veya sonuç döndürülen kısma girerek sadeleştirme işlemleri gerçekleştirmiştik.
  • Fakat burada zorunlu bir alan olmadığından dolayı yalnızca Roles listesi boş değilse kontrolü yapılarak ilgili kullanıcıya roller ekleniyor, boşsa hiçbir şey yapılmıyor.
  • Bu gibi zorunlu olmayan durumlarda her ne kadar algoritmaya göre doğrudan metoda ayrılmasını söylesem de, öncesinde karmaşık ve iç içe ifadeler bulunuyorsa bunların “Cognitive Complexity Ölçüm Kuralları” kısmında verdiğim bilgilere göre öncesinde sadeleştirilmesi gerekmektedir.
  • Buna ek olarak illa ki bir sonuç döndürülmese de boş olması durumunda ilgili alana farklı atamalar yapılabileceği de gözden kaçırılmamalıdır.

Final Bölümü: Handle Metodu

  • Tüm bu işlemlerin aradından Handle metodumuzun son hali :)
  • Artık Handle metodumuzun karmaşıklık düzeyi 39 değil yalnızca 5'tir.
  • Tüm metotlarımızın karmaşıklık düzeyinin 15'ten düşük olduğunu da gördükten sonra artık algoritmamızı “Son” aşamasına getirerek cognitive complexity problemimizi burada çözmüş bulunuyoruz.

Cognitive Complexity’yi olabildiğince edindiğim bilgiler ve oluşturduğum algoritma doğrultusunda çözümlemesini göstermeye çalıştım. Umarım sizler için yararlı bir kaynak olmuştur.

İlgili Projem -> https://github.com/ChessWizard/CognitiveComplexity.Examples

KAYNAKÇA

--

--

İsa ÖZGÜR

Software Developer & Professional Chess Player/Trainer