代码简洁思考

141 阅读4分钟

注:相关信息已进行了脱敏处理,确保敏感信息不被泄露

代码简洁思考

纬度:可读性、可维护性、美观性

一、结构化数据管理

通过使用结构体来集中管理相关数据,避免散乱的全局变量和重复的参数传递。对于相关的数据,考虑将它们封装成结构体或对象,这样不仅能更好地组织代码,还能清晰表达数据的用途

实践:在某些任务执行队列逻辑中,有多处逻辑需要将任务从执行队列投递到重试队列进行延迟执行,所以将重试队列的数据封装为 RetryTaskData 结构体,参数可以包含重试标志、延迟时间等信息

type RetryTaskData struct {
    isRetry   bool
    delayTime int
}

二、单一职责原则、简化逻辑表达

确保每个函数或方法仅承担单一的责任,避免功能过于复杂,遵循单一责任原则,将复杂的逻辑分解成多个简单的函数。简化逻辑表达,避免冗长、重复的逻辑表达式,以及嵌套过深的条件判断与循环

实践一:在任务执行队列中,将重试的逻辑提取到一个单独的函数中,使得每个函数仅做一件事

// 任务重新投递
func RetryTask(ctx context.Context, taskId int64, delayTime int64, dayTime int64) (err error) {
    tag := "TaskRetry"
    // 构造结构体,序列化
    retryTask := Task{
       TaskId: taskId,
    }
    retryTaskJson, err := json.Marshal(retryTask)
    if err != nil {
       logger.Ex(ctx, tag, "数据序列化失败, taskId:%s, err: %v", taskId, err)
       return
    }
    delayTime = time.Now().Unix() + delayTime

    if dayTime > 0 {
       delayTime = dayTime
    }
    // 投递到指定队列
    err = delay.DelaySendMessageNx(ctx, rediskey.NewTaskQueue, retryTaskJson, delayTime)
    if err != nil {
       logger.Ex(ctx, tag, "数据写入失败, taskId:%s, err: %v", taskId, err)
       return
    }
    logger.Ix(ctx, tag, "数据写入成功, taskId:%s", taskId)

    return
}

实践二:在某些统计脚本中,将按主体ID走的逻辑抽离出来放到 run 方法中,提高代码的美观性与可读性

func (r *Stats) Run(ctx context.Context) {
    r.ctx = ctx
    tag := `cron.Stats.Run`
    logger.Ix(r.ctx, tag, "start cron work")
    // 获取有效的主体ID
    corpList, err := corp.GetCorpList(ctx, GetCorpListReq{})
    if err != nil {
       logger.Ex(ctx, tag, "查询主体列表失败, err:%+v", err)
       return
    }
    // 执行统计脚本
    for _, corp := range corpList {
       SyncStats(ctx, corp.Id)
    }
    logger.Ix(r.ctx, tag, "cron work done")
}

三、defer简化资源释放与重试逻辑

使用 defer 来延迟执行一些清理工作或后续操作,尤其适合处理如重试机制等逻辑,避免在逻辑中插入冗余的代码

实践一:使用 defer 来统一执行重试操作,避免重复的代码

defer func() {
    if retryData.isRetry {
        log.Infof("Retrying with delay %d", retryData.delayTime)
        time.Sleep(time.Duration(retryData.delayTime) * time.Second)
        RetryTask()  // 重试任务
    }
}()

实践二:使用 defer 来执行导出操作,如文件导出等

// 创建文件
file := NewFile()
sheet := "Sheet1"
// 设置单元格样式
style := Style{
    Alignment: &Alignment{
       Horizontal: "left",
       Vertical:   "center",
       WrapText:   true,
    },
}
styleID, err := file.NewStyle(&style)
if err != nil {
    logger.Ex(ctx, tag, fmt.Sprintf("创建样式失败 err = %+v", err))
    return
}
// 设置列样式
_ = file.SetColStyle(sheet, "A", styleID)
// 设置表头
headers := []string{
    "统计日期", "有效链接数量", "日访问uv", "日新增客户", "累计访问uv", "累计新增客户", "结余使用量",
}

for col, header := range headers {
    cell := fmt.Sprintf("%s1", string(rune('A'+col)))
    _ = file.SetCellValue(sheet, cell, header)
}

// 导出文件
defer func() {
    if err != nil {
        logger.Ex(ctx, tag, "导出因为错误不执行,err: %+v", err)
        return
    }
    fileName := fmt.Sprintf("统计数据表_%s_%s.xlsx", GetDate(), GenerateRandomStr(6))
    ctx.Header("Content-Type", "application/octet-stream")
    ctx.Header("Content-Disposition", `attachment; filename=`+fileName)
    ctx.Header("Content-Transfer-Encoding", "binary")
    err = file.Write(ctx.Writer)
    if err != nil {
       logger.Ex(ctx, tag, `写入文件异常, req: %#v, err: %v`, req, err)
    }
    return
}()

四、其他

  • 避免重复代码与魔法数字:减少重复代码,避免硬编码的数字或常量,提升代码的可读性和可维护性,使用常量和配置代替魔法数字,使得代码更具可配置性
  • 合理的日志记录与注释:通过适当的日志记录和注释,帮助后续开发人员快速理解代码的意图和流程,记录重要的操作,特别是错误和警告信息
  • 条件判断的优化与清晰化:简化复杂的条件判断,通过提取条件判断的逻辑到独立的函数,增加可读性,比如将环境判断等条件逻辑提取为单独的函数或变量,避免冗长的 if 语句
// 函数使用
if isProductionEnvironment() {
    // 处理生产环境逻辑
}
  • 统一的命名规范:确保命名统一且有意义,遵循一致的命名规范,避免含糊不清或过于复杂的名称。使用简洁且明确的命名,确保代码在阅读时能够一眼看出其作用
  • 避免全局状态:避免过多的全局状态或共享数据,尽量将数据封装在局部作用域中,传递参数而非依赖全局变量,避免不必要的副作用,保证代码的模块化和独立性