Java开发中的常见危险信号
在開發,閱讀,復審和維護成千上萬行Java代碼的幾年中,我已經習慣于看到Java代碼中的某些“ 危險信號 ”,這些信號通常(但可能并非總是)暗示著代碼問題。 我不是在談論總是錯誤的做法,而是在有限的情況下談論適當的做法,但通常都表明存在錯誤。 這些“ 危險信號 ”有時可能是無辜的,但經常警告不可避免地會不可避免地出現“ 傷害堆 ”。 在這里,我總結了其中的一些內容,并簡要討論了它們可能還不錯的情況,并描述了它們通常不正常的原因。
這些“紅色標志”中的許多標志意義重大,足以保證可從諸如FindBugs之類的代碼分析工具發出警告。 流行的Java IDE也標記了其中許多。 但是,我已經看到開發人員錯過了這些工具和IDE的更多文字標記,這是因為他們關閉了這些選項,或者是因為他們習慣了或者由于不了解與標志相關的風險而忽略了警告。
在引用中使用==(而不是.equals)
大多數Java開發人員都學會了使用==比較基元,以及使用.equals比較引用類型。 通常,這是一個容易記住的規則,通常可以很好地為Java開發人員服務。 有時使用==比較標準Java類型引用(String,Integer,Long等) 可以很好地工作 ,但是依靠要緩存的值來使它可行并不是一個好主意。 在某些情況下,可能需要檢查身份相等性而不是內容相等性,然后==適合比較引用。 我在這里更喜歡Groovy的方法,其中==作用類似于.equals和===并且顯然傳達了開發人員更嚴格地比較身份的愿望。 相同的參數適用于使用!=將兩個引用比較為一個紅色標記,因為如果被比較的兩個對象即使共享相同的內容,也不共享相同的標識(內存地址),則始終返回true。
在枚舉中使用.equals(而不是==)
坦白說,Java開發人員對枚舉使用==還是.equals并不重要。 但是,我更喜歡將==與enums一起使用 。 此首選項的最重要原因是將==與枚舉一起使用可避免將枚舉與某個不相關的對象(永遠不會相等)進行比較的可能錯誤。 Object.equals(Object)方法必須必須接受任何Object,但這意味著編譯器無法強制傳入的對象實際上是要比較的類型。 與動態運行時檢測問題相比,我通常更喜歡靜態問題的靜態編譯時間檢測,并且將==與枚舉一起使用可滿足此偏好。 當然,在比較枚舉時,同樣的參數適用于!=和!.equals使用。
幻數和文字字符串
我知道它是“計算機科學101”,但是我仍然經常看到在Java代碼中經常使用“ 魔術數字 ”和文字字符串。 這些作為將來可維護性的“危險信號”而大聲疾呼,使我對當前應用程序的正確性產生嚴重懷疑。 在單個位置將它們表示為常量(或者在適當時更好地表示為枚舉)可以提高將來的可維護性,并且也使我對使用這些值的所有代碼都使用相同的值具有更高的信心。 此外,集中定義的常量和枚舉使使用IDE的“查找用法”功能輕松查找這些常量的所有“客戶端”。
字符串常量
當我看到一組有限的相關String常量時,我??常常認為枚舉會更好。 對于具有高度內聚性的一組String常量,尤其如此,它允許枚舉很好地表示這些String構成的概念。 與String常量相比,該枚舉提供了編譯時靜態類型的安全性和潛在的性能優勢。 在程序正確性方面,最讓我感興趣的是編譯時安全。
使用Java的“ Goto”
在這篇文章中,我幾乎沒有在使用分支到帶標簽的代碼上包含這個項目,因為事實是,我在生產代碼中看到的大多數實例都是合理的 。 換句話說,在這篇文章中列出該項目的原因更多是因為它有可能以錯誤的方式濫用和使用,而不是我實際看到的。 在大多數情況下,我看到Java開發人員使用Java的“ goto”是為了避免過于混亂和難以閱讀的代碼。 很少使用此事實可能部分歸因于其正確使用。 如果經常使用,那么這些用途中的大多數可能會變味。
取決于適用范圍的同名變量的適當引用
在我看來,該項目屬于絕對不合適的類別,但絕對可行,甚至某些時候某些Java開發人員有意地完成了。 最好的例子是Java開發人員在執行方法期間將傳遞給方法的變量指向另一個引用。 指向方法參數的變量暫時指向它所分配的任何替代方法,直到方法結束為止,此時該方法超出范圍。 在這種情況下,將final關鍵字放在方法定義的參數定義之前會導致編譯器錯誤,這也是我喜歡在所有方法參數之前使用final的原因之一。 對我來說,簡單地在該方法的局部聲明一個新變量更容易理解,因為無論如何該變量僅在該方法局部使用。 更重要的是,作為代碼的讀者,我無法知道開發人員是否有意讓該參數的名稱僅在本地用于不同的值,還是他們引入了一個錯誤,認為將參數重新分配給新的引用會實際上在主叫方進行更改。 當我看到這些內容時,我要么與原始開發人員合作,要么從單元測試和生產中搜集信息,然后使用意圖并使其更加清晰(或者如果打算更改調用方客戶的價值,則進行修復)。
equals(Object)和hashCode()方法不匹配
盡管我相信應該為幾乎所有編寫的Java類都編寫一個toString()方法,但是我對equals(Object)和hashCode()重寫的感覺并不相同。 我認為只有在打算將類用于需要這些方法的情況下,才應編寫這些內容,因為它們的存在應暗示它們在設計和開發中具有一定程度的額外徹底性。 特別是,equals和hashCode方法需要滿足它們的意圖并進行廣告宣傳(在Object的API文檔中),并且必須彼此對齊。 大多數IDE和分析工具會確定何時存在一種方法而沒有另一種方法。 但是,我想確保將用于equals的相同屬性用于hashCode,并且我希望在兩種方法中以相同的順序考慮它們。
缺少Javadoc注釋
我喜歡將所有合同方法(尤其是公共方法)與Javadoc注釋一起注釋。 我還發現用屬性要存儲的內容進行注釋非常有用。 我已經聽過當代碼“自我記錄”時不使用Javadoc注釋的借口,但是我幾乎總是可以告訴提出該要求的人一個或多個示例,以了解簡單的Javadoc注釋如何能夠傳遞與傳遞給您相同的信息。比解密代碼花費的時間更長。 甚至更長的方法名稱通常也不能足夠長以指定給定方法的所有預期輸入條件和輸出預期。 我認為像我一樣,許多Java開發人員都喜歡在使用JDK時閱讀Javadoc注釋,而不是閱讀JDK代碼。 為什么我們自己的內部代碼應該有所不同? 當注釋不足或行為與所宣傳的不一樣時,或者當我有理由認為注釋可能陳舊或偽劣時,我會閱讀源代碼。
我還希望看到Javadoc對類屬性的注釋。 有些人喜歡用屬性信息注釋公共的get / set方法,但是我也不喜歡這種方法,因為它假定get / set方法將始終可用(我不喜歡這樣的假設)。
方法的Javadoc注釋中的實現詳細信息
盡管我覺得沒有Javadoc注釋是一個危險信號,但是使用錯誤類型的Javadoc注釋也是一個危險信號。 Javadoc注釋不應解釋實現細節,而應側重于客戶端對方法的期望(參數)和客戶端對方法的期望(返回類型和可能引發的異常)。 在真正的面向對象的系統中,實現應該可以在公共接口下進行修改,因此將這些實現詳細信息放在接口文檔中似乎是不合適的。 這是一個“危險信號”,因為Javadoc中存在實現細節使我懷疑注釋的及時性。 換句話說,這些類型的注釋通常會隨著代碼的發展而Swift過時并且完全錯誤。
源代碼中的注釋
盡管在接口上缺少注釋(通常在Javadoc中)是一個危險信號,但方法和其他源代碼體內是否存在注釋也是一個指示潛在問題的危險信號。 如果需要對代碼進行注釋以了解其功能,則代碼可能比需要的復雜得多(“注釋是臭代碼的除臭劑”通常是正確的,也很有趣)。 就像這篇文章中的許多“紅色標記”一樣,當我看到代碼中的注釋內容豐富時,也有一些例外,在這些注釋中,我無法想到一種更好的方式來呈現代碼以消除對這些注釋的需要。 但是,我認為代碼內(而不是接口描述性Javadoc注釋)應該相對較少,并且應該關注“為什么”做某事,而不是“怎么做”。 該代碼應該說明“如何”的細節,但通常不能被編寫為隱式地解釋“為什么”(由于客戶/管理方向,設計決策,正式接口要求,正式算法要求等)。
實現繼承(擴展)
我已經看到許多情況下可以很好地使用extends (實現繼承),并且適合這種情況。 我已經看到了很多情況相反的情況,實現繼承帶來的麻煩多于好處。 艾倫·霍魯布(Allen Holub)曾寫過“ 擴展是邪惡的” ,《 四種 設計模式的幫派》 一書非常著重于為什么組合通常比實現繼承更可取的原因。 開發Java代碼的時間越長,更重要的是,我維護Java代碼的時間越長,我就越相信組合的優點和實現繼承的弊端。 正如我所說,我已經看到實現繼承可以起到很好的作用,但是通常情況下,類會“苦苦掙扎”以適應繼承層次結構,并且子類會遇到UnsupportedOperationException或“ noop ”實現,因為它們繼承的方法沒有實現。真的適用。 拋出一些在有效Java中概述的繼承問題(例如,用于繼承具體類的equals和hashCode方法實現),它可能會變成一團糟。 實現繼承并不總是不好的,但是它經常被不好地使用,因此是一個危險信號。
死碼
閑置未使用的代碼永遠不是一件好事。 人們很快就會忘記如何使用它或為什么使用它。 此后不久,開發人員開始懷疑它是否由于某種原因而遺留了下來。 死代碼不僅增加了必須讀取和可能維護的代碼,而且在通過IDE完成編碼的世界中,僅基于死代碼的名稱和參數列表,就可以輕松,意外地調用死方法。 死代碼通常也是被忽略的代碼庫的癥狀。
注釋代碼
已注釋掉的代碼可能不像可執行的死代碼那樣糟糕,因為至少不能意外地調用它,并且更明顯的是未使用它,但是它仍然是一個危險信號,因為它表明潛在的代碼庫被忽視了。 就像死掉的可執行代碼一樣,在注釋掉代碼之間經過的時間越長,就越難知道為什么代碼在那里,為什么注釋掉了以及為什么不再不再刪除代碼了需要。 開發人員可能會害怕刪除它,因為它顯然很重要,因此必須先離開,但沒人記得為什么。
待辦事項
在代碼中添加“待辦事項”語句變得非常普遍,以至于現代Java IDE為它們提供了特殊的支持和功能。 這些功能之所以有用,是因為它們經常將待辦事項標記放在列表中以供查看,但是“待辦事項”注釋仍然是危險標記,并且可能帶來一些與死代碼和注釋掉代碼相同的問題。 我肯定會使用“做”注釋來進行短期提醒,但是我認為最好在“做注釋”中包括“有效期”和聯系信息以及可能的人員或事件(如果有)需要蒸蒸日上,以使“工作”得以完成。 擁有到期日期,聯系信息以及解決“待辦事項”所必需的事件或人員,可以減少在代碼中進行“待辦事項”注釋的可能性,因為沒人記得確切的用途,但沒人敢刪除它們,因為是一件重要的事情。 當我在代碼中看到“要做”的陳述時,我不禁想知道代碼是否某種程度上缺乏功能。
編譯器警告和IDE /工具警告/提示/發現
Java紅旗的明顯示例是javac編譯器發出的警告以及IDE和其他代碼分析工具提供的發現和提示。 這些發現和警告中的每一個都可能是其自己的危險信號。 實際上,javac編譯器或工具和IDE會警告或暗示我在本文中引用的許多危險信號。 這些警告,提示和發現不僅直接對應于許多Java代碼危險信號,而且它們的合計存在是一個巨大的危險信號,表明潛在的被忽略的代碼庫可能會在其中丟失嚴重的警告(甚至是某些定義的錯誤)。大量的警告和提示。
編譯器警告和IDE /工具警告/提示/發現已關閉
我絕對不認為FindBugs在我的Java代碼中發現的每個問題都必然是錯誤或缺陷。 實際上,在某些情況下,我什至禁用了一些發現,因為我不同意這些發現,并且它們使FindBugs輸出變得混亂。 話雖如此,對發現的這種選擇應該謹慎進行,以確保只忽略真正的非發現,并且對開發團隊重要的事情是顯而易見的。 同樣,我希望打開許多IDE警告,但確實要關閉一些警告。 我也不認為在禁用Javac警告的情況下構建Java應用程序不是一個好主意。 禁用這些警告和發現可能會刪除警告所代表的危險信號,但是將其關閉的動作可能會導致更大的危險信號,因為這些警告和提示指出了可以解決的許多潛在危險信號。
太聰明了/ Science Fair項目/過度設計/過早的優化
我最后一類危險信號是一大類過于復雜的軟件,這通常是常見的開發人員功能失常行為之一的結果。 過于聰明以致于無法閱讀的代碼,或者在不需要時以靈活性或(過早的)優化為 重點的代碼,但往往以可讀性為代價,這通常會帶來其他問題。 對代碼進行過度工程化的開發人員可能正在這樣做,以查看他們是否可以或嘗試一些新的東西,或者只是想改變一些東西,但是這些行為很少會不利于高質量的軟件。 一旦軟件變得過于復雜且難以理解,就不太可能對其進行正確維護,并且更有可能對其進行不正確的更改。
通常,在閱讀代碼并想知道為什么開發人員沒有以更明顯和直接的方式實現此功能時,這種危險信號的例子就是一個明顯的例子。 一方面,您可能會對他們知道并能夠應用某些高級功能印象深刻,但另一方面,您知道這可能比原本應該的復雜。 這類危險信號有許多表現形式,但我似乎在一些地區普遍看到它們。 特別是其中一個領域是通過反射,Spring或其他依賴項注入,動態代理,觀察者等,將過多的功能(這些功能在靜態Java代碼中能很好地發揮作用)推向更多的動態構造。 所有這些東西在正確應用時都是方便且有用的,但我也看到所有這些東西都被過度使用和濫用,這使開發人員難以跟蹤代碼中發生的事情。
結論
在本文中,我研究了在Java代碼和Java開發環境中看到的一些常見的Java紅色標記。 如果使用不當,這些事情不一定是錯誤的或負面的,但如果使用不當,則可以視為對潛在有害做法的警告。 當我看到這些危險信號時,我不會急于做出判斷,直到我有機會更深入地研究以確定在這些危險信號下進行釀造是否有困難,或者它們是否處于可以采用該策略的情況下。 通常,這些危險信號是即將發生問題的早期指標。 在某些情況下,它們是對嚴重的現有問題(也許以前無法解釋的問題)的解釋或指示。
翻譯自: https://www.javacodegeeks.com/2013/06/common-red-flags-in-java-development.html
總結
以上是生活随笔為你收集整理的Java开发中的常见危险信号的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 软糯是什么意思 怎么理解软糯的意思
- 下一篇: 集成JavaFX和Swing(修订版)