第六届字节跳动青训营第三课 | 青训营

77 阅读4分钟

第三节课之高质量编程

简介

要完成高质量编程,首先是需要保证正确性,这不仅需要开发者在编写代码时对所有可能情况有全面的了解,同时也需要大量完备的测试去验证其正确性。 除此之外,简单性、可读性和生产力则是重中之重。

编码规范

  1. 代码格式

    • gofmt 工具是Go语言官方的代码规范格式化工具,一般的IDE,如GoLand,都带有 gofmt 并能实现自动的格式化,确保代码风格统一。
    • goimports 也是官方工具,可以认为是 gofmt 的功能加上依赖包自动增删等管理功能,也更为常用。
  2. 注释 注释需要解释清楚如下内容:

    • 代码功能
    • 代码如何实现这一功能
    • 代码实现的原因
    • 代码出错或不适用的情景
  3. 命名

    • 变量命名 变量命名追求能简洁则简洁,例如下例中的 index 就显得冗长,不如使用 i 来得方便。

      for index := 0; index < len(s); index++ {
          // ...
      }
      for i := 0; i < len(s); i++ {
          // ...
      }
      

      同时变量命名应追求能够将信息表达到位,下例中的 t 只是表示了时间,但是在此处该变量是截止时间的意味,若仅使用 t 则让函数调用者不明所以,最好使用 deadline 等类似变量名。

      func (c *Client) send(req *Request, t time.Time)
      func (c *Client) send(req *Request, deadline time.Time)
      
    • 函数命名 函数名一般和包名成对出现,例如以 http.Serve() 的形式调用函数,所以函数中一般不携带包名避免冗余,下例的 ServeHTTP() 就不是好的函数命名。

      func ServeHTTP(I net.Listener, handler Handler) error
      func Serve(I net.Listener, handler Handler) error
      

      在函数返回类型和包对应时,一般在函数名中省略返回类型的信息。下例 time 包中返回 Time 类型的函数就最好采用 Now() 的命名形式。

      t := time.NowTime()
      t := time.Now()
      

      而在函数返回类型和包不对应时,则最好带上返回类型的信息,下例的 ParseDuration() 明显更合适。

      duration := time.Parse(s)
      duration := time.ParseDuration(s)
      
    • 包命名 在包命名时,一般只使用小写字母,且需格外注意包名不要重复。 除此之外应注意不与常用变量重名,最好使用单数形式,和不影响语义情况下的缩写简化等规则。

  4. 控制流 在编写控制流程时,我们应保持线性推进,减少条件语句等的嵌套。这样既能使代码易读逻辑清晰,又可以避免因为嵌套层数过多而导致代码改动困难。 我们从下面的反面例子出发,可以看出其存在冗余的异常返回 return err ,并且存在嵌套判断,若还需增加判断语句只能继续嵌套增添麻烦。

    func OneFunc() error {
        err := doSomething()
        if err == nil {
            err := doAnotherThing()
            if err == nil {
                return nil
            }
            return err
        }
        return err
    }
    

    因而我们做如下改动,形成高质量的控制流,代码简洁易懂。

    func OneFunc() error {
        if err := doSomething(); err != nil {
            return err
        }
        if err := doAnotherThing(); err != nil {
            return err
        }
        return nil // normal case
    }
    
  5. 错误和异常处理

    • 错误传递 在出现错误的地方,我们可以通过 errors.New()fmt.Errorf() 来返回错误信息,后者可以满足格式化需求更适合用于错误信息链。注意错误信息应尽量简明。 在某处调用函数接收到错误后,我们可以根据需要添加信息到获取的错误上并再次向更高层次的调用者传递错误,一般在 fmt.Errorf() 中使用 %w 参数来涵盖获取的低层次错误信息。注意错误信息的层次顺序应得到维护,方便定位错误信息。
    • 错误判定 使用 errors.Is() 来判定错误链上是否含有某类错误。 使用 errors.As() 则可以从错误链上获取某类错误存储到变量中。
    • 异常 panic 一般用于严重的难以恢复的错误,若没有使用 recover 语句则程序崩溃,因而在业务代码中一般不使用或只在程序启动阶段时使用。 recover 只能在被 defer 的函数中使用,并只在当前协程生效。