引言
大學(xué)的時(shí)候?qū)W編程對(duì)于if else,不管哪本書上,都是這樣教的:(我們假想一種實(shí)際不同用戶的業(yè)務(wù)邏輯)
// 產(chǎn)品提出會(huì)員與非會(huì)員的計(jì)算邏輯
if (isVip) {
System.out.println("8折出售");
} else {
System.out.println("不打折出售");
}
// 產(chǎn)品又提出以前的會(huì)員邏輯不變,新增VVip(最近某些視頻APP推出這種制度,坊間調(diào)侃為VVip)
if (type == UserType.VIP) {
System.out.println("8折出售");
} else if (type == UserType.VVIP) {
System.out.println("7折出售");
} else {
System.out.println("不打折出售");
}
// 產(chǎn)品又提出以前的會(huì)員邏輯不變,新增VVVip
if (type == UserType.VIP) {
System.out.println("8折出售");
} else if (type == UserType.VVIP) {
System.out.println("7折出售");
} else if (type == UserType.VVVIP) {
System.out.println("6折出售");
} else {
System.out.println("不打折出售");
}
這些業(yè)務(wù)邏輯隨著時(shí)間的增長(zhǎng)會(huì)變得越來(lái)越多,而寫之前的代碼的人已經(jīng)離職了,現(xiàn)在出現(xiàn)新的業(yè)務(wù)邏輯超級(jí)會(huì)員,五折出售,你接手之后是繼續(xù)添加還是用各“技術(shù)”重構(gòu)?我在知乎上看到過(guò),為了逃避Sonar設(shè)置的if else的三個(gè)分支上限的報(bào)告檢查,在最后一個(gè)else里面調(diào)用新函數(shù)繼續(xù)if else的極品趕著下班的方案。這種方式類似于我為了應(yīng)對(duì)產(chǎn)品急著上線,草草地寫了單元測(cè)試糊弄Jenkins質(zhì)量檢查。(我相信很多開發(fā)者都這么干過(guò)O(∩_∩)O)用死板的程序防止工程師寫出惡心的代碼,這是在侮辱工程師的智商。所以分享一下我在工作中的經(jīng)驗(yàn),如果有不合適的地方,歡迎探討交流。
現(xiàn)象
因?yàn)楝F(xiàn)在大多數(shù)公司都是業(yè)務(wù)驅(qū)動(dòng),畢竟賺錢是王道,不賺錢你都不用在這里了。所以就產(chǎn)生,第一個(gè)開發(fā)者,if else多簡(jiǎn)單,一看就懂,隨著業(yè)務(wù)開發(fā),再加個(gè) if else 也能接受。第二個(gè)開發(fā)者,看到之前代碼,以前都這樣寫,我可不能動(dòng)以前的代碼,多個(gè)if else 也沒毛病。過(guò)兩天,再加兩個(gè) if ese ,第三個(gè)人接手之后,前面的人好惡心啊,業(yè)務(wù)這么復(fù)雜,算了,不優(yōu)化了,我也加個(gè) if else。
如果在稍大點(diǎn)的公司工作,Sonar或者Jenkins等工具檢查代碼質(zhì)量的時(shí)候會(huì)顯示 Cyclomatic Complexity,Cyclomatic Complexity,這是一種代碼復(fù)雜度的衡量標(biāo)準(zhǔn),中文叫圈復(fù)雜度。在軟件測(cè)試的概念里,圈復(fù)雜度用來(lái)衡量一個(gè)模塊判定結(jié)構(gòu)的復(fù)雜程度,數(shù)量上表現(xiàn)為線性無(wú)關(guān)的路徑條數(shù),即合理的預(yù)防錯(cuò)誤所需測(cè)試的最少路徑條數(shù)。圈復(fù)雜度大說(shuō)明程序代碼可能質(zhì)量低且難于測(cè)試和維護(hù),根據(jù)經(jīng)驗(yàn),程序的可能錯(cuò)誤和高的圈復(fù)雜度有著很大關(guān)系。什么意思呢?就是你寫的代碼,每個(gè)公司都有自己不同的單元測(cè)試分支覆蓋率的標(biāo)準(zhǔn),這里分支越來(lái)越多,單元測(cè)試會(huì)變得很復(fù)雜,或者壓根就沒法測(cè)了,因?yàn)檫@里并沒有TDD(測(cè)試驅(qū)動(dòng)開發(fā))。
解決方案與分析
方案一 switch
switch和if else是半斤八兩的寫法,可讀性差不多,switch 顯得更整齊點(diǎn)。但沒有 if esle 好維護(hù),if else可以更靈活。不管怎么樣,這兩種方法都不利于寫單測(cè)。而且代碼規(guī)范都會(huì)要求case后邊必須跟break,switch里邊必須有default。但是對(duì) if esle沒有過(guò)多要求。而且 switch 的條件只能是同一類型的不同值,比如顏色,但是現(xiàn)實(shí)世界中有些時(shí)候遇到的是年齡段,而不是年齡值。
if (age == 10) {
statement;
} else if (age == 20) {
statement;
} else if (age >30) {
statement;
} else {
statement;
}
···
swich是通過(guò)跳轉(zhuǎn)表設(shè)置索引值效率理論上更高,但在現(xiàn)在編譯器中 if else switch 基本沒差距,這種優(yōu)化對(duì)效率基本沒什么影響。
方案二 策略模式
// Java實(shí)際開發(fā)中和錢有關(guān)的不能用double,要用BigDecimal,這里只作為展示作用。
public interface Strategy {
double compute(double money);
}
public class VipStrategy implements Strategy {
@Override
public double compute(double money) {
return money * 0.8;
}
}
public class VvipStrategy implements Strategy {
@Override
public double compute(double money) {
return money * 0.7;
}
}
public class VvvipStrategy implements Strategy {
@Override
public double compute(double money) {
return money * 0.6;
}
}
public class SvipStrategy implements Strategy {
@Override
public double compute(double money) {
return money * 0.1;
}
}
public static void main(String[] args) {
Strategy svip = new SvipStrategy();
svip.compute(money);
Strategy vvip = new VvipStrategy();
vvip.compute(money);
}
這種使用涉及模式的方式寫底層沒問題,寫框架更沒問題。但是涉及到業(yè)務(wù)代碼,可讀性降低,入門門檻高。其實(shí)是不利于維護(hù)和交接的。
方案三 奇技淫巧
提前return,枚舉,Optional,工廠等方案,這一些方案其實(shí)說(shuō)白了只是在某種程度上降低了復(fù)雜度,只是轉(zhuǎn)移注意力,該判斷還是要判斷的。
感想
if else 是面向業(yè)務(wù)邏輯編程的,switch 是面向框架編程的。開發(fā)框架為什么可以使用大量的設(shè)計(jì)模式,因?yàn)榭蚣苁潜磺逦x的,有嚴(yán)謹(jǐn)邏輯的。但是大多數(shù)工程師面對(duì)的是不斷增加的業(yè)務(wù)邏輯,面對(duì)復(fù)雜的現(xiàn)實(shí)世界,抽象出來(lái)的業(yè)務(wù)邏輯使你無(wú)法使用固定的設(shè)計(jì)模式。當(dāng)?shù)谝粋€(gè)工程師寫下 if else, 悲劇已經(jīng)產(chǎn)生了,世界就變得復(fù)雜了,隨著需求逐漸增加,又想省事,借用之前的代碼,先加一個(gè),再加一個(gè),于是邏輯分支越來(lái)越多,據(jù)說(shuō)幾百個(gè)if else 或者上千的分支代碼不是沒有。這個(gè)時(shí)候已經(jīng)沒有工程師愿意重構(gòu)switch或者策略模式,畢竟業(yè)務(wù)復(fù)雜度擺在那里,你重構(gòu)了,所有的東西都需要重測(cè),測(cè)試不剁了你才怪。如果系統(tǒng)崩了,涉及大量資金損失,可以考慮下一家了。最終就變成了 if else if else if else ···,這種局面了。你不能說(shuō)第一個(gè)工程師有錯(cuò),因?yàn)楫a(chǎn)品并沒有告訴他以后的情況,編程嘛,最重要的是不要為難自己,繼續(xù)增加 if else ,測(cè)試,提交,上線沒問題,打卡下班。因?yàn)橛袝r(shí)候大多數(shù)的情況是兩頭都定下來(lái)了,你夾在中間,上游給你傳了枚舉類型,是這種枚舉類型,就走一種邏輯,如果是另一種枚舉類型,就走另一種邏輯,這時(shí)候,直接加if else是最快的解決方案了。
我也想成為優(yōu)秀的工程師,有時(shí)候不是我不思考,有時(shí)候不是我不想寫出優(yōu)秀的代碼,是不能寫出優(yōu)秀的代碼,根本沒有寫高質(zhì)量代碼的客觀環(huán)境。產(chǎn)品催,開發(fā)經(jīng)理催,需求不停的變動(dòng),什么設(shè)計(jì)模式都不頂用,最終就是怎樣快,怎樣方便怎樣來(lái)?,F(xiàn)實(shí)工作絕不是軟件工程課程講的那么理想,會(huì)有無(wú)數(shù)的突發(fā)情況在等著你,等你考慮好了設(shè)計(jì)模式,抽象類,接口產(chǎn)品通知你有個(gè)需求要加塞,或者那個(gè)需求砍掉了。重構(gòu)不僅增大自己工作量,也會(huì)增大測(cè)試團(tuán)隊(duì)的工作量,在現(xiàn)有交付壓力的情況下,重構(gòu)對(duì)交付產(chǎn)品毫無(wú)幫助。出問題不僅要自己背鍋,而且連累別人。然而,這鍋,本來(lái)不存在的,也不需要背的,是你給自己造了個(gè)鍋。
正確是相對(duì)的,一個(gè)優(yōu)秀的軟件開發(fā)工程師要考慮到成本和分險(xiǎn),重構(gòu)是體現(xiàn)優(yōu)秀程序員的時(shí)候,但是考慮過(guò)可能會(huì)帶來(lái)的額外風(fēng)險(xiǎn),這種風(fēng)險(xiǎn)是否可以接受?有的時(shí)候不需要把代碼當(dāng)藝術(shù)品,能夠適度忍受不完美,bug率可控,異常正常,有啥問題解決啥問題。給我?guī)啄陼r(shí)間,我也能把一個(gè)業(yè)務(wù)項(xiàng)目變成高雅藝術(shù)品,有啥用?!優(yōu)秀的程序員應(yīng)該按點(diǎn)把自己的事情做了。
祖?zhèn)鞯拇a盡量不要?jiǎng)铀琲f else 沒啥大問題就先放著,先把手頭的任務(wù)做了。if else 比動(dòng)不動(dòng)濫用策略模式好太多,策略模式作者感覺爽了,但是增加了維護(hù)成本。每個(gè)項(xiàng)目都有代碼難看的地方,這是工程的必然,工程向的代碼,第一要義要快速實(shí)現(xiàn),第二要義是方便維護(hù)。設(shè)計(jì)模式在技術(shù)選型階段定下來(lái)比較好,重構(gòu)還是算了吧。拿著底層碼農(nóng)的薪水,卻操著CTO都不操的心。業(yè)務(wù)驅(qū)動(dòng)技術(shù),不要維護(hù)只有自己關(guān)心但用戶不關(guān)心的特性。大量的不規(guī)范的代碼的源頭,都是不專業(yè)的產(chǎn)品或策劃,它們提出的不合理的需求,或者無(wú)腦的需求變更。
說(shuō)了這么多,但這不是我們不學(xué)重構(gòu),代碼整潔之道,設(shè)計(jì)模式的借口。設(shè)計(jì)模式和框架絕對(duì)一個(gè)項(xiàng)目中非常重要的東西,但不是絕對(duì)重要的,而本來(lái)就是幾個(gè) if else 就能直觀解決的問題。什么是優(yōu)秀的代碼?能讓人一眼看懂的代碼才是好代碼。業(yè)務(wù)代碼個(gè)人感覺還是直觀能看清楚實(shí)現(xiàn)邏輯,而不是在代碼架構(gòu)里跳來(lái)跳去。最簡(jiǎn)明易懂的代碼結(jié)構(gòu)有著最好的可維護(hù)性和可擴(kuò)展性。
結(jié)論
宋代的青原行思大禪師說(shuō)過(guò)這樣一段話:"老僧三十年前來(lái)參禪時(shí),見山是山,見水是水;及至后來(lái)親見知識(shí),有個(gè)入處,見山不是山,見水不是水;而今得個(gè)體歇處,依然見山還是山,見水還是水。"由此得來(lái)人生三個(gè)境界:"看山是山,看水是水;看山不是山,看水不是水;看山還是山,看水還是水。"
對(duì)于 if else if else ···情況,我們?cè)趯W(xué)習(xí)編程的時(shí)候是第一重境界;等我學(xué)了設(shè)計(jì)模式,學(xué)了重構(gòu),學(xué)了代碼整潔之道,感覺 if else if else太low了,設(shè)計(jì)模式多高大上,進(jìn)入第二重境界;等我真正面對(duì)大量變動(dòng)的業(yè)務(wù)邏輯的時(shí)候,我覺得還是第三重境界才是真。
所以,具體環(huán)境具體做法,通常情況下,可讀性,邏輯清晰度永遠(yuǎn)是第一。能讓讀者一眼看懂的代碼才是好代碼。if else if else ··· 該加的時(shí)候毫不手軟!而且還是最快的方式!