“推敲”这个词来源于唐朝诗人贾岛的故事,说他为诗中的“推”“敲”一字之差,反复思索。
Clojure 程序的基础是函数,函数和使用者唯一的合同是它的参数表,我们 Clojure 对待它也应该有推敲的精神!
不应该有不被引用的参数
这看起来是显然的,但我们经常就在反复改动一个函数实现的时候,忘记检查它有没有没有用的参数。
参数要求越简单精确越好
不要为了一个值引入整个结构。例如:
(defn hello [user]
(str "Hello" (:name user)))
这个函数既然只使用 name
,就不如改成这样更精确:
(defn hello [name]
(str "Hello" name))
这个原则的例外在于,有时为了连续调用,我们需要一些参数的统一性。这时可以使用 destructure 这个强大的语言特性来明示我们的需求:
(defn hello [{name :name}]
(str "Hello" name))
这样的调用形式既可以满足要求,也更为精确地说明了我们需要的不是一个特别的 user 的 map,而仅仅是一个有 :name
值的 map。
尽可能避免“过路参数”
所谓“过路参数”:函数对参数值不感兴趣,把他放进参数表仅仅为了调用其他函数。这样的参数在程序中很常见,例如上例中的函数实际就仅仅是 str 函数的包装。即使 name 参数都是过路参数,其实,第二个函数定义写成这样:
(def hello (partial str "Hello"))
根本不引入一个额外的参数表更加精确。而最后一种形式写成:
(def hello (comp (partial str "Hello") :name))
用函数组合来代替新建函数更为合适。
当然,这条规则实际上用在 hello
这么简单的函数上缺乏说明性,我们用任何一种写法都很容易理解。可是在实际的工作中,随意引入过路参数却会严重影响我们的大设计。
(defn order-description [{:keys [amount]} username]
(let [client-type (if (> amount 10000) "big" "small"]
(str (call-name username) " is a " client-type " client.")))
(order-description {:amount 15000} "Robert")
这个函数中的 username
又是一个过路参数,它在参数表中出现,将我们函数实际上仅仅关心 call-name
的结果的逻辑掩盖了起来,并且还造成了我们对 call-name 这个函数的依赖。改成这样:
(defn order-description [{:keys [amount]} call-name]
(let [client-type (if (> amount 10000) "big" "small"]
(str call-name " is a " client-type " client.")))
(order-description {:amount 15000} (call-name "Robert"))
就将 call-name
的调用责任放在了它的调用者,让 order-description
函数的使用范围更广和更加稳定。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。