重构36计
http://blog.csdn.net/m13666368773/article/details/7472201
重構(gòu),其實(shí)很簡(jiǎn)單,它的目的就是讓程序變得更容易被理解,更具有可維護(hù)性,結(jié)構(gòu)更合理。重構(gòu)應(yīng)該是我們平時(shí)寫(xiě)代碼過(guò)程中必不可少的一部分,比如給函數(shù)起了一個(gè)更好的名字、把大函數(shù)拆分成幾個(gè)小函數(shù)等都屬于重構(gòu)。重構(gòu)的經(jīng)典書(shū)籍包括Martin Flower的《重構(gòu)-改善既有代碼的設(shè)計(jì)》、Joshua Kerievsky的《重構(gòu)與模式》,本系列的所謂36計(jì)是我多年來(lái)使用最為頻繁的重構(gòu)策略和編碼準(zhǔn)則,有自己總結(jié)的,也有書(shū)上提到過(guò)的,希望對(duì)大家能有所幫助。
?第一計(jì):參數(shù)列表對(duì)象化
? 公有函數(shù)的參數(shù)應(yīng)盡可能保持不變,因?yàn)楹芏嗟胤蕉紩?huì)調(diào)用它,修改參數(shù)后需要修改它的調(diào)用處,另外,它的參數(shù)列表不宜過(guò)長(zhǎng),數(shù)量盡量保持在5個(gè)以內(nèi),長(zhǎng)參數(shù)列表會(huì)增加該函數(shù)的調(diào)用難度。對(duì)于參數(shù)較多或者參數(shù)經(jīng)常變化的公有函數(shù),較好的辦法是引入?yún)?shù)對(duì)象,即該函數(shù)的參數(shù)只有一個(gè),它就是參數(shù)對(duì)象,具體的參數(shù)都在該對(duì)象中聲明,為函數(shù)引入?yún)?shù)對(duì)象有以下幾個(gè)好處:
1、保持函數(shù)接口的不變性,修改函數(shù)參數(shù)只需修改參數(shù)對(duì)象中的成員變量。
2、調(diào)用方便,調(diào)用方不用再關(guān)心參數(shù)的順序。
以下代碼片段是一個(gè)添加用戶函數(shù)的聲明:
?
public long insertUser(String name,int age,String email,String address,String phone,String birthDay)?
每當(dāng)添加或刪除用戶的字段后都要修改insertUser的參數(shù)列表,調(diào)用者也需要修改,而且參數(shù)較多時(shí),不容易記憶。
以下是引入?yún)?shù)對(duì)象后的形式:
?
public class UserParam{ public String name; public int age; public String email; public String address; public String phone; public String birthDay; }public long insertUser(UserParam user);?
第二計(jì):條件運(yùn)算符賦值代替if else賦值
??對(duì)于根據(jù)條件為變量賦值的情況,可以有兩種方式,一種是通過(guò)if-else:
?
int value; if(condition)value = 1; elsevalue = 2;?
? 另一種是通過(guò)條件運(yùn)算符:
?
int value = condition ? 1 : 2;?
? 第二種方式明顯要比第一種方式好,但是很多人卻鐘愛(ài)第一種方式,可能是if-else習(xí)慣了。
?
第三計(jì):節(jié)約使用系統(tǒng)資源
?即使在寫(xiě)代碼時(shí),我們也應(yīng)該養(yǎng)成“節(jié)儉”的習(xí)慣,不要隨便浪費(fèi)系統(tǒng)提供的資源,對(duì)于那些較占用空間、影響性能的對(duì)象,應(yīng)該直到真正要用的時(shí)候才創(chuàng)建或者初始化,因此在提供這些對(duì)象的函數(shù)實(shí)現(xiàn)中,盡量采用如下形式:
?
// 管理數(shù)據(jù)庫(kù)連接的類 public class DataBaseConnectionHolder{private Connection conn;public Connection getConnection(){if(conn == null){conn = new Connection();conn.init(); }return conn;}}?
另外,我們可以通過(guò)引入緩存機(jī)制(如對(duì)象池)來(lái)充分利用系統(tǒng)資源,可以參看這篇文章:GoF著作中未提到的設(shè)計(jì)模式(5):Object Pool?第四計(jì):為接口引入抽象版本
??在聲明一個(gè)新的接口時(shí),不能保證該接口不會(huì)被修改,有可能會(huì)經(jīng)常修改它,每一次修改接口都要修改相應(yīng)的實(shí)現(xiàn)類,如果某個(gè)接口是公共庫(kù)的一部分,那么修改接口的代價(jià)是較大的,用到該接口的所有程序都需要重新修改、編譯...,通過(guò)為接口引入抽象版本可以解決這個(gè)問(wèn)題,例如為下面的接口增加一個(gè)抽象類:
?
public interface Widget{public void draw();public void layout(); public void invalidate();public void show(); }?
public abstract class AbstractWidget implements Widget{public abstract void draw();public void layout(){};public void invalidate(){};public void show(){}; }?
?這樣Widget的實(shí)現(xiàn)類可以直接從AbstractWidget繼承,如果要修改Widget接口,則只需要修改AbstractWidget即可,對(duì)于其他實(shí)現(xiàn)類沒(méi)有影響。
?
第五計(jì):消滅魔法數(shù)
??編程新手一般都會(huì)直接將表示類型或狀態(tài)的數(shù)字直接寫(xiě)在處理邏輯中,代碼的作者能明白該數(shù)字所表示的含義,但其他人讀到這段代碼時(shí)就很有可能看不懂了。即使代碼的作者再過(guò)一段時(shí)間來(lái)看這部分代碼,也可能會(huì)忘記該數(shù)字的含義,而且,當(dāng)我們要修改魔法數(shù)的值時(shí),過(guò)程是很繁瑣的,很有可能會(huì)有所遺漏,所以,最好的辦法是徹底消滅程序中的所有魔法數(shù),通過(guò)常量定義、枚舉等方式來(lái)避免魔法數(shù)的出現(xiàn)。
?
第六計(jì):使用斷言、異常確保實(shí)現(xiàn)的正確性
??使用斷言的目的是告知其他程序員代碼中某處必須要遵守的規(guī)矩,它是debug版本中的一種確保程序?qū)崿F(xiàn)正確性的手段,在正式發(fā)布的版本中,斷言是不起作用的。在java中,啟用斷言需要增加一個(gè)編譯選項(xiàng),不過(guò)可以通過(guò)拋出異常來(lái)達(dá)到相同目的,使用異常比斷言要危險(xiǎn),因?yàn)樵诔绦虻恼桨l(fā)布版本中會(huì)引起崩潰,不過(guò)有時(shí)候崩潰總比程序的詭異行為更好,例如:
??
// 表示集合的類 public class Collection{// 添加元素到集合中public void addElement(Element e){};// 獲取指定位置的元素public void getElement(int index){}; }// 表示只讀集合的類 public class ReadOnlyCollection extends Collection{// 添加元素到集合中public void addElement(Element e){throw new UnsupportedOperationException("只讀集合,不允許添加元素");}// 獲取指定位置的元素public void getElement(int index){}; }?調(diào)用ReadOnlyColletion派生類必須遵守規(guī)矩:不能調(diào)用addElement,否則拋出異常干掉程序!
?
第七計(jì):串聯(lián)函數(shù)調(diào)用
當(dāng)一個(gè)類的大部分函數(shù)被較為頻繁地調(diào)用,并且包含連續(xù)性地調(diào)用,那么可以考慮為這個(gè)類中那些沒(méi)有返回值的函數(shù)增加返回值,即返回對(duì)象本身,這樣就可以串聯(lián)函數(shù)調(diào)用,使用起來(lái)較為方便,舉個(gè)例子:
// 表示用戶的類 public class User{public void setName(String name);public void setAge(int age);public void setPhoneNumber(int phoneNumber); }下面是不使用串聯(lián)函數(shù)調(diào)用的情況:
?
User user = new User(); user.setName("West_Link"); user.setAge(3); user.setPhoneNumber(122333);?
下面是使用串聯(lián)函數(shù)調(diào)用的情況:
?
User user = new User().setName("West_Link").setAge(3).setPhoneNumber(123333);?
只需要為那些函數(shù)增加一個(gè)User對(duì)象的返回值即可,如下:
public User setName(String name){ this.name = name; return this; }?
第八計(jì):臨時(shí)變量在用到時(shí)才聲明
?很多人喜歡在函數(shù)的開(kāi)頭把所有要用到的臨時(shí)變量都聲明了,我認(rèn)為這種方式有以下幾個(gè)缺點(diǎn):
1、不利于代碼的閱讀,需要經(jīng)常在變量的使用處和變量的聲明處跳轉(zhuǎn),不方便。
2、容易造成資源的浪費(fèi),因?yàn)橛行?duì)象的初始化是比較耗費(fèi)資源的,而函數(shù)可能在用到該對(duì)象之前返回。
3、不利于函數(shù)的拆分。
所以,我們應(yīng)該盡可能降低臨時(shí)變量的作用域,那樣它能“搗亂“的范圍就被縮至最小了。
?
第九計(jì):保持類的公有函數(shù)粒度最小化
? 一個(gè)類的公有函數(shù)不應(yīng)該過(guò)多,那樣會(huì)使類變得臃腫、不易使用,我認(rèn)為最佳狀態(tài)是絕大部分公有函數(shù)不能被拆分,也就是說(shuō),不存在那些通過(guò)其他公有函數(shù)也能達(dá)到目的的函數(shù),例如下面的類:
?
public class StringArray{// 獲取數(shù)組的大小public int getSize();// 判斷數(shù)組是否為空public boolean isEmpty();// 將數(shù)據(jù)添加到數(shù)組的某個(gè)索引public void add(String value,int index);// 將數(shù)據(jù)添加到數(shù)組的末尾public void addToLast(String value);// 將數(shù)據(jù)添加到數(shù)組的起始位置public void addToFirst(String value); }?
StringArray其實(shí)只需要兩個(gè)公有函數(shù)即可,即getSize和add,因?yàn)閕sEmpty可以通過(guò)getSize來(lái)達(dá)到相同的目的:getSize() == 0。類似地,addToLast、addToFirst也可以通過(guò)add來(lái)實(shí)現(xiàn)。不過(guò),如果類的公有函數(shù)比較少,而且類似isEmpty類似的需求經(jīng)常被用到,那么保留這些公有函數(shù)還是值得的。
?
第十計(jì):將可訪問(wèn)性降至最低
? 面向?qū)ο笾械姆庋b性使得我們可以隱藏類或者接口的實(shí)現(xiàn)細(xì)節(jié),所以,為了讓程序更易維護(hù)、接口或者類的使用更加簡(jiǎn)單,我們應(yīng)該盡可能降低成員變量或者成員函數(shù)的可訪問(wèn)性,輔助函數(shù)一定要聲明為私有的,確保只將接口函數(shù)聲明為公有的,如果該輔助函數(shù)可能會(huì)被子類用到,則可以聲明為保護(hù)的。輔助類也應(yīng)聲明為私有的,對(duì)于成員變量則一定要聲明為私有的,只提供必要的set或者get函數(shù)。總之,當(dāng)我們?cè)黾有碌念悺⒊蓡T變量、成員函數(shù)時(shí),一定要合理地設(shè)置可訪問(wèn)性,暴露給外界的越少越好。
?
第十一計(jì):合并條件減少嵌套
? 條件判定如果嵌套的太多會(huì)大大降低程序的可讀性,很容易滋生Bug,例如:
? ?
if(a){if(b || c){if(d){...}} }?可以合并成:
if(a && (b || c) && d){... }?
?
第十二計(jì):循環(huán)中早用continue減少條件嵌套
?除了通過(guò)合并條件來(lái)減少嵌套層次外,在for或者while循環(huán)中,可以用continue來(lái)減少條件嵌套,例如:
?
for(int i=0; i<100;i++){if(a){if(b){if(c){...}}} }?
這段代碼的嵌套深度是4,使用continue可以大大減少嵌套層次:
?
for(int i=0; i<100;i++){ if(!a) continue; if(!b) continue; if(!c) continue; ...}
?
第十三計(jì):為集合類型的成員變量提供增刪改查函數(shù)
??對(duì)于集合類型的成員變量,直接提供一個(gè)函數(shù)將其暴露出去是不夠妥當(dāng)?shù)?#xff0c;缺點(diǎn)包括以下幾點(diǎn):
? 1、添加集合元素時(shí)無(wú)法校驗(yàn)它的合法性,例如類型是否符合要求、該元素是否已存在等。
? 2、無(wú)法控制集合的容量,集合的容量可能是動(dòng)態(tài)分配的。
? 所以,更好的做法是為集合類型的成員變量提供增刪改查等函數(shù),例如某個(gè)類中有名為userList的數(shù)組,則可以為它增加幾個(gè)函數(shù):
?
public void add(User user){if(user == null) // 如果對(duì)象為不合法,則不添加return;if(userList == null) // 如果集合還未初始化,則新建userList = new ArrayList();if(isUserExisted(user)) // 如果該用戶已經(jīng)存在,則不添加return;// 將該用戶添加到集合中 userList.add(user); }public void delete(User user){if(userList == null)return;userList.remove(user); }?
第十四計(jì):避免一個(gè)臨時(shí)變量充當(dāng)多種角色
? 當(dāng)在函數(shù)中聲明一個(gè)臨時(shí)變量的時(shí)候,其實(shí)已經(jīng)設(shè)定了該變量的角色,這或多或少能從它的命名中看出來(lái),例如下面變量的聲明:
?
String userName = null;???
? 可以看出這個(gè)臨時(shí)變量是存儲(chǔ)用戶名用的,有些人認(rèn)為聲明多個(gè)臨時(shí)變量會(huì)浪費(fèi)空間,所以在函數(shù)中會(huì)多次使用該變量,例如用這個(gè)變量存儲(chǔ)了用戶的密碼:
?
userName = “*******”;??
?這種方式很容易引入Bug,而且降低了可理解性。因此,一個(gè)變量應(yīng)該只充當(dāng)一種角色。
?
第十五計(jì):引入NULL Object來(lái)避免大量的對(duì)象合法性判斷
??當(dāng)我們獲得對(duì)象的引用后,在調(diào)用該對(duì)象的函數(shù)前一般都會(huì)檢查它是否為NULL來(lái)避免程序崩潰,這樣也會(huì)導(dǎo)致程序中出現(xiàn)大量類似下面的代碼段:
??
EventRecorder recorder = EventRecorderFactory.getRecorderByType(0); if( recorder ==null ){Log.error("Recorder對(duì)象為空");lastErrorCode =0; } else{recorder.record("記錄點(diǎn)啥..."); }?
而NULL Object模式則可以避免這種情況,具體內(nèi)容請(qǐng)參見(jiàn):GoF著作中未提到的設(shè)計(jì)模式(3):Null Object
?
第十六計(jì):函數(shù)命名有語(yǔ)法
??大部分函數(shù)的命名盡量采用動(dòng)詞+名詞的形式,并使其具有自注釋性,例如:findUserById,從函數(shù)名中不僅能看出函數(shù)的功能,甚至連參數(shù)也能猜出來(lái),另外,有些命名方式是有一定意義的,例如作為回調(diào)的函數(shù)一般以on開(kāi)頭,如:onUserPasswordChanged,說(shuō)明該函數(shù)會(huì)在用戶密碼變化時(shí)被調(diào)用。對(duì)于返回布爾值的函數(shù)盡量采用疑問(wèn)句式,如:isNameValid。
?
第十七計(jì):去除只是內(nèi)部狀態(tài)不同的派生類
??當(dāng)某些派生類與父類相比只是狀態(tài)不同時(shí),那就應(yīng)該考慮去掉這些派生類,把這些狀態(tài)作為父類的成員變量,并且可以為原來(lái)派生類所表示的對(duì)象準(zhǔn)備一些構(gòu)造函數(shù)或者工廠方法,例如下面表示員工的類:
?
public abstract class Employee{private int id;private String name;// 獲取薪水public abstract int getSalary();// 獲取是否有解雇員工的權(quán)利public abstract boolean canFireOthers(); }public class Programmer extends Employee{public int getSalary(){return 5000;}public boolean canFireOthers(){return false;} }public class Manager extends Employee{public int getSalary(){return 10000;}public boolean canFireOthers(){return true;} }可以看出,Employee的派生類實(shí)質(zhì)上只是狀態(tài)與父類不同,應(yīng)該將它們合并為一個(gè)類:
??
public class Employee{private int id;private String name;private int salary;private boolean canFireOthers;public static Employee newProgrammer(String name){return new Employee(name,5000,false);}public static Employee newManager(String name){return new Employee(name,10000,true);}public Employee(String name,int salary,boolean canFireOthers){this.name = name;this.salary = salary;this.canFireOthers = canFireOthers;}// 獲取薪水public int getSalary(){return salary;}// 獲取是否有解雇員工的權(quán)利public boolean canFireOthers(){return canFireOthers;} }?
第十八計(jì):少用標(biāo)記變量
??標(biāo)記變量一般都是布爾類型的變量,主要用來(lái)在某類事件或者操作發(fā)生后做個(gè)標(biāo)記,然后其他地方會(huì)用到這個(gè)標(biāo)記,用完之后很可能會(huì)重置這個(gè)標(biāo)記到初始狀態(tài)。少量并恰當(dāng)合理地使用標(biāo)記變量可以達(dá)到很好的效果,能解決一些難題,不過(guò),用的多了就會(huì)出亂子,尤其是一個(gè)類中有多個(gè)成員變量是標(biāo)記變量,成員函數(shù)的實(shí)現(xiàn)不得不“看它們的眼色行事“了,它們所產(chǎn)生的標(biāo)記值的組合會(huì)讓實(shí)現(xiàn)者越來(lái)越頭大,它們的同時(shí)存在增加了程序的復(fù)雜性。所以,當(dāng)類中出現(xiàn)多個(gè)標(biāo)記變量而隱約感覺(jué)到不對(duì)頭時(shí),應(yīng)該果斷干掉它們,然后認(rèn)真分析處理邏輯是否存在問(wèn)題,避免再次引入標(biāo)記變量。
?
?
第十九計(jì):避免類的臃腫
??在我接觸過(guò)的大部分項(xiàng)目中,總會(huì)有一個(gè)“大胖子“類特別惹眼,一般來(lái)說(shuō),這“大胖子“實(shí)際上是整個(gè)系統(tǒng)的核心類之一,之所以“胖“,主要原因是很多人都會(huì)把自己需要的函數(shù)加到這個(gè)類中,卻沒(méi)有人主動(dòng)請(qǐng)纓來(lái)為它“減肥“。可以通過(guò)以下幾種方式來(lái)為它“瘦身”:
??1、按照某種特性(如功能、類型等)將這個(gè)類拆分成多個(gè)類。
? 2、合并冗余函數(shù),保持函數(shù)粒度的最小化。
? 3、去除重復(fù)代碼。
??如果實(shí)在不能再“瘦”了,那就通過(guò)實(shí)現(xiàn)相應(yīng)的接口,讓它“看上去很瘦“,舉個(gè)例子:
??
public class BigBoy{public void foo1();public void foo2();public void foo3();public void foo4();public void foo5();public void foo6();public void foo7();public void foo8();public void foo9();// 還有很多... }?
這個(gè)類有很多函數(shù),這讓類的使用者很頭疼,沒(méi)辦法,它不能再“瘦“了,不過(guò),我們可以根據(jù)某種特性把這些函數(shù)抽象成多個(gè)接口,例如foo1、foo2、foo3可以抽象成一個(gè)接口:
public interface LittleBoy{public void foo1();public void foo2();public void foo3(); }?
然后讓BigBoy實(shí)現(xiàn)這個(gè)接口,并提供一個(gè)將BigBoy變成LitterBoy的函數(shù):
?
?
public class BigBoy implements LittleBoy{public LittleBoy asLittleBoy{)return this;}public void foo1();public void foo2();public void foo3();public void foo4();public void foo5();public void foo6();public void foo7();public void foo8();public void foo9();// 還有很多... }?
這樣,類的使用者得到將是“瘦版“的BigBoy,使用難度大大降低了,因此,對(duì)于需要使用該類所有函數(shù)中某個(gè)子集的用戶,我們可以提供一個(gè)包含該函數(shù)子集的接口實(shí)現(xiàn)對(duì)象即可。
?
第二十計(jì):保持代碼風(fēng)格的一致性
??程序員或多或少都有各自的代碼風(fēng)格,當(dāng)我們看和自己風(fēng)格不同的人寫(xiě)的代碼時(shí)都覺(jué)得有點(diǎn)別扭,甚至?xí)绊懽x代碼的流暢性,記得以前有個(gè)同事把for語(yǔ)句當(dāng)if語(yǔ)句用,像下面這樣,太個(gè)性了,看他的代碼真有點(diǎn)頭疼。
??
for(;value>10;){... }整個(gè)程序保持一致的代碼風(fēng)格還是比較重要的,如果看著就像一個(gè)人寫(xiě)那就太到位了,所以,最好能在項(xiàng)目初期就統(tǒng)一程序的命名規(guī)范、通過(guò)邏輯處理規(guī)范、注釋規(guī)范等。
?
第二十一計(jì):成員變量要封裝
??大部分情況下,類中的成員變量都應(yīng)該被聲明為私有的,為那些需要被其他類訪問(wèn)的變量增加set或者get函數(shù)。如果成員變量聲明為公有的,那么類就失去了對(duì)它們的控制權(quán)!封裝成員變量有以下幾個(gè)好處:
? 1、在為成員變量賦值時(shí)進(jìn)行有效性校驗(yàn)或其他預(yù)處理操作。
? 2、在返回成員變量的值時(shí)進(jìn)行二次包裝,當(dāng)該變量不可用或未初始化時(shí)返回默認(rèn)值。
? 為成員變量分別增加set和get函數(shù)是一件挺繁瑣的事情,很多人嫌麻煩,覺(jué)得公有成員變量更方便,不過(guò)從可維護(hù)性和可擴(kuò)展性的角度看,添加set和get是值得的,Eclipse中可以自動(dòng)生成set和get函數(shù)的,非常方便。
?
第二十二計(jì):用自注釋性變量代替復(fù)雜條件
??嵌套層次較深的IF判斷、沒(méi)有注釋的復(fù)雜條件大大增加了程序的邏輯復(fù)雜性,嚴(yán)重降低代碼的可讀性。對(duì)于子條件較多的條件判定,可以為各個(gè)子條件引入具有自注釋性的臨時(shí)變量來(lái)降低復(fù)雜性。例如下面是判斷登錄用戶是否能進(jìn)入網(wǎng)站的后臺(tái)管理界面:
??
if(userName!=null && userName.equals(name) && (userState != INACTIVE || userState != DELETE) && userPassword!=null && userPassword.equals(password) && (userGroup == "Manager" || userGroup == "Root")){... }?
?
? 下面是修改后的版本:
boolean isUserNameValid = userName!=null && userName.equals(name); boolean isUserActive = userState != INACTIVE || userState != DELETE;boolean isUserPasswordCorrect = userPassword!=null && userPassword.equals(password);boolean isUserHasAuth = userGroup == "Manager" || userGroup == "Root";if(isUserNameValid && isUserActive && isUserPasswordCorrect && isUserHasAuth){...}?
第二種方式不僅使條件判斷更具有可讀性,還能重用子條件。
?
第二十三計(jì):避免重復(fù)代碼
??重復(fù)代碼是破壞程序可維護(hù)性的重量級(jí)選手之一,大量的重復(fù)代碼會(huì)使代碼量膨脹,修改重復(fù)的代碼也很繁瑣,改了一處后必須同時(shí)修改和它重復(fù)的代碼,因此非常引入Bug,當(dāng)有人修改了某處代碼而忘記修改其他除重復(fù)的代碼,那么Bug就出現(xiàn)了。所以,一旦要拷貝某段代碼,請(qǐng)先考慮把這段代碼通用化。
?
第二十四計(jì):增加注釋
??記得在第一個(gè)公司工作的時(shí)候,公司很多程序員的代碼注釋率在40%左右,一般都是先寫(xiě)注釋,然后緊接著寫(xiě)代碼,因?yàn)樽⑨屢彩且环N文檔。很多人覺(jué)得寫(xiě)注釋浪費(fèi)時(shí)間或者沒(méi)有必要,所以他們的代碼中沒(méi)有綠色,或者只是星星點(diǎn)點(diǎn),如果代碼有一定的復(fù)雜性,那么其他人看這部分代碼可能會(huì)比代碼作者要費(fèi)勁的多,注釋就是幫助別人快速理解自己寫(xiě)的代碼。
第二十五計(jì):函數(shù)體最多不超過(guò)100行
??記得以前看過(guò)一個(gè)函數(shù)有9000多行,很壯觀啊,從那以后看到長(zhǎng)函數(shù)時(shí)也不奇怪了,我認(rèn)為過(guò)長(zhǎng)函數(shù)的主要缺點(diǎn)是:
1、嚴(yán)重影響代碼的閱讀,使用到某個(gè)變量的地方可能間隔幾百甚至上千行,如果if-else嵌套層次較多的話那就更噩夢(mèng)了。
2、不利于代碼的重用,短小而獨(dú)立的邏輯處理單元更容易被重用,而過(guò)長(zhǎng)的代碼段則需要經(jīng)過(guò)進(jìn)一步分解才行。
我覺(jué)得函數(shù)最好不要超過(guò)100行,對(duì)于過(guò)長(zhǎng)的函數(shù)要盡可能地進(jìn)行分解,如果實(shí)在不能分解,那么就通過(guò)注釋的方式增加該函數(shù)處理步驟的說(shuō)明,例如:
?
public void foo(){// 1、驗(yàn)證參數(shù)、內(nèi)部狀態(tài)的有效性 ... // 2、開(kāi)始傾斜角度 ... // 2.1 計(jì)算角度1 ... // 2.2 計(jì)算角度2 ... // 3、輸出計(jì)算說(shuō)明書(shū) ... }?
第二十六計(jì):使用語(yǔ)言的修飾符確保變量的不可變性
??當(dāng)聲明一個(gè)變量時(shí),如果能十分確定該變量不會(huì)被修改或者不應(yīng)該被修改,那最好把它聲明為不可變的,如使用Java中的final、C++中的const修飾符,這樣可以防止本該不變的變量被意外地修改。
?
第二十七計(jì):對(duì)象狀態(tài)共享
??大量對(duì)象的同時(shí)存在會(huì)占用系統(tǒng)寶貴的內(nèi)存,如果這些對(duì)象中某些狀態(tài)是相同的,那么可以將這些狀態(tài)提取出來(lái)讓所有需要它的對(duì)象共享,這可以大大減少冗余對(duì)象,從而達(dá)到節(jié)省內(nèi)存的目的,設(shè)計(jì)模式中的Flyweight模式可以解決這個(gè)問(wèn)題。
?
第二十八計(jì):用對(duì)象代替普通常量
??由于普通常量本質(zhì)上是一個(gè)簡(jiǎn)單的數(shù)字或者字符串,當(dāng)我們錯(cuò)誤地將某個(gè)類別的常量在另一個(gè)類別的常量的場(chǎng)景中使用時(shí),就會(huì)產(chǎn)生問(wèn)題,但是編譯器并不會(huì)提示有錯(cuò)誤,所以,這可能是一個(gè)不小的隱患,例如:
?
// 表示用戶狀態(tài)的常量聲明 public static int USER_STATE_ACTIVE = 0; public static int USER_STATE_DELETE = 1;// 表示用戶角色的常量聲明 public static int USER_ROLE_NORMAL = 2; public static int USER_ROLE_MANAGER = 3;// 下面用戶是否被激活的判斷 if(userState == USER_ROLE_NORMAL){}這個(gè)判斷本應(yīng)該使用USER_STATE_ACTIVE和USER_STATE_DELETE兩個(gè)常量之一,卻意外地使用了其他常量,可能直到Bug產(chǎn)生后才能被發(fā)現(xiàn)。
可以使用對(duì)象常量來(lái)避免這種情況,例如:
public class State{private int state;public State(int s){state = s;} }// 表示用戶狀態(tài)的常量聲明 public static State USER_STATE_ACTIVE = new State(0); public static State USER_STATE_DELETE = new State(1);public class Role{ private int role; public Role(int r){role = r;} }// 表示用戶角色的常量聲明 public static Role USER_ROLE_NORMAL = new Role(2); public static Role USER_ROLE_MANAGER = new Role(3);?
下面的判斷是無(wú)法通過(guò)編譯的,因?yàn)閡serState是State類型的。
if(userState == USER_ROLE_NORMAL){}?
第二十九計(jì):查詢函數(shù)中盡量不要有修改操作
??我們一般都是根據(jù)函數(shù)的名字來(lái)判斷它的功能,“表里不一“的函數(shù)可能會(huì)引起一些問(wèn)題,例如我們調(diào)了一個(gè)查詢函數(shù)(獲取類的成員變量值的函數(shù)):getName(),但是它內(nèi)部卻修改了其他成員變量的值,當(dāng)查找Bug的原因時(shí),很可能會(huì)忽略這個(gè)函數(shù),從它的名字看,覺(jué)得它不會(huì)引起問(wèn)題,到最后發(fā)現(xiàn)就是它搗的鬼,心里估計(jì)會(huì)罵這個(gè)函數(shù)的作者:他奶奶的,代碼如其人,表里不一!
?
第三十計(jì):盡量封裝對(duì)象的創(chuàng)建過(guò)程
??本文之前曾提到過(guò)要盡量為成員變量增加set和get函數(shù),主要目的是為了掌握成員變量的控制權(quán),對(duì)象的創(chuàng)建過(guò)程也是如此,如果提供者掌握了對(duì)象創(chuàng)建過(guò)程的控制權(quán),那么就可以屏蔽具體的實(shí)現(xiàn)類,并且任意修改對(duì)象的創(chuàng)建過(guò)程。?
第三十一計(jì):置空不用的對(duì)象
在C++中,銷(xiāo)毀一個(gè)對(duì)象后,一定要把指針置為NULL,否則會(huì)出現(xiàn)野指針,最好寫(xiě)成下面這樣,delete后立馬置為NULL,
delete pObject; pObject = NULL;在Java中,當(dāng)不再需要一個(gè)對(duì)象時(shí),最好能把它置為null,這樣有利于垃圾回收。
?
第三十二計(jì):善于利用接口
? 1、?回調(diào)型接口
? ??在C語(yǔ)言中,回調(diào)函數(shù)可以通過(guò)函數(shù)指針來(lái)實(shí)現(xiàn),Java中沒(méi)有指針的概念,可以利用接口來(lái)達(dá)到同樣的目的,例如:
? ??
public interface Callback{public void onChanged();}public void execute(Callback callback){...callback.onChanged();...}?
?2、標(biāo)記型接口
這種類型的接口中不包含函數(shù)的聲明,即接口是空的,主要用來(lái)讓實(shí)現(xiàn)這個(gè)接口的類表明自身具有某種特性,起一個(gè)標(biāo)記的作用,例如下面的接口:
public interface Millionaire{}public class Member extends User implements Millionaire{...}public boolean check(User user){if(user instanceOf Millionaire) // 這是百萬(wàn)富豪,直接讓它passreturn true;...}?
? 3、依賴注入型接口
? ? 這種接口一般用在工廠方法中,有選擇性地為要?jiǎng)?chuàng)建的對(duì)象注入對(duì)象引用或者數(shù)據(jù),例如:
?
?
?4、常量型接口
? 接口中定義的全是常量,這樣,相關(guān)類都可以實(shí)現(xiàn)這個(gè)接口,相當(dāng)于把這些常量都定義在了這些類中,而其他類則可以通過(guò)常量接口來(lái)引用這些常量
?
?第三十三計(jì):簡(jiǎn)化類關(guān)系
? ?如果類之間的關(guān)系比較復(fù)雜,那么很可能是面向?qū)ο蟮脑O(shè)計(jì)沒(méi)有做好。一般來(lái)說(shuō),兩個(gè)類之間有雙向調(diào)用關(guān)系則說(shuō)明它們?cè)诼氊?zé)上的分配上不夠清晰,例如,一個(gè)類(A)頻繁地調(diào)用另一個(gè)類(B)的函數(shù),而且A還把成員變量作為參數(shù)傳遞給B,那么大部分情況下就應(yīng)該把那個(gè)函數(shù)定義在A中,從而切斷兩者之間的關(guān)系。另外,系統(tǒng)中有很多類對(duì)某些類的狀態(tài)變化或者事件感興趣,例如添加一個(gè)新用戶、網(wǎng)絡(luò)狀態(tài)的變化等,對(duì)于這樣的需求,最好通過(guò)各種消息機(jī)制來(lái)達(dá)到它們之間的解耦,如觀察者模式、發(fā)布訂閱模式等。發(fā)布訂閱模式可以參見(jiàn)這篇文章:GoF著作中未提到的設(shè)計(jì)模式(7):Publish-Subscribe
?
?第三十四計(jì):用多態(tài)替換相似條件式
? 當(dāng)一個(gè)類中有些函數(shù)的處理邏輯很相似,都是根據(jù)某個(gè)狀態(tài)值或者某個(gè)類型值而采取不同的行為,那么這個(gè)類就需要被拆分成多個(gè)類了,例如有一個(gè)表示形狀的類:?
??
public class Shape{private int type;public void draw(){if(type == 0) // 如果是矩形 drawRect();else if(type == 1) // 如果是圓形 drawCircle();else if(type == 2) // 如果是直線 drawLine();} public boolean isPointIn(float x, float y){if(type == 0) // 如果是矩形return isPointInRect(x,y);else if(type == 1) // 如果是圓形return isPointInCircle(x,y);else if(type == 2) // 如果是直線return isPointInLine();return false;} }這兩個(gè)成員函數(shù)內(nèi)部包含相似的處理邏輯,都是根據(jù)type的值做相應(yīng)的處理,對(duì)于這種情況,應(yīng)該充分利用面向?qū)ο笾械亩鄳B(tài)性,下面是拆分成多個(gè)類的形式:
?
public abstract class Shape{public abstract void draw();public abstract boolean isPointIn(x,y); }public class Circleextends Shape{public void draw(){drawCircle();}public abstract boolean isPointIn(x,y){isPointInCircle(x,y);} }public class Rect extends Shape{public void draw(){drawRec();}public abstract boolean isPointIn(x,y){isPointInRect(x,y);} }public class Line extends Shape{public void draw(){drawLine();}public abstract boolean isPointIn(x,y){isPointInLine(x,y);} }?
?
第三十五計(jì):合理分層,分離界面顯示和業(yè)務(wù)處理邏輯
代碼的低耦合是保障軟件可維護(hù)性的關(guān)鍵因素之一,而分層是實(shí)現(xiàn)代碼低耦合的常用手段,也可以實(shí)現(xiàn)人員的有效分工,例如在企業(yè)級(jí)的Web開(kāi)發(fā)中,一般都會(huì)劃分出以下幾個(gè)層次:
1、數(shù)據(jù)訪問(wèn)層,完成數(shù)據(jù)庫(kù)的增刪改查,一般都使用ORM框架來(lái)實(shí)現(xiàn),例如Hibernate、LINQ等。
2、業(yè)務(wù)服務(wù)層,完成所有的業(yè)務(wù)處理邏輯
3、請(qǐng)求控制層,處理客戶端請(qǐng)求,將業(yè)務(wù)交給服務(wù)層處理,把處理結(jié)果以某種形式(HTML、JSON等)返回給客戶端。
在本地應(yīng)用程序的實(shí)現(xiàn)中也會(huì)有類似的分層,例如MFC中的文檔-視圖結(jié)構(gòu)也是界面顯示和業(yè)務(wù)處理邏輯的分離,所以在代碼中,應(yīng)該盡量避免界面對(duì)象和業(yè)務(wù)對(duì)象的交叉引用,尤其是對(duì)于有多種界面呈現(xiàn)方式的系統(tǒng)中。
?
第三十六計(jì):判斷參數(shù)有效性
? ?函數(shù)參數(shù)有效性的判斷是函數(shù)實(shí)現(xiàn)中很重要的一部分,尤其是作為類的對(duì)外接口的公有成員函數(shù),例如判斷參數(shù)中的對(duì)象是否為null、參數(shù)值是否合法等,這些工作應(yīng)該在函數(shù)實(shí)現(xiàn)的最開(kāi)始處完成,發(fā)現(xiàn)參數(shù)不合法時(shí)可以直接返回、拋出異常或者提供參數(shù)默認(rèn)值,一定不要通過(guò)非代碼的方法(比如調(diào)用約定)來(lái)保證參數(shù)的的有效性,斷言也不能完全保證函數(shù)執(zhí)行時(shí)用到的參數(shù)都是合法的,除非在程序發(fā)布時(shí)所有的公有函數(shù)都能被執(zhí)行到,因此,參數(shù)有效性的判斷是函數(shù)實(shí)現(xiàn)中必不可少的部分。
?
總結(jié)
- 上一篇: Servlet 3.0 新特性详解
- 下一篇: java.sql.SQLExceptio