重构代码的一些想法

重构代码的一些想法

最近需要新写一个业务模块,这个业务模块和两年前自己写的一个业务功能高度类似,就想着能不能拿过来改改就行。这个业务模块使用 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{})
}

posted on 2022-03-26 21:59  文一路挖坑侠  阅读(97)  评论(0编辑  收藏  举报

导航