`
yangdong
  • 浏览: 66423 次
  • 性别: 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 库的加强。
分享到:
评论

相关推荐

    Lacinia纯Clojure实现的GraphQL

    3. **错误处理**:Lacinia支持自定义错误处理机制,使得开发者可以优雅地处理异常并返回适当的错误信息。 4. **执行引擎**:Lacinia的执行引擎高效地处理GraphQL查询,遵循GraphQL的执行算法,包括分层解析、字段...

    Clojure学习——使用clojure jdbc操作mysql

    此外,`clojure.java.jdbc`提供了诸如批处理、结果集转换、异常处理等功能,使得在Clojure中操作数据库变得更加便捷和灵活。 至于"源码"标签,可能意味着这篇博客或压缩包中的示例代码会展示实际的Clojure代码片段...

    为mysql设计的Clojure库.zip

    总的来说,这个"mysql-master"项目为Clojure程序员提供了一种优雅的方式与MySQL数据库进行交互,使得在Clojure中处理数据存储变得更为便捷和高效。如果你是Clojure开发者并打算使用MySQL,理解并熟练运用这个库将是...

    josef:使用Clojure传感器处理Kafka流

    通过查看这些文件,我们可以深入学习如何配置和使用josef库,包括设置Kafka连接、定义消费者组、处理异常以及与其他Clojure库集成等。 总的来说,"josef:使用Clojure传感器处理Kafka流"这个主题涵盖了Clojure语言...

    clojure-sha-3-源码.rar

    5. **错误处理**:源码中可能包含异常处理机制,确保在遇到无效输入或资源不足时能够妥善处理。 6. **性能优化**:为了提高效率,Clojure的SHA-3实现可能采用了一些技巧,如批量处理数据、缓存中间结果或利用JVM的...

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

    Clojure的标准异常处理结构是`try`语句,它可以捕获并处理异常。然而,`try`通常与嵌套的`let`一起使用,以定义局部变量和提供块级作用域。`try-let`模式旨在简化这种组合,使代码更简洁。 首先,我们来理解Clojure...

    riemann:Clojure中的网络事件流处理系统

    Riemann是一款基于Clojure语言的网络事件流处理系统,专为实时监控、度量和日志分析设计。在IT运维领域,Riemann因其强大的数据处理能力和灵活性而备受推崇。本文将深入探讨Riemann的核心概念、工作原理以及如何利用...

    kafka-streams-clojure:Clojure换能器与Kafka Streams的接口

    此外,你还可以看到如何处理错误和异常,以及如何在Clojure中使用Kafka的键值对和窗口操作。 这个库的一个关键优势是其灵活性。由于Clojure的换能器可以任意组合,因此你可以构建出非常复杂的数据处理逻辑,同时...

    更轻松:通过clojure和clojure进行的透明,非侵入式RPC

    标题中的“更轻松:通过Clojure和Clojure进行的透明,非侵入式RPC”意味着我们要探讨的主题是如何在Clojure编程环境中实现远程过程调用(RPC)机制,而且这种实现方式应该是透明且非侵入式的。这意味着开发者在使用...

    c4:以 Clojure 方式处理记录文件的便捷功能

    4. 错误处理:由于Clojure的异常处理机制,c4可能提供了优雅的错误处理方案,能够捕获和记录处理过程中出现的问题。 5. 并发支持:利用Clojure的core.async库,c4可能支持并发处理记录,提升性能,特别是在多核...

    raml-clj-parser:在clojure中实现的RAML解析器

    `raml-clj-parser` 是一个在Clojure编程语言中实现的RAML解析器,它是对RAML规范的Clojure实现,允许开发人员将RAML文件转换为Clojure数据结构,从而方便在Clojure项目中处理和操作API定义。 Clojure是一种功能强大...

    灯塔:简化clojure关系数据库查询,迁移和连接池

    例如,灯塔提供的Datomic-Pull-SQL功能,使得从Datomic数据库中提取数据变得异常简单,这在处理大量数据时能大大提升效率。 连接池是数据库应用中的重要组件,它管理数据库连接的创建和释放,以提高性能并减少资源...

    clojure-data-science:关于使用 Clojure 进行数据科学的演示

    本文将深入探讨如何利用Clojure的强大功能进行数据处理、分析和建模,从而揭示隐藏在大数据背后的模式与洞察。 一、Clojure的数据处理基础 1. 集合操作:Clojure提供了丰富的集合类型,如列表、向量、映射和集合,...

    understanding-monads:用 clojure 中的例子解释 monad

    5. **错误处理**:Monad也可以用于优雅地处理错误,例如,Try Monad在出现异常时可以捕获错误并返回一个表示失败的Monad。Clojure的`try`表达式虽然不完全符合Monad模式,但提供了类似的功能。 6. **IO Monad**:在...

    clj-ds:修改为在Clojure之外使用的Clojure数据结构

    5. **异常处理**:Clojure和Java的异常处理机制不同,`clj-ds`需要适当地桥接这两种机制,确保错误信息的传递和处理符合Java的习惯。 6. **序列化**:为了在Java和Clojure之间传递数据,可能需要支持序列化和反序列...

    匹配器:Clojure的功能齐全的符号模式匹配器

    在IT行业中,Clojure是一种基于Lisp的现代函数式编程语言,它运行在Java虚拟机(JVM)上。在Clojure中,符号模式匹配是一种强大的编程技术,它允许程序员根据给定的数据结构来执行不同的操作。"匹配器:Clojure的...

    特殊:特殊(条件)。 Clojure的条件系统

    总的来说,Clojure的条件系统是一个强大的工具,它允许程序员以声明式的方式处理异常和条件,提供了灵活的错误处理和恢复策略。通过理解和熟练使用这个系统,开发者可以创建更稳定、可维护的代码。

    core.async-intro:Clojure 的 core.async 库的介绍

    7. **错误处理**:core.async提供了`throw+`和`catch+`机制,允许在`go`块中处理异常。这些函数可以帮助编写更健壮的异步代码。 8. **与其他库的集成**:core.async库不仅可以用于Clojure,还可以与JavaScript环境...

    已弃用:Clojure中的弃用变得容易

    此外,开发者应该在项目中启用警告处理,确保在开发过程中能够及时发现并处理弃用的使用。 在Clojure测试中,可以使用`clojure.test/deftest`和`is`来检查是否误用了弃用的功能。例如: ```clojure (deftest test-...

Global site tag (gtag.js) - Google Analytics