`
yangdong
  • 浏览: 65174 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

在 Clojure 中处理异常

 
阅读更多
Update: As of Clojure 1.3, Clojure standardized the exception handling mechanism. Refer to clj-stacktracefor the details.


Clojure 中虽然使用了 Java 的异常处理机制。但是,Clojure 很难自然地自定义自己的异常。我在与 Java 类库进行交互就时恰恰遇到了这种需求。下面的代码是与 svn-kit 进行交互的代码,它们提供了 svn-kit 的一个 wrapper。
(defmacro- try-catch-svn-ex [& exprs]
  `(try ~@exprs
    (catch org.tmatesoft.svn.core.SVNAuthenticationException e#
      :auth-ex)
    (catch org.tmatesoft.svn.core.SVNException e#
      (if (re-matches #".*404 Not Found.*" (.getMessage e#))
        nil
        (throw e#)))))

(defn svn-get-file! [svn-repo file-path local-file]
  (with-open [os (output-stream (file local-file))]
    (try-catch-svn-ex
      (.getFile svn-repo file-path -1 (SVNProperties.) os)
      local-file)))

调用 svn-get-file! 时可能会出现用户名密码无效的问题,这时候我希望能给用户重新输入的机会。但是又不想被其它的异常干扰。这时候我可以选择将 SVNAuthenticationException 暴露出去,但是明显捕获这样一个异常是很让外层函数头疼的事。同时,自定义 Clojure 异常在外部捕获更让人头疼。所以,我在捕获了 SVNAuthenticationException 后返回一个 :auth-ex。

这种异常处理机制的最大的问题就是回到 C 语言时代检查函数返回值的方式上。这种方式写出来的程序会比较繁琐。最好的办法是用 Stuart Chouser 写的 clojure.contrib.error-kit 库。它提供了类似 Common Lisp 的异常处理体系。比传统的 try...catch 要强大很多。现在,我用 error-kit 库重写上面的函数:
(require '[clojure.contrib.error-kit :as ek])

(ek/deferror *svn-auth-error* [] [msg]
  (:msg msg)
  (:unhandled (ek/throw-msg Exception)))
    
(defmacro- try-catch-svn-ex [& exprs]
  `(try ~@exprs
    (catch org.tmatesoft.svn.core.SVNAuthenticationException e#
      (ek/raise *svn-auth-error* (.getMessage e#)))
    (catch org.tmatesoft.svn.core.SVNException e#
      (if (re-matches #".*404 Not Found.*" (.getMessage e#))
        nil
        (throw e#)))))

(defn svn-get-file! [svn-repo file-path local-file]
  (with-open [os (output-stream (file local-file))]
    (try-catch-svn-ex
      (.getFile svn-repo file-path -1 (SVNProperties.) os)
      local-file)))

注意我用 raise 调用代替了 :auth-ex 返回值。如果捕获到了权限异常,那么我们就 raise 一个 error。这个 error 必须用 deferror 函数定义。这个 *svn-auth-error* 在没有处理函数来处理它时会通过 throw-msg 调用抛出 Exception 异常,异常的消息内容就是 :msg 所指定的消息。

注意 *svn-auth-error* 后面的第一个括号表示“父”error 是谁。这个父子关系内部通过标准库的 derive 方法定义。这里它没有父 error,所以留空。这时调用 svn-get-file! 的函数就可以拿到这个 error,可以选择让栈爆掉,也可以选择在异常抛出点继续执行。这里我们选择简单地处理后重新执行函数:
(defn svn-get-file-ex! [svn-repo file-path local-file]
  (let [ret (ek/with-handler
              (svn-get-file! svn-repo file-path local-file)
              (ek/handle *svn-auth-error* [msg]
                (println (str "Error getting " file-path ", authentication failed"))
                (rm-scm-repo-username!)
                (rm-scm-repo-password!)
                (get-scm-repo-username!)
                (get-scm-repo-password!)
                (svn-get-file-ex! (get-scm-repo) file-path local-file)))]
    (if
      (nil? ret)
        (ek/raise *get-scm-file-error* (str "404 not found: " file-path))
      ret)))

注意此时对 svn-get-file-ex! 的递归调用不能用 recur。很遗憾,可能是因为 with-handler 或 handle 宏展开后定义了新的函数或者 loop。同时也请注意 deferror 时的 :unhandled 后面的 throw-msg 不要用 (throw (Exception. msg)) 来代替。如果这样做,你会发现异常是抛出去了,但是却捕获不到。原因是 :unhandled 后面期望跟的是一个函数定义。具体可以参看 throw-msg 的实现。

更多关于 error-kit 的信息,比如 continue,请参阅:ANN: clojure.contrib.error-kit

但是如果你不需要 error-kit 里的 continue 相关的功能的话,也可以使用 clojure.contrib.condition。这个库比较容易使用。而且还带了一个 print-stack-trace 方法,可以打印出比较干净的栈。示例可以参看 contrib 库源代码里面的 example 目录中的 condition/example.clj。

这两种库实现上都利用 Java 的异常来跳出栈。所以,如果你想捕获所有的异常,包括这两种库抛出来的,可以用 catch Throwable。值得一提的是,condition 库的 print-stack-trace 是通用的。不仅可以打印 condition 库抛出来的异常,也可以打印其它的异常。

contrib 库中还有一个 except,也是用来处理异常的。作者跟 condition 库是一个人。根据作者的原话,condition 库是 except 库的加强。
分享到:
评论

相关推荐

    try-let:Clojure let表达式的更好的异常处理

    try-let:Clojure let表达式的更好的异常处理

    elv:Clojure 基于 Ring 的应用程序的异常记录器和查看器

    Elv 做了两件事: 记录所有未处理的异常在单独的页面上显示记录的异常Elv 提供了一个简单但可配置的环形中间件,它将捕获在环形服务器处理请求期间抛出的任何异常。 环的所有请求参数都将被记录。 除此之外,elv 还...

    shovel:一个简单的 Clojure 库,用于使用 core.async 处理 Kafka 流

    一个简单的 Clojure 库,用于使用 core.async 处理 Kafka 流。 它有一个简单的(高级)消费者和一个简单的生产者。 适用于 Kafka 0.8.1 或更新版本。 版本和依赖信息 有两个版本: 用于旧 API 用于新 API (kafka-...

    clj-try:Clojure 尝试错误宏

    这组宏允许以更实用、可组合的方式来处理在风格上类似于在其他函数式语言中发现的 Try 计算 (Monad) 的异常。 这些宏基于三个 Clojure 线程宏 ->(线程优先)、->>(线程最后)和 as->(线程为)。 传递给 try 块的...

    farolero:线程安全的通用Lisp样式条件,并为Clojure(Script)重新启动

    异常是在JVM中处理错误的默认方式,但是Clojure没有简单的方法来用新类型扩展异常机制,从而限制了您对不处理而无需重新抛出的错误的控制程度。 诸如之类的条件库为程序员提供了报告错误的工具,但是恢复中的选项...

    resilience-for-clojure:Clojure包装上的Resilience4j

    还有一个示例展示了如何在 一起使用它们以及如何在处理异常。 此外,它还具有创建Registry,以收集散布在各处的Circuit Breaker , Retry , Rate Limiter和Bulkhead ,并将它们一起管理。 和例子来组侦听消耗从您...

    t3chnique:实验 Clojure TADS3 VM

    T3chnique 是一个实验性的、正在进行的工作,尝试在 clojure 中实现虚拟机。 现在这只是一个偶尔的、空闲的、迭代的实现草图。 到目前为止,这对 VM 的几个主要方面缺乏一致的方法(特别是:元类继承、统一异常/...

    funoop:Clojure的“企业”方式

    漏斗最初为创建幻灯片(适用于Firefox): 谈话: 在许多基于JVM的强大语言中,Clojure和Java发挥了很大的作用。 几年前从Spring / Java进入Clojure,我真的很希望那时能有人从Spring / Java的角度向我解释函数式...

    pure-conditioning:一个简单,快速,功能纯正的Clojure条件重启系统

    是Clojure中的纯功能,快速且干净的分解/重新启动系统。 它不使用异常,根本不需要全局状态。 该项目仍处于Alpha阶段,可能会带来重大变化。 一旦获得第一轮反馈和错误修复,我将其提高到1.0.0,并在此之后尝试使...

    网络事件流处理系统Riemann.zip

    Riemann 是一个网络事件流处理系统(network event stream processing system),用 Clojure 编写。Riemann 给带有多个移动部件的系统提供低延迟、短暂共享的状态。Riemann 使用强大的流处理语言从你的服务器和应用...

    failjure:Monadic错误实用程序,通常用于Clojure(script)项目

    失败 Failjure是一个实用程序库,用于处理Clojure(Script)中的失败计算。 它为功能纯净度更为重要的应用程序提供了基于异常的错误处理的替代方法。 它是受实现的启发。安装将以下内容添加到您的构建依赖项中: ...

    promenade:使用ClojureClojureScript来大步走程序设计难题

    提供简单有效的API,而无需在Clojure中使用monad的笨拙 使该库的API的表面积保持小巧,有趣且易于使用 非目标 忠实执行monad 适应所有已知的单子(完整性) 仅限于单子思想 实现的功能 通过表示成功和失败(称为...

    basic-microservice-example:我们如何布局Clojure微服务的简单示意图

    逻辑处理纯业务逻辑,不应有副作用或引发异常。控制器所有其他层之间的“胶水”,协调纯业务逻辑,适配器和端口之间的调用。适配器将外部数据表示转换为内部数据表示的层,反之亦然。 充当缓冲,以保护服务免受外界...

    分布式深度学习库Deeplearning4j.zip

    此处为我们已经建立的各个库及其在系统整体中的所处位置:在定型深度学习网络的过程中,有许多可供调节的参数。我们已尽可能对这些参数进行解释,从而使Deeplearning4j能够成为Java、Scala和Clojure编程人员的DIY...

    Paguro:JVM的通用,空安全,不可变的集合和功能转换

    原始对象与装箱的对象(不要使用原始对象-泛型仍然无法处理它们) 在lambdas中检查异常(Paguro毫无保留地接受它们) 空指针异常Kotlin也几乎解决了所有这些问题,但是如果您陷于Java,Paguro是一个不错的解决方案。...

    dd-stat-alerts:使用 DataDog 进行统计警报

    DataDog 统计警报一个 Clojure 库,用于监控集群并对异常值发出警报。编译 cd dd-std-alerts && lein uberjar用法该软件目前有两件事: 以 1 分钟的时间块从 DD 下载数据并将其保存到磁盘使用统计引擎处理来自磁盘的...

    vnctst-audio4:html5游戏的音频播放库

    即使界面很简单,“必须按此顺序执行过程”,“必须在XX期间执行此过程”和“必须捕获此类异常”。拥有大量“承诺”毫无意义。 ”。我们正在努力尽可能地消除这种“承诺”。换句话说,“即使在不担心时间等的情况下...

    f:Python的功能性内容

    pcall_wrapsdef func ( a , b ): return a / bfunc ( 4 , 2 )>> > ( None , 2 )func ( 4 , 0 )>> > ( ZeroDivisionError ( 'integer division or modulo by zero' ), None )属性和项目链函数处理异常: # let's say...

    像计算机科学家一样思考Python(第2版).pdf

    书中还探讨了如何处理文件和数据库,如何理解对象、方法和面向对象编程,如何使用调试技巧来修正语法错误、运行时错误和语义错误。每一章都配有术语表和练习题,方便读者巩固所学的知识和技巧。此外,每一章都抽出...

Global site tag (gtag.js) - Google Analytics