İçeriğe geç
academia.sh

Ders 02 / 12

Bağımlılık Yönetimi

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.

İçindekiler

Ö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.

  • AracSabiti değil ama Derini doğrudan çağıran bir yardımcı sınıf.

  • Derin — yalnız Aracın çağırdığı, Kullananda hiç doğrudan adlandırılmayan bir sınıf.

  • Cephe — tek yöntemli bir arayüz.

  • KayitA, KayitBCephenin 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 Cepheyi arıyor.

// 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.
// 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 beşi tek tek derlenip koşturularak elde edildi: her satırda aynı Kullanan kaynağı, o tipin bulunmadığı bir sınıf yoluna karşı önce derlenmeye, sonra koşturulmaya çalışılı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, Derini doğrudan çağırıyor — ama Kullanan, Aracı çağırırken Derini 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ı.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat