重构一个糟糕的程序是非常困难的:

  • 它们往往没有任何自动化测试,让重构的保护伞完全无效,你必须在随时失足摔得粉身碎骨的危险下开始工作。
  • 这些程序往往随意使用外界的任何服务,让你很难猜测到它到底在做什么。

我这次的重构工作就面临这样的麻烦。在动手之前,我知道必须非常谨慎。重构工作中常常遇到的诱惑是可以改进的地方太多,我经常会抵抗不住这些诱惑顺手改动几个,可是这是有风险的,我很可能会在哪里犯下一点小错误,但在没有保护的情况下代价巨大。所以我知道我必须对一些小的毛病视而不见,甚至准备好用这程序当前的风格来写上一点糟糕代码。

我的第一目标在于给程序写出真正的自动化测试。

(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 的代码剥离出了一个对象,它将成为程序可测试的第一块基石。而这一步我进行得非常有纪律:函数同名,不过是增加了一个一致的参数罢了。


robertluo
738 声望21 粉丝

引用和评论

0 条评论