一個(gè)Go方法晦鞋,五種變換
Go語(yǔ)言中文網(wǎng)??原文地址:https://mp.weixin.qq.com/s/GR-8Nv65N66xbOPL_Q1mxg
我們先來(lái)模擬一段開(kāi)車(chē)去商場(chǎng)購(gòu)物的代碼湿颅。主要經(jīng)歷三個(gè)階段:開(kāi)車(chē)去商店敬扛、到商店購(gòu)物、然后購(gòu)物完成開(kāi)車(chē)回家幔荒。每個(gè)過(guò)程都需要去check其中返回的err錯(cuò)誤并返回shopper對(duì)象糊闽。
//?開(kāi)車(chē)去商店?
shopper,?err?:=?shopper.Drive(FuelNeededToGetToStore)
ifnil!=?err?{
log.Fatalf("could?not?complete?shopping:?%s",?err)
}
//?買(mǎi)雞蛋?
shopper,?err?=?shopper.BuyEggs(EggsRequired)
ifnil!=?err?{
log.Fatalf("could?not?complete?shopping:?%s",?err)
}
//?買(mǎi)完雞蛋開(kāi)車(chē)回家?
shopper,?err?=?shopper.Drive(FuelNeededToGetHome)
ifnil!=?err?{
log.Fatalf("could?not?complete?shopping:?%s",?err)
}
變換1:err集中判斷
針對(duì)這段代碼,錯(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)
}
}
不過(guò)在生產(chǎn)項(xiàng)目中,正常業(yè)務(wù)我們一般不會(huì)使用fatal去對(duì)非nil的err處理姚垃,fatal會(huì)以非0狀態(tài)退出程序念链。而是使用https://github.com/pkg/errors對(duì)err進(jìn)行wrap和cause?或者使用定義的業(yè)務(wù)相關(guān)的err并返回上一層做處理。這里也可以嘗試使用bool判斷來(lái)對(duì)err處理(不過(guò)之前土撥鼠之前群里也提出這樣的想法积糯,大家都說(shuō)多此一舉掂墓,尷尬)。
func main(){
shopper,?err?:=?shopper.Drive(FuelNeededToGetToStore)
ifErrorHandled(err)?{
return...
}
}
func ErrorHandled(err)bool{
if nil!=?err?{
return true
}
//?也可以對(duì)error進(jìn)行其他判斷操作
...
return false
}
變換2:方法中包括err處理
是不是覺(jué)得上面的err處理還是不夠優(yōu)雅看成,所以咱們這里考慮把err的處理移出到主線(xiàn)之外君编。每個(gè)方法里都包含了對(duì)err的非nil判斷和返回。每個(gè)操作也只有在err為nil的情況下正常執(zhí)行 川慌。只有在三個(gè)操作都結(jié)束后才真正對(duì)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
}
變換3:使用函數(shù)對(duì)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)
}
}
funcDrive(s?Shopper,?fuelRequired int)(Shopper,?error){
if nil!=?err?{
returns,?err
}
...
}
funcErrCheckFunc(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)rg轉(zhuǎn)移到了ErrCheckFunc入?yún)⒅械腶rg參數(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)
}
}
funcErrCheckFunc(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)
returnerr,?s
}
}
變換5:迭代變換
終極變換來(lái)了力崇,這次變換采用了迭代的思想斗塘。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)
}
}
funcProcessSteps(s?Shopper,?steps?...func(Shopper)(Shopper,?error))(Shopper,?error){
for_,?step?:=range steps?{
var err?error
s,?err?=?step(s)
if nil!=?err?{
returns,?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)
}
}
土撥鼠今天拿這個(gè)例子來(lái)展示給大家亮靴,主要是因?yàn)樵陧?xiàng)目中也會(huì)經(jīng)常遇到這樣的代碼邏輯場(chǎng)景馍盟,就想著看看別人是怎么去重構(gòu)改進(jìn)的,學(xué)習(xí)怎么去優(yōu)化重構(gòu)茧吊,使代碼變得更好維護(hù)贞岭、易于擴(kuò)展、復(fù)用性高搓侄、可讀性高。希望大家也可以借鑒學(xué)習(xí)讶踪,如果你有更好的變換和想法歡迎大家留言討論芯侥。