善用闭包(closure)让Go代码更优雅

通常来说我们降低一个函数的复杂度的方法是拆分。即大事化小,各个击破的原理。不过拆分成函数调用有一个问题,那就是修改 函数参数的时候很蛋疼。

比如,原本由于各种原因我们有一个巨长的函数,他的作用是发工资:

func loooooooooooog(userID int) {
    // 比如1000行代码
}

因为我们要拆分成若干个小函数,这自然是一件好事情,降低了变量之间的依赖:

func loooooooooooog(userID int) {
    checkUser(userID)
    checkSalary(userID)
    Send(userID, salary)
    Notify(userID)
}

但是现在我们决定引入 Context,用以随时取消Goroutine的执行,当然啦,在Go里没法自动取消,只能手动档:

func checkUser(ctx context.Context, userID int) error {
    select {
        case <-ctx.Done():
            err := ctx.Err()
            log.Printf("ctx is done for %s", err)
            return err
        default:
    }

    // blablabla 业务代码
}

同样的代码还会出现在若干个子函数中,例如 checkSalary, Send, Notify等等。难道有谁愿意写这么多重复的代码吗? 至少我不愿意,所以捏,我决定创造一个函数,取缔这些重复的context检查,幸好Go支持闭包,要不然就不好弄了:

// Execute execute fn until ctx.Done() is received
func Execute(ctx context.Context, fn func()) error {
    select {
    case <-ctx.Done():
        err := ctx.Err()
        log.Printf("ctx is done for %s", err)
        return err
    default:
        return fn()
    }
}

那么接下来我就可以这样用了:

func loooooooooooog(ctx context.Context, userID int) {
    Execute(ctx, func() error {/*业务代码在这里*/})
    Execute(ctx, func() error {/*业务代码在这里*/})
}

不过,没有银弹。这样做又带来了一个副作用,由于闭包是可以访问外层函数的变量的,也就意味着还是有可能产生变量混用的可能性, 但是我认为这个可以人为消除,也就是说,写的时候注意,不是公共变量的,不要乱用,尽量在匿名函数内使用内部变量,当然也不是 没有解决方案,那就是外边包装一层匿名函数+调用,不过那样的缺点就是更复杂了,没有关系,我们手动控制好即可。

所以说是 “善用”闭包,任何东西,都要拿捏好,不能滥用的。


参考资料:


微信公众号
关注公众号,获得及时更新

更多文章
  • Linux高分屏支持
  • GCC默认的头文件搜索路径
  • 读《远见-如何规划职业生涯3大阶段》
  • 后端工程师学前端(五): SASS
  • 后端工程师学前端(四): CSS进阶(盒子模型)
  • 读《投资中最简单的事》
  • 后端工程师学前端(三): CSS进阶(特指度、单位和字体族)
  • 后端工程师学前端(二): CSS基础知识(规则与选择器)
  • Swift语法笔记
  • 后端工程师学前端(一): HTML
  • 读《管理的实践》
  • frp 源码阅读与分析(二):TCP内网穿透的实现
  • 五天不用微信 - 爽得很
  • frp 源码阅读与分析(一):流程和概念
  • 学习frp源码之简洁的在两个connection之间转发流量