---
title: 'Derleme, Test ve Ekosistem'
source: 'https://academia.sh/tr/kurslar/java-ekosistem'
language: tr
updated: '2026-08-23T14:25:05+00:00'
license: 'CC BY-SA 4.0'
lessons: 12
outcomes:
    - 'Derleme aracıyla bağımlılık ve yaşam döngüsü yönetir'
    - 'Birim ve tümleşim testlerini sahte nesnelerle kurar'
    - 'Günlükleme cephesi ile gerçekleştirim ayrımını uygular'
    - 'Bir tipin derleme yolunda mı koşum yolunda mı gerektiğini ölçerek saptar'
---

# Derleme, Test ve Ekosistem

Bir Java programı iki ayrı sınıf yoluna karşı yaşar: derlendiği yola ve koştuğu yola, ve kaynak metin ikisini de göremez. Kurs derleme aracı modelini, bağımlılık kapsamlarını, test kurulumunu ve günlükleme cephesini tek bir soruyla ölçer — bu tip hangi yolda bulunmak zorunda, ve iki yol ayrıldığında kusur derlemede mi, koşumda mı, yoksa hiç görünmüyor mu?

## Derleme Araçları

Derleme aracının yaşam döngüsü, hedef ve görev modeli; bağımlılık kapsamlarının bir tipi hangi sınıf yolunda görünür kıldığı, geçişli bağımlılığın derlemede görünmeden koşumda gerekmesi ve aynı adın iki adayı çıktığında çözümün ne belirlediği; modül sınırlarının derlemede karşılığı; paketlemenin ürettiği çalıştırılabilir çıktının koşum yolunu nasıl taşıdığı.

### Derleme Aracı Modeli

Source: https://academia.sh/tr/kurslar/java-ekosistem/derleme-araci-modeli

Bir bildirimden bir derleme aracının türettiği evre dizisi sabittir ve bildirimin yazılış sırasından etkilenmez: üç bildirilen ögeden dört evre çıkar, ileri sıralı ve geri sıralı aynı bildirimden aynı dört evre türer. Aynı bildirimden çıkan iki sınıf yolu — biri derlemenin, biri koşumun — kaynak düzeyinde tutulan bir açıklama tipiyle ayrılır: sınıf dosyasının sabit havuzunda hiç görünmez, koşum sınıf yolunda da hiç gerekmez.

JVM, Eşzamanlılık ve Başarım kursunun kapanış dersi tek bir soruyu ölçtü: bir adımı hangi
iş parçacığının koşturduğu, çağrı yazımından okunamıyordu — yalnız koşturarak görülüyordu.
Aynı satır bir iş parçacığında da, başka bir iş parçacığında da aynı yazımdı; koşturan
tarafı belirleyen şey kaynak metnin dışındaydı.

Bu ders aynı sorunun bir katman dışını soruyor. Bir Java programı yalnız bir sınıf yoluna
karşı değil, iki ayrı sınıf yoluna karşı yaşar: derlendiği yol ve koştuğu yol. Kaynak metin
— hatta derlenmiş sınıf dosyasının kendisi — bu iki yolun ne içermesi gerektiğini söylemez;
söyleyen şey kaynağın dışında duran bir bildirimdir. Derleyicinin bir tipi görmesi için
gereken şey ile çalışma zamanının aynı tipi bulması için gereken şey aynı küme değildir ve
bu ayrımı üreten aracın kendisi bu dersin ve konunun sorusu.

Aracın adı yazılmıyor — hangi ürünün, hangi söz diziminin kullanıldığı bu dersin sorusu
değil. Sorulan şey şu: birkaç satırlık bir bildirimden bir derleme aracı ne kadarını
kendisi türetiyor ve türettiği şey neden kaynakta hiç görünmeyen iki ayrı listeye
bölünüyor?

Kursun üç konusu da üçüncü taraf araç adlarıyla anılan alanlar; bu yüzden derleme aracının
kendisi burada bir **davranış** olarak kuruluyor, bir ürün olarak değil. Ölçülen şey bir
bildirimden bir evre dizisi türeten kural ve o evrelerin sonunda ortaya çıkan iki sınıf
yolu — hangi ürünün hangi bayrağı kabul ettiği değil. Standart kitaplığın kendisi bu
modelin gerçek çalışan parçasını sağlıyor: derleyici, program içinden `ToolProvider` ile
çağrılıyor ve gerçekten derliyor.

#### Derleme Aracı Neyi Türetiyor

Bir bildirimin türettiği şey tek bir liste değil, iki ayrı şeydir: bir **evre dizisi**
(hangi sırayla ne yapılacağı) ve o evrelerin içinde kurulan **sınıf yolları** (her evrenin
hangi dizinlere baktığı). Bu iki şey birbirine karıştırılmamalı. "Kaynakları derle"
evresi tek bir derleme görevi çalıştırıyor: derleyiciyi bir kez çağırıyor. "Derleme sınıf
yolunu kur" evresi ise bildirimdeki her bağımlılığı tek tek gözden geçiren bir görev
çalıştırıyor — kapsamı derleme ya da ikisi olan her bağımlılığı listeye ekliyor,
kapsamı yalnız koşum olanı atlıyor. Aynı evre, "koşum sınıf yolunu kur", aynı bağımlılık
listesini bu kez tersinden süzüyor. Evrenin kendisi bir tane, ama içindeki derleme görevi
bildirimin uzunluğuna göre değişen sayıda iş yapıyor — bu, aşağıdaki ölçümde ayrı ayrı
görülecek iki sayının (bildirilen öge, türetilen evre) neden birbirine eşit çıkmayabileceğini
de açıklıyor.

#### Bildirimden Türeyen Evre Dizisi

Bir derleme aracına verilen girdi adım adım bir yordam değil, bir **bildirimdir**
(declaration): hangi kaynak dizininin derleneceği, hangi bağımlılıkların hangi
**bağımlılık kapsamıyla** (dependency scope) kullanılacağı. Aracın kendisi bu bildirimden
bir **derleme yaşam döngüsü** (build lifecycle) türetir — sabit sırayla işleyen bir
**evre** (phase) dizisi. Her evre bir ya da daha çok **derleme görevi** (task) çalıştırır;
burada yalnız evrelerin kendisi ölçülüyor, görevlerin içeriği değil.

Aşağıdaki `Bildirim` sınıfı bir bağımlılığı yalnız adı ve kapsamıyla tutuyor; kapsam üç
değerden birini alıyor: yalnız derlemede gerekli, yalnız koşumda gerekli, ya da ikisinde
birden.

- **DR1.** Derleme aracı burada bir model olarak kuruluyor; hiçbir gerçek ürünün adı, hedef
  adı, komut satırı bayrağı ya da bildirim söz dizimi kullanılmıyor. Evre adları ve bildirim
  biçimi bu dersin kendi kararı.
- **DR2.** Bildirilen öge sayısı, bildirimdeki her satırın — kaynak dizini bir, her
  bağımlılık bir — tek öge sayıldığı bir toplam. Bir bağımlılığın kapsamı öge sayısını
  değiştirmiyor.
- **DR3.** Türetilen evre dizisi sabit dört evreden oluşuyor ve bağımlılık sayısından ya da
  bildirimin yazılış sırasından etkilenmiyor; sıra aracın kendi kuralı, bildirimin tercihi
  değil.

```java
// Bildirim.java — derleme aracina verilen bildirim: kaynak dizini adi ve bagimliliklarin kapsam etiketi
import java.util.*;

public class Bildirim {
    record Bagimlilik(String ad, String kapsam) {}

    final String kaynakAdi;
    final List<Bagimlilik> bagimliliklar;

    Bildirim(String kaynakAdi, List<Bagimlilik> bagimliliklar) {
        this.kaynakAdi = kaynakAdi;
        this.bagimliliklar = bagimliliklar;
    }

    int bildirilenOgeSayisi() {
        return 1 + bagimliliklar.size();
    }
}
```

```java
// Evreler.java — bildirimden turetilen evre dizisi: sira bildirimin yazilis sirasina bakmaz
import java.util.*;

public class Evreler {
    static List<String> turet(Bildirim b) {
        List<String> dizi = new ArrayList<>();
        dizi.add("temizle");
        dizi.add("kaynaklari derle");
        dizi.add("derleme sinif yolunu kur");
        dizi.add("kosum sinif yolunu kur");
        return dizi;
    }
}
```

`Evreler.turet` bildirimin içeriğine hiç bakmadan dört evre üretiyor: temizleme, kaynak
derleme, derleme sınıf yolunu kurma, koşum sınıf yolunu kurma. Bildirimdeki bağımlılık
sayısı bir, iki ya da on olsun, dönen dizinin uzunluğu değişmiyor — çünkü ölçülen şey
bağımlılıkların kendisi değil, aracın onlardan ne türettiği.

#### Aynı Bildirimden İki Sayı, İki Sınıf Yolu

Ölçüm iki ayrı bildirimle başlıyor: aynı iki bağımlılık, biri ileri sırada yazılmış, öbürü
ters sırada. `Etiket` yalnız derlemede gerekli olarak, `Servis` yalnız koşumda gerekli
olarak işaretleniyor. `Etiket`, saklama politikası (retention policy) kaynak düzeyinde
tutulan bir açıklama (annotation) tipi: derleyici onu tip denetiminde kullanıyor, ama sınıf
dosyasına hiçbir iz bırakmıyor. `Servis`'e erişimse tam ters yoldan kuruluyor: `Program` onu
hiç doğrudan adlandırmıyor, yalnız bir ad dizgisiyle arıyor — bu yüzden derleme sınıf yoluna
hiç girmesi gerekmiyor.

- **DR4.** Etiket, saklama politikası kaynak düzeyinde tutulan bir açıklama tipi; derleyici
  tip denetiminde kullanır, sınıf dosyasına hiçbir referans bırakmaz.
- **DR5.** Servis'e erişim doğrudan tip referansıyla değil, ada göre aranan bir çağrıyla
  kuruluyor; bu yüzden derleme sınıf yoluna hiç girmiyor.
- **DR6.** Derleme sınıf yolu ile koşum sınıf yolu iki ayrı dizin listesi olarak kuruluyor;
  ikisi arasında ortak hiçbir dizin yok — Etiket'in dizini yalnız derlemede, Servis'in
  dizini yalnız koşumda veriliyor.

```java
// AracModeli.java — bildirimden turetilen evre dizisi bildirim sirasindan bagimsizdir, ve tek bildirimden iki sinif yolu cikar
import java.io.*;
import java.lang.classfile.*;
import java.lang.classfile.constantpool.*;
import java.net.*;
import java.nio.file.*;
import java.util.*;
import java.util.spi.ToolProvider;

public class AracModeli {

    static Path yaz(Path dizin, String ad, String kaynak) throws IOException {
        Files.createDirectories(dizin);
        Path p = dizin.resolve(ad + ".java");
        Files.writeString(p, kaynak);
        return p;
    }

    static boolean derle(Path cikti, List<Path> sinifYolu, Path kaynak) throws IOException {
        Files.createDirectories(cikti);
        List<String> arg = new ArrayList<>(List.of("-d", cikti.toString()));
        if (!sinifYolu.isEmpty()) {
            StringJoiner sj = new StringJoiner(File.pathSeparator);
            for (Path p : sinifYolu) sj.add(p.toString());
            arg.add("-classpath");
            arg.add(sj.toString());
        }
        arg.add(kaynak.toString());
        ToolProvider derleyici = ToolProvider.findFirst("javac").orElseThrow();
        StringWriter sessiz = new StringWriter();
        int sonuc = derleyici.run(new PrintWriter(sessiz), new PrintWriter(sessiz), arg.toArray(new String[0]));
        return sonuc == 0;
    }

    static boolean sabitHavuzundaAdiGeciyorMu(Path sinifDosyasi, String ad) throws IOException {
        ClassModel model = ClassFile.of().parse(sinifDosyasi);
        for (PoolEntry e : model.constantPool()) {
            if (e instanceof ClassEntry ce && ce.asInternalName().equals(ad)) return true;
        }
        return false;
    }

    public static void main(String[] args) throws Exception {
        var etiket = new Bildirim.Bagimlilik("Etiket", "derleme");
        var servis = new Bildirim.Bagimlilik("Servis", "kosum");

        Bildirim ileriSira = new Bildirim("kaynak", List.of(etiket, servis));
        Bildirim geriSira = new Bildirim("kaynak", List.of(servis, etiket));

        List<String> evreIleri = Evreler.turet(ileriSira);
        List<String> evreGeri = Evreler.turet(geriSira);

        System.out.println("-- evre dizisi, bildirim sirasindan bagimsiz --");
        System.out.println("ileri sirali bildirimden turetilen evreler: " + evreIleri);
        System.out.println("geri sirali bildirimden turetilen evreler : " + evreGeri);
        System.out.println("iki evre dizisi ayni mi                   : " + evreIleri.equals(evreGeri));
        System.out.println("bildirilen oge sayisi                     : " + ileriSira.bildirilenOgeSayisi());
        System.out.println("turetilen evre sayisi                     : " + evreIleri.size());

        Path kok = Files.createTempDirectory("arac");
        Path etiketD = kok.resolve("etiket"), servisD = kok.resolve("servis"), programD = kok.resolve("program");

        Path sEtiket = yaz(kok.resolve("kaynak"), "Etiket",
                "import java.lang.annotation.*;\n"
              + "@Retention(RetentionPolicy.SOURCE)\n"
              + "public @interface Etiket { String deger(); }");
        Path sServis = yaz(kok.resolve("kaynak"), "Servis",
                "public class Servis { public static String calistir() { return \"servis calisti\"; } }");
        Path sProgram = yaz(kok.resolve("kaynak"), "Program",
                "@Etiket(deger = \"ornek\")\n"
              + "public class Program {\n"
              + "  public static void main(String[] a) throws Exception {\n"
              + "    Class<?> s = Class.forName(\"Servis\");\n"
              + "    String r = (String) s.getMethod(\"calistir\").invoke(null);\n"
              + "    System.out.println(\"servis (yansima): \" + r);\n"
              + "  }\n"
              + "}");

        derle(etiketD, List.of(), sEtiket);
        derle(servisD, List.of(), sServis);

        List<Path> derlemeSinifYolu = List.of(etiketD);
        boolean programDerlendi = derle(programD, derlemeSinifYolu, sProgram);
        System.out.println();
        System.out.println("-- tek bildirimden iki sinif yolu --");
        System.out.println("Program, derleme sinif yoluna (yalniz Etiket) karsi derlendi mi   : " + programDerlendi);
        System.out.println("Program.class sabit havuzunda Etiket'e referans var mi            : "
                + sabitHavuzundaAdiGeciyorMu(programD.resolve("Program.class"), "Etiket"));

        List<Path> kosumSinifYolu = List.of(programD, servisD);
        URL[] url = new URL[kosumSinifYolu.size()];
        for (int i = 0; i < url.length; i++) url[i] = kosumSinifYolu.get(i).toUri().toURL();
        try (URLClassLoader yukleyici = new URLClassLoader(url, AracModeli.class.getClassLoader().getParent())) {
            Class<?> sinif = Class.forName("Program", true, yukleyici);
            System.out.println("Program, kosum sinif yoluna (Etiket yok, Servis var) karsi kosuyor mu:");
            sinif.getMethod("main", String[].class).invoke(null, (Object) new String[0]);
        }
    }
}
```

```
-- evre dizisi, bildirim sirasindan bagimsiz --
ileri sirali bildirimden turetilen evreler: [temizle, kaynaklari derle, derleme sinif yolunu kur, kosum sinif yolunu kur]
geri sirali bildirimden turetilen evreler : [temizle, kaynaklari derle, derleme sinif yolunu kur, kosum sinif yolunu kur]
iki evre dizisi ayni mi                   : true
bildirilen oge sayisi                     : 3
turetilen evre sayisi                     : 4

-- tek bildirimden iki sinif yolu --
Program, derleme sinif yoluna (yalniz Etiket) karsi derlendi mi   : true
Program.class sabit havuzunda Etiket'e referans var mi            : false
Program, kosum sinif yoluna (Etiket yok, Servis var) karsi kosuyor mu:
servis (yansima): servis calisti
```

Çıktının ilk yarısı bildirim sırasının evre dizisini etkilemediğini gösteriyor: ileri sıralı ve
geri sıralı bildirimden türeyen evre dizileri birebir aynı; `equals` karşılaştırması doğru
veriyor. Bildirilen öge sayısı **3** (bir kaynak dizini, iki bağımlılık), türetilen evre sayısı
**4** — iki sayı birbirinden bağımsız, biri öbürünü belirlemiyor. Aracın türettiği şey bildirimin
uzunluğuna değil, kendi kuralına bağlı.

İkinci yarı asıl iddiayı ölçüyor. `Program`, yalnız `Etiket`in bulunduğu bir derleme sınıf yoluna
karşı derleniyor ve derleme başarılı — derleyici `Etiket`i tip denetiminde kullandı ama sınıf
dosyasına hiçbir iz bırakmadı; sabit havuzunda `Etiket` adı hiç geçmiyor; bu satır sınıf dosyası
okunarak doğrulanıyor, tahmin edilmiyor. Sonra aynı derlenmiş `Program`, tamamen farklı bir koşum
sınıf yoluna karşı çalıştırılıyor: bu yolda `Etiket` hiç yok, ama `Servis` var. Program hatasız
koşuyor ve `Servis`i yansımayla buluyor. Derleyicinin gördüğü ad kümesi (`Etiket`, `Program`) ile
çalışma zamanının bulduğu ad kümesi (`Servis`, `Program`) örtüşmüyor — aynı tek bildirimden, aynı
tek derlenmiş sınıftan, iki ayrı sınıf yolu çıktı.

Bu iki sonuç aynı satırda birlikte okunmalı. Sabit havuzu denetimi olumsuz bir kanıt
veriyor: `Etiket` orada değil, dolayısıyla koşum sınıf yolunun onu içermesi gerekmiyor —
bu, tahmine değil doğrudan sınıf dosyasının kendisine dayanıyor. Koşum denemesi ise olumlu
bir kanıt veriyor: `Servis` gerçekten koşum sınıf yolunda bulunuyor ve gerçekten
çağrılıyor. İki kanıt birlikte, derleme sınıf yolu ile koşum sınıf yolunun aynı bildirimden
çıkan iki ayrı küme olduğunu, birinin öbürünün alt kümesi ya da üst kümesi olmak zorunda
olmadığını gösteriyor.

#### Sınırlayıcı Ölçüm: Sıra Bir Kural, Bir Tercih Değil

Yukarıdaki ölçümün ilk satırı bir sınır koyuyor: evre sırasının bildirim sırasından
bağımsız çıkması bir gözlem değil, aracın modelinin bir sonucu. `Evreler.turet` bildirimin
`bagimliliklar` listesine hiç bakmıyor; yalnız bildirimin var olduğunu biliyor. Bu yüzden
aynı bağımlılık kümesi yazılış sırası tersine çevrilerek verildiğinde bile evre dizisi
değişmiyor — sıra bir tercih olsaydı, iki bildirimden iki ayrı dizi çıkardı.

Bu ayrım önemli, çünkü bildirim ile evre dizisi arasındaki ilişki bir eşleme değil, bir
kural uygulamasıdır. Bildirimdeki bir satırı silmek ya da eklemek türetilen evrelerin
**içeriğini** değiştirmiyor, yalnız o evrelerin **içindeki** derleme görevlerinin kapsamını
değiştiriyor — örneğin derleme sınıf yolunu kurma evresi artık bir bağımlılık daha
içeriyor. Evre sırasının kendisi sabit kalıyor.

Aynı kural bildirimin uzunluğundan da bağımsız. Ölçümdeki bildirim iki bağımlılık
taşıyordu; bildirim beş, on ya da hiç bağımlılık taşımasaydı da `Evreler.turet` yine dört
evre dönerdi, çünkü yöntemin gövdesi bağımlılık listesini hiç okumuyor. Bu, aracın bir
**yorumlayıcı** değil bir **kural uygulayıcı** olduğunu gösteriyor: girdi ne kadar
değişirse değişsin, evre kümesi aynı kalıyor, değişen yalnız her evrenin içinde koşan
derleme görevinin ne kadar veriyle çalıştığı. Sonraki derste ölçülecek altı tip de bu
sabit evre kümesinin **içinden** geçecek — "derleme sınıf yolunu kur" ve "koşum sınıf
yolunu kur" evrelerinin her ikisi de aynı altı bağımlılığı görecek, ama her biri onlardan
farklı bir alt kümeyi seçecek.

Sıradaki ders bu ayrımı derinleştiriyor: bağımlılığın kapsamı yalnız o evrenin içindeki
listeyi değil, iki sınıf yolunun içeriğini de belirliyor — ve o belirleme, bu derste
görülen iki ayrı kümenin (biri derlemede, biri koşumda gerekli olan) tam ölçüsünü verecek.

#### Özet

- Bir derleme aracı, bir bildirimden sabit bir evre dizisi türetir; sıra bildirimin yazılış
  sırasından bağımsızdır ve aracın kendi kuralıdır.
- Bildirilen öge sayısı (bildirimdeki satır sayısı) ile türetilen evre sayısı ayrı
  kavramlardır; biri öbürünü belirlemez — burada 3 bildirilen ögeden 4 evre türer.
- Aynı bildirimden iki ayrı sınıf yolu çıkar: derleme sınıf yolu ve koşum sınıf yolu; bir
  tipin hangisinde gerekli olduğu, tipin kaynakta nasıl kullanıldığına bağlıdır (doğrudan
  referans mı, ada göre arama mı).
- Saklama politikası kaynak düzeyinde tutulan bir açıklama tipi (`Etiket`), derleyicinin
  gördüğü ama sınıf dosyasının hiç taşımadığı bir bağımlılık örneğidir; bu, sabit havuzu
  okunarak doğrudan doğrulanır.

#### Sonraki Adım

Bu derste bir bağımlılığın hangi sınıf yolunda gerekli olduğu iki örnekle — biri derlemede,
biri koşumda — gösterildi. Ama altı ayrı bağımlılık tipi, aralarında doğrudan bağımlılık ve
geçişli bağımlılık, tek bir arayüz ve onun iki gerçekleştirimi bir arada bulunduğunda tablo
çok daha karmaşık bir hâl alıyor. Sıradaki ders bağımlılık kapsamının kendisini ölçüyor:
altı tipin kaçı yalnız derlemede, kaçı yalnız koşumda, kaçı ikisinde birden gerekiyor — ve
iki sınıf yolu ayrıldığında ortaya çıkan dört kusurdan kaçının hiç ses çıkarmadığı.

### Bağımlılık Yönetimi

Source: https://academia.sh/tr/kurslar/java-ekosistem/bagimlilik-yonetimi

Aynı kaynak, önce bir derleme sınıf yoluna karşı derlenip sonra başka bir koşum sınıf yoluna karşı koşturulur: altı tipin 1'i yalnız derlemede, 3'ü yalnız koşumda, 2'si her ikisinde gerekli çıkar. Dört kusurun 1'i derleme hatası, 1'i koşum hatası, 2'si hiçbir şey söylemeden sessizce yanlış sonuç üretir — biri aynı arayüzün iki gerçekleştirimi arasındaki sıraya bağlı seçim, öbürü sabiti değiştirip çağıranı yeniden derlememenin bedeli.

Önceki ders bir bağımlılığın hangi sınıf yolunda gerekli olduğunu iki uç örnekle gösterdi:
biri yalnız derlemede, biri yalnız koşumda. Ama gerçek bir bildirim çoğu zaman ikisinin
ortasında duran bağımlılıklar da taşır — hem derlemede hem koşumda gerekli olan, ya da
derlemede hiç görünmeyip yalnız başka bir bağımlılığın üzerinden koşuma sızan. Bu ders altı
bağımlılık tipini bir arada koyup hepsine aynı soruyu soruyor: bu tip hangi sınıf yolunda
bulunmak zorunda? Yanıt kaynak metinden çıkarılamaz — çıkarılabilseydi bağımlılık kapsamı
diye bir kavrama hiç gerek kalmazdı.

#### Bağımlılık Kapsamı: Üç Değer, Tek Soru

Bir bağımlılığın **bağımlılık kapsamı** (dependency scope) üç değerden birini alır: yalnız
derleme sınıf yolunda gerekli, yalnız koşum sınıf yolunda gerekli, ya da her ikisinde
birden. Kapsam bir tahmin değil, bildirimde yazılan bir karardır — ama kararın doğru olup
olmadığı yalnız iki yolu gerçekten ayırarak sınanır. Aşağıdaki altı tip aynı soruya altı
farklı yanıt veriyor ve hiçbiri kaynak metnin yüzeysel okunuşundan çıkarılamıyor:

- `Sabit` — bir derleme zamanı sabiti taşıyan bir sınıf.
- `Arac` — `Sabit`i değil ama `Derin`i doğrudan çağıran bir yardımcı sınıf.
- `Derin` — yalnız `Arac`ın çağırdığı, `Kullanan`da hiç doğrudan adlandırılmayan bir sınıf.
- `Cephe` — tek yöntemli bir arayüz.
- `KayitA`, `KayitB` — `Cephe`nin iki ayrı gerçekleştirimi; kaynak metinde hiç
  adlandırılmadan bir servis bildirimiyle bulunuyorlar.

- **DR7.** Altı tipin sınıf yolu gereksinimi, aynı kaynağı önce bir derleme sınıf yoluna
  karşı derleyip sonra başka bir koşum sınıf yoluna karşı koşturarak ölçülüyor; tek yolla
  yapılan bir deneme bu ayrımı göstermez.
- **DR8.** `Sabit`'in derleme zamanı sabiti oluşu (`static final int`), Nesneye Dayalı Java
  kursunda kurulan değer gömme düzeneğiyle aynı kaynaktan geliyor; burada tekrar
  kurulmuyor, yalnız sınıf yolu sonucuna araç olarak kullanılıyor.
- **DR9.** `Cephe` bir arayüz; `KayitA` ve `KayitB` ona bir servis bildirimiyle
  (`META-INF/services` altında) bağlanıyor. `Cagiran` hiçbir gerçekleştirim adını kaynak
  metninde yazmıyor, yalnız `Cephe`yi arıyor.

```java
// SinifYolu.java — bir tipin hangi sinif yolunda gerekli oldugunu ve bir ayrimin nerede yakalandigini sayan yardimci
import java.io.*;
import java.net.*;
import java.nio.file.*;
import java.util.*;
import java.util.spi.ToolProvider;

public class SinifYolu {
    static int derleme = 0, kosum = 0, ikisi = 0;
    static int derlemeHatasi = 0, kosumHatasi = 0, sessiz = 0;

    static Path yaz(Path dizin, String ad, String kaynak) throws IOException {
        Files.createDirectories(dizin);
        Path p = dizin.resolve(ad + ".java");
        Files.writeString(p, kaynak);
        return p;
    }

    static boolean derle(Path cikti, List<Path> sinifYolu, Path kaynak) throws IOException {
        Files.createDirectories(cikti);
        List<String> arg = new ArrayList<>(List.of("-d", cikti.toString()));
        if (!sinifYolu.isEmpty()) {
            StringJoiner sj = new StringJoiner(File.pathSeparator);
            for (Path p : sinifYolu) sj.add(p.toString());
            arg.add("-classpath");
            arg.add(sj.toString());
        }
        arg.add(kaynak.toString());
        ToolProvider derleyici = ToolProvider.findFirst("javac").orElseThrow();
        StringWriter sessizCikti = new StringWriter();
        int sonuc = derleyici.run(new PrintWriter(sessizCikti), new PrintWriter(sessizCikti), arg.toArray(new String[0]));
        return sonuc == 0;
    }

    static String kos(List<Path> sinifYolu, String anaSinif) throws Exception {
        URL[] url = new URL[sinifYolu.size()];
        for (int i = 0; i < url.length; i++) url[i] = sinifYolu.get(i).toUri().toURL();
        try (URLClassLoader yukleyici = new URLClassLoader(url, SinifYolu.class.getClassLoader().getParent())) {
            Class<?> sinif = Class.forName(anaSinif, true, yukleyici);
            sinif.getMethod("main", String[].class).invoke(null, (Object) new String[0]);
            return "tamam";
        } catch (java.lang.reflect.InvocationTargetException e) {
            return e.getCause().getClass().getSimpleName();
        } catch (Throwable t) {
            return t.getClass().getSimpleName();
        }
    }

    static void bildirSaglayici(Path dizin, String saglayici) throws IOException {
        Path d = dizin.resolve("META-INF").resolve("services");
        Files.createDirectories(d);
        Files.writeString(d.resolve("Cephe"), saglayici + "\n");
    }

    static String secilenSaglayici(List<Path> sinifYolu) throws Exception {
        URL[] url = new URL[sinifYolu.size()];
        for (int i = 0; i < url.length; i++) url[i] = sinifYolu.get(i).toUri().toURL();
        try (URLClassLoader yukleyici = new URLClassLoader(url, SinifYolu.class.getClassLoader().getParent())) {
            Class<?> sinif = Class.forName("Cagiran", true, yukleyici);
            sinif.getMethod("main", String[].class).invoke(null, (Object) new String[0]);
            return (String) sinif.getField("secilen").get(null);
        }
    }

    static int okunanAlan(List<Path> sinifYolu, String anaSinif, String alan) throws Exception {
        URL[] url = new URL[sinifYolu.size()];
        for (int i = 0; i < url.length; i++) url[i] = sinifYolu.get(i).toUri().toURL();
        try (URLClassLoader yukleyici = new URLClassLoader(url, SinifYolu.class.getClassLoader().getParent())) {
            Class<?> sinif = Class.forName(anaSinif, true, yukleyici);
            sinif.getMethod("main", String[].class).invoke(null, (Object) new String[0]);
            return sinif.getField(alan).getInt(null);
        }
    }

    static void tip(String ad, boolean derlemeGerekli, boolean kosumGerekli) {
        String yer;
        if (derlemeGerekli && kosumGerekli) { ikisi++; yer = "ikisi"; }
        else if (derlemeGerekli) { derleme++; yer = "derleme"; }
        else { kosum++; yer = "kosum"; }
        System.out.printf("%-32s %s%n", ad, yer);
    }

    static void kusur(String ad, String sinif) {
        if (sinif.equals("derleme")) derlemeHatasi++;
        else if (sinif.equals("kosum")) kosumHatasi++;
        else sessiz++;
        System.out.printf("%-32s %s%n", ad, sinif);
    }

    static void ozet() {
        System.out.printf("tip   : %d yalniz derleme, %d yalniz kosum, %d ikisi%n", derleme, kosum, ikisi);
        System.out.printf("kusur : %d derleme hatasi, %d kosum hatasi, %d sessiz%n", derlemeHatasi, kosumHatasi, sessiz);
    }
}
```

`SinifYolu` üç ayrı ölçümü tek çatı altında topluyor: bir tipin hangi sınıf yolunda gerekli
olduğu (`tip`), bir kusurun hangi aşamada düştüğü (`kusur`) ve ikisinin toplamı (`ozet`).
`derle` ve `kos` yöntemleri önceki dersteki aynı işlemleri yapıyor; `bildirSaglayici` bir
servis bildirimini diske yazıyor, `secilenSaglayici` ise `Cagiran`ı koşturup hangi
gerçekleştirimin seçildiğini okuyor.

#### Altı Tip, Bir Ölçüm

- **DR10.** Geçişli bağımlılık (`Derin`), doğrudan bağımlılığın (`Arac`) derleme sınıf
  yolunda bulunmasına rağmen kendisi derleme sınıf yoluna hiç girmeden ölçülüyor;
  kaynağın derlenmesi bunun kanıtı değil.
- **DR11.** Çakışma çözümü, aynı arayüzün iki gerçekleştirmesi (`KayitA`, `KayitB`) koşum
  sınıf yoluna aynı anda konularak ve sıraları değiştirilerek ölçülüyor; hangi sırayla
  verildiği tek değişken.

```java
// BagimlilikOlcumu.java — alti tipin hangi sinif yolunda gerekli oldugunu ve dort kusurun nerede dustugunu olcer
import java.nio.file.*;
import java.util.*;

public class BagimlilikOlcumu {
    public static void main(String[] args) throws Exception {
        Path kok = Files.createTempDirectory("bagimlilik");
        Path kaynak = kok.resolve("kaynak");
        Path sabitD = kok.resolve("sabit"), aracD = kok.resolve("arac"), derinD = kok.resolve("derin");
        Path cepheD = kok.resolve("cephe"), aD = kok.resolve("kayitA"), bD = kok.resolve("kayitB");
        Path kullananD = kok.resolve("kullanan"), cagiranD = kok.resolve("cagiran");

        Path sSabit = SinifYolu.yaz(kaynak, "Sabit", "public class Sabit { public static final int DEGER = 7; }");
        Path sDerin = SinifYolu.yaz(kaynak, "Derin", "public class Derin { public static int uc() { return 3; } }");
        Path sArac = SinifYolu.yaz(kaynak, "Arac", "public class Arac { public static int iki() { return Derin.uc() - 1; } }");
        Path sKullanan = SinifYolu.yaz(kaynak, "Kullanan",
                "public class Kullanan { public static void main(String[] a) { int t = Sabit.DEGER + Arac.iki(); if (t < 0) throw new IllegalStateException(); } }");
        Path sCephe = SinifYolu.yaz(kaynak, "Cephe", "public interface Cephe { String ad(); }");
        Path sA = SinifYolu.yaz(kaynak, "KayitA", "public class KayitA implements Cephe { public String ad() { return \"A\"; } }");
        Path sB = SinifYolu.yaz(kaynak, "KayitB", "public class KayitB implements Cephe { public String ad() { return \"B\"; } }");
        Path sCagiran = SinifYolu.yaz(kaynak, "Cagiran",
                "import java.util.ServiceLoader;\n"
              + "public class Cagiran {\n"
              + "  public static String secilen = \"yok\";\n"
              + "  public static void main(String[] a) {\n"
              + "    for (Cephe c : ServiceLoader.load(Cephe.class, Cagiran.class.getClassLoader())) { secilen = c.ad(); return; }\n"
              + "  }\n"
              + "}");

        SinifYolu.derle(sabitD, List.of(), sSabit);
        SinifYolu.derle(derinD, List.of(), sDerin);
        SinifYolu.derle(aracD, List.of(derinD), sArac);
        SinifYolu.derle(cepheD, List.of(), sCephe);
        SinifYolu.derle(aD, List.of(cepheD), sA);
        SinifYolu.derle(bD, List.of(cepheD), sB);
        SinifYolu.bildirSaglayici(aD, "KayitA");
        SinifYolu.bildirSaglayici(bD, "KayitB");
        SinifYolu.derle(kullananD, List.of(sabitD, aracD), sKullanan);
        SinifYolu.derle(cagiranD, List.of(cepheD), sCagiran);

        System.out.println("-- alti tip hangi sinif yolunda gerekli --");

        boolean sabitDerleme = !SinifYolu.derle(kok.resolve("d1"), List.of(aracD), sKullanan);
        boolean sabitKosum = !SinifYolu.kos(List.of(kullananD, aracD, derinD), "Kullanan").equals("tamam");
        SinifYolu.tip("Sabit (derleme zamani sabiti)", sabitDerleme, sabitKosum);

        boolean aracDerleme = !SinifYolu.derle(kok.resolve("d2"), List.of(sabitD), sKullanan);
        boolean aracKosum = !SinifYolu.kos(List.of(kullananD, sabitD, derinD), "Kullanan").equals("tamam");
        SinifYolu.tip("Arac (dogrudan bagimlilik)", aracDerleme, aracKosum);

        boolean derinDerleme = !SinifYolu.derle(kok.resolve("d3"), List.of(sabitD, aracD), sKullanan);
        boolean derinKosum = !SinifYolu.kos(List.of(kullananD, sabitD, aracD), "Kullanan").equals("tamam");
        SinifYolu.tip("Derin (gecisli bagimlilik)", derinDerleme, derinKosum);

        boolean cepheDerleme = !SinifYolu.derle(kok.resolve("d4"), List.of(), sCagiran);
        boolean cepheKosum = !SinifYolu.kos(List.of(cagiranD, aD), "Cagiran").equals("tamam");
        SinifYolu.tip("Cephe (arayuz)", cepheDerleme, cepheKosum);

        boolean aDerleme = !SinifYolu.derle(kok.resolve("d5"), List.of(cepheD), sCagiran);
        SinifYolu.tip("KayitA (gerceklestirim)", aDerleme, true);
        SinifYolu.tip("KayitB (ikinci gerceklestirim)", aDerleme, true);

        System.out.println();
        System.out.println("-- gecisli bagimlilik: Derin, Arac uzerinden derleme sinif yoluna hic girmiyor --");
        System.out.println("Kullanan, [Sabit, Arac] derleme sinif yoluna karsi derlendi mi: " + SinifYolu.derle(kok.resolve("gecis"), List.of(sabitD, aracD), sKullanan));
        System.out.println("bu derleme sinif yolunda Derin hic yok, ama Kullanan derleniyor");

        System.out.println();
        System.out.println("-- cakisma cozumu: kosum yolunda iki gerceklestirim, secimi sira belirliyor --");
        String ilkAB = SinifYolu.secilenSaglayici(List.of(cagiranD, cepheD, aD, bD));
        String ilkBA = SinifYolu.secilenSaglayici(List.of(cagiranD, cepheD, bD, aD));
        System.out.println("A once verildiginde secilen: " + ilkAB);
        System.out.println("B once verildiginde secilen: " + ilkBA);

        System.out.println();
        System.out.println("-- yollar ayrildiginda kusur nerede goruluyor --");
        SinifYolu.kusur("derleme yolunda tip yok", SinifYolu.derle(kok.resolve("d6"), List.of(sabitD), sKullanan) ? "sessiz" : "derleme");
        SinifYolu.kusur("kosum yolunda tip yok", SinifYolu.kos(List.of(kullananD, sabitD), "Kullanan").equals("tamam") ? "sessiz" : "kosum");
        SinifYolu.kusur("kosum yolunda iki gerceklestirim", ilkAB.equals(ilkBA) ? "kosum" : "sessiz");

        Path sabitYeni = kok.resolve("sabitYeni");
        SinifYolu.derle(sabitYeni, List.of(), SinifYolu.yaz(kok.resolve("kaynak2"), "Sabit",
                "public class Sabit { public static final int DEGER = 8; }"));
        Path olcen = kok.resolve("olcen");
        Path sOlcen = SinifYolu.yaz(kaynak, "Olcen",
                "public class Olcen { public static int gorulen = -1;\n"
              + "  public static void main(String[] a) { gorulen = Sabit.DEGER; } }");
        SinifYolu.derle(olcen, List.of(sabitD), sOlcen);
        int eskiDeger = SinifYolu.okunanAlan(List.of(olcen, sabitYeni), "Olcen", "gorulen");
        SinifYolu.kusur("sabit degisti, cagiran derlenmedi", eskiDeger == 8 ? "kosum" : "sessiz");
        System.out.println("yeni sinif yolundaki sabit : 8");
        System.out.println("cagiranin gordugu deger    : " + eskiDeger);

        System.out.println();
        SinifYolu.ozet();
    }
}
```

```
-- alti tip hangi sinif yolunda gerekli --
Sabit (derleme zamani sabiti)    derleme
Arac (dogrudan bagimlilik)       ikisi
Derin (gecisli bagimlilik)       kosum
Cephe (arayuz)                   ikisi
KayitA (gerceklestirim)          kosum
KayitB (ikinci gerceklestirim)   kosum

-- gecisli bagimlilik: Derin, Arac uzerinden derleme sinif yoluna hic girmiyor --
Kullanan, [Sabit, Arac] derleme sinif yoluna karsi derlendi mi: true
bu derleme sinif yolunda Derin hic yok, ama Kullanan derleniyor

-- cakisma cozumu: kosum yolunda iki gerceklestirim, secimi sira belirliyor --
A once verildiginde secilen: A
B once verildiginde secilen: B

-- yollar ayrildiginda kusur nerede goruluyor --
derleme yolunda tip yok          derleme
kosum yolunda tip yok            kosum
kosum yolunda iki gerceklestirim sessiz
sabit degisti, cagiran derlenmedi sessiz
yeni sinif yolundaki sabit : 8
cagiranin gordugu deger    : 7

tip   : 1 yalniz derleme, 3 yalniz kosum, 2 ikisi
kusur : 1 derleme hatasi, 1 kosum hatasi, 2 sessiz
```

Altı satırın ilk dördü tek tek derlenip koşturularak elde edildi; son iki satırın koşum sütunu
ölçülmeden veriliyor. İlk üç satır `Kullanan`, sonraki üçü `Cagiran` kaynağını, o tipin
bulunmadığı bir sınıf yoluna karşı önce derlemeye, sonra koşturmaya çalışıyor. Derleme başarısızsa
o tip derlemede gerekli; koşum başarısızsa koşumda gerekli. `Sabit` yalnız derlemede gerekli çıktı
— değeri çağrı yerine gömüldüğü için koşum sınıf yolunda hiç aranmıyor. `Arac` ve `Cephe` her iki
yolda da gerekli: ikisi de kaynakta doğrudan adlandırılıyor, ikisinin de sınıf dosyası hem derleme
hem koşum aşamasında aranıyor. `Derin`, `KayitA` ve `KayitB` yalnız koşumda gerekli — üçü de
kaynak metinde hiçbir yerde doğrudan yazılmıyor.

Sonuç: **altı tipin biri yalnız derleme sınıf yolunda, üçü yalnız koşum sınıf yolunda,
ikisi her iki yolda birden gerekli.** Bu sayı bu konunun ortak ölçümüdür ve konu boyunca
değişmeden kalıyor.

#### Geçişli Bağımlılık ve Çakışma Çözümü

`Derin`'in durumu en çok yanıltan olanı. `Arac`, `Derin`i doğrudan çağırıyor — ama
`Kullanan`, `Arac`ı çağırırken `Derin`i hiç adlandırmıyor. Derleme sınıf yolu yalnız
`Sabit` ve `Arac`ı içerdiğinde `Kullanan` sorunsuz derleniyor; `Derin` bu yolda hiç yok.
Bu, **geçişli bağımlılığın** (transitive dependency) derleme sınıf yolunda görünmeden
koşumda gerekebileceğinin doğrudan kanıtı — derlemenin geçmesi, koşum sınıf yolunun
eksiksiz olduğunu göstermiyor.

Aynı kaynak metin farklı bir soruyu da açığa çıkarıyor: koşum sınıf yolunda `Cephe`'nin
iki gerçekleştirimi (`KayitA`, `KayitB`) aynı anda bulunduğunda hangisi seçiliyor?
`Cagiran` servis bildirimini tarıyor ve ilk bulduğunu alıyor; ilk bulunan, sınıf yoluna
verilme **sırasına** bağlı. `A` önce verildiğinde seçilen `A`, `B` önce verildiğinde
seçilen `B` — **çakışma çözümü** (conflict resolution) burada bir kural değil, bir
sıralama sonucu. Kod hiçbir yerde hata vermiyor; iki farklı kurulum aynı programdan iki
farklı yanıt üretiyor.

#### Sınırlayıcı Ölçüm: Sessiz Kusurun İki Yüzü

- **DR12.** "Sessiz" kusur sınıfı, ne derleyicinin ne çalışma zamanının hiçbir şey
  söylemediği ama sonucun beklenenden farklı çıktığı durumları topluyor; her sessiz
  satırda beklenen değer de yazılıyor.
- **DR13.** `Sabit` değiştirilip `Kullanan` yeniden derlenmeden koşum sınıf yolundaki
  sürüm değiştirildiğinde, iki taraf arasındaki fark yalnız okunan alanın değeriyle
  ölçülüyor; hata fırlatılmıyor.

Aynı ölçüm dört ayrı kusuru üç sınıfa ayırıyor: derleme sınıf yolunda tip yokluğu
derleyici tarafından reddediliyor (`derleme`), koşum sınıf yolunda tip yokluğu çalışma
zamanında bir hata
fırlatıyor (`kosum`), geri kalan ikisi hiçbir şey söylemiyor (`sessiz`). Koşum yolunda iki
gerçekleştirim bulunması bunlardan biri — program hiçbir hata vermeden iki farklı yanıt
üretiyor ve hangi yanıtın "doğru" olduğu kodun kendisinden anlaşılmıyor.

İkinci sessiz kusur daha ince: `Sabit`'in değeri **8**'e çıkarılıyor, ama `Kullanan` bu yeni
sürüme karşı yeniden derlenmiyor. Koşum sınıf yolundaki `Sabit` **8** taşısa da, `Kullanan`'ın
sınıf dosyasına önceden gömülen değer **7** olarak kalıyor — çünkü gömme derleme anında oldu,
koşum anında değil. Sınıf yolu güncellenmiş görünüyor, ama derlenmiş sınıf hâlâ eski değeri
taşıyor; bu satır sessiz sınıfına giriyor çünkü ne derleme ne koşum bir şikâyet üretiyor; sonuç
yalnız yanlış.

Toplamda: **dört kusurun biri derleme hatası, biri koşum hatası, ikisi sessiz.**
"Derlendi" sonucu bu yüzden bir kanıt değil — geçişli bağımlılık derleme sınıf yolunda hiç
bulunmadan kaynak sorunsuz derleniyor, ama koşumda gerekiyor; derlemenin geçmesi koşum
sınıf yolunun eksiksiz olduğunu göstermiyor. Bir tipin koşum sınıf yolunda gerekmemesi de
ona bağlı olunmadığı anlamına gelmiyor — `Sabit`'in yokluğu onun **kopyalanmış** olduğu
anlamına geliyor ve o kopya iki taraf ayrıştığında sessizce yanlış sonuç üretiyor.

#### Özet

- Bağımlılık kapsamı üç değer alır (derleme, koşum, ikisi); altı tip üzerinde ölçülünce
  dağılım **1 yalnız derleme, 3 yalnız koşum, 2 ikisi** çıkar ve kaynak metinden
  çıkarılamaz.
- Geçişli bağımlılık (`Derin`) derleme sınıf yolunda hiç bulunmadan kaynak sorunsuz
  derlenir, ama koşumda gerekir; "derlendi" bir kanıt değildir.
- Çakışma çözümü, aynı arayüzün iki gerçekleştirimi koşum sınıf yolunda birlikte
  bulunduğunda sıraya bağlı hâle gelir; hata yoktur, sonuç kurulumdan kuruluma değişir.
- Dört kusurdan biri derleme hatası, biri koşum hatası, ikisi sessizdir; sessiz
  kusurlarda derleyici de çalışma zamanı da hiçbir şey söylemez; sonuç yalnız yanlış çıkar.
- Bir tipin koşum sınıf yolunda gerekmemesi ona bağlı olunmadığı anlamına gelmez: değeri
  gömülmüş olabilir ve o gömülü değer kaynak değiştiğinde sessizce eskir.

#### Sonraki Adım

Bu derste altı tekil tip ölçüldü — her biri tek bir sınıfın ya da arayüzün sınırları
içinde kaldı. Ama gerçek bir bildirim tek dosyalık sınıflardan değil, birden çok kaynak
dizininden kurulu modüllerden oluşur ve bir modülün sınırı derlemede nasıl bir karşılığa
denk düşüyor sorusu henüz sorulmadı. Sıradaki ders bunu ölçüyor: bir modülün "iç" saydığı
bir tip, sınır aşıldığında derlemede mi yakalanıyor, koşumda mı, yoksa hiç yakalanmıyor mu
— ve bu yakalamanın kaynağı gerçekten bir dizin ayrımı mı.

### Çoklu Modül Projeler

Source: https://academia.sh/tr/kurslar/java-ekosistem/coklu-modul-projeler

Üç sınır kurulumundan biri derlemede, biri koşumda yakalanır, biri hiç yakalanmadan sınırı sessizce aşar. Sınırlayıcı ölçüm bunun neden böyle olduğunu gösterir: Genel ve Dahili aynı dizine derlenir ve sınırı gerçekten kuran şey dizin değil bir yükleyicinin izin listesidir — aynı dizindeki iki sınıf dosyasından biri yüklenirken öbürü ClassNotFoundException ile reddedilir.

Önceki ders altı tekil tipin her birinin hangi sınıf yolunda gerekli olduğunu ölçtü — ama
her tip kendi başınaydı, tek bir sınıfın ya da arayüzün sınırları içinde kaldı. Gerçek bir
proje tek dosyalık sınıflardan değil, birbirine bağımlı **modüllerden** kurulur ve bir
modülün "başkasına açık" saydığı yüzey ile "kendi içinde" tuttuğu yüzey arasındaki çizgi bir
dil kuralı değil, bir derleme kararıdır. Bu ders o çizginin derlemedeki karşılığını ölçüyor:
modül sınırı aşıldığında ne oluyor ve bu yakalama nereden geliyor?

**Modül sınırı** burada bir çoklu modül projenin en küçük hâliyle kuruluyor: iki taraf,
biri "sağlayan" biri "tüketen". Sağlayan tarafın kendi içinde bir genel yüzeyi (`Genel`)
ve bir iç uygulaması (`Dahili`) var; tüketen taraf yalnız genel yüzeyi kullanmalı,
iç uygulamaya hiç dokunmamalı. Gerçek bir çoklu modül projede bu ayrım genellikle ayrı
dizinlere, ayrı derleme çıktılarına yansır — ama derlemenin kendisi bu ayrımı **neden**
uyguladığını hiç söylemez. Bu ders o nedeni ölçerek çıkarıyor.

#### Modül Sınırının Derlemedeki Karşılığı

Bir modülün genel yüzeyi ile iç uygulaması aynı kaynak dizininden, aynı derleme çağrısıyla,
aynı çıktı dizinine derlenebilir — Java'nın kendisi bunları ayırmaz. Aşağıdaki `Genel` ve
`Dahili` sınıfları tam olarak böyle: ikisi de tek bir derleme çağrısında, tek bir çıktı
dizinine (`aTumOut`) yazılıyor. `Genel` dışarıya açık sayılan tarafı, `Dahili` yalnız
modülün kendi içinde kalması gereken tarafı temsil ediyor — ama bu ayrım şu ana kadar
yalnız bir isimlendirme kararı, derlemenin kendisi henüz hiçbir şey uygulamıyor.

- **DR14.** Üç sınır kurulumu da aynı iç tip (`Dahili.gizli`) üzerinden ölçülüyor; farklı
  olan yalnız erişim biçimi (doğrudan referans / yansıma) ve koşum sınıf yoluna hangi
  dizinin konduğu.
- **DR15.** `Genel` ve `Dahili` aynı kaynak dizininden aynı derleme çağrısıyla, aynı çıktı
  dizinine derleniyor; ikisi arasında fiziksel hiçbir ayrım yok.

Sınırı "gerçek" kılan adım, çıktının bir alt kümesini ayrı bir dizine kopyalamak: yalnız
`Genel.class`, `aGenelSadece` adlı seçilmiş bir dizine kopyalanıyor. Bir modülün
tüketicisine verilen sınıf yolu bu seçilmiş dizini gösteriyor, `Dahili`'nin de bulunduğu
tam çıktıyı değil.

```java
// ModulYardimci.java — bir kaynagi derleyip sinif yolu listesine karsi kosturmak icin ortak islemler
import java.io.*;
import java.net.*;
import java.nio.file.*;
import java.util.*;
import java.util.spi.ToolProvider;

public class ModulYardimci {
    static Path yaz(Path dizin, String ad, String kaynak) throws IOException {
        Files.createDirectories(dizin);
        Path p = dizin.resolve(ad + ".java");
        Files.writeString(p, kaynak);
        return p;
    }

    static boolean derle(Path cikti, List<Path> sinifYolu, Path... kaynaklar) throws IOException {
        Files.createDirectories(cikti);
        List<String> arg = new ArrayList<>(List.of("-d", cikti.toString()));
        if (!sinifYolu.isEmpty()) {
            StringJoiner sj = new StringJoiner(File.pathSeparator);
            for (Path p : sinifYolu) sj.add(p.toString());
            arg.add("-classpath");
            arg.add(sj.toString());
        }
        for (Path k : kaynaklar) arg.add(k.toString());
        ToolProvider derleyici = ToolProvider.findFirst("javac").orElseThrow();
        StringWriter sessiz = new StringWriter();
        int sonuc = derleyici.run(new PrintWriter(sessiz), new PrintWriter(sessiz), arg.toArray(new String[0]));
        return sonuc == 0;
    }

    static String kos(List<Path> sinifYolu, String anaSinif) throws Exception {
        URL[] url = new URL[sinifYolu.size()];
        for (int i = 0; i < url.length; i++) url[i] = sinifYolu.get(i).toUri().toURL();
        try (URLClassLoader yukleyici = new URLClassLoader(url, ModulYardimci.class.getClassLoader().getParent())) {
            Class<?> sinif = Class.forName(anaSinif, true, yukleyici);
            sinif.getMethod("main", String[].class).invoke(null, (Object) new String[0]);
            return "tamam";
        } catch (java.lang.reflect.InvocationTargetException e) {
            return e.getCause().getClass().getSimpleName();
        } catch (Throwable t) {
            return t.getClass().getSimpleName();
        }
    }
}
```

`derle`, önceki derslerdeki aynı işlemi yapıyor: kaynakları verilen bir sınıf yoluna karşı
derliyor. `kos` derlenmiş sınıfı ayrı bir sınıf yoluna karşı çalıştırıyor ve fırlayan
istisnanın sınıf adını döndürüyor — "tamam" değilse, sınırın çalışma zamanında yakalandığı
anlamına geliyor.

Bu iki yöntemin dışında `ModulYardimci` hiçbir şey bilmiyor — ne "genel" ne "iç" kavramını
tanıyor, ne bir modülün ne olduğunu. Sınırın kendisi bu sınıfın dışında, çağıran kodun
hangi dizinleri hangi sınıf yoluna kattığı kararında kuruluyor. Bu, bu dersin temel
gözlemini önceden özetliyor: modül sınırı bir dil özelliği değil, bir kullanım kararı.

#### Üç Sınır Kurulumu

`Dahili.gizli` bir modülün iç uygulamasını temsil ediyor: dışarıdan çağrılması "sınır aşımı".
Aşağıdaki üç kurulum bu aşımı üç farklı biçimde deniyor; hepsi aynı `Dahili` kaynağını kullanıyor;
ilk ikisi `aGenelSadece` sınıf yolu tuzağını, üçüncüsü onun yerine `aTumOut`u kullanıyor, ama biri
kaynak metninde bir tip adı yazarak, ikisi bir dizgi vererek erişiyor.

- **DR16.** "Yakalanmadı" sonucu yalnız kurulum 1'in derleme başarısızlığıyla
  karşılaştırılan bir referans değil; kurulum 3'te program hatasız çalışıyor ve iç tipin
  değeri okunuyor.
- **DR17.** Sınırlı yükleyici (`SinirliYukleyici`), JVM, Eşzamanlılık ve Başarım kursunun
  Sınıf Yükleme dersinde kurulan özel yükleyici düzeneğiyle aynı ilkeyi kullanıyor; yalnız
  bir izin listesini kendi bildirimine ekliyor.
- **DR18.** İzin listesi sınıf adına bakıyor, dizine bakmıyor; aynı dizindeki iki sınıf
  dosyasından biri yüklenebiliyor, öbürü `ClassNotFoundException` ile reddediliyor.
- **DR19.** `hesapla()` yöntemi, sınırlı yükleyicinin ölçümünde `Dahili` tipini hiç
  anmıyor; bu yüzden `Genel`'in yüklenip çalışması, `Dahili`'nin erişilebilir olup
  olmamasından bağımsız ölçülüyor.

```java
// SinirliYukleyici.java — ayni dizindeki sinif dosyalarindan yalniz izin verilen adlari tanimlayan yukleyici
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Set;

class SinirliYukleyici extends ClassLoader {
    private final Path dizin;
    private final Set<String> izinli;

    SinirliYukleyici(Path dizin, Set<String> izinli) {
        super(SinirliYukleyici.class.getClassLoader().getParent());
        this.dizin = dizin;
        this.izinli = izinli;
    }

    protected Class<?> findClass(String ad) throws ClassNotFoundException {
        if (!izinli.contains(ad)) throw new ClassNotFoundException(ad);
        try {
            byte[] bayt = Files.readAllBytes(dizin.resolve(ad + ".class"));
            return defineClass(ad, bayt, 0, bayt.length);
        } catch (IOException e) {
            throw new ClassNotFoundException(ad);
        }
    }
}
```

```java
// ModulSiniri.java — modul sinirinin derlemedeki karsiligi: sinir bir sinif yolu ve yukleyici karari
import java.io.*;
import java.nio.file.*;
import java.util.*;

public class ModulSiniri {
    public static void main(String[] args) throws Exception {
        Path kok = Files.createTempDirectory("modul");
        Path kaynak = kok.resolve("kaynak");

        Path sDahili = ModulYardimci.yaz(kaynak, "Dahili",
                "public class Dahili { public static int gizli() { return 99; } }");
        Path sGenel = ModulYardimci.yaz(kaynak, "Genel",
                "public class Genel { public static int hesapla() { return 41; } }");

        Path aTumOut = kok.resolve("aTumOut");
        ModulYardimci.derle(aTumOut, List.of(), sDahili, sGenel);

        Path aGenelSadece = kok.resolve("aGenelSadece");
        Files.createDirectories(aGenelSadece);
        Files.copy(aTumOut.resolve("Genel.class"), aGenelSadece.resolve("Genel.class"));

        System.out.println("-- uc sinir kurulumu, kaci derlemede kaci kosumda yakalaniyor --");

        Path sB1 = ModulYardimci.yaz(kok.resolve("b1"), "B1",
                "public class B1 { public static void main(String[] a) { System.out.println(Dahili.gizli()); } }");
        boolean b1Derlendi = ModulYardimci.derle(kok.resolve("b1Cikti"), List.of(aGenelSadece), sB1);
        System.out.println("kurulum 1 - dogrudan referans, ic dizin sinif yolunda yok: derlendi mi = " + b1Derlendi
                + " -> " + (b1Derlendi ? "yakalanmadi" : "derlemede yakalandi"));

        Path sB2 = ModulYardimci.yaz(kok.resolve("b2"), "B2",
                "public class B2 { public static void main(String[] a) throws Exception {\n"
              + "  Class<?> c = Class.forName(\"Dahili\");\n"
              + "  c.getMethod(\"gizli\").invoke(null); } }");
        Path b2Cikti = kok.resolve("b2Cikti");
        boolean b2Derlendi = ModulYardimci.derle(b2Cikti, List.of(aGenelSadece), sB2);
        System.out.println("kurulum 2 - yansimayla erisim, ic dizin sinif yolunda yok: derlendi mi = " + b2Derlendi);
        String kurulum2Kosum = ModulYardimci.kos(List.of(b2Cikti, aGenelSadece), "B2");
        System.out.println("kurulum 2 kosum sonucu: " + kurulum2Kosum
                + " -> " + (kurulum2Kosum.equals("tamam") ? "yakalanmadi" : "kosumda yakalandi"));

        String kurulum3Kosum = ModulYardimci.kos(List.of(b2Cikti, aTumOut), "B2");
        System.out.println("kurulum 3 - ayni yansimali sinif, kosum sinif yoluna ic dizinin tamami eklendi: sonuc = "
                + kurulum3Kosum + " -> " + (kurulum3Kosum.equals("tamam") ? "hic yakalanmadi, sinir sessizce asildi" : "yakalandi"));

        System.out.println();
        System.out.println("-- sinirlayici olcum: sinir bir dizin ayrimi degil --");
        System.out.println("Genel ve Dahili ayni dizinde (aTumOut): " + Files.exists(aTumOut.resolve("Genel.class"))
                + ", " + Files.exists(aTumOut.resolve("Dahili.class")));
        SinirliYukleyici sinirli = new SinirliYukleyici(aTumOut, Set.of("Genel"));
        Class<?> genelSinif = Class.forName("Genel", true, sinirli);
        System.out.println("sinirli yukleyici Genel'i yukledi mi: true, hesapla() = "
                + genelSinif.getMethod("hesapla").invoke(null));
        String dahiliSonuc;
        try {
            Class.forName("Dahili", true, sinirli);
            dahiliSonuc = "yuklendi";
        } catch (ClassNotFoundException e) {
            dahiliSonuc = "ClassNotFoundException";
        }
        System.out.println("ayni sinirli yukleyici Dahili'yi yukleyebildi mi (ayni dizinde olmasina ragmen): " + dahiliSonuc);
    }
}
```

```
-- uc sinir kurulumu, kaci derlemede kaci kosumda yakalaniyor --
kurulum 1 - dogrudan referans, ic dizin sinif yolunda yok: derlendi mi = false -> derlemede yakalandi
kurulum 2 - yansimayla erisim, ic dizin sinif yolunda yok: derlendi mi = true
kurulum 2 kosum sonucu: ClassNotFoundException -> kosumda yakalandi
kurulum 3 - ayni yansimali sinif, kosum sinif yoluna ic dizinin tamami eklendi: sonuc = tamam -> hic yakalanmadi, sinir sessizce asildi

-- sinirlayici olcum: sinir bir dizin ayrimi degil --
Genel ve Dahili ayni dizinde (aTumOut): true, true
sinirli yukleyici Genel'i yukledi mi: true, hesapla() = 41
ayni sinirli yukleyici Dahili'yi yukleyebildi mi (ayni dizinde olmasina ragmen): ClassNotFoundException
```

İlk kurulum sınırı en katı biçimde ölçüyor: `B1`, `Dahili.gizli`'yi doğrudan tip referansıyla
çağırıyor ve sınıf yolunda `Dahili` hiç yok. Derleyici sembolü çözemiyor; derleme çöküyor — sınır
**derlemede** yakalanıyor. İkinci kurulum aynı çağrıyı yansımayla yapıyor: `B2` kaynağında
`Dahili` adı hiçbir yerde doğrudan geçmiyor, bu yüzden derleme sorunsuz tamamlanıyor. Ama koşum
sınıf yolu da `Dahili`'yi içermiyor ve `Class.forName` çalışma zamanında `ClassNotFoundException`
fırlatıyor — sınır bu kez **koşumda** yakalanıyor.

Üçüncü kurulum aynı derlenmiş `B2` sınıfını değiştirmeden, yalnız koşum sınıf yoluna farklı
bir dizin ekleyerek çalıştırıyor: `aGenelSadece` yerine `aTumOut` — modülün tüm iç çıktısı.
Bu, gerçek bir projede sık rastlanan bir hata: birileri "yalnız genel arayüzü" değil, tüm
derleme çıktısını koşum sınıf yoluna koyuyor. Sonuç: program hatasız çalışıyor, `Dahili`
bulunuyor ve çağrılıyor — sınır **hiç yakalanmıyor**, sessizce aşılıyor.

**Üç sınır kurulumundan biri derlemede, biri koşumda yakalanıyor, biri hiç yakalanmıyor.**
Yakalamanın hangisi olduğunu belirleyen şey `B1`/`B2` kaynağının yazılışı değil, koşum
sınıf yoluna hangi dizinin konduğu.

Bu üçlünün sırası da anlamlı. En sıkı yakalama (derleme) en erken düşen yakalama — derleyici
sembolü çözemediği an duruyor; ekstra bir adım gerekmiyor. Orta sıkılıktaki yakalama (koşum) bir
adım daha esnek: kaynak derleniyor, program başlıyor, hata ancak `Dahili`'ye gerçekten erişilmeye
çalışıldığı satırda düşüyor — program o satıra hiç uğramasaydı hata da hiç görünmezdi. En gevşek
durum ise hiç yakalama değil: üçüncü kurulumda kod hem derleniyor hem çalışıyor ve `Dahili`'nin
çağrılması hiçbir uyarı üretmiyor. Modül sınırının ne kadar sıkı olduğu, bu üç kurulumun hangisine
benzediğine bağlı — ve bu bir dil garantisi değil, sınıf yoluna ne konduğuyla ilgili bir işletim
kararı.

#### Sınırlayıcı Ölçüm: Sınır Bir Dizin Ayrımı Değildir

Yukarıdaki üç kurulum, sınırı ayrı dizinlere kopyalayarak kurdu — bu, gerçek bir yapı
sürecinin de yaptığı şey: yalnız genel yüzeyi ayrı bir çıktıya taşımak. Ama bu yönteme
güvenmek, sınırın **neden** işlediğini yanlış anlatır: sanki sınır fiziksel bir dizin
farkıymış gibi görünür. Son ölçüm bunun yanlış olduğunu gösteriyor.

`Genel` ve `Dahili` bu ölçümde hâlâ **aynı** dizinde duruyor — `aTumOut`, ikisinin de
sınıf dosyasını taşıyor, kopyalama yok. Sınırı burada kuran şey `SinirliYukleyici`'nin
kendi izin listesi: `Set.of("Genel")`. Bu yükleyici `Genel`i istendiğinde diskten okuyup
tanımlıyor — `hesapla()` çağrılabiliyor, `41` dönüyor. Ama aynı yükleyiciden `Dahili`
istendiğinde, `findClass` izin listesinde olmadığını görüp `ClassNotFoundException`
fırlatıyor; `Dahili.class` dosyası tam olarak aynı dizinde, birkaç bayt ötede duruyor,
ama yükleyici ona hiç bakmıyor bile.

Bu, önceki üç kurulumun ölçtüğü şeyi tersinden doğruluyor: modül sınırı bir **dizin
ayrımı** değil, bir **sınıf yolu ve yükleyici kararıdır**. Aynı dizinden derlenen iki tip
farklı sınıf yollarına (ya da farklı izin listelerine) konularak ayrı erişim düzeylerine
sahip olabiliyor ve bu ayrım o zaman da işliyor — hiçbir fiziksel dizin ayrımı olmadan.
Kurulum 3'ün gösterdiği kaçış da bunun aynadaki yansıması: dizin ayrımı yeterli bir
güvence değil, çünkü sınırı taşıyan gerçek mekanizma her zaman hangi sınıf yolunun (ya
da hangi yükleyicinin) hangi adı görmesine izin verildiğidir.

Bu iki bulgu birlikte okunduğunda bir çoklu modül projenin sınırının nereye
yerleştirildiği ortaya çıkıyor: sınır ne kaynak dosyasının hangi klasörde durduğuna, ne
de sınıfın hangi paket adını taşıdığına bağlı. Sınır, bir sağlayan modülün derleme
çıktısından tüketen modüle hangi alt kümenin — hangi dizinin, hangi arşivin, hangi izin
listesinin — verildiğine bağlı. İki modül aynı depoda, aynı derleme çağrısıyla, hatta
aynı çıktı dizininde üretilebilir; sınırın gerçekten işlemesi için gereken tek şey,
tüketen tarafa verilen sınıf yolunun neyi içerip neyi içermediğidir.

#### Özet

- Modül sınırının derlemedeki karşılığı bir sınıf yolu kararıdır: aynı kaynak dizininden
  derlenen "genel" ve "iç" tipler, tüketiciye farklı sınıf yolları verilerek ayrılır.
- Üç sınır kurulumundan biri derlemede yakalanır (doğrudan referans, tip sınıf yolunda
  yok), biri koşumda yakalanır (yansıma, tip koşum sınıf yolunda yok), biri hiç
  yakalanmaz (yansıma, ama koşum sınıf yoluna iç çıktının tamamı yanlışlıkla eklenmiş).
- Bir sınıf yükleyicinin izin listesi sınıf adına bakar, dizine bakmaz: aynı dizindeki iki
  sınıf dosyasından biri yüklenip çalıştırılabilirken öbürü `ClassNotFoundException` ile
  reddedilebilir.
- Sınırlayıcı ölçüm: `Genel` ve `Dahili` fiziksel olarak aynı dizinde durmaya devam eder;
  sınırı kuran şey dizin değil, yükleyicinin kendi kararıdır.

#### Sonraki Adım

Bu derste bir modülün sınırı, koşum sınıf yoluna hangi dizinin ya da hangi yükleyicinin
konduğu üzerinden ölçüldü — sınırı biz kurduk, kararı biz verdik. Ama bu bildirim henüz
tek bir çalışan programa dönüşmedi; koşum sınıf yolu şu ana kadar hep dağınık dizinlerin
bir listesiydi. Sıradaki ders bu listeyi taşınabilir tek bir ürüne çeviriyor: derlenmiş
sınıflar bir arşive konuyor, giriş noktası kaynakta değil arşivin kendi bildiriminde
duruyor ve arşivin bağımlılıklarını içerip içermediği ayrı bir soru olarak kalıyor.

### Paketleme ve Çalıştırılabilir Çıktı

Source: https://academia.sh/tr/kurslar/java-ekosistem/paketleme-ve-calistirilabilir-cikti

Aynı arşivleme çağrısı hem kendine yeten bir arşiv hem de dışarıdan bir sınıfa muhtaç bir arşiv üretir: birincisi koşum sınıf yolunda tek başına yeterlidir, ikincisi tek başına NoClassDefFoundError ile düşer ve yalnız eksik dizin eklendiğinde çalışır. Giriş noktası da kaynakta değil arşivin kendi bildiriminde durur: aynı sınıflardan üretilen bildirimsiz bir arşivde bu bilgi hiç yoktur.

Önceki ders bir modül sınırının derlemedeki karşılığının bir sınıf yolu ve yükleyici
kararı olduğunu ölçtü: sınır ne kaynağın dizin yapısına ne paket adına bağlıydı, yalnız
tüketen tarafa hangi sınıf dosyalarının verildiğine bağlıydı. Ama bu ölçümün tamamı hâlâ
dağınık dizinler üzerinden yürüdü — koşum sınıf yolu, birbirinden ayrı, birer birer
oluşturulan dizinlerin bir listesiydi. Bu ders o listeyi taşınabilir tek bir ürüne
çeviriyor: derlenmiş sınıflar bir **arşive** konuyor ve arşivin kendisi artık koşum sınıf
yolunun tek bir öğesi olarak taşınabiliyor.

İlk derste bir bildirimden türeyen evre dizisi dört evreyle sınırlıydı: temizleme, kaynak
derleme, derleme sınıf yolunu kurma, koşum sınıf yolunu kurma. Paketleme, bu dizinin
doğal bir uzantısı — kaynak derlendikten, iki sınıf yolu kurulduktan sonra, koşum sınıf
yolunun bir bölümünü tek bir taşınabilir dosyaya sıkıştıran beşinci bir adım. Bu adımın
girdisi hep aynı: daha önce derlenmiş sınıf dosyalarının bulunduğu bir dizin. Çıktısı ise
şimdiye kadar hiç görülmemiş bir şey: kendi başına açılıp okunabilen, kendi içinde bir
**giriş noktası** taşıyabilen tek bir dosya — **çalıştırılabilir çıktı**.

#### Arşivleme ve Bildirim

Bir arşiv, bir dizindeki sınıf dosyalarının tek bir dosyaya paketlenmiş hâlidir; **arşivleyici**
(standart kitaplığın bir parçası, program içinden çağrılan bir araç sağlayıcısı) bu paketlemeyi
gerçekten yapıyor. Arşivleyiciye verilen tek girdi, hangi dizinin paketleneceği ve
isteğe bağlı olarak bir **giriş noktası** adı; arşivleyici bu ikisinden bir arşiv dosyası
ve bir **bildirim** (manifest) üretiyor.

- **DR20.** Arşivleyici çağrısı, sınıf dosyalarının bulunduğu dizini olduğu gibi
  paketliyor; hiçbir bağımlılık dizini bu çağrıya eklenmiyor. Paketlenen şey yalnız
  verilen dizinin içeriği.

Aşağıdaki `arsivle` yöntemi bu tek derleme görevini üç parametreyle çalıştırıyor: hangi
arşiv dosyasının üretileceği, hangi dizinin paketleneceği ve isteğe bağlı olarak hangi
sınıfın giriş noktası sayılacağı. Bu üçü dışında hiçbir bilgi arşivleme çağrısına
girmiyor — özellikle hiçbir bağımlılık listesi, hiçbir başka sınıf yolu. Bu kısıtlılık
kasıtlı: bu dersin ikinci yarısı tam olarak bu kısıtlılığın sonucunu ölçüyor.

```java
// PaketYardimci.java — kaynak yazma, derleme ve arsivleme icin ortak islemler
import java.io.*;
import java.net.*;
import java.nio.file.*;
import java.util.*;
import java.util.spi.ToolProvider;

public class PaketYardimci {
    static Path yaz(Path dizin, String ad, String kaynak) throws IOException {
        Files.createDirectories(dizin);
        Path p = dizin.resolve(ad + ".java");
        Files.writeString(p, kaynak);
        return p;
    }

    static boolean derle(Path cikti, List<Path> sinifYolu, Path... kaynaklar) throws IOException {
        Files.createDirectories(cikti);
        List<String> arg = new ArrayList<>(List.of("-d", cikti.toString()));
        if (!sinifYolu.isEmpty()) {
            StringJoiner sj = new StringJoiner(File.pathSeparator);
            for (Path p : sinifYolu) sj.add(p.toString());
            arg.add("-classpath");
            arg.add(sj.toString());
        }
        for (Path k : kaynaklar) arg.add(k.toString());
        ToolProvider derleyici = ToolProvider.findFirst("javac").orElseThrow();
        StringWriter sessiz = new StringWriter();
        int sonuc = derleyici.run(new PrintWriter(sessiz), new PrintWriter(sessiz), arg.toArray(new String[0]));
        return sonuc == 0;
    }

    static void arsivle(Path jarYolu, Path siniflarDizini, String anaSinif) throws IOException {
        Files.createDirectories(jarYolu.getParent());
        List<String> arg = new ArrayList<>(List.of("--create", "--file", jarYolu.toString()));
        if (anaSinif != null) { arg.add("--main-class"); arg.add(anaSinif); }
        arg.add("-C"); arg.add(siniflarDizini.toString()); arg.add(".");
        ToolProvider arsivleyici = ToolProvider.findFirst("jar").orElseThrow();
        StringWriter sessiz = new StringWriter();
        arsivleyici.run(new PrintWriter(sessiz), new PrintWriter(sessiz), arg.toArray(new String[0]));
    }

    static String kos(List<Path> sinifYolu, String anaSinif) throws Exception {
        URL[] url = new URL[sinifYolu.size()];
        for (int i = 0; i < url.length; i++) url[i] = sinifYolu.get(i).toUri().toURL();
        try (URLClassLoader yukleyici = new URLClassLoader(url, PaketYardimci.class.getClassLoader().getParent())) {
            Class<?> sinif = Class.forName(anaSinif, true, yukleyici);
            sinif.getMethod("main", String[].class).invoke(null, (Object) new String[0]);
            return "tamam";
        } catch (java.lang.reflect.InvocationTargetException e) {
            return e.getCause().getClass().getSimpleName();
        } catch (Throwable t) {
            return t.getClass().getSimpleName();
        }
    }
}
```

`arsivle` yalnız iki şey yapıyor: verilen dizini paketliyor ve verilirse bir giriş noktası
adını bildirime yazıyor. `kos` önceki derslerdeki aynı işlemi yapıyor — tek farkla, sınıf
yolundaki bir öğe artık bir dizin değil, bir arşiv dosyası olabiliyor; `URLClassLoader` bir
arşiv dosyasının konumunu da bir dizin gibi kabul ediyor.

Bildirim, arşivin içinde ayrı bir kayıt olarak duruyor — sınıf dosyalarının yanında, ama
onlardan biri değil. Bir bildirim birçok alan taşıyabilir; bu ders yalnız birini
kullanıyor: Ana-Sınıf. Bu alan bir sınıf adını (bir dizgiyi) taşıyor, hiçbir çalıştırılabilir
kod içermiyor. Bildirimi okuyan taraf — burada bizim kendi kodumuz, gerçek bir kurulumda
ise bir çalıştırıcı — bu dizgiyi alıyor, o adı taşıyan sınıfı arşivin içinden buluyor ve
onun `main` yöntemini çağırıyor. Giriş noktasının kendisi hâlâ sıradan bir Java sınıfı;
değişen tek şey, o sınıfın adının nereden okunduğu.

#### Giriş Noktası Kaynakta Değil, Bildirimde

Bir arşiv onlarca sınıf taşıyabilir ve bunların yalnız biri (ya da hiçbiri) bir `main`
yöntemi içerebilir. Arşivin kendisini çalıştırmak isteyen taraf — bir çalıştırıcı, ya da
bu derste bizim kendi kodumuz — hangi sınıftan başlayacağını bilmek zorunda, ama bu
bilgiyi arşivin içindeki sınıf dosyalarını tek tek tarayarak bulmuyor. Aradığı yer sabit:
arşivin bildirimindeki tek bir alan.

- **DR21.** Giriş noktası bilgisi yalnız bildirimin Ana-Sınıf (`Main-Class`) alanına
  bakılarak okunuyor; kaynak dosyalarının hiçbirinde bu bilgi aranmıyor.

```java
// CalistirilabilirCikti.java — arsivleyici gercekten cagirilir, giris noktasi bildirimde durur, arsiv bagimliliklari icermez
import java.io.*;
import java.net.*;
import java.nio.file.*;
import java.util.*;
import java.util.jar.*;

public class CalistirilabilirCikti {
    public static void main(String[] args) throws Exception {
        Path kok = Files.createTempDirectory("paket");
        Path kaynak = kok.resolve("kaynak");

        Path sBagimsiz = PaketYardimci.yaz(kaynak, "Bagimsiz",
                "public class Bagimsiz { public static void main(String[] a) { } }");
        Path bagimsizD = kok.resolve("bagimsizD");
        PaketYardimci.derle(bagimsizD, List.of(), sBagimsiz);

        Path bildirimliJar = kok.resolve("bildirimli.jar");
        PaketYardimci.arsivle(bildirimliJar, bagimsizD, "Bagimsiz");

        Path bildirimsizJar = kok.resolve("bildirimsiz.jar");
        PaketYardimci.arsivle(bildirimsizJar, bagimsizD, null);

        System.out.println("-- giris noktasi kaynakta degil, arsivin bildiriminde --");
        try (JarFile jf = new JarFile(bildirimliJar.toFile())) {
            String ana = jf.getManifest().getMainAttributes().getValue("Main-Class");
            System.out.println("bildirimli arsivin giris noktasi   : " + ana);
        }
        try (JarFile jf = new JarFile(bildirimsizJar.toFile())) {
            Manifest m = jf.getManifest();
            String ana = (m == null) ? null : m.getMainAttributes().getValue("Main-Class");
            System.out.println("bildirimsiz arsivin giris noktasi  : " + ana);
        }
        System.out.println("bildirimli arsiv, arsivin kendisinden kosuyor mu: "
                + PaketYardimci.kos(List.of(bildirimliJar), "Bagimsiz"));

        System.out.println();
        System.out.println("-- sinirlayici olcum: arsiv bagimliliklarini icermez --");

        Path sYardimci = PaketYardimci.yaz(kaynak, "Yardimci",
                "public class Yardimci { public static String yardim() { return \"yardimci\"; } }");
        Path sMuhtac = PaketYardimci.yaz(kaynak, "Muhtac",
                "public class Muhtac { public static void main(String[] a) { Yardimci.yardim(); } }");
        Path muhtacD = kok.resolve("muhtacD");
        PaketYardimci.derle(muhtacD, List.of(), sYardimci, sMuhtac);

        Path sadeceMuhtacDizini = kok.resolve("sadeceMuhtac");
        Files.createDirectories(sadeceMuhtacDizini);
        Files.copy(muhtacD.resolve("Muhtac.class"), sadeceMuhtacDizini.resolve("Muhtac.class"));
        Path muhtacJar = kok.resolve("muhtac.jar");
        PaketYardimci.arsivle(muhtacJar, sadeceMuhtacDizini, "Muhtac");

        Path yardimciD = kok.resolve("yardimciD");
        Files.createDirectories(yardimciD);
        Files.copy(muhtacD.resolve("Yardimci.class"), yardimciD.resolve("Yardimci.class"));

        String bagimsizSonuc = PaketYardimci.kos(List.of(bildirimliJar), "Bagimsiz");
        String muhtacTekBasina = PaketYardimci.kos(List.of(muhtacJar), "Muhtac");
        String muhtacArtiYardimci = PaketYardimci.kos(List.of(muhtacJar, yardimciD), "Muhtac");

        System.out.printf("%-48s %s%n", "Bagimsiz arsivi, koşum sınıf yolu yalniz arsiv:", bagimsizSonuc);
        System.out.printf("%-48s %s%n", "Muhtac arsivi, koşum sınıf yolu yalniz arsiv:", muhtacTekBasina);
        System.out.printf("%-48s %s%n", "Muhtac arsivi, Yardimci dizini eklenince:", muhtacArtiYardimci);
    }
}
```

```
-- giris noktasi kaynakta degil, arsivin bildiriminde --
bildirimli arsivin giris noktasi   : Bagimsiz
bildirimsiz arsivin giris noktasi  : null
bildirimli arsiv, arsivin kendisinden kosuyor mu: tamam

-- sinirlayici olcum: arsiv bagimliliklarini icermez --
Bagimsiz arsivi, koşum sınıf yolu yalniz arsiv:  tamam
Muhtac arsivi, koşum sınıf yolu yalniz arsiv:    NoClassDefFoundError
Muhtac arsivi, Yardimci dizini eklenince:        tamam
```

İlk üç satır giriş noktasının nerede durduğunu ölçüyor. Aynı `Bagimsiz` sınıfından iki
arşiv üretiliyor: biri bir giriş noktasıyla (`--main-class Bagimsiz`), öbürü hiç giriş
noktası verilmeden. Bildirimli arşivin bildirimi okunduğunda Ana-Sınıf alanı `Bagimsiz`
değerini taşıyor; bildirimsiz arşivin bildiriminde bu alan hiç yok, okunan değer `null`.
İki arşiv de aynı sınıf dosyasını taşıyor, aynı derlemeden geliyor — aralarındaki tek fark
bildirimdeki bir satır. Kaynak dosyasının kendisinde giriş noktası diye bir bilgi hiç yok;
`Bagimsiz.java` ne "ben giriş noktasıyım" der ne de böyle bir işaretleyici taşır. Bu bilgi
yalnız arşivleme anında, arşivin kendi bildirimine yazılıyor — ve üçüncü satırın gösterdiği
gibi, arşiv bu bildirimi kullanarak kendi başına koşabiliyor.

Bu, Java Temelleri kursunda kurulan giriş noktası kavramının bir katman ötesi. Orada
giriş noktasının imzası (bir `main` yönteminin biçimi) sabitti; burada sorulan soru farklı:
birden çok sınıfın bulunduğu bir arşivde, **hangi** sınıf giriş noktası? Bu sorunun yanıtı
artık kaynakta değil, üzerinde çalışılan arşivin kendi bildiriminde. Bildirimsiz arşiv de
geçerli bir arşiv — sınıf dosyalarını taşıyor, açılabiliyor, içeriği okunabiliyor — ama
"hangi sınıftan başlanacağı" sorusunun yanıtı o arşivde yok. Bu eksiklik yalnız bildirim
okunduğunda görünüyor; arşivin kendisi bunu hiçbir yerde açıkça söylemiyor, `null` dönen
bir alan olarak duruyor.

#### Sınırlayıcı Ölçüm: Arşiv Bağımlılıklarını İçermez

- **DR22.** `Bagimsiz` arşivi hiçbir dış tipi anmıyor; `Muhtac` arşivi ise kendi
  dışındaki `Yardimci` tipine çağrı zamanında ihtiyaç duyuyor. İkisi de aynı arşivleme
  çağrısıyla üretiliyor.
- **DR23.** `Muhtac` arşivi yalnız kendi sınıf dosyasını içeriyor; `Yardimci`'nin sınıf
  dosyası bilinçli olarak dışarıda bırakılıyor ki koşum sınıf yolunun geri kalanına
  duyulan ihtiyaç ölçülebilsin.
- **DR24.** Koşum hatası, arşivin kendisinden değil arşivin tek başına konduğu sınıf
  yolundan doğuyor; aynı arşiv farklı bir sınıf yoluna konunca aynı hata düşmüyor.
- **DR25.** Bildirimsiz arşiv de geçerli bir arşivdir ve sınıf dosyalarını taşır;
  yalnızca giriş noktası bilgisi eksiktir ve bu eksiklik yalnız bildirim okunduğunda
  görünür.

Son üç satır iki arşivi yan yana koşturuyor. `Bagimsiz` arşivi tek başına koşum sınıf
yoluna konduğunda sorunsuz çalışıyor — çünkü `Bagimsiz.main` hiçbir dış tipi
anmıyor, ihtiyaç duyduğu her şey kendi sınıf dosyasında. `Muhtac` arşivi ise aynı
biçimde, tek başına koşum sınıf yoluna konduğunda `NoClassDefFoundError` ile düşüyor:
`Muhtac.main`, `Yardimci.yardim()`'i çağırıyor, ama `Yardimci`'nin sınıf dosyası bu
arşivin içinde yok — arşivleme sırasında bilinçli olarak yalnız `Muhtac.class`
paketlendi. Aynı `Muhtac` arşivi, koşum sınıf yoluna `Yardimci`'nin bulunduğu dizin de
eklendiğinde sorunsuz çalışıyor; arşivin kendisi değişmedi, değişen yalnız yanına
konan sınıf yolu.

Bu üç satır tek bir kuralı doğruluyor: **arşiv bağımlılıklarını içermez.** Arşivleyici
çağrısı yalnız verilen dizini paketler; o dizinin dışındaki hiçbir sınıf dosyası
otomatik olarak eklenmez. Bir arşivin "kendine yeter" olup olmadığı arşivin kendi
içeriğine değil, kaynak kodunun hangi tipleri andığına bağlı — ve bu, derleme anında bilinen
bir şey (`Muhtac`, `Yardimci`'yi derleme sınıf yolunda gerektirdi) ile paketleme anında
unutulabilecek bir şey (`Yardimci`'nin sınıf dosyası arşive hiç girmedi) arasındaki
boşluk. Derlemenin geçmiş olması, arşivin kendine yeteceğinin garantisi değil.

Bu boşluk, Bağımlılık Yönetimi dersinde ölçülen sessiz kusurla aynı ailedendir, ama
tersine dönmüş biçimde. Orada bir tipin koşum sınıf yolunda gerekmemesi onun
**kopyalanmış** olduğu anlamına geliyordu — sabitin değeri çağrı yerine gömülmüştü ve
kaynak dosyası ortadan kalkabilirdi. Burada ise bir tipin arşive girmemesi onun hâlâ
**gerekli** olduğu, yalnız paketlemenin onu unuttuğu anlamına geliyor. İki durumda da
hata anında değil, sonraki bir adımda ortaya çıkıyor: orada sabit değiştiğinde, burada
arşiv tek başına koşum sınıf yoluna konduğunda. Aradaki fark, kusurun sınıfı: `Muhtac`'ın
eksik bağımlılığı sessiz değil, `NoClassDefFoundError` ile açıkça düşüyor — çünkü
`Yardimci`'ye yapılan çağrı derleme zamanında gömülemeyecek kadar dinamik, bir yöntem
çağrısı, bir sabit değil.

Bu, arşivleme adımının sorumluluğunu da netleştiriyor: bir arşivin bağımlılıklarını
"otomatik olarak" toplaması beklenmemeli. Hangi sınıf dosyalarının bir arşive girdiği,
tıpkı hangi bağımlılığın hangi sınıf yoluna girdiği gibi, paketleme çağrısına verilen
girdiyle belirlenen bir karardır — ve bu kararın eksik verilmesi, derleme anında hiç
görünmeyen bir koşum hatasına dönüşebilir.

Dört ders boyunca ölçülen şeyin özeti burada tamamlanıyor. Bir bildirimden sabit bir evre
dizisi türedi; o evrelerden ikisi bildirimdeki her bağımlılığı bir kapsam etiketine göre
süzerek iki ayrı sınıf yolu kurdu; bir modülün genel ile iç yüzeyi arasındaki sınır bu iki
yoldan birine hangi dizinin girdiğiyle belirlendi; ve şimdi bu yollardan biri — koşum sınıf
yolu — tek bir taşınabilir dosyaya paketlendi. Dört adımın hiçbirinde kaynak metnin kendisi
değişmedi; değişen hep bildirimdi, hep sınıf yoluydu.

#### Özet

- Bir arşiv, bir dizindeki sınıf dosyalarının arşivleyici tarafından tek bir dosyaya
  paketlenmiş hâlidir; koşum sınıf yolunun tek bir öğesi olarak taşınabilir.
- Giriş noktası bilgisi kaynak dosyasında değil, arşivin kendi bildiriminin Ana-Sınıf
  alanında durur; bildirimsiz bir arşiv geçerlidir ama giriş noktası bilgisi eksiktir.
- Arşiv bağımlılıklarını içermez: arşivleme çağrısı yalnız verilen dizini paketler,
  o dizinin dışındaki hiçbir sınıf dosyası otomatik eklenmez.
- Kendine yeten bir arşiv (dış tip anmayan) tek başına koşum sınıf yoluna
  konduğunda çalışır; dışa muhtaç bir arşiv aynı koşulda koşum hatasıyla düşer ve
  yalnız eksik sınıf yolu tamamlandığında çalışır.
- Derlemenin geçmiş olması bir arşivin kendine yeteceğinin garantisi değildir; derleme
  sınıf yolunda gerekli olan bir tip, paketleme sırasında arşive hiç girmemiş olabilir.

#### Sonraki Adım

Bu konuda dört ders boyunca tek bir soru izlendi: bir bildirimden, bir bağımlılıktan, bir
modül sınırından, bir arşivden — hep aynı ikili sınıf yolu ayrımı çıktı. Koşum sınıf yolu
artık taşınabilir bir ürün hâline geldi ve içine ne konduğu bir karardır. Sıradaki konu
aynı kararı üçüncü bir yol için veriyor — testin gördüğü yol, üretimin gördüğü yol
değildir.

## Test

Test çerçevesinin yaşam döngüsü ve iddiaları, testin gördüğü sınıf yolunun üretimin gördüğü yoldan ayrıldığı yer, sahte bağımlılıkların gerçek tipin yerine koşum yolunda geçmesi, aynı test gövdesinin veriyle çoğaltılması, dış sistemli kurulumların test yoluna eklediği yük ve senaryo dilinde yazılan sınamanın çalıştırılabilir karşılığı.

### Birim Test Çerçevesi

Source: https://academia.sh/tr/kurslar/java-ekosistem/birim-test-cercevesi

Kurulum, gövde ve sökme çağrılarını sırayla yapan küçük bir koşturucu elle kurulur; aynı iddia sökmeden önce ve sonra kontrol edildiğinde iki ayrı sonuç veriyor, ikisi de koştu sayılıyor. Aynı derlenmiş test sınıfı, çerçeve tipi eksik bir sınıf yoluyla çalıştırılınca test sınıf yolunun üçüncü, bağımsız bir yol olduğu ölçülüyor.

Önceki konu koşum sınıf yolunu taşınabilir bir ürüne çevirdi ve içine ne konduğunun bir karar
olduğunu gösterdi. O karar orada iki yolla sınırlıydı: derleme sınıf yolu ve koşum sınıf yolu.
Bu ders aynı kararı üçüncü bir yol için verir. Test yazarken elimizin altında bir sınıf yolu
daha durur — testin gördüğü yol — ve bu yol üretimin gördüğü yolla aynı değildir. Sorumuz şu:
bu üçüncü yol neyi taşır ve üretim sınıf yolunda hiç bulunmayan bir tip eksik olduğunda ne
olur?

Sorunun ikinci yarısı ayrı bir konudur ve M21/K02 Birim Testi ve Test Odaklı Geliştirme
kursunda kuruldu: test sınıflandırması, iddia yazımı, test adlandırma — bunların hiçbiri burada
yeniden kurulmaz. Bu derste tek yeni soru, testin çalışması için gereken tiplerin hangi sınıf
yolunda durduğudur. O soruyu yanıtlamadan önce, üzerine bir sınıf yolu sorusu kuracağımız
çerçevenin kendisini — kurulum, gövde ve sökme çağrılarını sırayla yapan koşturucuyu — elimizle
kurmamız gerekiyor.

Bu iki soru göründüğünden daha yakın duruyor. Bir çerçeve, kaynakta hiç yazılmayan bir sırayı
her olgu için tekrar tekrar uygular; bu tekrar, bir dosyadaki kaynağın hiçbir yerinde
görünmeyen bir dizi çağrı anlamına gelir. O çağrıların derlenip koşabilmesi için bir yere
yazılmış olması gerekir — ve o yer, üretim kodunun okuduğu sınıf yolu değildir. Çerçeveyi
kurmadan sınıf yolu sorusuna geçmek, boş bir soyutlamayı ölçmek olurdu; bu yüzden ders önce
çağrıları elle yazıyor, sonra o çağrıların hangi tipe ihtiyaç duyduğunu sayıyor.

#### Çerçevenin Kurduğu Sıra

- **TS1.** Kurulum ve sökme burada birer `Runnable` olarak modellenir; gerçek bir çerçevenin
  test keşfi (dosya tarama, ad eşleme) burada kurulmaz — yalnız kurulum, gövde, sökme sırası ve
  başarısızlığın bildirilme biçimi modellenir.
- **TS2.** İddia başarısızlığı bir `AssertionError` fırlatarak bildirilir; koşturucu bunu
  yakalar ve olguyu "düştü" diye günlükler, aksi hâlde "geçti" yazar. Sökme adımı her koşulda —
  gövde düşse de düşmese de — çalışır.

Bir test çerçevesinin yaptığı iş, dışarıdan bakınca üç çağrıyı belirli bir sırayla yapmaktan
ibarettir: önce kurulum, sonra gövde, en sonda sökme. İddia bir karşılaştırmadır ve
başarısızlığını bir istisnayla bildirir; çerçeve bu istisnayı yakalayıp olguyu "düştü" diye
işaretler, yakalamazsa "geçti" yazar. Kurulum her olgu için yeniden çalışır — bir olgunun
bıraktığı durum bir sonrakine sızmaz — ve sökme, olgu geçse de düşse de çalışır; aksi hâlde
bir olgunun düşmesi bir sonrakinin kurulumunu bozardı. Aşağıdaki koşturucu bu üç çağrıyı ve
iddiayı elle kurar.

```java
// Cerceve.java — kurulum, govde ve sokme cagrilarini sirayla yapan kucuk bir kosturucu
public class Cerceve {

    static void olguKosustur(String ad, Runnable kurulum, Runnable govde, Runnable sokme) {
        kurulum.run();
        try {
            govde.run();
            System.out.println(pad(ad) + "gecti");
        } catch (AssertionError hata) {
            System.out.println(pad(ad) + "dustu: " + hata.getMessage());
        } finally {
            sokme.run();
        }
    }

    static void iddiaEsit(String alan, int beklenen, int gelen) {
        if (beklenen != gelen) throw new AssertionError(alan + " (beklenen " + beklenen + ", gelen " + gelen + ")");
    }

    static String pad(String s) { return String.format("%-16s", s); }

    public static void main(String[] args) {
        int[] miktar = {0};
        olguKosustur("depo-dolu",
                () -> miktar[0] = 10,
                () -> iddiaEsit("miktar", 10, miktar[0]),
                () -> miktar[0] = 0);

        int[] alan = {0};
        Runnable kurulum = () -> alan[0] = 5;
        Runnable sokme = () -> alan[0] = 0;

        olguKosustur("once-bakan", kurulum, () -> iddiaEsit("alan", 5, alan[0]), sokme);

        kurulum.run();
        olguKosustur("sonra-bakan", () -> {}, () -> { sokme.run(); iddiaEsit("alan", 5, alan[0]); }, () -> {});
    }
}
```

```
depo-dolu       gecti
once-bakan      gecti
sonra-bakan     dustu: alan (beklenen 5, gelen 0)
```

İlk olgu sıradan bir geçiş: kurulum `miktar`'ı 10 yapıyor, gövde bunu okuyup doğruluyor, sökme
sıfırlıyor. Asıl soru ikinci ve üçüncü olguda. Her ikisi de aynı iddiayı taşıyor — `alan`'ın
değeri 5 olmalı — ama biri iddiayı sökmeden **önce**, öteki sökmeden **sonra** kontrol ediyor.
Sökme `alan`'ı sıfırlıyor; bu yüzden aynı iddia birinci olguda geçiyor, ikincisinde düşüyor.
Koşturucunun kendisi bu ayrımı bilmiyor — o yalnız üç çağrıyı sırayla yapıyor ve gövdenin
fırlattığı istisnayı yakalıyor. `once-bakan` ve `sonra-bakan` çıktıda ikisi de birer satır
olarak görünüyor, ikisi de koşturucunun listesinde "koştu" sayılıyor; aralarındaki fark yalnız
sonuç sütununda okunuyor.

Buradan çıkan sonuç şudur: çerçevenin çağırdığı sıra bir garanti değil, bir sözleşmedir.
Kurulum-gövde-sökme sırası her zaman aynı kalır, ama gövdenin **ne zaman** okuduğu — kurulumun
bıraktığı durumu mu, sökmenin bıraktığı durumu mu — yazan kişinin elindedir. Aynı iddia metni,
iki farklı konumda yazıldığında iki farklı gerçeği ölçer ve çerçeve ikisi arasında hakemlik
yapmaz; yalnız hangisinin fırlattığını, hangisinin fırlatmadığını kaydeder. Bir olgunun "koştu"
sayılması, o olgunun **doğru** kurulduğu anlamına gelmez — yalnız üç çağrının sırayla yapıldığı
ve bir sonucun üretildiği anlamına gelir. M21/K02'nin kurduğu test sınıflandırması ve iddia
yazımı burada değişmiyor; değişen tek şey, iddianın gövde içinde nereye yazıldığının sonucu
belirlediğidir.

Bu ayrımın kaynağı `finally` bloğudur: sökme çağrısı gövdenin sonucundan bağımsız olarak
çalışır, çünkü koşturucu onu `try` bloğunun dışına değil, `finally` içine koymuştur. Sökme
adımı bu yüzden hem `depo-dolu` gibi geçen bir olguda hem `sonra-bakan` gibi düşen bir olguda
aynı şekilde işler — `alan`'ı sıfırlar. Fark, iddianın bu sıfırlamadan **önce mi sonra mı**
çalıştığındadır ve bu bilgi koşturucunun kendisinde değil, gövdeyi yazan kişinin
kararındadır. Gerçek bir çerçevede de durum aynıdır: kurulum ve sökmenin çağrılma sırası
belgelenir ve garanti edilir, ama gövdenin o sırayı nasıl kullandığı — hangi anı, hangi
durumu okuduğu — çerçevenin denetiminde değildir.

#### Test Sınıf Yolu: Üçüncü Bir Yol

- **TS3.** Derleme sınıf yolu ve koşum sınıf yolu ayrımı önceki konudan (java-build)
  devralınır ve burada yeniden kurulmaz; bu derste eklenen tek soru, üçüncü bir sınıf yolunun
  neyi taşıdığıdır.
- **TS4.** "Tip sayısı" burada derlenmiş `.class` dosyalarının sayımıdır. Aşağıdaki üç kaynağın
  her biri iç sınıf içermediği için tam olarak bir `.class` dosyası üretir; sayım bu yüzden
  kaynak sayısıyla örtüşüyor, ama genel olarak tip sayısı kaynak dosyası sayısından farklı
  çıkabilir.
- **TS5.** "Çerçeve tipi olmadan" koşum, gerçek bir sınıf yükleyicisinin, verilen sınıf yolunda
  o tipi arayıp bulamamasıdır — yükleyici burada `URLClassLoader`'dır ve platform sınıf
  yükleyicisinin üstüne kurulur, böylece uygulamanın kendi sınıf yolundaki hiçbir şeyi miras
  almaz.

Yukarıdaki koşturucu ve içindeki iddia yardımcısı `Cerceve` sınıfının bir parçasıydı — ama
gerçek bir programda bu tür bir yardımcı, sınanan koddan **ayrı** bir yerde durur. Aşağıdaki
ölçüm üç ayrı sınıfı gerçekten derliyor: `Depo` (üretim kodu), `Iddia` (çerçevenin kendisi,
yalnız testin ihtiyaç duyduğu bir eşitlik kontrolü) ve `DepoTest` (ikisini de kullanan test).
`Depo` yalnız kendi sınıf yolunda derleniyor; `Iddia` ayrı bir dizinde derleniyor; `DepoTest`
ikisine karşı derleniyor. Sonra iki ayrı sınıf yoluyla koşturuluyor: biri üçünü de içeriyor,
öteki `Iddia`'yı dışarıda bırakıyor.

```java
// UcuncuYol.java — test sinif yolunun uretim sinif yolundan fazladan tasidigi tipler gercekten sayilir
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.net.*;
import java.nio.file.*;
import java.util.*;
import java.util.spi.ToolProvider;

public class UcuncuYol {

    static Path yaz(Path dizin, String ad, String kaynak) throws IOException {
        Files.createDirectories(dizin);
        Path p = dizin.resolve(ad + ".java");
        Files.writeString(p, kaynak);
        return p;
    }

    static boolean derle(Path cikti, List<Path> sinifYolu, List<Path> kaynaklar) throws IOException {
        Files.createDirectories(cikti);
        List<String> arg = new ArrayList<>(List.of("-d", cikti.toString()));
        if (!sinifYolu.isEmpty()) {
            StringJoiner sj = new StringJoiner(File.pathSeparator);
            for (Path p : sinifYolu) sj.add(p.toString());
            arg.add("-classpath");
            arg.add(sj.toString());
        }
        for (Path k : kaynaklar) arg.add(k.toString());
        ToolProvider derleyici = ToolProvider.findFirst("javac").orElseThrow();
        StringWriter sessizCikti = new StringWriter();
        return derleyici.run(new PrintWriter(sessizCikti), new PrintWriter(sessizCikti),
                arg.toArray(new String[0])) == 0;
    }

    static String kos(List<Path> sinifYolu, String anaSinif) {
        URL[] url = new URL[sinifYolu.size()];
        try {
            for (int i = 0; i < url.length; i++) url[i] = sinifYolu.get(i).toUri().toURL();
            try (URLClassLoader yukleyici = new URLClassLoader(url, UcuncuYol.class.getClassLoader().getPlatformClassLoader())) {
                Class<?> sinif = Class.forName(anaSinif, true, yukleyici);
                sinif.getMethod("main", String[].class).invoke(null, (Object) new String[0]);
                return "tamam";
            }
        } catch (InvocationTargetException e) {
            return e.getCause().getClass().getSimpleName();
        } catch (Throwable t) {
            return t.getClass().getSimpleName();
        }
    }

    static long tipSayisi(Path dizin) throws IOException {
        try (var akis = Files.walk(dizin)) {
            return akis.filter(p -> p.toString().endsWith(".class")).count();
        }
    }

    public static void main(String[] args) throws Exception {
        Path kok = Files.createTempDirectory("ucuncuyol");
        Path kaynak = kok.resolve("kaynak");
        Path uretimD = kok.resolve("uretim"), cercD = kok.resolve("cerceve"), testD = kok.resolve("test");

        Path sDepo = yaz(kaynak, "Depo",
                "public class Depo {\n"
              + "  private int miktar;\n"
              + "  public Depo(int b) { miktar = b; }\n"
              + "  public void ekle(int adet) { miktar += adet; }\n"
              + "  public void azalt(int adet) {\n"
              + "    if (adet > miktar) throw new IllegalStateException(\"yetersiz miktar\");\n"
              + "    miktar -= adet;\n"
              + "  }\n"
              + "  public int miktar() { return miktar; }\n"
              + "}\n");
        Path sIddia = yaz(kaynak, "Iddia",
                "public class Iddia {\n"
              + "  public static void esit(int beklenen, int gelen) {\n"
              + "    if (beklenen != gelen) throw new AssertionError(\"beklenen \" + beklenen + \", gelen \" + gelen);\n"
              + "  }\n"
              + "}\n");
        Path sDepoTest = yaz(kaynak, "DepoTest",
                "public class DepoTest {\n"
              + "  public static void main(String[] a) {\n"
              + "    Depo d = new Depo(10);\n"
              + "    d.ekle(5);\n"
              + "    d.azalt(3);\n"
              + "    Iddia.esit(12, d.miktar());\n"
              + "    System.out.println(\"olgu gecti\");\n"
              + "  }\n"
              + "}\n");

        derle(uretimD, List.of(), List.of(sDepo));
        derle(cercD, List.of(), List.of(sIddia));
        derle(testD, List.of(uretimD, cercD), List.of(sDepoTest));

        System.out.println("uretim sinif yolundaki tip sayisi : " + tipSayisi(uretimD));
        System.out.println("test sinif yolundaki tip sayisi   : "
                + (tipSayisi(uretimD) + tipSayisi(cercD) + tipSayisi(testD)));

        System.out.println("tam test sinif yoluyla kosum        : " + kos(List.of(uretimD, cercD, testD), "DepoTest"));
        System.out.println("cerceve tipi olmadan (uretim+test)  : " + kos(List.of(uretimD, testD), "DepoTest"));
    }
}
```

```
uretim sinif yolundaki tip sayisi : 1
test sinif yolundaki tip sayisi   : 3
olgu gecti
tam test sinif yoluyla kosum        : tamam
cerceve tipi olmadan (uretim+test)  : NoClassDefFoundError
```

Sayılar iddiayı doğruluyor. `Depo` tek başına derlenip koştuğunda üretim sınıf yolu tek bir
tip taşıyor. `DepoTest`'i derlemek ve koşturmak için gereken tam küme ise üç tip: üretim kodu,
çerçevenin kendisi ve testin kendisi. Bu fazladan iki tip — `Iddia` ve `DepoTest` — üretim
programının hiçbir yerinde geçmiyor; dağıtılan çalıştırılabilir çıktıya konmaları gerekmiyor,
konursa gereksiz büyür. Test sınıf yolu bu yüzden üretim sınıf yolunun bir üst kümesi değil,
**ayrı** bir yoldur: üretimin ihtiyaç duymadığı tipleri taşır ve üretimin taşıdığı tipleri de
kapsar.

Yön tek taraflıdır. `DepoTest`'i derlemek `Depo`'yu ve `Iddia`'yı sınıf yoluna koymayı
gerektiriyor, çünkü test ikisini de kaynağında adıyla anıyor. `Depo`'yu derlemek ise ne
`Iddia`'ya ne `DepoTest`'e ihtiyaç duyuyor — üretim kodu test tiplerinin varlığından habersiz
yazıldı. Paketleme konusunun kurduğu ayrım burada aynen geçerli: bir çalıştırılabilir çıktı
üretim sınıf yolundan üretiliyorsa, içine `Iddia` ya da `DepoTest` gibi bir tip hiç girmiyor;
girerse, o çıktı gereğinden büyük ve gereğinden çok tip taşıyor demektir. Test sınıf yolunun
üçüncü bir yol olması, üretim çıktısına karışmaması gerektiği anlamına da gelir — iki yolun
birbirine karışması, sızmış bir bağımlılığın en sık görülen biçimidir.

Üç sınıf yolunu yan yana koyunca sıralama netleşiyor: derleme sınıf yolu `DepoTest`'i derlemek
için `Depo` ile `Iddia`'yı ister, koşum sınıf yolu yalnız `Depo`'yu taşır ve `Iddia`'dan hiç
haberdar değildir, test sınıf yolu ise üçünü birden taşır. Üç yol da aynı diskteki aynı
`.class` dosyalarına işaret edebilir; aralarındaki fark dosyaların içeriğinde değil, hangi
komuta hangi dizinlerin verildiğindedir. Bir tipin "test sınıf yolunda gerekli" olması, o
tipin üretim kodunda hiç yazılmadığı, yalnız testin kendi ihtiyacından doğduğu anlamına gelir
— ve bu ayrım, iki yolu gerçekten ayrı ayrı kurmadan görünmez.

Bu derste `Iddia` ve `DepoTest` derleme zamanında birbirine bağımlıydı, ama koşum zamanında
`Iddia`'nın **koşum sınıf yolunda** bulunması derlemeden ayrı bir zorunluluktu — nitekim son
satır bunu ayırıyor. Sıradaki ders bu gözlemi bir adım öteye taşıyor: testin ihtiyaç duyduğu
tip, sınanan kodun **zaten** kullandığı bir tipin yerine geçtiğinde, aynı derlenmiş test iki
ayrı koşum sınıf yoluyla iki ayrı sonuç verebilir.

Son iki satır bunu koşarak gösteriyor. `DepoTest` tam sınıf yoluyla — üretim, çerçeve ve test
tipleri bir arada — çalıştırıldığında `Iddia.esit` çağrısı bulunuyor ve olgu "tamam" dönüyor.
Aynı `DepoTest.class` dosyası, bu kez yalnız üretim ve test dizinleriyle, çerçeve dizini
**olmadan** çalıştırıldığında yükleyici `Iddia` adını hiçbir yerde bulamıyor ve
`NoClassDefFoundError` fırlatıyor. Dosyanın kendisi değişmedi, yeniden derlenmedi; yalnız
kendisine verilen sınıf yolu daraldı. Bu, "test sınıf yolu üçüncü bir yoldur" iddiasının en
somut kanıtı: çerçevenin sağladığı tip, testin **derlenmesi** için de **koşması** için de
gerekli, ama üretimin ne derlenmesi ne koşması için gerekli.

#### Özet

- Test çerçevesi burada kurulum, gövde ve sökme çağrılarını sırayla yapan, iddia
  başarısızlığını bir istisnayla yakalayan küçük bir koşturucu olarak modellendi.
- Aynı iddia sökmeden önce kontrol edildiğinde geçti, sökmeden sonra kontrol edildiğinde
  düştü; koşturucu ikisini de "koştu" saydı, sıranın kendisi doğruluğu garanti etmedi.
- Üretim sınıf yolu tek bir tip taşıdı; test sınıf yolu aynı tipe ek olarak çerçevenin ve
  testin kendi tiplerini taşıdı — toplam üç tip.
- Aynı derlenmiş `DepoTest.class`, çerçeve tipi eksik bir sınıf yoluyla koşturulunca yeniden
  derlenmeden `NoClassDefFoundError` verdi; dosya değişmedi, yalnız sınıf yolu değişti.
- Test sınıflandırması ve iddia yazımı M21/K02 Birim Testi ve Test Odaklı Geliştirme
  kursunda kuruldu ve burada tekrarlanmadı.

#### Sonraki Adım

Bu derste test sınıf yoluna giren tipler kendi elimizle yazdığımız, sınanan koddan tamamen
ayrı duran tiplerdi. Ama testin ihtiyaç duyduğu bir bağımlılık, üretim kodunun **zaten**
kullandığı bir tipin yerini almak zorunda kalabilir — aynı adı taşıyan, aynı imzayı sağlayan
ama farklı davranan bir tip. Sıradaki ders aynı derlenmiş testi, gerçek tipin durduğu bir
sınıf yoluna karşı bir kez, aynı adı taşıyan sahte tipin durduğu başka bir sınıf yoluna karşı
bir daha koşturur ve test yeniden derlenmeden iki ayrı sonuç aldığını sayar.

### Sahteleme

Source: https://academia.sh/tr/kurslar/java-ekosistem/sahteleme

Aynı derlenmiş test, gerçek tedarikçi tipinin durduğu bir koşum sınıf yoluna karşı bir kez, aynı adı taşıyan sahte tipin durduğu başka bir koşum sınıf yoluna karşı bir daha çalıştırılır. Test yeniden derlenmeden iki ayrı sonuç veriyor, hata yok; imza ayrıldığında ise aynı kurulum koşumda düşüyor ve nedeni test metninde görünmüyor.

Önceki derste test sınıf yoluna giren tipler — çerçeve ve testin kendisi — sınanan koddan
tamamen ayrı duruyordu; üretim kodu onları hiç anmıyordu. Ama testin sık sık yerine koyduğu
bir bağımlılık, üretim kodunun **zaten** kullandığı bir tipin yerini alır: aynı adı taşır,
aynı çağrıyı yanıtlar, ama farklı davranır. Bu ders aynı derlenmiş testi böyle bir tipin iki
ayrı gerçekleştirimine karşı çalıştırıyor — biri gerçek, öteki sahte — ve testi hiç yeniden
derlemeden iki ayrı sonuç aldığını sayıyor. Bu, önceki dersin sınadığı boşluğun tam tersi
yönde bir boşluk: orada bir tip hiç bulunmadığında ne olduğuna bakıldı, burada bir tip
**başka bir tipin yerine** bulunduğunda ne olduğuna bakılıyor.

Sahte bağımlılık ailesi — sahte nesne, güdük, casus nesne, kukla nesne — M21/K02 Birim Testi
ve Test Odaklı Geliştirme kursunda kuruldu ve burada yeniden tanıtılmaz; hangi türün hangi
soruyu yanıtladığı orada yazılı. Bu dersin eklediği tek soru farklı bir eksendedir: sahte tip
**hangi sınıf yolunda** duruyor ve gerçek tipin yerine geçmesi derleme zamanında mı, koşum
zamanında mı oluyor?

Bu soru, önceki derste kurulan test sınıf yolu sorusundan farklı bir yerde duruyor. Orada
test sınıf yolu, üretim sınıf yolunun **hiç tanımadığı** iki tip taşıyordu — çerçeve ve testin
kendisi. Burada durum tersine dönüyor: sahte tip, test kodunun **zaten** bir adla andığı bir
bağımlılığın yerine geçiyor. Test kodu `Tedarikci` adını yazdı, o adın hangi sınıf dosyasıyla
karşılık bulacağı ise sınıf yoluna bırakıldı. Aynı ad, aynı çağrı, iki ayrı gövde — ve hangi
gövdenin çalışacağına karar veren şey kaynak metin değil, o koşumda sınıf yoluna **hangi**
dizinin verildiğidir.

#### Aynı Adı Taşıyan İki Gerçekleştirim

- **TS6.** Sahte tip burada aynı ada ve uyumlu bir imzaya sahip, elle yazılmış ayrı bir
  sınıftır. Sahteleme kitaplıklarının çalışma zamanında bayt kodu üreten vekil (proxy)
  düzeneği burada kurulmaz — davranış aynı sonucu elle yazılmış bir sınıfla üretir.
- **TS7.** Test kaynağı `Tedarikci` adını yalnız yansımayla (`Class.forName`) arar; kaynağında
  hiçbir gerçekleştirime derleme zamanında bağlı değildir. Bu yüzden test bir kez derlenir ve
  farklı koşum sınıf yollarıyla değişmeden çalışır.

Sınanan davranış bir depo dolum çağrısıdır: `Tedarikci` adlı bir tip, istenen miktarı alır ve
ne kadarının karşılandığını döndürür. Üretim kodunda bu çağrının arkasında gerçek bir tedarik
süreci durur — burada modellenmiyor, yalnız çağrının kendisi ve döndürdüğü sayı önemli. Bir
testin bu çağrıyı her koştuğunda gerçek tedariğe gitmesi hem yavaş hem kararsız olurdu; bu
yüzden test, gerçek gerçekleştirimin yerine geçebilecek, aynı adı ve aynı çağrı biçimini
taşıyan başka bir tipe ihtiyaç duyuyor. Üç ayrı gerçekleştirim yazıyoruz. Üretim
gerçekleştirimi istenen miktarın tamamını karşılıyor. Sahte gerçekleştirim her çağrıda bir
eksik karşılıyor ve kaç kez çağrıldığını statik bir alanda tutuyor — casus nesnenin çağrı
kaydını burada bu alan üstleniyor. Üçüncü gerçekleştirim ise aynı adı taşıyor ama ilk
parametresinin tipini `int`'ten `long`'a değiştiriyor; bu, imza uyuşmazlığının koşuma ne
yaptığını göstermek için sonradan kullanılıyor. Test, üç gerçekleştirimin **hiçbirine**
derleme zamanında bağlı değildir — yalnız `Tedarikci` adını ve `dolum(int)` imzasını yansıma
üzerinden arar.

```java
// IkiKosum.java — ayni derlenmis test, gercek tip ve ayni adli sahte tip arasinda kosum sinif yolu degisince farkli sonuc verir
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.net.*;
import java.nio.file.*;
import java.util.*;
import java.util.spi.ToolProvider;

public class IkiKosum {

    static Path yaz(Path dizin, String ad, String kaynak) throws IOException {
        Files.createDirectories(dizin);
        Path p = dizin.resolve(ad + ".java");
        Files.writeString(p, kaynak);
        return p;
    }

    static boolean derle(Path cikti, List<Path> sinifYolu, List<Path> kaynaklar) throws IOException {
        Files.createDirectories(cikti);
        List<String> arg = new ArrayList<>(List.of("-d", cikti.toString()));
        if (!sinifYolu.isEmpty()) {
            StringJoiner sj = new StringJoiner(File.pathSeparator);
            for (Path p : sinifYolu) sj.add(p.toString());
            arg.add("-classpath");
            arg.add(sj.toString());
        }
        for (Path k : kaynaklar) arg.add(k.toString());
        ToolProvider derleyici = ToolProvider.findFirst("javac").orElseThrow();
        StringWriter sessizCikti = new StringWriter();
        return derleyici.run(new PrintWriter(sessizCikti), new PrintWriter(sessizCikti),
                arg.toArray(new String[0])) == 0;
    }

    static String kos(List<Path> sinifYolu, String anaSinif) {
        URL[] url = new URL[sinifYolu.size()];
        try {
            for (int i = 0; i < url.length; i++) url[i] = sinifYolu.get(i).toUri().toURL();
            try (URLClassLoader yukleyici = new URLClassLoader(url, IkiKosum.class.getClassLoader().getPlatformClassLoader())) {
                Class<?> sinif = Class.forName(anaSinif, true, yukleyici);
                sinif.getMethod("main", String[].class).invoke(null, (Object) new String[0]);
                return "tamam";
            }
        } catch (InvocationTargetException e) {
            return e.getCause().getClass().getSimpleName();
        } catch (Throwable t) {
            return t.getClass().getSimpleName();
        }
    }

    public static void main(String[] args) throws Exception {
        Path kok = Files.createTempDirectory("ikikosum");
        Path kaynak = kok.resolve("kaynak");
        Path testD = kok.resolve("test"), uretimD = kok.resolve("uretim"),
             sahteD = kok.resolve("sahte"), bozukD = kok.resolve("bozuk");

        Path sTest = yaz(kaynak, "TedarikTest",
                "import java.lang.reflect.Field;\n"
              + "import java.lang.reflect.Method;\n"
              + "public class TedarikTest {\n"
              + "  public static void main(String[] a) throws Exception {\n"
              + "    Class<?> c = Class.forName(\"Tedarikci\");\n"
              + "    Object nesne = c.getDeclaredConstructor().newInstance();\n"
              + "    Method m = c.getMethod(\"dolum\", int.class);\n"
              + "    int[] istekler = {5, 3, 8};\n"
              + "    int toplam = 0;\n"
              + "    for (int istek : istekler) toplam += (int) m.invoke(nesne, istek);\n"
              + "    int beklenen = 5 + 3 + 8;\n"
              + "    System.out.println(\"beklenen    : \" + beklenen);\n"
              + "    System.out.println(\"gelen       : \" + toplam);\n"
              + "    System.out.println(\"iddia       : \" + (toplam == beklenen));\n"
              + "    try {\n"
              + "      Field f = c.getField(\"cagriSayisi\");\n"
              + "      System.out.println(\"cagri sayisi: \" + f.getInt(null));\n"
              + "    } catch (NoSuchFieldException e) {\n"
              + "      System.out.println(\"cagri sayisi: izlenmiyor\");\n"
              + "    }\n"
              + "  }\n"
              + "}\n");

        Path sUretim = yaz(kaynak.resolve("uretim"), "Tedarikci",
                "public class Tedarikci {\n"
              + "  public int dolum(int istenen) { return istenen; }\n"
              + "}\n");
        Path sSahte = yaz(kaynak.resolve("sahte"), "Tedarikci",
                "public class Tedarikci {\n"
              + "  public static int cagriSayisi = 0;\n"
              + "  public int dolum(int istenen) { cagriSayisi++; return istenen - 1; }\n"
              + "}\n");
        Path sBozuk = yaz(kaynak.resolve("bozuk"), "Tedarikci",
                "public class Tedarikci {\n"
              + "  public int dolum(long istenen) { return (int) istenen; }\n"
              + "}\n");

        derle(testD, List.of(), List.of(sTest));
        derle(uretimD, List.of(), List.of(sUretim));
        derle(sahteD, List.of(), List.of(sSahte));
        derle(bozukD, List.of(), List.of(sBozuk));

        System.out.println("-- gercek tiple --");
        System.out.println("kosum sonucu: " + kos(List.of(testD, uretimD), "TedarikTest"));
        System.out.println("-- sahte tiple (test yeniden derlenmedi) --");
        System.out.println("kosum sonucu: " + kos(List.of(testD, sahteD), "TedarikTest"));
        System.out.println("-- imza degisen tiple --");
        System.out.println("kosum sonucu: " + kos(List.of(testD, bozukD), "TedarikTest"));
    }
}
```

```
-- gercek tiple --
beklenen    : 16
gelen       : 16
iddia       : true
cagri sayisi: izlenmiyor
kosum sonucu: tamam
-- sahte tiple (test yeniden derlenmedi) --
beklenen    : 16
gelen       : 13
iddia       : false
cagri sayisi: 3
kosum sonucu: tamam
-- imza degisen tiple --
kosum sonucu: NoSuchMethodException
```

`TedarikTest` bir kez derleniyor (`testD`) ve üç kez farklı bir ikinci sınıf yolu bileşeniyle
koşturuluyor: önce `uretimD`, sonra `sahteD`, en son `bozukD`. Aradaki fark yalnız `kos`
çağrısına verilen listenin ikinci elemanı; testin kendi `.class` dosyası üçünde de birebir
aynı bayt kodu taşıyor.

`TedarikTest`'in derlenmesi hiçbir gerçekleştirime ihtiyaç duymuyor — kaynağında `Tedarikci`
adı yalnız bir dize sabiti olarak geçiyor, `import` edilen bir tip olarak değil. Bu, birinci
derste kurulan derleme sınıf yolu ile koşum sınıf yolu ayrımını daha da keskinleştiriyor:
burada test **hiçbir** derleme sınıf yolu bileşenine ihtiyaç duymadan derleniyor, ama üç ayrı
koşumun her biri için farklı bir koşum sınıf yolu bileşenine ihtiyaç duyuyor. Derleme zamanında
görünmeyen bir bağımlılık, koşum zamanında zorunlu hâle geliyor — bu da geçen konunun
kurduğu "derlendi, bir kanıt değildir" okumasının test sınıf yolundaki karşılığı.

Gerçek sahteleme kitaplıkları bu değişimi genellikle arayüz tabanlı yapar: sınanan kod bir
arayüze bağlanır, sahte tip o arayüzü uygular ve derleyici ikisinin uyumunu denetler. Burada
seçilen yol daha çıplak — doğrudan yansıma — çünkü ders üçüncü sınıf taraf bir çatı adı
yazmadan aynı sonucu üretmek zorunda. Arayüz tabanlı yaklaşımda da sonuç aynıdır: hangi
gerçekleştirimin kullanılacağı yine koşum sınıf yoluna bırakılır, derleyici yalnız **bir**
gerçekleştirimin var olduğunu, kaç tane olduğunu ve hangisinin seçileceğini denetlemez.

#### Sessiz Ayrışma: Hata Yok, Sonuç Farklı

- **TS8.** Çağrı kaydı burada statik bir alan (`cagriSayisi`) olarak tutulur. Her koşum kendi
  `URLClassLoader`'ını kullandığı için bu alan her koşumda sıfırdan başlar; iki koşum
  arasında sayaç birbirine karışmaz.

Gerçek tiple koşulan ilk çalıştırma beklendiği gibi geçiyor: üç istek toplamı 16, dönen toplam
da 16, iddia doğru. İkinci çalıştırma **aynı test dosyasını, aynı bayt kodla**, yalnız ikinci
sınıf yolu bileşenini `sahteD` yaparak koşturuyor. Sahte gerçekleştirim her çağrıda isteneni
bir eksik karşılıyor; üç çağrının toplamı bu yüzden 13'e düşüyor ve iddia satırı `false`
basıyor. Burada hiçbir istisna fırlamıyor, hiçbir derleme hatası yok — `kos` çağrısı ikisinde
de "tamam" döndürüyor. Fark yalnız bir satırın içeriğinde: `iddia` alanı `true`'dan `false`'a
düşüyor. Bu, kursun sessiz kusur ailesinin en temiz örneği: iki koşum da başarıyla tamamlanıyor,
ikisi de "çalıştı" sayılıyor, ama ürettikleri sonuç aynı değil.

Casus nesnenin tuttuğu çağrı kaydı bu ayrımı doğruluyor. Üretim gerçekleştiriminde
`cagriSayisi` diye bir alan yok — test bunu yakalıyor ve "izlenmiyor" yazıyor. Sahte
gerçekleştirimde ise aynı alan `3` değerini taşıyor: `dolum` üç kez çağrılmış, üçü de kaydı
artırmış. Bu sayı, testin dönüş değerine hiç bakmadan da tek başına bir kanıt taşır — sahte
tip gerçekten üç kez çağrıldı, ne bir eksik ne bir fazla. `iddia` alanı **ne** döndüğünü
söylüyor, `cagriSayisi` **kaç kez** çağrıldığını söylüyor; ikisi ayrı sorulara yanıt veriyor
ve ikisi de aynı koşumda birlikte okunabiliyor.

Bu iki koşumun ürettiği fark, bir test takımının "geçti" demesinin neyi garanti etmediğini
gösteriyor. Gerçek gerçekleştirimle koşulan takım geçiyor, sahte gerçekleştirimle koşulan
takım düşüyor — ikisi de aynı test dosyasını, aynı iddiayı taşıyor. Eğer bir dağıtım hattı
testleri yalnız sahte gerçekleştirimin durduğu bir sınıf yoluyla çalıştırıyorsa, üretim
gerçekleştirimindeki bir bozulmayı hiç görmez; testler geçmeye devam eder çünkü hiçbiri gerçek
tiple hiç koşmamıştır. Bu, kursun ölçü sorusunun test konusundaki en doğrudan karşılığı: bir
testin "geçmesi", o testin **hangi sınıf yoluyla** koştuğuna bakılmadan okunamaz.

#### İmza Ayrıldığında Kusur Koşuma Düşer

- **TS9.** İmza karşılaştırması burada yalnız ilk parametrenin tipine bakıyor
  (`getMethod("dolum", int.class)`); gerçek bir yansıma araması adı, parametre sayısını ve
  parametre tiplerini birlikte eşleştirir, dönüş tipine bakmaz.
- **TS10.** Sahteleme kitaplıklarının doğrulama (verify) çağrısı burada kurulmaz; casus
  nesnenin ürettiği sinyal bu derste yalnız okunan bir sayaçtır, kendi kendini doğrulayan bir
  nesne değildir.

Üçüncü koşum aynı `TedarikTest.class`'ı, bu kez `dolum`'un ilk parametresi `int` değil `long`
olan bir gerçekleştirimin durduğu sınıf yoluyla çalıştırıyor. `Class.forName("Tedarikci")`
sınıfı buluyor — ad aynı, sınıf gerçekten var — ama `c.getMethod("dolum", int.class)` çağrısı
tam olarak bu imzayla bir yöntem bulamıyor ve `NoSuchMethodException` fırlatıyor. Bu istisna
testin kendi `main`'inden yakalanmadan çıkıyor, `kos` yardımcısı bunu yakalayıp adını
döndürüyor: `kosum sonucu: NoSuchMethodException`.

Buradaki ayrım önceki iki koşumdan farklı bir kusur sınıfına düşüyor. Sahte tiple koşum
**sessizdi** — iki taraf da geçerli bir sonuç üretti, farkı yalnız bir sayı taşıdı. İmza
ayrılan tiple koşum ise **koşumda** düşüyor: program bir istisnayla duruyor. Ama bu istisnanın
neyi anlattığına dikkat etmek gerekir — `NoSuchMethodException` mesajı hangi sınıfın hangi
yöntemi aradığını söyler, ama **neden** o yöntemin artık orada olmadığını söylemez. Test
metninin kendisi hiç değişmedi; kusurun nedeni yalnız değiştirilen gerçekleştirimin
kaynağında görünür, test dosyasında değil. Bir sınama koşum sınıf yolunda hangi tipin
durduğuna bu kadar bağlıysa, imza uyuşmazlığı derlemede değil koşumda yakalanıyor ve o anda
elde yalnız bir istisna sınıfı adı kalıyor.

Bu üç koşumun sıralaması bilinçli: gerçek tip, sonra uyumlu sahte tip, en son uyumsuz tip.
Aradaki fark sahte tipin **ne kadar** gerçek tipe benzediğidir. Uyumlu sahte tip, imzası
gerçek tipin imzasıyla birebir eşleştiği için testin hiç haberi olmadan yerine geçebiliyor —
kusuru yalnız sonuçtaki bir sayı taşıyor. Uyumsuz tip ise adı aynı olsa da imzası ayrıldığı
için testin yansıma çağrısı onu hiç bulamıyor — kusur artık bir sayı değil, bir istisna. Bu
iki uç arasındaki fark, sahtelemenin gerçek riskini gösteriyor: sahte tipi güncel tutmak
yalnız adını değil, imzasını da gerçek tiple eşleşir tutmayı gerektiriyor ve bu eşleşme
derleyici tarafından denetlenmiyor çünkü ikisi hiçbir zaman aynı derlemede bir arada
bulunmuyor.

Üç koşumun üçü de aynı üç kusur sınıfını örnekliyor: gerçek tiple koşum sorunsuz, sahte tiple
koşum sessizce farklı bir sonuç veriyor, imzası ayrılan tiple koşum bir istisnayla düşüyor.
Hiçbiri derleme sırasında yakalanmadı — üçü de derleme sınıf yolunda hiç görünmeyen bir
kararın koşum sınıf yolunda ortaya çıkmasıydı. Bir test takımının güvenilirliği bu yüzden
yalnız hangi iddiaları yazdığına değil, o iddiaların hangi sınıf yoluna karşı koştuğuna da
bağlıdır.

#### Özet

- Sahte bağımlılık ailesi M21/K02'de kuruldu; bu derste eklenen tek soru sahte tipin hangi
  sınıf yolunda durduğudur.
- Aynı derlenmiş test, aynı adı taşıyan gerçek ve sahte gerçekleştirimlere karşı yeniden
  derlenmeden koşturuldu ve iki ayrı sonuç verdi; ikisi de hatasız tamamlandı.
- Sahte gerçekleştirimin tuttuğu çağrı sayacı, casus nesnenin sinyalini üretim
  gerçekleştiriminde bulunmayan bir alan üzerinden taşıdı.
- İmzası değişen bir gerçekleştirimle koşum `NoSuchMethodException` verdi; bu istisna
  koşumda düştü ve nedeni test metninde görünmedi.
- Aynı adı taşıyan iki tip yalnız imzaları uyuştuğu sürece birbirinin yerine geçebilir;
  imza ayrıldığında kusur derlemede değil koşumda ortaya çıkar.

#### Sonraki Adım

Bu derste tek bir test gövdesi üç ayrı sınıf yoluna karşı koşturuldu, ama her koşumda aynı
üç sayı — 5, 3, 8 — kullanıldı. Bir test gövdesini birden çok veri kümesiyle çoğaltmak ayrı
bir soru açıyor: aynı gövde, aynı iddia, farklı girdiler — kaç ayrı koşum, kaç ayrı
başarısızlık ve başarısızlığın hangi veri satırından geldiği nasıl kayda geçiyor? Sıradaki
ders bunu tek bir gövdeyi bir veri kümesi üzerinde tekrar tekrar çalıştırarak sayıyor.

### Parametreli Testler

Source: https://academia.sh/tr/kurslar/java-ekosistem/parametreli-testler

Tek bir gövde, bildirilen bir veri kümesi üzerinde üç kez çağrılıyor ve her çağrı kendi satırının başarılı ya da düştüğünü kaydediyor. Kurulum döngünün dışına taşınıp bir Depo örneği satırlar arasında paylaşıldığında ise hangi satırın geçtiği veri kümesinin sırasına bağlı hâle geliyor, aynı üç satır ters sırayla farklı bir satırı geçirtiyor.

Önceki derste tek bir test gövdesi üç ayrı sınıf yoluna karşı koşturuldu, ama her koşumda
aynı üç sayı kullanıldı. Bu derste gövde sabit kalıyor, değişen taraf veri: aynı kontrolü
üç ayrı girdiyle çalıştırmak için gövdeyi üç kez yazmak yerine, veri kümesini bir bildirim
olarak yazıp gövdeyi **bir kez** kuruyoruz. Soru şu: bir gövde, N satırlık bir veri kümesine
karşı çağrıldığında kaç ayrı sonuç üretiyor, kaçı düşüyor ve düşen satır hangisi olduğunu
nereden biliyoruz?

Bu sorunun ikinci yarısı ilk bakışta ayrı görünüyor ama değil: aynı gövdeyi N kez çalıştırmak,
N koşumun birbirinden bağımsız olduğunu **varsayar**. Gövde bir kez yazıldığı için, bir
satırın kurulumu bir sonraki satırın kurulumuyla aynı koddan geçer — ve o kodun paylaşılan
bir değişkene dokunup dokunmadığı, veri kümesinin kendisinde hiç görünmez. Bu ders önce
doğru kurulmuş bir gövdeyle üç bağımsız sonucu sayıyor, sonra aynı veriyi bozuk bir kurulumla
koşturup varsayımın ne zaman çöktüğünü gösteriyor.

Veri kümesinin kendisi burada üç satırla sınırlı tutuluyor, ama sayının kendisi önemli
değil — önemli olan, N ne olursa olsun aynı gövdenin N kez çağrılabilmesi ve her çağrının
kendi sonucunu üretebilmesidir. Üç satır, elle izlenebilecek en küçük sayı olduğu için
seçildi; aynı kurulum hatası yüz satırlık bir veri kümesinde de aynı biçimde ortaya çıkar,
yalnız izlemesi güçleşir.

#### Veri Kümesi Bildirimde Durur, Gövde Bir Kez Yazılır

- **TS11.** Veri kümesi burada bir `record` dizisi olarak bildirilir (`Veri[]`). Gerçek
  çerçevelerin veri kaynağını bir dosyadan, bir ek açıklamadan ya da bir yöntem
  referansından okuma mekanizması burada kurulmaz — veri doğrudan kaynak kodda durur.
- **TS12.** Gövde burada veri dizisinde gezinen bir döngüdür; aynı üç satır aynı sırayla,
  her biri kendi girdisiyle bir kez çağrılır. Çağrı sayısı veri satırı sayısına eşittir.

Aşağıdaki `VERI` dizisi üç satır taşıyor; her satır bir başlangıç miktarı, bir azaltma ve
beklenen sonucu bir arada tutuyor. `bagimsizKosum` gövdesi bu üç satırı sırayla işliyor ve
her satır için **kendi** `Depo` örneğini kuruyor — kurulum satırın içinde, döngünün her
turunda yeniden çalışıyor. Aşağıda aynı veri kümesi, kurulumun döngü **dışına** taşındığı
bozuk bir sürümle de koşturuluyor; ikisi arasındaki fark bu dersin asıl konusu.

```java
// Veriler.java — tek govde, veri kumesince cogaltilan kosum, ve paylasilan durumun sirali bagimliligi
import java.util.*;

public class Veriler {

    static class Depo {
        private int miktar;
        Depo(int baslangic) { miktar = baslangic; }
        void azalt(int adet) { miktar -= adet; }
        int miktar() { return miktar; }
    }

    record Veri(int baslangic, int azalt, int beklenen) {}

    static final Veri[] VERI = {
        new Veri(10, 3, 7),
        new Veri(5, 2, 3),
        new Veri(8, 8, 0),
    };

    // -- govde bir kez yazilir, N koşumun her biri kendi Depo'sunu kurar --------------------
    static List<String> bagimsizKosum(Veri[] veri) {
        List<String> gunluk = new ArrayList<>();
        int dusen = 0;
        for (int i = 0; i < veri.length; i++) {
            Veri v = veri[i];
            Depo d = new Depo(v.baslangic());
            d.azalt(v.azalt());
            boolean gecti = d.miktar() == v.beklenen();
            if (!gecti) dusen++;
            gunluk.add("satir " + i + ": " + (gecti ? "gecti" : "dustu, gelen=" + d.miktar()));
        }
        gunluk.add("dusen sayisi: " + dusen);
        return gunluk;
    }

    // -- kurulum yanlislikla dongunun disina alinirsa Depo koşumlar arasinda paylasilir -----
    static List<String> paylasilanKosum(Veri[] veri) {
        List<String> gunluk = new ArrayList<>();
        int dusen = 0;
        Depo d = new Depo(veri[0].baslangic());
        for (int i = 0; i < veri.length; i++) {
            Veri v = veri[i];
            d.azalt(v.azalt());
            boolean gecti = d.miktar() == v.beklenen();
            if (!gecti) dusen++;
            gunluk.add("satir " + i + ": " + (gecti ? "gecti" : "dustu, gelen=" + d.miktar()));
        }
        gunluk.add("dusen sayisi: " + dusen);
        return gunluk;
    }

    public static void main(String[] args) {
        System.out.println("-- bagimsiz kosum: her satir kendi Depo'sunu kuruyor --");
        bagimsizKosum(VERI).forEach(System.out::println);

        System.out.println("-- paylasilan kosum, veri sirasi orijinal --");
        paylasilanKosum(VERI).forEach(System.out::println);

        Veri[] tersVeri = { VERI[2], VERI[1], VERI[0] };
        System.out.println("-- paylasilan kosum, ayni veri, sira tersine cevrildi --");
        paylasilanKosum(tersVeri).forEach(System.out::println);
    }
}
```

```
-- bagimsiz kosum: her satir kendi Depo'sunu kuruyor --
satir 0: gecti
satir 1: gecti
satir 2: gecti
dusen sayisi: 0
-- paylasilan kosum, veri sirasi orijinal --
satir 0: gecti
satir 1: dustu, gelen=5
satir 2: dustu, gelen=-3
dusen sayisi: 2
-- paylasilan kosum, ayni veri, sira tersine cevrildi --
satir 0: gecti
satir 1: dustu, gelen=-2
satir 2: dustu, gelen=-5
dusen sayisi: 2
```

`bagimsizKosum` üç satırın üçünü de geçiriyor. Bu beklenen sonuç, çünkü her satır kendi
`Depo`'sunu sıfırdan kuruyor — üçü de birbirinden habersiz, birbirinin durumuna dokunmuyor.
Gövde tek bir yerde yazıldı (döngünün gövdesi), ama üç kez çalıştı ve üç ayrı sonuç üretti.
Başarısızlık sayısı (`dusen sayisi`) ve her satırın kendi durumu (`satir i: gecti`/`dustu`)
ayrı ayrı günlüğe geçiyor — bir satır düştüğünde hangi satır olduğu ve dönen değer kayıtta
kalıyor, yalnız toplam bir sayı değil.

Bu, önceki dersteki "bir iddia" fikrinden farklı bir sayma biçimi. Orada bir test bir sonuç
üretiyordu; burada bir gövde N kez çağrılıyor ve N ayrı iddia doğuruyor. Üç satırlık bir veri
kümesi üç ayrı iddia demektir ve üçünün de bağımsız değerlendirilmesi gerekir — biri
düşerken diğer ikisinin geçmesi hâlâ anlamlı bir sonuçtur, çünkü her satır kendi girdisini
kendi başına sınıyor. Gövdenin bir kez yazılması burada bir kısaltmadır, kaynak metindeki
tekrarı azaltır; ama derlenmiş ya da koşan programda üç ayrı çağrı olduğu gerçeğini
değiştirmez. `VERI` dizisi ve `bagimsizKosum`/`paylasilanKosum` yöntemleri de önceki dersin
kurduğu ayrımın dışında değil — bunların hiçbiri üretim kodunda yazılı değil, ikisi de
yalnız test sınıf yolunda durur.

#### Paylaşılan Kurulum, Sıraya Bağlı Sonuç

- **TS13.** Başarısızlığın hangi satırdan geldiği burada bir liste elemanı olarak
  biriktirilir. Gerçek çerçevelerin ürettiği rapor biçimi (dosya adı, satır numarası,
  yığıt izi) burada kurulmaz; kayıt yalnız satır indeksi ve gelen değerdir.
- **TS14.** Paylaşılan durum burada döngünün dışına taşınan tek bir `Depo` örneğidir.
  Kurulumun döngünün neresinde çalıştığı — her turda mı, bir kez mi — satırların
  birbirinden bağımsız olup olmadığını belirler.
- **TS15.** Sıra bağımlılığı burada aynı veri kümesinin ters çevrilmiş bir kopyasıyla
  gösterilir. Gerçek bir test takımında satırların (ya da testlerin) çalışma sırası
  çoğunlukla belgelenmez ve koşumdan koşuma değişebilir; bu ders yalnız iki olası sırayı
  örnekliyor.

`paylasilanKosum` aynı üç satırı, aynı iddiayı taşıyor — tek fark, `Depo`'nun döngünün
**dışında**, yalnız bir kez kurulmasıdır. Bu, kurulumun her satır için yeniden çalışması
gerekirken yanlışlıkla yalnız ilk satır için çalıştığı bir hatayı modelliyor: `d` nesnesi
ilk satırın başlangıç değerinden kuruluyor, sonra her satırın `azalt` çağrısı **aynı**
nesne üzerinde birikiyor. Orijinal sırada ilk satır geçiyor — çünkü paylaşılan `Depo` tam
o satırın beklediği başlangıç değeriyle kuruldu — ama ikinci ve üçüncü satır artık kendi
beklediği başlangıç durumunu görmüyor, önceki satırların bıraktığı durumu görüyor ve
ikisi de düşüyor.

Sayıları takip etmek farkı netleştiriyor. Paylaşılan `Depo`, `VERI[0].baslangic()` olan
10 ile kuruluyor. Birinci satır `azalt(3)` çağırıyor, miktar 7 oluyor, beklenen de 7 —
geçiyor. İkinci satır kendi başlangıcı olan 5'i hiç görmüyor; onun yerine birincinin
bıraktığı 7'yi görüyor ve `azalt(2)` çağırınca miktar 5 oluyor, beklenen 3 olduğu için
düşüyor. Üçüncü satır da kendi başlangıcı olan 8'i değil, ikincinin bıraktığı 5'i görüyor;
`azalt(8)` çağırınca miktar -3'e düşüyor, beklenen 0'dan uzak. Her satırın kendi verisi
doğru, kendi hesabı doğru — yanlış olan, hangi durumdan başladıklarıdır.

Asıl gösterim son iki koşumda. Aynı üç veri satırı, yalnız sırası tersine çevrilerek
tekrar koşturuluyor. Sonuç yine "bir satır geçti, ikisi düştü" biçiminde — ama geçen satır
**değişti**. Orijinal sırada geçen satır, verinin ilk satırıydı (10 → 7); tersine çevrilmiş
sırada geçen satır, o sırada ilk gelen satırdır (8 → 0). Veri kümesinin kendisi değişmedi,
satırların hiçbirinin içeriği düzenlenmedi — yalnız hangi satırın **önce** işlendiği
değişti ve bu tek değişiklik hangi satırın geçtiğini baştan aşağı değiştirdi. Bir satırın
geçip geçmemesi artık o satırın kendi verisine değil, konumuna bağlı.

Tersine çevrilmiş sırada aynı izleme yapılabilir. Paylaşılan `Depo` bu kez `8` ile kuruluyor
— tersine çevrilmiş dizinin ilk elemanı olan üçüncü satırın başlangıcı. O satır `azalt(8)`
çağırıyor, miktar `0` oluyor, beklenen de `0` — geçiyor. Sırada ikinci satır olarak gelen
ikinci veri satırı kendi başlangıcı olan 5'i değil, önceki adımın bıraktığı 0'ı görüyor;
`azalt(2)` çağırınca miktar -2 oluyor, beklenen 3'ten uzak, düşüyor. Son olarak gelen
birinci veri satırı kendi başlangıcı olan 10'u değil, -2'yi görüyor; `azalt(3)` çağırınca
miktar -5 oluyor, beklenen 7'den uzak, düşüyor. İki koşumda da yalnızca **ilk işlenen**
satır geçiyor — çünkü paylaşılan `Depo` yalnız o satırın beklediği başlangıç değeriyle
kuruluyor — ve geriye kalanlar önceki satırların bıraktığı durumu devralıp düşüyor. Hangi
satırın "ilk işlenen" olduğu ise veri dizisinin sırasına bağlı, satırın kendi içeriğine
değil.

Bu, M21/K02 Birim Testi ve Test Odaklı Geliştirme kursunda kurulan kararsız test kavramının
somut bir kaynağıdır: testler arasında (ya da burada olduğu gibi, aynı testin satırları
arasında) paylaşılan durum, sonucu çalışma sırasına bağlı kılar. Kavramın kendisi orada
kuruldu; bu derste yeni olan, kaynağın **nerede** durduğudur — kurulumun döngü içinde mi
dışında mı çalıştığı, bildirilen veri kümesinin kendisinde görünmeyen bir karardır. İki
koşumun günlüğüne bakarak bu kararı geri çıkarmak mümkün değildir; yalnız veri sırası
değiştirilip yeniden koşturularak görünür hâle gelir.

Bu yüzden gerçek test çerçevelerinin çoğu, satırların ya da testlerin çalışma sırasını
**bilinçli olarak** karıştırır: iki ayrı koşumun her seferinde aynı sırayı vermesi,
paylaşılan durumdan doğan bir hatayı gizli tutar. Sıra her koşumda sabit kalırsa, bu ders
göstermiş olduğu ikinci koşumdaki gibi bir sapma hiç ortaya çıkmayabilir — takım "geçti"
der, ama yalnız çünkü sıra hep aynıdır. Sıra karıştırıldığında ise aynı hatalı kurulum
bazen bir satırı, bazen başka bir satırı geçirir ve toplam başarısızlık sayısı koşumdan
koşuma değişir görünebilir. Bu değişkenlik, hatanın belirtisidir — testin kendisinde değil,
testler arasında paylaşılan bir durumda aranması gerektiğinin işaretidir.

Buradan çıkan kural şudur: parametreleme bir gövdeyi çoğaltır, ama çoğalan koşumların
birbirinden **bağımsız** olduğunu garanti etmez. Bağımsızlığı garanti eden şey, her koşumun
kendi kurulumunu kendi başına çalıştırmasıdır — `bagimsizKosum`'da olduğu gibi. Kurulum bir
kez çalışıp N koşum arasında paylaşıldığında, veri kümesi ne kadar doğru yazılmış olursa
olsun, sonuç veri kümesinin **sırasına** bağlı hâle gelir. Bir satırın geçmesi artık o
satırın doğru olduğunu değil, o satırın konumunun uygun düştüğünü gösterir.

Hatanın kökeni bir aliaslama sorunudur: iki ayrı satırın iki ayrı `Depo` beklediği yerde,
tek bir `Depo` referansı ikisi arasında paylaşılıyor. Kaynak metinde bu paylaşım açıkça
görünmüyor — `d` adı döngünün her turunda aynı satırda geçiyor, değişen yalnız hangi
`Veri` kaydının işlendiği. Değişkenin **kapsamı** (döngünün dışı) ile **niyet edilen ömrü**
(tek bir satır) burada birbirinden ayrılıyor ve bu ayrılığı kaynak metinden görmek
mümkün değil; yalnız birden çok satırla, birden çok sırayla koşturarak görülüyor. Bu
gözlem, kursun ölçü sorusunu test verisi düzeyine indiriyor: bir kurulumun "hangi kapsamda"
çalıştığı da, tıpkı bir tipin hangi sınıf yolunda durduğu gibi, kaynak metinde açıkça
yazılmayan ama sonucu belirleyen bir karardır.

Kurulumu döngü dışına almanın bir bedeli olmadığını düşünmek de yanıltıcı olurdu — tam
tersi, o taşımanın kendisi genellikle bir kazanç aramanın sonucudur. `bagimsizKosum` her
satır için yeni bir `Depo` kuruyor; bu, N satırlık bir veri kümesinde N ayrı kurulum çağrısı
demektir. Kurulum ağır bir işse — gerçek bir bağlantı açmak, büyük bir yapı kurmak — bu
tekrar maliyetli görünebilir ve kurulumu bir kez yapıp paylaşmak cazip gelir. Bu ders o
cazibenin bedelini gösteriyor: paylaşılan kurulumun kazandırdığı hız, satırlar arasındaki
bağımsızlığı kaybettirdiği için ödenmiş oluyor. İki özelliği aynı anda istemek — hem
paylaşılan hem bağımsız bir kurulum — çelişkilidir; hangisinin öncelikli olduğu, gövdeyi
yazan kişinin kararındadır ve bu ders yalnız o kararın sonucunu ölçüyor.

Bu seçimin doğru yanıtı yoktur, yalnız bilinçli ve bilinçsiz biçimleri vardır. Bir gövde
kurulumu bilinçli olarak paylaşıyorsa — örneğin satırların ortak, değişmeyen bir kaynağı
okuduğu, hiçbirinin yazmadığı durumlarda — paylaşım bir kusur değildir. Kusur, paylaşımın
**yazma** içeren bir kurulumda, farkında olunmadan gerçekleşmesindedir. Bu dersin
`paylasilanKosum` yöntemi ikinci durumu modelliyor: kurulum döngü dışına taşınırken hangi
satırın hangi durumu göreceği hiç düşünülmemiş, yalnız tekrarı azaltmak hedeflenmiştir.

#### Özet

- Veri kümesi bir bildirim olarak yazıldı, gövde bir kez kuruldu ve veri sayısınca — üç
  kez — çağrıldı; her çağrı kendi satırının sonucunu kaydetti.
- Bağımsız kurulumla üç satırın üçü de geçti; başarısızlık sayısı ve hangi satırdan
  geldiği ayrı ayrı günlüğe geçti.
- Kurulum döngünün dışına taşınıp tek bir `Depo` satırlar arasında paylaşıldığında iki
  satır düştü, ama hangi satırın geçtiği veri sırasına bağlı hâle geldi.
- Aynı üç satır ters sırayla koşturulduğunda geçen satır değişti; veri içeriği aynı kaldı,
  yalnız işlenme sırası değişti.
- Paylaşılan durumun sonucu sıraya bağlı kılması, M21/K02'de kurulan kararsız test
  kavramının bu derste somutlaşan bir kaynağıdır.

#### Sonraki Adım

Bu derste paylaşılan olan şey bir test gövdesinin kendi kurduğu bir nesneydi — sınanan
kodun dışına, testin kendi düzenine ait bir hataydı. Sınanan kodun kendisi (`Depo`) hiç
değişmedi; değişen yalnız test tarafının kurulumuydu. Sıradaki ders paylaşılan durumu
sınanan kodun **dışına** taşıyor: birim testinde sahtelenen bir bağımlılık, tümleşim
kurulumunda gerçek gerçekleştirimiyle yerine konuyor ve aynı test gövdesi artık daha büyük
bir sınıf yoluna karşı koşuyor. Kusurun nereye taşındığı — birimde sessiz kalan bir sapma,
tümleşimde nasıl bir hataya dönüşüyor — sıradaki dersin sorusu.

### Tümleşim Testleri

Source: https://academia.sh/tr/kurslar/java-ekosistem/tumlesim-testleri

Birim kurulumunda sahtelenen tedarikçi burada gerçek gerçekleştirimiyle, gerçek gerçekleştirimin bağlı olduğu ikinci bir tiple birlikte koşum sınıf yoluna geri konuyor; yolun içerdiği tip sayısı bir tipten ikiye çıkıyor. Aynı test gövdesi iki kurulumda koşturuluyor: birimde sınır aşımı sessizce geçiyor, tümleşimde aynı istek koşum hatası veriyor.

Önceki derste paylaşılan olan, bir test gövdesinin kendi kurduğu bir nesneydi; sınanan kodun
kendisi hiç değişmedi. Bu ders paylaşılan durumu sınanan kodun **dışına** taşıyor: ikinci
derste sahtelenen `Tedarikci` bağımlılığı burada gerçek gerçekleştirimiyle geri geliyor ve
gerçek gerçekleştirimin kendi bağlı olduğu ikinci bir tip de sınıf yoluna eklenmiş oluyor.
Soru şu: aynı test gövdesi, içeriği büyüyen bir sınıf yoluna karşı koşturulduğunda, önceden
sessiz kalan bir sapma nereye düşüyor?

Dördüncü dersin ekseni, ikinci dersle aynı `Tedarikci` adı üzerinden ilerlese de farklı bir
soruya yanıt arıyor. İkinci ders "aynı ad, iki gövde" sorusunu sormuştu ve bir tipin
diğerinin yerine geçmesinin ne zaman sessiz, ne zaman koşum hatası verdiğini ölçmüştü. Bu
ders o sorunun bir adım gerisine gidiyor: gerçekleştirimlerden biri, ötekinin taşımadığı
bir bağımlılık taşıdığında, sınıf yolunun kendisi nasıl değişiyor ve o değişiklik hangi
kusuru görünür kılıyor?

Tümleşim testinin konu alanı — test ortamı yönetimi, dış servislerin sahtelenmesi — M21/K03
Tümleşim, Sözleşme ve Uçtan Uca Test kursunda kuruldu ve burada yeniden kurulmaz. Bu dersin
eklediği tek soru, testin gördüğü sınıf yolunun **büyüklüğü** ile kusurun düştüğü yer
arasındaki ilişkidir.

Birim ve tümleşim kurulumları arasındaki fark çoğunlukla kurulum süresi ya da dış sistemlere
erişim üzerinden anlatılır — biri hızlı ve yalıtılmış, öteki yavaş ve gerçek. Bu ders aynı
farkı sınıf yolu üzerinden okuyor: iki kurulum arasındaki gerçek fark, testin **derleme
zamanında hiç görmediği** ama **koşum zamanında karşılaştığı** tip kümesidir. Kurulumu
"birim" ya da "tümleşim" yapan şey bir etiket değil, o koşumda sınıf yoluna hangi tiplerin
konduğudur.

#### Aynı Bağımlılık, Gerçek Gerçekleştirimiyle

- **TS16.** Dış sistem burada `Envanter` adlı, gerçek dünyadaki bir stok sınırını
  modelleyen sıradan bir Java sınıfı olarak kurulur. Hiçbir ürün, sürücü ya da protokol adı
  yazılmaz; gerçek bir ağ çağrısı, dosya sistemine erişim ya da dış süreç de yapılmaz.
- **TS17.** "Yolun içerdiği tip sayısı" burada derlenmiş `.class` dosyalarının sayımıdır —
  ilk derste kurulan ölçüm yöntemiyle aynı.

İkinci derste `Tedarikci` adlı bir tip, istenen miktarın tamamını karşılayan bir üretim
gerçekleştirimiyle ve isteneni bir eksik karşılayan bir sahte gerçekleştirimle vardı; test
ikisine de derleme zamanında bağlı değildi, yalnız adı yansımayla arıyordu. Bu ders aynı adı
tekrar kullanıyor, ama gerçek gerçekleştirimi bu kez **yalnız başına** değil, kendi bağlı
olduğu bir sınır denetimiyle birlikte kuruyor: `Envanter.sinirDenetle`, istenen miktar
100'ü aştığında bir istisna fırlatıyor. Birim kurulumundaki sahte gerçekleştirim böyle bir
denetim taşımıyor — her isteği sessizce karşılıyor.

Bu iki gerçekleştirimin farkı bilinçli bir tasarım kararı. Birim kurulumu bir sınır
denetimi taşımıyor, çünkü birim testinin amacı `Tedarikci`'yi çağıran kodun akışını
sınamaktır — sınırın kendisi `Envanter`'in sorumluluğundadır ve `Envanter`'in kendi testi
ayrı yazılır. Tümleşim kurulumu ise iki tipi birlikte çalıştırarak, aralarındaki gerçek
etkileşimi sınıyor: `Tedarikci` çağrıyı `Envanter`'e gerçekten iletiyor mu, `Envanter`'in
fırlattığı istisna gerçekten çağıranı buluyor mu? Bu soruların ikisi de birim kurulumunda
sorulamaz, çünkü `Envanter` orada hiç yok.

```java
// Buyuyen.java — tumlesim kurulumu kosum sinif yoluna gercek tipleri geri koyar, yolun icerdigi tip sayisi buyur
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.net.*;
import java.nio.file.*;
import java.util.*;
import java.util.spi.ToolProvider;

public class Buyuyen {

    static Path yaz(Path dizin, String ad, String kaynak) throws IOException {
        Files.createDirectories(dizin);
        Path p = dizin.resolve(ad + ".java");
        Files.writeString(p, kaynak);
        return p;
    }

    static boolean derle(Path cikti, List<Path> sinifYolu, List<Path> kaynaklar) throws IOException {
        Files.createDirectories(cikti);
        List<String> arg = new ArrayList<>(List.of("-d", cikti.toString()));
        if (!sinifYolu.isEmpty()) {
            StringJoiner sj = new StringJoiner(File.pathSeparator);
            for (Path p : sinifYolu) sj.add(p.toString());
            arg.add("-classpath");
            arg.add(sj.toString());
        }
        for (Path k : kaynaklar) arg.add(k.toString());
        ToolProvider derleyici = ToolProvider.findFirst("javac").orElseThrow();
        StringWriter sessizCikti = new StringWriter();
        return derleyici.run(new PrintWriter(sessizCikti), new PrintWriter(sessizCikti),
                arg.toArray(new String[0])) == 0;
    }

    static String kos(List<Path> sinifYolu, String anaSinif) {
        URL[] url = new URL[sinifYolu.size()];
        try {
            for (int i = 0; i < url.length; i++) url[i] = sinifYolu.get(i).toUri().toURL();
            try (URLClassLoader yukleyici = new URLClassLoader(url, Buyuyen.class.getClassLoader().getPlatformClassLoader())) {
                Class<?> sinif = Class.forName(anaSinif, true, yukleyici);
                sinif.getMethod("main", String[].class).invoke(null, (Object) new String[0]);
                return "tamam";
            }
        } catch (InvocationTargetException e) {
            return e.getCause().getClass().getSimpleName();
        } catch (Throwable t) {
            return t.getClass().getSimpleName();
        }
    }

    static long tipSayisi(Path dizin) throws IOException {
        try (var akis = Files.walk(dizin)) {
            return akis.filter(p -> p.toString().endsWith(".class")).count();
        }
    }

    public static void main(String[] args) throws Exception {
        Path kok = Files.createTempDirectory("buyuyen");
        Path kaynak = kok.resolve("kaynak");
        Path testD = kok.resolve("test"), birimD = kok.resolve("birim"), tumlesimD = kok.resolve("tumlesim");

        Path sTest = yaz(kaynak, "TumlesimTest",
                "import java.lang.reflect.InvocationTargetException;\n"
              + "import java.lang.reflect.Method;\n"
              + "public class TumlesimTest {\n"
              + "  public static void main(String[] a) throws Throwable {\n"
              + "    Class<?> c = Class.forName(\"Tedarikci\");\n"
              + "    Object nesne = c.getDeclaredConstructor().newInstance();\n"
              + "    Method m = c.getMethod(\"dolum\", int.class);\n"
              + "    int[] istekler = {30, 150};\n"
              + "    for (int istek : istekler) {\n"
              + "      try {\n"
              + "        int sonuc = (int) m.invoke(nesne, istek);\n"
              + "        System.out.println(\"istek \" + istek + \" -> sonuc \" + sonuc);\n"
              + "      } catch (InvocationTargetException e) {\n"
              + "        throw e.getCause();\n"
              + "      }\n"
              + "    }\n"
              + "    System.out.println(\"tum istekler tamamlandi\");\n"
              + "  }\n"
              + "}\n");

        Path sBirim = yaz(kaynak.resolve("birim"), "Tedarikci",
                "public class Tedarikci {\n"
              + "  public int dolum(int istenen) { return istenen; }\n"
              + "}\n");

        Path envDizin = kaynak.resolve("tumlesim");
        Path sEnvanter = yaz(envDizin, "Envanter",
                "public class Envanter {\n"
              + "  static void sinirDenetle(int istenen) {\n"
              + "    if (istenen > 100) throw new IllegalStateException(\"stok siniri asildi\");\n"
              + "  }\n"
              + "}\n");
        Path sGercek = yaz(envDizin, "Tedarikci",
                "public class Tedarikci {\n"
              + "  public int dolum(int istenen) { Envanter.sinirDenetle(istenen); return istenen; }\n"
              + "}\n");

        derle(testD, List.of(), List.of(sTest));
        derle(birimD, List.of(), List.of(sBirim));
        derle(tumlesimD, List.of(), List.of(sEnvanter, sGercek));

        System.out.println("-- yolun icerdigi tip sayisi --");
        System.out.println("birim kurulumu    : " + tipSayisi(birimD) + " tip");
        System.out.println("tumlesim kurulumu : " + tipSayisi(tumlesimD) + " tip");

        System.out.println("-- ayni test govdesi, iki kurulum --");
        System.out.println("birim kosum sonucu   : " + kos(List.of(testD, birimD), "TumlesimTest"));
        System.out.println("tumlesim kosum sonucu: " + kos(List.of(testD, tumlesimD), "TumlesimTest"));
    }
}
```

```
-- yolun icerdigi tip sayisi --
birim kurulumu    : 1 tip
tumlesim kurulumu : 2 tip
-- ayni test govdesi, iki kurulum --
istek 30 -> sonuc 30
istek 150 -> sonuc 150
tum istekler tamamlandi
birim kosum sonucu   : tamam
istek 30 -> sonuc 30
tumlesim kosum sonucu: IllegalStateException
```

Birim kurulumu tek bir `.class` dosyası taşıyor: sahte `Tedarikci`, başka hiçbir şeye bağlı
değil. Tümleşim kurulumu iki `.class` dosyası taşıyor: gerçek `Tedarikci` ve onun çağırdığı
`Envanter`. `TumlesimTest` üçüncü kez de aynı dosya, aynı bayt kodu — değişen yalnız hangi
dizinlerin sınıf yoluna verildiği. Yolun içeriği büyüyor çünkü gerçek gerçekleştirim, sahte
gerçekleştirimin hiç taşımadığı bir bağımlılığı kendisiyle birlikte getiriyor.

Bu büyüme, birinci derste sayılan büyümeden farklı bir yönde ilerliyor. Orada test sınıf
yolu, üretim sınıf yolunun **hiç tanımadığı** iki tip taşıyordu — çerçeve ve testin kendisi,
ikisi de üretim kodunun dışındaydı. Burada büyüyen taraf üretim tarafı: `Envanter`, üretim
kodunun **gerçek** bir parçasıdır, ama birim kurulumunda hiç sınıf yoluna girmedi. Bir sınıf
yolunun büyümesi tek bir anlama gelmiyor — kimi zaman test kendi araçlarını taşıdığı için
büyüyor, kimi zaman üretimin gerçek bir parçasını geri kattığı için. İkisi de aynı sayma
yöntemiyle — derlenmiş `.class` dosyalarını saymak — ölçülüyor, ama işaret ettikleri şey
ayrı.

#### Aynı Test Gövdesi, İki Kurulum, İki Kusur Sınıfı

- **TS18.** Aynı istek dizisi — `30` ve `150` — hem birim hem tümleşim kurulumuna karşı,
  test yeniden derlenmeden koşturulur.
- **TS19.** Testin kendisi burada bir iddia taşımıyor, yalnız çağrıları yapıp sonucu
  yazdırıyor. Bu, "sessiz" sonucu daha da keskinleştiriyor: bir iddia eklenseydi bile o
  iddia yalnız dönüş değerine bakardı, sınır ihlalinden habersiz kalırdı.
- **TS20.** Gerçek gerçekleştirimin attığı istisna işaretlenmemiş (unchecked) bir
  istisnadır. Yansımalı çağrı bunu `InvocationTargetException` içine sarar; test kaynağı
  bu sarmalayıcıyı açıp asıl nedeni yeniden fırlatır, aksi hâlde harness yalnız sarmalayıcı
  sınıfının adını görürdü.

İki istek de aynı sırayla gönderiliyor: önce 30, sonra 150. Birim kurulumunda ikisi de
sorunsuz dönüyor — sahte gerçekleştirim hiçbir sınır tanımıyor, "istek 150 -> sonuc 150"
satırı diğerleriyle aynı biçimde yazılıyor ve program "tum istekler tamamlandi" diyerek
bitiyor. Sınırın var olduğu bilgisi hiçbir yerde görünmüyor; test geçti, çünkü test zaten
sınırdan habersizdi.

Tümleşim kurulumunda ilk istek (30) aynı şekilde dönüyor, ama ikinci istek (150) `Envanter`
sınırını aşıyor ve `Tedarikci.dolum` bir istisna fırlatıyor. Bu istisna testin kendi
kaynağında yakalanıp yeniden fırlatılıyor, sonra koşumu üstlenen yardımcı bunu yakalayıp
adını raporluyor: `IllegalStateException`. Aynı iki istek, aynı sırayla, aynı test dosyası
— fark yalnız hangi `Tedarikci`'nin ve hangi ek bağımlılığın sınıf yolunda durduğu.

Bu istisnanın testin kendi kaynağından çıkışı iki katmanlıdır ve bu ders bilerek bu iki
katmanı ayırıyor. `Tedarikci.dolum` bir `IllegalStateException` fırlatıyor; test bunu
`m.invoke(nesne, istek)` çağrısıyla yansıma üzerinden tetiklediği için, yansımanın kendisi
bu istisnayı önce bir `InvocationTargetException`'ın içine sarıyor. Test kaynağı bu
sarmalayıcıyı `catch (InvocationTargetException e)` ile yakalayıp `throw e.getCause()`
diyerek asıl istisnayı açığa çıkarıyor ve yeniden fırlatıyor. Bu adım atlansaydı, koşumu
üstlenen yardımcı yalnız "InvocationTargetException" adını raporlardı — doğru ama az şey
söyleyen bir isim. Sarmalayıcının testin kendi kaynağında açılması, raporlanan adın
`IllegalStateException` olmasını, yani neyin yanlış gittiğinin bir adım daha görünür
kalmasını sağlıyor.

Bu iki sonuç, kursun sınırlayıcı okumasının test konusundaki karşılığı: gerçek tipi geri
koymak testi **daha doğru** yapmıyor, yalnız **başka bir kusur sınıfını** görünür kılıyor.
Birim kurulumu zaten geçerli bir kurulumdu — hızlı, yalıtılmış, gerçek bir sınırın varlığından
bağımsız. Tümleşim kurulumu onu yanlışlamıyor, üstüne bir katman ekliyor: gerçek
gerçekleştirimin taşıdığı kısıtları da sınıf yoluna dahil ediyor. Birim kurulumunda "sessiz"
kalan sapma — sınırı aşan bir isteğin fark edilmeden geçmesi — burada bir istisnaya, yani
"koşum" kusuruna dönüşüyor. İkisi de aynı iki isteği, aynı sırayla işledi; aralarındaki
fark testin yazdığı iddialarda değil, testin koştuğu sınıf yolunun içeriğindeydi. İki
kurulum da doğru kurulmuş olabilir; ayırdıkları şey aynı
girdiye verdikleri yanıt değil, o girdinin **hangi gerçekliğe** karşı sınandığıdır.

Bu ayrımın pratik sonucu, hangi kurulumun "yeterli" olduğuna dair tek bir yanıt olmadığıdır.
Birim kurulumu 150 gibi bir isteğin normal akışta işlenip işlenmediğini hızlıca doğrular;
`Envanter`'in kendisi değiştiğinde ya da hiç var olmadığında bile çalışmaya devam eder,
çünkü ona hiç bağlı değildir. Tümleşim kurulumu ise `Envanter`'in gerçekten çağrıldığını ve
gerçekten bir sınırı uyguladığını doğrular, ama bunun bedeli sınıf yoluna ikinci bir tipi
dahil etmek ve o tipin davranışına bağımlı hâle gelmektir. Hangi sorunun sorulduğu, hangi
kurulumun seçileceğini belirliyor; ikisi de aynı `Tedarikci` adını taşıyan farklı
gerçeklikleri sınıyor.

İki kurulumun birbirinin yerini almadığını, ikisinin de gerekli kaldığını vurgulamak
gerekiyor. Birim kurulumu hâlâ
değerli: `Envanter` gibi ağır ya da yavaş bir bağımlılık her koşumda gerçekten kurulsaydı,
`Tedarikci`'yi çağıran kodun kendi mantığını sınamak her seferinde o bağımlılığın kurulma
maliyetini de taşırdı. Birim kurulumu bu maliyeti tamamen ortadan kaldırıyor ve karşılığında
yalnız `Envanter`'in var olmadığı bir dünyayı sınıyor. Tümleşim kurulumu ise o maliyeti
göze alıp iki tipin birlikte nasıl davrandığını gerçekten koşturuyor. Bir test takımı
genellikle ikisini bir arada tutar: çok sayıda birim kurulumu her değişiklikte hızlıca
koşar, daha az sayıda tümleşim kurulumu ise gerçek bağımlılıkların bir arada çalıştığını
daha seyrek doğrular. Bu dersin gösterdiği, iki katmanın **aynı davranışı iki kez
sınamadığı** — her biri kendi göremediği bir kusur sınıfını taşıdığıdır.

Bu noktada sınıf yolu ölçüsü somut bir karar aracına dönüşüyor: bir kurulumun "birim" mi
"tümleşim" mi olduğunu tartışmak yerine, o kurulumun sınıf yoluna hangi tiplerin girdiğine
bakmak yeterli. `Envanter` sınıf yolundaysa, `Envanter`'in davranışı sınanıyor demektir;
sınıf yolunda değilse, sınanmıyor demektir — aradaki fark bir niyet beyanı değil, derlenmiş
bir gerçektir ve `tipSayisi` gibi bir sayımla doğrudan okunabilir.

Bu ölçü aynı zamanda bir uyarı taşıyor: sınıf yoluna eklenen her gerçek tip, testin
gördüğü dünyayı üretimin gördüğü dünyaya biraz daha yaklaştırıyor, ama hiçbir zaman
tamamen eşitlemiyor. `Envanter`'in kendisi de başka bir şeye bağlı olabilirdi — bu derste
öyle kurulmadı, sınırın kendisi kendi içinde tam. Gerçek bir sistemde tümleşim kurulumu da
kendi sınırında durur; bir noktadan sonra sınanan sistemin tamamını ayağa kaldırmak
tümleşim testinin değil, uçtan uca testin işidir ve o sınır M21/K03'ün konusudur.

#### Özet

- Birim kurulumu tek bir tip taşıdı, tümleşim kurulumu gerçek gerçekleştirimle birlikte
  onun bağlı olduğu ikinci bir tipi de taşıdı; yolun içeriği bire karşı ikiye çıktı.
- Aynı derlenmiş test gövdesi, aynı iki istekle, iki ayrı koşum sınıf yoluna karşı yeniden
  derlenmeden koşturuldu.
- Birim kurulumunda sınırı aşan istek sessizce geçti ve test "tamamlandı" dedi; hiçbir
  yerde bir uyarı çıkmadı.
- Tümleşim kurulumunda aynı istek `IllegalStateException` fırlattı; kusur artık sessiz
  değil, koşum hatasıydı.
- Gerçek gerçekleştirimi geri koymak testi daha doğru yapmadı, yalnız birim kurulumunda
  görünmeyen bir kusur sınıfını görünür kıldı.

#### Sonraki Adım

Bu derste test bir sayı gönderdi ve dönen değeri okudu; iddia gövdenin içinde, düzyazı
olmayan bir biçimdeydi. Bazı testler ise gövdeye hiç düzyazı olmayan bir kodla değil,
senaryo dilinde yazılmış bir metinle yaklaşır — adımları sıradan cümlelerle anlatır ve o
cümlelerin bir karşılığı olmasını bekler. Bu, testin gördüğü sınıf yolu sorusunu bir kez
daha başka bir yöne taşıyor: artık iki kod tabanı arasındaki bir uyumsuzluk değil, düzyazı
bir metin ile o metnin çalıştırılabilir karşılığı arasındaki uyumsuzluk aranıyor. Sıradaki
ders böyle bir metni bir bildirim olarak ele alıyor ve sorusu şu: metindeki her adımın
çalıştırılabilir bir karşılığı var mı ve bazı adımların bu karşılığı bulunamadığında
sessizce mi atlandığı.

### Davranış Odaklı Test

Source: https://academia.sh/tr/kurslar/java-ekosistem/davranis-odakli-test

Dört satırlık bir senaryo metni bir adım haritasına karşı çalıştırılıyor; haritada karşılığı olmayan adım bir koşumda sessizce atlanıyor, başka bir koşumda programı durduruyor. Aynı dört adım tam eşleştiğinde bile, kontrol adımlarının iddiası kaldırılmış bir haritayla koşturulunca yanlış bir değer üzerinden yine 'geçti' diyor.

Önceki derste sınanan sistem büyüdü: birim kurulumunda sahtelenen bir bağımlılık gerçek
gerçekleştirimiyle geri geldi ve testin gördüğü sınıf yolu iki tipe çıktı. Bu derste
büyüyen taraf farklı: sınanan kod değil, testin **kendisi** düzyazıya yaklaşıyor. Bir
senaryo, sıradan cümlelerle yazılmış bir adım listesidir — "depoya 10 birim eklendi",
"depo miktarı 7 olmalı" gibi — ve bu cümlelerin adım haritasında çalıştırılabilir bir
karşılığı olması beklenir. Soru şu: metindeki dört adımın kaçının gerçek bir karşılığı
var, karşılığı olmayan adım koşumu nasıl etkiliyor ve adımların tümü eşleşse bile bu
senaryonun **doğru** çalıştığı anlamına mı geliyor?

Senaryo dilinin çekiciliği, teknik olmayan bir okurun da adımları anlayabilmesindedir —
"depoya 10 birim eklendi" cümlesi Java bilmeyen biri için de bir anlam taşır. Ama bu
okunabilirlik bir bedel taşır: düzyazı, derleyicinin denetleyebileceği bir sözdizimi
değildir. Bir yöntem adı yanlış yazıldığında derleyici hemen durur; bir senaryo satırı
yanlış yazıldığında ya da hiç uygulanmadığında, bunu fark eden yalnız koşumun kendisidir.
Bu ders, o farkın nasıl fark edildiğini ve fark edilmemesinin ne anlama geldiğini
koşturarak gösteriyor.

Davranış odaklı geliştirme kavramı M21/K02 Birim Testi ve Test Odaklı Geliştirme kursunda
kuruldu ve burada yeniden tanıtılmaz. Bu dersin eklediği tek soru, senaryo metninin
bildirdiği adımlar ile adım haritasında gerçekten bulunan adımlar arasındaki **eşleşmedir**.

Bu soru, aslında dersin başından beri sorduğumuz sorunun bir varyasyonu. Birinci derste
soru "bu tip test sınıf yolunda var mı" idi ve tip somut, derlenmiş bir `.class` dosyasıydı.
Beşinci derste tip yerini bir cümleye bırakıyor: senaryo metnindeki her satır, adım
haritasında kendisini karşılayacak bir davranışa ihtiyaç duyuyor. Fark bir adlandırma
sorunundan ibaret değil — bir tipin sınıf yolunda bulunup bulunmadığını derleyici bize
söyler, ama bir metin satırının bir davranışla eşleştiğini kimse bize söylemez; bu ders o
eşleşmeyi elle kurup elle sayıyor.

#### Senaryo Bir Bildirimdir, Adım Haritası Karşılığını Verir

- **TS21.** Senaryo adımları burada düz dizeler olarak eşleştirilir (`Map<String, ...>`).
  Gerçek çerçevelerin örüntü eşleme, düzenli ifadeyle adım tanımlama ve parametre çıkarma
  mekanizması burada kurulmaz.
- **TS22.** Bir adımın "eşleşmesi", metninin haritada birebir bir anahtarla bulunmasıdır.
  Bu, adımın **doğru** çalıştığını değil, yalnız çalıştırılabilir bir karşılığı olduğunu
  gösterir — ikisi ayrı sorulardır.
- **TS23.** "Sessiz" ve "durduran" iki ayrı koşum stratejisi burada elle yazılır. Gerçek
  çerçevelerin bu ikisi arasında hangisini varsayılan seçtiği ya da nasıl yapılandırıldığı
  burada modellenmez.

Senaryo metni dört satırdan oluşuyor ve bir `record` dizisi olarak bildiriliyor — üçüncü
derste veri kümesinin bildirilme biçimiyle aynı yöntem, burada veri yerine düzyazı adımlar
taşıyor. Adım haritası ise her satırın metnini bir çalıştırılabilir gövdeye bağlıyor.
Aşağıdaki `eksikHarita` dördüncü adımın metnini hiç içermiyor — bilinçli bir eksiklik.
`sessizKosum` karşılığı bulunamayan adımı atlayıp devam ediyor, `durduranKosum` aynı
durumda bir istisna fırlatıp koşumu kesiyor.

Haritanın kendisi bir `LinkedHashMap` — sıradan bir eşleme değil, eklenme sırasını koruyan
bir eşleme. Bu, senaryonun kendi sırasını değil, haritanın **kayıt** sırasını koruyor;
adımların hangi sırayla çalıştığına haritanın kendisi karar vermiyor, o kararı `METIN`
dizisindeki sıra veriyor. Sıra korumasının burada tek pratik faydası, iki haritayı yan
yana okurken adımların hangi sırayla tanımlandığını görebilmek — koşumun kendisi `METIN`
dizisini baştan sona dolaşıyor, haritanın iç sırasına bakmıyor.

Adım haritasının kendisi, ilk derste kurulan test sınıf yolu kavramının doğal bir uzantısı.
`eksikHarita` ve `bosIddialiHarita` üretim kodunun hiçbir yerinde geçmiyor; ikisi de yalnız
test tarafının bildiği, test tarafının koştuğu birer eşleme. Senaryo metninin kendisi de
öyle — `METIN` dizisi üretim kodunun okuduğu bir veri değil, testin okuduğu bir bildirim.
Bu derste yeni olan, bu bildirimin **düzyazı** biçiminde durması ve karşılığının bir tip
adı değil bir dize anahtarı olmasıdır.

```java
// Senaryo.java — senaryo metni bir bildirimdir, adim haritasinda karsiligi bulunamayan adim sessiz mi kaliyor durduruyor mu
import java.util.*;
import java.util.function.Consumer;

public class Senaryo {

    record Adim(String metin) {}

    static final Adim[] METIN = {
        new Adim("depoya 10 birim eklendi"),
        new Adim("depodan 3 birim azaltildi"),
        new Adim("depo miktari 7 olmali"),
        new Adim("depo miktari eksiye dusmemeli"),
    };

    // -- adim haritasi eksik: dorduncu adimin hicbir kaydi yok --------------------
    static Map<String, Consumer<int[]>> eksikHarita() {
        Map<String, Consumer<int[]>> h = new LinkedHashMap<>();
        h.put("depoya 10 birim eklendi", d -> d[0] += 10);
        h.put("depodan 3 birim azaltildi", d -> d[0] -= 3);
        h.put("depo miktari 7 olmali", d -> { if (d[0] != 7) throw new AssertionError("miktar 7 degil: " + d[0]); });
        return h;
    }

    // -- adim haritasi tam: dordu de kayitli, ama kontrol adimlarinin iddiasi kaldirilmis --
    static Map<String, Consumer<int[]>> bosIddialiHarita() {
        Map<String, Consumer<int[]>> h = new LinkedHashMap<>();
        h.put("depoya 10 birim eklendi", d -> d[0] += 10);
        h.put("depodan 3 birim azaltildi", d -> d[0] -= 2); // kasitli hata: 3 degil 2 azaltiyor
        h.put("depo miktari 7 olmali", d -> {});            // iddia kaldirilmis
        h.put("depo miktari eksiye dusmemeli", d -> {});    // iddia kaldirilmis
        return h;
    }

    static void sessizKosum(Adim[] senaryo, Map<String, Consumer<int[]>> harita, int[] durum) {
        int eslesen = 0;
        for (Adim a : senaryo) {
            Consumer<int[]> uygulama = harita.get(a.metin());
            if (uygulama == null) { System.out.println("atlaniyor    : " + a.metin()); continue; }
            uygulama.accept(durum);
            eslesen++;
        }
        System.out.println("eslesen adim : " + eslesen + "/" + senaryo.length);
        System.out.println("sonuc        : gecti, depo=" + durum[0]);
    }

    static void durduranKosum(Adim[] senaryo, Map<String, Consumer<int[]>> harita, int[] durum) {
        int eslesen = 0;
        for (Adim a : senaryo) {
            Consumer<int[]> uygulama = harita.get(a.metin());
            if (uygulama == null) throw new IllegalStateException("adimin karsiligi yok: " + a.metin());
            uygulama.accept(durum);
            eslesen++;
        }
        System.out.println("eslesen adim : " + eslesen + "/" + senaryo.length);
        System.out.println("sonuc        : gecti, depo=" + durum[0]);
    }

    public static void main(String[] args) {
        System.out.println("-- eksik harita, sessiz kosum --");
        sessizKosum(METIN, eksikHarita(), new int[]{0});

        System.out.println("-- eksik harita, durduran kosum --");
        try {
            durduranKosum(METIN, eksikHarita(), new int[]{0});
        } catch (IllegalStateException e) {
            System.out.println("durdu        : " + e.getMessage());
        }

        System.out.println("-- tam harita, iddiasi kaldirilmis kontrol adimlari --");
        sessizKosum(METIN, bosIddialiHarita(), new int[]{0});
    }
}
```

```
-- eksik harita, sessiz kosum --
atlaniyor    : depo miktari eksiye dusmemeli
eslesen adim : 3/4
sonuc        : gecti, depo=7
-- eksik harita, durduran kosum --
durdu        : adimin karsiligi yok: depo miktari eksiye dusmemeli
-- tam harita, iddiasi kaldirilmis kontrol adimlari --
eslesen adim : 4/4
sonuc        : gecti, depo=8
```

İlk iki koşum aynı `eksikHarita`'yı, aynı dört satırlık senaryoyu kullanıyor; fark yalnız
karşılığı bulunamayan adıma verilen tepkide. `sessizKosum` dördüncü adımı atlıyor, üç
adımı çalıştırıyor, "3/4" diyor ve "gecti" ile bitiriyor — senaryo metninde dört satır
olduğu hâlde, koşum yalnız üçünü gerçekten işletti. `durduranKosum` ise aynı eksikliği bir
istisnaya çeviriyor: hangi adımın metninin karşılıksız kaldığı doğrudan hata mesajında
görünüyor ve koşum orada duruyor, kalan adımlar hiç işletilmiyor.

Bu iki strateji arasındaki fark, kursun daha önceki derslerinde görülen sessiz/koşum
ayrımının senaryo metni düzeyindeki karşılığı. Sessiz strateji, eksik bir adımı fark
edilmeden geçiştiriyor — üç adımlık bir senaryo, dört adımlık bir senaryo gibi raporlanıyor,
"3/4" satırı okunmazsa hiç görünmüyor. Durduran strateji ise eksikliği bir koşum hatasına
çeviriyor ve neyin eksik olduğunu adının içine yazıyor. Hiçbiri "doğru" strateji değil;
biri yazım hatası yapılmış bir adımı erken yakalar, öteki senaryonun geri kalanının hâlâ
çalışabildiğini görmeyi sağlar. Seçim, senaryo metnini kimin yazdığına ve o metnin ne
sıklıkla değiştiğine bağlıdır.

Sessiz stratejinin riski, "3/4" satırının gözden kaçmasının hiç zor olmamasıdır. Bir senaryo takımı
onlarca satırlık metinler biriktirdiğinde, her koşumun ürettiği eşleşme oranını tek tek
okumak gerçekçi değildir; çoğu rapor yalnız "geçti" ya da "düştü" özetini gösterir, eşleşme
sayısını göstermez. Bu durumda sessiz strateji, hiç yazılmamış bir adımı hiç fark
edilmeden aylarca taşıyabilir — senaryo metni bir davranışı bildiriyor gibi görünür, ama o
davranış hiçbir zaman gerçekten koşmamıştır. Durduran strateji bu riski ortadan kaldırır,
ama karşılığında senaryo metnindeki her küçük yazım farkını (eksik bir boşluk, değişen bir
sayı) bir koşum hatasına çevirir; metin sık değişiyorsa bu, gerçek kusurlardan çok metin
bakımı hatalarını raporlamaya başlar. İki stratejinin birlikte var olması bu yüzden
tesadüfi değil: hangi hatanın erken yakalanması, hangisinin görmezden gelinebilir olduğu bir
mühendislik kararıdır ve senaryo metninin kimin tarafından, ne sıklıkla yazıldığına bağlıdır.

#### Adım Sayısının Tam Olması Doğruluğu Göstermez

- **TS24.** Kasıtlı hata — azaltmanın 3 yerine 2 uygulanması — yalnız sınırlayıcı ölçümü
  göstermek için üçüncü koşumda tanıtılır. İlk iki koşum doğru işleyen bir azaltma
  kullanır; üçünün karşılaştırılabilir olması için yalnız tek bir değişken değiştirilir.
- **TS25.** "İddiası kaldırılmış" adım burada boş bir gövdeyle (`d -> {}`) modellenir.
  Adımın haritada bir karşılığı vardır ve eşleşme sayılır, yalnız o karşılık hiçbir şeyi
  denetlemez.

Üçüncü koşum ilk ikisinden iki noktada ayrılıyor. Birincisi, `bosIddialiHarita` dört
adımın dördünü de içeriyor — hiçbir adım atlanmıyor, hiçbir istisna fırlamıyor, eşleşen
adım sayısı "4/4". İkincisi, bu haritadaki azaltma adımı kasıtlı olarak yanlış: 3 yerine
2 azaltıyor. Doğru işleseydi depo miktarı 7 olurdu; bu koşumda 8 kalıyor. `eksikHarita`
kullanan ilk koşumda "depo miktari 7 olmali" adımı gerçek bir kontrol taşıyordu ve yanlış
bir değeri yakalardı. `bosIddialiHarita`'da aynı metin haritada duruyor, eşleşiyor, ama
gövdesi boş — hiçbir karşılaştırma yapmıyor. Sonuç satırı yine "gecti, depo=8" diyor;
"gecti" kelimesi burada yalnızca istisna fırlamadığı anlamına geliyor, değerin doğru
olduğu anlamına gelmiyor.

Bu üçüncü koşumun ilk iki koşumla aynı senaryo metnini paylaşması bilinçli bir tercih.
Metin hiç değişmedi — dört satır, aynı sırayla, aynı kelimelerle. Değişen yalnız o metnin
arkasındaki uygulamadır: bir sürümde üçüncü adım gerçek bir sayı karşılaştırması taşıyordu,
diğerinde aynı adım hiçbir şey yapmıyor. Senaryo metnini okuyan biri bu farkı göremez —
metin ikisinde de birebir aynı satırı taşıyor. Farkı görmenin tek yolu, adım haritasının
kendisini okumak ya da koşturup gerçek değeri gözlemlemektir; metnin kendisi hiçbir zaman
arkasındaki uygulamanın ne yaptığını garanti etmez.

Bu, kursun beşinci konusunun kapanış gözlemi: eşleşen adım sayısının tam olması —
senaryonun her satırının bir karşılığı olması — senaryonun **doğru çalıştığını**
göstermez. Bir metnin her adımının adım haritasında bir karşılığı bulunması, o karşılığın
içinde gerçekten bir iddia yazılı olduğu anlamına gelmiyor; adım haritasındaki bir giriş,
boş bir gövdeyle de tamamen geçerli bir eşleşme sayılıyor. "4/4 eşleşti" ile "senaryo
doğru çalıştı" iki ayrı önermedir ve bu ders ikisini aynı koşumda birbirinden ayırarak
gösteriyor: sayı tamken bile bir hata sessizce geçebiliyor.

Bu üç koşumu yan yana koyunca kursun ölçü sorusunun son biçimi ortaya çıkıyor. Önceki
derslerde soru "bu tip hangi sınıf yolunda bulunmak zorunda" biçimindeydi ve tip somut bir
`.class` dosyasıydı. Burada "tip" yerine bir **adım** duruyor, "sınıf yolu" yerine bir
**adım haritası** duruyor, ama soru aynı kalıba oturuyor: bu adımın adım haritasında bir
karşılığı var mı ve o karşılık gerçekten bir şey mi denetliyor. Senaryo metni bir
bildirimdir; adım haritası ise o bildirimin çalıştırılabilir karşılığıdır ve ikisi arasında
bir boşluk kaldığında — ister eksik bir kayıt, ister boş bir gövde olarak — o boşluk
metinden okunarak değil, yalnız koşturarak görülür.

Bu konunun beş dersi boyunca aynı soru beş ayrı biçimde soruldu. Birinci derste soru bir
çerçeve tipinin test sınıf yolunda bulunup bulunmadığıydı; ikincisinde aynı adı taşıyan
iki gerçekleştirimden hangisinin koşum sınıf yolunda durduğuydu; üçüncüsünde bir kurulumun hangi
kapsamda çalıştığıydı; dördüncüsünde gerçek bir bağımlılığın sınıf yoluna girip
girmediğiydi. Bu son derste soru, bir davranışın adım haritasında bir karşılığı olup
olmadığına indirgendi. Beşinde de ortak olan, sorunun kaynak metinden değil, gerçekten
koşturarak yanıtlanmasıydı — bir tipin, bir kurulumun ya da bir adımın ne yaptığı, yazıldığı
biçimden değil, çalıştırıldığında ürettiği sonuçtan okunuyordu.

Beş dersin de paylaştığı ikinci bir örüntü, "tam" görünen bir sonucun yeterlilik anlamına
gelmemesiydi. Birinci derste üretim ve test tiplerini taşıyan, tam görünen bir sınıf yolu çerçeve
tipi eksik olduğu için çöküyordu; ikincide hatasız tamamlanan bir koşum yanlış bir sayı taşıyordu;
üçüncüde geçen bir satır yalnız sırası uygun düştüğü için geçiyordu; dördüncüde sessiz bir koşum
bir sınır ihlalini hiç görmüyordu. Bu son ders aynı örüntüyü adım sayısı üzerinden tekrarladı:
"4/4" görünen bir senaryo, arkasında hiçbir gerçek kontrol taşımayabiliyordu. Bu tekrar tesadüfi
değil — kursun tezinin kendisi budur: derlenmiş, koşan, "tamam" diyen bir program, hangi soruya
yanıt verdiği söylenmeden okunamaz.

#### Özet

- Senaryo metni dört adımlık bir bildirim olarak yazıldı; adım haritası bu adımların
  çalıştırılabilir karşılıklarını verdi.
- Karşılığı bulunamayan bir adım bir koşumda sessizce atlandı (3/4 eşleşti), başka bir
  koşumda bir istisnayla koşumu durdurdu.
- Dört adımın dördü de eşleştiğinde ("4/4"), bu tek başına senaryonun doğru çalıştığını
  göstermedi.
- Kontrol adımlarının iddiası kaldırılmış bir haritayla koşulan senaryo, yanlış bir değer
  (8, beklenen 7) üzerinden yine "gecti" dedi.
- Davranış odaklı geliştirme kavramı M21/K02'de kuruldu; bu derste eklenen tek soru,
  senaryo metni ile adım haritasındaki karşılığı arasındaki eşleşmeydi.

#### Sonraki Adım

Bu konunun beş dersinde testin gördüğü sınıf yolunu biz kurduk: hangi tipin, hangi adımın
o yolda duracağını baştan biliyorduk, çünkü hepsini kendi ellerimizle yazdık — sahte
tipin adını da, adım haritasının anahtarlarını da testi yazan taraf belirledi. Sıradaki
konu bunun tam tersini soruyor. Bir günlükleme çağrısı, kendisini karşılayacak
gerçekleştirimin adını hiç yazmaz — kaynak metinde yalnız bir cepheye seslenir, hangi
sınıfın o çağrıyı karşılayacağını belirtmez. O adın koşum sınıf yoluna nasıl girdiği, kim koyduğu
ve iki gerçekleştirim birden bulunduğunda hangisinin seçildiği, sıradaki konunun sorusu.

## Günlükleme ve Diğer JVM Dilleri

Günlükleme cephesinin çağıranın sınıf dosyasında bıraktığı iz ve gerçekleştirimin koşum yoluna bildirimle girmesi, günlük düzeylerinin ve yapılandırılmış kaydın üretimde okunabilirliğe etkisi, aynı çalışma zamanını paylaşan diğer dillerin sınıf dosyası biçimi üzerinden aynı yolda buluşması.

### Günlükleme Cephesi ve Gerçekleştirim

Source: https://academia.sh/tr/kurslar/java-ekosistem/gunlukleme-cephesi-ve-gerceklestirim

Çağıranın sınıf dosyasının cepheyi andığı ama gerçekleştirimi anmadığı sabit havuzdan okunarak gösterilir; gerçekleştirim koşum sınıf yoluna bir servis bildirimiyle girer ve iki gerçekleştirim bulunduğunda seçimi yolun sırası belirler. Sınırlayıcı ölçüm, gerçekleştirim hiç bulunmadığında çağıranın yine de derlenip koştuğunu ama hiçbir kayıt üretmediğini gösterir.

Testin gördüğü yolu biz kurduk ve içine neyi koyacağımızı biliyorduk: hangi sahte tipin hangi
gerçek tipin yerine geçtiğini, hangi kurulumun test sınıf yoluna hangi bağımlılığı eklediğini,
adım adım biz yazdık. Bu derste yazan taraf değişiyor. Çağıran bir arayüze karşı derlenir, o
arayüzün arkasında duracak sınıfın adını **hiç yazmaz** — ne içe aktarır, ne örnekler, ne bir
dizgi olarak anar. Yine de program koştuğunda o adsız sınıf bulunur ve çağrılır. Soru şu: bir
ad, kaynakta hiç yazılmadan, koşum sınıf yoluna nasıl giriyor ve orada birden fazla aday
varsa hangisinin kazanacağına ne karar veriyor?

Bu konu üç dersle günlüklemeyi ele alıyor, ama bu ilk ders günlük tutmanın kendisiyle değil,
günlükleme cephesinin kurduğu **bağımlılık yönüyle** ilgileniyor. Bir cephe — burada tek
yöntemli bir arayüz — çağıranın konuştuğu tek tip oluyor; arkasındaki gerçekleştirim çağırana
hiç görünmüyor. Aşağıdaki ölçüm bunu dört adımda kuruyor: önce derlemenin gerçekleştirimi hiç
görmeden geçtiğini ve sınıf dosyasının onu anmadığını, sonra gerçekleştirimin koşum sınıf
yoluna nasıl bir bildirimle girdiğini, sonra birden fazla gerçekleştirim olduğunda kazananı
neyin belirlediğini, son olarak da hiç gerçekleştirim yokken neyin olduğunu.

#### Derleme Sınıf Yolunda Yalnız Cephe Var

**GL1.** Çağıran kaynak metninde yalnız cephe arayüzünün adını yazar; gerçekleştirim
sınıflarının adı kaynakta hiç geçmez, bu yüzden derleyicinin onları görmesine gerek yoktur.
**GL2.** Sınıf dosyasının sabit havuzu, yalnız kaynakta adı geçen tipleri taşır; kaynakta hiç
yazılmayan bir ad sabit havuzda da bulunmaz.

Ölçümün geri kalanında tekrar eden dört iş var: bir kaynağı diske yazmak, onu belirli bir
sınıf yoluna karşı derlemek, derlenmiş bir sınıfı belirli bir sınıf yoluyla koşturmak ve bir
sınıf dosyasının sabit havuzunda hangi tiplerin adı geçtiğini okumak. Bu dördü aşağıdaki
yardımcı sınıfta toplanıyor; konunun kalan iki dersi de aynı dördü kullanıyor.

```java
// Arac.java — kaynak yazma, derleme, kosum ve sinif dosyasi okuma icin ortak yordamlar (yardimci sinif, main yok)
import java.io.*;
import java.lang.classfile.*;
import java.lang.classfile.constantpool.*;
import java.net.*;
import java.nio.file.*;
import java.util.*;
import java.util.spi.ToolProvider;

class Arac {
    static Path yaz(Path dizin, String ad, String kaynak) throws IOException {
        Files.createDirectories(dizin);
        Path p = dizin.resolve(ad + ".java");
        Files.writeString(p, kaynak);
        return p;
    }

    static boolean derle(Path cikti, List<Path> sinifYolu, List<Path> kaynaklar) throws IOException {
        Files.createDirectories(cikti);
        List<String> arg = new ArrayList<>(List.of("-d", cikti.toString()));
        if (!sinifYolu.isEmpty()) {
            StringJoiner sj = new StringJoiner(File.pathSeparator);
            for (Path p : sinifYolu) sj.add(p.toString());
            arg.add("-classpath");
            arg.add(sj.toString());
        }
        for (Path k : kaynaklar) arg.add(k.toString());
        ToolProvider derleyici = ToolProvider.findFirst("javac").orElseThrow();
        StringWriter sessizCikti = new StringWriter();
        int sonuc = derleyici.run(new PrintWriter(sessizCikti), new PrintWriter(sessizCikti),
                arg.toArray(new String[0]));
        return sonuc == 0;
    }

    static void calistir(List<Path> sinifYolu, String anaSinif) throws Exception {
        URL[] url = new URL[sinifYolu.size()];
        for (int i = 0; i < url.length; i++) url[i] = sinifYolu.get(i).toUri().toURL();
        try (URLClassLoader yukleyici = new URLClassLoader(url, Arac.class.getClassLoader().getParent())) {
            Class<?> sinif = Class.forName(anaSinif, true, yukleyici);
            sinif.getMethod("main", String[].class).invoke(null, (Object) new String[0]);
        }
    }

    static Object alan(List<Path> sinifYolu, String anaSinif, String alanAdi) throws Exception {
        URL[] url = new URL[sinifYolu.size()];
        for (int i = 0; i < url.length; i++) url[i] = sinifYolu.get(i).toUri().toURL();
        try (URLClassLoader yukleyici = new URLClassLoader(url, Arac.class.getClassLoader().getParent())) {
            Class<?> sinif = Class.forName(anaSinif, true, yukleyici);
            sinif.getMethod("main", String[].class).invoke(null, (Object) new String[0]);
            return sinif.getField(alanAdi).get(null);
        }
    }

    static Set<String> atiflar(Path sinifDosyasi) throws IOException {
        Set<String> adlar = new TreeSet<>();
        ClassModel model = ClassFile.of().parse(sinifDosyasi);
        for (PoolEntry e : model.constantPool()) {
            if (e instanceof ClassEntry ce) adlar.add(ce.asInternalName().replace('/', '.'));
        }
        return adlar;
    }

    static void goster(String etiket, Object deger) {
        System.out.printf("%-62s: %s%n", etiket, deger);
    }

    static void bildir(Path dizin, String servis, String saglayici) throws IOException {
        Path d = dizin.resolve("META-INF").resolve("services");
        Files.createDirectories(d);
        Files.writeString(d.resolve(servis), saglayici + "\n");
    }

    static String cepheKaynagi() {
        return "public interface GunlukCephesi { void yaz(String ileti); int uretilenKayitSayisi(); }";
    }

    static String cagiranKaynagi() {
        return "import java.util.ServiceLoader;\n"
             + "public class Cagiran {\n"
             + "  static final class Sessiz implements GunlukCephesi {\n"
             + "    public void yaz(String ileti) { }\n"
             + "    public int uretilenKayitSayisi() { return 0; }\n"
             + "  }\n"
             + "  public static String secilenSinif = \"yok\";\n"
             + "  public static int uretilenKayit = -1;\n"
             + "  static GunlukCephesi bul() {\n"
             + "    for (GunlukCephesi g : ServiceLoader.load(GunlukCephesi.class, Cagiran.class.getClassLoader())) return g;\n"
             + "    return new Sessiz();\n"
             + "  }\n"
             + "  public static void main (String[] a) {\n"
             + "    GunlukCephesi g = bul();\n"
             + "    secilenSinif = g.getClass().getSimpleName();\n"
             + "    g.yaz(\"baslangic\"); g.yaz(\"ilerleme\"); g.yaz(\"bitis\");\n"
             + "    uretilenKayit = g.uretilenKayitSayisi();\n"
             + "  }\n"
             + "}";
    }

    static String gerceklestirimKaynagi(String ad) {
        return "public class " + ad + " implements GunlukCephesi {\n"
             + "  private int sayac = 0;\n"
             + "  public void yaz(String ileti) { sayac++; }\n"
             + "  public int uretilenKayitSayisi() { return sayac; }\n"
             + "}";
    }
}
```

`cagiranKaynagi()` çağıranın kaynağını üretiyor: `Cagiran` yalnız `GunlukCephesi` arayüzünü ve
`ServiceLoader`'ı içe aktarıyor, hiçbir gerçekleştirim sınıfının adını yazmıyor. Bulamazsa
kendi içinde tanımladığı `Sessiz` iç sınıfına düşüyor — bu da bir gerçekleştirim, ama
çağıranın **kendi kaynağında** duruyor, dışarıdan gelmiyor.

```java
// SadeceCephe.java — derleme sinif yolunda yalniz cephe varken cagiran derleniyor mu, sinif dosyasi kimi aniyor
import java.nio.file.*;
import java.util.*;

public class SadeceCephe {
    public static void main(String[] args) throws Exception {
        Path kok = Files.createTempDirectory("gl1");
        Path kaynak = kok.resolve("kaynak");

        Path sCephe = Arac.yaz(kaynak, "GunlukCephesi", Arac.cepheKaynagi());
        Path sCagiran = Arac.yaz(kaynak, "Cagiran", Arac.cagiranKaynagi());
        Path sA = Arac.yaz(kaynak, "GerceklestirimA", Arac.gerceklestirimKaynagi("GerceklestirimA"));
        Path sB = Arac.yaz(kaynak, "GerceklestirimB", Arac.gerceklestirimKaynagi("GerceklestirimB"));

        Path cepheD = kok.resolve("cephe");
        Path cagiranD = kok.resolve("cagiran");
        Path aD = kok.resolve("kayitA");
        Path bD = kok.resolve("kayitB");

        Arac.derle(cepheD, List.of(), List.of(sCephe));
        Arac.derle(aD, List.of(cepheD), List.of(sA));
        Arac.derle(bD, List.of(cepheD), List.of(sB));
        boolean cagiranDerlendi = Arac.derle(cagiranD, List.of(cepheD), List.of(sCagiran));

        Set<String> atif = Arac.atiflar(cagiranD.resolve("Cagiran.class"));

        Arac.goster("gerceklestirim adi derleme sinif yolunda hic olmadan cagiran derlendi mi", cagiranDerlendi);
        Arac.goster("cagiranin sinif dosyasi GunlukCephesi'ni aniyor mu", atif.contains("GunlukCephesi"));
        Arac.goster("cagiranin sinif dosyasi GerceklestirimA'yi aniyor mu", atif.contains("GerceklestirimA"));
        Arac.goster("cagiranin sinif dosyasi GerceklestirimB'yi aniyor mu", atif.contains("GerceklestirimB"));
    }
}
```

```
gerceklestirim adi derleme sinif yolunda hic olmadan cagiran derlendi mi: true
cagiranin sinif dosyasi GunlukCephesi'ni aniyor mu            : true
cagiranin sinif dosyasi GerceklestirimA'yi aniyor mu          : false
cagiranin sinif dosyasi GerceklestirimB'yi aniyor mu          : false
```

`GerceklestirimA` ve `GerceklestirimB` bu ölçümde gerçekten var: ikisi de derlenmiş, ikisinin
de sınıf dosyası diskte duruyor. `cagiranD`'nin derleme sınıf yolu bile onları içermiyor —
yalnız `cepheD` var — ve `Cagiran` yine de derleniyor (GL1). Sabit havuz okunduğunda
`GunlukCephesi` orada, `GerceklestirimA` ve `GerceklestirimB` orada değil (GL2). Bu, bir
tahmin değil; kaynakta hiç yazılmayan bir ad, derleyicinin üretebileceği hiçbir komutta da
geçmiyor, çünkü sınıf dosyası kaynağın **çevirisidir**, kaynağın bilmediği bir şeyi icat
etmez. Cephenin bütün gücü tam olarak burada: çağıran, arkada duracak sınıfın var olup
olmadığını bile derleme anında bilmek zorunda değil.

#### Gerçekleştirimin Yola Bildirimle Girmesi

**GL3.** Gerçekleştirim koşum sınıf yoluna bir servis bildirimi dosyasıyla girer:
`META-INF/services/<cephe-arayüzünün-tam-adı>` adlı dosya, sağlayıcı sınıfın adını satır
olarak taşır. **GL4.** `ServiceLoader.load(...)` bu dosyayı okuyarak sağlayıcıyı bulur ve
örnekler; çağıran sağlayıcının kurucusunu hiçbir yerde çağırmaz.

```java
// ServisBildirimi.java — gerceklestirim yola bir servis bildirimiyle giriyor, cagiran onu ServiceLoader ile buluyor
import java.nio.file.*;
import java.util.*;

public class ServisBildirimi {
    public static void main(String[] args) throws Exception {
        Path kok = Files.createTempDirectory("gl2");
        Path kaynak = kok.resolve("kaynak");

        Path sCephe = Arac.yaz(kaynak, "GunlukCephesi", Arac.cepheKaynagi());
        Path sCagiran = Arac.yaz(kaynak, "Cagiran", Arac.cagiranKaynagi());
        Path sA = Arac.yaz(kaynak, "GerceklestirimA", Arac.gerceklestirimKaynagi("GerceklestirimA"));

        Path cepheD = kok.resolve("cephe");
        Path cagiranD = kok.resolve("cagiran");
        Path aD = kok.resolve("kayitA");

        Arac.derle(cepheD, List.of(), List.of(sCephe));
        Arac.derle(cagiranD, List.of(cepheD), List.of(sCagiran));
        Arac.derle(aD, List.of(cepheD), List.of(sA));
        Arac.bildir(aD, "GunlukCephesi", "GerceklestirimA");

        Object secilenOncesi = Arac.alan(List.of(cagiranD, cepheD), "Cagiran", "secilenSinif");
        Arac.goster("gerceklestirim kosum yolunda yokken dusulen sinif", secilenOncesi);

        List<Path> kosumYoluSonrasi = List.of(cagiranD, cepheD, aD);
        Object secilenSonrasi = Arac.alan(kosumYoluSonrasi, "Cagiran", "secilenSinif");
        Object kayitSonrasi = Arac.alan(kosumYoluSonrasi, "Cagiran", "uretilenKayit");
        Arac.goster("gerceklestirim yola bildirimle girince secilen sinif", secilenSonrasi);
        Arac.goster("uretilen kayit sayisi", kayitSonrasi);
    }
}
```

```
gerceklestirim kosum yolunda yokken dusulen sinif             : Sessiz
gerceklestirim yola bildirimle girince secilen sinif          : GerceklestirimA
uretilen kayit sayisi                                         : 3
```

`aD` dizini iki şey taşıyor: `GerceklestirimA.class` ve `META-INF/services/GunlukCephesi`
adlı, içinde tek satır `GerceklestirimA` yazan bir bildirim dosyası. Koşum sınıf yoluna `aD`
eklendiğinde `Cagiran` içindeki `ServiceLoader.load` çağrısı bu dosyayı okuyor,
`GerceklestirimA`'yı yüklüyor ve örnekliyor — kod tarafında hiçbir satır `new
GerceklestirimA()` yazmadan (GL3, GL4). Üç `yaz` çağrısının üçü de bu örneğe ulaşıyor ve
sayaç üçe çıkıyor. `aD` koşum sınıf yolundan çıkarılınca aynı `Cagiran.class`, hiç yeniden
derlenmeden, kendi `Sessiz` düşüşüne geri dönüyor. Değişen tek şey koşum sınıf yolunun
içeriği; `Cagiran.class` dosyası ikisinde de birebir aynı bayt dizisi.

#### İki Gerçekleştirim Olunca Sırayı Yol Belirliyor

**GL5.** Koşum sınıf yolunda aynı servis için birden fazla sağlayıcı bildirildiğinde
`ServiceLoader`'ın döndürdüğü ilk sağlayıcı, sınıf yolu girişlerinin **sırasına** bağlıdır.
**GL6.** Sıra değişince seçilen gerçekleştirim değişir, ama ne derleme ne koşum hatası
oluşur — ikisi de "tamam" sonucuyla biter.

```java
// SiraKazanani.java — kosum sinif yolunda iki gerceklestirim varken kazanani yolun sirasi belirliyor
import java.nio.file.*;
import java.util.*;

public class SiraKazanani {
    public static void main(String[] args) throws Exception {
        Path kok = Files.createTempDirectory("gl3");
        Path kaynak = kok.resolve("kaynak");

        Path sCephe = Arac.yaz(kaynak, "GunlukCephesi", Arac.cepheKaynagi());
        Path sCagiran = Arac.yaz(kaynak, "Cagiran", Arac.cagiranKaynagi());
        Path sA = Arac.yaz(kaynak, "GerceklestirimA", Arac.gerceklestirimKaynagi("GerceklestirimA"));
        Path sB = Arac.yaz(kaynak, "GerceklestirimB", Arac.gerceklestirimKaynagi("GerceklestirimB"));

        Path cepheD = kok.resolve("cephe");
        Path cagiranD = kok.resolve("cagiran");
        Path aD = kok.resolve("kayitA");
        Path bD = kok.resolve("kayitB");

        Arac.derle(cepheD, List.of(), List.of(sCephe));
        Arac.derle(cagiranD, List.of(cepheD), List.of(sCagiran));
        Arac.derle(aD, List.of(cepheD), List.of(sA));
        Arac.derle(bD, List.of(cepheD), List.of(sB));
        Arac.bildir(aD, "GunlukCephesi", "GerceklestirimA");
        Arac.bildir(bD, "GunlukCephesi", "GerceklestirimB");

        Object aOnce = Arac.alan(List.of(cagiranD, cepheD, aD, bD), "Cagiran", "secilenSinif");
        Object bOnce = Arac.alan(List.of(cagiranD, cepheD, bD, aD), "Cagiran", "secilenSinif");

        Arac.goster("A once verilen kosum sinif yolunda secilen", aOnce);
        Arac.goster("B once verilen kosum sinif yolunda secilen", bOnce);
        Arac.goster("ikisi de hatasiz kosup yalniz sirayla farklilasti mi", !aOnce.equals(bOnce));
    }
}
```

```
A once verilen kosum sinif yolunda secilen                    : GerceklestirimA
B once verilen kosum sinif yolunda secilen                    : GerceklestirimB
ikisi de hatasiz kosup yalniz sirayla farklilasti mi          : true
```

`aD` ve `bD` birbirinden habersiz iki dizin; her biri kendi sınıfını ve kendi servis
bildirimini taşıyor. Koşum sınıf yolu `[cagiranD, cepheD, aD, bD]` sırasıyla kurulduğunda
`ServiceLoader` önce `aD`'nin bildirimine rastlıyor ve `GerceklestirimA`'yı döndürüyor; aynı
iki dizin `bD, aD` sırasıyla verildiğinde kazanan `GerceklestirimB` oluyor (GL5). `Cagiran`
kaynağı, `GunlukCephesi` arayüzü, iki gerçekleştirimin kendisi — hiçbiri iki koşum arasında
değişmedi; değişen yalnız listenin sırası. Kod tarafında hiçbir yerde bir çakışma uyarısı ya
da hata yok (GL6): iki sağlayıcı bulunması, K05'in ortak tanımındaki "koşum sınıf yolunda iki
gerçekleştirim bulunduğunda seçim yolun sırasına göre değişir, hata yok" gözlemiyle aynı
kalıba giriyor, burada arayüz düzeyinde bir kez daha görülüyor.

#### Gerçekleştirim Hiç Bulunmazsa

**GL7.** Koşum sınıf yolunda hiçbir sağlayıcı bulunmazsa `ServiceLoader.load(...)` boş bir
yineleyici döner; çağıran bunu bir istisna olarak değil, kendi tanımladığı bir varsayılanla
karşılar. **GL8.** Bu durumda program hatasız çalışır ama hiçbir gerçekleştirim
`uretilenKayitSayisi()`'nı artırmadığı için üretilen kayıt sıfırdır.

```java
// GerceklestirimYok.java — gerceklestirim kosum yolunda hic yokken cagiran hatasiz kosuyor ama hicbir kayit uretmiyor
import java.nio.file.*;
import java.util.*;

public class GerceklestirimYok {
    public static void main(String[] args) throws Exception {
        Path kok = Files.createTempDirectory("gl4");
        Path kaynak = kok.resolve("kaynak");

        Path sCephe = Arac.yaz(kaynak, "GunlukCephesi", Arac.cepheKaynagi());
        Path sCagiran = Arac.yaz(kaynak, "Cagiran", Arac.cagiranKaynagi());

        Path cepheD = kok.resolve("cephe");
        Path cagiranD = kok.resolve("cagiran");
        Arac.derle(cepheD, List.of(), List.of(sCephe));
        Arac.derle(cagiranD, List.of(cepheD), List.of(sCagiran));

        List<Path> kosumYolu = List.of(cagiranD, cepheD);
        boolean hataAlmadanKostu;
        try {
            Arac.calistir(kosumYolu, "Cagiran");
            hataAlmadanKostu = true;
        } catch (Exception e) {
            hataAlmadanKostu = false;
        }
        Object secilen = Arac.alan(kosumYolu, "Cagiran", "secilenSinif");
        Object kayit = Arac.alan(kosumYolu, "Cagiran", "uretilenKayit");

        Arac.goster("gerceklestirim kosum yolunda hic yokken hata almadan kosuyor mu", hataAlmadanKostu);
        Arac.goster("dusulen sinif", secilen);
        Arac.goster("uretilen kayit sayisi", kayit);
    }
}
```

```
gerceklestirim kosum yolunda hic yokken hata almadan kosuyor mu: true
dusulen sinif                                                 : Sessiz
uretilen kayit sayisi                                         : 0
```

Bu ölçümün koşum sınıf yolunda `GerceklestirimA` da `GerceklestirimB` da yok — yalnız cephe
ve çağıran var. Program yine de tamamlanıyor, hiçbir istisna fırlatmıyor (GL7) ve
`uretilenKayit` sıfırda kalıyor (GL8). Bu, dersin sınırlayıcı ölçümü: cephe, çağıranla
gerçekleştirim arasındaki bağımlılığı **yok etmiyor**, yönünü çeviriyor. Gerçekleştirim
hiç yoksa çağıran bunu bir hata olarak değil bir **boşluk** olarak görüyor ve kendi
`Sessiz` düşüşüyle dolduruyor. Üretilen kayıt sayısının sıfır olması burada bir kusurun
işareti değil; çağıranın kendi kaynağında yazdığı bir kuralın sonucu. Bir günlükleme
cephesinin gerçek dünyadaki gerçekleştirimleri de bu sözü taşır: gerçekleştirim
bağlanmamışsa program çökmez, yalnız konuşmaz. Bu, cephe düzeneğinin çağırana verdiği asıl
güvencedir — çağıranın kendisi hangi gerçekleştirimin koşum sınıf yoluna gireceğini
bilmeden yazılabilir, çünkü en kötü ihtimalle davranış "hiç kayıt yok" olur, "program
çalışmıyor" değil. Bu güvence bir bedel de taşır: koşum sınıf yolu yanlış kurulduğunda
ortada hiçbir hata mesajı olmadığı için eksik kalan kayıtların fark edilmesi, derleyicinin
reddettiği bir satırı fark etmekten çok daha zordur.

#### Özet

- Çağıran kaynak metninde yalnız cephe arayüzünün adını yazar; derleme sınıf yolunda
  gerçekleştirim hiç bulunmasa da çağıran derlenir.
- Sınıf dosyasının sabit havuzu kaynakta yazılan tipleri taşır: `GunlukCephesi` orada,
  kaynakta hiç geçmeyen `GerceklestirimA` ve `GerceklestirimB` orada değil.
- Gerçekleştirim koşum sınıf yoluna bir servis bildirim dosyasıyla girer; `ServiceLoader` bu
  bildirimi okuyarak sağlayıcıyı çağıranın hiçbir satırında adı geçmeden bulur ve örnekler.
- Koşum sınıf yolunda birden fazla sağlayıcı bildirildiğinde kazananı sınıf yolu
  girişlerinin sırası belirler; sıra değişince seçim değişir ama hata oluşmaz.
- Sınırlayıcı ölçüm: gerçekleştirim hiç bulunmadığında çağıran yine derlenip hatasız koşar,
  ama hiçbir kayıt üretmez — cephe bağımlılığı yok etmiyor, yönünü çeviriyor.

#### Sonraki Adım

Bu derste ölçülen şey bir adın yola nasıl girdiğiydi: bildirimle, sırayla, ya da hiç
girmeden. Sıradaki ders aynı çağrı kümesini sabit tutup değişen tek şeyi bir eşik yapıyor —
aynı üç çağrı iki ayrı eşikle koşturulduğunda kaç tanesi gerçekten bir kayda dönüşüyor ve
eşiğin altında kalan bir çağrının hâlâ bir bedeli var mı?

### Günlük Düzeyleri ve Yapılandırılmış Günlük

Source: https://academia.sh/tr/kurslar/java-ekosistem/gunluk-duzeyleri-ve-yapilandirilmis-gunluk

Aynı çağrı kümesinin iki ayrı eşikte kaç kayıt ürettiği koşturularak ölçülür ve eşiğin çağrı yerinde değil koşumda belirlendiği gösterilir; yapılandırılmış bir kaydın okunabilirliğinin alan sayısıyla değil aynı alanın her kayıtta bulunmasıyla ölçüldüğü örneklenir. Sınırlayıcı ölçüm, eşiğin altında kalan bir çağrının dizgi birleştirmeyle yazıldığında yine de değerlendirildiğini, Supplier ile yazıldığında değerlendirilmediğini gösterir.

Önceki ders bir adın yola nasıl girdiğini ölçtü: bildirimle, sırayla, ya da hiç girmeden.
Bu ders aynı çağrı kümesini sabit tutup değişen tek şeyi bir **eşik** yapıyor. Cephe bir
kayıt çağrısını gerçekleştirime taşıdı; şimdiki soru, o çağrının gerçekten bir kayda dönüşüp
dönüşmeyeceğini kimin belirlediği. Çağrı yerinde mi, yoksa programı çalıştıran tarafın
verdiği bir ayarda mı?

Günlük düzeyi ve yapılandırılmış günlük kavramları bu kursta yeni değil — **M22/K06
Gözlemlenebilirlik ve İşletim** ve **M19/K06 Başarım Karşıt Kalıpları ve İzleme** ikisini de
kavram olarak kurdu: düzeylerin sıralı bir ölçek olduğu, yapılandırılmış bir kaydın
serbest metinden farkı. Bu ders o kavramları tekrar tanımlamıyor; tek sorduğu şey, Java'nın
standart günlükleme kütüphanesinde bu iki kavramın **koşarken** nasıl davrandığı. Ölçüm
`java.util.logging` ile yapılıyor — üçüncü taraf bir günlükleme gerçekleştirimi değil,
dilin kendi standart kitaplığı. Önceki dersteki cephe/gerçekleştirim ayrımı burada da
geçerli: bir çağrı yerinin `gunlukcu.info(...)` yazması, o çağrının bir kayda dönüşeceğinin
garantisi değil. Aradaki fark, önceki derste "gerçekleştirim bulundu mu" sorusuna,
bu derste "düzey eşiği geçti mi" sorusuna bakıyor olması; ikisi de çağrı yerinin bilmediği,
koşum kurulumunun verdiği bir yanıt.

#### Eşik Çağrı Yerinde Değil, Koşumda Belirleniyor

**GL9.** Bir günlük çağrısının işlenip işlenmeyeceği, çağrının taşıdığı düzeyin günlükçüye o
an atanmış eşikle karşılaştırılmasıyla belirlenir. **GL10.** Bu karşılaştırma koşum
zamanında olur; aynı derlenmiş çağrı kümesi, çalıştırma sırasında verilen eşiğe göre farklı
sayıda kayıt üretir.

```java
// EsikSayaci.java — ayni cagri kumesi iki ayri kosum esiginde kac kayit uretiyor
import java.util.logging.*;
import java.util.ArrayList;
import java.util.List;

public class EsikSayaci {
    static class SayanIsleyici extends Handler {
        List<String> yayimlanan = new ArrayList<>();
        public void publish(LogRecord r) { yayimlanan.add(r.getLevel() + " " + r.getMessage()); }
        public void flush() { }
        public void close() { }
    }

    static int calistir(Level esik) {
        Logger gunlukcu = Logger.getLogger("EsikDenemesi-" + esik);
        gunlukcu.setUseParentHandlers(false);
        SayanIsleyici h = new SayanIsleyici();
        h.setLevel(Level.ALL);
        gunlukcu.addHandler(h);
        gunlukcu.setLevel(esik);

        gunlukcu.fine("ayrinti");
        gunlukcu.info("ilerleme");
        gunlukcu.warning("uyari");
        gunlukcu.severe("kritik");

        return h.yayimlanan.size();
    }

    public static void main(String[] args) {
        int infoEsigindeUretilen = calistir(Level.INFO);
        int uyariEsigindeUretilen = calistir(Level.WARNING);

        System.out.println("dort cagri: fine, info, warning, severe");
        System.out.println("esik INFO iken uretilen kayit    : " + infoEsigindeUretilen);
        System.out.println("esik WARNING iken uretilen kayit : " + uyariEsigindeUretilen);
    }
}
```

```
dort cagri: fine, info, warning, severe
esik INFO iken uretilen kayit    : 3
esik WARNING iken uretilen kayit : 2
```

`calistir` yöntemi ikisinde de birebir aynı dört çağrıyı yapıyor: `fine`, `info`, `warning`,
`severe`. Değişen tek şey `gunlukcu.setLevel(esik)` satırına verilen değer. `INFO` eşiğinde `fine`
çağrısı süzülüyor; geri kalan üçü (`info`, `warning`, `severe`) işleyiciye ulaşıyor (GL9). Eşik
`WARNING`'e çekildiğinde `info` de süzülüyor; yalnız ikisi kalıyor. Bu iki koşum aynı sınıf
dosyasını kullanıyor — hiçbir yeniden derleme yok — ve yalnızca çalıştırma anında verilen bir
değerle farklı sayıda kayıt üretiyor (GL10). Çağrı yerindeki `gunlukcu.info(...)` satırı ne
INFO'nun geçeceğini ne geçmeyeceğini biliyor; bunu bilen taraf, günlükçüye eşiği atayan koşum
kurulumu.

**GL11.** Eşik tek bir ayar değildir: günlükçünün kendi eşiği ve ona bağlı her işleyicinin
kendi eşiği ayrı ayrı uygulanır; bir kaydın yayımlanması için ikisini de geçmesi gerekir.

```java
// IkiKatmanliEsik.java — gunlukcunun ve isleyicinin ayri esikleri var, kayit ikisini de gecmek zorunda
import java.util.logging.*;
import java.util.ArrayList;
import java.util.List;

public class IkiKatmanliEsik {
    static class SuzenIsleyici extends Handler {
        List<String> yayimlanan = new ArrayList<>();
        public void publish(LogRecord r) {
            if (!isLoggable(r)) return;
            yayimlanan.add(r.getLevel() + " " + r.getMessage());
        }
        public void flush() { }
        public void close() { }
    }

    static int calistir(Level gunlukcuEsigi, Level isleyiciEsigi) {
        Logger gunlukcu = Logger.getLogger("IkiKatman-" + gunlukcuEsigi + "-" + isleyiciEsigi);
        gunlukcu.setUseParentHandlers(false);
        SuzenIsleyici h = new SuzenIsleyici();
        h.setLevel(isleyiciEsigi);
        gunlukcu.addHandler(h);
        gunlukcu.setLevel(gunlukcuEsigi);

        gunlukcu.info("ilerleme");
        gunlukcu.warning("uyari");
        gunlukcu.severe("kritik");

        return h.yayimlanan.size();
    }

    public static void main(String[] args) {
        System.out.println("uc cagri: info, warning, severe");
        System.out.println("gunlukcu ALL, isleyici ALL     -> uretilen: " + calistir(Level.ALL, Level.ALL));
        System.out.println("gunlukcu ALL, isleyici WARNING -> uretilen: " + calistir(Level.ALL, Level.WARNING));
        System.out.println("gunlukcu WARNING, isleyici ALL -> uretilen: " + calistir(Level.WARNING, Level.ALL));
    }
}
```

```
uc cagri: info, warning, severe
gunlukcu ALL, isleyici ALL     -> uretilen: 3
gunlukcu ALL, isleyici WARNING -> uretilen: 2
gunlukcu WARNING, isleyici ALL -> uretilen: 2
```

Üç kurulumun üçü de aynı üç çağrıyı yapıyor. Günlükçü de işleyici de eşiği `ALL` iken
üçü de geçiyor. İkisinden yalnız biri `WARNING`'e çekildiğinde — fark etmez hangisi —
sonuç aynı: iki kayıt (GL11). Eşiği yalnız günlükçüde arayan bir okuma yanlış olurdu;
`SuzenIsleyici.publish` kendi `isLoggable` denetimini yapmasaydı, işleyicinin `setLevel`
çağrısı hiçbir şeyi süzmezdi ve son iki satır da üç kayıt verirdi. Eşiğin koşumda
belirlenmesi bu yüzden tek bir sayı değil, zincirdeki her halkanın kendi kararı. Bu, bir
günlükçüye birden fazla işleyici bağlandığında pratik bir sonuç doğuruyor: aynı kayıt kümesi
bir işleyicide görünüp ötekinde görünmeyebilir, çünkü her işleyici kendi eşiğini kendi
kararıyla uyguluyor ve günlükçünün eşiği yalnız ilk süzgeç.

#### Yapılandırılmış Kayıt: Alan Sayısı Değil Alan Tutarlılığı

**GL12.** Yapılandırılmış bir kaydın alan adları çağrı yerinde rastgele seçilmez; üretici
tarafın uyduğu sabit bir bildirimde durur ve okuyan taraf hangi alanı arayacağını bu
bildirime bakarak bilir. **GL13.** Bir günlük akışının okunabilirliği tek bir kaydın
taşıdığı alan **sayısıyla** değil, aynı alanın her kayıtta **bulunmasıyla** ölçülür.

Aşağıdaki ölçüm iki tasarımı karşılaştırıyor. Birincisinde her kayıt aynı iki alanı taşıyor:
`olay` ve `sayi`. İkincisinde çağrı yerleri kendi başlarına buyruk: bazıları fazladan bir
`kaynak` alanı ekliyor, bazıları `sayi` alanını hiç yazmıyor. Biçimlendirici ikisinde de aynı
— kayıt zaten çağrı yerinde tam metin olarak kuruluyor, biçimlendirici yalnız zaman alanı
eklemeden onu iletiyor (ortama bağlı veri yasağı burada da geçerli: zaman damgası hiç
basılmıyor).

```java
// AlanTutarliligi.java — okunabilirlik toplam alan sayisiyla degil, ayni alanin her kayitta bulunmasiyla olculuyor
import java.util.logging.*;
import java.util.ArrayList;
import java.util.List;

public class AlanTutarliligi {
    static class AlanBicimlendirici extends Formatter {
        public String format(LogRecord r) { return r.getMessage() + System.lineSeparator(); }
    }

    static class ToplayanIsleyici extends Handler {
        List<String> kayitlar = new ArrayList<>();
        Formatter b = new AlanBicimlendirici();
        public void publish(LogRecord r) { kayitlar.add(b.format(r)); }
        public void flush() { }
        public void close() { }
    }

    static boolean ikiAlanDaVar(String kayit) {
        return kayit.contains("olay=") && kayit.contains("sayi=");
    }

    static int toplamAlanSayisi(List<String> kayitlar) {
        int toplam = 0;
        for (String k : kayitlar) {
            for (String parca : k.trim().split(" ")) if (parca.contains("=")) toplam++;
        }
        return toplam;
    }

    public static void main(String[] args) {
        List<String> tutarli = new ArrayList<>();
        for (int i = 1; i <= 5; i++) tutarli.add("olay=siparis sayi=" + i);

        List<String> tutarsiz = List.of(
                "olay=siparis sayi=1 kaynak=web",
                "olay=siparis sayi=2 kaynak=web",
                "olay=siparis sayi=3 kaynak=mobil",
                "olay=siparis",
                "olay=siparis");

        long tutarliTam = tutarli.stream().filter(AlanTutarliligi::ikiAlanDaVar).count();
        long tutarsizTam = tutarsiz.stream().filter(AlanTutarliligi::ikiAlanDaVar).count();

        System.out.println("tutarli tasarim  - toplam alan: " + toplamAlanSayisi(tutarli)
                + ", iki alani da tasiyan kayit: " + tutarliTam + "/" + tutarli.size());
        System.out.println("tutarsiz tasarim - toplam alan: " + toplamAlanSayisi(tutarsiz)
                + ", iki alani da tasiyan kayit: " + tutarsizTam + "/" + tutarsiz.size());
    }
}
```

```
tutarli tasarim  - toplam alan: 10, iki alani da tasiyan kayit: 5/5
tutarsiz tasarim - toplam alan: 11, iki alani da tasiyan kayit: 3/5
```

`AlanBicimlendirici` ve `ToplayanIsleyici` bu ölçümde asıl işi yapmıyor — kaydı olduğu gibi
topluyorlar, çünkü asıl fark çağrı yerinde kuruluyor. Tutarsız tasarımın topladığı alan
**sayısı** tutarlıdan daha yüksek: 11'e karşı 10 (GL12'nin bildirime uyma zorunluluğu burada
yok, her çağrı yeri kendi kararını veriyor). Buna rağmen `sayi` alanını **her kayıtta**
arayan bir ayrıştırıcı tutarlı tasarımda beş kaydın beşini de okuyor, tutarsız tasarımda
yalnız üçünü (GL13). İki kayıt yalnızca `olay` taşıyor; bu kayıtlarda `sayi` yok ve
ayrıştırıcı için "yok" ile "sıfır" aynı şey değil. Fazladan alan eklemek — üçüncü ve dördüncü
kayıtlardaki `kaynak` — ayrıştırılabilirliği artırmıyor; yalnız eksik kalan alan
azaltıyor. Bir günlük akışını **makineyle** okunabilir yapan şey, her kaydın aynı sabit
alan kümesini taşıması; hangi alanların bu kümede olduğu değil.

Bu ayrımın pratik sonucu, bir günlük akışını sonradan işleyen tarafın davranışında görünür.
Binlerce kayıt üzerinden `sayi` alanına göre bir toplam çıkarmak isteyen bir okuyucu,
tutarlı akışta beş kaydın beşini de güvenle işleyebilir; tutarsız akışta iki kaydı ya
atlamak ya da özel bir durum olarak ele almak zorunda kalır. Alan sayısının kayıttan kayda
değişmesi bir hata değildir — `AlanTutarliligi` örneğinde hiçbir istisna fırlamıyor,
hiçbir çağrı reddedilmiyor — ama okuyucunun her alanı **var sayamaması**, o akışı tek tek
elle okumaya zorlar. Yapılandırılmış günlüğün asıl kazancı burada: alan adlarını sabit bir
bildirime bağlamak, okuyucuyu bu varsayımdan kurtarıyor.

#### Sınırlayıcı Ölçüm: Eşiğin Altındaki Çağrı Bedelsiz Değil

**GL14.** Eşiğin altında kalan bir çağrının ilettiği ifade, dizgi birleştirmeyle yazıldığında
düzeyden bağımsız olarak değerlendirilir. **GL15.** Aynı çağrı bir `Supplier` ile
yazıldığında ifade yalnız düzey etkinken değerlendirilir; eşiğin altında kalan çağrılarda
değerlendirme hiç çalışmaz.

Önceki iki ölçüm eşiğin **üretilen kayıt sayısını** belirlediğini gösterdi. Bundan "eşiğin
altında kalan çağrı bedelsizdir" sonucu çıkarılabilir gibi görünüyor, ama bu yanlış: eşik
yalnız kaydın **yayımlanıp yayımlanmayacağını** belirliyor, çağrıya verilen **argümanın
hesaplanıp hesaplanmayacağını** değil — hesaplama zamanı Java'nın yöntem çağırma kuralının
konusu, günlükleme çerçevesinin değil.

```java
// BedelliCagri.java — esigin altinda kalan cagrinin argumani dizgi birlestirmeyle her zaman, Supplier ile yalniz duzey acikken hesaplaniyor
import java.util.logging.*;

public class BedelliCagri {
    static int hesaplananIfade = 0;

    static String pahaliIfade() {
        hesaplananIfade++;
        return "deger=" + hesaplananIfade;
    }

    public static void main(String[] args) {
        Logger gunlukcu = Logger.getLogger("BedelDenemesi");
        gunlukcu.setUseParentHandlers(false);
        gunlukcu.addHandler(new Handler() {
            public void publish(LogRecord r) { }
            public void flush() { }
            public void close() { }
        });
        gunlukcu.setLevel(Level.WARNING);

        hesaplananIfade = 0;
        for (int i = 0; i < 5; i++) {
            gunlukcu.fine("ayrinti: " + pahaliIfade());
        }
        int istekliDegerlendirmeSayisi = hesaplananIfade;

        hesaplananIfade = 0;
        for (int i = 0; i < 5; i++) {
            gunlukcu.log(Level.FINE, () -> "ayrinti: " + pahaliIfade());
        }
        int tembelDegerlendirmeSayisi = hesaplananIfade;

        System.out.println("gunlukcunun esigi WARNING, bes cagrinin besi de FINE seviyesinde");
        System.out.println("dizgi birlestirmeyle (istekli) degerlendirilen ifade sayisi : " + istekliDegerlendirmeSayisi);
        System.out.println("Supplier ile (tembel) degerlendirilen ifade sayisi          : " + tembelDegerlendirmeSayisi);
    }
}
```

```
gunlukcunun esigi WARNING, bes cagrinin besi de FINE seviyesinde
dizgi birlestirmeyle (istekli) degerlendirilen ifade sayisi : 5
Supplier ile (tembel) degerlendirilen ifade sayisi          : 0
```

Günlükçünün eşiği `WARNING`; beş çağrının beşi de bundan daha düşük olan `FINE` düzeyinde ve
hiçbiri bir kayda dönüşmüyor. Buna rağmen `gunlukcu.fine("ayrinti: " + pahaliIfade())`
satırında `pahaliIfade()` beş kez de çalışıyor ve sayaç beşe çıkıyor (GL14): Java bir
yöntemi çağırmadan önce argümanlarını hesaplar, çağrının kendisi eşiğin altında kalacak olsa
bile bu sıra değişmiyor. İkinci döngü aynı beş çağrıyı `Logger.log(Level, Supplier<String>)`
biçimiyle yapıyor; bu aşırı yüklenmiş biçim, `Supplier`'ı yalnızca düzey etkinse çağırıyor —
eşiğin altında kalan beş çağrının beşinde de `get()` hiç işletilmiyor ve sayaç sıfırda
kalıyor (GL15). İki yazım da aynı dört düzeyi, aynı eşiği kullanıyor; fark yalnız argümanın
ne zaman hesaplandığında. Bu dersin ölçtüğü şey bir süre değil — hiçbir yerde bir zaman
ölçülmüyor — yalnız **değerlendirilen ifade sayısı**.

Bu fark Java'nın yöntem çağırma kuralından geliyor, günlükleme çerçevesinin bir eksikliği
değil: `gunlukcu.fine(mesaj)` çağrılmadan önce `mesaj` ifadesinin **değeri** hazır olmak
zorunda, çünkü Java bir çağrının argümanlarını çağrı gerçekleşmeden önce hesaplar — bu sıra
`fine` yönteminin içinde eşik denetimi yapıp yapmamasından bağımsız. `Supplier` bu sırayı
değiştirmiyor; yalnız hesaplamayı bir başka yöntemin — `get()`'in — arkasına saklıyor ve
o yöntemi çağırıp çağırmama kararını `fine` yönteminin içine, eşik denetiminden **sonraya**
taşıyor. Cephenin kendisi de aynı seçime açık: bir günlükleme cephesi çağırana yalnız
`Supplier` alan bir imza sunuyorsa, gerçekleştirim eşiği reddettiğinde ifade hiç
çalışmaz; cephe yalnız dizgi alan bir imza sunuyorsa, çağıran ne kadar dikkatli yazarsa
yazsın bu bedeli ödemekten kaçamaz. Bu, bir cephenin yalnız hangi yöntemleri **çağırmayı**
değil, hangi yöntemleri **sunmayı** seçtiğinin de ölçülebilir bir sonucu olduğunu gösteriyor.

#### Özet

- Bir günlük çağrısının kayda dönüşüp dönüşmeyeceği, çağrının düzeyiyle günlükçüye atanmış
  eşiğin koşum zamanında karşılaştırılmasıyla belirlenir; aynı derlenmiş çağrı kümesi farklı
  eşiklerde farklı sayıda kayıt üretir.
- Yapılandırılmış bir kaydın alan adları üretici tarafın bildirimde uyduğu sabit bir kümedir;
  okuyan taraf bu kümeye bakarak hangi alanı arayacağını bilir.
- Bir günlük akışının okunabilirliği toplam alan sayısıyla değil, aynı alanın her kayıtta
  bulunmasıyla ölçülür; fazladan alan eklemek eksik kalan bir alanı telafi etmez.
- Sınırlayıcı ölçüm: eşiğin altında kalan bir çağrının argümanı dizgi birleştirmeyle
  yazıldığında yine de hesaplanır; `Supplier` ile yazıldığında yalnız düzey etkinken
  hesaplanır.
- Ölçülen şey bir süre değil, bir sayıdır: kaç kayıt üretildi, kaç alan tutarlıydı, kaç
  ifade hesaplandı.

#### Sonraki Adım

Bu iki ders boyunca ölçülen her şey aynı çalışma zamanının içindeydi: aynı sanal makine,
aynı sınıf dosyası biçimi, tek bir kaynak dilden gelen kaynaklar. Sıradaki ders bu son
varsayımı kaldırıyor: koşum sınıf yoluna giren bir sınıf dosyası, kaynağı hiç Java
diliyle yazılmamış olsa bile aynı biçimde mi karşılanıyor ve çağıran bunu fark ediyor mu?

### JVM Üzerinde Diğer Diller

Source: https://academia.sh/tr/kurslar/java-ekosistem/jvm-uzerinde-diger-diller

Kaynak metni olmadan, doğrudan sınıf dosyası biçiminde üretilen bir sınıfın, bir arayüzü gerçekleştirdiği sürece javac ile derlenmiş bir sınıf gibi yüklenip çağrıldığı koşturularak gösterilir; çağıranın sınıf dosyası ne gerçek ne üretilen gerçekleştirimin adını anmaz. Sınırlayıcı ölçüm, üretilen sınıf dosyasının Java söz diziminde karşılığı olmayan bir üye adı taşıyabildiğini ve bunun yalnız yansıma (reflection) ile çağrılabildiğini gösterir.

Bu iki ders boyunca ölçülen her şey aynı çalışma zamanının içindeydi: cephe de, gerçekleştirim
de, günlükçü de, hepsi kaynağı Java olan sınıflardı. Bu ders bu son varsayımı kaldırıyor.
Koşum sınıf yoluna giren bir sınıf dosyası, kaynağı hiç Java diliyle yazılmamış olsa bile aynı
biçimde mi karşılanıyor ve çağıran bunu fark ediyor mu? Kursun tamamını taşıyan soru burada
son bir kez soruluyor: koşum sınıf yolu, üzerine konan şeyin **hangi dilden** geldiğini hiç
bilmiyor, yalnız sınıf dosyası biçimine uyup uymadığını biliyor. Bu dersin ölçümü bunu bir
iddia olarak değil, bir koşum olarak kuruyor: sınıf dosyasını javac'a hiç uğramadan,
doğrudan sınıf dosyası biçiminde üretiyor, koşum sınıf yoluna koyuyor ve Java'dan yazılmış
bir çağıranın onu çağırdığını gösteriyor. Hiçbir dil adı, satıcı adı ya da ürün adı
geçmiyor — konu "aynı sınıf dosyası biçimini üreten başka bir üretici" olarak kuruluyor.

#### Sınıf Dosyasını Kaynak Metin Olmadan Üretmek

**GL16.** Bir sınıf dosyası üretmenin zorunlu yolu kaynak metin yazıp derlemek değildir;
sınıf dosyası biçiminin kendisini doğrudan üreten bir yordam da sanal makinenin kabul
edeceği bir sınıf dosyası üretebilir. **GL17.** Böyle üretilen bir sınıf dosyası, bir
arayüzü gerçekleştirdiğini bildirdiği ve arayüzün istediği imzayı taşıdığı sürece, kaynaktan
derlenmiş bir sınıf gibi yüklenir ve çağrılır.

```java
// Arac.java — kaynak yazma, derleme, sabit havuz okuma ve kaynaksiz sinif dosyasi uretme icin ortak yordamlar (yardimci sinif, main yok)
import java.io.*;
import java.lang.classfile.*;
import java.lang.classfile.constantpool.*;
import java.lang.constant.*;
import java.net.*;
import java.nio.file.*;
import java.util.*;
import java.util.spi.ToolProvider;

class Arac {
    static Path yaz(Path dizin, String ad, String kaynak) throws IOException {
        Files.createDirectories(dizin);
        Path p = dizin.resolve(ad + ".java");
        Files.writeString(p, kaynak);
        return p;
    }

    static boolean derle(Path cikti, List<Path> sinifYolu, List<Path> kaynaklar) throws IOException {
        Files.createDirectories(cikti);
        List<String> arg = new ArrayList<>(List.of("-d", cikti.toString()));
        if (!sinifYolu.isEmpty()) {
            StringJoiner sj = new StringJoiner(File.pathSeparator);
            for (Path p : sinifYolu) sj.add(p.toString());
            arg.add("-classpath");
            arg.add(sj.toString());
        }
        for (Path k : kaynaklar) arg.add(k.toString());
        ToolProvider derleyici = ToolProvider.findFirst("javac").orElseThrow();
        StringWriter sessizCikti = new StringWriter();
        int sonuc = derleyici.run(new PrintWriter(sessizCikti), new PrintWriter(sessizCikti),
                arg.toArray(new String[0]));
        return sonuc == 0;
    }

    static void goster(String etiket, Object deger) {
        System.out.printf("%-58s: %s%n", etiket, deger);
    }

    static Set<String> atiflar(Path sinifDosyasi) throws IOException {
        Set<String> adlar = new TreeSet<>();
        ClassModel model = ClassFile.of().parse(sinifDosyasi);
        for (PoolEntry e : model.constantPool()) {
            if (e instanceof ClassEntry ce) adlar.add(ce.asInternalName().replace('/', '.'));
        }
        return adlar;
    }

    /** Kaynagi hic olmayan, verilen arayuzu gerceklestiren tek yontemli bir sinif dosyasi uretir. */
    static byte[] uret(String sinifAdi, String arayuzAdi, String yontemAdi, int sabit) {
        ClassDesc kendi = ClassDesc.of(sinifAdi);
        ClassDesc arayuz = ClassDesc.of(arayuzAdi);
        return ClassFile.of().build(kendi, cb -> {
            cb.withFlags(ClassFile.ACC_PUBLIC);
            cb.withInterfaceSymbols(arayuz);
            cb.withMethodBody(ConstantDescs.INIT_NAME, ConstantDescs.MTD_void, ClassFile.ACC_PUBLIC, code -> {
                code.aload(0);
                code.invokespecial(ConstantDescs.CD_Object, ConstantDescs.INIT_NAME, ConstantDescs.MTD_void);
                code.return_();
            });
            cb.withMethodBody(yontemAdi,
                    MethodTypeDesc.of(ConstantDescs.CD_int, ConstantDescs.CD_int),
                    ClassFile.ACC_PUBLIC,
                    code -> {
                        code.iload(1);
                        code.loadConstant(sabit);
                        code.iadd();
                        code.ireturn();
                    });
        });
    }
}
```

`Arac.uret` hiçbir kaynak dosyası yazmıyor; doğrudan sınıf dosyasının bayt dizisini kuruyor —
sabit havuzu, yöntem tablosunu, kod dizisini elle örüyor. Bu, önceki iki derste `javac`'ı
`ToolProvider` ile çağıran `Arac.derle`'nin **tam tersi**: burada derleyicinin kendisi hiç
devrede değil.

```java
// KaynaksizUretim.java — sinif dosyasi kaynak metin hic yazilmadan uretiliyor, ama derlenmis bir sinif gibi calisiyor
import java.net.*;
import java.nio.file.*;
import java.util.*;

public class KaynaksizUretim {
    static String hesaplayiciKaynagi() {
        return "public interface Hesaplayici { int hesapla(int girdi); }";
    }

    public static void main(String[] args) throws Exception {
        Path kok = Files.createTempDirectory("gl17");
        Path kaynak = kok.resolve("kaynak");
        Path arayuzD = kok.resolve("arayuz");
        Path uretilenD = kok.resolve("uretilen");

        Path sHesaplayici = Arac.yaz(kaynak, "Hesaplayici", hesaplayiciKaynagi());
        Arac.derle(arayuzD, List.of(), List.of(sHesaplayici));

        Files.createDirectories(uretilenD);
        byte[] bayt = Arac.uret("UretilenSinif", "Hesaplayici", "hesapla", 1);
        Path sinifDosyasi = uretilenD.resolve("UretilenSinif.class");
        Files.write(sinifDosyasi, bayt);

        boolean kaynakVarMi = Files.exists(uretilenD.resolve("UretilenSinif.java"));
        boolean sinifVarMi = Files.exists(sinifDosyasi);

        URL[] url = { arayuzD.toUri().toURL(), uretilenD.toUri().toURL() };
        Object sonuc;
        try (URLClassLoader y = new URLClassLoader(url, KaynaksizUretim.class.getClassLoader().getParent())) {
            Class<?> arayuz = Class.forName("Hesaplayici", true, y);
            Class<?> sinif = Class.forName("UretilenSinif", true, y);
            Object ornek = sinif.getDeclaredConstructor().newInstance();
            sonuc = arayuz.getMethod("hesapla", int.class).invoke(ornek, 10);
        }

        Arac.goster("UretilenSinif.java kaynak dosyasi diskte var mi", kaynakVarMi);
        Arac.goster("UretilenSinif.class sinif dosyasi diskte var mi", sinifVarMi);
        Arac.goster("uretilen sinifin hesapla(10) sonucu", sonuc);
    }
}
```

```
UretilenSinif.java kaynak dosyasi diskte var mi           : false
UretilenSinif.class sinif dosyasi diskte var mi           : true
uretilen sinifin hesapla(10) sonucu                       : 11
```

`Hesaplayici` arayüzünün bir kaynak dosyası var ve `javac` ile derlendi; `UretilenSinif`'in
hiç yok (GL16) — `uretilenD` dizininde yalnız `.class` dosyası duruyor. Buna rağmen bu sınıf
`Hesaplayici` arayüzüne atanabiliyor ve `hesapla` yöntemi arayüz üzerinden çağrılabiliyor,
sonuç doğru: `10 + 1 = 11` (GL17). Sanal makine için önemli olan, sınıf dosyasının
formatına uyması ve bildirdiği arayüzün istediği imzayı taşıması; o dosyayı kimin, hangi
kaynak dilden, hangi araçla ürettiği sanal makinenin sınıf yükleme kararına hiç girmiyor.

#### Çağıran Fark Görmeden Çağırıyor

**GL18.** Çağıranın derleme sınıf yolunda yalnız arayüz bulunur; hangi sınıfın
gerçekleştirimi sağlayacağı çağıranın derleme anında bilmediği bir karardır — kaynak dilinin
adı dahil. **GL19.** Aynı arayüze karşı derlenmiş çağıran, kaynağı Java ile yazılmış bir
gerçekleştirim ile kaynağı hiç olmayan bir gerçekleştirim arasında hiçbir davranış farkı
görmez.

```java
// FarkGormeden.java — kaynagi Java olan gerceklestirim ile kaynagi hic olmayan uretilen sinif, cagirandan ayirt edilemiyor
import java.net.*;
import java.nio.file.*;
import java.util.*;

public class FarkGormeden {
    static String hesaplayiciKaynagi() {
        return "public interface Hesaplayici { int hesapla(int girdi); }";
    }

    static String gercekSinifKaynagi() {
        return "public class GercekSinif implements Hesaplayici {\n"
             + "  public int hesapla(int girdi) { return girdi + 1; }\n"
             + "}";
    }

    static String cagiranKaynagi() {
        return "public class Cagiran {\n"
             + "  public static java.util.List<Integer> sonuclar = new java.util.ArrayList<>();\n"
             + "  public static void main (String[] a) throws Exception {\n"
             + "    String sinifAdi = a[0];\n"
             + "    Hesaplayici h = (Hesaplayici) Class.forName(sinifAdi).getDeclaredConstructor().newInstance();\n"
             + "    for (int girdi : new int[]{ 1, 5, 100 }) sonuclar.add(h.hesapla(girdi));\n"
             + "  }\n"
             + "}";
    }

    public static void main(String[] args) throws Exception {
        Path kok = Files.createTempDirectory("gl18");
        Path kaynak = kok.resolve("kaynak");

        Path sHesaplayici = Arac.yaz(kaynak, "Hesaplayici", hesaplayiciKaynagi());
        Path sGercek = Arac.yaz(kaynak, "GercekSinif", gercekSinifKaynagi());
        Path sCagiran = Arac.yaz(kaynak, "Cagiran", cagiranKaynagi());

        Path arayuzD = kok.resolve("arayuz");
        Path gercekD = kok.resolve("gercek");
        Path cagiranD = kok.resolve("cagiran");
        Path uretilenD = kok.resolve("uretilen");

        Arac.derle(arayuzD, List.of(), List.of(sHesaplayici));
        Arac.derle(gercekD, List.of(arayuzD), List.of(sGercek));
        Arac.derle(cagiranD, List.of(arayuzD), List.of(sCagiran));

        Files.createDirectories(uretilenD);
        byte[] bayt = Arac.uret("UretilenSinif", "Hesaplayici", "hesapla", 1);
        Files.write(uretilenD.resolve("UretilenSinif.class"), bayt);

        Object gercekSonuc = calistirVeOku(List.of(cagiranD, arayuzD, gercekD), "GercekSinif");
        Object uretilenSonuc = calistirVeOku(List.of(cagiranD, arayuzD, uretilenD), "UretilenSinif");

        Arac.goster("kaynagi Java ile yazilmis GercekSinif'in sonuclari", gercekSonuc);
        Arac.goster("kaynagi hic olmayan UretilenSinif'in sonuclari", uretilenSonuc);
        Arac.goster("ikisi birebir ayni mi", gercekSonuc.equals(uretilenSonuc));

        Set<String> atif = Arac.atiflar(cagiranD.resolve("Cagiran.class"));
        Arac.goster("cagiranin sinif dosyasi Hesaplayici'yi aniyor mu", atif.contains("Hesaplayici"));
        Arac.goster("cagiranin sinif dosyasi GercekSinif'i aniyor mu", atif.contains("GercekSinif"));
        Arac.goster("cagiranin sinif dosyasi UretilenSinif'i aniyor mu", atif.contains("UretilenSinif"));
    }

    static Object calistirVeOku(List<Path> sinifYolu, String argSinifAdi) throws Exception {
        URL[] url = new URL[sinifYolu.size()];
        for (int i = 0; i < url.length; i++) url[i] = sinifYolu.get(i).toUri().toURL();
        try (URLClassLoader y = new URLClassLoader(url, FarkGormeden.class.getClassLoader().getParent())) {
            Class<?> cagiran = Class.forName("Cagiran", true, y);
            cagiran.getMethod("main", String[].class).invoke(null, (Object) new String[]{ argSinifAdi });
            return cagiran.getField("sonuclar").get(null);
        }
    }
}
```

```
kaynagi Java ile yazilmis GercekSinif'in sonuclari        : [2, 6, 101]
kaynagi hic olmayan UretilenSinif'in sonuclari            : [2, 6, 101]
ikisi birebir ayni mi                                     : true
cagiranin sinif dosyasi Hesaplayici'yi aniyor mu          : true
cagiranin sinif dosyasi GercekSinif'i aniyor mu           : false
cagiranin sinif dosyasi UretilenSinif'i aniyor mu         : false
```

`Cagiran` kaynağında ne `GercekSinif` ne `UretilenSinif` adı hiç geçiyor; hangi sınıfın
kullanılacağına komut satırı argümanı `sinifAdi` karar veriyor ve bu karar koşum sınıf
yoluyla birlikte dışarıdan geliyor (GL18, ilk dersteki cephe/gerçekleştirim ayrımının aynı
kalıbı). Üç girdi üzerinden alınan sonuç listesi — `[2, 6, 101]` — iki koşumda da birebir
aynı (GL19). Sabit havuz okuması bunu doğruluyor: `Cagiran.class` yalnız `Hesaplayici`'yi
anıyor, ne `GercekSinif`'i ne `UretilenSinif`'i. Çağıranın gözünden iki gerçekleştirim
arasında hiçbir fark yok, çünkü çağıranın gördüğü tek şey arayüz ve arayüzün verdiği söz;
sınıfın kaynağının bir derleyiciden mi yoksa doğrudan bir bayt dizisi kurucusundan mı
geldiği bu sözün parçası değil.

#### Sınırlayıcı Ölçüm: Söz Diziminde Karşılığı Olmayan Bir Üye

**GL20.** Sanal makinenin bir üye adına koyduğu kısıt Java dilinin bir tanıtıcıya koyduğu kısıttan
çok daha gevşektir; birkaç ayrılmış karakterin (`.`, `;`, `[`, `/`) dışında kalan hemen her dizge
geçerli bir üye adıdır; yöntem adları ayrıca `<` ve `>` taşıyamaz — bu iki karakter yalnız
`<init>` ve `<clinit>` özel adlarında bulunur. **GL21.** Böyle bir ad taşıyan bir yöntem yalnız
üretici tarafından ve yansıma (reflection) yoluyla çağrılabilir; kaynak metinde hiçbir çağrı
satırı bu adı yazamaz, çünkü derleyicinin sözdizimi onu bir tanıtıcı olarak kabul etmez.

```java
// SozDizimiDisi.java — sinif dosyasinin uyesi, hicbir Java kaynagi tarafindan yazilamayacak bir ad tasiyabiliyor
import java.lang.classfile.*;
import java.lang.constant.*;
import java.lang.reflect.*;
import java.net.*;
import java.nio.file.*;

public class SozDizimiDisi {
    public static void main(String[] args) throws Exception {
        Path kok = Files.createTempDirectory("gl20");
        Path uretilenD = kok.resolve("uretilen");
        Files.createDirectories(uretilenD);

        String gecerliAd = "hesapla";
        String gecersizAd = "2#kat";

        ClassDesc kendi = ClassDesc.of("IkiYontemliSinif");
        byte[] bayt = ClassFile.of().build(kendi, cb -> {
            cb.withFlags(ClassFile.ACC_PUBLIC);
            cb.withMethodBody(ConstantDescs.INIT_NAME, ConstantDescs.MTD_void, ClassFile.ACC_PUBLIC, code -> {
                code.aload(0);
                code.invokespecial(ConstantDescs.CD_Object, ConstantDescs.INIT_NAME, ConstantDescs.MTD_void);
                code.return_();
            });
            cb.withMethodBody(gecerliAd, MethodTypeDesc.of(ConstantDescs.CD_int, ConstantDescs.CD_int),
                    ClassFile.ACC_PUBLIC, code -> {
                        code.iload(1); code.iconst_1(); code.iadd(); code.ireturn();
                    });
            cb.withMethodBody(gecersizAd, MethodTypeDesc.of(ConstantDescs.CD_int, ConstantDescs.CD_int),
                    ClassFile.ACC_PUBLIC, code -> {
                        code.iload(1); code.loadConstant(2); code.imul(); code.ireturn();
                    });
        });
        Path sinifDosyasi = uretilenD.resolve("IkiYontemliSinif.class");
        Files.write(sinifDosyasi, bayt);

        URL[] url = { uretilenD.toUri().toURL() };
        try (URLClassLoader y = new URLClassLoader(url, SozDizimiDisi.class.getClassLoader().getParent())) {
            Class<?> sinif = Class.forName("IkiYontemliSinif", true, y);
            Object ornek = sinif.getDeclaredConstructor().newInstance();

            Method gecerli = sinif.getMethod(gecerliAd, int.class);
            Method gecersiz = sinif.getMethod(gecersizAd, int.class);

            boolean gecerliTaniticiMi = gecersizAd.length() > 0
                    && Character.isJavaIdentifierStart(gecersizAd.charAt(0))
                    && gecersizAd.chars().allMatch(Character::isJavaIdentifierPart);

            Arac.goster("gecerli addaki yontemin (" + gecerliAd + ") cagri sonucu", gecerli.invoke(ornek, 10));
            Arac.goster("gecersiz addaki yontemin (" + gecersizAd + ") cagri sonucu", gecersiz.invoke(ornek, 10));
            Arac.goster("\"" + gecersizAd + "\" gecerli bir Java taniticisi mi", gecerliTaniticiMi);
        }
    }
}
```

```
gecerli addaki yontemin (hesapla) cagri sonucu            : 11
gecersiz addaki yontemin (2#kat) cagri sonucu             : 20
"2#kat" gecerli bir Java taniticisi mi                    : false
```

`IkiYontemliSinif` iki yöntem taşıyor: `hesapla`, geçerli bir Java tanıtıcısı ve `2#kat`,
değil — rakamla başlıyor ve bir tanıtıcının taşıyamayacağı `#` karakterini içeriyor.
`Character.isJavaIdentifierStart`/`isJavaIdentifierPart` denetimi bunu doğruluyor: `false`
(GL20). Buna rağmen sanal makine bu adı reddetmiyor; sınıf yükleniyor, `getMethod("2#kat",
...)` onu buluyor, `invoke` onu çağırıyor ve sonuç doğru: `10 * 2 = 20`. Bu, dersin
sınırlayıcı ölçümü: koşum sınıf yoluna giren bir üye, kaynak dilin sözdizim kurallarına
uymak zorunda değil, yalnız sınıf dosyası biçiminin kurallarına uymak zorunda. Bu ayrım tek
bir cümleyle sınırlanır — paylaşılan şey burada **anlambilim değil**, yalnız **biçim**dir:
`2#kat` adlı yöntemin ne anlama geldiğini, hangi sözleşmeyi karşıladığını sanal makine hiç
bilmiyor, yalnız çağrılabilir bir üye olarak kabul ediyor. Bu satırı hiçbir Java kaynak
dosyası yazamaz (GL21); üreten taraf onu doğrudan bayt dizisine yazdı, çağıran taraf da onu
yalnız bir dizge olarak sağlayan yansıma ile buldu.

#### Özet

- Bir sınıf dosyası üretmenin zorunlu yolu kaynak metin yazıp derlemek değildir; sınıf
  dosyası biçimini doğrudan üreten bir yordam da sanal makinenin yükleyeceği bir sınıf
  dosyası üretebilir.
- Böyle üretilen bir sınıf, bir arayüzü gerçekleştirdiğini bildirdiği ve arayüzün istediği
  imzayı taşıdığı sürece, kaynaktan derlenmiş bir sınıf gibi yüklenir ve çağrılır.
- Çağıranın derleme sınıf yolunda yalnız arayüz bulunur; kaynağı Java olan bir
  gerçekleştirim ile kaynağı hiç olmayan bir gerçekleştirim arasında çağıranın gördüğü ya
  da sınıf dosyasının andığı hiçbir fark yoktur.
- Sanal makinenin bir üye adına koyduğu kısıt, Java dilinin bir tanıtıcıya koyduğu
  kısıttan çok daha gevşektir; birkaç ayrılmış karakterin dışında kalan hemen her dizge
  geçerli bir üye adıdır.
- Sınırlayıcı ölçüm: üretilen bir sınıf dosyası, hiçbir Java kaynağının yazamayacağı bir
  üye adı taşıyabilir; bu üye yalnız yansıma ile, adı bir dizge olarak vererek
  çağrılabilir.

#### Kurs Kapanışı

Kurs tek bir soruyla açıldı: bir Java programı iki ayrı sınıf yoluna karşı yaşıyor —
derlendiği yola ve koştuğu yola — ve kaynak metin ikisini de göremiyor. On iki ders bu
soruyu dört ayrı düzenekte sordu: derleme aracının bildirimden iki yol çıkarması, testin
üçüncü bir yol eklemesi, günlükleme cephesinin bu ayrımı **isteyerek** kurması ve bu son
derste koşum sınıf yolunun kaynak dilini hiç görmediğinin gösterilmesi.

Birinci iddia doğrulandı: derlenen metin, koşacak programı belirlemiyor — bu derste
çağıranın gördüğü tek şey bir arayüzdü ve arkasındaki sınıfın bir derleyiciden geçip
geçmediği fark etmedi. İkinci iddia, kusurun çoğunlukla gürültülü olmadığı, günlükleme
cephesinde bir kez daha görüldü: gerçekleştirim bulunmadığında çağıran çökmüyor, yalnız
konuşmuyor. Üçüncü iddia — bağımlılığın yönünün derlenmiş üründe okunabilen bir olgu olduğu —
kursun üç konusunda da sabit havuzdan okunarak sınandı. Sınır da baştan beri aynıydı:
"derlendi" bir kanıt değil.

| Ders | Ölçülen davranış | Gereken yol (derleme · koşum · ikisi) | Sınırlayıcı ölçüm |
|---|---|---|---|
| Derleme Aracı Modeli | 3 bildirilen ögeden 4 evre türüyor; sıra bildirimin yazılış sırasından bağımsız | ikisi (aynı bildirimden iki yol birden çıkıyor) | Kaynakta kalan açıklama tipi sabit havuzda hiç görünmüyor |
| Bağımlılık Yönetimi | Altı tipte dağılım 1 derleme, 3 koşum, 2 ikisi; dört kusurun ikisi sessiz | derleme (1) · koşum (3) · ikisi (2) | Koşum yolunda gerekmemek bağımlılığın yokluğu değil, değerin gömülmüş olması olabilir |
| Çoklu Modül Projeler | Üç sınır kurulumundan biri derlemede, biri koşumda yakalanıyor, biri hiç yakalanmıyor | derleme · koşum (aynı tipin iki kullanımı) | Sınırı kuran dizin değil yükleyicinin kararı; iki sınıf aynı dizinde kalıyor |
| Paketleme ve Çalıştırılabilir Çıktı | Kendine yeten arşiv tek başına koşuyor, dışa muhtaç arşiv koşum hatasıyla düşüyor | koşum (arşivin taşıdığı yol) | Giriş noktası kaynakta değil bildirimde; derlemenin geçmesi arşivin yeterliğini göstermiyor |
| Birim Test Çerçevesi | Üretim yolu bir tip, test yolu üç tip; çerçeve tipi eksikken aynı sınıf koşumda düşüyor | ikisi (test yolu üçüncü yol) | Çerçevenin çağırdığı sıra doğruluğu vermiyor: aynı iddia sökmeden önce geçiyor, sonra düşüyor |
| Sahteleme | Aynı derlenmiş test iki koşum yoluyla iki ayrı sonuç veriyor, ikisi de hatasız | koşum (sahte tipin yeri) | İmza ayrıldığında kusur derlemede değil koşumda düşüyor, nedeni test metninde görünmüyor |
| Parametreli Testler | Tek gövde üç veri satırına karşı çağrılıyor; bağımsız kurulumda üçü de geçiyor | koşum (veri bildirimi) | Kurulum paylaşıldığında hangi satırın geçtiği veri sırasına bağlı hâle geliyor |
| Tümleşim Testleri | Yolun içeriği bir tipten iki tipe çıkıyor; aynı istek birimde sessiz, tümleşimde istisna | koşum (gerçek gerçekleştirim ve bağımlısı) | Gerçek tipi geri koymak testi doğrulamıyor, yalnız başka bir kusur sınıfını görünür kılıyor |
| Davranış Odaklı Test | Dört adımın üçü eşleşiyor; eksik adım bir kurulumda sessiz, ötekinde koşumu durduruyor | koşum (adım haritası) | Tam eşleşme doğruluğu göstermiyor: iddiası kaldırılmış harita yanlış değerle geçti diyor |
| Günlükleme Cephesi ve Gerçekleştirim | Çağıranın sınıf dosyası cepheyi anıp gerçekleştirimi anmaması; gerçekleştirimin servis bildirimiyle koşum sınıf yoluna girip sırayla seçilmesi | ikisi (cephe) · koşum (gerçekleştirim) | Gerçekleştirim hiç bulunmadığında çağıran derlenip hatasız koşar ama hiçbir kayıt üretmez |
| Günlük Düzeyleri ve Yapılandırılmış Günlük | Aynı çağrı kümesinin iki ayrı koşum eşiğinde ürettiği kayıt sayısı; alan tutarlılığının okunabilirliğe etkisi | koşum (eşik ve işleyici ayarı, alan bildirimi) | Eşiğin altında kalan çağrının argümanı dizgi birleştirmeyle yine değerlendirilir, `Supplier` ile değerlendirilmez |
| JVM Üzerinde Diğer Diller | Kaynak metni olmadan üretilen bir sınıfın, arayüzü gerçekleştirdiği sürece derlenmiş bir sınıf gibi çağrılması | koşum (üretilen sınıf dosyası) | Üretilen sınıf dosyası Java söz diziminde karşılığı olmayan bir üye adı taşıyabilir, çağıran onu yalnız yansıma ile çağırabilir |

Derleme, Test ve Ekosistem burada kapanıyor. Kursun sorduğu soru — bu tip hangi sınıf
yolunda bulunmak zorunda — bir Java programının derlenmesiyle koşması arasındaki her adımda
hep durur; kaynak metin bu sorunun yanıtını hiçbir zaman kendiliğinden vermiyor.

