Sintaks lakukan-coba-tangkap Swift

162

Saya mencobanya untuk memahami hal penanganan kesalahan baru di swift 2. Inilah yang saya lakukan: Saya pertama kali menyatakan kesalahan enum:

enum SandwichError: ErrorType {
    case NotMe
    case DoItYourself
}

Dan kemudian saya menyatakan metode yang melempar kesalahan (tidak terkecuali orang. Ini adalah kesalahan.). Inilah metode itu:

func makeMeSandwich(names: [String: String]) throws -> String {
    guard let sandwich = names["sandwich"] else {
        throw SandwichError.NotMe
    }

    return sandwich
}

Masalahnya adalah dari sisi panggilan. Berikut adalah kode yang memanggil metode ini:

let kitchen = ["sandwich": "ready", "breakfeast": "not ready"]

do {
    let sandwich = try makeMeSandwich(kitchen)
    print("i eat it \(sandwich)")
} catch SandwichError.NotMe {
    print("Not me error")
} catch SandwichError.DoItYourself {
    print("do it error")
}

Setelah dobaris kompiler mengatakan Errors thrown from here are not handled because the enclosing catch is not exhaustive. Tapi menurut saya itu lengkap karena hanya ada dua kasus di SandwichErrorenum.

Untuk laporan pergantian reguler, swift dapat memahaminya lengkap ketika setiap kasus ditangani.

mustafa
sumber
3
Anda tidak menentukan jenis kesalahan yang Anda lemparkan, jadi Swift tidak dapat menentukan semua opsi yang mungkin
Farlei Heinen
Apakah ada cara untuk menentukan jenis kesalahan?
mustafa
Saya tidak dapat menemukan apa pun di versi baru dari buku Swift - hanya kata kunci yang melempar sekarang
Farlei Heinen
Bekerja untuk saya di taman bermain tanpa kesalahan atau peringatan.
Fogmeister
2
Taman bermain tampaknya memungkinkan doblok di tingkat atas yang tidak lengkap - jika Anda membungkus do dalam fungsi non-lemparan, itu akan menghasilkan kesalahan.
Sam

Jawaban:

267

Ada dua poin penting pada model penanganan kesalahan Swift 2: kelengkapan dan ketahanan. Bersama-sama, mereka direbus ke do/ catchpernyataan Anda perlu menangkap setiap kesalahan yang mungkin, bukan hanya yang Anda tahu Anda bisa melempar.

Perhatikan bahwa Anda tidak menyatakan jenis kesalahan apa yang bisa dilempar suatu fungsi, hanya apakah itu benar-benar melempar. Ini adalah nol-satu-infinity masalah: sebagai seseorang yang mendefinisikan fungsi untuk orang lain (termasuk diri masa depan Anda) untuk digunakan, Anda tidak ingin harus membuat setiap klien fungsi Anda beradaptasi dengan setiap perubahan dalam implementasi Anda fungsi, termasuk kesalahan apa yang bisa dilemparkan. Anda ingin kode yang memanggil fungsi Anda tangguh terhadap perubahan tersebut.

Karena fungsi Anda tidak bisa mengatakan kesalahan apa yang dilemparkannya (atau mungkin terjadi di masa depan), catchblok yang menangkap kesalahan itu tidak tahu jenis kesalahan apa yang mungkin ditimbulkannya. Jadi, selain menangani jenis kesalahan yang Anda ketahui, Anda perlu menangani yang tidak Anda lakukan dengan catchpernyataan universal - dengan cara itu jika fungsi Anda mengubah set kesalahan yang dilemparkannya di masa depan, penelepon masih akan menangkap kesalahannya. kesalahan.

do {
    let sandwich = try makeMeSandwich(kitchen)
    print("i eat it \(sandwich)")
} catch SandwichError.NotMe {
    print("Not me error")
} catch SandwichError.DoItYourself {
    print("do it error")
} catch let error {
    print(error.localizedDescription)
}

Tapi jangan berhenti di situ. Pikirkan lagi gagasan ketahanan ini. Cara Anda mendesain sandwich Anda, Anda harus menggambarkan kesalahan di setiap tempat Anda menggunakannya. Itu berarti bahwa setiap kali Anda mengubah set kasus kesalahan, Anda harus mengubah setiap tempat yang menggunakannya ... tidak terlalu menyenangkan.

Gagasan di balik mendefinisikan jenis kesalahan Anda sendiri adalah membiarkan Anda memusatkan hal-hal seperti itu. Anda dapat menentukan descriptionmetode untuk kesalahan Anda:

extension SandwichError: CustomStringConvertible {
    var description: String {
        switch self {
            case NotMe: return "Not me error"
            case DoItYourself: return "Try sudo"
        }
    }
}

Dan kemudian kode penanganan kesalahan Anda dapat meminta jenis kesalahan Anda untuk menggambarkan dirinya sendiri - sekarang setiap tempat di mana Anda menangani kesalahan dapat menggunakan kode yang sama, dan menangani kemungkinan kasus kesalahan yang akan datang juga.

do {
    let sandwich = try makeMeSandwich(kitchen)
    print("i eat it \(sandwich)")
} catch let error as SandwichError {
    print(error.description)
} catch {
    print("i dunno")
}

Ini juga membuka jalan bagi tipe kesalahan (atau ekstensi pada mereka) untuk mendukung cara pelaporan kesalahan lainnya - misalnya, Anda bisa memiliki ekstensi pada jenis kesalahan Anda yang tahu bagaimana menyajikan a UIAlertControlleruntuk melaporkan kesalahan kepada pengguna iOS.

rickster
sumber
1
@rickster: Bisakah Anda benar-benar mereproduksi kesalahan kompiler? Kode asli dikompilasi tanpa kesalahan atau peringatan untuk saya. Dan jika pengecualian yang tidak cocok dilemparkan, program dibatalkan dengan error caught in main().- Jadi sementara semua yang Anda katakan terdengar masuk akal, saya tidak dapat mereproduksi perilaku itu.
Martin R
5
Sukai bagaimana Anda memisahkan pesan kesalahan dalam ekstensi. Cara yang sangat bagus untuk menjaga kode Anda tetap bersih! Contoh yang bagus!
Konrad77
Sangat disarankan agar Anda tidak menggunakan paksa - tryekspresi dalam kode produksi karena dapat menyebabkan kesalahan runtime dan menyebabkan aplikasi Anda macet
Otar
@Otar pemikiran yang baik secara umum, tapi itu sedikit di luar topik - jawabannya tidak membahas menggunakan (atau tidak menggunakan) try!. Selain itu, ada kasus penggunaan yang valid dan "aman" untuk berbagai operasi "force" di Swift (buka, coba, dll.) Bahkan untuk kode produksi - jika melalui prakondisi atau konfigurasi Anda telah dengan andal menghilangkan kemungkinan kegagalan, itu bisa lebih masuk akal untuk hubungan pendek ke kegagalan instan daripada menulis kode penanganan kesalahan yang tidak dapat diuji.
rickster
Jika Anda semua yang Anda butuhkan adalah menampilkan pesan kesalahan, menempatkan logika di dalam SandwichErrorkelas masuk akal. Namun, saya menduga untuk sebagian besar kesalahan, logika penanganan kesalahan tidak dapat dienkapsulasi. Ini karena biasanya memerlukan pengetahuan tentang konteks penelepon (apakah untuk memulihkan, atau coba lagi, atau melaporkan kegagalan di bagian hulu, dll.). Dengan kata lain, saya menduga pola yang paling umum harus cocok dengan jenis kesalahan tertentu.
Maks
29

Saya menduga ini belum diimplementasikan dengan benar. The Swift Pemrograman Panduan pasti tampaknya menyiratkan bahwa compiler dapat menyimpulkan pertandingan lengkap 'seperti pernyataan switch'. Itu tidak menyebutkan perlu jenderal catchagar lengkap.

Anda juga akan melihat bahwa kesalahan ada di trybaris, bukan di akhir blok, yaitu pada beberapa titik kompiler akan dapat menentukan trypernyataan mana di blok yang memiliki jenis pengecualian yang tidak tertangani.

Dokumentasinya agak ambigu. Saya telah membaca video 'Apa yang baru di Swift' dan tidak dapat menemukan petunjuk apa pun; Saya akan terus berusaha.

Memperbarui:

Kami sekarang hingga Beta 3 tanpa sedikit pun inferensi ErrorType. Saya sekarang percaya jika ini pernah direncanakan (dan saya masih berpikir itu pada titik tertentu), pengiriman dinamis pada ekstensi protokol mungkin mematikannya.

Pembaruan Beta 4:

Xcode 7b4 menambahkan dukungan komentar dokumen untuk Throws:, yang "harus digunakan untuk mendokumentasikan kesalahan apa yang dapat dilemparkan dan mengapa". Saya kira ini setidaknya menyediakan beberapa mekanisme untuk mengkomunikasikan kesalahan kepada konsumen API. Siapa yang butuh sistem tipe ketika Anda memiliki dokumentasi!

Pembaruan lain:

Setelah menghabiskan beberapa waktu berharap untuk ErrorTypekesimpulan otomatis , dan mencari tahu apa keterbatasan model itu, saya berubah pikiran - inilah yang saya harapkan dari Apple. Pada dasarnya:

// allow us to do this:
func myFunction() throws -> Int

// or this:
func myFunction() throws CustomError -> Int

// but not this:
func myFunction() throws CustomErrorOne, CustomErrorTwo -> Int

Namun Pembaruan Lainnya

Alasan penanganan kesalahan Apple sekarang tersedia di sini . Ada juga beberapa diskusi menarik tentang milis swift-evolution . Pada dasarnya, John McCall menentang kesalahan pengetikan karena dia yakin sebagian besar perpustakaan akan berakhir termasuk kasus kesalahan umum, dan bahwa kesalahan pengetikan tidak mungkin menambah banyak kode selain boilerplate (dia menggunakan istilah 'aspirational bluff'). Chris Lattner mengatakan dia terbuka untuk kesalahan pengetikan di Swift 3 jika itu dapat bekerja dengan model ketahanan.

Sam
sumber
Terima kasih atas tautannya. Namun, tidak dibujuk oleh John: "banyak perpustakaan menyertakan jenis 'kesalahan lain'" tidak berarti bahwa setiap orang membutuhkan jenis "kesalahan lain".
Franklin Yu
Penghitung yang jelas adalah bahwa tidak ada cara sederhana untuk mengetahui jenis kesalahan apa yang akan dilemparkan suatu fungsi, sampai hal itu terjadi, memaksa pengembang untuk menangkap semua kesalahan dan mencoba menanganinya sebaik mungkin. Agak menyebalkan, terus terang saja.
William T Froggard
4

Swift khawatir pernyataan kasus Anda tidak mencakup semua kasus, untuk memperbaikinya Anda harus membuat kasing default:

do {
    let sandwich = try makeMeSandwich(kitchen)
    print("i eat it \(sandwich)")
} catch SandwichError.NotMe {
    print("Not me error")
} catch SandwichError.DoItYourself {
    print("do it error")
} catch Default {
    print("Another Error")
}
Icaro
sumber
2
Tapi bukankah itu canggung? Saya hanya punya dua kasus dan semuanya tercantum dalam catchpernyataan.
mustafa
2
Sekarang adalah waktu yang tepat untuk permintaan tambahan yang menambahkan func method() throws(YourErrorEnum), atau meskipun throws(YourEnum.Error1, .Error2, .Error3)begitu Anda tahu apa yang bisa dilemparkan
Matthias Bauch
8
Tim kompiler Swift di salah satu sesi WWDC menegaskan bahwa mereka tidak ingin daftar pedantic dari semua kesalahan yang mungkin 'seperti Java'.
Sam
4
Tidak ada Default / kesalahan standar; tinggalkan tangkapan kosong {} seperti yang ditunjukkan poster lainnya
Opus1217
1
@Icaro Itu tidak membuat saya aman; jika saya "menambahkan entri baru dalam array" di masa depan, kompiler harus meneriaki saya karena tidak memperbarui semua klausa tangkapan yang terpengaruh.
Franklin Yu
3

Saya juga kecewa dengan kurangnya tipe fungsi yang bisa melempar, tapi saya mengerti sekarang berkat @rickster dan saya akan meringkasnya seperti ini: katakanlah kita dapat menentukan tipe yang dilempar fungsi, kita akan memiliki sesuatu seperti ini:

enum MyError: ErrorType { case ErrorA, ErrorB }

func myFunctionThatThrows() throws MyError { ...throw .ErrorA...throw .ErrorB... }

do {
    try myFunctionThatThrows()
}
case .ErrorA { ... }
case .ErrorB { ... }

Masalahnya adalah bahkan jika kita tidak mengubah apa pun di myFunctionThatThrows, jika kita hanya menambahkan kasus kesalahan ke MyError:

enum MyError: ErrorType { case ErrorA, ErrorB, ErrorC }

kita kacau karena do / try / catch kita tidak lagi lengkap, serta tempat lain di mana kita memanggil fungsi yang melempar MyError

greg3z
sumber
3
Tidak yakin saya mengikuti mengapa Anda kacau. Anda akan mendapatkan kesalahan kompiler, yang Anda inginkan, bukan? Itulah yang terjadi pada pergantian pernyataan jika Anda menambahkan enum case.
Sam
Dalam arti sepertinya bagi saya bahwa ini akan terjadi dengan error enums / do case, tapi persis seperti yang akan terjadi pada enums / switch, Anda benar. Saya masih berusaha meyakinkan diri sendiri bahwa pilihan Apple untuk tidak mengetik apa yang kami lemparkan adalah yang baik, tetapi Anda tidak membantu saya untuk yang satu ini! ^^
greg3z
Mengetik kesalahan yang dilempar secara manual akan berakhir sebagai kekacauan besar dalam kasus yang tidak sepele. Jenisnya adalah gabungan dari semua kemungkinan kesalahan dari semua lemparan dan coba pernyataan dalam fungsi. Jika Anda secara manual mempertahankan kesalahan enum, ini akan menyakitkan. A catch {}di bagian bawah setiap blok bisa dibilang lebih buruk. Saya berharap kompiler pada akhirnya akan menyimpulkan jenis kesalahan secara otomatis di mana ia bisa tetapi saya belum dapat mengonfirmasi.
Sam
Saya setuju bahwa kompiler harus, secara teoritis, dapat menyimpulkan tipe kesalahan yang dilempar oleh suatu fungsi. Tapi saya pikir masuk akal juga bagi dev untuk secara eksplisit menuliskannya untuk kejelasan. Dalam kasus non-sepele yang Anda bicarakan, daftar jenis kesalahan yang berbeda tampaknya ok untuk saya: func f () melempar ErrorTypeA, ErrorTypeB {}
greg3z
Jelas ada bagian besar yang hilang karena tidak ada mekanisme untuk mengkomunikasikan tipe kesalahan (selain dari komentar doc). Namun, tim Swift mengatakan mereka tidak ingin daftar eksplisit jenis kesalahan. Saya yakin sebagian besar orang yang pernah berurusan dengan Java memeriksa pengecualian di masa lalu akan setuju 😀
Sam
1
enum NumberError: Error {
  case NegativeNumber(number: Int)
  case ZeroNumber
  case OddNumber(number: Int)
}

extension NumberError: CustomStringConvertible {
         var description: String {
         switch self {
             case .NegativeNumber(let number):
                 return "Negative number \(number) is Passed."
             case .OddNumber(let number):
                return "Odd number \(number) is Passed."
             case .ZeroNumber:
                return "Zero is Passed."
      }
   }
}

 func validateEvenNumber(_ number: Int) throws ->Int {
     if number == 0 {
        throw NumberError.ZeroNumber
     } else if number < 0 {
        throw NumberError.NegativeNumber(number: number)
     } else if number % 2 == 1 {
         throw NumberError.OddNumber(number: number)
     }
    return number
}

Sekarang Nomor Validasi:

 do {
     let number = try validateEvenNumber(0)
     print("Valid Even Number: \(number)")
  } catch let error as NumberError {
     print(error.description)
  }
Yogendra Singh
sumber
-2

Buat enum seperti ini:

//Error Handling in swift
enum spendingError : Error{
case minus
case limit
}

Buat metode seperti:

 func calculateSpending(morningSpending:Double,eveningSpending:Double) throws ->Double{
if morningSpending < 0 || eveningSpending < 0{
    throw spendingError.minus
}
if (morningSpending + eveningSpending) > 100{
    throw spendingError.limit
}
return morningSpending + eveningSpending
}

Sekarang periksa apakah ada kesalahan atau tidak dan tangani:

do{
try calculateSpending(morningSpending: 60, eveningSpending: 50)
} catch spendingError.minus{
print("This is not possible...")
} catch spendingError.limit{
print("Limit reached...")
}
Mr.Javed Multani
sumber
Tutup tapi tidak ada cerutu. Coba perbaiki spasi dan buat casing unta enum.
Alec