---
title: 'Çevrimsel Karmaşıklığı Azaltma'
source: 'https://academia.sh/tr/kurslar/temiz-kod/cevrimsel-karmasikligi-azaltma'
course: 'Temiz Kod'
language: tr
updated: '2026-08-17T18:10:37+00:00'
license: 'CC BY-SA 4.0'
---

# Çevrimsel Karmaşıklığı Azaltma

Dallanma sayısının ölçülmesi ve düşürülmesi: karar noktalarını sayan bir ölçerin yazılması, ücretlendirme fonksiyonunun iç içe geçmiş ve sadeleştirilmiş sürümlerinde çevrimsel karmaşıklığın karşılaştırılması, koruma cümlesi ve arama tablosu dönüşümleriyle dallanmanın veriye taşınması.

Biçim kararları kodun göze nasıl göründüğünü belirliyor, yapısını değil. Düzgün
girintilenmiş bir fonksiyon da okunması güç olabilir: iç içe geçmiş koşullar, birden çok
çıkış noktası ve birleşik mantıksal ifadeler girintiyle düzelmez. Bu güçlüğün ölçüsü,
denetim akışının kaç yere ayrıldığıdır.

**Çevrimsel karmaşıklık** (cyclomatic complexity) bu sayıdır. Bir fonksiyonun denetim
akış grafiğinde birbirinden bağımsız kaç yol bulunduğunu verir ve tek bir kuralla
hesaplanır: karar noktası sayısı artı bir. Karar noktası, akışı ikiye ayıran her yapıdır —
`if`, `for`, `while`, `case`, `catch`, `&&`, `||`, `??` ve üçlü koşul. Sayı iki şeye
karşılık gelir: fonksiyonu okuyan kişinin aklında tutması gereken durum sayısı ve tüm
dalları geçen bir sınama kümesinin alt sınırı.

## Kararların Tek Fonksiyonda Toplandığı Sürüm

Ücretlendirme fonksiyonu zamanla üç iş daha üstlendi: girdi doğrulama, müşteri türüne
göre indirim ve bölgeye göre katsayı. Hepsi aynı fonksiyonda, iç içe.

```sh
mkdir -p karmasik sade
```

```js
// karmasik/ucret.mjs — dogrulama, indirim ve bolge kararlari ic ice
const HACIMSEL_BOLEN = 3000;

export function ucretHesapla(gonderi, tarife, musteri) {
  if (gonderi.agirlikKg > 0) {
    if (gonderi.enCm > 0 && gonderi.boyCm > 0 && gonderi.yukseklikCm > 0) {
      const hacimsel = (gonderi.enCm * gonderi.boyCm * gonderi.yukseklikCm) / HACIMSEL_BOLEN;
      const agirlik = gonderi.agirlikKg > hacimsel ? gonderi.agirlikKg : hacimsel;
      let ucret = 0;
      for (const kademe of tarife.agirlikKademeleri) {
        if (ucret === 0 && agirlik <= kademe.ustSinirKg) {
          ucret = kademe.kiloBasiUcret * agirlik;
        }
      }
      if (ucret === 0) {
        throw new RangeError("kademe disi agirlik");
      }
      if (musteri.tur === "sozlesmeli") {
        ucret = ucret * 0.85;
      } else if (musteri.tur === "kurumsal") {
        ucret = ucret * 0.92;
      }
      if (gonderi.bolgeKodu === 3 || gonderi.bolgeKodu === 4) {
        ucret = ucret * 1.8;
      } else if (gonderi.bolgeKodu === 2) {
        ucret = ucret * 1.35;
      }
      return ucret < tarife.asgariUcret ? tarife.asgariUcret : ucret;
    } else {
      throw new RangeError("olculer pozitif olmali");
    }
  } else {
    throw new RangeError("agirlik pozitif olmali");
  }
}
```

## Sadeleştirilmiş Sürüm

İkinci sürüm üç dönüşüm uygular. **Koruma cümlesi:** doğrulama koşulları ters çevrilip
başa alınır, geçerli olmayan girdi erkenden atılır ve asıl hesap girinti düzeyinin
dibinden çıkar. **Arama tablosu:** indirim ve bölge kararları koşul zincirinden veri
yapısına taşınır. **Ayıklama:** kalan işler kendi fonksiyonlarına alınır.

```js
// sade/ucret.mjs — ayni davranis, kararlar tabloya ve koruma cumlelerine tasinmis
const HACIMSEL_BOLEN = 3000;
const INDIRIM_ORANLARI = { sozlesmeli: 0.15, kurumsal: 0.08 };
const BOLGE_KATSAYILARI = { 1: 1, 2: 1.35, 3: 1.8, 4: 1.8 };

function olculeriDogrula(gonderi) {
  if (gonderi.agirlikKg <= 0) throw new RangeError("agirlik pozitif olmali");
  const boyutlar = [gonderi.enCm, gonderi.boyCm, gonderi.yukseklikCm];
  if (boyutlar.some((o) => o <= 0)) throw new RangeError("olculer pozitif olmali");
}

function ucretlendirilenAgirlikKg(gonderi) {
  const hacimsel = (gonderi.enCm * gonderi.boyCm * gonderi.yukseklikCm) / HACIMSEL_BOLEN;
  return Math.max(gonderi.agirlikKg, hacimsel);
}

function kademeUcreti(agirlikKg, tarife) {
  const kademe = tarife.agirlikKademeleri.find((k) => agirlikKg <= k.ustSinirKg);
  if (kademe === undefined) throw new RangeError("kademe disi agirlik");
  return kademe.kiloBasiUcret * agirlikKg;
}

export function ucretHesapla(gonderi, tarife, musteri) {
  olculeriDogrula(gonderi);
  const agirlik = ucretlendirilenAgirlikKg(gonderi);
  const indirim = 1 - (INDIRIM_ORANLARI[musteri.tur] ?? 0);
  const katsayi = BOLGE_KATSAYILARI[gonderi.bolgeKodu] ?? 1;
  return Math.max(kademeUcreti(agirlik, tarife) * indirim * katsayi, tarife.asgariUcret);
}
```

Karşılaştırmanın anlamlı olması için iki sürümün aynı davranışı sürdürdüğü gösterilir.
Yedi durum sınanır: dört geçerli hesap ve üç hata.

```js
// karsilastir.mjs — iki surumun ayni girdilerde ayni sonucu verdigi dogrulanir
import { ucretHesapla as karmasik } from "./karmasik/ucret.mjs";
import { ucretHesapla as sade } from "./sade/ucret.mjs";

const tarife = {
  agirlikKademeleri: [{ ustSinirKg: 1, kiloBasiUcret: 38 }, { ustSinirKg: 5, kiloBasiUcret: 26 },
    { ustSinirKg: 10, kiloBasiUcret: 21 }, { ustSinirKg: 30, kiloBasiUcret: 17 }],
  asgariUcret: 52,
};
const kutu = { enCm: 30, boyCm: 24, yukseklikCm: 18 };
const DURUMLAR = [
  ["sozlesmeli, bolge 2", { ...kutu, agirlikKg: 2.4, bolgeKodu: 2 }, "sozlesmeli"],
  ["kurumsal, bolge 3", { ...kutu, agirlikKg: 6, bolgeKodu: 3 }, "kurumsal"],
  ["bireysel, bolge 1", { enCm: 10, boyCm: 10, yukseklikCm: 10, agirlikKg: 0.4, bolgeKodu: 1 }, "bireysel"],
  ["kurumsal, bolge 4", { ...kutu, agirlikKg: 12, bolgeKodu: 4 }, "kurumsal"],
  ["agirlik sifir", { ...kutu, agirlikKg: 0, bolgeKodu: 1 }, "bireysel"],
  ["en sifir", { ...kutu, enCm: 0, agirlikKg: 2, bolgeKodu: 1 }, "bireysel"],
  ["kademe disi", { ...kutu, agirlikKg: 40, bolgeKodu: 1 }, "bireysel"],
];

const sonuc = (fn, gonderi, tur) => {
  try {
    return fn(gonderi, tarife, { tur }).toFixed(2);
  } catch (hata) {
    return `hata: ${hata.message}`;
  }
};

for (const [ad, gonderi, tur] of DURUMLAR) {
  const a = sonuc(karmasik, gonderi, tur);
  const b = sonuc(sade, gonderi, tur);
  console.log(`${ad.padEnd(20)} ${a === b ? "ayni  " : "FARKLI"} ${a}`);
}
```

```
sozlesmeli, bolge 2  ayni   128.89
kurumsal, bolge 3    ayni   208.66
bireysel, bolge 1    ayni   52.00
kurumsal, bolge 4    ayni   337.82
agirlik sifir        ayni   hata: agirlik pozitif olmali
en sifir             ayni   hata: olculer pozitif olmali
kademe disi          ayni   hata: kademe disi agirlik
```

## Ölçer

Ölçer kaynağı fonksiyonlara ayırır, her gövdede karar noktası örüntülerini sayar ve
karmaşıklığı karar sayısının bir fazlası olarak verir. Modül satırı, fonksiyon başına
hesaplanan değerlerin toplamıdır.

```js
// karmasiklik-olc.mjs — her fonksiyonun karar noktalarini ve cevrimsel karmasikligini sayar
import { readFileSync } from "node:fs";

// Denetim akisini dallandiran her yapi bir karar noktasidir.
const KARAR = [
  ["if", /\bif\s*\(/g], ["for", /\bfor\s*\(/g], ["while", /\bwhile\s*\(/g],
  ["case", /\bcase\s+/g], ["catch", /\bcatch\s*\(/g],
  ["&&", /&&/g], ["||", /\|\|/g], ["??", /\?\?/g], ["?:", /\?(?!\?)[^:\n]*:/g],
];

function fonksiyonlar(kaynak) {
  const cikti = [];
  let acik = null, derinlik = 0;
  for (const satir of kaynak.split("\n")) {
    if (acik === null) {
      const m = satir.match(/^(?:export\s+)?function\s+(\w+)/);
      if (m === null) continue;
      [acik, derinlik] = [{ ad: m[1], govde: [] }, 0];
    }
    acik.govde.push(satir);
    derinlik += (satir.match(/{/g) ?? []).length - (satir.match(/}/g) ?? []).length;
    if (derinlik === 0) { cikti.push({ ad: acik.ad, govde: acik.govde.join("\n") }); acik = null; }
  }
  return cikti;
}

for (const dosya of process.argv.slice(2)) {
  console.log(dosya);
  let toplam = 0;
  for (const { ad, govde } of fonksiyonlar(readFileSync(dosya, "utf8"))) {
    const sayilar = KARAR.map(([etiket, oruntu]) => [etiket, (govde.match(oruntu) ?? []).length]);
    const karar = sayilar.reduce((t, [, n]) => t + n, 0);
    const dokum = sayilar.filter(([, n]) => n > 0).map(([e, n]) => `${e}:${n}`).join(" ");
    toplam += karar + 1;
    console.log(`  ${ad.padEnd(24)} karar=${String(karar).padEnd(3)}M=${String(karar + 1).padEnd(3)}${dokum || "-"}`);
  }
  console.log(`  ${"= modul".padEnd(24)} toplam M=${toplam}`);
}
```

```sh
node karmasiklik-olc.mjs karmasik/ucret.mjs sade/ucret.mjs
```

```
karmasik/ucret.mjs
  ucretHesapla             karar=15 M=16 if:8 for:1 &&:3 ||:1 ?::2
  = modul                  toplam M=16
sade/ucret.mjs
  olculeriDogrula          karar=2  M=3  if:2
  ucretlendirilenAgirlikKg karar=0  M=1  -
  kademeUcreti             karar=1  M=2  if:1
  ucretHesapla             karar=2  M=3  ??:2
  = modul                  toplam M=9
```

Tek fonksiyonda toplanan on beş karar noktası, on altı bağımsız yol demektir. Sadeleşmiş
sürümde en yüksek değer üçtür ve modül toplamı dokuza inmiştir. İki sayı ayrı şeyler
söylüyor: modül toplamı işin bütününde kaç dal bulunduğunu, fonksiyon başına düşen değer
ise bir kişinin tek seferde ne kadarını aklında tutmak zorunda olduğunu.

## Dallar Aynı Fonksiyonda Çarpılır

Toplam neden dokuza inebiliyor? Bir kısmı dallanmanın veriye taşınmasından geliyor:
indirim zinciri iki koşuldan iki tablo satırına, bölge zinciri iki koşuldan dört tablo
satırına dönüştü. Kalan kazanç ise bölmenin kendisinden.

Aynı fonksiyonda duran dallar **çarpılır**. Karmaşık sürümde doğrulama geçtikten sonra
indirim üç sonuç veriyor (sözleşmeli, kurumsal, ikisi de değil), bölge üç sonuç veriyor
(uzak, komşu, aynı şehir), asgari ücret karşılaştırması iki sonuç veriyor. Bunlar
birbirinden bağımsız olduğu için fonksiyonun sonunu belirleyen birleşim sayısı
3 × 3 × 2 = 18'dir; hepsi tek bir gövdenin içinde bulunur.

Aynı üç karar ayrı fonksiyonlara dağıtıldığında birleşim sayısı **toplanır**: her fonksiyon
kendi dallarıyla ayrı ayrı sınanır, çünkü aralarındaki bağ yalnız bir değerin geçirilmesidir.
Tablo dönüşümü bu adımı daha da ileri götürür: `INDIRIM_ORANLARI` ve `BOLGE_KATSAYILARI`
birer veri satırı olduğu için hiç dal üretmezler. Yeni bir müşteri türü eklemek karmaşık
sürümde bir `else if` dalı, sadeleşmiş sürümde bir tablo satırı ekler; birincisi
karmaşıklığı bir arttırır, ikincisi arttırmaz.

## Sayının Söylemediği

Çevrimsel karmaşıklık ölçüsünün sınırları var ve bunları bilmeden kullanmak yanlış
kararlar ürettirir.

**Sayı ile anlaşılırlık aynı şey değildir.** Uzun bir `switch` bloğu yüksek bir değer
üretir ama tek bakışta okunabilir; iki iç içe döngü içindeki tek koşul daha düşük bir
değer üretir ve daha zor izlenir. Ölçü, akışın kaça ayrıldığını sayar; ayrılan yolların
birbirine ne kadar benzediğini saymaz.

**Sayı bölerek düşürülür ama bölme bedavaya gelmez.** On beş karar noktasını on beş
fonksiyona dağıtan bir kod, ölçünün gözünde kusursuzdur ve okunması güçtür: okuyan kişi
bu kez çağrı zincirini izlemek zorunda kalır. Önceki dersin ölçütü burada devreye girer;
bölme yerleri soyutlama düzeyine göre seçilir, sayıyı düşürmek için seçilmez.

**Yüksek değer bir kusur değil, bir işarettir.** Ölçüm bir fonksiyonu işaretlediğinde
sorulacak soru "bu nasıl bölünür" değil, "bu kararların hepsi gerçekten burada mı
verilmeli" sorusudur. Karmaşık sürümdeki sekiz `if` bunun örneğiydi: üçü doğrulamaya,
ikisi indirime, ikisi bölgeye aitti ve hiçbiri ücret hesabının kendisi değildi.

## Özet

- Çevrimsel karmaşıklık, karar noktası sayısının bir fazlasıdır ve fonksiyonun denetim
  akışındaki bağımsız yol sayısını verir; karar noktaları `if`, `for`, `while`, `case`,
  `catch`, `&&`, `||`, `??` ve üçlü koşuldur.
- İç içe sürümde tek fonksiyon on beş karar noktası ve M = 16 taşıyordu; koruma cümlesi,
  arama tablosu ve ayıklama dönüşümlerinden sonra en yüksek fonksiyon değeri 3, modül
  toplamı 9 oldu.
- İki sürüm yedi durumda da aynı sonucu üretiyor; ölçüm davranış değişmeden yapıldı.
- Aynı gövdedeki bağımsız dallar çarpılır (3 × 3 × 2 = 18 birleşim), ayrı fonksiyonlara
  dağıtıldığında toplanır; tabloya taşınan karar hiç dal üretmez.
- Ölçü akışın kaça ayrıldığını sayar, yolların anlaşılırlığını saymaz; yüksek değer
  bölünmesi gereken bir yeri değil, sorgulanması gereken bir karar yığılmasını gösterir.

## Sonraki Adım

Bölge katsayıları koşul zincirinden bir tabloya taşındı ve karmaşıklık düştü. Bu dönüşüm
her zaman doğru mudur? Tablo, anahtara karşılık bir değer döndürür; ya karşılık bir değer
değil de bir davranışsa, ya her tarife türü ücreti başka bir yöntemle hesaplıyorsa? O
noktada üçüncü bir seçenek belirir: çok biçimlilik. Sonraki ders aynı davranışın koşul
zinciri, arama tablosu ve çok biçimlilikle üç sürümünü yazar ve seçimi ölçülebilir bir
soruya bağlar: yeni bir tarife türü eklendiğinde kaç dosyaya, kaç satıra dokunulur.
