摘要:寫代碼時(shí),代碼的運(yùn)行中的控制狀態(tài)或業(yè)務(wù)狀態(tài)是會讓你的代碼流程變得混亂的一個(gè)重要原因,重構(gòu)箭頭型代碼的一個(gè)很重要的工作就是重新梳理和描述這些狀態(tài)的變遷關(guān)系。重構(gòu)箭頭型代碼其實(shí)是在幫你重新梳理所有的代碼和邏輯,這個(gè)過程非常值得為之付出。
所謂箭頭型代碼,基本上來說就是下面這個(gè)圖片所示的情況。
那么,這樣“箭頭型”的代碼有什么問題呢?看上去也挺好看的,有對稱美。但是……
關(guān)于箭頭型代碼的問題有如下幾個(gè):
1)我的顯示器不夠?qū)挘^型代碼縮進(jìn)太狠了,需要我來回拉水平滾動條,這讓我在讀代碼的時(shí)候,相當(dāng)?shù)牟皇娣?/p>
2)除了寬度外還有長度,有的代碼的if-else里的if-else里的if-else的代碼太多,讀到中間你都不知道中間的代碼是經(jīng)過了什么樣的層層檢查才來到這里的。
總而言之,“箭頭型代碼”如果嵌套太多,代碼太長的話,會相當(dāng)容易讓維護(hù)代碼的人(包括自己)迷失在代碼中,因?yàn)榭吹阶顑?nèi)層的代碼時(shí),你已經(jīng)不知道前面的那一層一層的條件判斷是什么樣的,代碼是怎么運(yùn)行到這里的,所以,箭頭型代碼是非常難以維護(hù)和Debug的。
代碼量如果再大一點(diǎn),嵌套再多一點(diǎn),你很容易會在條件中迷失掉(下面這個(gè)示例只是那個(gè)“大箭頭”下的一個(gè)小箭頭)
FOREACH(Ptr
int index = manager->expressionResolvings.Keys().IndexOf(argument.Obj()); if (index != -1) { auto type = manager->expressionResolvings.Values()[index].type; if (! types.Contains(type.Obj())) { types.Add(type.Obj()); if (auto group = type->GetTypeDescriptor()->GetMethodGroupByName(L"CastResult", true)) { int count = group->GetMethodCount(); for (int i = 0; i < count; i++) { auto method = group->GetMethod(i); if (method->IsStatic()) { if (method->GetParameterCount() == 1 && method->GetParameter(0)->GetType()->GetTypeDescriptor() == description::GetTypeDescriptor() && method->GetReturn()->GetTypeDescriptor() != description::GetTypeDescriptor () ) { symbol->typeInfo = CopyTypeInfo(method->GetReturn()); break; } } } } } }
}
上面這段代碼,可以把條件反過來寫,然后就可以把箭頭型的代碼解掉了,重構(gòu)的代碼如下所示:
FOREACH(Ptr
int index = manager->expressionResolvings.Keys().IndexOf(argument.Obj()); if (index == -1) continue; auto type = manager->expressionResolvings.Values()[index].type; if ( types.Contains(type.Obj())) continue; types.Add(type.Obj()); auto group = type->GetTypeDescriptor()->GetMethodGroupByName(L"CastResult", true); if ( ! group ) continue; int count = group->GetMethodCount(); for (int i = 0; i < count; i++) { auto method = group->GetMethod(i); if (! method->IsStatic()) continue; if ( method->GetParameterCount() == 1 && method->GetParameter(0)->GetType()->GetTypeDescriptor() == description::GetTypeDescriptor() && method->GetReturn()->GetTypeDescriptor() != description::GetTypeDescriptor () ) { symbol->typeInfo = CopyTypeInfo(method->GetReturn()); break; } }
}
這里的思路其實(shí)就是,讓出錯(cuò)的代碼先返回,前面把所有的錯(cuò)誤判斷全判斷掉,然后就剩下的就是正常的代碼了。
對于 if-else 語句來說,一般來說,就是檢查兩件事:錯(cuò)誤 和 狀態(tài)。
檢查錯(cuò)誤
對于檢查錯(cuò)誤來說,使用 Guard Clauses 會是一種標(biāo)準(zhǔn)解,但我們還需要注意下面幾件事:
1)當(dāng)然,出現(xiàn)錯(cuò)誤的時(shí)候,還會出現(xiàn)需要釋放資源的情況。你可以使用 goto fail; 這樣的方式,但是最優(yōu)雅的方式應(yīng)該是C++面向?qū)ο笫降?RAII 方式。
2)以錯(cuò)誤碼返回是一種比較簡單的方式,這種方式有很一些問題,比如,如果錯(cuò)誤碼太多,判斷出錯(cuò)的代碼會非常復(fù)雜,另外,正常的代碼和錯(cuò)誤的代碼會混在一起,影響可讀性。所以,在更為高組的語言中,使用 try-catch 異常捕捉的方式,會讓代碼更為易讀一些。
檢查狀態(tài)
對于檢查狀態(tài)來說,實(shí)際中一定有更為復(fù)雜的情況,比如下面幾種情況:
1)像TCP協(xié)議中的兩端的狀態(tài)變化。
2)像shell各個(gè)命令的命令選項(xiàng)的各種組合。
3)像游戲中的狀態(tài)變化(一棵非常復(fù)雜的狀態(tài)樹)。
4)像語法分析那樣的狀態(tài)變化。
對于這些復(fù)雜的狀態(tài)變化,其本上來說,你需要先定義一個(gè)狀態(tài)機(jī),或是一個(gè)子狀態(tài)的組合狀態(tài)的查詢表,或是一個(gè)狀態(tài)查詢分析樹。
寫代碼時(shí),代碼的運(yùn)行中的控制狀態(tài)或業(yè)務(wù)狀態(tài)是會讓你的代碼流程變得混亂的一個(gè)重要原因,重構(gòu)“箭頭型”代碼的一個(gè)很重要的工作就是重新梳理和描述這些狀態(tài)的變遷關(guān)系。
總結(jié)
好了,下面總結(jié)一下,把“箭頭型”代碼重構(gòu)掉的幾個(gè)手段如下:
1)使用 Guard Clauses 。 盡可能的讓出錯(cuò)的先返回, 這樣后面就會得到干凈的代碼。
2)把條件中的語句塊抽取成函數(shù)。 有人說:“如果代碼不共享,就不要抽取成函數(shù)!”,持有這個(gè)觀點(diǎn)的人太死讀書了。函數(shù)是代碼的封裝或是抽象,并不一定用來作代碼共享使用,函數(shù)用于屏蔽細(xì)節(jié),讓其它代碼耦合于接口而不是細(xì)節(jié)實(shí)現(xiàn),這會讓我們的代碼更為簡單,簡單的東西都能讓人易讀也易維護(hù),寫出讓人易讀易維護(hù)的代碼才是重構(gòu)代碼的初衷!
3)對于出錯(cuò)處理,使用try-catch異常處理和RAII機(jī)制。返回碼的出錯(cuò)處理有很多問題,比如:A) 返回碼可以被忽略,B) 出錯(cuò)處理的代碼和正常處理的代碼混在一起,C) 造成函數(shù)接口污染,比如像atoi()這種錯(cuò)誤碼和返回值共用的糟糕的函數(shù)。
4)對于多個(gè)狀態(tài)的判斷和組合,如果復(fù)雜了,可以使用“組合狀態(tài)表”,或是狀態(tài)機(jī)加Observer的狀態(tài)訂閱的設(shè)計(jì)模式。這樣的代碼即解了耦,也干凈簡單,同樣有很強(qiáng)的擴(kuò)展性。
5) 重構(gòu)“箭頭型”代碼其實(shí)是在幫你重新梳理所有的代碼和邏輯,這個(gè)過程非常值得為之付出。重新整思路去想盡一切辦法簡化代碼的過程本身就可以讓人成長。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/76719.html
摘要:無論如何,單元測試一直是一中非常重要卻常常被忽視的技能。在實(shí)踐中,重構(gòu)的要求是很高的它需要有足夠詳盡的單元測試,需要有持續(xù)集成的環(huán)境,需要隨時(shí)隨地在小步伐地永遠(yuǎn)讓代碼處于可工作狀態(tài)下去進(jìn)行改善。 showImg(https://segmentfault.com/img/bVbttWF?w=1000&h=528); 五月初的時(shí)候朋友和我說《重構(gòu)》出第 2 版了,我才興沖沖地下單,花了一個(gè)...
摘要:以下兩個(gè)要點(diǎn)將會對任何微服務(wù)重構(gòu)策略產(chǎn)生重大影響。批量替換通過批發(fā)更換,您可以一次性重構(gòu)整個(gè)應(yīng)用程序,直接從單體式轉(zhuǎn)移到一組微服務(wù)器。如果您通過使用破解您的微服務(wù)器,那么每個(gè)域?qū)@一個(gè)用例,或者更常見的,一組相互關(guān)聯(lián)的用例。 在決定使用微服務(wù)之后,為了將微服務(wù)付諸實(shí)踐,也許你已經(jīng)開始重構(gòu)你的應(yīng)用程序或把重構(gòu)工作列入了待辦事項(xiàng)清單。 無論是哪種情況,如果這是你第一次重構(gòu)應(yīng)用程序,那么您...
摘要:重構(gòu)定義重構(gòu)是對軟件內(nèi)部結(jié)構(gòu)的調(diào)整,目的是在不改變軟件可觀察行為的前提下,提高其可理解性,降低其修改成本。重構(gòu)節(jié)奏小步前進(jìn),頻繁測試。 1.重構(gòu)定義: 重構(gòu)是對軟件內(nèi)部結(jié)構(gòu)的調(diào)整,目的是在不改變軟件可觀察行為的前提下,提高其可理解性,降低其修改成本。 2.重構(gòu)節(jié)奏: 小步前進(jìn),頻繁測試。 3.重構(gòu)意義: 1.改進(jìn)軟件設(shè)計(jì) 2.使軟件更容易被理解 3.幫助找到bug 4.提高編程速度 惡...
摘要:如果你不能以高標(biāo)準(zhǔn)來要求自己,即使你看再多的如何寫出高質(zhì)量代碼,懂再多的代碼規(guī)范,也是沒有用,最終還是會寫出低質(zhì)量代碼。建議先從代碼規(guī)范開始,熟悉代碼規(guī)范,遵循規(guī)范寫代碼,直到成為習(xí)慣,然后再學(xué)習(xí)其它方法,最終寫出高質(zhì)量代碼。 更多文章 什么是高質(zhì)量代碼? 高質(zhì)量代碼具有以下幾個(gè)特點(diǎn): 可讀性高 結(jié)構(gòu)清晰 可擴(kuò)展(方便維護(hù)) 代碼風(fēng)格統(tǒng)一 低復(fù)雜性 簡練 編寫高質(zhì)量代碼主要遵循以下...
摘要:改進(jìn)代碼設(shè)計(jì)的一個(gè)重要原則就是消除重復(fù)代碼使軟件更容易被理解優(yōu)秀的代碼能夠讓接收你代碼的付出更少的學(xué)習(xí)成本。重構(gòu)更容易找到重構(gòu)能加深對代碼的理解??梢灾貥?gòu)的情況添加功能時(shí)可以重構(gòu)。說明你沒有發(fā)現(xiàn)代碼的錯(cuò)誤。需要重構(gòu)復(fù)審代碼時(shí)可以重構(gòu)。 為何重構(gòu) 重構(gòu)不是銀彈,但是幫助你達(dá)到以下幾個(gè)目的 改進(jìn)軟件設(shè)計(jì) 不良的程序需要更多的代碼。而代碼越多,正確的修改就越困難。改進(jìn)代碼設(shè)計(jì)的一個(gè)重要原則就...
閱讀 2921·2021-11-22 11:56
閱讀 3632·2021-11-15 11:39
閱讀 955·2021-09-24 09:48
閱讀 824·2021-08-17 10:14
閱讀 1405·2019-08-30 15:55
閱讀 2805·2019-08-30 15:55
閱讀 1394·2019-08-30 15:44
閱讀 2843·2019-08-30 10:59