高质量编程 | 青训营笔记

54 阅读3分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的第1篇笔记

高质量编程

  • 各种边界条件是否考虑完备
  • 异常情况处理,稳定性保证
  • 易读易维护
  • 高质量的编程需要注意以下原则:简单性、可读性、生产力

常见编码规范

代码格式

  • 推荐使用gofmt自动格式化代码
  • goimports工具:gofmt+依赖包管理

注释

  • 解释代码作用:适合注释公共符号

    •  // Open opens the named file for reading. If successful, methods on the returned file can be used for reading; 
       // the associated file descriptor has mode O_RDONLY.
       // If there is an error, it will be of type *PathError.
       func Open(name string) (*File, error) {
           return OpenFile(name, O_RDONLY, 0)
       }
      
    • 包中声明的每个公共的符号:变量、常量、函数以及结构都需要添加注释
    • 不需要注释实现接口的方法
  • 解释代码如何做的:适合注释实现过程

    •  // Add the Referer header from the most recent
       // request URL to the new one, if it's not https->http:
       if ref := refererForURL(reqs[len(reqs)-1].URL, req.URL); ref != "" {
           req.Header.Set("Referer", ref)
       }
      
  • 解释代码实现原因:适合解释代码的外部因素;提供额外上下文

    •  if ireq.GetBody == nil && ireq.outgoingLength() != 0 {
           // We had a request body, and 307/308 require
           // re-sending it, but GetBody is not defined. So just
           // return this response to the user instead of an
           // error, like we did in Go 1.7 and earlier.
           shouldRedirect = false
       }
      
  • 解释代码什么情况下会出错:适合解释代码的限制条件

命名规范

  • 变量

    • 简洁
    • 缩略词全大写,但当其位于变量开头且不需要导出时,使用全小写
    • 变量距离其被使用的地方越远,则需要携带越多的上下文信息
  • 函数

    • 函数名不携带包名的上下文信息
    • 函数名尽量简短
    • 当名为foo的包某个函数返回类型Foo时,可以省略类型信息而不导致歧义,比如http.Serve而不是http.ServeHTTP
    • 当名为foo的包某个函数返回类型T时,可以在函数名中加入类型信息
  • package包

    • 只由小写字母组成。不包含大写字母和下划线等字符
    • 简短并包含一定的上下文信息。例如 schema、task 等
    • 不要与标准库同名。例如不要使用 sync 或者 strings
  • 尽量满足

    • 不使用常用变量名作为包名。例如使用bufio而不是buf
    • 使用单数而不是复数。例如使用encoding而不是encodings
    • 谨慎地使用缩写。例如使用fmtformat更简洁

控制流程

  • 避免嵌套,保持正常流程清晰
  • 尽量保持正常代码路径为最小缩进:优先处理错误/特殊情况,尽早返回或继续循环来减少嵌套

错误与异常处理

  • 简单错误:仅出现一次的错误,且在其它地方不需要捕获该错误

    • 优先使用errors.New来创建匿名变量直接表示简单错误
    • 如果有格式化的需求,使用fmt.Errorf
    •  func defaultCheckRedirect(req *Request, via []*Request) error {
           if len(via) >= 10 {
               return errors.New("stopped after 10 redirects")
           }
           return nil
       }
      
  • 错误的WarpUnwrap

    • 错误的Wrap提供一个error嵌套另一个error的能力,从而生成一个error跟踪链
    • fmt.Errorf中使用%w关键字将一个错误关联至错误链中
    •  list, _, err := c.GetBytes(cache.Subkey(a.actionID, "srcfiles"))
       if err != nil {
           return fmt.Errorf("reading srcfiles list: %w", err)
       }
      
  • 错误判定

    • 判定一个错误是否为特定错误,使用errors.Is
    • 不同于==,该方法可以判定错误链上是否包含该特定错误
    • 在错误链上获取特定种类的错误,使用errors.As(err, &pathError) //err错误链里获取pathError错误
  • panic:不建议在业务代码中使用panic

    • 如果当前 goroutine 中所有 deferred 函数都不包含 recover 就会造成整个程序崩溃
    • 当程序启动阶段发生不可逆转的错误时,可以在 init 或 main 函数中使用 panic
  • recover:处理panicdefer语句是后进先出

  • error尽可能提供简明的上下文信息链,方便定位问题

  • panic用于真正异常的情况

  • recover在当前的goroutine的被defer的函数中生效