考虑这段完全嵌套的clojure代码,它描述了向命令行工具传递的edn-config文件的验证过程。
(defn -main [& [config-path]]
(if config-path
(if-let [content (read-file config-path)]
(if-let [raw-data (read-edn content)]
(if-let [parsed (parse raw-data)]
(start-processing parsed)
(error "parsing-error"))
(error "invalid-edn-format"))
(error "file-not-found"))
(error "no argument"))注意:被调用的函数是虚拟函数。
这就需要更少的嵌套和更少强制的方法来做到这一点。你对这里的改进有什么建议吗?
上面的函数可以归结为实际的4个验证函数,它们在一个链中调用:(1)参数-检查,(2)文件读取,(3)分析-EDN,(4)分析-数据。它们对待“错误”的方式不同:对于1和4,我使用的是clojure.spec,因此:cljure.spec/ fail在失败时返回。其他的(2和3)将抛出一个异常,当有问题时。这使得这里的抽象变得特别困难。
发布于 2016-08-29 14:52:43
使用Either monad。
首先,您需要修改函数以返回Either
(require '[cats.monad.either :as either])
(defn assert-config-path-e [config-path]
(if config-path
(either/right config-path)
(either/left "no argument")))
(defn read-file-e [config-path]
;; interpret return value as error
(if-let [content (read-file config-path)]
(either/right content)
(either/left "file-not-found")))
(defn read-edn-e [content]
(try
(read-edn content)
(catch ...
;; interpret thrown exception as error
(either/left "invalid-edn-format"))))
(defn parse-e [raw-data]
(if-let [parsed (parse raw-data)]
(either/right parsed)
(either/left "parsing-error")))Right值表示成功的计算,Left -错误已经发生.
之后,使用do-表示法/mlet组合所有内容。
(require '[cats.core :as cats])
(defn -main [& [config-path]]
(cats/mlet [_ (assert-config-path-e config-path)
content (read-file-e config-path)
raw-data (read-edn-e content)
parsed (parse-e raw-data)]
(cats/return (start-processing parsed)))发布于 2016-08-29 13:58:35
使用delay延迟计算,但允许将值绑定到本地let变量。接下来使用and来计算表达式,但是会在nil上短路,这就是你想要的.然后,cond将给出特定的错误消息,并在没有错误的情况下评估最后一步。
(defn -main [& [config-path]]
(let [content (delay (read-file config-path))
raw-data (delay (read-edn @content))
parsed (delay (parse @raw-data))]
(and config-path
@content
@raw-data
@parsed)
(cond
(nil? config-path) (error "no argument")
(nil? @content) (error "file-not-found")
(nil? @raw-data) (error "invalid-edn-format")
(nil? @parsed) (error "parsing-error")
:else (start-processing @parsed))))它具有以下优点:
delay?)发布于 2016-08-29 13:36:48
我将考虑为逻辑的每个部分编写单独的函数,以便它们能够处理验证,并使用一致的方法报告错误(在本例中为例外):
(defn validate-config-path [config-path]
(if config-path
config-path
(throw ...)))
(defn read-raw-config [config-path]
(try
(read-file config-path)
(catch ...
(throw ...))))
(defn read-edn-config [raw-config]
(try
...
(catch ...
(throw ...))))
(defn read-parsed-config [edn-config]
(try
...
(catch ...
(throw ...))))
(defn -main [& [config-path]]
(try
(-> config-path
validate-config-path
read-edn-config
read-parsed-config)
(catch Exception e
(error (.getMessage e)))))使用这种方法,您可以轻松地分别测试每个部分(因为它封装在一个单独的函数中),并在其他地方重用它。您还可以集中处理在一个地方向用户显示错误的方式,这样就可以更改向用户显示编程错误的方式。
https://stackoverflow.com/questions/39206491
复制相似问题