---
title: 'Kapsam ve Görünürlük'
source: 'https://academia.sh/tr/kurslar/temiz-kod/kapsam-ve-gorunurluk'
course: 'Temiz Kod'
language: tr
updated: '2026-08-17T18:10:37+00:00'
license: 'CC BY-SA 4.0'
---

# Kapsam ve Görünürlük

En dar görünürlük ilkesinin ithal grafiğiyle ölçülmesi: aynı ücret paketinin her adı dışa açan ve yalnız iki ad açan iki sürümünde dışa açılan yüzeyin, bağımlı dosya sayısının, ölü yüzeyin ve serbestçe değiştirilebilir ad sayısının karşılaştırılması.

İmza kararları bir fonksiyonun dışarıya ne söylediğini belirliyor. Aynı soru modül
düzeyinde de sorulur: bir modül dışarıya kaç ad açıyor? Önceki dersteki
`bolunmus/ucret.mjs` dosyasında `temelUcret` dışa aktarılmamış, iki ücret fonksiyonu
aktarılmıştı. Bu karar keyfî değil.

**Kapsam** (scope), bir adın görülebildiği kod bölgesidir. Bir modül dosyasında tanımlanan
her ad, varsayılan olarak yalnız o dosyada görünür; `export` sözcüğü o adı dosyanın
dışına taşır. Dışa açılan her ad, modülün geri alamayacağı bir sözdür: başka bir dosya o
adı kullandığı anda ad artık modülün kendi malı değildir. Bu ders o sözün bedelini
sayıyor.

## Her Adı Açan Paket

Ücret paketi dört dosyadan oluşuyor: hesabın kendisi ve onu kullanan üç modül — rapor,
etiket, fatura. Birinci sürümde hesap modülü her adını dışa açıyor.

```sh
mkdir -p genis dar
```

```js
// genis/ucret.mjs — modulun her adi disa acilmis
export const HACIMSEL_BOLEN = 3000;
export const ASGARI_UCRET = 52;
export const BOLGE_KATSAYILARI = { 1: 1, 2: 1.35, 3: 1.8 };
export const AGIRLIK_KADEMELERI = [{ ustSinirKg: 1, kiloBasiUcret: 38 },
  { ustSinirKg: 5, kiloBasiUcret: 26 }, { ustSinirKg: 10, kiloBasiUcret: 21 },
  { ustSinirKg: 30, kiloBasiUcret: 17 }];

export function hacimselAgirlikKg(gonderi) {
  return (gonderi.enCm * gonderi.boyCm * gonderi.yukseklikCm) / HACIMSEL_BOLEN;
}

export function ucretlendirilenAgirlikKg(gonderi) {
  return Math.max(gonderi.agirlikKg, hacimselAgirlikKg(gonderi));
}

export function kademeSec(agirlikKg) {
  return AGIRLIK_KADEMELERI.find((k) => agirlikKg <= k.ustSinirKg);
}

export function kademeUcreti(agirlikKg) {
  return kademeSec(agirlikKg).kiloBasiUcret * agirlikKg;
}

export function ucretHesapla(gonderi) {
  const agirlikKg = ucretlendirilenAgirlikKg(gonderi);
  return Math.max(kademeUcreti(agirlikKg) * BOLGE_KATSAYILARI[gonderi.bolgeKodu], ASGARI_UCRET);
}
```

Açık olan her ad, kullanılabilir olduğu için kullanılır.

```js
// genis/rapor.mjs — iki ic ada ve bir sabite dogrudan basvuruyor
import { ucretHesapla, kademeSec, AGIRLIK_KADEMELERI } from "./ucret.mjs";

export function rapor(gonderiler) {
  const satirlar = gonderiler.map((g) =>
    `${g.kod} kademe<=${kademeSec(g.agirlikKg).ustSinirKg}kg ucret=${ucretHesapla(g).toFixed(2)}`);
  return satirlar.concat(`kademe sayisi=${AGIRLIK_KADEMELERI.length}`);
}
```

```js
// genis/etiket.mjs — hacimsel bolene dogrudan basvuruyor
import { ucretlendirilenAgirlikKg, HACIMSEL_BOLEN } from "./ucret.mjs";

export function etiket(gonderi) {
  return `${gonderi.kod} ${ucretlendirilenAgirlikKg(gonderi).toFixed(2)}kg (bolen ${HACIMSEL_BOLEN})`;
}
```

```js
// genis/fatura.mjs — kademe ucretini ayrica hesaplatiyor
import { ucretHesapla, kademeUcreti } from "./ucret.mjs";

export function fatura(gonderi) {
  const agirlikKg = Math.max(gonderi.agirlikKg,
    (gonderi.enCm * gonderi.boyCm * gonderi.yukseklikCm) / 3000);
  return `${gonderi.kod} kademe=${kademeUcreti(agirlikKg).toFixed(2)} toplam=${ucretHesapla(gonderi).toFixed(2)}`;
}
```

`fatura.mjs` dikkat çekici: hacimsel bölen dışa açık olmasına rağmen o dosyada `3000`
sayısı elle yazılmış. Yüzeyin geniş olması tutarlılığı garanti etmiyor, yalnız seçenek
sayısını arttırıyor.

## İki Adı Açan Paket

İkinci sürümde hesap modülü iki ad açıyor: ücretin kendisi ve hesabın dökümü. Kullanan
modüllerin ihtiyacı olan ara değerler dökümde adlarıyla veriliyor; modülün sabitleri ve
ara fonksiyonları dışarı çıkmıyor.

```js
// dar/ucret.mjs — iki ad disa acik, gerisi modulun ici
const HACIMSEL_BOLEN = 3000;
const ASGARI_UCRET = 52;
const BOLGE_KATSAYILARI = { 1: 1, 2: 1.35, 3: 1.8 };
const AGIRLIK_KADEMELERI = [{ ustSinirKg: 1, kiloBasiUcret: 38 },
  { ustSinirKg: 5, kiloBasiUcret: 26 }, { ustSinirKg: 10, kiloBasiUcret: 21 },
  { ustSinirKg: 30, kiloBasiUcret: 17 }];

function hacimselAgirlikKg(gonderi) {
  return (gonderi.enCm * gonderi.boyCm * gonderi.yukseklikCm) / HACIMSEL_BOLEN;
}

function agirlikKg(gonderi) {
  return Math.max(gonderi.agirlikKg, hacimselAgirlikKg(gonderi));
}

function kademeSec(kg) {
  return AGIRLIK_KADEMELERI.find((k) => kg <= k.ustSinirKg);
}

function kademeUcreti(kg) {
  return kademeSec(kg).kiloBasiUcret * kg;
}

export function ucretHesapla(gonderi) {
  const kg = agirlikKg(gonderi);
  return Math.max(kademeUcreti(kg) * BOLGE_KATSAYILARI[gonderi.bolgeKodu], ASGARI_UCRET);
}

export function ucretDokumu(gonderi) {
  const kg = agirlikKg(gonderi);
  return { agirlikKg: kg, hacimselBolen: HACIMSEL_BOLEN, kademeSayisi: AGIRLIK_KADEMELERI.length,
    kademeUstSinirKg: kademeSec(kg).ustSinirKg, kademeUcreti: kademeUcreti(kg) };
}
```

```js
// dar/rapor.mjs — yalnizca dokum ve ucret uzerinden
import { ucretHesapla, ucretDokumu } from "./ucret.mjs";

export function rapor(gonderiler) {
  const satirlar = gonderiler.map((g) =>
    `${g.kod} kademe<=${ucretDokumu(g).kademeUstSinirKg}kg ucret=${ucretHesapla(g).toFixed(2)}`);
  return satirlar.concat(`kademe sayisi=${ucretDokumu(gonderiler[0]).kademeSayisi}`);
}
```

```js
// dar/etiket.mjs — bolen degeri dokumden geliyor
import { ucretDokumu } from "./ucret.mjs";

export function etiket(gonderi) {
  const dokum = ucretDokumu(gonderi);
  return `${gonderi.kod} ${dokum.agirlikKg.toFixed(2)}kg (bolen ${dokum.hacimselBolen})`;
}
```

```js
// dar/fatura.mjs — kademe ucreti dokumden geliyor
import { ucretHesapla, ucretDokumu } from "./ucret.mjs";

export function fatura(gonderi) {
  return `${gonderi.kod} kademe=${ucretDokumu(gonderi).kademeUcreti.toFixed(2)}` +
    ` toplam=${ucretHesapla(gonderi).toFixed(2)}`;
}
```

İki paketin aynı çıktıyı ürettiği doğrulanır.

```js
// karsilastir.mjs — iki paketin ayni ciktiyi urettigi dogrulanir
import { rapor as genisRapor } from "./genis/rapor.mjs";
import { etiket as genisEtiket } from "./genis/etiket.mjs";
import { fatura as genisFatura } from "./genis/fatura.mjs";
import { rapor as darRapor } from "./dar/rapor.mjs";
import { etiket as darEtiket } from "./dar/etiket.mjs";
import { fatura as darFatura } from "./dar/fatura.mjs";

const GONDERILER = [
  { kod: "GN-4172", agirlikKg: 2.4, enCm: 30, boyCm: 24, yukseklikCm: 18, bolgeKodu: 2 },
  { kod: "GN-4173", agirlikKg: 0.4, enCm: 10, boyCm: 10, yukseklikCm: 10, bolgeKodu: 1 },
];

const genis = [...genisRapor(GONDERILER), ...GONDERILER.map(genisEtiket), ...GONDERILER.map(genisFatura)];
const dar = [...darRapor(GONDERILER), ...GONDERILER.map(darEtiket), ...GONDERILER.map(darFatura)];

for (const [i, satir] of genis.entries()) {
  console.log(`${satir === dar[i] ? "ayni  " : "FARKLI"} ${satir}`);
}
```

```
ayni   GN-4172 kademe<=5kg ucret=151.63
ayni   GN-4173 kademe<=1kg ucret=52.00
ayni   kademe sayisi=4
ayni   GN-4172 4.32kg (bolen 3000)
ayni   GN-4173 0.40kg (bolen 3000)
ayni   GN-4172 kademe=112.32 toplam=151.63
ayni   GN-4173 kademe=15.20 toplam=52.00
```

## Yüzey Ölçer

Ölçer bir dizindeki modülleri okur, her modülün dışa açtığı ve içeride tuttuğu adları
çıkarır, sonra ithal satırlarından hangi adın hangi dosyalarca kullanıldığını sayar. Son
sütun, modülün kendi başına değiştirebileceği ad sayısıdır: dışarıdan bağımlısı olmayan
her ad bu sayıya girer.

```js
// yuzey-olc.mjs — modullerin disa actigi ad sayisi, bu adlarin bagimlilari ve degisim ozgurlugu
import { readFileSync, readdirSync } from "node:fs";

const acikAdlar = (m) => [...m.matchAll(/^export\s+(?:const|function|class)\s+(\w+)/gm)].map((e) => e[1]);
const icAdlar = (m) => [...m.matchAll(/^(?:const|function|class)\s+(\w+)/gm)].map((e) => e[1]);
const ithaller = (m) => [...m.matchAll(/import\s*{([^}]*)}\s*from\s*"\.\/([\w.-]+)"/g)]
  .map((e) => [e[2], e[1].split(",").map((s) => s.trim()).filter(Boolean)]);

for (const dizin of process.argv.slice(2)) {
  const dosyalar = readdirSync(dizin).filter((a) => a.endsWith(".mjs")).sort();
  const metin = Object.fromEntries(dosyalar.map((a) => [a, readFileSync(`${dizin}/${a}`, "utf8")]));
  const kullanim = new Map();
  for (const m of Object.values(metin)) {
    for (const [hedef, adlar] of ithaller(m)) {
      for (const ad of adlar) kullanim.set(`${hedef}:${ad}`, (kullanim.get(`${hedef}:${ad}`) ?? 0) + 1);
    }
  }
  console.log(dizin);
  for (const dosya of dosyalar) {
    const acik = acikAdlar(metin[dosya]);
    const ic = icAdlar(metin[dosya]);
    const bagimli = acik.map((ad) => [ad, kullanim.get(`${dosya}:${ad}`) ?? 0]);
    const bagli = bagimli.filter(([, n]) => n > 0);
    console.log(`  ${dosya.padEnd(11)} disa acik=${acik.length}  ic ad=${ic.length}` +
      `  bagimlisi olan=${bagli.length}  olu yuzey=${acik.length - bagli.length}` +
      `  serbestce degistirilebilir=${acik.length + ic.length - bagli.length}`);
    for (const [ad, n] of bagli) console.log(`    ${ad.padEnd(24)} bagimli dosya=${n}`);
  }
}
```

```sh
node yuzey-olc.mjs genis dar
```

```
genis
  etiket.mjs  disa acik=1  ic ad=0  bagimlisi olan=0  olu yuzey=1  serbestce degistirilebilir=1
  fatura.mjs  disa acik=1  ic ad=0  bagimlisi olan=0  olu yuzey=1  serbestce degistirilebilir=1
  rapor.mjs   disa acik=1  ic ad=0  bagimlisi olan=0  olu yuzey=1  serbestce degistirilebilir=1
  ucret.mjs   disa acik=9  ic ad=0  bagimlisi olan=6  olu yuzey=3  serbestce degistirilebilir=3
    HACIMSEL_BOLEN           bagimli dosya=1
    AGIRLIK_KADEMELERI       bagimli dosya=1
    ucretlendirilenAgirlikKg bagimli dosya=1
    kademeSec                bagimli dosya=1
    kademeUcreti             bagimli dosya=1
    ucretHesapla             bagimli dosya=2
dar
  etiket.mjs  disa acik=1  ic ad=0  bagimlisi olan=0  olu yuzey=1  serbestce degistirilebilir=1
  fatura.mjs  disa acik=1  ic ad=0  bagimlisi olan=0  olu yuzey=1  serbestce degistirilebilir=1
  rapor.mjs   disa acik=1  ic ad=0  bagimlisi olan=0  olu yuzey=1  serbestce degistirilebilir=1
  ucret.mjs   disa acik=2  ic ad=8  bagimlisi olan=2  olu yuzey=0  serbestce degistirilebilir=8
    ucretHesapla             bagimli dosya=2
    ucretDokumu              bagimli dosya=3
```

Hesap modülünün yüzeyi dokuzdan ikiye indi. Bundan daha önemlisi son sütun: geniş sürümde
modül kendi adlarının üçünü, dar sürümde sekizini kimseye sormadan değiştirebilir. Aradaki
fark, altı adın dışarıdan bağlanmış olmasıdır.

Üç kullanıcı modülün `olu yuzey=1` görünmesi ölçümün sınırıdır: bunlar paketin dış
yüzüdür, kendi içinde kimse onları kullanmaz. Ölçü yalnız verilen dizindeki bağları görür.

## Yeniden Adlandırma Maliyeti

Değişim özgürlüğü soyut bir kavram değil. Bir adı yeniden adlandırmanın maliyeti, o adın
geçtiği dosya sayısıdır.

```sh
for d in genis dar; do
  echo "$d: kademeSec $(grep -rl kademeSec $d | wc -l | tr -d ' ') dosyada," \
    "HACIMSEL_BOLEN $(grep -rl HACIMSEL_BOLEN $d | wc -l | tr -d ' ') dosyada"
done
```

```
genis: kademeSec 2 dosyada, HACIMSEL_BOLEN 2 dosyada
dar: kademeSec 1 dosyada, HACIMSEL_BOLEN 1 dosyada
```

Fark iki dosyaya karşı bir dosya gibi görünüyor, çünkü paket küçük. Ölçünün asıl anlamı
oranda: dar sürümde bu iki ad **modülün içinde**dir ve dışarıdan kaç dosya olursa olsun
sayı bir kalır. Geniş sürümde sayı, o adı ithal eden dosya sayısıyla birlikte büyür.

Kademe seçiminin `Map` tabanlı bir yapıya dönüştürülmesi, hacimsel bölenin taşıyıcıya göre
değişen bir alana taşınması, asgari ücretin tarifeden okunması — bunların hepsi dar
sürümde modülün iç işidir. Geniş sürümde her biri dışarıdaki dosyaları da değiştirmeyi
gerektirir.

## En Dar Görünürlük İlkesi

İlke tek cümledir: **bir ad, işini görebilecek en dar kapsamda tanımlanır.** Uygulaması üç
adımdır.

**Varsayılan kapalıdır.** Yeni bir ad önce dosyanın içinde kalır. Dışa açma, bir kullanıcı
gerçekten ortaya çıktığında ve o kullanıcının başka yolu olmadığında yapılır.

**Genişletmek kolay, daraltmak zordur.** Kapalı bir adı açmak tek satırlık bir
değişikliktir ve hiçbir dosyayı bozmaz. Açık bir adı kapatmak, onu kullanan her dosyayı
değiştirmeyi gerektirir; ölçümdeki `bagimli dosya` sütunu tam olarak bu maliyettir.

**Yüzey, ara değerleri değil kararları taşır.** `ucretDokumu` bunun örneğiydi: kullanan
modüllerin ihtiyacı ara fonksiyonlar değil, hesabın sonuçlarıydı. Dışarıya bir sabit
vermek yerine o sabitin belirlediği sonucu vermek, sabitin değişme özgürlüğünü korur.

Aynı ilke fonksiyon içinde de işler. Bir değişken kullanıldığı bloğun dışında
tanımlandığında, o bloğun dışındaki her satır onu değiştirebilir hâle gelir; okuyan kişi
de değerin ne olduğunu anlamak için aradaki satırların hepsini izlemek zorunda kalır.

## Özet

- Dışa açılan her ad geri alınması pahalı bir sözdür; ölçüsü, o adı ithal eden dosya
  sayısıdır.
- Aynı davranışı üreten iki pakette hesap modülünün yüzeyi dokuz ve iki addı; ölü yüzey
  üçten sıfıra indi.
- Modülün kimseye sormadan değiştirebileceği ad sayısı üçten sekize çıktı; aradaki fark,
  dışarıdan bağlanmış altı addır.
- Ara değerleri değil kararları dışa açmak sabitlerin değişme özgürlüğünü korur;
  `ucretDokumu` sabitleri değil sonuçları veriyor.
- Genişletmek tek satırlık bir değişikliktir, daraltmak her bağımlı dosyayı değiştirmeyi
  gerektirir; bu yüzden varsayılan kapalıdır.

## Sonraki Adım

Görünürlük kararı bir modülün dışarıya ne verdiğini belirliyor. Bir soru daha kalıyor: bu
modüller hangi ölçüte göre ayrıldı? Ücret hesabı bir dosyada, rapor başka bir dosyada
duruyor; ayrım işlevsel görünüyor. Ancak bir kod tabanını asıl zorlayan şey, farklı
kişilerin farklı nedenlerle aynı dosyaya dokunmasıdır. Muhasebenin istediği bir yuvarlama
değişikliği ile operasyonun istediği bir kademe değişikliği aynı dosyada buluşuyorsa,
iki karar birbirini bekler. Sonraki ders bu buluşmayı ölçer: iki farklı değişim nedeni
aynı dosyaya kaç kez dokunuyor ve kod aktörlere göre düzenlendiğinde bu sayı ne oluyor.
