Recide, present singular orang kedua kehadiran recidere
: - untuk mundur, menjadi sia -sia; untuk mengurangi
assoc
/ update
menjadi Ex-Data Errors 'Recidedeferror
deferror-group
getCurrentSanitizationLevel()
createSuppressionMap(...)
sanitize(Throwable)
, sanitize(Throwable, IPersistentMap)
ErrorForm
yang disesuaikan ex-info
Clojure adalah konstruk yang sangat berguna: Anda dapat melampirkan peta data sewenang-wenang ke pengecualian yang dilemparkan, memungkinkan kode yang telah menangkap pengecualian untuk memeriksa, log, atau memproses ex-data
ini.
Misalnya, pernyataan assert
memberikan pemeriksaan kewarasan yang berharga dalam logika bisnis, tetapi ketika gagal biasanya sangat diinginkan untuk mengetahui bagaimana mereka gagal. Seseorang dapat mencoba memasukkan informasi ini ke dalam string pengecualian, tetapi kadang -kadang data yang relevan terlalu besar untuk pesan pengecualian yang bergantung. Alih-alih memeriksa properti yang diinginkan dan melempar ex-info
dengan semua data yang relevan yang terlampir dapat menjaga ringkasan pesan kesalahan sambil menghemat banyak waktu pengembang, terutama ketika debugging di repl.
Salah satu kelemahan utama ex-info
adalah bahwa penggunaannya dapat mendorong pengecualian ad-hoc tanpa standar: jika Anda menangkap mantan info, jenisnya sama dengan semua mantan infos lainnya, stringnya sewenang-wenang, dan bukan Hanya Anda yang tidak dapat mengandalkan kunci tertentu yang muncul di ex-data
, sangat mungkin bahwa peta itu sepenuhnya kosong.
Jika Anda ingin menikmati manfaat menggunakan ex-info
secara luas dalam proyek besar, tetapi Anda juga ingin mempertahankan Ukuran kewarasan Manfaat dari kesalahan yang terdefinisi dengan baik, ada kemungkinan bahwa Anda pada akhirnya akan menggunakan, di setiap komponen logis aplikasi Anda, baik untuk melanggar dengan idiom clojure universal atau untuk mendefinisikan Anda sendiri mendefinisikan serangkaian standar "fungsi lemparan" yang menggunakan ex-info
tetapi menjamin kekakuan tertentu: mungkin awalan umum untuk string pengecualian, mungkin kunci yang dijamin tertentu di peta ex-data
.
Tujuan utama perpustakaan ini, reside , adalah untuk menyediakan alat untuk memudahkan proses ini. Ini memberikan utilitas untuk mendefinisikan formulir mantan info standar, serta kapasitas untuk memeriksa pada waktu kompilasi bahwa mereka digunakan sebagaimana dimaksud.
Semua mantan peta ex-info
yang dihasilkan oleh alat-alat dalam recide
mengandung setidaknya dua tombol:
:recide/error
, yang nilainya merupakan instance dari ErrorForm
.ErrorForm
(default di Recide adalah :recide/type
). Dokumentasi API Clojure dapat ditemukan di sini. Dokumentasi API Java dapat ditemukan di sini.
recide.core/insist
analog untuk assert
. Tanda tangannya sama, dan sama seperti assert
hanya dieksekusi ketika clojure.core/*assert*
adalah benar.
Tetapi alih-alih melempar AssertionError
, ia melempar ex-info
dengan string penjelasan dari formulir: "Pernyataan gagal: <ekspresi yang ditegaskan atau disediakan pesan>". Jenis ex-data adalah :recide/assertion
. Ada dua kunci lain yang digunakan oleh insist
:
:expression
, yang nilainya adalah ekspresi aktual yang terkandung dalam insist
:values
, yang nilainya merupakan peta dari setiap variabel yang digunakan dalam ekspresi ke nilainya pada saat kegagalan. :values
hanya ada setiap kali recide.impl/*capture-insists*
benar, tetapi salah secara default. Di perpustakaan waktu pemuatan, diatur ke true jika setidaknya satu dari yang berikut ini benar:
Tanda tangan insist
adalah [[expr] [expr message]]
, dan ex-data pada ex-info yang dihasilkan memiliki bentuk berikut:
{ :recide/type :recide/assertion ,
:expression <expr>
:values { ... }}
Contoh yang digunakan:
( let [y not
x true ]
( insist ( y x)))
; ; Unhandled clojure.lang.ExceptionInfo
; ; Assertion failed: (y x)
; ; {:expression (y x),
; ; :values {y #function[clojure.core/not],
; ; x true},
; ; :recide/type :recide/assertion}
recide.core/error
memiliki dua arities: ([type msg data] [type msg data cause])
. error
membangun ex-info
dengan data
peta terlampir, yang tipenya (sekali lagi, ditandai oleh :recide/type
secara default) adalah type
. Memasok cause
hanya memberi pengecualian penyebab sesuai dengan Idiom Java.
( let [x " not b! haha " ]
( raise :my-type
" my explanation! "
{ :a " a "
:b x}))
; ; #error {
; ; :cause "my explanation!"
; ; :data {:a "a",
; ; :b "not b! haha",
; ; :recide/type :my-type, :recide/error #object[...]}
; ; :via
; ; [{:type clojure.lang.ExceptionInfo
; ; :message "my explanation!"
; ; :data {:a "a",
; ; :b "not b! haha",
; ; :recide/type :my-type, :recide/error #object[...]}
; ; :at [clojure.core$ex_info invokeStatic "core.clj" 4725]}]
; ; :trace ... }
recide.core/raise
memiliki dua arities yang sama. raise
lemparan pengecualian yang dibangun berdasarkan error
.
assoc
/ update
menjadi Ex-Data Errors 'Recide Fungsi Kenyamanan: recide.core/assoc-error
dan recide.core/update-error
Setiap pengembalian pengecualian baru dari jenis asli, dengan ex-data dimodifikasi seperti assoc
dan update
.
Kadang -kadang bisa lebih mudah untuk melewati dan memanipulasi representasi peta dari kesalahan sebelum mengubahnya kembali menjadi pengecualian dan melemparkannya. Untuk tujuan ini, kami menyediakan recide.core/error->map
dan recide.core/error-map->throwable
yang melakukan seperti yang Anda harapkan.
Selain itu, kami memberikan "konstruktor" error-map
alternatif untuk kesalahan yang mengembalikan formulir peta, dan error-map?
.
Untuk kesalahan jenis type
dengan msg
pesan, data
ex-data, dan cause
, peta persisten terlihat seperti ini:
{ :recide/error <ErrorForm>,
:recide/type type,
:recide/msg msg,
:recide/cause cause,
:recide/data data}
Semua kunci ini kecuali :recide/error
dapat dimodifikasi dengan menyediakan ErrorForm
yang disesuaikan (lihat di bawah untuk detailnya).
raise
dan error
tidak banyak memberikan jenis pengecualian standar. Untuk mengatasi ini lebih lanjut, Recide menyediakan deferror
dan deferror-group
.
deferror
deferror
adalah makro yang mengambil nama kesalahan, tipe, dan string "generik" yang akan mengawali pesan dari semua kesalahan jenis ini. Itu juga, secara opsional, mengambil koleksi kunci yang diperlukan. Jika tombol yang diperlukan ditentukan, maka kesalahan waktu kompilasi akan dilemparkan kapan saja alat yang ditentukan oleh deferror
digunakan tanpa menentukan kunci tersebut secara eksplisit dalam kode sumber.
Contoh Penggunaan:
( deferror storage-timeout
:storage/timeout
" A storage operation timed out "
[ :method-at-fault :timeout-ms ])
Dalam contoh ini, panggilan untuk deferror
ini akan mendefinisikan dua makro baru, storage-timeout
, dan raise-storage-timeout
. Untuk kenyamanan Anda, IDE yang kompeten akan dapat mengakses Docstring terperinci di VARS baru ini:
> ( clojure.repl/doc storage-timeout)
; ; -------------------------
; ; my-ns/storage-timeout
; ; [[detail-str data] [detail-str data cause]]
; ; Macro
; ; Records this raise-site under :storage/timeout in recide, and expands into the equivalent of:
; ;
; ; (ex-info (str "A storage operation timed out: " detail-str)
; ; (assoc data :recide/type :storage/timeout)
; ; cause)
; ;
; ; The following keys are required in the data-map:
; ; #{:method-at-fault,
; ; :timeout-ms}
> ( clojure.repl/doc raise-storage-timeout)
; ; -------------------------
; ; [[detail-str data] [detail-str data cause]]
; ; Macro
; ; Records this raise-site under :storage/timeout in recide, and expands into:
; ;
; ; (raise :storage/timeout
; ; (str "A storage operation timed out: " detail-str)
; ; data
; ; cause)
; ;
; ; The following keys are required in the data-map:
; ; #{:method-at-fault,
; ; :timeout-ms}
Jika Anda mencoba menggunakan salah satu dari ini tanpa, di peta data Anda, menentukan masing -masing tombol yang diperlukan, kompiler clojure akan melempar pengecualian:
> ( raise-storage-timeout " blah " { :method-at-fault 'not-really-a-method})
; ; Unhandled clojure.lang.ExceptionInfo
; ; Assertion failed: storage-timeout requires the following missing
; ; keys in its data: :timeout-ms
deferror-group
deferror-group
adalah makro yang mendefinisikan seluruh keluarga kesalahan. Dibutuhkan nama kesalahan, deklarasi tipe dasar, dan sejumlah deklarasi subtipe.
Setiap deklarasi tipe dasar harus berupa kata kunci yang mewakili namespace umum untuk kelompok kesalahan ini, atau tuple yang elemen pertamanya adalah kata kunci seperti itu dan yang elemen kedua adalah urutan kunci yang diperlukan. Kunci yang ditentukan di sini akan diperlukan di setiap subtipe.
Setiap deklarasi subtipe terdiri dari urutan yang istilah pertama adalah simbol, istilah kedua adalah string generik untuk kesalahan, dan istilah ketiga (opsional) adalah urutan kunci yang diperlukan untuk subtipe tersebut.
Contoh:
( deferror-group parse-err
( :query.invalid [ :expression ])
( find-spec " Invalid find spec " )
( inputs " Invalid inputs " [ :invalid ]))
Dalam contoh ini, ada dua jenis kesalahan yang ditentukan :: :query.invalid/find-spec
dan :query.invalid/inputs
. Yang pertama membutuhkan :expression
dalam peta datanya, tetapi yang kedua membutuhkan keduanya :expression
dan :invalid
.
Seperti halnya deferror
, utilitas yang diproduksi oleh deferror-group
memiliki docstrings terperinci:
> ( clojure.repl/doc parse-err)
; ; -------------------------
; ; recide/parse-err
; ; [[subtype detail-str data] [subtype detail-str data cause]]
; ; Macro
; ; Records this raise-site under :query.invalid/<subtype> in recide, and expands into the
; ; equivalent of:
; ;
; ; (ex-info (str "<subtype-generic-str>: " detail-str)
; ; (assoc data
; ; :recide/type
; ; :query.invalid/<subtype>)
; ; cause)
; ;
; ; The following map shows, for each subtype, what keywords are required in
; ; the data map, and what the generic portion of the string will be:
; ;
; ; {:find-spec {:required #{:expression},
; ; :generic-str "Invalid find spec"},
; ; :inputs {:required #{:expression :invalid},
; ; :generic-str "Invalid inputs"}}
> ( clojure.repl/doc raise-parse-err)
; ; -------------------------
; ; recide/raise-parse-err
; ; [[subtype detail-str data] [subtype detail-str data cause]]
; ; Macro
; ; Records this raise-site under :query.invalid/<subtype> in recide, and expands into:
; ;
; ; (raise :query.invalid/<subtype>
; ; (str "<subtype-generic-str>: " detail-str)
; ; data
; ; cause)
; ;
; ; The following map shows, for each subtype, what keywords are required in
; ; the data map, and what the generic portion of the string will be:
; ;
; ; {:find-spec {:required #{:expression},
; ; :generic-str "Invalid find spec"},
; ; :inputs {:required #{:expression :invalid},
; ; :generic-str "Invalid inputs"}}
Seperti yang terlihat sebelumnya, tombol yang diperlukan menghasilkan kesalahan waktu kompilasi saat dihilangkan.
> ( raise-parse-err :inputs " detailed this, detailed that " { :expression nil })
; ; Unhandled clojure.lang.ExceptionInfo
; ; Assertion failed: parse-err called with subtype :inputs requires
; ; the following missing keys in its data: :invalid
Jika kami menggunakan kata kunci untuk menunjuk tipe kesalahan, akan berguna untuk dapat catch
kesalahan melalui kata kunci ini. Recide memberikan try*
untuk tujuan ini. try*
adalah makro yang mengembang untuk try
Clojure, yang merupakan salah satu dari beberapa bentuk khusus Clojure. Dalam kebanyakan kasus, try*
lalu harus berperilaku persis seperti try
. Ini berbeda karena memaparkan fungsionalitas catch
yang ditingkatkan. Anda dapat menangkap:
instance?
Periksa.ErrorForm
yang digunakan untuk membangunnya)recide.core/try*
[( try* expr* catch-clause* finally-clause?)]
Macro
Expands to Clojure's try Special Form, allowing for enhanced `catch` clauses:
You can catch:
* Classes/Interfaces ( represents an instance? check)
`( catch RuntimeException e ...)`
* keywords ( recide error types ; fully-qualified: :namspace/name, wildcard: :namespace/*)
`( catch :library/error e ...)`
* arbitrary predicates
`( catch bad-error? e ...)`
You can also catch conjunctions/disjunctions of these:
* conjunction
`( catch :and [RuntimeException :library/error bad-error?] e ...)`
* disjunction
`( catch :or [IllegalArgumentException :library/error bad-error?] e ...)`
You can also negate each of these:
`( catch ( :not RuntimeException) e ...)`
`( catch :and [( :not RuntimeException) :library/* ] e ...)`
Otherwise, behavior should match 'normal' catch clauses in `clojure.core/try`.
Perhatikan bahwa Anda dapat menggunakan kata kunci dari formulir :namespace/*
sebagai wildcard untuk menangkap keluarga kesalahan reside, seperti yang ditentukan oleh deferror-group
.
> ( try* ( raise :genus/species-1
" went extinct "
{ :year -1839421 })
( catch :genus/* e
( println ( :year ( ex-data e)))))
; ; -1839421
Recide menyediakan kumpulan alat untuk memperoleh versi sanitasi dari pengecualian yang harus dianggap aman untuk dicatat (tetapi mungkin tidak berguna sebagai hasilnya).
Kelas ini berisi beberapa metode utilitas statis:
getCurrentSanitizationLevel()
Setara dengan recide.sanex/*sanitization-level*
.
createSuppressionMap(...)
Membuat IpersistentMap dengan kata kunci yang sesuai yang sesuai dengan arg boolean.
sanitize(Throwable)
, sanitize(Throwable, IPersistentMap)
Pintasan ke clojure ifn recide.sanex/sanitize
.
ErrorForm
yang disesuaikan Secara default, kesalahan yang diangkat oleh perpustakaan ini Gunakan ex-info
sebagai konstruktor, recide.utils/serialize-throwable
dan recide.utils/deserialize-throwable
for (de) serialisasi, dan dalam bentuk peta mereka menggunakan :recide/type
, :recide/msg
, :recide/data
, dan :recide/cause
sebagai kata kunci standar mereka.
Dengan mendefinisikan ErrorForm
baru, Anda dapat mengubah semua perilaku ini untuk perpustakaan Anda sendiri. Dengan memodifikasi kata kunci, Anda dapat "merek" kesalahan yang keluar dari perpustakaan Anda. Anda dapat menukar ex-info
dengan konstruktor lain dari arity yang sama, yang diharapkan untuk mengembalikan IExceptionInfo
. Misalnya, kekhawatiran yang terkait dengan Java Interop dapat memotivasi membuat kelas pengecualian baru, sementara idiom Clojure mungkin memotivasi mempertahankan kompatibilitas mantan info.
Anda dapat dengan mudah mendefinisikan kustomisasi Anda dengan recide.core/def-error-form
Metode tidak ditentukan default untuk menyatukan pustaka default.
Metode penanganan kesalahan di recide.core
adalah agnostik untuk ErrorForm
spesifik yang digunakan. Untuk membuat kesalahan menggunakan ErrorForm
khusus, Anda dapat dengan mudah menghasilkan serangkaian metode lengkap yang disesuaikan secara khusus untuk kustomisasi Anda dengan recide.core/generate-library!
.
( ns my-library.error
( :require [recide.core :as rc]))
( rc/def-error-form custom-error-form
( type-kw [_] :my-library/type )
( constructor [_] my-library/error-constructor)
; ; all other methods are filled out with recide defaults.
( def ^:dynamic *capture-insists?* true )
( rc/generate-library! custom-error-form *capture-insists?*)
; ; recide.core/generate-library!
; ; [custom-error capture-flag]
; ; Macro
; ; Generates and defs custom versions of the following recide.core methods, tailored specifically
; ; to custom-error, with variable capture in the generated insist subject to capture-flag.
; ; * error
; ; * error?
; ; * error-map
; ; * error-map?
; ; * throwable->error-map
; ; * raise
; ; * insist
; ; * deferror
; ; * deferror-group
; ;
; ; custom-error should be an instance of recide.error/ErrorForm (see def-error-form).
; ; capture-flag must be resolvable to a dynamic var.