İçeriğe geç
academia.sh

Ders 23 / 23

İstisna Yönetimi

Denetlenmeyen istisna modelinin, try ifadesinin, Result'ın ve eş yordamdaki yayılmanın ölçümü; aynı mesajlı iki istisnanın eşit sayılmadığının gösterilmesi ve kursun kapanışı.

Yayın Güncelleme

Bu derste

  • Kotlin'de istisna bildirmenin gerekmediğini ve try'ın değer üreten bir ifade olduğunu ölçer
  • runCatching, Result, getOrElse ve exceptionOrNull ile hatayı değer olarak taşır
  • coroutineScope içinde bir kardeşin başarısızlığının diğerini iptal edip dışarıya yayıldığını gösterir
  • Aynı mesajla kurulan iki istisnanın eşit sayılmadığını ve veri sınıfı istisnanın içerikle karşılaştırıldığını ölçer
İçindekiler

Önceki ders iki akışın eşit sayılmadığını ölçtü. Bu son ders değer yerine bir hatanın eşit olup olmadığını sorar: aynı mesajı taşıyan iki istisna eşit midir? Önce Kotlin’in hata modeli okunur. Java’da bir fonksiyon hangi istisnaları fırlatabileceğini throws ile bildirir ve çağıran onları yakalamak zorundadır. Kotlin’de böyle bir bildirim yoktur; bütün istisnalar denetlenmeyen istisna sayılır, yakalamak çağıranın isteğine kalır. Fırlatma throw ile, yakalama try ve catch ile yapılır. Ölçü sorusu yine aynıdır: bu iki değeri eşit yapan ne?

// Criterion.kt — iki degeri esit yapan olcutu siniflandirir
class Criterion(val label: String, first: Any?, twin: Any?, other: Any?) {
    val sameObject = first === twin
    val equalTwin = first == twin
    val equalOther = first == other

    val name: String = when {
        sameObject -> "tekil"
        !equalTwin -> "kimlik"
        !equalOther -> "icerik"
        else -> "kismi-icerik"
    }

    fun report() {
        println("$label: ayni-nesne=$sameObject, ikiz=$equalTwin, farkli=$equalOther -> $name")
    }
}

fun summarize(checks: List<Criterion>) {
    for (name in listOf("tekil", "kimlik", "icerik", "kismi-icerik")) {
        println("$name: ${checks.count { it.name == name }}")
    }
}

Denetlenmeyen İstisna ve try İfadesi

Aşağıdaki blok iki şeyi ölçer. readDisk Java’nın IOException istisnasını fırlatır, ama imzasında hiçbir bildirim yoktur ve derleyici bir şey istemez. try ise ifadedir: blok son ifadesinin değerini verir, catch dalı kendi değerini verir ve finally her durumda koşar ama değer üretmez. Günlük listesi sırayı gösterir.

// Main.kt — denetlenmeyen istisna, try ifadesi ve finally
import java.io.IOException

fun readDisk(): String {
    throw IOException("disk")
}

fun withdraw(balance: Int, amount: Int): Int {
    if (amount > balance) throw IllegalStateException("yetersiz")
    return balance - amount
}

fun main() {
    val log = mutableListOf<String>()
    val left = try {
        log.add("dene")
        withdraw(100, 500)
    } catch (e: IllegalStateException) {
        log.add("yakala")
        -1
    } finally {
        log.add("son")
    }
    println(left)
    println(log)
    try {
        readDisk()
    } catch (e: IOException) {
        println(e::class.simpleName)
    }
    println(withdraw(100, 30))
}
-1
[dene, yakala, son]
IOException
70

Birinci satır -1: withdraw fırlattığı için try bloğunun değeri atılıp catch dalının -1 değeri left’e atandı. Günlük [dene, yakala, son] sırasını söyler; finally en sonda, değer belli olduktan sonra koştu. Üçüncü satır IOException adını verir; catch yazıldığı için yakalandı, yazılmasaydı da derleme geçerdi. Son satır fırlatmayan yolun 70 döndüğünü gösterir.

KE35. Bu ölçümler tek iş parçacıklı, return içermeyen try ifadeleri için. finally içinden return ya da yeni istisna fırlatmanın sonucu, use ile kaynak kapatma ve Error sınıfları ölçülmedi.

runCatching ve Result

Hatayı yakalamak yerine değer olarak taşımak için runCatching { } kullanılır. Blok döndüyse Result başarılı, fırlattıysa başarısızdır. getOrElse başarısızlıkta yedek değeri verir, exceptionOrNull başarılıysa null, değilse istisnanın kendisini verir. Önce yardımcı dosyada kendi istisna sınıfı tanımlanır; ana blok onu fırlatan bir fonksiyon kurar.

// Errors.kt — kendi istisna sinifi
class LimitException(val limit: Int) : Exception("limit asildi")
// Main.kt — runCatching ve Result
fun parse(text: String): Int {
    if (text.length > 3) throw LimitException(3)
    return text.toInt()
}

fun main() {
    val ok = runCatching { parse("42") }
    val badFormat = runCatching { parse("x") }
    val tooLong = runCatching { parse("12345") }
    println(ok.getOrElse { -1 })
    println(badFormat.getOrElse { -1 })
    println(ok.exceptionOrNull())
    println(badFormat.exceptionOrNull()?.let { it::class.simpleName })
    val error = tooLong.exceptionOrNull()
    if (error is LimitException) {
        println(error.limit)
    }
}
42
-1
null
NumberFormatException
3

ok başarılı olduğu için getOrElse 42 verdi; "x".toInt() NumberFormatException fırlattığı için ikinci satır yedek değer olan -1. Üçüncü satır başarılı sonuçta exceptionOrNull’un null olduğunu, dördüncü satır başarısız sonuçta istisnanın tipini gösterir. Son satır kendi istisna sınıfının taşıdığı limit değerini okur: is denetimi sonrası error akıllı tip dönüşümüyle LimitException olur.

KE36. Bu ölçümler Int sonuçlu runCatching çağrıları ve Exception altındaki bir sınıf için. map, recover ve onFailure zincirleri, getOrThrow ve Result’ın fonksiyon dönüş tipi olarak kullanımı ölçülmedi.

Eş Yordamda Yayılma

Yapılandırılmış eşzamanlılıkta bir çocuk eş yordam başarısız olursa kapsam iptal edilir: kardeşler iptal alır ve istisna coroutineScope’tan dışarı çıkar. Aşağıdaki blokta ilk çocuk bin milisaniye bekleyecek, ikincisi hemen fırlatacak. Dışarıda yakalanan istisnanın yalnız tipi basılır, mesajı basılmaz.

// Main.kt — eş yordamda istisnanın yayılması
import kotlinx.coroutines.*

fun main() = runBlocking {
    val log = mutableListOf<String>()
    val caught = try {
        coroutineScope {
            launch {
                try {
                    delay(1000)
                    log.add("bitti")
                } finally {
                    log.add("iptal")
                }
            }
            launch {
                throw IllegalStateException("kardes")
            }
        }
        "yok"
    } catch (e: Exception) {
        e::class.simpleName
    }
    println(caught)
    println(log)
}
IllegalStateException
[iptal]

Dışarıda yakalanan istisna çocuğun fırlattığı IllegalStateException’dır. Günlükte yalnız iptal vardır: bekleyen çocuğa iptal geldi, finally bloğu koştu ve bitti hiç yazılmadı. coroutineScope çağrısından sonraki "yok" satırına ise hiç gelinmedi. İstisna, eş yordamın kapsamını aşıp sıradan bir try ile yakalanabilir.

KE37. Bu ölçümler tek coroutineScope, tek runBlocking ve bir başarısız çocuk için. supervisorScope, CoroutineExceptionHandler ve birden çok başarısız çocuğun istisnalarının nasıl toplandığı ölçülmedi.

Ölçüt: Kimlik

Şimdi asıl soru. Aynı mesajla kurulan iki istisna eşit midir? Throwable sınıfı equals tanımlamaz, kimliğe düşer. Sınırlayıcı ölçümler Result’ı ve veri sınıfı olarak yazılan bir istisnayı ekler. Result.failure içindeki istisnayı taşır: iki ayrı istisna iki ayrı Result yapar, aynı istisna iki Result’ı eşit yapar.

// Main.kt — istisnanın ölçütü ve sınırlayıcı ölçümler
data class CodedError(val code: Int) : Exception("kod")

fun main() {
    val shared = IllegalStateException("m")
    val checks = listOf(
        Criterion("istisna", IllegalStateException("m"), IllegalStateException("m"), IllegalStateException("x")),
        Criterion("sonuc-iki-istisna", Result.failure<Int>(IllegalStateException("m")), Result.failure<Int>(IllegalStateException("m")), Result.failure<Int>(IllegalStateException("x"))),
        Criterion("sonuc-ayni-istisna", Result.failure<Int>(shared), Result.failure<Int>(shared), Result.failure<Int>(IllegalStateException("x"))),
        Criterion("sonuc-basari", Result.success(1), Result.success(1), Result.success(2)),
        Criterion("veri-sinifi-istisna", CodedError(7), CodedError(7), CodedError(8)),
    )
    for (check in checks) check.report()
    summarize(checks)
}
istisna: ayni-nesne=false, ikiz=false, farkli=false -> kimlik
sonuc-iki-istisna: ayni-nesne=false, ikiz=false, farkli=false -> kimlik
sonuc-ayni-istisna: ayni-nesne=false, ikiz=true, farkli=false -> icerik
sonuc-basari: ayni-nesne=false, ikiz=true, farkli=false -> icerik
veri-sinifi-istisna: ayni-nesne=false, ikiz=true, farkli=false -> icerik
tekil: 0
kimlik: 2
icerik: 3
kismi-icerik: 0

İlk satır bu dersin ölçütüdür: aynı mesajlı iki istisna eşit değildir, ölçüt kimlik. İkinci satırda iki ayrı istisnayı saran Result’lar da eşit değildir. Üçüncü satırda aynı istisnayı saran iki ayrı Result nesnesi eşittir: Result istisnanın ölçütünü devralır. Result.success(1) iki kez içerik verir. Veri sınıfı olarak yazılan CodedError ise aynı koduyla eşittir, çünkü üretilen equals, Exception’dan devralınan kimlik eşitliğini yeniden tanımlar. Sınırlayıcı ölçüm şudur: istisnanın ölçütünü tipi belirler, mesaj belirlemez.

KE38. Bu ölçümler IllegalStateException ve Result için. İstisnanın cause zinciri, stackTrace içeriği ve equals’i yazılmış başka istisna sınıfları ölçülmedi.

Özet

  • Kotlin’de throws yoktur; bütün istisnalar denetlenmeyen istisnadır. try değer üreten bir ifadedir ve finally en sonda koşar.
  • runCatching hatayı Result olarak taşır; getOrElse yedek değer, exceptionOrNull istisnayı verir.
  • coroutineScope içinde bir çocuk başarısız olunca kardeşler iptal edilir ve istisna dışarıya yayılır.
  • Aynı mesajlı iki istisna eşit değildir. Bu dersin ölçütü: kimlik.
  • Sınırlayıcı ölçüm: Result.failure istisnanın ölçütünü taşır; veri sınıfı olarak yazılan istisna aynı alanla içeriktir.

Kurs Kapanışı

Kurs yirmi üç dersle tek soruyu sordu ve her dersin ölçütü kaynak dersin özetinden alındı:

Ders Ölçülen değer Ölçüt Sınırlayıcı ölçüm
Kotlin’in Konumu veri sınıfı, JVM listesi içerik kurucudaki name farklıysa eşit değil
Değişkenler const val sabiti tekil çalışma anında kurulan aynı metin içerik
Temel Tipler kutulanan 1000 içerik 127 tekil; NaN statik tipe göre değişir
Dizgi Şablonları iki ayrı şablon içerik tek değişkenli şablon tekil
Denetim Akışı when dalı, sıradan sınıf kimlik is Box tutar; iki 1..3 içerik
Fonksiyonlar iki ::plain içerik listOf() varsayılanı tekil
Lambdalar iki ayrı lambda kimlik yakalamayan lambda tekil; ::inc içerik
Kapsam Fonksiyonları let ile copy içerik also ikizi tekil, ölçümü bozar
Null Güvenliği null ile null tekil dolu iki dizgi içerik; dolu ile null kimlik
Güvenli Çağrı ve Elvis iki güvenli çağrı içerik boş alıcıda iki null tekil
Sınıflar ve Yapıcılar equals’siz sınıf kimlik aynı kalıp equals’e göre içerik, kısmi içerik
Veri Sınıfları copy ile kurulan nesne içerik IntArray alanı kimlik, List alanı içerik
Mühürlü Tipler ve Numaralandırmalar numaralandırma girdisi tekil alt tipin türü ölçütü belirler
Nesne Bildirimleri iki okuma tekil nesne ifadesi her seferinde yeni, kimlik
Arayüzler ve Devretme by ile devreden iki nesne kimlik aynı temsilcili iki veri sınıfı eşit
Uzantı Fonksiyonları uzantı equals kimlik üye uzantıyı yener
Genellikler ve Varyans iki boş liste, iki tip parametresi tekil genel veri sınıfı alan değerine bakar
Koleksiyon Türleri liste eşitliği içerik küme sırası kısmi içerik; boş liste tekil
Dönüşüm İşlemleri iki map içerik distinct öğenin ölçütünü kullanır
Tembel Diziler iki sequenceOf kimlik toList() ile içerik
Eş Yordamlar iki Deferred kimlik await sonuçları içerik
Akışlar distinctUntilChanged içerik sıradan sınıfta 5 değer kalır; akış kimlik
İstisna Yönetimi aynı mesajlı iki istisna kimlik Result istisnanın ölçütünü taşır; veri sınıfı istisna içerik

Yirmi üç dersin dağılımı: tekil 5, kimlik 8, içerik 10, kısmi içerik 0. Kısmi içerik hiçbir dersin ölçütü olmadı; yalnız sınırlayıcı ölçümlerde göründü. Açılış dersinin çekirdeği ayrı bir sayımdır: sekiz değerin dağılımı 2/2/3/1’dir (tekil/kimlik/içerik/kısmi içerik). İkisi aynı şey değildir, çünkü çekirdek sekiz değer, tablo yirmi üç ders sayar.

Aynı mesajla kurulan iki istisnanın eşit çıkmaması, kursun bütün cevabını toplar: Kotlin’de eşitliği değerin görünüşü değil tipin kuralı verir. Sıradaki kurs Android Platformu ve Araçlar’dır; uygulamanın parçalarını ve bu parçaların yaşam döngülerini konu alır.

İ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