10

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

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

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

4

2 回答 2

6

也许您正在寻找记录?

(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'列表中的位置三”之类的东西。

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

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

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

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

于 2011-10-07T07:31:37.650 回答
6

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

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

他们依赖assert,因此可以被禁用。(doc assert)更多细节。

于 2011-10-07T12:25:00.357 回答