GO 中优雅编码和降低圈复杂度

2023-10-05 14:58:32 浏览数 (1)

本次主要是聊聊关于使用接口抽象和降低圈复杂度的方式

工作中,难免会遇到老项目老代码,不仅仅需要我们维护,可能还需要我们在原来的垃圾代码上进行新增功能或者是进行优化调整

例如

现有的老代码中关于用户系统这一块就已经经是摇摇欲坠,牵一发而动全身,并且去弄清其中的业务细节,那可以说是很难拨开迷雾,甚至交接都是一句话的那种,更是难上加难

这种情况,相信每个公司都会存在,毕竟过去的需求,过去的标准,放到现在来看,啥也不是

若是很多代码都是面向过程的,各种业务逻辑,非业务的逻辑都混合在一起,主流程上插入一些乱七八糟的逻辑,上下文并没有啥关系的东西,一个函数上千行的代码也是随处可见,这种情况狗看了都摇头

对业务函数需要做基本的封装

首先咱们编码前一定会去捋清楚基本的需求,设计,以及实现流程,对于需要用到的工具我们会对代码结构进行分层

例如一些与业务主逻辑没有什么关联的功能就可以独立封装,便于维护和使用,例如:

  • 工具包(例如语言中的各种计算,数据处理,加解密等等)
  • 基本的 rpc 通信
  • http 相关的各种通信方式
  • 基本的中间件,拦截器,打点接口延时等等
  • 数据库操作(独立封装 DAO 层提供出来)
  • 缓存操作
  • 消息队列
  • ...等等

尽可能的将这些单独的功能模块拆解出去,独立出来,单独维护

对于那种没有必要同步的功能,完全可以通过异步化来进行处理,异步的话相信你会很容易想到消息队列来进行实现

自然实际项目中你能够看到最开始可能也会这样去做,但是随着业务越来越复杂,这些独立的模块被各种包进行使用,甚至有的开始慢慢的弄成定制化的方式

例如

代码语言:javascript复制
func OpenTenant(){
    // 校验基本租户信息
    // 检查租户是否特权,完成权限分配
    // 检查实际开户的线路,分配各种租户下的必备账号
    // 完成各种系统的对接交互
    // 进行数据库操作
    // 返回结果
}

对于一个基本的开户流程,我们或许可以在代码中看到第一步做什么,第二步又做什么,第三步... ,然而每一个大步骤下面还有各种小步骤,每一个小步骤也会有自己的复杂逻辑

虽然有了基本的封装,但是使用的时候,可能还是会写到哪,需要啥就去按需定义啥

最终就会看到一个函数上千行,让你去阅读和维护,你内心能不拒绝吗吗?

发现对模块进行独立封装还是不太够,代码里面太多的冗余代码,这个时候咱们就可以使用接口来做抽象

用接口来做抽象

使用接口来做抽象的话,相当于是提前考虑好这一类的业务需要去考虑哪些问题,需要注意哪些场景,需要实现哪一些接口

不同的对象各自去实现自己的内容就可以了,单独去维护自己的对象

例如上面的 A 系统的开户流程

代码语言:javascript复制
// 开户 interface{}
type OpenTenant interface{
   ValidateTenantInfo(xxx)xxx // 校验基本租户信息
   CheckPrivilege(xxx) xxx // 检查租户是否特权,完成权限分配
   CheckLine(xxx) xxx // 检查实际开户的线路
   ProcessNeccessaryAccount(xxx) xxx //分配各种租户下的必备账号
   ProcessNoticeMsg(xxx) xxx// 完成各种系统的对接交互
   AddTenant(xxx) xxx// 进行数据库操作
}

这仅仅是一个 demo,对于一个开户 interface{} 来说,A 系统可以去实现,B 系统仍然也可以去实现,各自完成自己的内容,例如这样

对于优化代码的话,我们就可以将上述的一些实现步骤,放到这个接口中来即可

咱们定义接口,更多的是去规范流程和便于维护,这样还可以让我们的程序往高内聚低耦合方面去靠,不同的对象之间,完全是安全的,自己玩自己的一套,只不过遵循的规范是一样的的

尽可能降低圈复杂度

圈复杂度也可以理解为条件复杂度,是一种用来衡量代码复杂度的标准

例如一些没有判断语句的代码,圈复杂度就是 1

如果是 if...else 那么圈复杂度就是 2 ,简单的就可以理解为涉及到判断条件的数量,那么就 1

例如有这样的代码

代码语言:javascript复制
func testDemo() {
    var op OpenTenant
    switch TenantType {
        case A:
            op = a.New()
        case B:
            op = b.New()
        case C:
            op = c.New()
        default:
           ...
    }
    op.ValidateTenantInfo()....
}

那么就如上demo ,来看,圈复杂度就是 4 ,其中有 3 个判断条件和一个默认的正常顺序,因此是 3 1 = 4

这个时候,我们可以如何降低圈复杂度呢?

我们完全就可以使用表格的方式,访问数据直接访问表格就可以了,尽可能的减少这些判断条件,例如我们就可以这样来写

代码语言:javascript复制
var openTenantMap = map[string]openTenantObject{
    A: a.New(),
    B: b.New(),
    C: c.New(),
}
func testDemo(){
    op := openTenantMap[TenantType ]
    ...
    op.ValidateTenantInfo()
    ...
}

这种方式,是不是就可以将圈复杂度降低到 1 了呢?而且看起来也优雅了很多

总结

主要叮嘱了我们维护和开发的时候,要重视封装,重视抽象,重视降低圈复杂度

只要你用心去打磨,自然会变得越来越好

但是可别生搬硬套,毕竟一些定制化的需求,定制化的代码你去做接口抽象是没有啥意义的,一起加油吧,xdm

至此,本次就是这样,希望能够给你带来一丁点帮助

0 人点赞