如何在clojure中构建健壮的数据apis

时间:2011-10-07 05:09:58

标签: types clojure protocols code-completion decoupling

大家好:我发现由于缺少数据API,我的clojure应用程序在结构上非常快速地耦合... - 我有一些带有名称的键映射,如果输入错误,会导致抛出异常或错误。我还注意到,在对列表进行解构时很容易出错(例如,你可能会破坏列表中错误的部分).......

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

如果没有类型系统或ide代码完成,clojurians如何进行防御性编码......?

2 个答案:

答案 0 :(得分:5)

也许您正在寻找记录?

(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

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

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

错别字会导致任何编程语言出现问题,你可以做的最好的事情就是尽早抓住它们。

具有自动完成功能的Java IDE可能会在您仍在键入时捕获一些拼写错误,并且静态类型语言将在编译时捕获许多错误,但在动态语言中,您将无法在运行时找到它们。有些人认为这是动态语言(包括Python,Ruby等)的缺点,但鉴于它们的普及,相当多的程序员认为获得的灵活性和代码保存比丢失IDE自动完成和编译时错误更重要。 p>

原则在两种情况下都是相同的:早期的例外情况更好,因为需要更少的代码来查找原因。理想情况下,堆栈跟踪会引导您直接输入错字。在Clojure中,记录和访问器函数可以为您提供。

答案 1 :(得分:5)

为你的“模式”(密钥以及值类型等)编写验证器函数,然后在代码中的前后条件中使用它们 - 因为它们的语法鲜为人知,这是一个快速的复习:

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

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