一個(gè)Go方法,五種變換
one-way-to-do-it-six-variations,土撥鼠在這里加了一些自己的理解。文章: https://phlatphrog.medium.com/one-way-to-do-it-six-variations-cd58602ac06d
代碼倉庫: https://github.com/pdk/oneway
我們先來模擬一段開車去商場購物的代碼。主要經(jīng)歷三個(gè)階段:開車去商店、到商店購物、然后購物完成開車回家。每個(gè)過程都需要去check其中返回的err錯(cuò)誤并返回shopper對象。
//?開車去商店?
shopper,?err?:=?shopper.Drive(FuelNeededToGetToStore)
if?nil?!=?err?{
????log.Fatalf("could?not?complete?shopping:?%s",?err)
}
//?買雞蛋?
shopper,?err?=?shopper.BuyEggs(EggsRequired)
if?nil?!=?err?{
????log.Fatalf("could?not?complete?shopping:?%s",?err)
}
//?買完雞蛋開車回家?
shopper,?err?=?shopper.Drive(FuelNeededToGetHome)
if?nil?!=?err?{
????log.Fatalf("could?not?complete?shopping:?%s",?err)
}
變換1:err集中判斷
針對這段代碼,錯(cuò)誤處理代碼較多,可以將三個(gè)重復(fù)代碼塊抽離成一個(gè)代碼塊。代碼如下:
func?main(){
????shopper,?err?:=?shopper.Drive(FuelNeededToGetToStore)
????FatalIfErrNotNil(err)
????shopper,?err?=?shopper.BuyEggs(EggsRequired)
????FatalIfErrNotNil(err)
????shopper,?err?=?shopper.Drive(FuelNeededToGetHome)
????FatalIfErrNotNil(err)
}??
func?FatalIfErrNotNil(err?error)?{
????if?nil?!=?err?{
????????log.Fatalf("could?not?complete?shopping:?%s",?err)
????}
}
不過在生產(chǎn)項(xiàng)目中,正常業(yè)務(wù)我們一般不會(huì)使用fatal去對非nil的err處理,fatal會(huì)以非0狀態(tài)退出程序。而是使用https://github.com/pkg/errors對err進(jìn)行wrap和cause 或者使用定義的業(yè)務(wù)相關(guān)的err并返回上一層做處理。這里也可以嘗試使用bool判斷來對err處理(不過之前土撥鼠之前群里也提出這樣的想法,大家都說多此一舉,尷尬)。
func?main(){
????shopper,?err?:=?shopper.Drive(FuelNeededToGetToStore)
????if?ErrorHandled(err)?{
??????return?...
????}
}
func?ErrorHandled(err)?bool?{
??if?nil?!=?err?{
????return?true
??}
??//?也可以對error進(jìn)行其他判斷操作
??...
??return?false
}
變換2:方法中包括err處理
是不是覺得上面的err處理還是不夠優(yōu)雅,所以咱們這里考慮把err的處理移出到主線之外。每個(gè)方法里都包含了對err的非nil判斷和返回。每個(gè)操作也只有在err為nil的情況下正常執(zhí)行 。只有在三個(gè)操作都結(jié)束后才真正對err進(jìn)行處理。
func?main(){
??shopper,?err?:=?shopper.Drive(FuelNeededToGetToStore,?nil)
????shopper,?err?=?shopper.BuyEggs(EggsRequired,?err)
????shopper,?err?=?shopper.Drive(FuelNeededToGetHome,?err)
????if?nil?!=?err?{
????????log.Fatalf("could?not?complete?shopping:?%s",?err)
????}
}
func?(s?Shopper)?Drive(fuelRequired?int,?err?error)?(Shopper,?error)?{
????if?nil?!=?err?{
????????return?s,?err
????}
???//?業(yè)務(wù)邏輯處理
????...?
}
變換3:使用函數(shù)對err分解
下面這個(gè)變換,拋棄了之前Shopper作為receiver的做法,而是將Shopper作為函數(shù)的參數(shù)。其實(shí)跟變換2很相似。這兒是單獨(dú)抽離出一個(gè)公共的函數(shù)ErrCheckFunc進(jìn)行err處理和函數(shù)執(zhí)行。
func?main(){
????drive?:=?ErrCheckFunc(Drive)
????buy?:=?ErrCheckFunc(BuyEggs)
????err,?shopper?:=?drive(nil,?shopper,?FuelNeededToGetToStore)
????err,?shopper?=?buy(err,?shopper,?EggsRequired)
????err,?shopper?=?drive(err,?shopper,?FuelNeededToGetHome)
????if?nil?!=?err?{
????????log.Fatalf("could?not?complete?shopping:?%s",?err)
????}
}
func?Drive(s?Shopper,?fuelRequired?int)?(Shopper,?error)?{
????if?nil?!=?err?{
????????return?s,?err
????}
???...
}
func?ErrCheckFunc(f?func(Shopper,?int)?(Shopper,?error))?func(error,?Shopper,?int)?(error,?Shopper)?{
????return?func(err?error,?s?Shopper,?arg?int)?(error,?Shopper)?{
????????if?nil?!=?err?{
????????????return?err,?s
????????}
????????s,?err?=?f(s,?arg)
????????return?err,?s
????}
}
變換4:單行操作
這次改進(jìn)是采用了裝飾器模式的思想把err的check邏輯和函數(shù)的執(zhí)行操作都封裝在了ErrCheckFunc一個(gè)方法中。相比變換3,是將ErrCheckFunc中的返回值中的函數(shù)入?yún)?code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(60, 112, 198);">arg轉(zhuǎn)移到了ErrCheckFunc入?yún)⒅械?code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(60, 112, 198);">arg參數(shù)。
func?main(){
????driveToStore?:=?ErrCheckFunc(Drive,?FuelNeededToGetToStore)
????buyEggs?:=?ErrCheckFunc(BuyEggs,?EggsRequired)
????driveHome?:=?ErrCheckFunc(Drive,?FuelNeededToGetHome)
??
????err,?shopper?:=?driveHome(buyEggs(driveToStore(nil,?shopper)))
????if?nil?!=?err?{
????????log.Fatalf("could?not?complete?shopping:?%s",?err)
????}
}
func?ErrCheckFunc(f?func(Shopper,?int)?(Shopper,?error),?arg?int)?func(error,?Shopper)?(error,?Shopper)?{
????return?func(err?error,?s?Shopper)?(error,?Shopper)?{
????????if?nil?!=?err?{
????????????return?err,?s
????????}
????????s,?err?=?f(s,?arg)
????????return?err,?s
????}
}
變換5:迭代變換
終極變換來了,這次變換采用了迭代的思想。ProcessSteps這里使用的是一個(gè)可變的函數(shù)參數(shù)。
func?main(){
????driveToStore?:=?Flavorize(Drive,?FuelNeededToGetToStore)
????buyEggs?:=?Flavorize(BuyEggs,?EggsRequired)
????driveHome?:=?Flavorize(Drive,?FuelNeededToGetHome)
??
????shopper,?err?:=?ProcessSteps(shopper,
????????driveToStore,
????????buyEggs,
????????driveHome,
????)
????if?nil?!=?err?{
????????log.Fatalf("could?not?complete?shopping:?%s",?err)
????}
}
func?ProcessSteps(s?Shopper,?steps?...func(Shopper)?(Shopper,?error))?(Shopper,?error)?{
????for?_,?step?:=?range?steps?{
????????var?err?error?
????????s,?err?=?step(s)
????????if?nil?!=?err?{
????????????return?s,?err
????????}
????}
????return?s,?nil
}
func?Flavorize(f?func(Shopper,?int)?(Shopper,?error),?arg?int)?func(Shopper)?(Shopper,?error)?{
?return?func(s?Shopper)?(Shopper,?error)?{
??return?f(s,?arg)
?}
}
小結(jié)
土撥鼠今天拿這個(gè)例子來展示給大家,主要是因?yàn)樵陧?xiàng)目中也會(huì)經(jīng)常遇到這樣的代碼邏輯場景,就想著看看別人是怎么去重構(gòu)改進(jìn)的,學(xué)習(xí)怎么去優(yōu)化重構(gòu),使代碼變得更好維護(hù)、易于擴(kuò)展、復(fù)用性高、可讀性高。希望大家也可以借鑒學(xué)習(xí),如果你有更好的變換和想法歡迎大家留言討論。
推薦閱讀
