1. 盡量在合適的場合使用單例
創(chuàng)新互聯(lián)建站專業(yè)為企業(yè)提供琿春網(wǎng)站建設(shè)、琿春做網(wǎng)站、琿春網(wǎng)站設(shè)計、琿春網(wǎng)站制作等企業(yè)網(wǎng)站建設(shè)、網(wǎng)頁設(shè)計與制作、琿春企業(yè)網(wǎng)站模板建站服務,十余年琿春做網(wǎng)站經(jīng)驗,不只是建網(wǎng)站,更提供有價值的思路和整體網(wǎng)絡服務。
使用單例可以減輕加載的負擔,縮短加載的時間,提高加載的效率,但并不是所有地方都適用于單例,簡單來說,單例主要適用于以下三個方面:
第一,控制資源的使用,通過線程同步來控制資源的并發(fā)訪問;
第二,控制實例的產(chǎn)生,以達到節(jié)約資源的目的;
第三,控制數(shù)據(jù)共享,在不建立直接關(guān)聯(lián)的條件下,讓多個不相關(guān)的進程或線程之間實現(xiàn)通信。
2. 盡量避免隨意使用靜態(tài)變量
要知道,當某個對象被定義為stataic變量所引用,那么gc通常是不會回收這個對象所占有的內(nèi)存
3. 盡量避免過多過常的創(chuàng)建Java對象
盡量避免在經(jīng)常調(diào)用的方法,循環(huán)中new對象,由于系統(tǒng)不僅要花費時間來創(chuàng)建對象,而且還要花時間對這些對象進行垃圾回收和處理,在我們可以控制的范圍內(nèi),最大限度的重用對象,最好能用基本的數(shù)據(jù)類型或數(shù)組來替代對象。
4. 盡量使用final修飾符
帶有final修飾符的類是不可派生的。在Java核心API中,有許多應用final的例子,例如java.lang.String.為String類指定final防止了使用者覆蓋length()方法。另外,如果一個類是final的,則該類所有方法都是final的。Java編譯器會尋找機會內(nèi)聯(lián)(inline)所有的final方法(這和具體的編譯器實現(xiàn)有關(guān))。此舉能夠使性能平均提高50%.
5. 盡量使用局部變量
調(diào)用方法時傳遞的參數(shù)以及在調(diào)用中創(chuàng)建的臨時變量都保存在棧(Stack)中,速度較快。其他變量,如靜態(tài)變量、實例變量等,都在堆(Heap)中創(chuàng)建,速度較慢。
6. 盡量處理好包裝類型和基本類型兩者的使用場所
雖然包裝類型和基本類型在使用過程中是可以相互轉(zhuǎn)換,但它們兩者所產(chǎn)生的內(nèi)存區(qū)域是完全不同的,基本類型數(shù)據(jù)產(chǎn)生和處理都在棧中處理,包裝類型是對象,是在堆中產(chǎn)生實例。
在集合類對象,有對象方面需要的處理適用包裝類型,其他的處理提倡使用基本類型。
7. 慎用synchronized,盡量減小synchronize的方法
都知道,實現(xiàn)同步是要很大的系統(tǒng)開銷作為代價的,甚至可能造成死鎖,所以盡量避免無謂的同步控制。synchronize方法被調(diào)用時,直接會把當前對象鎖 了,在方法執(zhí)行完之前其他線程無法調(diào)用當前對象的其他方法。所以synchronize的方法盡量小,并且應盡量使用方法同步代替代碼塊同步。
8. 盡量使用StringBuilder和StringBuffer進行字符串連接
這個就不多講了。
9. 盡量不要使用finalize方法
實際上,將資源清理放在finalize方法中完成是非常不好的選擇,由于GC的工作量很大,尤其是回收Young代內(nèi)存時,大都會引起應用程序暫停,所以再選擇使用finalize方法進行資源清理,會導致GC負擔更大,程序運行效率更差。
10. 盡量使用基本數(shù)據(jù)類型代替對象
String str = "hello";
上面這種方式會創(chuàng)建一個"hello"字符串,而且JVM的字符緩存池還會緩存這個字符串;
String str = new String("hello");
此時程序除創(chuàng)建字符串外,str所引用的String對象底層還包含一個char[]數(shù)組,這個char[]數(shù)組依次存放了h,e,l,l,o
11. 單線程應盡量使用HashMap、ArrayList
HashTable、Vector等使用了同步機制,降低了性能。
12. 盡量合理的創(chuàng)建HashMap
當你要創(chuàng)建一個比較大的hashMap時,充分利用另一個構(gòu)造函數(shù)
public HashMap(int initialCapacity, float loadFactor)
避免HashMap多次進行了hash重構(gòu),擴容是一件很耗費性能的事,在默認中initialCapacity只有16,而loadFactor是 0.75,需要多大的容量,你最好能準確的估計你所需要的最佳大小,同樣的Hashtable,Vectors也是一樣的道理。
13. 盡量減少對變量的重復計算
并且在循環(huán)中應該避免使用復雜的表達式,在循環(huán)中,循環(huán)條件會被反復計算,如果不使用復雜表達式,而使循環(huán)條件值不變的話,程序?qū)\行的更快。
14. 盡量避免不必要的創(chuàng)建
15. 盡量在finally塊中釋放資源
程序中使用到的資源應當被釋放,以避免資源泄漏。這最好在finally塊中去做。不管程序執(zhí)行的結(jié)果如何,finally塊總是會執(zhí)行的,以確保資源的正確關(guān)閉。
16. 盡量使用移位來代替'a/b'的操作
"/"是一個代價很高的操作,使用移位的操作將會更快和更有效
17.盡量使用移位來代替'a*b'的操作
同樣的,對于'*'操作,使用移位的操作將會更快和更有效
18. 盡量確定StringBuffer的容量
StringBuffer 的構(gòu)造器會創(chuàng)建一個默認大小(通常是16)的字符數(shù)組。在使用中,如果超出這個大小,就會重新分配內(nèi)存,創(chuàng)建一個更大的數(shù)組,并將原先的數(shù)組復制過來,再 丟棄舊的數(shù)組。在大多數(shù)情況下,你可以在創(chuàng)建 StringBuffer的時候指定大小,這樣就避免了在容量不夠的時候自動增長,以提高性能。
19. 盡量早釋放無用對象的引用
大部分時,方法局部引用變量所引用的對象 會隨著方法結(jié)束而變成垃圾,因此,大部分時候程序無需將局部,引用變量顯式設(shè)為null.
20. 盡量避免使用二維數(shù)組
二維數(shù)據(jù)占用的內(nèi)存空間比一維數(shù)組多得多,大概10倍以上。
21. 盡量避免使用split
除非是必須的,否則應該避免使用split,split由于支持正則表達式,所以效率比較低,如果是頻繁的幾十,幾百萬的調(diào)用將會耗費大量資源,如果確實需 要頻繁的調(diào)用split,可以考慮使用apache的StringUtils.split(string,char),頻繁split的可以緩存結(jié)果。
22. ArrayList LinkedList
一 個是線性表,一個是鏈表,一句話,隨機查詢盡量使用ArrayList,ArrayList優(yōu)于LinkedList,LinkedList還要移動指 針,添加刪除的操作LinkedList優(yōu)于ArrayList,ArrayList還要移動數(shù)據(jù),不過這是理論性分析,事實未必如此,重要的是理解好2 者得數(shù)據(jù)結(jié)構(gòu),對癥下藥。
23. 盡量使用System.arraycopy ()代替通過來循環(huán)復制數(shù)組
System.arraycopy() 要比通過循環(huán)來復制數(shù)組快的多
24. 盡量緩存經(jīng)常使用的對象
盡可能將經(jīng)常使用的對象進行緩存,可以使用數(shù)組,或HashMap的容器來進行緩存,但這種方式可能導致系統(tǒng)占用過多的緩存,性能下降,推薦可以使用一些第三方的開源工具,如EhCache,Oscache進行緩存,他們基本都實現(xiàn)了FIFO/FLU等緩存算法。
25. 盡量避免非常大的內(nèi)存分配
有時候問題不是由當時的堆狀態(tài)造成的,而是因為分配失敗造成的。分配的內(nèi)存塊都必須是連續(xù)的,而隨著堆越來越滿,找到較大的連續(xù)塊越來越困難。
26. 慎用異常
當創(chuàng)建一個異常時,需要收集一個棧跟蹤(stack track),這個棧跟蹤用于描述異常是在何處創(chuàng)建的。構(gòu)建這些棧跟蹤時需要為運行時棧做一份快照,正是這一部分開銷很大。當需要創(chuàng)建一個 Exception 時,JVM 不得不說:先別動,我想就您現(xiàn)在的樣子存一份快照,所以暫時停止入棧和出棧操作。棧跟蹤不只包含運行時棧中的一兩個元素,而是包含這個棧中的每一個元素。
如 果您創(chuàng)建一個 Exception ,就得付出代價。好在捕獲異常開銷不大,因此可以使用 try-catch 將核心內(nèi)容包起來。從技術(shù)上講,您甚至可以隨意地拋出異常,而不用花費很大的代價。招致性能損失的并不是 throw 操作--盡管在沒有預先創(chuàng)建異常的情況下就拋出異常是有點不尋常。真正要花代價的是創(chuàng)建異常。幸運的是,好的編程習慣已教會我們,不應該不管三七二十一就 拋出異常。異常是為異常的情況而設(shè)計的,使用時也應該牢記這一原則。
(1)。 用Boolean.valueOf(boolean b)代替new Boolean()
包裝類的內(nèi)存占用是很恐怖的,它是基本類型內(nèi)存占用的N倍(N2),同時new一個對象也是性能的消耗。
(2)。 用Integer.valueOf(int i)代替new Integer()
和Boolean類似,java開發(fā)中使用Integer封裝int的場合也非常多,并且通常用int表示的數(shù)值都非常小。SUN SDK中對Integer的實例化進行了優(yōu)化,Integer類緩存了-128到127這256個狀態(tài)的Integer,如果使用 Integer.valueOf(int i),傳入的int范圍正好在此內(nèi),就返回靜態(tài)實例。這樣如果我們使用Integer.valueOf代替new Integer的話也將大大降低內(nèi)存的占用。
(3)。 用StringBuffer的append方法代替"+"進行字符串相加。
這個已經(jīng)被N多人說過N次了,這個就不多說了。
(4)。 避免過深的類層次結(jié)構(gòu)和過深的方法調(diào)用。
因為這兩者都是非常占用內(nèi)存的(特別是方法調(diào)用更是堆棧空間的消耗大戶)。
(5)。 變量只有在用到它的時候才定義和實例化。
這是初學者最容易犯的錯,合理的使用變量,并且只有在用到它的時候才定義和實例化,能有效的避免內(nèi)存空間和執(zhí)行性能上的浪費,從而提高了代碼的效率。
(6)。 避免在循環(huán)體中聲明創(chuàng)建對象,即使該對象占用內(nèi)存空間不大。
這種情況在我們的實際應用中經(jīng)常遇到,而且我們很容易犯類似的錯誤
采用上面的第二種編寫方式,僅在內(nèi)存中保存一份對該對象的引用,而不像上面的第一種編寫方式中代碼會在內(nèi)存中產(chǎn)生大量的對象引用,浪費大量的內(nèi)存空間,而且增大了垃圾回收的負荷。因此在循環(huán)體中聲明創(chuàng)建對象的編寫方式應該盡量避免。
(7)。 如果if判斷中多個條件用'||'或者''連接,請將出現(xiàn)頻率最高的條件放在表達式最前面。
這個小技巧往往能有效的提高程序的性能,尤其是當if判斷放在循環(huán)體里面時,效果更明顯。
1.JVM管理兩種類型的內(nèi)存:堆內(nèi)存(heap),棧內(nèi)存(stack),堆內(nèi)在主要用來存儲程序在運行時創(chuàng)建或?qū)嵗膶ο笈c變量。而棧內(nèi)存則是用來存儲程序代碼中聲明為靜態(tài)(static)(或非靜態(tài))的方法。
2.JVM中對象的生命周期,創(chuàng)建階段,應用階段,不可視階段,不可到達階段,可收集階段,終結(jié)階段,釋放階段
3.避免在循環(huán)體中創(chuàng)建對象,即使該對象點用內(nèi)存空間不大。
4.軟引用的主要特點是具有較強的引用功能。只有當內(nèi)存不夠的時候,才回收這類內(nèi)存,因此在內(nèi)存足夠的時候,它們通常不被回收。它可以用于實現(xiàn)一些常用資源的緩存,實現(xiàn)Cache的功能
5.弱引用對象與Soft引用對象最大不同就在于:GC在進行回收時,需要通過算法檢查是否回收Soft引用對象,而對于Weak引用對象,GC總是進行回收。
6.共享靜態(tài)變量存儲空間
7.有時候我們?yōu)榱颂岣呦到y(tǒng)性能,避免重復耗時的操作,希望能夠重用一些創(chuàng)建完成的對象,利用對象池實現(xiàn)。類似JDBC連接池。
8.瞬間值,序列化對象大變量時,如果此大變量又沒有用途,則使用transient聲明,不序列化此變量。同時網(wǎng)絡傳輸中也不傳輸。
9.不要提前創(chuàng)建對象
10 .(1)最基本的建議就是盡早釋放無用對象的引用
A a = new A();
a = null; //當使用對象a之后主動將其設(shè)置為空
(2)盡量少用finalize函數(shù)。
(3) 如果需要使用經(jīng)常用到的圖片展,可以使用軟引用。
(4) 注意集合數(shù)據(jù)類型,包括數(shù)組,樹等數(shù)據(jù),這些數(shù)據(jù)結(jié)構(gòu)對GC來說,回收更為復雜,
(5) 盡量避免在類的默認構(gòu)造器中創(chuàng)建,初始化大量的對象,防止在調(diào)用其自類的構(gòu)造器時造成不必要的內(nèi)存資源浪費。
(6) 盡量避免強制系統(tǒng)做垃圾內(nèi)存回收。
(7) 盡量避免顯式申請數(shù)組空間。
(8) 盡量在合適的場景下使用對象池技術(shù)以提高系統(tǒng)性能,縮減系統(tǒng)內(nèi)存開銷。
11.當做數(shù)組拷貝操作時,采用System.arraycopy()方法完成拷貝操作要比采用循環(huán)的辦法完成數(shù)組拷貝操作效率高
12. 盡量避免在循環(huán)體中調(diào)用方法,因為方法調(diào)用是比較昂貴的。
13. 盡量避免在循環(huán)體中使用try-catch 塊,最好在循環(huán)體外使用try--catch塊以提高系統(tǒng)性能。
14. 在多重循環(huán)中,如果有可能,盡量將最長的循環(huán)放在最內(nèi)層,最短的循環(huán)放在最外層,以減少循環(huán)層間的變換次數(shù)。
15. 在需要線程安全的情況下,使用List list = Collections.synchronizedList(new ArrayList());
16. 如果預知長度,就設(shè)置ArrayList的長度。
17. ArrayList 與 LinkedList 選擇,熟悉底層的實現(xiàn)原理,選擇適當?shù)娜萜鳌?/p>
18. 字符串累加采用StringBuffer.
19. 系統(tǒng)I/O優(yōu)化,采用緩沖和壓縮技術(shù)。優(yōu)化性能。
20. 避免在類在構(gòu)造器的初始化其他類
21 盡量避免在構(gòu)造中對靜態(tài)變量做賦值操作
22. 不要在類的構(gòu)造器中創(chuàng)建類的實例
23. 組合優(yōu)化繼承
24. 最好通過Class.forname() 動態(tài)的裝載類
25. JSP優(yōu)化,采用out 對象中的print方法代替println()方法
26 .采用ServletOutputStream 對象代替JSPWriter對象
27. 采用適當?shù)闹党跏蓟痮ut 對象緩沖區(qū)的大小
28. 盡量采用forward()方法重定向新的JSP
29. 利用線程池技術(shù)處理客戶請求
30.Servlet優(yōu)化
(1) 通過init()方法來緩存一些靜態(tài)數(shù)據(jù)以提高應用性能。
(2) 用print() 方法取代println()方法。
(3) 用ServletOutputStream 取代 PrintWriter.
(4) 盡量縮小同步代碼數(shù)量
31. 改善Servlet應用性能的方法
(1)不要使用SingleThreadModel
(2)使用線程池ThreadPool
32. EJB優(yōu)化
實體EJB:
(1)實體EJB中常用數(shù)據(jù)緩存與釋放
(2)采用延遲加載的方式裝載關(guān)聯(lián)數(shù)據(jù)
(3)盡可能地應用CMP類型實體EJB
(4)直接采用JDBC技術(shù)處理大型數(shù)據(jù)
33. 優(yōu)化JDBC連接
(1)設(shè)置合適的預取行值
(2)采用連接池技術(shù)
(3)全合理應用事務
(4)選擇合適的事務隔離層與及時關(guān)閉連接對象
34. PreparedStatemetn只編譯解析一次,而Statement每次都編譯解析。
35. 盡可能地做批處理更新
36. 通過采用合適的getXXX方法提高系統(tǒng)性能
37. 采用設(shè)計模式。
代碼重構(gòu)(英語:Code refactoring)重構(gòu)就是在不改變軟件系統(tǒng)外部行為的前提下,改善它的內(nèi)部結(jié)構(gòu)。
軟件重構(gòu)需要借助工具完成,重構(gòu)工具能夠修改代碼同時修改所有引用該代碼的地方。在極限編程的方法學中,重構(gòu)需要單元測試來支持。
java重構(gòu):指程序員對已有程序在盡量不改變接口的前提下,進行重新編寫代碼的工作,一般有以下幾方面:
1、去除已知bug。
2、提高程序運行效率。
3、增加新的功能。
重構(gòu)舉例:(簡化代碼、提升效率)
重構(gòu)前:
if(list != null list.size() 0){
for(int i = 0; i list.size(); i++){
//skip...
}
}
重構(gòu)后
if(list != null){
for(int i = 0, len = list.size(); i len; i++){
//skip...
}
}
何時著手重構(gòu)(Refactoring)
新官上任三把火,開始一個全新??、腳不停蹄、加班加點,一支聲勢浩大的千軍萬"碼"夾裹著程序員激情和扣擊鍵盤的鳴金奮力前行,勢如破竹,攻城掠地,直指"黃龍府"。
開發(fā)經(jīng)理是這支浩浩湯湯代碼隊伍的統(tǒng)帥,他負責這支隊伍的命運,當齊桓公站在山頂上看到管仲訓練的隊伍整齊劃一地前進時,他感嘆說"我有這樣一支軍隊哪里還怕沒有勝利呢?"。但很遺憾,你手中的這支隊伍原本只是散兵游勇,在前進中招兵買馬,不斷壯大,所以隊伍變形在所難免。當開發(fā)經(jīng)理發(fā)覺隊伍變形時,也許就是克制住攻克前方山頭的誘惑,停下腳步整頓隊伍的時候了。
Kent Beck提出了"代碼壞味道"的說法,和我們所提出的"隊伍變形"是同樣的意思,隊伍變形的信號是什么呢?以下列述的代碼癥狀就是"隊伍變形"的強烈信號:
·代碼中存在重復的代碼
中國有118 家整車生產(chǎn)企業(yè),數(shù)量幾乎等于美、日、歐所有汽車廠家數(shù)之和,但是全國的年產(chǎn)量卻不及一個外國大汽車公司的產(chǎn)量。重復建設(shè)只會導致效率的低效和資源的浪費。
程序代碼更是不能搞重復建設(shè),如果同一個類中有相同的代碼塊,請把它提煉成類的一個獨立方法,如果不同類中具有相同的代碼,請把它提煉成一個新類,永遠不要重復代碼。
·過大的類和過長的方法
過大的類往往是類抽象不合理的結(jié)果,類抽象不合理將降低了代碼的復用率。方法是類王國中的諸侯國,諸侯國太大勢必動搖中央集權(quán)。過長的方法由于包含的邏輯過于復雜,錯誤機率將直線上升,而可讀性則直線下降,類的健壯性很容易被打破。當看到一個過長的方法時,需要想辦法將其劃分為多個小方法,以便于分而治之。
·牽一毛而需要動全身的修改
當你發(fā)現(xiàn)修改一個小功能,或增加一個小功能時,就引發(fā)一次代碼地震,也許是你的設(shè)計抽象度不夠理想,功能代碼太過分散所引起的。
·類之間需要過多的通訊
A類需要調(diào)用B類的過多方法訪問B的內(nèi)部數(shù)據(jù),在關(guān)系上這兩個類顯得有點狎昵,可能這兩個類本應該在一起,而不應該分家。
·過度耦合的信息鏈
"計算機是這樣一門科學,它相信可以通過添加一個中間層解決任何問題",所以往往中間層會被過多地追加到程序中。如果你在代碼中看到需要獲取一個信息,需要一個類的方法調(diào)用另一個類的方法,層層掛接,就象輸油管一樣節(jié)節(jié)相連。這往往是因為銜接層太多造成的,需要查看就否有可移除的中間層,或是否可以提供更直接的調(diào)用方法。
·各立山頭干革命
如果你發(fā)現(xiàn)有兩個類或兩個方法雖然命名不同但卻擁有相似或相同的功能,你會發(fā)現(xiàn)往往是因為開發(fā)團隊協(xié)調(diào)不夠造成的。筆者曾經(jīng)寫了一個頗好用的字符串處理類,但因為沒有及時通告團隊其他人員,后來發(fā)現(xiàn)項目中居然有三個字符串處理類。革命資源是珍貴的,我們不應各立山頭干革命。
·不完美的設(shè)計
在筆者剛完成的一個比對報警項目中,曾安排阿朱開發(fā)報警模塊,即通過Socket向指定的短信平臺、語音平臺及客戶端報警器插件發(fā)送報警報文信息,阿朱出色地完成了這項任務。后來用戶又提出了實時比對的需求,即要求第三方系統(tǒng)以報文形式向比對報警系統(tǒng)發(fā)送請求,比對報警系統(tǒng)接收并響應這個請求。這又需要用到Socket報文通訊,由于原來的設(shè)計沒有將報文通訊模塊獨立出來,所以無法復用阿朱開發(fā)的代碼。后來我及時調(diào)整了這個設(shè)計,新增了一個報文收發(fā)模塊,使系統(tǒng)所有的對外通訊都復用這個模塊,系統(tǒng)的整體設(shè)計也顯得更加合理。
每個系統(tǒng)都或多或少存在不完美的設(shè)計,剛開始可能注意不到,到后來才會慢慢凸顯出來,此時唯有勇于更改才是最好的出路。
·缺少必要的注釋
雖然許多軟件工程的書籍常提醒程序員需要防止過多注釋,但這個擔心好象并沒有什么必要。往往程序員更感興趣的是功能實現(xiàn)而非代碼注釋,因為前者更能帶來成就感,所以代碼注釋往往不是過多而是過少,過于簡單。人的記憶曲線下降的坡度是陡得嚇人的,當過了一段時間后再回頭補注釋時,很容易發(fā)生"提筆忘字,愈言且止"的情形。
曾在網(wǎng)上看到過微軟的代碼注釋,其詳盡程度讓人嘆為觀止,也從中體悟到了微軟成功的一個經(jīng)驗。
1_代碼重構(gòu)漫畫.jpeg
項目在不斷演進過程中,代碼不停地在堆砌。如果沒有人為代碼的質(zhì)量負責,代碼總是會往越來越混亂的方向演進。當混亂到一定程度之后,量變引起質(zhì)變,項目的維護成本已經(jīng)高過重新開發(fā)一套新代碼的成本,想要再去重構(gòu),已經(jīng)沒有人能做到了。
造成這樣的原因往往有以下幾點:
對于此類問題,業(yè)界已有有很好的解決思路:通過持續(xù)不斷的重構(gòu)將代碼中的“壞味道”清除掉。
重構(gòu)一書的作者Martin Fowler對重構(gòu)的定義:
根據(jù)重構(gòu)的規(guī)??梢源笾路譃榇笮椭貥?gòu)和小型重構(gòu):
大型重構(gòu) :對頂層代碼設(shè)計的重構(gòu),包括:系統(tǒng)、模塊、代碼結(jié)構(gòu)、類與類之間的關(guān)系等的重構(gòu),重構(gòu)的手段有:分層、模塊化、解耦、抽象可復用組件等等。這類重構(gòu)的工具就是我們學習過的那些設(shè)計思想、原則和模式。這類重構(gòu)涉及的代碼改動會比較多,影響面會比較大,所以難度也較大,耗時會比較長,引入bug的風險也會相對比較大。
小型重構(gòu) :對代碼細節(jié)的重構(gòu),主要是針對類、函數(shù)、變量等代碼級別的重構(gòu),比如規(guī)范命名和注釋、消除超大類或函數(shù)、提取重復代碼等等。小型重構(gòu)更多的是使用統(tǒng)一的編碼規(guī)范。這類重構(gòu)要修改的地方比較集中,比較簡單,可操作性較強,耗時會比較短,引入bug的風險相對來說也會比較小。什么時候重構(gòu) 新功能開發(fā)、修bug或者代碼review中出現(xiàn)“代碼壞味道”,我們就應該及時進行重構(gòu)。持續(xù)在日常開發(fā)中進行小重構(gòu),能夠降低重構(gòu)和測試的成本。
2_代碼常見問題.png
代碼重復
方法過長
過大的類
邏輯分散
嚴重的情結(jié)依戀
數(shù)據(jù)泥團/基本類型偏執(zhí)
不合理的繼承體系
過多的條件判斷
過長的參數(shù)列
臨時變量過多
令人迷惑的暫時字段
純數(shù)據(jù)類
不恰當?shù)拿?/p>
過多的注釋
3_代碼質(zhì)量如何衡量.jpg
代碼質(zhì)量的評價有很強的主觀性,描述代碼質(zhì)量的詞匯也有很多,比如可讀性、可維護性、靈活、優(yōu)雅、簡潔。這些詞匯是從不同的維度去評價代碼質(zhì)量的。其中,可維護性、可讀性、可擴展性又是提到最多的、最重要的三個評價標準。
要寫出高質(zhì)量代碼,我們就需要掌握一些更加細化、更加能落地的編程方法論,這就包含面向?qū)ο笤O(shè)計思想、設(shè)計原則、設(shè)計模式、編碼規(guī)范、重構(gòu)技巧等。
4_SOLID原則.png
一個類只負責完成一個職責或者功能,不要存在多于一種導致類變更的原因。
單一職責原則通過避免設(shè)計大而全的類,避免將不相關(guān)的功能耦合在一起,來提高類的內(nèi)聚性。同時,類職責單一,類依賴的和被依賴的其他類也會變少,減少了代碼的耦合性,以此來實現(xiàn)代碼的高內(nèi)聚、松耦合。但是,如果拆分得過細,實際上會適得其反,反倒會降低內(nèi)聚性,也會影響代碼的可維護性。
添加一個新的功能,應該是通過在已有代碼基礎(chǔ)上擴展代碼(新增模塊、類、方法、屬性等),而非修改已有代碼(修改模塊、類、方法、屬性等)的方式來完成。
開閉原則并不是說完全杜絕修改,而是以最小的修改代碼的代價來完成新功能的開發(fā)。
很多設(shè)計原則、設(shè)計思想、設(shè)計模式,都是以提高代碼的擴展性為最終目的的。特別是 23 種經(jīng)典設(shè)計模式,大部分都是為了解決代碼的擴展性問題而總結(jié)出來的,都是以開閉原則為指導原則的。最常用來提高代碼擴展性的方法有:多態(tài)、依賴注入、基于接口而非實現(xiàn)編程,以及大部分的設(shè)計模式(比如,裝飾、策略、模板、職責鏈、狀態(tài))。
子類對象(object of subtype/derived class)能夠替換程序(program)中父類對象(object of base/parent class)出現(xiàn)的任何地方,并且保證原來程序的邏輯行為(behavior)不變及正確性不被破壞。
子類可以擴展父類的功能,但不能改變父類原有的功能
調(diào)用方不應該依賴它不需要的接口;一個類對另一個類的依賴應該建立在最小的接口上。接口隔離原則提供了一種判斷接口的職責是否單一的標準:通過調(diào)用者如何使用接口來間接地判定。如果調(diào)用者只使用部分接口或接口的部分功能,那接口的設(shè)計就不夠職責單一。
高層模塊不應該依賴低層模塊,二者都應該依賴其抽象;抽象不應該依賴細節(jié),細節(jié)應該依賴抽象。
一個對象應該對其他對象保持最少的了解
盡量使用合成/聚合的方式,而不是使用繼承。
單一職責原則告訴我們實現(xiàn)類要職責單一;里氏替換原則告訴我們不要破壞繼承體系;依賴倒置原則告訴我們要面向接口編程;接口隔離原則告訴我們在設(shè)計接口的時候要精簡單一;迪米特法則告訴我們要降低耦合。而開閉原則是總綱,告訴我們要對擴展開放,對修改關(guān)閉。
image.png
模塊結(jié)構(gòu)說明
代碼開發(fā)要遵守各層的規(guī)范,并注意層級之間的依賴關(guān)系。
多個方法代碼重復、方法中代碼過長或者方法中的語句不在一個抽象層級。
方法是代碼復用的最小粒度,方法過長不利于復用,可讀性低,提煉方法往往是重構(gòu)工作的第一步。
意圖導向編程 :把處理某件事的流程和具體做事的實現(xiàn)方式分開。
將函數(shù)放進一個單獨對象中,如此一來局部變量就變成了對象內(nèi)的字段。然后你可以在同一個對象中將這個大型函數(shù)分解為多個小型函數(shù)。
方法參數(shù)比較多時,將參數(shù)封裝為參數(shù)對象
任何有返回值的方法,都不應該有副作用
臨時變量僅使用一次或者取值邏輯成本很低的情況下
將復雜表達式(或其中一部分)的結(jié)果放進一個臨時變量,以此變量名稱來解釋表達式用途
把復雜的條件表達式拆分成多個條件表達式,減少嵌套。嵌套了好幾層的if - then-else語句,轉(zhuǎn)換為多個if語句
當出現(xiàn)大量類型檢查和判斷時,if else(或switch)語句的體積會比較臃腫,這無疑降低了代碼的可讀性。 另外,if else(或switch)本身就是一個“變化點”,當需要擴展新的類型時,我們不得不追加if else(或switch)語句塊,以及相應的邏輯,這無疑降低了程序的可擴展性,也違反了面向?qū)ο蟮拈_閉原則。
非正常業(yè)務狀態(tài)的處理,使用拋出異常的方式代替返回錯誤碼
某一段代碼需要對程序狀態(tài)做出某種假設(shè),以斷言明確表現(xiàn)這種假設(shè)。
當使用一個方法返回的對象時,而這個對象可能為空,這個時候需要對這個對象進行操作前,需要進行判空,否則就會報空指針。當這種判斷頻繁的出現(xiàn)在各處代碼之中,就會影響代碼的美觀程度和可讀性,甚至增加Bug的幾率。
空引用的問題在Java中無法避免,但可以通過代碼編程技巧(引入空對象)來改善這一問題。
根據(jù)單一職責原則,一個類應該有明確的責任邊界。但在實際工作中,類會不斷的擴展。當給某個類添加一項新責任時,你會覺得不值得分離出一個單獨的類。于是,隨著責任不斷增加,這個類包含了大量的數(shù)據(jù)和函數(shù),邏輯復雜不易理解。
此時你需要考慮將哪些部分分離到一個單獨的類中,可以依據(jù)高內(nèi)聚低耦合的原則。如果某些數(shù)據(jù)和方法總是一起出現(xiàn),或者某些數(shù)據(jù)經(jīng)常同時變化,這就表明它們應該放到一個類中。另一種信號是類的子類化方式:如果你發(fā)現(xiàn)子類化只影響類的部分特性,或者類的特性需要以不同方式來子類化,這就意味著你需要分解原來的類。
繼承使實現(xiàn)代碼重用的有力手段,但這并非總是完成這項工作的最佳工具,使用不當會導致軟件變得很脆弱。與方法調(diào)用不同的是,繼承打破了封裝性。子類依賴于其父類中特定功能的實現(xiàn)細節(jié),如果父類的實現(xiàn)隨著發(fā)行版本的不同而變化,子類可能會遭到破壞,即使他的代碼完全沒有改變。
舉例說明,假設(shè)有一個程序使用HashSet,為了調(diào)優(yōu)該程序的性能,需要統(tǒng)計HashSet自從它創(chuàng)建以來添加了多少個元素。為了提供該功能,我們編寫一個HashSet的變體。
通過在新的類中增加一個私有域,它引用現(xiàn)有類的一個實例,這種設(shè)計被稱為組合,因為現(xiàn)有的類變成了新類的一個組件。這樣得到的類將會非常穩(wěn)固,它不依賴現(xiàn)有類的實現(xiàn)細節(jié)。即使現(xiàn)有的類添加了新的方法,也不會影響新的類。許多設(shè)計模式使用就是這種套路,比如代理模式、裝飾者模式
繼承與組合如何取舍
Java提供了兩種機制,可以用來定義允許多個實現(xiàn)的類型:接口和抽象類。自從Java8為接口增加缺省方法(default method),這兩種機制都允許為實例方法提供實現(xiàn)。主要區(qū)別在于,為了實現(xiàn)由抽象類定義的類型,類必須稱為抽象類的一個子類。因為Java只允許單繼承,所以用抽象類作為類型定義受到了限制。
接口相比于抽象類的優(yōu)勢:
接口雖然提供了缺省方法,但接口仍有有以下局限性:
接口缺省方法的設(shè)計目的和優(yōu)勢在于:
為了接口的演化
可以減少第三方工具類的創(chuàng)建
可以避免創(chuàng)建基類
由于接口的局限性和設(shè)計目的的不同,接口并不能完全替換抽象類。但是通過對接口提供一個抽象的骨架實現(xiàn)類,可以把接口和抽象類的優(yōu)點結(jié)合起來。 接口負責定義類型,或許還提供一些缺省方法,而骨架實現(xiàn)類則負責實現(xiàn)除基本類型接口方法之外,剩下的非基本類型接口方法。擴展骨架實現(xiàn)占了實現(xiàn)接口之外的大部分工作。這就是模板方法(Template Method)設(shè)計模式。
Image [5].png
接口Protocol:定義了RPC協(xié)議層兩個主要的方法,export暴露服務和refer引用服務
抽象類AbstractProtocol:封裝了暴露服務之后的Exporter和引用服務之后的Invoker實例,并實現(xiàn)了服務銷毀的邏輯
具體實現(xiàn)類XxxProtocol:實現(xiàn)export暴露服務和refer引用服務具體邏輯
由于為了保持Java代碼的兼容性,支持和原生態(tài)類型轉(zhuǎn)換,并使用擦除機制實現(xiàn)的泛型。但是使用原生態(tài)類型就會失去泛型的優(yōu)勢,會受到編譯器警告。
每一條警告都表示可能在運行時拋出ClassCastException異常。要盡最大的努力去消除這些警告。如果無法消除但是可以證明引起警告的代碼是安全的,就可以在盡可能小的范圍中,使用@SuppressWarnings("unchecked")注解來禁止警告,但是要把禁止的原因記錄下來。
參數(shù)化類型不支持協(xié)變的,即對于任何兩個不同的類型Type1和Type2而言,List既不是List的子類型,也不是它的超類。為了解決這個問題,提高靈活性,Java提供了一種特殊的參數(shù)化類型,稱作有限制的通配符類型,即List? extends E和List? super E。使用原則是producer-extends,consumer-super(PECS)。如果即是生產(chǎn)者,又是消費者,就沒有必要使用通配符了。
還有一種特殊的無限制通配符List?,表示某種類型但不確定。常用作泛型的引用,不可向其添加除Null以外的任何對象。
嵌套類(nested class)是指定義在另一個類的內(nèi)部的類。 嵌套類存在的目的只是為了它的外部類提供服務,如果其他的環(huán)境也會用到的話,應該成為一個頂層類(top-level class)。 嵌套類有四種:靜態(tài)成員類(static member class)、非靜態(tài)成員類(nonstatic member class)、匿名類(anonymous class)和 局部類(local class)。除了第一種之外,其他三種都稱為內(nèi)部類(inner class)。
總而言之,這四種嵌套類都有自己的用途。假設(shè)這個嵌套類屬于一個方法的內(nèi)部,如果只需要在一個地方創(chuàng)建實例,并且已經(jīng)有了一個預置的類型可以說明這個類的特征,就要把它做成匿名類。如果一個嵌套類需要在單個方法之外仍然可見,或者它太長了,不適合放在方法內(nèi)部,就應該使用成員類。如果成員類的每個實例都需要一個指向其外圍實例的引用,就要把成員類做成非靜態(tài)的,否則就做成靜態(tài)的。
通過對常見場景的代碼邏輯進行抽象封裝,形成相應的模板工具類,可以大大減少重復代碼,專注于業(yè)務邏輯,提高代碼質(zhì)量。
面向?qū)ο缶幊滔鄬τ诿嫦蜻^程,多了實例化這一步,而對象的創(chuàng)建必須要指定具體類型。我們常見的做法是“哪里用到,就在哪里創(chuàng)建”,使用實例和創(chuàng)建實例的是同一段代碼。這似乎使代碼更具有可讀性,但是某些情況下造成了不必要的耦合。
對于頂層的(非嵌套的)類和接口,只有兩種的訪問級別:包級私有的(沒有public修飾)和公有的(public修飾)。
對于成員(實例/域、方法、嵌套類和嵌套接口)由四種的訪問級別,可訪問性如下遞增:
正確地使用這些修飾符對于實現(xiàn)信息隱藏是非常關(guān)鍵的,原則就是:盡可能地使每個類和成員不被外界訪問(私有或包級私有)。這樣好處就是在以后的發(fā)行版本中,可以對它進行修改、替換或者刪除,而無須擔心會影響現(xiàn)有的客戶端程序。
不可變類是指其實例不能被修改的類。每個實例中包含的所有信息都必須在創(chuàng)建該實例時提供,并在對象的整個生命周期內(nèi)固定不變。不可變類好處就是簡單易用、線程安全、可自由共享而不容易出錯。Java平臺類庫中包含許多不可變的類,比如String、基本類型包裝類、BigDecimal等。
為了使類成為不可變,要遵循下面五條規(guī)則:
可變性最小化的一些建議:
TDD的最終目標是整潔可用的代碼(clean code that works)。大多數(shù)的開發(fā)者大部分時間無法得到整潔可用的代碼。辦法是分而治之。首先解決目標中的“可用”問題,然后再解決“代碼的整潔”問題。這與體系結(jié)構(gòu)驅(qū)動(architecture-driven)的開發(fā)相反。
采用TDD另一個好處就是讓我們擁有一套伴隨代碼產(chǎn)生的詳盡的自動化測試集。將來無論出于任何原因(需求、重構(gòu)、性能改進)需要對代碼進行維護時,在這套測試集的驅(qū)動下工作,我們代碼將會一直是健壯的。
Image [6].png
添加一個測試 - 運行所有測試并檢查測試結(jié)果 - 編寫代碼以通過測試 - 運行所有測試且全部通過 - 重構(gòu)代碼,以消除重復設(shè)計,優(yōu)化設(shè)計結(jié)構(gòu)
作者:VectorJin