自創(chuàng)實現(xiàn)模式 ~ 以結(jié)果驅(qū)動代替條件驅(qū)動

所屬文章系列:尋找塵封的銀彈:自創(chuàng)實現(xiàn)模式


今天要寫第一篇自創(chuàng)實現(xiàn)模式,也就是我自己獨家創(chuàng)造的一個實現(xiàn)模式。

先解釋一下什么是實現(xiàn)模式,這個詞是Kent Beck《實現(xiàn)模式》的書名,意思就是在代碼的最底層的模式,即類內(nèi)部及函數(shù)內(nèi)部的模式,而設(shè)計模式是多個類之間的模式。

說是獨家創(chuàng)造,其實很可能早已有人提出來過,或者實踐過,只是我不知道而已,就像設(shè)計模式的最初作者也并非是《設(shè)計模式》書中的四個作者一樣,我只是一個模式的總結(jié)者而已。無論如何,對程序員有幫助,才是最重要的,也是我的初衷。

【動機(jī)】

先看一段看起來邏輯清晰,但有漏洞的代碼:

void DoAdjust(ClassA **a, ClassB **b) {

? ??if ((*a) == NULL)?{

? ? ? ??if ((*b)->next?!= NULL)?{

? ? ? ? ? ??*a = *b;

? ??? ? ? ? *b = (*b)->next;

? ??? ??}

? ??}

}

注:代碼中的雙指針(**)只是為了在函數(shù)體內(nèi)給外部的指針賦值,本質(zhì)是輸出參數(shù),方式是解引用,即*a。

表面上看起來,這段代碼邏輯清晰,但只是看似清晰而已,實際上編寫這段代碼的人并沒有搞清楚這兩個變量在所有執(zhí)行路徑到底該如何賦值。

這段代碼漏掉了一種情況:當(dāng)(*a) != NULL時,b也應(yīng)該根據(jù)(*b)->next來決定取值,具體的取值應(yīng)該有特定的業(yè)務(wù)邏輯。

當(dāng)我根據(jù)需求編寫了較為完備的測試用例之后,我才發(fā)現(xiàn)了這個Bug。經(jīng)過分析,在這段代碼中,確實應(yīng)該有這種業(yè)務(wù)邏輯,但是這段代碼卻使用了默認(rèn)的“不作為”行為,導(dǎo)致錯誤發(fā)生!

于是,我們加上了一些代碼來補上這個漏洞:

void DoAdjust(ClassA **a, ClassB **b) {

? ??if ((*a) == NULL)?{

? ? ? ??if ((*b)->next?!= NULL)?{

? ? ? ? ? ??*a = *b;

? ??? ? ? ? *b = (*b)->next;

? ??? ??}

? ??}?else {

? ? ? ??if ((*b)->next?!= NULL)?{

? ??? ? ? ? *b = (*b)->next;

? ??? ??}

? ??}

}

漏洞是補上了,但再觀察這段代碼,總是感覺哪里不對,會不會還有漏洞?這是因為我們無法一眼看出問題,必須用白盒的方式枚舉所有的情況。

由于這段代碼比較簡單,只有三個條件:*a、*b、(*b)->next,需要處理的情況數(shù)= 2(*a為空或非空) * 2(*b為空或非空) * 2((*b)->next為空或非空) = 8,所以枚舉起來并不太難。

但是,當(dāng)條件的數(shù)目猛增到10個時,它們的變化就會有2的10次方個,也就是1024個,那么枚舉的方式就顯得很傻,而且容易出錯。不過,程序員總是想追求好方法,讓自己在檢查代碼漏洞的時候可以更加“懶惰”一點。

有沒有一種方法讓這段代碼看上去一目了然,一眼就能看出有沒有漏洞?

【典型代碼】

答案當(dāng)然是有。用的方法是“以結(jié)果驅(qū)動代替條件驅(qū)動”。

先看修改后的代碼,下一小節(jié)再給你解釋:

void DoAdjust(ClassA **a, ClassB **b) {

? ?//為a賦值

? ??if ((*a) != NULL) {

? ??? ??*a = *a;

? ??} else if ((*b)->next)

?? ?? ??*a = *b;

? ??} else?{

? ??? ??*a = NULL;

? ??}


? ?//為b賦值

? ??if ((*b)->next?!= NULL)?{

? ??? ??*b = *b->next;

? ??} else {

? ??? ??*b = *b;

? ??}

}

【優(yōu)劣對比】

使用該模式后的代碼,看起來有點累贅,但你稍稍看仔細(xì)一點,就能完全懂得代碼的所有意圖和所有變化,做到了然于心。

為什么這樣說?

這個函數(shù)要解決的根本問題是為*a和*b返回值,而使用該模式的代碼恰恰是從這個為*a和*b賦值的角度出發(fā)的,我們一下子就能看出*a和*b是不是在退出的時候被賦過值了。這就是“以結(jié)果驅(qū)動代替條件驅(qū)動”這個實現(xiàn)模式中的“結(jié)果驅(qū)動”。

反觀未使用該模式的代碼,它是從條件的角度出發(fā),條件包括*a、*b和(*b)->next,我們只看到了眼花繚亂的各種條件組合,卻容易忽略掉最終要給每個返回變量賦值。這就是“以結(jié)果驅(qū)動代替條件驅(qū)動”這個實現(xiàn)模式中的“條件驅(qū)動”。

【思維進(jìn)階(一):單一路徑思維、顯式思維】

該模式體現(xiàn)出的是兩種思維:單一路徑思維和顯式思維。

單一路徑思維:函數(shù)必須確保在函數(shù)內(nèi)為每個輸出變量賦值的地方只有一處,而不依賴于函數(shù)內(nèi)其他路徑中的賦值代碼。如“典型代碼”小節(jié)中的代碼:

? ?//為a賦值

? ??if ((*a) != NULL) {

? ??? ??*a = *a;

? ??} else if ((*b)->next)

?? ?? ??*a = *b;

? ??} else?{

? ??? ??*a = NULL;

? ??}


顯式思維:在這一處賦值的代碼中,所有if/else路徑都為那個輸出變量賦值,而且必須包含else。代碼示例也是如上這段代碼。

“單一路徑思維”中還提到了“依賴于函數(shù)內(nèi)其他路徑中的賦值”,這是反面教材,代碼示例如下:

void DoAdjust(ClassA **a, ClassB **b) {

? ??if (flag == TRUE) {

? ??? ?*a = NULL; //此處為“其他路徑”

? ??}


? ??if ((*a) != NULL) {

? ??? ??*a = *a;

? ??} else if ((*b)->next)

?? ?? ??*a = *b;

? ??} else

? ??? ??*a = NULL;

? ??}

? ??...

}

這種代碼就會讓人思維混亂,理不清頭緒。

【思維進(jìn)階(二):多個條件的優(yōu)先級】

這個模式會迫使你思考:

到底是在什么特定情況下,該給什么值。而不是遇到“條件1”先給賦一個值,再遇到“條件2”再給賦一個值,就像剛舉的這個反面教材代碼那樣。當(dāng)我們有本文這種模式的思維時,就會豁然開朗,一切邏輯都是那么清晰可見!

再看這種“營養(yǎng)不良”的代碼產(chǎn)生的原因:一般是由于多個人先后修改代碼產(chǎn)生的。后面修改代碼的人不去看已有代碼,他也不知道兩個條件的優(yōu)先級是什么,他只是加上自己的邏輯就以為萬事大吉。這種情況在真實的產(chǎn)品代碼里非常常見,它是一大類Bug的根源,卻又藏得很深。

【結(jié)束語】

當(dāng)我們了解了本文所講的“以結(jié)果驅(qū)動代替條件驅(qū)動”這種實現(xiàn)模式后,即便不能按照這種模式修改“由堆積如山的條件”組成的產(chǎn)品代碼,也能通過本文的思路找到Bug的根源,從而找到合適的解決方案。


作于2018-5-23


------------------------------

心定時刻:夫道不欲雜,雜則多,多則擾,擾則憂,憂而不救。

------------------------------

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 34,734評論 18 399
  • Lua 5.1 參考手冊 by Roberto Ierusalimschy, Luiz Henrique de F...
    蘇黎九歌閱讀 14,256評論 0 38
  • 第2章 基本語法 2.1 概述 基本句法和變量 語句 JavaScript程序的執(zhí)行單位為行(line),也就是一...
    悟名先生閱讀 4,569評論 0 13
  • ¥開啟¥ 【iAPP實現(xiàn)進(jìn)入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個線程,因...
    小菜c閱讀 7,355評論 0 17
  • 電話不停在吵, 老板不停地鬧。 總逃不開工作表, 做完了又來了怎樣也甩不掉。 回家感覺真好, 別管世俗紛擾。 把一...
    賈小呆520閱讀 567評論 0 0

友情鏈接更多精彩內(nèi)容