codeReview常見代碼問題


codeReview常見代碼問題

  

路線圖
  常見代碼問題
  空值
  未捕獲潛在的異常
  低性能
  影響范圍過大
  單測問題
  與原有業務邏輯不兼容
  缺乏必要日志
  錯誤碼不符合規范
  參數檢測缺乏或不足
  引用錯誤
  名字沖突
  細節錯誤
  多重條件
  文不符實
  跨語言或跨系統交互
  可維護性問題
  硬編碼
  重復代碼
  通用邏輯與定制業務邏輯耦合
  直接在原方法里加邏輯
  多業務耦合
  代碼層次不合理
  不用多余的代碼
  使用全局變量
  缺乏必要的注釋
  更難發現的錯誤
  並發
  資源泄露
  事務
  SQL問題
  安全問題
  設計問題
  較輕微的問題
  命名不貼切
  聲明時未初始化
  風格與整體有不一致
  類型轉換錯誤
  否定式風格
  容器遍歷的結構變更
  API參數傳遞錯誤
  單行調用括號過多
  修改方法簽名
  打印日志太多
  多級數據結構
  作用域過大
  分支與循環
  殘留的無用代碼
  代碼與文檔不一致
  使用冷僻用法或奇淫巧技

常見代碼問題

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

空值

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

原則上,對於任何異常, 希望能夠打印出具體的錯誤信息,根據錯誤信息很快明白是什么原因, 而不是一個 null ,還要在代碼里去推敲為什么為空。這樣我們必須識別出程序中可能的null, 並及時檢測、捕獲和拋出異常。

對於空值,最好的防護是“防御式編程”。當獲取到對象之后, 使用之前總是判斷是否為空,並適當拋出異常、打錯誤日志或做其它處理。 有的人嫌檢測為空的 if 語句充斥在代碼里會破壞代碼的可維護性, 對此我的建議是:

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

未捕獲潛在的異常

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

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

低性能

低性能會導致產品功能不好用、不可用,甚至導致產品失敗。

  常見情況有: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 循環的准確性。

殘留的無用代碼

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

代碼與文檔不一致

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

使用冷僻用法或奇淫巧技

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM