İçeriğe geç
academia.sh

Ders 09 / 19

Doğrulama ve Hata İşleme

Dört alanın üçü doğrulama kuralını bir açıklamadan alıyor, biri hiç kural taşımıyor. Kural bozulduğunda ortak hata yanıtının zarfı nesne kabının varsayılanından, alan mesajları ise açıklamanın kendisinden geliyor; aynı geçersiz nesne kap dışında doğrudan işleyiciye verildiğinde hiçbir kural uygulanmıyor ve kusur sessiz kalıyor.

İçindekiler

Önceki ders bir isteğin hangi yönteme ulaştığını ölçtü, ama o yönteme ulaşan verinin doğru biçimde olup olmadığı hiç sorulmadı. Bu ders bunu ele alıyor: bir alanın doğrulama kuralının kaynakta bir çağrı mı yoksa bir açıklama mı olduğunu, ve kural bozulduğunda ortak hata yanıtının hangi alanları nereden topladığını ölçüyor.

Doğrulama burada da aynı üç kaynak sorusuna tabi: bir alanın kuralı kaynakta bir ifadeyle mi yazılı, yoksa yalnız bir işaretle mi belirtiliyor, yoksa kap alan hakkında hiçbir şey söylemeden mi geçiyor? Bu derste dördüncü bir olasılık daha var — bir alanın hiçbir kuralı olmaması — ve bu, önceki iki dersten farklı bir ölçüm ekliyor: kap yalnız kuralları nereden aldığını değil, hangi alanların hiç kural taşımadığını da ortaya koyuyor.

Hata işleme de bu dersin konusu, ve iki anlamda: bir isteğin hangi kuralı bozduğunu toplamak (doğrulama), ve bu toplamı çağırana nasıl bir biçimde geri vermek (hata işleme). İkisi ayrı sorular gibi görünse de aynı kapta yaşıyor — kap bir isteği reddederken hem hangi alanların bozuk olduğunu bulmak hem de bunu tek bir tutarlı yanıta dönüştürmek zorunda. Bu derste her iki iş de aynı istek yönteminde birleşiyor, ama kaynakları farklı kalıyor: hangi kuralın bozulduğu açıklamadan geliyor, bozulan kuralların nasıl bir araya toplanacağı ise kabın kendi kararı.

Dört Alanın Üçü Açıklamadan Kural Alıyor

  • WV11. Kural kontrolü yalnız alan düzeyinde yapılıyor; yöntem parametreleri, iç içe nesneler ya da koleksiyon elemanları burada doğrulanmıyor.
  • WV12. Bir alanın birden çok kuralı olabilir, ama bu derste her alan en çok bir kural taşıyor; kuralların birbirini nasıl sıraladığı ayrı bir ölçüm konusu.

KayitIstegi dört alan taşıyor: ad, miktar, kategori, kod. İlk üçü sırasıyla @ZorunluAlan, @PozitifDeger ve @Secenekler açıklamalarını taşıyor; dördüncüsü, kod, hiçbir açıklama taşımıyor. Kaynakta bu dört alanı doğrulayan hiçbir çağrı yok — dogrula yöntemi hangi alanların hangi kurala tabi olduğunu bilmiyor, yalnız yansımayla her alanı tek tek gözden geçirip üzerinde hangi açıklamanın durduğuna bakıyor. kod alanı için hiçbir açıklama bulunmadığından, bu alan hiçbir zaman doğrulanmıyor — istenen herhangi bir değer kabul ediliyor.

// Kap.java — dogrulama kurallari aciklamadan doguyor, ortak hata yaniti kabin varsayilani
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.*;

public class Kap {

    @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD)
    @interface ZorunluAlan { String mesaj() default "alan bos olamaz"; }

    @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD)
    @interface PozitifDeger { String mesaj() default "deger sifirdan buyuk olmali"; }

    @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD)
    @interface Secenekler { String[] deger(); String mesaj() default "taninmayan secenek"; }

    interface Isleyici { String calistir(Object istek); }

    List<String> dogrula(Object nesne) throws ReflectiveOperationException {
        List<String> hatalar = new ArrayList<>();
        for (Field f : nesne.getClass().getDeclaredFields()) {
            f.setAccessible(true);
            Object deger = f.get(nesne);

            ZorunluAlan z = f.getAnnotation(ZorunluAlan.class);
            if (z != null && (deger == null || deger.toString().isBlank()))
                hatalar.add(f.getName() + ": " + z.mesaj());

            PozitifDeger p = f.getAnnotation(PozitifDeger.class);
            if (p != null && deger instanceof Integer i && i <= 0)
                hatalar.add(f.getName() + ": " + p.mesaj());

            Secenekler s = f.getAnnotation(Secenekler.class);
            if (s != null && !Arrays.asList(s.deger()).contains(deger))
                hatalar.add(f.getName() + ": " + s.mesaj());
        }
        return hatalar;
    }

    String istek(Object istekNesnesi, Isleyici isleyici) throws ReflectiveOperationException {
        List<String> hatalar = dogrula(istekNesnesi);
        if (!hatalar.isEmpty())
            return "hata; alan-sayisi=" + hatalar.size() + "; alanlar=" + hatalar;
        return isleyici.calistir(istekNesnesi);
    }

    static int kuralliAlanSayisi(Class<?> tip) {
        int n = 0;
        for (Field f : tip.getDeclaredFields())
            if (f.isAnnotationPresent(ZorunluAlan.class) || f.isAnnotationPresent(PozitifDeger.class)
                    || f.isAnnotationPresent(Secenekler.class)) n++;
        return n;
    }

    public static void main(String[] args) throws Exception {
        Kap kap = new Kap();
        Isleyici isleyici = istek -> {
            KayitIstegi k = (KayitIstegi) istek;
            return "kayit-tamam: ad=" + k.ad + ", miktar=" + k.miktar + ", kategori=" + k.kategori;
        };

        System.out.println("-- kacinin kurali aciklamadan geldi --");
        int toplamAlan = KayitIstegi.class.getDeclaredFields().length;
        int kuralliAlan = kuralliAlanSayisi(KayitIstegi.class);
        System.out.println("toplam alan: " + toplamAlan);
        System.out.println("aciklamayla kural tasiyan alan: " + kuralliAlan);

        KayitIstegi gecerli = new KayitIstegi("vida", 12, "elektronik", 501);
        System.out.println("-- gecerli istek --");
        System.out.println(kap.istek(gecerli, isleyici));

        KayitIstegi gecersiz = new KayitIstegi("", -3, "mobilya", 502);
        System.out.println("-- gecersiz istek: kap uzerinden --");
        System.out.println(kap.istek(gecersiz, isleyici));

        System.out.println("-- ayni gecersiz nesne, dogrudan isleyiciye verildi (kap devre disi) --");
        System.out.println(isleyici.calistir(gecersiz));
        System.out.println("kusur turu: sessiz (istisna yok, gecersiz veri isleyiciye ulasti)");
    }
}

class KayitIstegi {
    @Kap.ZorunluAlan String ad;
    @Kap.PozitifDeger int miktar;
    @Kap.Secenekler(deger = {"gida", "elektronik", "giyim"}) String kategori;
    long kod;

    KayitIstegi(String ad, int miktar, String kategori, long kod) {
        this.ad = ad;
        this.miktar = miktar;
        this.kategori = kategori;
        this.kod = kod;
    }
}
-- kacinin kurali aciklamadan geldi --
toplam alan: 4
aciklamayla kural tasiyan alan: 3
-- gecerli istek --
kayit-tamam: ad=vida, miktar=12, kategori=elektronik
-- gecersiz istek: kap uzerinden --
hata; alan-sayisi=3; alanlar=[ad: alan bos olamaz, miktar: deger sifirdan buyuk olmali, kategori: taninmayan secenek]
-- ayni gecersiz nesne, dogrudan isleyiciye verildi (kap devre disi) --
kayit-tamam: ad=, miktar=-3, kategori=mobilya
kusur turu: sessiz (istisna yok, gecersiz veri isleyiciye ulasti)

Dört alanın üçü kural taşıyor, biri taşımıyor. Bu oranın kendisi bir ölçüm: kaynağı okuyan biri kod alanına bakıp hiçbir kısıtlama görmüyor, ve bu doğru bir okuma — kural gerçekten yok, kap onu hiç doğrulamıyor. Diğer üç alan için durum farklı: kaynakta ad’ın boş olamayacağını söyleyen hiçbir if yok, miktar’ın sıfırdan büyük olması gerektiğini söyleyen hiçbir karşılaştırma yok. Bu kurallar yalnız açıklamalarda yazılı, ve onları okuyan dogrula yöntemi olmadan hiçbir etkileri yok. Geçerli isteğin kayit-tamam ile sonuçlanması, geçersiz isteğin ise üç ayrı hatayla dönmesi arasındaki fark, kodun hiçbir satırında açıkça görünmeyen bu üç kuralın gerçekten çalıştığının kanıtı.

kod alanının kural taşımaması, bir eksiklik olarak da okunabilir, bilinçli bir seçim olarak da. Kaynak metin bu ikisini ayırt etmiyor — bir alanın üzerinde açıklama yoksa, o alanın “kasıtlı olarak serbest bırakıldığı” ile “kuralı yazmayı unuttuk” arasındaki fark kaynakta hiçbir iz bırakmıyor. Önceki iki derste eksik olan şey bir davranıştı (bir halka, bir uç nokta); burada eksik olan şey bir kısıtlama, ve kısıtlamanın eksikliği kendini hiçbir zaman bir hatayla göstermiyor — yalnız her değerin kabul edildiği bir sessizlikle. Dört alanın üçünün kural taşıması, kalan birinin de aynı dikkatle gözden geçirildiği anlamına gelmiyor; kural sayısı kadar, kuralsız bırakılan alanların bilinçli bir liste olarak tutulması da bu yüzden ayrı bir disiplin gerektiriyor.

Ortak Hata Yanıtı: Zarf Varsayılan, İçerik Açıklama

  • WV13. Hata yanıtının zarfı ("hata; alan-sayisi=...; alanlar=..." biçimi) istek yönteminin gövdesine yazılı; hiçbir açıklama bu biçimi belirlemiyor.

Geçersiz isteğe verilen yanıt iki parçadan oluşuyor, ve ikisi de farklı bir kaynaktan geliyor. Zarf — yanıtın kaç hata taşıdığını söyleyen ve hataları bir liste olarak saran biçim — Kap.istek yönteminin kendi gövdesinde yazılı bir dizge kalıbı; bu, önceki derslerdeki varsayılan davranışlarla aynı aileden: kaynakta hiçbir alan bunu istemedi, kap her geçersiz istekte bu biçimi kendiliğinden üretiyor. İçerik ise farklı: "ad: alan bos olamaz" ve "kategori: taninmayan secenek" dizgeleri doğrudan ZorunluAlan ile Secenekler açıklamalarının mesaj alanından geliyor. Bu iki açıklama hiçbir özel mesaj belirtmedi, bu yüzden kendi varsayılan mesajları kullanıldı — ama bir geliştirici @Kap.ZorunluAlan(mesaj = "ad girilmeden kayit olusturulamaz") yazsaydı, yanıtta o metin görünürdü. Zarf sabit, içerik açıklamanın elindedir.

Bu ayrım pratik bir sonuç doğuruyor: ortak hata yanıtının biçimini değiştirmek için (örneğin alan sayısını en başa değil en sona koymak) kap’ın kendi kodu değiştirilmeli, hiçbir açıklama bunu etkilemez. Ama tek bir alanın hata mesajını değiştirmek için kap’a hiç dokunmaya gerek yok — açıklamanın mesaj alanı yeterli. İki değişiklik türü kaynakta iki ayrı yerde durur ve biri diğerinden çok daha ucuz: mesaj metnini değiştirmek bir açıklama parametresi düzenlemektir, zarfı değiştirmek kabın kendisini değiştirmektir.

Bu ayrımın bir de tersi var: dogrula yöntemi hataları topluyor ama hangi kuralın önce kontrol edileceğine karar veriyor — burada alan bildirim sırasıyla aynı, ad, sonra miktar, sonra kategori. Bu sıra da kabın kendi kararı; hiçbir açıklama “önce beni kontrol et” demiyor. Sıralama görünüşte önemsiz duruyor, çünkü bu derste tüm kurallar birlikte toplanıp tek bir yanıtta veriliyor — hiçbiri diğerini durdurmuyor. Ama bir kap “ilk hatada dur” biçiminde tasarlansaydı, bu sıra hangi hatanın çağırana ulaşıp hangisinin hiç görülmeyeceğini belirlerdi; o zaman sıra bir uygulama ayrıntısı olmaktan çıkıp davranışın kendisi olurdu. Bu derste seçilen “hepsini topla” yaklaşımı bu riski taşımıyor, ama seçim kabın kendisine ait — kaynakta hiçbir yerde “bütün hatalar toplansın” yazmıyor.

Kap Dışında Doğrulama Hiç Çalışmıyor

  • WV14. isleyici.calistir(gecersiz) çağrısı Kap.istek yöntemini hiç görmüyor; dogrula hiçbir zaman çağrılmıyor.
  • WV15. kod alanı zaten kural taşımadığı için hiçbir kurulumda doğrulanmıyor; aradaki fark yalnız diğer üç alanda görünüyor.

Son ölçüm aynı geçersiz nesneyi — boş ad, negatif miktar, tanınmayan kategori — kap’ı hiç devreye sokmadan doğrudan işleyiciye veriyor. Sonuç: kayit-tamam: ad=, miktar=-3, kategori=mobilya. Hiçbir istisna yok, hiçbir hata mesajı yok — işleyici geçersiz veriyle de aynı sadakatle çalışıyor, çünkü onun kendi kodu hiçbir doğrulama içermiyor. Doğrulama mantığı yalnız Kap.istek yönteminin içinde yaşıyor, ve bu yöntem çağrılmadığı sürece üç açıklamanın hiçbiri okunmuyor.

Bu, kursun tekrar eden sınırlayıcı kuralının bir başka örneği: bir kabın sağladığı garanti, yalnız o kabın çağrıldığı yol üzerinde geçerlidir. KayitIstegi sınıfının üzerindeki açıklamalar kaldırılmadı, hâlâ orada duruyor — ama onları okuyacak dogrula çağrısı devreye girmediği sürece, sınıf sıradan bir veri taşıyıcısından farksız. Bu, önceki iki derste görülenden biraz daha riskli bir sonuç doğuruyor, çünkü burada geçersiz veri yalnız yanlış bir yanıt üretmiyor — geçerliymiş gibi görünen, “tamam” diyen bir yanıt üretiyor. Kural bozulduğunda kap üzerinden geçen istek açıkça bir hata görüyor; kap dışından gelen aynı veri ise hiçbir uyarı almadan “tamam” yanıtıyla devam ediyor. Doğrulamanın var olması, onu uygulayan tek bir giriş noktası olduğu sürece bir güvence; birden fazla giriş noktası olduğunda — burada kap üzerinden gelen ve doğrudan çağrılan iki yol — güvence yalnız kullanılan yola bağlı kalıyor.

Bu, önceki iki dersteki sınırlayıcı ölçümlerle aynı köke iniyor ama sonucu bir derece daha ağır. Birinci derste kap dışında kalan halka yalnız sırasını kaybediyordu, ikinci derste elle çağrılan denetleyici yalnız kabın kayıt durumunu görmüyordu — ikisi de işlevsel olarak doğru çalışmaya devam ediyordu. Burada ise kap dışında kalan çağrı, geçersiz veriyi geçerliymiş gibi işliyor: isleyici’nin döndürdüğü dizge, doğru bir isteğin döndürdüğü dizgeyle aynı kayit-tamam önekini taşıyor. Çağıran taraf yanıtın biçimine bakarak iki durumu ayırt edemiyor — ayırt edebilmek için ad alanının boş olup olmadığını ayrıca kontrol etmesi gerekiyor, ki bu da doğrulamanın kap dışında yeniden yazılması anlamına gelir. Bir uygulamada bu tür bir ikinci giriş noktası genellikle bir arka plan işi, bir toplu içe aktarma adımı ya da bir başka bileşenin doğrudan çağrısı biçiminde ortaya çıkıyor; hangi biçimde olursa olsun, kap onun varlığından habersiz ve kural onun üzerinde hiç çalışmıyor.

Özet

  • Dört alanın üçü (ad, miktar, kategori) doğrulama kuralını bir açıklamadan aldı; dördüncüsü (kod) hiçbir kural taşımadı ve hiç doğrulanmadı.
  • Geçersiz istek kap üzerinden geçtiğinde ortak bir hata yanıtı üretti; yanıtın zarfı kabın kendi kodunda sabit, alan mesajları ise açıklamaların mesaj alanından geldi.
  • Bir açıklamanın mesajını değiştirmek yalnız açıklamayı düzenlemeyi gerektirdi; zarfı değiştirmek kabın kendi kodunu değiştirmeyi gerektirirdi.
  • Aynı geçersiz nesne kap dışında doğrudan işleyiciye verildiğinde hiçbir kural uygulanmadı ve işleyici “tamam” anlamına gelen bir yanıt üretti; kusur sessiz kaldı.
  • Doğrulamanın güvencesi yalnız kabın çağrıldığı yol üzerinde geçerli; kap dışındaki bir çağrı aynı sınıfı hiçbir kısıtlama olmadan kullanabiliyor.

Sonraki Adım

Bu ders bir isteğin verisinin doğru olup olmadığını ölçtü, ama o veri işlendikten sonra nereye yazıldığı hiç sorulmadı. Sıradaki ders bunu ele alıyor: bir depo arayüzünün gerçekleştirimini kabın nasıl sağladığını, ve aynı çağıranın yeniden derlenmeden iki ayrı gerçekleştirimle nasıl koşabildiğini ölçüyor.

İ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