恢复,第二人称recidere
的现场命令: - 倒下,毫无身心;削减
assoc
/ update
重新错误错误的EX-DATAdeferror
deferror-group
getCurrentSanitizationLevel()
createSuppressionMap(...)
sanitize(Throwable)
, sanitize(Throwable, IPersistentMap)
ErrorForm
Clojure的ex-info
是一个非常有用的结构:您可以将任意数据的地图附加到抛出的例外,从而允许捕获例外的代码来检查,日志或以其他方式处理此ex-data
。
例如, assert
语句提供了商业逻辑中有价值的理智检查,但是当他们失败时,通常是非常希望知道它们是如何失败的。可以尝试将这些信息插入异常字符串中,但有时相关数据对于一个顽强的异常消息来说太大了。取而代之的是,检查所需的属性并使用所附的所有相关数据投掷ex-info
可以保留错误消息的简洁性,同时节省开发人员巨大的时间,尤其是在REPP进行调试时。
ex-info
的主要弱点之一是,它的使用可以鼓励无标准的临时例外:如果您抓住了前侵入式,它的类型与所有其他前Infos相同,其字符串是任意的,而不是您只能依靠ex-data
中出现的任何特定键,地图很可能完全空了。
如果您想在一个大型项目中享受广泛使用ex-info
的好处,但您也想保留理智的措施定义明确的错误的好处,您最终可能会在应用程序的每个逻辑组件中求助于使用通用的clojure成语,或者定义自己的定义一组标准的“投掷功能”,这些功能使用ex-info
但可以保证一定的刚性:也许是例外字符串的常见前缀,也许是ex-data
图中的某些保证键。
该库的主要目的是,恢复,是提供简化此过程的工具。它提供了定义标准前INFO表格的实用程序,以及在编译时间检查的能力。
recide
中工具生成的所有ex-info
地图至少包含两个密钥:
:recide/error
,其值是ErrorForm
的实例。ErrorForm
定义的“类型键”(recide中的默认值为:recide/type
)。 可以在此处找到Clojure API文档。 Java API文档可以在此处找到。
recide.core/insist
类似于assert
。它的签名是相同的,就像assert
它仅在clojure.core/*assert*
为true时执行。
但是,它没有抛出AssertionError
,而是以形式的解释性字符串抛出了一个ex-info
:“断言失败:<soptert optert opertert ofert oferters conseption>”。 EX-DATA中的类型是:recide/assertion
。 insist
使用的另外两个密钥:
:expression
,其值是insist
中包含的实际表达式:values
,其值是从表达式中使用的每个变量到其在失败时值的映射。 :只有每当recide.impl/*capture-insists*
为true时, :values
才存在,但默认情况下是错误的。在库加载时间,如果以下至少一个是正确的,则将其设置为true:
insist
的签名是[[expr] [expr message]]
,而由此产生的Ex-Info中的前数据具有以下形式:
{ :recide/type :recide/assertion ,
:expression <expr>
:values { ... }}
使用中的示例:
( 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
有两个ARITIT: ([type msg data] [type msg data cause])
。 error
构建了带有映射data
ex-info
,其类型(再次表示为:recide/type
默认为默认情况下)是type
。根据Java Idiom,提供cause
仅给出了例外原因。
( 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
具有相同的两个Arities。 raise
引发由error
构建的异常。
assoc
/ update
重新错误错误的EX-DATA便利功能: recide.core/assoc-error
和recide.core/update-error
每个返回原始类型的新例外,ex-data已修改为与assoc
和update
一样。
有时,在将误差变回异常并扔掉之前,传递并操纵错误表示错误可能会很方便。为此,我们提供了recide.core/error->map
和recide.core/error-map->throwable
,可以按照您的预期进行。
此外,我们为返回地图表单的错误以及相应的谓词错误映射提供了替代error-map
“构造函数” error-map?
。
对于带有消息msg
,ex-data data
和cause
任何类型type
错误,持久地图看起来像这样:
{ :recide/error <ErrorForm>,
:recide/type type,
:recide/msg msg,
:recide/cause cause,
:recide/data data}
除了:可以通过提供自定义的ErrorForm
来修改所有这些密钥:recide/error
(有关详细信息,请参见下文)。
raise
和error
几乎没有提供标准异常类型。为了进一步解决这一问题,恢复提供了deferror
和deferror-group
。
deferror
deferror
是一个宏,它采用错误名称,类型和“通用”字符串,将前缀此类错误的所有错误。还可以选择地收集了必需的密钥。如果指定了必要的键,则在使用deferror
定义的工具时,将抛出编译时错误,而无需在源代码中明确指定这些键。
示例用法:
( deferror storage-timeout
:storage/timeout
" A storage operation timed out "
[ :method-at-fault :timeout-ms ])
在此示例中, deferror
呼吁将定义两个新的宏, storage-timeout
和raise-storage-timeout
。为了方便起见,任何有能力的IDE都将能够在这些新Vars上访问详细的Docstrings:
> ( 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}
如果您尝试在数据图中使用其中的任何一种,则指定每个必需的键,则Clojure编译器将抛出一个例外:
> ( 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
是一个宏,它定义了整个错误家族。它需要一个错误名称,基本类型的声明和一些子类型声明。
每个基本类型声明必须是代表此组错误的通用命名空间的关键字,或者其第一个元素是这样的关键字,其第二个元素是所需键的序列。每个子类型中都需要此处指定的键。
每个子类型声明由一个序列组成,其第一项是符号,第二项是误差的通用字符串,第三项(可选)项是该子类型的必需键序列。
例子:
( deferror-group parse-err
( :query.invalid [ :expression ])
( find-spec " Invalid find spec " )
( inputs " Invalid inputs " [ :invalid ]))
在此示例中,定义了两种错误类型:: :query.invalid/find-spec
和:query.invalid/inputs
。第一个需要:expression
,但第二个需要同时:expression
和:invalid
。
与deferror
一样,由deferror-group
生产的公用事业也具有详细的Docstrings:
> ( 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"}}
如前所述,所需键在省略时会产生编译时错误。
> ( 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
如果我们使用关键字来指定错误类型,那么能够通过这些关键字catch
错误将很有用。 RECIDE为此提供了try*
。 try*
是一个扩展到Clojure的try
的宏,这是Clojure的少数特殊形式之一。在大多数情况下, try*
应该完全像try
一样。它的不同之处在于它揭示了增强的catch
功能。您可以抓住:
instance?
检查。ErrorForm
如何)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`.
请注意,您可以使用形式的关键字:namespace/*
作为通配符来捕获回收错误的家庭,例如由deferror-group
定义的错误。
> ( try* ( raise :genus/species-1
" went extinct "
{ :year -1839421 })
( catch :genus/* e
( println ( :year ( ex-data e)))))
; ; -1839421
recide提供了一系列工具,用于获取一个例外的消毒版本,该版本应被认为是安全登录的(但结果可能没有用)。
该类包含一些静态实用方法:
getCurrentSanitizationLevel()
等效于Deref'ing recide.sanex/*sanitization-level*
。
createSuppressionMap(...)
使用与布尔Args相对应的适当关键字创建一个iPersistentMap。
sanitize(Throwable)
, sanitize(Throwable, IPersistentMap)
快捷方式到Clojure ifn recide.sanex/sanitize
。
ErrorForm
默认情况下,该库提出的错误使用ex-info
用作构造函数, recide.utils/serialize-throwable
and recide.utils/deserialize-throwable
用于(de)序列化,在地图形式中使用:recide/type
,:recide, :recide/msg
, :recide/data
,以及:recide/cause
作为其标准关键字。
通过定义新的ErrorForm
,您可以为自己的库更改所有这些行为。通过修改关键字,您可以“品牌”出现在库中。您可以将ex-info
交换为同一Arity的另一个构造函数,这将期望返回IExceptionInfo
。例如,与Java Interop相关的问题可能会激励创建一个新的异常类,而Clojure Idioms可能会激发保留前INFO兼容性。
您可以通过recide.core/def-error-form
,仅覆盖您希望覆盖的方法;方法未指定默认值以恢复库默认值。
recide.core
中的错误处理方法是对所使用的特定ErrorForm
不可知的。要使用自定义ErrorForm
创建错误,您可以轻松地生成一套完整的回收方法,专门针对您的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.