正如食物腐爛以前,可能會發出異味。當代碼存在隱藏問題時,代碼也會表現出一些異狀,咱們稱之爲代碼異味(code smell),它存在於總體結構和代碼設計階段,暗示代碼塊或通用的編程模式中可能存在更深層次的問題。算法
代碼異味一般被認爲是暗示代碼段須要重構的標誌,但這並非說代碼有bug或者是無用的 。一般狀況下,存在代碼異味的代碼塊也可以運行的很好,可是通常難以維護和擴展,這就會致使一些技術問題,特別是在大型項目中,這種現象更加明顯。編程
那麼常見的代碼異味有哪些呢?又要如何解決呢?下面咱們將重點介紹10種最多見的代碼異味以及如何對它們進行「除臭」。函數
1.緊耦合工具
存在的問題測試
緊耦合是指兩個對象相互依賴於彼此的數據或函數,若是修改其中一個對象,那麼另外一個對象也須要修改。當兩個對象過於緊耦合時,修改代碼可能會是一場噩夢,同時更有可能在每次修改時引入bug。編碼
例如:設計
在這種狀況下,Worker類和Bike類緊密耦合。若是有一天你想開車去上班而不是騎自行車?你必須進入Worker類,將全部與Bike類相關的代碼替換爲與Car類相關的代碼。這就會變得很混亂,很容易出錯。3d
解決方法調試
你能夠經過添加一個抽象層來下降耦合程度。在這種狀況下,Worker類不只能夠騎自行車,還能夠開車,還能夠開卡車,甚至還能夠騎摩托車。這些都屬於交通工具,不是嗎? 因此能夠建立一個交通工具接口,根據你的需求,容許插入和修改不一樣類型的交通工具。code
例如:
2.上帝對象
存在的問題
上帝對象是指包含太多變量和函數的大型類或模塊。「知道得太多」和「作得太多」都會形成一些問題,緣由有如下兩點。首先,其餘類或模塊會變得過度依賴於數據(緊密耦合)。其次,因爲全部代碼都擠在同一個地方,使得總體結構雜亂無章。
解決方案
取一個上帝對象,而後根據它存在的問題來分離它的數據和函數,再將這些分組轉換成對象。相較於上帝對象,分解爲許多小對象可能會更好。
例如,假設你有一個巨大的User類:
您能夠將其轉換爲如下內容的組合:
這樣在下次須要修改登陸過程時,就沒必要經過巨大的User類,而是經過易於管理的Credentials類!
3.長函數
存在的問題
顧名思義,長函數是指函數太長了。雖然沒有一個特定的數字表示多少行代碼對於一個函數來講「太長」,但當你看到這個函數時,你就會知道它是否是太長。這幾乎是上帝對象問題的一個更嚴重的版本,一個長函數包含了太多的功能實現。
解決方案
長函數應該被分解成許多子函數,其中每一個子函數被設計爲處理單個任務或問題。理想狀況下,原始的長函數將變成一個子函數調用列表,從而使代碼更清晰,更易於閱讀。
4.參數過多
存在的問題
函數或類的構造函數擁有太多的參數會形成一些問題,緣由有如下兩點。首先,這會使得代碼不易閱讀,測試也更加困難。其次,更重要的是,這意味着該函數的功能太模糊,承擔着太多功能的實現。
解決方案
儘管「過多」對於參數列表而言是主觀的,但咱們建議對任何超過3個參數的函數保持關注並儘可能避免。固然,有時候一個函數有5個甚至6個參數也是容許的,前提是有合理的理由。
大多數狀況下,不存在一種方法能更好地將該函數分解爲兩個或更多不一樣的函數。與「長函數」不一樣的是,這個問題不能僅僅經過用子函數替換代碼來解決 ,由於是函數自己須要分解爲單獨的子函數,而每一個子函數都須要包含各自的功能。
5.命名模糊的標識符
存在的問題
一個或兩個字母的變量名、無明顯意義的函數名稱、過度修飾的類名、使用變量類型標記的變量名稱(例如,b_isCounted表示布爾變量),最糟糕的是,在一個代碼中混合使用不一樣的命名規則,全部這些都將致使代碼難以閱讀,難以理解和難以維護。
解決方案
爲變量,函數和類命名是一個難學的技能。若是你正在參與一個已有的項目,請仔細觀察現有的標識符命名方式。若是存在命名風格指南,那麼請記住它並時刻遵照它。若是是新項目,就能夠考慮造成本身的命名風格而且堅持下去。
通常而言,變量名稱應該簡短但具備描述性。函數名一般應該至少有一個動詞,而且函數名稱應該表現出該函數的功能,可是不要使用太多的單詞,類名也是如此。
6.幻數
存在的問題
當你正在瀏覽一些其餘人寫的代碼,這時你發現了一些硬編碼的數字。它們也許是if語句的一部分,或者是一些難以理解的計算的一部分,看起來沒什麼意義,而你須要修改該模塊,但卻沒法理解這些數字的含義,這會使你很是苦惱。
解決方案
在編程時,應該不惜一切代價避免這些所謂的「幻數」。硬編碼數字在寫的時侯是有意義的,可是它們很快就會失去全部含義 ,特別是當其餘人試圖維護你的代碼時。
其中一種解決方法是留下數字的註釋,但更好的選擇是將幻數轉換爲常量變量(用於計算)或枚舉(用於if語句和switch語句)。經過給幻數起一個名字,代碼可讀性一目瞭然,同時也不太容易出現錯誤。
7.深度嵌套
存在的問題
有兩種主要的語句可能形成深度嵌套代碼:循環和條件語句。深度嵌套的代碼並不老是很糟糕,但可能會產生問題,由於它很難理解(特別是變量沒有被很好地命名的狀況下),甚至更加難以修改。
解決方案
若是你發現本身正在編寫一個雙重,三重甚至四重for循環,那麼代碼將可能試圖在超出自身的範圍外查找數據。因此你應該提供一種方法,使之能夠經過包含該數據的對象或模塊函數調用來請求數據。
另外一方面,深層嵌套的if語句一般代表你試圖在單個函數或類中處理過多的邏輯代碼塊。事實上,深層嵌套和長函數每每是同時出現的。若是你的代碼有大量的switch語句或嵌套的if-then-else語句,你可能須要實現一個狀態機或策略模式。
8.未處理異常
存在的問題
異常的功能是很是強大的,但卻容易被濫用。不正確地使用throw-catch語句可能會致使調試難度大幅增加。例如,忽略或掩蓋捕獲的異常。
解決方案
不要忽略或掩蓋捕獲的異常,而是要打印出異常的及其調用信息,這樣調試人員才能夠發現錯誤。若是你的程序悄無聲息地運行失敗,那麼未來你可能就要頭痛不已了!此外,我更傾向於輸出特殊的異常信息而非全部異常。
9.重複的代碼
存在的問題
你在程序多個無關部分執行相同的邏輯代碼塊,而後發現須要修改該邏輯代碼塊,可是卻不記得全部執行該代碼塊的地方,假設最終你只修改了5個位置,而實際上有8個位置的代碼塊須要進行更改,這就會致使結果出現錯誤。
解決方案
解決重複代碼問題的首要選擇是轉化爲函數。假設你正在開發一個聊天應用程序,你是這樣編寫的:
在代碼中的其餘地方,你發現你須要執行一個相同的「這個用戶在線嗎?」檢查。這時不要複製粘貼代碼塊,而是把它放到一個函數中:
這樣在代碼的任何地方,你均可以使用isUserOnline()函數進行檢查。若是你須要修改此邏輯代碼塊,就只須要修改該方法,再將其應用於全部調用該方法的地方就能夠了。
10.缺少註釋
存在的問題
代碼在任何地方都沒有註釋。沒有函數的功能註釋,沒有類的使用概述,沒有對算法的解釋等等。有人可能會說,寫得好的代碼不須要註釋,但事實上,即便是寫的最好的代碼也不如註釋更容易被理解。
解決方案
易於維護的代碼塊應該是代碼寫得足夠好以致於不須要註釋,但它仍然有註釋。在寫註釋的時候,要記住你的目的是爲解釋代碼塊爲何存在,而不是解釋代碼塊在作什麼。註釋能幫助你更好的理解本身和他人的代碼,減小工做量,因此不要忽視他們。
如何編寫風格良好的代碼
顯而易見,大多數不規範的代碼都是因爲對良好的編程原則和代碼風格的忽視。假如你可以嚴格遵照DRY原則(Don't repeat yourself)就能消除大部分的重複代碼,而掌握單一職責原則就能夠避免創造巨大的上帝對象。
千萬不要輕視這個問題,若是連你都沒法一目瞭然地看懂本身的代碼,更況且其餘人呢?