İçeriğe geç

Swift 6'nin Typed Throws Ozelliginin Gercek Gucunu Aciga Cikarmak: Error Chain'ler

Typed Throws'u bir bas agrisindan superguce nasil donustureceginizi kesfet -- temiz hata yonetimi ve guclu hata ayiklama icgorulerle.

Swift 6'nin Typed Throws Ozelliginin Gercek Gucunu Aciga Cikarmak: Error Chain'ler

Swift 6 nihayet en cok talep edilen ozelliklerden birini Swift’e getirdi: Typed Throws. Bu iyilestirme, bir fonksiyonun tam olarak hangi hata turlerini firlatabilecegini belirtmeni sagliyor ve Swift’in tur guvenligini hata yonetimine tasiyor. Ancak bu gucle birlikte “ic ice gecme cehennemi” diyebilecegim yeni bir zorluk geliyor – uygulamanin katmanlari arasinda hatalarin nasil yayildigini etkileyen bir sorun.

Bu yazida ic ice gecme sorununu aciklayacak ve bunu ErrorKit icerisinde boilerplate olmadan typed throws’u pratikte kullanilabilir kilan basit bir protokolle nasil cozdugumuzu gosterecegim. Bonus olarak, duzgun hata zincirlemenin hata ayiklama deneyimini nasil onemli olcude iyilestirebilecegini goreceksin.

Typed Throws: Vaad ve Sorun

Once Swift 6’da Typed Throws’un bize ne sagladigina bakalim:

// Instead of just 'throws', we can specify the error type
func processFile() throws(FileError) {
    if !fileExists {
        throw FileError.fileNotFound(fileName: "config.json")
    }
    // Implementation...
}

Bu, cagirma noktasinda daha iyi hata yonetimi sagliyor:

do {
    try processFile()
} catch FileError.fileNotFound(let fileName) {
    print("Could not find file: \(fileName)")
} catch FileError.readFailed {
    print("Could not read file")
}
// No generic catch needed if we've handled all possible FileError cases!

Faydalari acik:

  • Hata yonetiminin derleme zamani dogrulamasi

  • Catch bloklarinda as? ile tur donusumu gerektirmiyor

  • Cagiranlara tam olarak neyin yanlis gidebilecegini soyleyen, kendi kendini belgeleyen API

  • Hata case’leri icin IDE otomatik tamamlama

Ic Ice Gecme Cehennemi Sorunu

Sorun cok katmanli uygulamalarla calisirken ortaya cikiyor. Suna bak:

// Database layer throws DatabaseError
func fetchUser(id: String) throws(DatabaseError) {
    // Database operations...
}

// Profile layer needs to call the database layer
func loadUserProfile(id: String) throws(ProfileError) {
    do {
        // ⚠️ Problem: This throws DatabaseError, not ProfileError
        let user = try fetchUser(id: id)
    } catch {
        // Manual error conversion needed
        switch error {
        case DatabaseError.recordNotFound:
            throw ProfileError.userNotFound
        default:
            throw ProfileError.databaseError(error) // Need a wrapper case
        }
    }
}

Bu birkac sorun yaratiyor:

  1. Wrapper Case Patlamasi: Her hata turunun tum olasi alt hatalar icin wrapper case’lere ihtiyaci var

  2. Manuel Hata Eslemesi: Acik hata donusumu iceren tekrarlayan do-catch bloklari

  3. Tur Cogalmasi: Hata turleri her katmanda buyuyor, yonetimi zorlastiyor

  4. Kayip Baglam: Orijinal hata hakkindaki detaylar ceviride genellikle kayboluyor

Kucuk uygulamalar icin bu yonetilebilir olabilir. Cok katmanli buyuk uygulamalar icin ise hizla “ic ice gecme cehennemi” haline geliyor.

Cozum: Catching Protokolu

ErrorKit bunu Catching adinda basit bir protokolle cozuyor:

public protocol Catching {
    static func caught(_ error: Error) -> Self
}

Bu protokol, herhangi bir hatayi senin turune saran caught adinda tek bir enum case gerektiriyor. Iste nasil kullaniyorsun:

enum ProfileError: Throwable, Catching {
    case userNotFound
    case invalidProfile
    case caught(Error) // Single case for all other errors

    var userFriendlyMessage: String {
        switch self {
        case .userNotFound:
            return "User not found."
        case .invalidProfile:
            return "Profile data is invalid."
        case .caught(let error):
            // Use the wrapped error's message
            return ErrorKit.userFriendlyMessage(for: error)
        }
    }
}

Throwable’in Error icin dogrudan bir yedek oldugunu unutma (bkz. onceki yazi).

Simdi sihir, protokolle birlikte gelen catch fonksiyonuyla gerceklesiyor:

func loadUserProfile(id: String) throws(ProfileError) {
    // For known errors, throw them directly
    guard isValidID(id) else {
        throw ProfileError.invalidInput
    }

    // For operations that may throw other error types, use the catch function
    let user = try ProfileError.catch {
        // Any error thrown here will be automatically wrapped
        // into ProfileError.caught(error)
        return try fetchUser(id: id)
    }

    // Rest of implementation...
}

catch fonksiyonunun closure’da ne donersen onu dondurdugunu unutma.

catch fonksiyonu closure’inda firlatan hatalari otomatik olarak senin hata turune sariyor. Manuel do-catch bloklari yok, acik hata eslemesi yok – sadece calisiyor. Birden fazla try ifadesi bile mumkun.

catch Fonksiyonunun Gizli Sosu

catch fonksiyonu zarif bir sekilde basit:

extension Catching {
    public static func `catch`<ReturnType>(
        _ operation: () throws -> ReturnType
    ) throws(Self) -> ReturnType {
        do {
            return try operation()
        } catch {
            throw Self.caught(error)
        }
    }
}

Bu fonksiyon:

  1. Hata firlatabilen bir closure aliyor

  2. Calistirmayi deniyor

  3. Basarili olursa sonucu donduruyor

  4. Firlanan herhangi bir hatayi senin caught case’ini kullanarak otomatik sariyor

  5. Islemin donus turunu koruyuyor

En guzel tarafi? Swift 6’nin typed throws’uyla sorunsuz calisiyor, boilerplate’i ortadan kaldirirken tur guvenligini koruyor.

Hata Ayiklama Icin Hata Zincirini Korumak

Bu yaklasimin en buyuk faydalarindan biri tam hata zincirini korumasi. Hatalar sinirlari gecerken baglami kaybetmek yerine, her katman orijinal hatayi korurken bilgi ekliyor.

ErrorKit, guclu hata ayiklama icin errorChainDescription(for:) fonksiyonuyla bundan yararlaniyor:

do {
    try await updateUserProfile()
} catch {
    print(ErrorKit.errorChainDescription(for: error))

    // Output shows the complete chain:
    // AppError
    // └─ ProfileError
    //    └─ DatabaseError
    //       └─ FileError.notFound(path: "/Users/data.db")
    //          └─ userFriendlyMessage: "Could not find database file."
}

Bu hiyerarsik gorunum sunu soyluyor:

  1. Hatanin nereden kaynaklandigini (FileError)

  2. Uygulamandan gectigi tam yolu (FileError -> DatabaseError -> ProfileError -> AppError)

  3. Neyin yanlis gittiginin spesifik detaylarini (dosya bulunamadi, yol ile birlikte)

  4. Kullanicilara gosterilecek kullanici dostu mesaji

Bu seviyede bir icgoru, ozellikle hatalarin cagri yigininin derinlerinden kaynaklanabilecegi karmasik uygulamalar icin hata ayiklama sirasinda cok degerli.

Yapilandirilmis Hata Zinciri Ciktisi

Hata zinciri tanimi, hata yapisini tekrarlamali olarak inceleyerek calisiyor:

static func errorChainDescription(for error: Error) -> String {
    // Recursive implementation that builds a hierarchical description
    Self.chainDescription(for: error, enclosingType: type(of: error))
}

chainDescription’in ErrorKit icindeki tam implementasyonu icin buraya bak.

Fonksiyon, Swift’in yansima yeteneklerini su sekilde kullaniyor:

  1. Mirror API kullanarak hatayi inceliyor

  2. Catching’e uyumlu hatalar icin sarilan hatayi cikartiyor

  3. Enum hatalari icin case adlarini ve iliskili degerleri yakaliyor

  4. Struct veya class hatalari icin tur meta verisini dahil ediyor

  5. Her seyi hiyerarsik bir agac yapisinda biclendiriyor

Bu, ozellikle karmasik hata hiyerarsileri icin standart hata loglamasindan cok daha fazla bilgi sagliyor.

ErrorKit’te Yerlesik Destek

ErrorKit’in tum yerlesik hata turleri (ornegin FileError veya NetworkError) zaten Catching’e uyumlu, yani hemen kullanabilirsin:

func saveUserData() throws(DatabaseError) {
    // Automatically wraps SQLite errors, file system errors, etc.
    try DatabaseError.catch {
        try database.beginTransaction()
        try database.execute(query)
        try database.commit()
    }
}

Gercek Dunya Ornegi: Tipik Bir Uygulama

Bunun daha kapsamli bir ornekte nasil calistigina bakalim:

// Data Access Layer
func fetchUserData(id: String) throws(DatabaseError) {
    guard database.isConnected else {
        throw DatabaseError.connectionFailed
    }

    // This could throw file system errors
    try DatabaseError.catch {
        let query = try QueryBuilder.build(for: id)
        return try database.execute(query)
    }
}

// Business Logic Layer
func processUserProfile(id: String) throws(ProfileError) {
    guard isValidID(id) else {
        throw ProfileError.invalidInput
    }

    // This automatically wraps DatabaseError
    let userData = try ProfileError.catch {
        return try fetchUserData(id: id)
    }

    // Process the user data...
}

// Presentation Layer
func displayUserProfile(id: String) throws(UIError) {
    // This automatically wraps ProfileError (which might contain DatabaseError)
    let profile = try UIError.catch {
        return try processUserProfile(id: id)
    }

    // Display the profile...
}

Bir veritabani baglantisi basarisiz olursa, hata zincirinde sunu goreceksin:

UIError
└─ ProfileError
   └─ DatabaseError.connectionFailed
      └─ userFriendlyMessage: "Unable to establish a connection to the database. Check your network settings and try again."

Bu sana tam olarak ne oldugunu ve hatanin nereden kaynaklandigini soyluyor, hata ayiklamayi cok daha kolaylastiriyor. Eklenen baglam sorunu cozmek icin dogru ipucunu verebilir!

Sonuc

Swift 6’nin typed throws’u dile guclu bir eklenti ama katmanlar arasi hata yayilimi icin zorluklar getiriyor. Catching protokolu, boilerplate’i ortadan kaldirirken tur guvenligini koruyan basit ve zarif bir cozum sunuyor.

ErrorKit’in errorChainDescription fonksiyonuyla birlestirince, hata yonetimi guclu bir hata ayiklama aracina donusuyor. ErrorKit’i simdi kullan ve gercek dunya uygulamalarinda Swift’te hata yonetimini daha kullanisli hale getiren bircok baska iyilestirmeden faydalan:

github.comFlineDev / ErrorKitSimplified error handling with built-in user-friendly messages for common errors. Fully localized. Community-driven

Swift 6’nin typed throws’unu kullanmaya basladin mi? Uygulamalarinda katmanlar arasi hata yayilimini nasil yonetiyorsun? Sosyal medyada (asagidaki linklerden) bana bildir!

Bu serideki onceki yazi:

  1. Swift Error Handling Done Right: Overcoming the ObjC Legacy

Bu serideki sonraki yazilar:

  1. Better Error Reporting in Swift Apps: Automatic Logs + Analytics

  2. Making Swift Error Messages Human-Friendly—Together

Bu yazıyı beğendin mi? Swift ipuçları ve indie geliştirici güncellemeleri için Bluesky ve Mastodon üzerinden takip et.