不管我们的程序写了多少漂亮的模块,最终它总是要集成的,将不同来源的输入,如用户输入、网络服务的结果、数据库保存的数据,经过各种各样的业务处理,输出成观众喜闻乐见的输出格式。
这个过程中,往往充满了没有特别技术原因的决定:例如输入的格式可能需要兼容历史数据、可能需要格式转换,输出的格式往往是为了当前的方便等。仅仅从上面的表述我们就可以知道,这个集成的过程往往是低内聚性的、逻辑关系不明确的操作的组合。
处于某种原因,很多程序员喜欢将集成的复杂度隐藏在简洁的外壳下面,如下面这个工具程序的 -main
函数:
clojure
(defn -main [] (let [cfg (conver-param (util/load-edn-config "config.edn")) msg (validate cfg)] (if msg (exit -1 msg) (time (search-users-data (create-system cfg))))))
看起来很美。但它的输入是什么?我们从上面代码中难以判断,输出又是什么?也无从看起。实际上,它的输入和输出的集成在 search-users-data
这个函数中:
clojure
(defn search-users-data "查所有用户数据,并输出到excel文件" [system] (let [{:keys [user-names-file stat-time retain-days retain-def newer-time] :as cfg} system usernames (util/load-excel-users user-names-file)] (out-excel (core/stat usernames stat-time retain-days retain-def newer-time cfg) cfg)))
在这个函数中,我们终于可以看到 load-excel-users
这个输入函数了,我们也能够注意到这个函数的名字已经包含了实现的细节:excel,显然,如果变化输入格式,本函数将需要改写。我们也能看到输出函数 out-excel
,它当然也有同样的问题。
实际上,这种有意将集成延迟在第二、第三层的做法,不过是引入了不必要的包装,search-users-data
函数本身并没有重用的价值,它反而增加了维护者、读者的思考负担。该程序的问题还没有到此为止,我们注意到,在 search-users-data
函数中有大批的过路参数(请见我的另一篇博客),让函数的耦合负担进一步加重。
更好的做法是一层集成,将输入、处理和输出能更明确地表达出来:
clojure
(defn -main [user-names-file output-filename] (let [cfg (convert-param (util/load-edn-config "config.edn")) error-msg (validate cfg)] (if error-msg (exit -1 error-msg) (let [usernames (load-excel-users user-names-file) ;从文件中读取所有用户名 conn (esr/connect (:elk-url cfg)) cfg (assoc cfg :conn conn) ;连接查询服务器 data (user-data usernames cfg) ;查询出所有数据 ] (to-excel-file! data (date-out-file output-filename (:stat-time cfg))) ;输出到 excel 文件 ))))
偶然耦合的关系到此为止,具体的输入处理函数如 load-excel-users
,具体的输出函数如 to-excel-files
和具体的处理函数 user-data
之间如何作用,一目了然,在恰当的注释帮助下读者非常容易定位。而中间的集成函数则可以简单删掉。
对于 web 服务器这样的服务器程序,我们的集成点则不象上述例子中只有一个-main
函数,而很可能是多个:每种请求都可能是一个出发点,但集成的道理是一致的:
应减少集成的中间层次。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。