Pemrosesan Kesalahan yang Tidak Dapat Dipulihkan di Swift

Kata pengantar


Artikel ini adalah contoh bagaimana kita dapat melakukan penelitian tentang fungsi fungsi Perpustakaan Swift Standar membangun pengetahuan kita tidak hanya pada dokumentasi Perpustakaan tetapi juga pada kode sumbernya.


Kesalahan yang Tidak Dapat Dipulihkan


Semua peristiwa yang oleh pemrogram disebut "kesalahan" dapat dipisahkan menjadi dua jenis.


  • Peristiwa yang disebabkan oleh faktor eksternal seperti kegagalan koneksi jaringan.
  • Peristiwa yang disebabkan oleh kesalahan programmer seperti mencapai kasus operator switch yang harus dijangkau.

Kejadian dari tipe pertama diproses dalam aliran kontrol reguler. Misalnya, kami bereaksi terhadap kegagalan jaringan dengan menunjukkan pesan kepada pengguna dan mengatur aplikasi untuk menunggu pemulihan koneksi jaringan.


Kami mencoba mencari tahu dan menghilangkan peristiwa tipe kedua sedini mungkin sebelum kode tersebut diproduksi. Salah satu pendekatan di sini adalah menjalankan beberapa pengecekan runtime yang menghentikan eksekusi program dalam status debuggable dan mencetak pesan dengan indikasi di mana dalam kode kesalahan telah terjadi.


Sebagai contoh, seorang programmer dapat menghentikan eksekusi jika initializer yang diperlukan tidak disediakan tetapi dipanggil. Itu akan selalu diperhatikan dan diperbaiki selama uji coba pertama.


required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } 

Contoh lain adalah beralih antar indeks (mari kita asumsikan bahwa karena alasan tertentu Anda tidak dapat menggunakan enumerasi).


 switch index { case 0: // something is done here case 1: // other thing is done here case 2: // and other thing is done here default: assertionFailure("Impossible index") } 

Sekali lagi, seorang programmer akan menyebabkan crash selama debugging di sini untuk melihat bug dalam pengindeksan.


Ada lima fungsi terminating dari Swift Standard Library (seperti untuk Swift 4.2).


 func precondition(_ condition: @autoclosure () -> Bool, _ message: @autoclosure () -> String = default, file: StaticString = #file, line: UInt = #line) 

 func preconditionFailure(_ message: @autoclosure () -> String = default, file: StaticString = #file, line: UInt = #line) -> Never 

 func assert(_ condition: @autoclosure () -> Bool, _ message: @autoclosure () -> String = default, file: StaticString = #file, line: UInt = #line) 

 func assertionFailure(_ message: @autoclosure () -> String = default, file: StaticString = #file, line: UInt = #line) 

 func fatalError(_ message: @autoclosure () -> String = default, file: StaticString = #file, line: UInt = #line) -> Never 

Manakah dari lima fungsi terminating yang harus kita sukai?


Kode Sumber vs Dokumentasi


Mari kita lihat kode sumbernya . Kita dapat melihat hal berikut segera:


  1. Masing-masing dari lima fungsi ini mengakhiri eksekusi program atau tidak melakukan apa pun.
  2. Kemungkinan pemutusan terjadi dalam dua cara.
    • Dengan mencetak pesan debug yang nyaman dengan memanggil _assertionFailure(_:_:file:line:flags:) .
    • Tanpa pesan debug hanya dengan memanggil Builtin.condfail(error._value) atau Builtin.int_trap() .
  3. Perbedaan antara lima fungsi terminating terletak pada kondisi di mana semua hal di atas terjadi.
  4. fatalError(_:file:line) memanggil _assertionFailure(_:_:file:line:flags:) tanpa syarat.
  5. Empat fungsi terminating lainnya mengevaluasi kondisi dengan memanggil fungsi evaluasi konfigurasi berikut. (Mereka mulai dengan garis bawah yang berarti bahwa mereka internal dan tidak seharusnya dipanggil langsung oleh seorang programmer yang menggunakan Swift Standard Library).
    • _isReleaseAssertConfiguration()
    • _isDebugAssertConfiguration()
    • _isFastAssertConfiguration()

Sekarang mari kita lihat dokumentasi . Kita bisa melihat yang berikut ini segera.


  1. fatalError(_:file:line) tanpa syarat mencetak pesan yang diberikan dan menghentikan eksekusi .
  2. Efek dari empat fungsi terminating lainnya bervariasi tergantung pada flag build yang digunakan: -Onone , -O , -Ounchecked . Misalnya, lihat dokumentasi preconditionFailure(_:file:line:) .
  3. Kita dapat mengatur flag build ini dalam Xcode melalui SWIFT_OPTIMIZATION_LEVEL pengaturan build compiler.
  4. Kami juga tahu dari dokumentasi Xcode 10 bahwa satu lagi bendera pengoptimalan - -Osize - diperkenalkan.
  5. Jadi kami memiliki empat flag build optimisasi untuk dipertimbangkan.
    • -Onone (tidak mengoptimalkan)
    • -O (optimalkan kecepatan)
    • -Osize (mengoptimalkan ukuran)
    • -Ounchecked (matikan banyak pemeriksaan kompiler)

Kami dapat menyimpulkan bahwa konfigurasi yang dievaluasi dalam empat fungsi terminating diatur oleh flag build ini.


Menjalankan Fungsi Evaluasi Konfigurasi


Meskipun fungsi evaluasi konfigurasi dirancang untuk penggunaan internal, beberapa di antaranya bersifat publik untuk tujuan pengujian , dan kami dapat mencobanya melalui CLI dengan memberikan perintah berikut di Bash.


 $ echo 'print(_isFastAssertConfiguration())' >conf.swift $ swift conf.swift false $ swift -Onone conf.swift false $ swift -O conf.swift false $ swift -Osize conf.swift false $ swift -Ounchecked conf.swift true 

 $ echo 'print(_isDebugAssertConfiguration())' >conf.swift $ swift conf.swift true $ swift -Onone conf.swift true $ swift -O conf.swift false $ swift -Osize conf.swift false $ swift -Ounchecked conf.swift false 

Tes dan inspeksi kode sumber ini membawa kita pada kesimpulan kasar berikut.


Ada tiga konfigurasi yang saling eksklusif.


  • Konfigurasi rilis diatur dengan menyediakan flag build -O atau -Osize .
  • Konfigurasi debug diatur dengan menyediakan flag build -Onone atau tidak ada flag optimasi sama sekali.
  • _isFastAssertConfiguration() dievaluasi ke true jika flag build -Ounchecked diatur. Meskipun fungsi ini memiliki kata "cepat" dalam namanya, itu tidak ada hubungannya dengan mengoptimalkan kecepatan -O build flag.

NB: Kesimpulan ini bukan definisi ketat tentang kapan debug membangun atau melepaskan build terjadi. Ini masalah yang lebih kompleks. Tetapi kesimpulan ini benar untuk konteks penggunaan fungsi terminating.


Menyederhanakan gambar


-Ounchecked


Mari kita tidak melihat untuk apa flag -Ounchecked (tidak relevan di sini) tetapi pada perannya dalam konteks penghentian penggunaan fungsi.


  • Dokumentasi untuk precondition(_:_:file:line:) dan assert(_:_:file:line:) mengatakan, "Dalam -Ounchecked bangun, kondisi tidak dievaluasi, tetapi pengoptimal dapat berasumsi bahwa itu selalu bernilai true. Kegagalan untuk memenuhi asumsi itu adalah kesalahan pemrograman yang serius. "
  • Dokumentasi untuk preconditionFailure(_:file:line) dan assertionFailure(_:file:line:) mengatakan, "In -Ounchecked builds, pengoptimal mungkin menganggap bahwa fungsi ini tidak pernah dipanggil. Kegagalan untuk memenuhi asumsi itu adalah kesalahan pemrograman yang serius. "
  • Kita dapat melihat dari kode sumber bahwa evaluasi _isFastAssertConfiguration() ke true seharusnya tidak terjadi . (Jika hal itu terjadi, _conditionallyUnreachable() aneh disebut. Lihat lihat baris 136 dan _conditionallyUnreachable() )

Berbicara lebih langsung, Anda tidak boleh membiarkan jangkauan dari empat fungsi penghentian berikut ini dengan flag build yang sudah -Ounchecked sebelumnya untuk program Anda.


  • precondition(_:_:file:line:)
  • preconditionFailure(_:file:line)
  • assert(_:_:file:line:)
  • assertionFailure(_:file:line:)

Gunakan hanya fatalError(_:file:line) saat menerapkan -Ounchecked dan pada saat yang sama memungkinkan bahwa titik program Anda dengan fatalError(_:file:line) instruksi mungkin dapat dijangkau.


Peran pemeriksaan kondisi


Dua fungsi terminating mari kita periksa kondisinya. Inspeksi kode sumber memungkinkan kita untuk melihat bahwa jika kondisi gagal maka perilaku fungsi sama dengan perilaku sepupunya masing-masing:


  • precondition(_:_:file:line:) menjadi preconditionFailure(_:file:line) ,
  • assert(_:_:file:line:) menjadi assertionFailure(_:file:line:) .

Pengetahuan itu membuat analisis lebih mudah.


Rilis vs Konfigurasi Debug


Akhirnya, dokumentasi lebih lanjut dan inspeksi kode sumber memungkinkan kami untuk merumuskan tabel berikut.


Mengakhiri fungsi


Jelas sekarang bahwa pilihan yang paling penting bagi seorang programmer adalah seperti apa perilaku program seharusnya dalam rilis jika pemeriksaan runtime mengungkapkan kesalahan.


Kunci takeaway di sini adalah assertionFailure(_:file:line:) assert(_:_:file:line:) dan assertionFailure(_:file:line:) membuat dampak kegagalan program tidak terlalu parah. Misalnya, aplikasi iOS mungkin telah merusak UI (karena beberapa pemeriksaan runtime penting gagal) tetapi tidak akan macet.


Tapi skenario itu mungkin bukan yang Anda inginkan. Anda punya pilihan.


Never kembali ketik


Never digunakan sebagai tipe kembali fungsi yang tanpa syarat melempar kesalahan, jebakan, atau yang tidak berakhir secara normal. Fungsi-fungsi semacam itu tidak benar-benar kembali, mereka tidak pernah kembali.


Di antara lima fungsi terminating, hanya preconditionFailure(_:file:line) dan fatalError(_:file:line) kembali Never karena hanya dua fungsi ini yang tanpa syarat menghentikan eksekusi program dan karenanya tidak pernah kembali.


Ini adalah contoh yang bagus untuk menggunakan Never ketik di aplikasi baris perintah. (Meskipun contoh ini tidak menggunakan fungsi terminasi Swift Standard Library tetapi fungsi standar C exit() ).


 func printUsagePromptAndExit() -> Never { print("Usage: command directory") exit(1) } guard CommandLine.argc == 2 else { printUsagePromptAndExit() } // ... 

Jika printUsagePromptAndExit() mengembalikan Void bukannya Never , Anda mendapatkan galat buildtime dengan pesan, " badan 'penjaga' tidak boleh jatuh, pertimbangkan menggunakan 'kembali' atau 'lempar' untuk keluar dari ruang lingkup ". Dengan menggunakan Never Anda katakan sebelumnya bahwa Anda tidak pernah keluar dari cakupan dan karenanya kompiler tidak akan memberi Anda kesalahan waktu bangun. Jika tidak, Anda harus menambahkan return di akhir blok kode penjaga, yang tidak terlihat bagus.


Takeaways


  • Tidak masalah fungsi terminating mana yang digunakan jika Anda yakin bahwa semua pemeriksaan runtime Anda hanya relevan untuk konfigurasi Debug .
  • Gunakan hanya fatalError(_:file:line) saat menerapkan -Ounchecked dan pada saat yang sama memungkinkan bahwa titik program Anda dengan fatalError(_:file:line) instruksi mungkin dapat dijangkau.
  • Gunakan assert(_:_:file:line:) dan assertionFailure(_:file:line:) jika Anda khawatir pemeriksaan runtime akan gagal dalam rilis. Setidaknya aplikasi Anda tidak akan macet.
  • Gunakan Never untuk membuat kode Anda terlihat rapi.


Source: https://habr.com/ru/post/id440884/


All Articles