【问题标题】:How to build robust data apis in clojure?如何在 clojure 中构建健壮的数据 API?
【发布时间】:2011-10-07 05:09:58
【问题描述】:

我发现由于缺少数据 API,我的 clojure 应用程序在结构上非常迅速地耦合。我有带有名称的键的映射,如果输入错误,因为要抛出异常或错误。我还注意到在解构列表时很容易出错(例如,您可能解构了列表的错误部分)。

来自 Java 世界,通常我使用我的 IDE 来帮助我从最小的、无序的数据对象中获取“正确的”数据。但是 clojure 映射传递似乎与此相反。

在没有类型系统或 ide 代码补全的情况下,clojurians 如何进行防御性编码?

【问题讨论】:

    标签: types clojure protocols code-completion decoupling


    【解决方案1】:

    为您的“模式”(键以及值的类型等)编写验证器函数,然后在代码的前置条件和后置条件中使用 thm —— 因为它们的语法鲜为人知,所以在这里快速复习一下:

    (defn foo [x y] ; works with fn too
      {:pre [(number? x) (number? y)]
       :post [(number? %) (pos? %)]}
      (+ (* x x) (* y y)))
    

    它们依赖于assert,因此可以被禁用。 (doc assert) 了解更多详情。

    【讨论】:

    • 发布后,core.typed 也发布了,也可以做同样的事情typedclojure.org
    【解决方案2】:

    也许您正在寻找记录?

    (require '[clojure.set :as cset])
    
    (defrecord Person [name age address phone email])
    
      ;; Make a keyword-based constructor to verify 
      ;; args and decouple ordering.
    (let [valid #{:name :age :address :phone :email}]
      (defn mk-person[& args]
        (let [h (apply hash-map args)
              invalid (cset/difference (set (keys h)) valid)]       
          (when-not (empty? invalid)
            (throw (IllegalArgumentException. (pr-str invalid))))
          ; any other argument validation you want here
          (Person. 
            (:name h) (:age h) (:address h) (:phone h) (:email h)))))
    
    => (def p (mk-person :name "John" :email "john@hotmail.com"))
    #:user.Person{:name "John", :age nil, :address nil, :phone nil, 
                  :email "john@hotmail.com"}
    

    现在您可以通过使用函数(异常)或关键字(非异常)访问数据来选择是否要对错误输入的名称进行异常处理。

    => (.fax p) 
    java.lang.IllegalArgumentException: 
        No matching field found: fax for class user.Person
    => (:fax p)
    nil
    

    此方法要求您避免与现有方法冲突的字段名称。 (参见@Jouni 的评论。)

    或者,您可以通过使用关键字进行查找和检查无效键的访问器函数来绕过字段名称限制:

    (defn get-value [k rec]
      (let [v (k rec ::not-found)]
        (if (= v ::not-found)
          (throw (IllegalArgumentException. (pr-str k)))
        v)))
    
    => (get-value :name p)
    "John"
    => (get-value :fax p)
    IllegalArgumentException: :fax
    

    “解构列表的错误部分”类型的问题可能来自尝试在列表中编码“人”之类的内容;那么您需要记住诸如“邮政编码是 '地址' 列表中的第四个元素在 'person' 列表中的位置 3”之类的东西。

    在“经典”Lisp 中,您可以通过编写访问器函数来解决这个问题,在 Clojure 中,您可以使用记录。

    错别字会在任何编程语言中引起问题,你能做的最好的就是尽早发现它们。

    具有自动补全功能的 Java IDE 可能会在您仍在键入时捕捉到一些拼写错误,而静态类型语言会在编译时捕捉到其中的许多错误,但在动态语言中,您要到运行时才能找到它们。有些人认为这是动态语言(包括 Python、Ruby 等)的缺点,但鉴于它们的流行,不少程序员认为获得的灵活性和节省的代码比失去 IDE 自动完成和编译时错误更重要。

    这两种情况的原理都是一样的:早期的异常更好,因为需要通过更少的代码来寻找原因。理想情况下,堆栈跟踪会引导您直接找到错字。在 Clojure 中,记录和访问器函数为您提供了这一点。

    【讨论】:

    • 虽然没有名为 size 的记录字段。对(.size rec) 的调用将调用java.util.Collection 中定义的方法 size,对于Clojure 记录所具有的所有接口中的所有空方法也是如此。
    • @Jouni 是的,有 17 个名字你不能用; sizecountvaluesmeta 可能是最麻烦的。您要么必须将它们视为保留字并避免使用它们,要么编写自己的数据 API。 AFAIK。
    猜你喜欢
    • 2010-10-10
    • 2021-06-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-30
    • 2014-06-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多