استجيب ، من الشخص الثاني الحاضر الحاضر الحتمي من recidere : - أن تراجع ، تعال إلى شيء ؛ للحد من
assoc / update في عمليات التسرب السابقة لـ Recide Errorsdeferrordeferror-groupgetCurrentSanitizationLevel()createSuppressionMap(...)sanitize(Throwable) ، sanitize(Throwable, IPersistentMap)ErrorForm مخصص يعد ex-info لـ Clojure عبارة عن بنية مفيدة للغاية: يمكنك إرفاق خريطة للبيانات التعسفية باستثناء تم إلقاؤها ، مما يسمح للرمز الذي اكتشف استثناء فحص أو تسجيل أو معالجة هذه ex-data .
على سبيل المثال ، توفر بيانات assert اختبارات عقلانية قيمة في منطق العمل ، ولكن عندما يفشلون يكون من المستحسن عادة معرفة كيفية فشلها. يمكن للمرء أن يحاول إدراج هذه المعلومات في سلسلة الاستثناء ، ولكن في بعض الأحيان تكون البيانات ذات الصلة كبيرة جدًا لرسالة استثناء مستردة. بدلاً من ذلك ، يمكن للتحقق من الخاصية المطلوبة ورمي ex-info مع جميع البيانات المرفقة ذات الصلة الحفاظ على إيجاز رسائل الخطأ مع حفظ المطورين كميات زمنية هائلة ، خاصة عند تصحيح الأخطاء في الاستبدال.
واحدة من نقاط الضعف الرئيسية في ex-info هي أن استخدامه يمكن أن يشجع الاستثناءات المخصصة مع عدم وجود معايير: إذا قمت بالقبض على info ، فإن نوعه هو نفس جميع المشاركات السابقة الأخرى ، وسلسلةها تعسفية ، وليس أنت فقط غير قادر على الاعتماد على أي مفتاح معين يظهر في ex-data ، من الممكن تمامًا أن تكون الخريطة فارغة تمامًا.
إذا كنت ترغب في الاستمتاع بفوائد استخدام ex-info على نطاق واسع في مشروع كبير ، لكنك تريد أيضًا الاحتفاظ بها مقياس من العقل فوائد الأخطاء المحددة جيدًا ، من المحتمل أن تقوم في النهاية باللجوء ، في كل مكون منطقي من تطبيقك ، إما إلى الانفصال عن تعبيرات clojure العالمية أو لتحديد تحديد مجموعة قياسية من "وظائف الرمي" التي تستخدم ex-info لكن ex-info ولكن تضمن صلابة معينة: ربما بادئة مشتركة لسلاسل الاستثناء ، وربما بعض المفاتيح المضمونة في خرائط ex-data .
الغرض الأساسي من هذه المكتبة ، Recide ، هو توفير أدوات لتخفيف هذه العملية. يوفر الأدوات المساعدة لتحديد نماذج ex-info القياسية ، وكذلك القدرة على التحقق في وقت الترجمة التي يتم استخدامها على النحو المقصود.
تحتوي جميع خرائط ex-info التي تم إنشاؤها بواسطة أدوات في recide على مفتاحين على الأقل:
:recide/error ، الذي تكون قيمته مثيل ErrorForm .ErrorForm (افتراضي في Recide هو :recide/type ). يمكن العثور على وثائق API Clojure هنا. يمكن العثور على وثائق Java API هنا.
recide.core/insist مماثل assert . توقيعه هو نفسه ، ومثل assert أنه ينفذ فقط عندما يكون clojure.core/*assert* صحيح.
ولكن بدلاً من إلقاء AssertionError ، يلقي ex-info مع سلسلة توضيحية من النموذج: "فشل التأكيد: <تعبير مؤكد أو رسالة مزود>". النوع في data السابقين هو :recide/assertion . هناك مفتاحان آخران يستخدمان من قبل insist :
:expression ، الذي تكون قيمته هو التعبير الفعلي الوارد في insist:values ، التي تكون قيمتها خريطة من كل متغير يستخدم في التعبير إلى قيمته في وقت الفشل. :values موجودة فقط عندما يتم recide.impl/*capture-insists* صحيح ، ولكنها خاطئة بشكل افتراضي. في وقت تحميل المكتبة ، يتم ضبطه على صحيح إذا كان أحد ما يلي على الأقل صحيحًا:
توقيع 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 يحتوي على اثنين من arity: ([type msg data] [type msg data cause]) . يقوم error ببناء ex-info مع data الخريطة المرفقة ، الذي type على نوعه (مرة أخرى ، يدل على :recide/type افتراضيًا). توفير cause يعطي مجرد استثناء سبب وفقًا لـ Java Idiom.
( 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 له نفس اثنين من القشور. raise رمي الاستثناء الذي تم بناؤه عن طريق error .
assoc / update في عمليات التسرب السابقة لـ Recide Errors وظائف الراحة: recide.core/assoc-error و recide.core/update-error كل إرجاع استثناءات جديدة من النوع الأصلي ، مع تعديل البيانات السابقة كما هو الحال مع assoc update .
في بعض الأحيان ، قد يكون من المريح أن يتجول ومعالجة خريطة خريطة للخطأ قبل إعادته إلى استثناء ورميه. تحقيقًا لهذه الغاية ، نقدم recide.core/error->map و recide.core/error-map->throwable كما تتوقع.
بالإضافة إلى ذلك ، نحن نقدم "مُنشئ" error-map بديلة للأخطاء التي تُرجع نموذج الخريطة ، error-map? .
لأي خطأ من type مع رسالة msg ، data data السابقين ، cause ، فإن الخريطة المستمرة تبدو هكذا:
{ :recide/error <ErrorForm>,
:recide/type type,
:recide/msg msg,
:recide/cause cause,
:recide/data data} يمكن تعديل كل هذه المفاتيح باستثناء :recide/error عن طريق توفير ErrorForm مخصص (انظر أدناه للحصول على التفاصيل).
raise وخطأ error القليل جدًا لتوفير أنواع الاستثناءات القياسية. لمعالجة هذا الأمر أكثر ، توفر Recide 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 الجديدة:
> ( 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-msdeferror-group deferror-group هو ماكرو يحدد عائلة كاملة من الأخطاء. يستغرق اسم خطأ ، وإعلان نوع الأساس ، وبعض عدد من إعلانات النوع الفرعي.
يجب أن يكون كل إعلان من نوع الأساس إما كلمة رئيسية تمثل مساحة الاسم الشائعة لهذه المجموعة من الأخطاء ، أو tuple الذي يكون العنصر الأول هو الكلمة الرئيسية ويعتبر العنصر الثاني عبارة عن سلسلة من المفاتيح المطلوبة. ستكون المفاتيح المحددة هنا مطلوبة في كل نوع فرعي.
يتكون كل إعلان من النوع الفرعي من تسلسل مصطلحه الأول هو رمز ، والمصطلح الثاني هو سلسلة عامة للخطأ ، والمصطلح الثالث (الاختياري) هو سلسلة من المفاتيح المطلوبة لهذا النوع الفرعي.
مثال:
( 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* هو ماكرو يتوسع في try Clojure ، وهو أحد أشكال 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/* كطاردات برية للقبض على عائلات أخطاء Recide ، مثل تلك المحددة بواسطة 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(...)ينشئ ipersenceMAP مع الكلمات الرئيسية المناسبة المقابلة لـ args المنطقية.
sanitize(Throwable) ، sanitize(Throwable, IPersistentMap) اختصار إلى clojure ifn recide.sanex/sanitize .
ErrorForm مخصص بشكل افتراضي ، تستخدم الأخطاء التي أثيرتها هذه المكتبة ex-info كمشارك ، recide.utils/serialize-throwable و recide.utils/deserialize-throwable for (de) serialization ، وفي شكل خريطة يستخدمون :recide/type ، :recide/msg ، :recide/data ، و :recide/cause ككلمات رئيسية قياسية.
من خلال تحديد ErrorForm جديد ، يمكنك تغيير كل هذا السلوك لمكتبة الخاصة بك. من خلال تعديل الكلمات الرئيسية ، يمكنك "العلامة التجارية" أخطاء تخرج من مكتبتك. يمكنك مبادلة ex-info لمؤسسة أخرى من نفس القوس ، والتي من المتوقع أن تعيد IExceptionInfo . على سبيل المثال ، قد تحفز المخاوف المتعلقة بـ Java interop إنشاء فئة استثناء جديدة ، في حين أن تعبيرات Clojure قد تحفز الاحتفاظ بتوافق ex-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.