• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

Golang 编码规范

武飞扬头像
云满笔记
帮助1

1. Golang 编码规范

注: 此文是作者所在团队约定的编码规范, 参考官方指南 Effective GolangGolang Code Review Comments 进行整理, 力图与官方及社区编码风格保持一致。

1.1. gofmt

大部分的格式问题可以通过 gofmt 解决, gofmt 自动格式化代码, 保证所有的 go 代码一致的格式。

正常情况下, 采用 Sublime 编写 go 代码时, 插件 GoSublilme 已经调用 gofmt 对代码实现了格式化。

1.2. 注释

在编码阶段同步写好变量、函数、包注释, 注释可以通过 godoc 导出生成文档。

注释必须是完整的句子, 以需要注释的内容作为开头, 句点作为结尾。

程序中每一个被导出的 (大写的) 名字, 都应该有一个文档注释。

1.2.1. 包注释

每个程序包都应该有一个包注释, 一个位于 package 子句之前的块注释或行注释。

包如果有多个 go 文件, 只需要出现在一个 go 文件中即可。

//Package regexp implements a simple library 
//for regular expressions.
package regexp 

1.2.2. 可导出类型

第一条语句应该为一条概括语句, 并且使用被声明的名字作为开头。

// Compile parses a regular expression and returns, if successful, a Regexp
// object that can be used to match against text.
func Compile(str string) (regexp *Regexp, err error) {

1.3. 命名

使用短命名, 长名字并不会自动使得事物更易读, 文档注释会比格外长的名字更有用。

1.3.1. 包名

包名应该为小写单词, 不要使用下划线或者混合大小写。

1.3.2. 接口名

单个函数的接口名以 “er” 作为后缀, 如 Reader,Writer

接口的实现则去掉 “er”

type Reader interface {
        Read(p []byte) (n int, err error)
}

两个函数的接口名综合两个函数名

type WriteFlusher interface {
    Write([]byte) (int, error)
    Flush() error
}

三个以上函数的接口名, 类似于结构体名

type Car interface {
    Start([]byte) 
    Stop() error
    Recover()
}

1.3.3. 混合大小写

采用驼峰式命名

MixedCaps 大写开头, 可导出
mixedCaps 小写开头, 不可导出

1.3.4. 变量

全局变量: 驼峰式, 结合是否可导出确定首字母大小写
参数传递: 驼峰式, 小写字母开头
局部变量: 下划线形式

1.4. 控制结构

1.4.1. if

if 接受初始化语句, 约定如下方式建立局部变量

if err := file.Chmod(0664); err != nil {
    return err
}

1.4.2. for

采用短声明建立局部变量

sum := 0
for i := 0; i < 10; i   {
    sum  = i
}

1.4.3. range

如果只需要第一项 (key), 就丢弃第二个:

for key := range m {
    if key.expired() {
        delete(m, key)
    }
}

如果只需要第二项, 则把第一项置为下划线

sum := 0
for _, value := range array {
    sum  = value
}

1.4.4. return

尽早 return: 一旦有错误发生, 马上返回

f, err := os.Open(name)
if err != nil {
    return err
}
d, err := f.Stat()
if err != nil {
    f.Close()
    return err
}
codeUsing(f, d)

1.5. 函数 (必须)

  • 函数采用命名的多值返回
  • 传入变量和返回变量以小写字母开头
func nextInt(b []byte, pos int) (value, nextPos int) {

在 godoc 生成的文档中, 带有返回值的函数声明更利于理解

1.6. 错误处理

  • error 作为函数的值返回, 必须对 error 进行处理
  • 错误描述如果是英文必须为小写, 不需要标点结尾
  • 采用独立的错误流进行处理

不要采用这种方式

    if err != nil {
        // error handling
    } else {
        // normal code
    }

而要采用下面的方式

    if err != nil {
        // error handling
        return // or continue, etc.
    }
    // normal code

如果返回值需要初始化, 则采用下面的方式

x, err := f()
if err != nil {
    // error handling
    return
}
// use x

1.7. panic

尽量不要使用 panic, 除非你知道你在做什么

1.8. import

对 import 的包进行分组管理, 而且标准库作为第一组

package main

import (
    "fmt"
    "hash/adler32"
    "os"

    "appengine/user"
    "appengine/foo"

    "code.谷歌.com/p/x/y"
    "github.com/foo/bar"
)

goimports 实现了自动格式化

1.9. 缩写

1.9.1. 采用全部大写或者全部小写来表示缩写单词

比如对于 url 这个单词, 不要使用

UrlPony

而要使用

urlPony 或者 URLPony  

1.10. 参数传递

  • 对于少量数据, 不要传递指针
  • 对于大量数据的 struct 可以考虑使用指针
  • 传入参数是 map, slice, chan 不要传递指针

因为 map, slice, chan 是引用类型, 不需要传递指针的指针

1.11. 接受者

1.11.1. 名称

统一采用单字母’p’ 而不是 this, me 或者 self

type T struct{} 

func (p *T)Get(){}

1.11.2. 类型

对于 go 初学者, 接受者的类型如果不清楚, 统一采用指针型

func (p *T)Get(){}

而不是

func (p T)Get(){}   

在某些情况下, 出于性能的考虑, 或者类型本来就是引用类型, 有一些特例

  • 如果接收者是 map,slice 或者 chan, 不要用指针传递
//Map
package main

import (
    "fmt"
)

type mp map[string]string

func (m mp) Set(k, v string) {
    m[k] = v
}

func main() {
    m := make(mp)
    m.Set("k", "v")
    fmt.Println(m)
}
//Channel
package main

import (
    "fmt"
)

type ch chan interface{}

func (c ch) Push(i interface{}) {
    c <- i
}

func (c ch) Pop() interface{} {
    return <-c
}

func main() {
    c := make(ch, 1)
    c.Push("i")
    fmt.Println(c.Pop())
}
  • 如果需要对 slice 进行修改, 通过返回值的方式重新赋值
//Slice
package main

import (
    "fmt"
)

type slice []byte

func main() {
    s := make(slice, 0)
    s = s.addOne(42)
    fmt.Println(s)
}

func (s slice) addOne(b byte) []byte {
    return append(s, b)
}
  • 如果接收者是含有 sync.Mutex 或者类似同步字段的结构体, 必须使用指针传递避免复制
package main

import (
    "sync"
)

type T struct {
    m sync.Mutex
}

func (t *T) lock() {
    t.m.Lock()
}

/*
Wrong !!!
func (t T) lock() {
    t.m.Lock()
}
*/

func main() {
    t := new(T)
    t.lock()
}
  • 如果接收者是大的结构体或者数组, 使用指针传递会更有效率。
package main

import (
    "fmt"
)

type T struct {
    data [1024]byte
}

func (t *T) Get() byte {
    return t.data[0]
}

func main() {
    t := new(T)
    fmt.Println(t.Get())
}

1.12. 编写地道的 Go 代码

1.12.1. 注释

可以通过 /* ... */ 或者 // 增加注释, // 之后应该有个空格。

如果想在每个文件的头部加上注释, 需要在版权注释和 Package 前面加一个空行, 否则版权注释会作为 package 的注释

// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

/*
Package net provides a portable interface for network I/O, including
TCP/IP, UDP, domain name resolution, and Unix domain sockets.
......
*/

package net
......

注: 注释应该用一个完整的句子, 注释的第一个单词应该是要注释的指示符, 以便在 godoc 中容易查找;
注释应该以 . 结尾;

1.12.2. 声明 slice

使用下面这种方式声明 slice:

var s []string

而不是下面这种格式

t := []string{}

注: 前者声明了一个 nil slice, 而后者声明了一个长度为 0 的非 nil slice

1.12.3. 字符串的大小写

错误字符串不应该大写, 应写成:

fmt.Errorf("failed to write data.")

而不是写成:

fmt.Errorf("Failed to write data")

因为, 这些字符串可能和其他字符串相连接, 组合后的字符串如果中间有大写字母开头的单词很突兀, 除非这些首字母大写单词是固定使用的单词。

注: 缩写词必须保持一致, 比如都大写 URL 或者小写 url;
常亮一般声明为 MaxLength, 而不是以下划线分割 MAX_LENGTH 或者 MAXLENGTH;

1.12.4. 处理 error 而不是 panic 或者忽略

为了代码的强健性, 不要使用_忽略错误, 而是要处理每一个错误, 尽管代码写起来有些繁琐也不要忽略错误;

尽量不要使用 panic;

1.12.5. 一些名称

包名应该使用单数形式, 比如 util,model, 而不是 utils,models;

Receiver 的名称应该缩写, 一般使用一个或两个字符作为 Receiver 的名称, 如:

func (f foo) method {

    ...

}

有些单词可能有多种写法, 在项目中应保持一致, 比如 Golang 采用的写法:

// marshaling
// unmarshaling
// canceling
// cancelation

而不是:

// marshalling
// unmarshalling
// cancelling
// cancellation

1.12.6. 空字符串检查

正确方式:

if s == "" {
    ...
}

而不是:

if len(s) == 0 {
    ...
}

更不是:

if s == nil || s == ""{
    ...
}

1.12.7. 非空 slice 检查

正确方式:

if len(s) > 0 {
    ...
}

而不是:

if s != nil && len(s) > 0 {
    ...
}

1.12.8. 直接使用 bool 值

对于 bool 类型的变量 var b bool, 直接使用它作为判断, 而不是使用它和 true/false 进行比较
正确方式:

if b {
    ...
}
if !b {
    ...
}

而不是:

if b == true {
    ...
}
if b == false {
    ...
}

1.12.9. byte/slice/string 相等性比较

var s1 []byte
var s2 []byte

    ...
bytes.Equal(s1, s2) == 0
bytes.Equal(s1, s2) != 0

而不是:

var s1 []byte
var s2 []byte

    ...
bytes.Compare(s1, s2) == 0    
bytes.Compare(s1, s2) != 0

1.12.10. 检查是否包含子字符串

应使用 strings.ContainesRune, strings.ContainesAny, strings.Contains

1.12.11. 复制 slice

使用内建函数 copy, 而不是遍历 slice 逐个复制
正确方式

var b1, b2 []byte
copy(b2, b1)

1.12.12. 尽量缩短 if

正确方式:

  var a, b int
  ...
  return a > b

而不是:

    var a, b int
    ...
    if a > b {
        return true
    } else {
        return false
    }

1.12.13. 简化 range

正确方式:

    for range m {
        ...
    }

而不是:

    var m map[string]int
    for _ = range m { 
    }
    for _, _ = range m {
    }

1.12.14. 使用 strings.TrimPrefix / strings.TrimSuffix

正确方式:

    var s1 = "a string value"
    var s2 = "a"
    var s3 = strings.TrimPrefix(s1, s2)

而不是:

       var s1 = "a string value"
       var s2 = "a"
       var s3 string
    if strings.HasPrefix(s1, s2) { 
        s3 = s1[len(s2):]
    }

1.12.15. append slice

正确方式:

    var a, b []byte
    a = append(b, a...)

而不是:

    var a, b []byte
    for _,v range a {
        append(b, v)
    }

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgfafia
系列文章
更多 icon
同类精品
更多 icon
继续加载