Python 工匠:一個關(guān)于模塊的小故事
前言

模塊(Module)是我們用來組織 Python 代碼的基本單位。很多功能強(qiáng)大的復(fù)雜站點(diǎn),都由成百上千個獨(dú)立模塊共同組成。
雖然模塊有著不可替代的用處,但它有時也會給我們帶來麻煩。比如,當(dāng)你接手一個新項(xiàng)目后,剛展開項(xiàng)目目錄。第一眼就看到了攀枝錯節(jié)、難以理解的模塊結(jié)構(gòu),那你肯定會想:“這項(xiàng)目也太難搞了?!?/p>
在這篇文章里,我準(zhǔn)備了一個和模塊有關(guān)的小故事與你分享。
一個關(guān)于模塊的小故事
小 R 是一個剛從學(xué)校畢業(yè)的計(jì)算機(jī)專業(yè)學(xué)生。半個月前,他面試進(jìn)了一家互聯(lián)網(wǎng)公司做 Python 開發(fā),負(fù)責(zé)一個與用戶活動積分有關(guān)的小項(xiàng)目。項(xiàng)目的主要功能是查詢站點(diǎn)活躍用戶,并為他們發(fā)送有關(guān)活動積分的通知:“親愛的用戶,您好,您當(dāng)前的活動積分為 x”。
項(xiàng)目主要由 notify_users.py 腳本和 fancy_site 包組成,結(jié)構(gòu)與各文件內(nèi)容如下:

文件 notify_users.py:

文件 fancy_site/users.py:

文件:fancy_site/marketing.py:

只要在項(xiàng)目目錄下執(zhí)行 python notify_user.py,就能實(shí)現(xiàn)給所有活躍用戶發(fā)送通知。
需求變更
但有一天,產(chǎn)品經(jīng)理找過來說,光給用戶發(fā)站內(nèi)信通知還不夠,容易被用戶忽略。除了站內(nèi)信以外,我們還需要同時給用戶推送一條短信通知。
琢磨了五秒鐘后,小 R 跟產(chǎn)品經(jīng)理說:“這個需求可以做!”。畢竟給手機(jī)號發(fā)送短信的 send_sms() 函數(shù)早就已經(jīng)有人寫好了。他只要先給 add_notification 方法添加一個可選參數(shù) enable_sms=False,當(dāng)傳值為 True 時調(diào)用 fancy_site.marketing 模塊里的 send_sms 函數(shù)就行。
一切聽上去根本沒有什么難度可言,十分鐘后,小 R 就把 user.py 改成了下面這樣:

但是,當(dāng)他修改完代碼,再次執(zhí)行 notify_users.py 腳本時,程序卻報(bào)錯了:

錯誤信息說,無法從 fancy_site.users 模塊導(dǎo)入 User 對象。
解決環(huán)形依賴問題
小 R 仔細(xì)分析了一下錯誤,發(fā)現(xiàn)錯誤是因?yàn)?users 與 marketing 模塊之間產(chǎn)生的環(huán)形依賴關(guān)系導(dǎo)致的。
當(dāng)程序在 notify_users.py 文件導(dǎo)入 fancy_site.users 模塊時, users 模塊發(fā)現(xiàn)自己需要從 marketing 模塊那里導(dǎo)入 send_sms 函數(shù)。而解釋器在加載 marketing 模塊的過程中,又反過來發(fā)現(xiàn)自己需要依賴 users 模塊里面的 User 對象。
如此一來,整個模塊依賴關(guān)系成為了環(huán)狀,程序自然也就沒法執(zhí)行下去了。

不過,沒有什么問題能夠難倒一個可以正常訪問 Google 的程序員。小 R 隨便上網(wǎng)一搜,發(fā)現(xiàn)這樣的問題很好解決。因?yàn)?Python 的 import 語句非常靈活,他只需要 把在 users 模塊內(nèi)導(dǎo)入 send_sms 函數(shù)的語句挪到 add_notification 方法內(nèi),延緩 import 語句的執(zhí)行就行啦。

改動一行代碼后,大功告成。小 R 簡單測試后,發(fā)現(xiàn)一切正常,然后把代碼推送了上去。不過小 R 還沒來得及為自己點(diǎn)個贊,意料之外的事情發(fā)生了。
這段明明幾乎完美的代碼改動在 Code Review 的時候被審計(jì)人小 C 拒絕了。
小 C 的疑問
小 R 的同事小 C 是一名有著多年經(jīng)驗(yàn)的 Python 程序員,他對小 R 說:“使用延遲 import,雖然可以馬上解決包導(dǎo)入問題。但這個小問題背后隱藏了更多的信息。比如,你有沒有想過 send_sms 函數(shù),是不是已經(jīng)不適合放在 marketing 模塊里了?”
被小 C 這么一問,聰明的小 R 馬上意識到了問題所在。要在 users 模塊內(nèi)發(fā)送短信,重點(diǎn)不在于用延遲導(dǎo)入解決環(huán)形依賴。而是要以此為契機(jī),發(fā)現(xiàn)當(dāng)前模塊間依賴關(guān)系的不合理,拆分/合并模塊,創(chuàng)建新的分層與抽象,最終消除環(huán)形依賴。
認(rèn)識清楚問題后,他很快提交了新的代碼修改。在新代碼中,他創(chuàng)建了一個專門負(fù)責(zé)通知與消息類的工具模塊 msg_utils,然后把 send_sms 函數(shù)挪到了里面。之后 users 模塊內(nèi)就可以毫無困難的從 msg_utils 模塊中導(dǎo)入 send_sms 函數(shù)了

新的模塊依賴關(guān)系如下圖所示:

在新的模塊結(jié)構(gòu)中,整個項(xiàng)目被整齊的分為三層,模塊間的依賴關(guān)系也變得只有單向流動。之前在函數(shù)內(nèi)部 import 的“延遲導(dǎo)入”技巧,自然也就沒有用武之地了。
小 R 修改后的代碼獲得了大家的認(rèn)可,很快就被合并到了主分支。故事暫告一段落,那么這個故事告訴了我們什么道理呢?
總結(jié)
模塊間的循環(huán)依賴是一個在大型 Python 項(xiàng)目中很常見的問題,越復(fù)雜的項(xiàng)目越容易碰到這個問題。當(dāng)我們在參與這些項(xiàng)目時,如果對模塊結(jié)構(gòu)、分層、抽象缺少應(yīng)有的重視。那么項(xiàng)目很容易就會慢慢變得復(fù)雜無比、難以維護(hù)。
所以,合理的模塊結(jié)構(gòu)與分層非常重要。它可以大大降低開發(fā)人員的心智負(fù)擔(dān)和項(xiàng)目維護(hù)成本。這也是我為什么要和你分享這個簡單故事的原因?!霸诤瘮?shù)內(nèi)延遲 import” 的做法當(dāng)然沒有錯,但我們更應(yīng)該關(guān)注的是:整個項(xiàng)目內(nèi)的模塊依賴關(guān)系與分層是否合理。
最后,讓我們再嘗試從 小 R 的故事里強(qiáng)行總結(jié)出幾個道理吧:
合理的模塊結(jié)構(gòu)與分層可以降低項(xiàng)目的開發(fā)維護(hù)成本
合理的模塊結(jié)構(gòu)不是一成不變的,應(yīng)該隨著項(xiàng)目發(fā)展調(diào)整
遇到問題時,不要選“簡單但有缺陷”的那個方案,要選“麻煩但正確”的那個
整個項(xiàng)目內(nèi)的模塊間依賴關(guān)系流向,應(yīng)該是單向的,不能有環(huán)形依賴存在
附錄
題圖來源: Photo by Ricardo Gomez Angel on Unsplash
更多系列文章地址:https://github.com/piglei/one-python-craftsman
系列其他文章:
Python 工匠:讓函數(shù)返回結(jié)果的技巧:
http://mp.weixin.qq.com/s?__biz=Mzg2NjExNDI0MQ==&mid=2247483658&idx=1&sn=ac7222c597c2c063fab225af818a84af&chksm=ce4e8ea4f93907b2267ea873fd57b4a1c9cd640ee03d84f88672e0749472f91bbceb712083ce&scene=21#wechat_redirect
Python 工匠:異常處理的三個好習(xí)慣:
http://mp.weixin.qq.com/s?__biz=Mzg2NjExNDI0MQ==&mid=2247483664&idx=1&sn=0897d215ab2fc80399397d6f982f9f04&chksm=ce4e8ebef93907a80c31b79acc3d73e5a741ccd412ba6e6741cf0f5ed9e342b5878acbb754d3&scene=21#wechat_redirect
Python 工匠:編寫地道循環(huán)的兩個建議:
http://mp.weixin.qq.com/s?__biz=Mzg2NjExNDI0MQ==&mid=2247483669&idx=1&sn=f1fde152bfc7a8a606967b18824f42dc&chksm=ce4e8ebbf93907adabff31678bbf92d3a47c4708cd6b1a232dc74064005a6118b775867fc008&scene=21#wechat_redirect
藍(lán)鯨智云
本文由騰訊藍(lán)鯨智云編輯發(fā)布,騰訊藍(lán)鯨智云(簡稱藍(lán)鯨)軟件體系是一套基于PaaS的技術(shù)解決方案,致力于打造行業(yè)領(lǐng)先的一站式自動化運(yùn)維平臺。目前已經(jīng)推出社區(qū)版、企業(yè)版,歡迎體驗(yàn)。
官網(wǎng):https://bk.tencent.com/
下載鏈接:https://bk.tencent.com/download/
社區(qū):https://bk.tencent.com/s-mart/community/question