代碼問題及對策

代碼問題及對策前端

路線圖

CR代碼問題

常見代碼問題

常見的潛在代碼問題是當前直接會致使BUG、故障或者產品功能不能正常工做的類別。

java

空值

空值恐怕是最容易出現的地方之一。 常見錯誤有: a. 值爲NULL致使空指針異常; b. 參數字符串含有前導或後綴空格沒有Trim致使查詢爲空。 致使以上結果的緣由主要有: 無此記錄、有此記錄但因爲SQL訪問異常而沒查到、網絡調用失敗、記錄中有髒數據、參數沒傳。git

原則上,對於任何異常, 但願可以打印出具體的錯誤信息,根據錯誤信息很快明白是什麼緣由, 而不是一個 null ,還要在代碼裏去推敲爲何爲空。這樣咱們必須識別出程序中可能的null, 並及時檢測、捕獲和拋出異常。算法

對於空值,最好的防禦是「防護式編程」。當獲取到對象以後, 使用以前老是判斷是否爲空,並適當拋出異常、打錯誤日誌或作其它處理。 有的人嫌檢測爲空的 if 語句充斥在代碼裏會破壞代碼的可維護性, 對此個人建議是:sql

  • 空值檢測必定要有, 有勝於無。
  • 在空值檢測老是存在的前提下, 能夠優化空值檢測的方法和存在形式。 好比集中於一個類 NullChecker 中管理,並與系統的總體錯誤處理設計保持一致。集中管理和處理一致性原則能夠做爲系統設計的一個準則。 這樣主流程中只要增長一行調用便可, 既能夠天網恢恢疏而不漏地檢測對象爲空, 也不會讓代碼顯得難看。
class NullChecker {
       public static void checkNull(Object obj, Error error) {
               if (obj == null)  { throw new BizException(error); }
       }
}
  • 在參數入口處統一作 trim。 若是在業務邏輯裏作 trim , 就會致使有的業務邏輯作了 trim , 有的沒作, 體如今產品上就會有令用戶困惑的事情發生。 好比搜索和導出業務, 搜索能搜索出來, 導出卻沒有。

未捕獲潛在的異常

第二個容易出錯的地方是未捕獲潛在的異常。調用API接口、庫函數或系統服務等,只顧着享受便利卻不作防禦,常致使由於局部失敗而影響總體的功能。最好的防禦依然是「防護式編程」。 要麼在當前方法捕獲異常並返回合適的空值或空對象,要麼拋給高層處理。數據庫

切不可默默"吞掉錯誤和異常"。 若是這樣作了, 出問題了等着加班和耗費大量腦細胞吧!
在CodeReview的時候必定要仔細詢問:這裏是否可能會拋出異常?若是拋異常會怎麼處理?是否會影響總體服務和返回結果?

編程

低性能

低性能會致使產品功能很差用、不可用,甚至致使產品失敗。api

常見狀況有:a. 循環地逐個調用單個接口獲取數據或訪問數據庫; b. 重複建立幾乎徹底相同的(開銷大的)對象;c. 數據庫訪問、網絡調用等服務未處理超時的狀況; d. 多重循環對於大數據量處理的算法性能低;e. 大量字符串拼接時使用了String而非StringBuilder.數組

對於 a,最好提供批量接口或批量併發獲取數據; 對於 b, 將可複用對象抽離出循環,一次建立屢次使用; 對於 c,設置合理的超時時間並捕獲超時異常處理; 對於 d,使用預排序或預處理, 構造合適的數據結構, 使得算法平均性能在 O(n) 或 O(nlogn) ; 對於 e, 記住: 少許字符串拼接使用String, 大量字符串拼接使用 StringBuilder, 一般不會使用到 StringBuffer.

緩存

影響範圍過大

對多個模塊依賴的公共函數的修改,容易形成影響範圍超過當前業務改動,無心識地破壞依賴於該公共函數的其餘業務。要特別慎重。可靠的方式是:先查看該公共函數的調用, 若是隻有本身的業務用,可適當大膽一些; 若是有多個地方依賴,抽離一個新的函數,抽離原函數裏的可複用部分,而後基於可複用部分構建新的函數。修改原則遵循「開閉」原則,才能儘量使改動影響下降到最小化。

基類及實例字段和方法也屬於公共函數的範疇。 儘可能不要修改基類的東西。

單測問題

單測是保證工程質量的第一道重要防線。單測問題通常包括: a. 單測未所有經過; b. 重要業務邏輯缺少單測; c. 缺少異常單測; d. 代碼變動或BUG修復缺少單測。

單測所有經過應當是提交代碼到代碼庫以及代碼Review的前提條件。代碼提交者應當保證單測所有經過。沒有捷徑可走。僅當單測所有經過才提交到代碼庫, 能夠經過工具自動化實現。 對於 maven 管理的工程, 只需一個命令: mvn test && git push origin branch_name 。 單測應當更注重質,而非單純追求覆蓋率。

缺少單測的重要業務邏輯就像裸露在空氣中的電線同樣,雖然能跑起來,倒是很容易「觸電」的。 方法: 增長覆蓋比較全面的單測。

缺少異常單測也是代碼提交者常忽略的問題。 異常也是一種實際的業務場景,反映系統的健壯性和友好性。異常應該有相應的單元測試覆蓋。建立條件使之拋出異常,並判斷異常是不是指定異常;若沒有拋出異常或者不是指定異常,則應該 AssertFailed 而不是經過。

對於代碼變動和BUG修復,若是當時因爲時間緊而沒有寫,後續應當補上。對於每一個代碼變動和BUG,均可以抽離出相應的代碼部分, 並有相應單測覆蓋,並註明緣由。

與原有業務邏輯不兼容

改動針對當前需求是合理的,卻與原有業務邏輯不兼容,也是常見的問題。好比增長一個搜索條件, 卻不能與原有條件聯合查詢。

與原有業務不兼容, 通常出如今:

  1. 一對一與一對多的變化。 好比原來的關係是一個訂單對應一個物流信息, 後來變化爲一個訂單可能對應多個物流信息; 原來的邏輯是一個訂單顯示多個物流信息能夠更改,後來要求一個訂單隻展現最近一次的物流信息能夠修改。
  2. 多個業務組合。 業務 A 與業務 B 原來是分開發展的, 後來開展一種活動,將業務A與業務B進行一種組合營銷。 此時,多半會出現不少 if-else 語句。

業務邏輯的兼容問題通常體如今系統的複用性和可擴展機制上。良好的系統可複用性和可擴展性能夠更容易地作到業務邏輯兼容。 主要有以下幾種級別:

  1. 自動兼容。 增長一種類型, 只是 biz_type 的值多了一種, 系統自動將已有功能適配給新的 biz_type;
  2. 一點改動。增長一個分支語句, 對 biz_type 的某個特性進行擴展;
  3. 一些改動。 須要見縫插針地增長一個單獨的分支判斷和邏輯處理模塊, 對總體可擴展性沒有影響, 但會形成局部的複雜化;
  4. 一部分功能改動。 只須要對其中一個功能模塊作個擴展;
  5. 多處改動。 須要對多個功能模塊作相應的改造,不過更可能是新增而不是修改;
  6. 難以改動。 須要深刻到功能模塊內部作艱難的修改, 並要保證原有功能不受影響。

如何應對呢?

  1. 針對關聯關係, 在項目之初, 能夠詢問清楚: 未來在產品上是否有可擴展的變化? 及早預留空間, 或者肯定產品上的對策; 在代碼實現上, 兼顧考慮一對一到一對多,或一對多到一對一的關聯變化。好比使用列表來表達單個信息, 使用索引從列表中獲取單個信息。
  2. 針對業務組合, 明確各業務的核心部分, 抽離出業務的可複用的部分,造成 API ; 考慮組合模式和裝飾器模式來進行擴展。

核心不變, 外圍定製化。

缺少必要日誌

對於重要而關鍵的實例狀態、代碼路徑及API調用,應當添加適當的INFO日誌;對於異常,應當捕獲並添加Error日誌。缺少日誌並不會影響業務功能,但出現問題排查時,就會很是不方便,甚至錯失極寶貴的機會(不易重現的狀況尤爲如此)。此外,缺少日誌也會致使可控性差,難以作數據統計和分析。

錯誤碼不符合規範

錯誤碼自己不算是代碼問題,不過基於整個組織和工程的可維護性來講,能夠將錯誤碼不符合規範做爲一種錯誤加以免。方法: 對錯誤碼進行可控的管理和遵循規範使用。可使用公共文檔維護, 也能夠開發錯誤碼管理系統來避免相同的錯誤碼。

參數檢測缺少或不足

參數檢測是對業務處理的第一層重要過濾。若是參數檢測不足夠,就會致使髒數據進入服務處理,輕則致使異常,重則插入髒數據到數據庫,對後續維護都會形成不少維護成本。方法: 採用「契約式編程」,規定前置條件,並使用單測進行覆蓋。

對於複雜的業務應用, 優雅的參數檢測處理尤其重要。 根據 「集中管理和處理一致性原則」, 能夠創建一個 paramchecker 包, 設計一個可複用的微框架來對應用中全部的參數進行統一集中化檢測。參數檢測主要包括: (1) 參數的值類型, 能夠根據不一樣值類型作基礎的檢測; (2) 參數的業務類型, 有基礎非業務參數, 基礎業務參數和具體業務參數。 不一樣的參數業務類型有不一樣的處理。 將參數值類型與參數業務類型結合起來, 結合一致性的異常捕獲處理, 就能夠實現一個可複用的參數檢測框架。參數檢測既能夠採用普通的分支語句,也能夠採用註解方式。採用註解方式更可讀,不過單測編寫更具技巧。

引用錯誤

對於動態語言, 因爲缺少強大的靜態代碼檢測,修改了類引用的地方尤爲要注意,極可能致使依賴的其餘業務出錯; 尤爲是修改重名引用時。有線上故障教訓。PHP工程中含有兩個 Format 類, 一個基礎的一個業務相關的, 被改動的類文件裏開始沒有指明引用,默認採用了基礎 Format 類的實現, 而後提交者在改動文件頭增長了對業務 Format 的引用, 致使依賴於基礎Format類的其餘業務不能正常工做。避免引用錯誤的方法: 當要在文件裏增長新的類引用時, 先在文件裏搜索是否有重名類的引用。若是有, 就要格外當心了。

名字衝突

引用錯誤其實是名字衝突的一種情形。名字衝突經常出如今自定義函數命名跟庫函數名字同樣的狀況下。此時,自定義函數的定義會覆蓋庫函數,致使在某一處正常,而其餘地方出問題。所以,在命名時要足夠有意識,避免和庫函數命名衝突。

細節錯誤

好比邏輯運算符誤寫、優先級錯誤、長整型截斷、溢出、數組越界、JSON解析出錯、函數參數傳遞出錯、API 版本不對、使用網上拷貝的未經測試的代碼、不成熟的算法、傳值與傳引用、相等性比較等。

對於數組越界錯誤, 一般要對空數組、針對數組大小的邊界值+1和-1寫單測來避免; 使用網上拷貝的代碼,誠然可節省時間,也必定要加工一下並用單測覆蓋; 傳值和傳引用可經過單測來避免錯誤; 對象的相等性比較切忌使用等號=。

多重條件

相似 if ((!A || !B) && C || (D && E)) 的多重條件要仔細推敲。方法: 最好拆分紅多個有含義變量。 isNotDelay = !A || !B ; isNormal = C ; isAllow = D && E ; cond = isNotDelay && isNormal || isAllow 。

文不符實

文不符實是一種可能致使線上故障的錯誤。好比一個 getXXX 的函數,結果裏面還作了 add, update 的操做。對問題排查、產品運維等都有很是大的殺傷力。所以命名必定要用實質內容相符,除非是故意搞破壞。

跨語言或跨系統交互

稍具規模的互聯網創業公司一般會採用多語言開發,好比PHP做爲前端,Java做爲後臺服務。當動態類型語言與靜態類型語言交互時,會有一些問題產生。好比PHP的對象一般是一個Map, 若是是空對象就會寫成 [], 然而 [] 會被 Java 解析成列表。這樣, 若是數據庫的值是經過 PHP 寫入,那麼這個值既有多是JSON對象字符串,也多是空數組字符串, Java 來解析就有點尷尬了。 一樣,當 Java 調用 PHP 接口時, 不規範的PHP接口既可能返回列表,也可能返回 true or false , Java 解析返回結果也會比較尷尬。 所以, 在跨語言交互的邊界處,要特別注意這些類型轉換的差別。

跨系統交互則主要是接口設計與約定的問題。同一個項目裏不一樣業務團隊之間的業務接口設計與約定, 不一樣企業裏開放接口的設計與約定, 要在最初深思熟慮,一旦開放,在後期不多有接口設計改動的空間。開放接口設計要符合小而美、正交的特性, 命名要貼切一致, 參數取值要指明約束,枚舉參數要給出列表, 結果返回要規範一致,能夠採用通用的 {"code":200, "msg": "success", "data": xxx} 。跨系統交互也要統一對術語和接口的理解的一致。

可維護性問題

可維護性問題是「在當前業務變動的範圍內一般不會致使BUG、故障,卻會在往後埋下地雷,引起BUG、故障、維護成本大幅增長」的類別。

硬編碼

硬編碼主要有三種狀況: a. 「魔數」; b. 寫死的配置; c. 臨時加的邏輯和文案。

「魔數」與重複代碼相似,當前或許不會引起問題,時間一長,爲了弄清楚其表明的含義,增長不少溝通維護成本,且分散在各處很容易致使修改的時候遺漏不一致。務必清清除。方法也比較簡單:定義含義明顯的枚舉或常量,表明這個魔數在代碼中發言。

「寫死的配置」不會影響業務功能, 不過在環境變動或系統調優的時候,就顯得很不方便了。 方法: 儘可能將配置抽離出來作成配置項放到配置文件裏。

「臨時加的邏輯和文案」也是一種破壞系統可維護性的作法。方法: 抽離出來放在單獨的函數或方法裏,並特別加以註釋。

重複代碼

重複代碼在當前可能不會形成 BUG,但上線後,須要維護多處的事實一致性;時間一長,後續修改的時候就特別容易遺漏或處理不一致致使 BUG;重複代碼是公認的「代碼壞味」,必當盡力清除。方法: 抽離通用的部分,定製差別。重複代碼還有一種狀況出現,即創造新函數時,先看看是否既有方法已經實現過。

通用邏輯與定製業務邏輯耦合

這大概是每一個媛猿們在開發生涯中遇到的最噁心的事情之一了。通用邏輯與具體的各類業務邏輯混雜交錯,想插根針都難。遇到這種狀況,只能先祈福,而後抽離一個新的函數,嚴格判斷相應條件知足後去調用它。

若是是新建立邏輯,可使用函數式編程或基於接口的編程,將通用處理流程抽離出來,而將具體業務邏輯以回調函數的形式傳入處理。

不要讓不一樣的業務共用相同的函數,而後在函數裏一堆 if-else plus switch , 而是每一個業務都有各自的函數, 並可複用相同的通用邏輯和流程處理; 或者各個業務能夠覆寫一樣命名的函數。

複用,而非混雜。

直接在原方法里加邏輯

有業務改動時,猿媛們圖方便傾向於直接在原方法里加判斷和邏輯。這樣作是很很差的習慣。一方面,增長了原方法的長度,破壞了其可維護性;另外一方面,有可能對原方法的既有邏輯形成破壞。 可靠的方式是: 新增一個函數,而後在原方法中調用並說明緣由。

多業務耦合

在業務邊界未仔細劃分清晰的狀況下出現,一個業務過多深刻和摻雜另外一個非相關業務的實現細節。在項目和系統設計之初,特別要注意先劃分業務邊界,定義好接口設計和服務依賴關係,再着手開發;不然,延遲到後期作這些工做,極可能會致使重複的工做量,含糊複雜的交互、增長後期系統維護和問題排查的許多成本。磨刀不誤砍柴工。劃分清晰的業務、服務、接口邊界就屬於磨刀的功夫。

代碼層次不合理

代碼改動邏輯是正確的,然而代碼的放置位置不符合當前架構設計約定,致使後續維護成本增長。

代碼層次不合理可能致使重複代碼。好比獲取操做人和操做記錄,若是寫在類 XController 裏, 那麼類 YController 就面臨尷尬局面: 若是寫在 YController , 就會致使重複代碼; 若是跨層去調用 XController 方法,又是很是不推薦的作法。所以, 獲取操做人和操做記錄,最好寫在 Service 層, Controller 層只負責參數傳入、檢測和結果轉譯、返回。

不用多餘的代碼

工程中經常會有一些不用的代碼。或者是一些暫時未用到的Util工具或庫函數,或者是因爲業務變動致使已經廢棄不用的代碼,或者是因爲一時寫出後來又重寫的代碼。儘可能清除掉不用多餘的代碼,對系統可維護性是一種很好的改善,同時也有利於CodeReview。

使用全局變量

使用全局變量並無「錯」,錯的是,一旦出現問題,排查和調試問題起來,真的會讓人「一晚上之間白了頭」,耗費數個小時是輕微懲罰。此外,全局變量還能「順手牽羊」地破壞函數的通用性,致使可維護性變差。務必消除全局變量的使用。固然,全局常量是能夠的。

缺少必要的註釋

對重要和關鍵點的代碼缺少必要的註釋,使用到的重要算法缺少必要的引用出處,對特別的處理缺少必要的說明。

原則上, 每一個方法至少要用一個簡短的單行註釋, 適宜地描述了方法的用途、業務邏輯、做者及日期。對於特殊甚至奇葩的需求的特別實現,要加一些註釋。 這樣後續維護時有個基礎。

更難發現的錯誤

更難發現的錯誤是指「複雜併發場景下的有必定技術難度的、須要豐富開發與設計經驗才能看出來的錯誤」。

併發

併發的問題更難檢測、復現和調試。常見的問題有:a. 在可能由多線程併發訪問的對象中含有共享變量卻沒有同步保護;b. 在代碼中手動建立缺少控制的線程或線程池;c. 併發訪問數據庫時沒有作任何同步措施;d. 多個線程對同一對象的互斥操做沒有同步保護。

對於 a, 在大部分Java應用中,一般由Spring框架來控制和建立請求和服務實例,所以,保證「Controller, Service 類中的實例變量只容許 Service, DAO 的單例,不容許業務變量實例」基本確保沒有併發不正確更新的問題;不過,包含緩存策略的對象要特別注意多線程併發訪問的問題,出於性能考量, 儘可能只對共享實例部分加鎖。

對於 b, 禁止在應用中手動建立線程或線程池,失控的線程池很容易致使應用崩潰(有線上應用崩潰的教訓)。

對於 c, 併發訪問數據庫時,要特別注意時序和狀態同步。若是時序控制不對,會致使狀態同步和更新出錯。

對於 d, 對同一對象的互斥操做須要加分佈式鎖同步。

使用線程池、併發庫、併發類、同步工具而不是線程對象、併發原語。在複雜併發場景下,還需注意多個同步對象上的鎖是否按合適的順序得到和釋放以免死鎖,相應的錯誤處理代碼是否合理。

資源泄露

  • 打開文件卻沒有關閉;
  • 鏈接池的鏈接未回收;
  • 重複建立的腳本引用沒有置空,沒法被回收;
  • 已使用完的集合元素始終被引用,沒法被回收;

事務

事務方面常出現的問題是:多個緊密關聯的業務操做和 SQL 語句沒有事務保證。 在資金業務操做或數據強一致性要求的業務操做中,要注意使用事務,保證數據更新的一致性和完整性。

SQL問題

SQL的正確性一般能夠經過 DAO 測試來保證。 SQL問題主要是指潛在的性能問題和安全問題。

要避免SQL性能問題, 在表設計的時候就要作好索引工做。在表數據量很是大的狀況下,SQL語句編寫要很是當心。查詢SQL須要添加必要索引,添加合適的查詢條件和查詢順序,加快查詢效率, 避免慢查; 儘可能避免使用 Join, 子查詢;避免SQL注入。

尤爲避免在 update 語句中使用 where-if ! 很容易致使全表更新和嚴重的數據丟失,形成嚴重的線上故障 !!!

SQL優秀書籍推薦: SQL語言藝術

安全問題

安全問題一貫是互聯網產品研發中極容易被忽視、而在爆發後又極引起熱議的議題。安全和隱私是用戶的心理紅線之一。應用、數據、資金的安全性應當僅次於產品功能的準確性和使用體驗。

好比:緩衝區溢出; 惡意代碼注入;權限賦予不當; 應用目錄泄露等。

安全問題的CodeReview可參見檢查點清單:信息安全 。主要是以下措施: a. 嚴格檢查和屏蔽非法輸入; b. 對含敏感信息的請求加密通訊; c. 業務處理後消除任何敏感私密信息的任何痕跡; d. 結果返回前在反序列化中清除敏感私密信息; e. 敏感私密信息在數據存儲設備中應當加密存儲; f. 應用有嚴格的角色、權限、操做、數據訪問分級和控制; g. 切忌暴露服務器的重要的安全性信息,防止服務器被攻擊影響正常服務運行。

設計問題

設計問題一般體如今: a. 是否有潛在的性能問題; b. 是否有安全問題; c. 業務變化時是否容易擴展; d. 是否有遺漏的點; e. 持續高負荷壓力下是否會崩潰。

較輕微的問題

較輕微問題是指「沒有技術難度、經過良好習慣便可避免的問題」。

較輕微問題通常不會形成負面影響的BUG或故障,不過創建一些好的習慣,主動使用代碼檢測工具,消除這些較輕微錯誤,也是一種修行。

命名不貼切

命名不貼切不會影響功能實現,卻會誤導理解或增長理解難度。

方法:先查查字典,找個通俗易懂並且比較貼近的名字。能夠參考 jdk 的命名、通用詞彙和行業詞彙; 做用域小的採用短命名,做用域大的採用長命名。取名字是一種重要技能,—— 多少父母爲此愁灰了頭!

聲明時未初始化

聲明時未初始化一般狀況下都不會是問題,由於後面會進行賦值。不過,若是賦值的過程當中出現異常,那麼可能會返回空值,從而致使空值異常。一般,變量聲明時賦予默認初始值是個好習慣。

風格與總體有不一致

工程一般求穩,一致性能更好地維護。在工程項目中,最好可以遵循工程約定的風格,在我的項目中能夠凸顯個性風格。Java編程通常要遵循《Java編程規範》,有追求的程序猿媛還會追求更高層次的,好比《Google Java 規範》等。

類型轉換錯誤

編程語言的類型系統是很是重要的。如何在不一樣類型之間可靠地互轉,尤爲是在父子類型之間相互賦值,也是一個微技能。濫用類型轉換,也會致使BUG 。

Java 中容易出現的錯誤是:a. 字符串轉數值,字符串含有非數字部分;b. JSON字符串轉對象,某個字段含有不兼容的值類型致使解析出錯;c. 子類型轉不兼容的父類型,滋生運行時異常 ClassCastException;d. 相同特質的類型不兼容。好比 Long 與 Integer 都是數值型,卻不能互轉。

類型轉換中最容易出BUG的地方是非布爾類型取反。受C語言的影響,不少高級語言支持各類數據類型轉布爾類型,好比 PHP 字符串、數組、數字等均可以轉布爾類型,相應的就喜歡寫 if (!notBoolVar) 這種表達式, 容易隱藏看不出的BUG甚至錯誤。

否認式風格

變量含義、表達式語句傾向於使用否認式風格,可能不知不覺耗費大量腦細胞,由於每次理解的時候都要繞個彎子。 好比 isNoExpress 是否無需物流, 就有點繞。 爲何呢? 無需物流是針對快遞發貨的, 若是快遞發貨佔發貨的90%, 無需物流只佔10%,那麼, isNoExpress = false 幾乎總爲真。 涉及到判斷的時候,可能不得不寫 if (!isNoExpress) , 雙重否認足夠弄暈你。

容器遍歷的結構變動

絕大多數語言都承襲了 C 語言的 for(int i=0;i<N;i++) 循環形式。不過,現代編程語言一般都提供了迭代器遍歷、或 foreach 遍歷。 foreach 遍歷一般基於迭代器遍歷實現。 只要對容器結構不作變動,推薦使用 foreach ; 若要遍歷的同時作修改或更新,推薦迭代器模式。 遍歷容器的時候同時作刪除元素操做,要特別留意,極可能致使越界錯誤。更可靠的方式時,直接生成新的容器,若是不涉及空間效率的話。

API參數傳遞錯誤

若是API參數有多個,並且相鄰參數的類型相同,那麼要特別留意是否參數順序是正確的,而不會張冠李戴。

固然,在設計API參數的時候,就能夠仔細用更精準類型進行區分,並將相同類型的參數錯開。好比 calc(int accountNo, int pay, int timestamp) , 就容易傳錯,比較可靠的是 calc(int accountNo, Currency pay, Timestamp now) ,這樣是不可能將參數傳遞錯誤的。

單行調用括號過多

爲了簡便,經常會寫出 wapper(calc(now, String.format("%s\n", new BufferedFileReader(filename, "UTF-8").readLines() ))) 的語句 , 嗯,你得好好瞧瞧和算算右邊的括號數量是否正確了。更糟糕的時候,結合API參數傳遞錯誤,IDE 可能沒有報錯, 而你極可能沒有意識到本身的參數傳遞錯誤了。 可靠的方式是, 拆出一部分變量,並將調用之間的括號用空格隔開,顯示出層次感。

String fileContent = new BufferedFileReader(filename, "UTF-8").readLines();
wapper( calc( now,  String.format("%s\n", fileContent) ) )


修改方法簽名

對某個方法有業務改動時,程序猿媛們傾向直接修改原方法的簽名。這時,要特別注意:a. 不要修改原方法的參數順序; b. 在最後面增長可選參數。 從另外一個角度來看,複雜的業務方法應當分兩層: 最外層負責調度,方法參數具備包容性,裏面包含的字段比較多 ; 內層方法負責特定業務邏輯的實現,方法參數少而精。

修改原方法簽名自己就是容易產生問題的習慣, 篡改原方法的參數順序更是大忌。 最好的方法是新建一個方法去複用原方法, 而後調用新的方法。代碼變動始終銘記「開閉」原則。

打印日誌太多

打印過多的日誌並很差。一方面遮掩真正須要的信息,致使排查耗費時間, 另外一方面形成服務器空間浪費、影響性能。生產環境日誌通常只開放 INFO及以上級別的日誌; Debug 日誌只在調試或排錯的時候使用,生產環境能夠禁止debug日誌。

多級數據結構

使用多級數據結構時,要肯定父級數據必定有值,或者進行檢測。好比 $order['baole']['ump']['money'],必須確保 $order['baole'], $order['baole']['money'] 必定有值或作非空檢測。

做用域過大

因爲C語言的影響,猿媛們會在開頭就定義好一些變量或要返回的對象,在很靠後的地方纔使用到。沒必要要的過大的做用域對變量和對象的變化產生不可測的影響,並增大理解的成本。可靠的方法是,僅當在使用時才定義,並儘快返回結果。

另外一種狀況是,暴露的訪問域過大,好比 public 字段。 儘量地縮小可訪問的範圍,能夠增大變動和重構的空間; 減小可變性,則能夠天然地得到併發安全性,下降CodeReview的理解成本。

好比,不可變的類和字段定義成 final , 最小化包,類,接口,方法和域的可訪問性,默認爲 private , 若須要繼承,可定義爲 protected , 僅當須要做爲 API 服務暴露出去時,使用 public.

分支與循環

條件與循環偶爾也會致使錯誤, 不過一般錯誤能夠在發佈前解決掉。

對於 if-else 嵌套條件, 須要仔細檢查是否符合業務邏輯; 若是嵌套太深,是否可使用另外一種方式「解結」 ; 對於 switch 語句, 大多數語言的 case 有 fall through 問題, 要注意加上 break ; 最好加上 default 的處理。

對於 for 循環, 編寫合理的結束條件避免死循環; 對於循環變量的控制, 避免出現 -1或 +1 錯誤, 消除越界錯誤; for 循環也要特別注意對空值和空容器的處理,避免拋出空值異常。能夠經過單測來確保 for 循環的準確性。

殘留的無用代碼

殘留的無用代碼,會成爲系統的垃圾,增長系統的理解和維護成本。須要及時清理掉。

代碼與文檔不一致

文檔是理解代碼的第一扇窗口。優秀的文檔,能夠極大地下降理解代碼的成本。可是大多數開發者還並不習慣編寫友好的文檔。經常出現無文檔、失效文檔、誤導性文檔等,影響人們的理解和判斷。

使用冷僻用法或奇淫巧技

使用冷僻用法或奇淫巧技會增大系統的理解成本,徒然消耗人的腦細胞。思路可借鑑,但不宜用於生產環境中。樸實最宜。

相關文章
相關標籤/搜索