重構代碼的一些想法

重構代碼的一些想法

最近需要新寫一個業務模塊,這個業務模塊和兩年前自己寫的一個業務功能高度類似,就想着能不能拿過來改改就行。這個業務模塊使用 golang 實現的,是我寫的第一個 golang 代碼。
以上爲背景,但是代碼拿過來後發現通用性太差,雖然業務有相似的地方,但是小改達不到自己的期望,於是做了一下幾個部分的重構。

函數儘量選擇非對象化實現

很多功能函數的實現都強行面向對象,多餘的維護了一些不必要的狀態信息;

直接拿 golang 舉個例子,一個調用 docker sdk 來啓動容器的接口 Start 調用如下

err = cli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{})

cli 的構造可能會發生錯誤

cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())

獲取容器 id 也可能發生錯誤

resp, err := cli.ContainerInspect(context.Background(), name)

也就是說,如果需要實現一個 Start,需要額外處理兩個錯誤。
假設提供的對象化實現,表面上看有一個好處,可以複用基於對象的一些其他變量,例如 cli 指針,這樣我不僅 Start 可以用,Stop 也能夠用這個變量。
帶來的問題也是顯而易見的,對於構造函數而言,cli指針不管是從外部傳入還是直接在內部進行構造都有失敗的風險。

type DockerManager struct {
    cli         *client.Client
    ctx         context.Context
    name        string
    id          string
}
// New...
func (d *DockerManager) Start() error {
    return d.cli.ContainerStart(d.ctx, d.id, types.ContainerStartOptions{})
}

對於一個容器的啓動,調用是這個樣子的,d.Start(),d 有可能不是一個正常的對象,直接調用可能有panic的風險,又需要一些其他的措施來預防。
也帶來一個傳染性的問題,對於外部調用者來說,構造一個 DockerManager 對象有可能是直接在函數的執行過程中完成的,也有可能是外部構造函數中,這樣的影響面又變大了,一層一層往外傳染,失敗該怎麼辦。
當然對於 golang 而言,一般構造函數都不會選擇失敗的做法,所以我們這裏換一種實現,構造函數只構造無狀態的 name

type DockerManager struct {
    name        string
}
// func NewDockerManager(name string)
func (d *DockerManager) Start() error {
    cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
    if err != nil {
        return err
    }
    
    ctx := context.Background()
    resp, err := cli.ContainerInspect(ctx, d.name)
    if err != nil {
        return err
    }
    
    return cli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{})
}

這樣構造函數永遠不失敗,對於對象而言,不會影響到外部的調用,但是這樣做基本就退化成非面向對象的實現了,
去掉對象的語言,name 從參數傳進來,在有些場景之下,構造函數再調用,不如直接這樣調用來的方便一些,錯誤於外部而言只需要處理一次。

func Start(name string) error {
    cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
    if err != nil {
        return err
    }
    ctx := context.Background()
    resp, err := cli.ContainerInspect(ctx, name)
    if err != nil {
        return err
    }
    return cli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{})
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章