求求你們了堡掏,別再寫(xiě)滿屏的 if/ else 了念秧!

為什么我們寫(xiě)的代碼都是 if-else?

程序員想必都經(jīng)歷過(guò)這樣的場(chǎng)景:剛開(kāi)始自己寫(xiě)的代碼很簡(jiǎn)潔布疼,邏輯清晰摊趾,函數(shù)精簡(jiǎn),沒(méi)有一個(gè) if-else游两,可隨著代碼邏輯不斷完善和業(yè)務(wù)的瞬息萬(wàn)變:比如需要對(duì)入?yún)⑦M(jìn)行類型和值進(jìn)行判斷砾层;這里要判斷下對(duì)象是否為 null;不同類型執(zhí)行不同的流程贱案。

落地到具體實(shí)現(xiàn)只能不停地加 if-else 來(lái)處理肛炮,漸漸地,代碼變得越來(lái)越龐大宝踪,函數(shù)越來(lái)越長(zhǎng)侨糟,文件行數(shù)也迅速突破上千行,維護(hù)難度也越來(lái)越大瘩燥,到后期基本達(dá)到一種難以維護(hù)的狀態(tài)秕重。

雖然我們都很不情愿寫(xiě)出滿屏 if-else 的代碼,可邏輯上就是需要特殊判斷厉膀,很絕望溶耘,可也沒(méi)辦法避免啊。

其實(shí)回頭看看自己的代碼服鹅,寫(xiě) if-else 不外乎兩種場(chǎng)景:異常邏輯處理和不同狀態(tài)處理凳兵。

兩者最主要的區(qū)別是:異常邏輯處理說(shuō)明只能一個(gè)分支是正常流程,而不同狀態(tài)處理都所有分支都是正常流程企软。
怎么理解庐扫?舉個(gè)例子:

 1//舉例一:異常邏輯處理例子
 2Object obj = getObj();
 3if (obj != null) {
 4    //do something
 5}else{
 6    //do something
 7}
 8
 9//舉例二:狀態(tài)處理例子
10Object obj = getObj();
11if (obj.getType == 1) {
12    //do something
13}else if (obj.getType == 2) {
14    //do something
15}else{
16    //do something
17}

第一個(gè)例子 if (obj != null) 是異常處理,是代碼健壯性判斷,只有 if 里面才是正常的處理流程形庭,else 分支是出錯(cuò)處理流程杰妓;而第二個(gè)例子不管 type 等于 1,2 還是其他情況碘勉,都屬于業(yè)務(wù)的正常流程巷挥。對(duì)于這兩種情況重構(gòu)的方法也不一樣。

代碼 if-else 代碼太多有什么缺點(diǎn)验靡?

缺點(diǎn)相當(dāng)明顯了:最大的問(wèn)題是代碼邏輯復(fù)雜倍宾,維護(hù)性差,極容易引發(fā) bug胜嗓。如果使用 if-else高职,說(shuō)明 if 分支和 else 分支的重視是同等的,但大多數(shù)情況并非如此辞州,容易引起誤解和理解困難怔锌。

是否有好的方法優(yōu)化?如何重構(gòu)变过?

方法肯定是有的埃元。重構(gòu) if-else 時(shí),心中無(wú)時(shí)無(wú)刻把握一個(gè)原則:
盡可能地維持正常流程代碼在最外層媚狰。

意思是說(shuō)岛杀,可以寫(xiě) if-else 語(yǔ)句時(shí)一定要盡量保持主干代碼是正常流程,避免嵌套過(guò)深崭孤。

實(shí)現(xiàn)的手段有:減少嵌套类嗤、移除臨時(shí)變量、條件取反判斷辨宠、合并條件表達(dá)式等遗锣。關(guān)注公眾號(hào)互聯(lián)網(wǎng)架構(gòu)師可以獲取一份全套的 Java 架構(gòu)視頻。
下面舉幾個(gè)實(shí)例來(lái)講解這些重構(gòu)方法:

異常邏輯處理型重構(gòu)方法實(shí)例一

重構(gòu)前:

 1double disablityAmount(){
 2    if(_seniority < 2)
 3        return 0;
 4
 5    if(_monthsDisabled > 12)
 6        return 0;
 7
 8    if(_isPartTime)
 9        return 0;
10
11    //do somethig
12}

重構(gòu)后:

1double disablityAmount(){
2    if(_seniority < 2 || _monthsDisabled > 12 || _isPartTime)
3        return 0;
4
5    //do somethig
6}

這里的重構(gòu)手法叫合并條件表達(dá)式:如果有一系列條件測(cè)試都得到相同結(jié)果嗤形,將這些結(jié)果測(cè)試合并為一個(gè)條件表達(dá)式精偿。

這個(gè)重構(gòu)手法簡(jiǎn)單易懂,帶來(lái)的效果也非常明顯派殷,能有效地較少if語(yǔ)句还最,減少代碼量邏輯上也更加易懂。

異常邏輯處理型重構(gòu)方法實(shí)例二

重構(gòu)前:

 1double getPayAmount(){
 2    double result;
 3    if(_isDead) {
 4        result = deadAmount();
 5    }else{
 6        if(_isSeparated){
 7            result = separatedAmount();
 8        }
 9        else{
10            if(_isRetired){
11                result = retiredAmount();
12            else{
13                result = normalPayAmount();
14            }
15        }
16    }
17    return result;
18}

重構(gòu)后:

 1double getPayAmount(){
 2    if(_isDead)
 3        return deadAmount();
 4
 5    if(_isSeparated)
 6        return separatedAmount();
 7
 8    if(_isRetired)
 9        return retiredAmount();
10
11    return normalPayAmount();
12}

怎么樣毡惜?比對(duì)兩個(gè)版本,會(huì)發(fā)現(xiàn)重構(gòu)后的版本邏輯清晰斯撮,簡(jiǎn)潔易懂经伙。

和重構(gòu)前到底有什么區(qū)別呢?

最大的區(qū)別是減少 if-else 嵌套∨聊ぃ可以看到枣氧,最初的版本 if-else 最深的嵌套有三層,看上去邏輯分支非常多垮刹,進(jìn)到里面基本都要被繞暈。其實(shí),仔細(xì)想想嵌套內(nèi)的 if-else 和最外層并沒(méi)有關(guān)聯(lián)性的草添,完全可以提取最頂層豆瘫。

改為平行關(guān)系,而非包含關(guān)系寺董,if-else 數(shù)量沒(méi)有變化覆糟,但是邏輯清晰明了,一目了然遮咖。

另一個(gè)重構(gòu)點(diǎn)是廢除了 result 臨時(shí)變量滩字,直接 return 返回。好處也顯而易見(jiàn)直接結(jié)束流程御吞,縮短異常分支流程麦箍。原來(lái)的做法先賦值給 result 最后統(tǒng)一 return,那么對(duì)于最后 return 的值到底是那個(gè)函數(shù)返回的結(jié)果不明確陶珠,增加了一層理解難度内列。

總結(jié)重構(gòu)的要點(diǎn):如果 if-else 嵌套沒(méi)有關(guān)聯(lián)性,直接提取到第一層背率,一定要避免邏輯嵌套太深话瞧。盡量減少臨時(shí)變量改用 return 直接返回。

異常邏輯處理型重構(gòu)方法實(shí)例三

重構(gòu)前:

1public double getAdjustedCapital(){
2    double result = 0.0;
3    if(_capital > 0.0 ){
4        if(_intRate > 0 && _duration >0){
5            resutl = (_income / _duration) *ADJ_FACTOR;
6        }
7    }
8    return result;
9}

第一步寝姿,運(yùn)用第一招交排,減少嵌套和移除臨時(shí)變量:

1public double getAdjustedCapital(){
2    if(_capital <= 0.0 ){
3        return 0.0;
4    }
5    if(_intRate > 0 && _duration >0){
6        return (_income / _duration) *ADJ_FACTOR;
7    }
8    return 0.0;
9}

這樣重構(gòu)后,還不夠饵筑,因?yàn)橹饕恼Z(yǔ)句 (_income / _duration) *ADJ_FACTOR; 在 if 內(nèi)部埃篓,并非在最外層,根據(jù)優(yōu)化原則(盡可能地維持正常流程代碼在最外層)根资,可以再繼續(xù)重構(gòu):

 1public double getAdjustedCapital(){
 2    if(_capital <= 0.0 ){
 3        return 0.0;
 4    }
 5    if(_intRate <= 0 || _duration <= 0){
 6        return 0.0;
 7    }
 8
 9    return (_income / _duration) *ADJ_FACTOR;
10}

這才是好的代碼風(fēng)格架专,邏輯清晰,一目了然玄帕,沒(méi)有 if-else 嵌套難以理解的流程部脚。

這里用到的重構(gòu)方法是:將條件反轉(zhuǎn)使異常情況先退出,讓正常流程維持在主干流程裤纹。

異常邏輯處理型重構(gòu)方法實(shí)例四

重構(gòu)前:

 1   /* 查找年齡大于18歲且為男性的學(xué)生列表 */
 2    public ArrayList<Student> getStudents(int uid){
 3        ArrayList<Student> result = new ArrayList<Student>();
 4        Student stu = getStudentByUid(uid);
 5        if (stu != null) {
 6            Teacher teacher = stu.getTeacher();
 7            if(teacher != null){
 8                ArrayList<Student> students = teacher.getStudents();
 9                if(students != null){
10                    for(Student student : students){
11                        if(student.getAge() > = 18 && student.getGender() == MALE){
12                            result.add(student);
13                        }
14                    }
15                }else {
16                    logger.error("獲取學(xué)生列表失敗");
17                }
18            }else {
19                logger.error("獲取老師信息失敗");
20            }
21        } else {
22            logger.error("獲取學(xué)生信息失敗");
23        }
24        return result;
25    }

典型的"箭頭型"代碼委刘,最大的問(wèn)題是嵌套過(guò)深,解決方法是異常條件先退出,保持主干流程是核心流程:

重構(gòu)后:

 1   /* 查找年齡大于18歲且為男性的學(xué)生列表 */
 2    public ArrayList<Student> getStudents(int uid){
 3        ArrayList<Student> result = new ArrayList<Student>();
 4        Student stu = getStudentByUid(uid);
 5        if (stu == null) {
 6            logger.error("獲取學(xué)生信息失敗");
 7            return result;
 8        }
 9
10        Teacher teacher = stu.getTeacher();
11        if(teacher == null){
12            logger.error("獲取老師信息失敗");
13            return result;
14        }
15
16        ArrayList<Student> students = teacher.getStudents();
17        if(students == null){
18            logger.error("獲取學(xué)生列表失敗");
19            return result;
20        }
21
22        for(Student student : students){
23            if(student.getAge() > 18 && student.getGender() == MALE){
24                result.add(student);
25            }
26        }
27        return result;
28    }

狀態(tài)處理型重構(gòu)方法實(shí)例一

重構(gòu)前:

 1double getPayAmount(){
 2    Object obj = getObj();
 3    double money = 0;
 4    if (obj.getType == 1) {
 5        ObjectA objA = obj.getObjectA();
 6        money = objA.getMoney()*obj.getNormalMoneryA();
 7    }
 8    else if (obj.getType == 2) {
 9        ObjectB objB = obj.getObjectB();
10        money = objB.getMoney()*obj.getNormalMoneryB()+1000;
11    }
12}

重構(gòu)后:

 1double getPayAmount(){
 2    Object obj = getObj();
 3    if (obj.getType == 1) {
 4        return getType1Money(obj);
 5    }
 6    else if (obj.getType == 2) {
 7        return getType2Money(obj);
 8    }
 9}
10
11double getType1Money(Object obj){
12    ObjectA objA = obj.getObjectA();
13    return objA.getMoney()*obj.getNormalMoneryA();
14}
15
16double getType2Money(Object obj){
17    ObjectB objB = obj.getObjectB();
18    return objB.getMoney()*obj.getNormalMoneryB()+1000;
19}

這里使用的重構(gòu)方法是:把 if-else 內(nèi)的代碼都封裝成一個(gè)公共函數(shù)锡移。函數(shù)的好處是屏蔽內(nèi)部實(shí)現(xiàn)呕童,縮短 if-else 分支的代碼。代碼結(jié)構(gòu)和邏輯上清晰淆珊,能一下看出來(lái)每一個(gè)條件內(nèi)做的功能夺饲。

狀態(tài)處理型重構(gòu)方法實(shí)例二

針對(duì)狀態(tài)處理的代碼,一種優(yōu)雅的做法是用多態(tài)取代條件表達(dá)式(《重構(gòu)》推薦做法)施符。

你手上有個(gè)條件表達(dá)式往声,它根據(jù)對(duì)象類型的不同而選擇不同的行為。將這個(gè)表達(dá)式的每個(gè)分支放進(jìn)一個(gè)子類內(nèi)的覆寫(xiě)函數(shù)中操刀,然后將原始函數(shù)聲明為抽象函數(shù)烁挟。

重構(gòu)前:

 1double getSpeed(){
 2    switch(_type){
 3        case EUROPEAN:
 4            return getBaseSpeed();
 5        case AFRICAN:
 6            return getBaseSpeed()-getLoadFactor()*_numberOfCoconuts;
 7        case NORWEGIAN_BLUE:
 8            return (_isNailed)?0:getBaseSpeed(_voltage);
 9    }
10}

重構(gòu)后:

 1class Bird{
 2    abstract double getSpeed();
 3}
 4
 5class European extends Bird{
 6    double getSpeed(){
 7        return getBaseSpeed();
 8    }
 9}
10
11class African extends Bird{
12    double getSpeed(){
13        return getBaseSpeed()-getLoadFactor()*_numberOfCoconuts;
14    }
15}
16
17class NorwegianBlue extends Bird{
18    double getSpeed(){
19        return (_isNailed)?0:getBaseSpeed(_voltage);
20    }
21}

可以看到,使用多態(tài)后直接沒(méi)有了 if-else骨坑,但使用多態(tài)對(duì)原來(lái)代碼修改過(guò)大撼嗓,需要一番功夫才行。最好在設(shè)計(jì)之初就使用多態(tài)方式欢唾。關(guān)注公眾號(hào)互聯(lián)網(wǎng)架構(gòu)師可以獲取架構(gòu)視頻且警。

總結(jié)

if-else 代碼是每一個(gè)程序員最容易寫(xiě)出的代碼,同時(shí)也是最容易被寫(xiě)爛的代碼礁遣,稍不注意斑芜,就產(chǎn)生一堆難以維護(hù)和邏輯混亂的代碼。

針對(duì)條件型代碼重構(gòu)把握一個(gè)原則:

盡可能地維持正常流程代碼在最外層祟霍,保持主干流程是正常核心流程杏头。
為維持這個(gè)原則:合并條件表達(dá)式可以有效地減少if語(yǔ)句數(shù)目;減少嵌套能減少深層次邏輯沸呐;異常條件先退出自然而然主干流程就是正常流程醇王。

針對(duì)狀態(tài)處理型重構(gòu)方法有兩種:一種是把不同狀態(tài)的操作封裝成函數(shù),簡(jiǎn)短 if-else 內(nèi)代碼行數(shù)崭添;另一種是利用面向?qū)ο蠖鄳B(tài)特性直接干掉了條件判斷寓娩。

現(xiàn)在回頭看看自己的代碼,犯了哪些典型錯(cuò)誤呼渣,趕緊運(yùn)用這些重構(gòu)方法重構(gòu)代碼吧<椤!

原文鏈接:https://blog.csdn.net/qq_35440678/article/details/77939999

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末屁置,一起剝皮案震驚了整個(gè)濱河市焊夸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌缰犁,老刑警劉巖淳地,帶你破解...
    沈念sama閱讀 211,265評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件怖糊,死亡現(xiàn)場(chǎng)離奇詭異帅容,居然都是意外死亡颇象,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén)并徘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)遣钳,“玉大人,你說(shuō)我怎么就攤上這事麦乞≡誊睿” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,852評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵姐直,是天一觀的道長(zhǎng)倦淀。 經(jīng)常有香客問(wèn)我,道長(zhǎng)声畏,這世上最難降的妖魔是什么撞叽? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,408評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮插龄,結(jié)果婚禮上愿棋,老公的妹妹穿的比我還像新娘。我一直安慰自己均牢,他們只是感情好糠雨,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著徘跪,像睡著了一般甘邀。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上垮庐,一...
    開(kāi)封第一講書(shū)人閱讀 49,772評(píng)論 1 290
  • 那天松邪,我揣著相機(jī)與錄音,去河邊找鬼突硝。 笑死测摔,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的解恰。 我是一名探鬼主播锋八,決...
    沈念sama閱讀 38,921評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼护盈!你這毒婦竟也來(lái)了挟纱?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,688評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤腐宋,失蹤者是張志新(化名)和其女友劉穎紊服,沒(méi)想到半個(gè)月后檀轨,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,130評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡欺嗤,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評(píng)論 2 325
  • 正文 我和宋清朗相戀三年参萄,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片煎饼。...
    茶點(diǎn)故事閱讀 38,617評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡讹挎,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出吆玖,到底是詐尸還是另有隱情筒溃,我是刑警寧澤,帶...
    沈念sama閱讀 34,276評(píng)論 4 329
  • 正文 年R本政府宣布沾乘,位于F島的核電站怜奖,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏翅阵。R本人自食惡果不足惜歪玲,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望怎顾。 院中可真熱鬧读慎,春花似錦、人聲如沸槐雾。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,740評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)募强。三九已至株灸,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間擎值,已是汗流浹背慌烧。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,967評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鸠儿,地道東北人屹蚊。 一個(gè)月前我還...
    沈念sama閱讀 46,315評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像进每,于是被迫代替她去往敵國(guó)和親汹粤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容