重构一个糟糕的程序是非常困难的:
- 它们往往没有任何自动化测试,让重构的保护伞完全无效,你必须在随时失足摔得粉身碎骨的危险下开始工作。
- 这些程序往往随意使用外界的任何服务,让你很难猜测到它到底在做什么。
我这次的重构工作就面临这样的麻烦。在动手之前,我知道必须非常谨慎。重构工作中常常遇到的诱惑是可以改进的地方太多,我经常会抵抗不住这些诱惑顺手改动几个,可是这是有风险的,我很可能会在哪里犯下一点小错误,但在没有保护的情况下代价巨大。所以我知道我必须对一些小的毛病视而不见,甚至准备好用这程序当前的风格来写上一点糟糕代码。
我的第一目标在于给程序写出真正的自动化测试。
(defn process-order
[request]
(let [time (System/currentTimeMillis)
{:keys [mobileId]} request
_ (log/info :order-request-info request)
[card-id user-id] (order/get-cardtype-and-userid (request :goodsInf))
available-mobile? (service/is-available-mobile-phone user-id mobileId)
card-info (get-card-info card-id)
over-month-coin-limit?
(< (- config/MONTH_MAX_COIN
(or (:coinValue
(service/coin-value-limit-of-user-id user-id) 0)))
(or (:coin-value card-info) 0))
{:keys [coin-value cash-value]} card-info
user-info (try
(service/user-info-by-id user-id)
;;捕捉找不到用户的错误
(catch Exception e
(do (log/warn :wrong-user-id user-id :request request)
nil)))
handle-order-result (order/do-order request available-mobile?
over-month-coin-limit?
card-info user-info
(get @user-pay-records user-id {})
@mobile-ten-yuan-pay-times time)
order-id (if-let [order (:order handle-order-result)]
(let [{:keys [mobile username user_id payid code]} order]
(insert-new-order! mobile username
(str user_id) payid (str time) code)))
text (order/order-result-text request
(assoc-in handle-order-result
[:order :order_id] order-id)
time)
_ (log/info :order-result-info
(merge request (assoc-in handle-order-result
[:order :order_id]
order-id)))
_ (log/info :order-reps-text-for-send text)]
(if (nil? order-id)
(send-off last-limit-reason
(fn [a]
(update-limit-reason! mobileId
(:ret-msg handle-order-result)))))
(util/build-resp text)))
你知道这段程序在干什么?我能猜到一些,但在有安全网之前,我啥也不敢动。
我知道所有的 service/
都是在通过 RPC 在调用外部功能,这些基本是无法测试的。我的对策是将它们全部抽出来,放在一个单独的命名空间中,象这样:
(ns myapp.system
(:require [some.rpc]))
(defn is-available-mobile-phone...)
(defn coin-value...)
通过这个新的 system
命名空间,我将程序中对外的依赖转化成为了同名的内部依赖,不过我给这些函数都插入了第一个参数:system
。system
基本是面向对象思想中的一个对象。只有它才真正去联系 RPC。
通过这第一步,我将包含在胶水代码中隐含的连接 RPC 的代码剥离出了一个对象,它将成为程序可测试的第一块基石。而这一步我进行得非常有纪律:函数同名,不过是增加了一个一致的参数罢了。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。