Duplicated Code(重復代碼)
如果你在一個以上的地點看到相同的程序結(jié)構(gòu),那么可以肯定:設(shè)法將它們合而為一,程序會變得更好狂芋。
-
場景1:
同一個類中,兩個函數(shù)含有相同的表達式-->將相同的表達式抽出一個方法
-
場景2:
兩個具有相同父類的子類內(nèi)含有相同的表達式-->將相同的表達式憨栽,放入父類中帜矾。
如果兩個子類中,有類似的表達式屑柔,那么將相同的部分放入父類中屡萤,將差異的部分由子類實現(xiàn)。這也是一種模板方法的設(shè)計模式掸宛。
-
場景3:
如果兩個毫無相關(guān)的類出現(xiàn)重復代碼死陆,可以考慮將重復代碼提煉到一個獨立的類中。通過組合的方式交給使用方唧瘾。
Long Method(過長函數(shù))
程序越長越難理解措译。
你應該更積極地分解函數(shù)。我們遵循這樣一條原則:每當感覺需要以注釋來說明點什么的時候饰序,我們就把需要說明的東西寫進一個獨立函數(shù)中领虹,并以其用途命名。
關(guān)鍵點不在于函數(shù)的長度菌羽,而在于函數(shù)“做什么”和“如何做”之間的語義距離掠械。
Large Class(過大的類)
如果用單個類做太多事情,其內(nèi)往往就會出現(xiàn)太多實例變量注祖,一旦如此猾蒂,Duplicated Code也就接踵而至了。
Long Parameter List(過長參數(shù)列)
"把函數(shù)所需的所有東西都以參數(shù)傳遞進去是晨,否則你就只能選擇全局數(shù)據(jù)"肚菠。
全局數(shù)據(jù)是邪惡的東西,它可能會引導你的程序走向錯誤罩缴。
"如果你手上沒有所需的東西蚊逢,可以叫另一個對象給你"层扶。
通過對象傳遞參數(shù),要比太長的參數(shù)列要好理解烙荷;太多參數(shù)會造成前后不一致镜会、不易使用,而且一旦你需要更多的數(shù)據(jù)终抽,就不得不修改它戳表。
Divergent Change(發(fā)散式變化)
我們希望軟件能夠更容易被修改。
一旦需要修改昼伴,我們希望能夠跳到系統(tǒng)的某一點匾旭,只在該處做修改。
如果不能做到這一點圃郊,你就嗅出兩種緊密相關(guān)的刺鼻味道中的一種了价涝。
如果某個類經(jīng)常因為不同的原因在不同的方向上發(fā)生變化,Divergent Change就出現(xiàn)了持舆。
針對某一外界變化的所有相應修改色瘩,都只應該發(fā)生在單一類中,而這個新類內(nèi)的所有內(nèi)容都應該反應此變化吏廉。
public class Person{
public void say(SayThing t,String message){
if(t.getType()==null){
System.out.println(message);
}else if(t.getType=='喇叭'){
System.out.println("大聲喊:"+message);
}
}
public void do(DoThing t){
if(t.getType()==null){
System.out.println("do sth");
}else if(t.getType().equals("hit")){
System.out.println("hit sth");
}
}
}
針對Person類泞遗,如果新增加SayThing或者DoThing都要修改Person類,這種情況就是屬于Divergent Change席覆,同一個類受多種變化影響史辙。
如何更改?可以將say以及do方法內(nèi)的部分佩伤,分別抽取出一個類聊倔,做到當新增某一個類型的時候,只需要處理一個地方即可生巡。
Shotgun Surgery(散彈式修改)
如果遇到某種變化耙蔑,你都必須在許多不同的類內(nèi)做出許多小修改,你所面臨的壞味道就是Shotgun Surgery孤荣。如果需要修改的代碼散布四處甸陌,你不但很難找到它們,也很容易忘記某個重要的修改盐股。
Divergent Change是指“一個類受多種變化的影響”钱豁,Shotgun Surgery則是指“一種變化引發(fā)多個類響應修改”。
例子:
public class A{
@Value("${system.test}")
private String systemTest;
}
public class B{
@Value("${system.test}")
private String systemTest;
}
如例子所示疯汁,如A和B都使用了配置文件中的屬性system.test
牲尺,假如后面配置文件變了,這個system.test的名稱改了幌蚊,改成了systen.name谤碳,那么A和B都要修改這個配置信息溃卡。
可以通過GoubleConfig類管理通用的配置信息。
Feature Envy(依戀情節(jié))
面向?qū)ο蠹夹g(shù)的全部要點在于:這是一種“將數(shù)據(jù)和對數(shù)據(jù)的操作行為包裝在一起“的技術(shù)蜒简。
函數(shù)對某個類的興趣高過對自己所處類的興趣瘸羡。
影響:數(shù)據(jù)和行為不在一處,修改不可控臭蚁。
public class UserInfoService{
@Autowired
private IUserUnitRelationService userUnitRelationService;
public void addUserRelation(List<Integer> unitIds,Integer userId){
//1. 刪除原有的關(guān)聯(lián)關(guān)系
userUnitRelationService.deleteRelationByIds(userId);
//2. 新增新的關(guān)聯(lián)關(guān)系
userUnitRelationService.addRealtions(unitIds,userId);
//3. 更新用戶狀態(tài)
this.updateUserStatus();
}
}
上面的列子就是一個種不好的方法最铁,因為在UserInfoService中關(guān)注了太多UserUnitRelationService的東西了。
更好的方式應該是垮兑,將步驟1和步驟2合并,放到UserUnitRelationService中漱挎。
public class UserInfoService{
@Autowired
private IUserUnitRelationService userUnitRelationService;
public void addUserRelation(List<Integer> unitIds,Integer userId){
//1. 處理用戶關(guān)系
userUnitRelationService.addNewRelations(unitIds,userId);
//2. 更新用戶狀態(tài)
this.updateUserStatus();
}
}
Data Clumps(數(shù)據(jù)泥團)
多個類/方法參數(shù)中都有相同的屬性系枪,且這些相同的屬性的業(yè)務意義也是相同的。
一個大對象磕谅,裝載太多的屬性字段私爷。
Primitive Obsession(基本類型偏執(zhí))
大多數(shù)編程環(huán)境都有兩種數(shù)據(jù):結(jié)構(gòu)類型允許你將數(shù)據(jù)組織成有意義的形式;基本類型則是構(gòu)成結(jié)構(gòu)類型的積木塊膊夹。
對象的一個極大的價值在于:它們模糊(甚至打破)了橫亙于基本數(shù)據(jù)和體積較大的類之間的界限衬浑。
如果你有一組應該總是被放在一起的字段,應該將這些字段抽出來一個類放刨。
比如工秩,錢。很多情況下我們都直接使用基本類型來表示錢进统,比如:Integer money助币;
但實際上,錢還有其他的屬性在螟碎。比如:是美元 眉菱、日元、人民幣等掉分。
所以我們定義錢俭缓,可以這樣定義:
public class Money{
private Integer money;
private String type;
}
Switch Statements(switch驚悚現(xiàn)身)
面向?qū)ο蟪绦虻囊粋€最明顯特征就是:少用switch(或case)語句。
從本質(zhì)上說酥郭,swtich語句的問題在于重復华坦。你經(jīng)常會發(fā)現(xiàn)同樣的switch語句散步于不同地點。如果要為它添加一個新的case語句褥民,就必須找到所有switch語句并修改它們季春。
面向?qū)ο笾械亩鄳B(tài)概念可為此帶來優(yōu)雅的解決辦法。
如果你只是在單一函數(shù)中使用switch消返,且不想改動它們载弄,那么多態(tài)就有點殺雞用牛刀了耘拇。
Parallel Inheritance Hierarchies(平行繼承體系)
Parallel Inheritance Hierarchies其實是Shotgun Surgery的特殊情況。
每當你為某個類增加一個子類宇攻,必須也為另一個類相應增加一個子類惫叛。
如果你發(fā)現(xiàn)某個繼承體系的類名稱前綴和另一個繼承體系的類名稱前綴完全相同,便是聞到了這種壞味道逞刷。
如上圖所示嘉涌,如果再出現(xiàn)一個叫MiddleXXXService的類,那么AppService和OrangeService都要有這樣一個子類夸浅。
Lazy Class(冗贅類)
你所創(chuàng)建的每一個類仑最,都得有人去理解它、維護它帆喇,這些工作都是要花錢的警医。
如果一個類的所得不值其身價,它就應該消失坯钦。
項目中經(jīng)常會出現(xiàn)這樣的情況:某個類原本對得起自己的身價预皇,但重構(gòu)使它身形縮水,不再做那么多工作婉刀;或開發(fā)者事前規(guī)劃了某些變化吟温,并添加一個類來應付這些變化,但這些變化實際上沒有發(fā)生(超前設(shè)計突颊?鲁豪?)。不論什么情況洋丐,如果這個類沒有意義呈昔,就讓他“去死”吧。
Speculative Generality(夸夸其談未來性)
我想我們總有一天需要做這事友绝,并因此企圖以各式各樣的鉤子和特殊情況來處理一些非要的事情堤尾,這種壞味道就出現(xiàn)了。
這么做的結(jié)果往往造成系統(tǒng)更難理解和維護迁客。
如果所有裝置都會被用到郭宝,那就值得那么做;如果用不到掷漱,就不值得粘室。用不上的裝置只會擋你的路,所有卜范,把它搬開吧衔统。
Temporary Field(令人迷惑的暫時字段)
有時你會看到這樣的對象:其內(nèi)某個實例變量僅為某種特定情況而設(shè)。這樣的代碼讓人不易理解。
在變量未被使用的情況下猜測當初其設(shè)置目的锦爵,會令人苦惱的舱殿。
如果類中有一個復雜算法,需要好幾個變量险掀,往往就可能導致壞味道Temporary Filed的出現(xiàn)沪袭。由于提供者不希望傳遞一長串參數(shù),所以他把這些參數(shù)都放進字段中樟氢,但是這些字段只在使用該算法時才有效冈绊,其他情況下只會讓人迷惑。這時候你可以把這些變量抽到一個新的類中埠啃。
Message Chains(過度耦合的消息鏈)
示例:
a.getB().getC().getD().doSth();
采用這種方式死宣,意味著代碼與查找過程中的結(jié)構(gòu)緊密耦合。一旦對象間的關(guān)系發(fā)生任何變化霸妹,客戶端就不得不做出相應修改十电。
Middle Man(中間人)
對象的基本特征之一就是封裝--對外部世界隱藏其內(nèi)部細節(jié)。
封裝往往伴隨著委托叹螟。
比如你詢問你的主管是否有時間參加一個會議。他就把這個消息“委托”給他的記事本台盯,然后才能回答你罢绽。但是對于你來說,你沒必要知道主管到底是使用什么樣的記事本静盅。
//“我”類
public Class Me(){
//"我"有一個功能良价,叫詢問會議
public boolean askMetting(Manager manager,Date date){
//"詢問"你是否有時間?
if(manager.haveTime(date)){
return true;
}
return false;
}
}
在上面的列子中蒿叠,詢問領(lǐng)導是否有時間明垢,對于“我”來說,我是不關(guān)注他從哪里知道自己的日程的市咽,他只需要給我一個答案痊银。
反例
//“我”類
public Class Me(){
//"我"有一個功能,叫詢問會議
public boolean askMetting(Manager manager,Date date){
NoteBook noteBook=manager.getNoteBook();
List<Date> dates=noteBook.getSchedule();
for(Date exsists: dates){
if(exsists==date){
return false;
}
}
return true;
}
}
上面這種情況就屬于過度運用委托施绎。
Inappropriate Intimacy(狎昵關(guān)系--不適當?shù)挠H密)
有時你會看到兩個類過于親密溯革,花費太多時間去探究彼此的private成分。
過分親密的類必須拆散谷醉。
Alternative Classes with Different Interfaces(異曲同工的類)
兩個類做的同一件事或者同一類事致稀。
為什么是一種壞味道?
- 會有選擇上的疑慮俱尼,不知道兩個類應該調(diào)用哪個抖单,而疑慮之下就是時間的浪費。
- 修改代碼的時候,只向其中一個類添加了邏輯矛绘,后續(xù)調(diào)用時耍休,就會困擾調(diào)用者,而且容易導致兩個類容易出現(xiàn)重復代碼蔑歌。
Incomplete Library Class(不完美的庫類)
復用常被視為對象的終極目的羹应。
但目前很多時候復用的意義經(jīng)常被高估--大多數(shù)對象只要夠用就好。
Data Class(純稚的數(shù)據(jù)類)
所謂Data Class是指:它們擁有一些字段次屠,以及用于訪問(讀寫)這些字段的函數(shù)园匹,除此之外一無長物。
Data Class就像小孩子劫灶。作為一個起點很好裸违,但若要讓它們像成熟的對象那樣參與整個系統(tǒng)的工作,它們就必須要承擔一定責任本昏。
Refused Bequest(被拒絕的遺贈)
子類應該繼承超類的函數(shù)和數(shù)據(jù)供汛。
但如果它們不想或不需要繼承,又該怎么辦呢涌穆?按傳統(tǒng)說法怔昨,這就意味著繼承體系設(shè)計錯誤。
有時候你需要的可能不是繼承宿稀,而是組合趁舀。
Comments(過多的注釋)
注釋不是壞味道,事實上它們還是一種香味祝沸。
但人們經(jīng)常把它當作除臭劑來使用矮烹,常常會有這樣的情況:你看到一段代碼有著長長的注釋,然后發(fā)現(xiàn)罩锐,這些注釋之所以存在是因為代碼很糟糕奉狈。
Comments可以帶我們找到之前提過的壞味道,找到壞味道后涩惑,我們首先應該以各種重構(gòu)手法把壞味道去除仁期。完成之后我們常常會發(fā)現(xiàn):注釋已經(jīng)變得多余了,因為代碼已經(jīng)說明了一切境氢。
如果你需要注釋來解釋一塊代碼做了什么蟀拷,那么就把它抽取出一個方法把。
當你感覺需要撰寫注釋時萍聊,請先嘗試重構(gòu)问芬,試著讓所有注釋都變得多余。