所屬文章系列:尋找塵封的銀彈:自創(chuàng)實現(xiàn)模式
今天要寫第一篇自創(chuàng)實現(xiàn)模式戈轿,也就是我自己獨家創(chuàng)造的一個實現(xiàn)模式蒜危。
先解釋一下什么是實現(xiàn)模式盼砍,這個詞是Kent Beck《實現(xiàn)模式》的書名碾盟,意思就是在代碼的最底層的模式锄俄,即類內(nèi)部及函數(shù)內(nèi)部的模式奶赠,而設計模式是多個類之間的模式。
說是獨家創(chuàng)造药有,其實很可能早已有人提出來過,或者實踐過愤惰,只是我不知道而已,就像設計模式的最初作者也并非是《設計模式》書中的四個作者一樣宦言,我只是一個模式的總結者而已。無論如何奠旺,對程序員有幫助蜘澜,才是最重要的,也是我的初衷鄙信。
【動機】
先看一段看起來邏輯清晰瞪醋,但有漏洞的代碼:
void DoAdjust(ClassA **a, ClassB **b) {
? ??if ((*a) == NULL)?{
? ? ? ??if ((*b)->next?!= NULL)?{
? ? ? ? ? ??*a = *b;
? ??? ? ? ? *b = (*b)->next;
? ??? ??}
? ??}
}
注:代碼中的雙指針(**)只是為了在函數(shù)體內(nèi)給外部的指針賦值,本質是輸出參數(shù)装诡,方式是解引用银受,即*a。
表面上看起來鸦采,這段代碼邏輯清晰宾巍,但只是看似清晰而已,實際上編寫這段代碼的人并沒有搞清楚這兩個變量在所有執(zhí)行路徑到底該如何賦值赖淤。
這段代碼漏掉了一種情況:當(*a) != NULL時蜀漆,b也應該根據(jù)(*b)->next來決定取值,具體的取值應該有特定的業(yè)務邏輯咱旱。
當我根據(jù)需求編寫了較為完備的測試用例之后确丢,我才發(fā)現(xiàn)了這個Bug。經(jīng)過分析吐限,在這段代碼中鲜侥,確實應該有這種業(yè)務邏輯,但是這段代碼卻使用了默認的“不作為”行為诸典,導致錯誤發(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,所以枚舉起來并不太難膛檀。
但是宿刮,當條件的數(shù)目猛增到10個時胡桃,它們的變化就會有2的10次方個翠胰,也就是1024個之景,那么枚舉的方式就顯得很傻锻狗,而且容易出錯轻纪。不過叠纷,程序員總是想追求好方法崇众,讓自己在檢查代碼漏洞的時候可以更加“懶惰”一點顷歌。
有沒有一種方法讓這段代碼看上去一目了然衙吩,一眼就能看出有沒有漏洞?
【典型代碼】
答案當然是有澈蚌。用的方法是“以結果驅動代替條件驅動”。
先看修改后的代碼盈电,下一小節(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)劣對比】
使用該模式后的代碼,看起來有點累贅吸重,但你稍稍看仔細一點嚎幸,就能完全懂得代碼的所有意圖和所有變化,做到了然于心。
為什么這樣說舶担?
這個函數(shù)要解決的根本問題是為*a和*b返回值闸氮,而使用該模式的代碼恰恰是從這個為*a和*b賦值的角度出發(fā)的译断,我們一下子就能看出*a和*b是不是在退出的時候被賦過值了。這就是“以結果驅動代替條件驅動”這個實現(xiàn)模式中的“結果驅動”翎蹈。
反觀未使用該模式的代碼,它是從條件的角度出發(fā),條件包括*a、*b和(*b)->next,我們只看到了眼花繚亂的各種條件組合蕊唐,卻容易忽略掉最終要給每個返回變量賦值。這就是“以結果驅動代替條件驅動”這個實現(xià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;
? ??}
? ??...
}
這種代碼就會讓人思維混亂返十,理不清頭緒洞坑。
【思維進階(二):多個條件的優(yōu)先級】
這個模式會迫使你思考:
到底是在什么特定情況下蝇率,該給什么值本慕。而不是遇到“條件1”先給賦一個值,再遇到“條件2”再給賦一個值锅尘,就像剛舉的這個反面教材代碼那樣。當我們有本文這種模式的思維時浪腐,就會豁然開朗议街,一切邏輯都是那么清晰可見!
再看這種“營養(yǎng)不良”的代碼產(chǎn)生的原因:一般是由于多個人先后修改代碼產(chǎn)生的特漩。后面修改代碼的人不去看已有代碼犹菱,他也不知道兩個條件的優(yōu)先級是什么腊脱,他只是加上自己的邏輯就以為萬事大吉。這種情況在真實的產(chǎn)品代碼里非常常見悍抑,它是一大類Bug的根源,卻又藏得很深搜骡。
【結束語】
當我們了解了本文所講的“以結果驅動代替條件驅動”這種實現(xiàn)模式后记靡,即便不能按照這種模式修改“由堆積如山的條件”組成的產(chǎn)品代碼,也能通過本文的思路找到Bug的根源摸吠,從而找到合適的解決方案。
作于2018-5-23
------------------------------
心定時刻:夫道不欲雜呀洲,雜則多啼止,多則擾献烦,擾則憂,憂而不救贰您。
------------------------------