go语言风格培训

go语言风格培训

参考资料

  • go官方:https://github.com/golang/go/wiki/CodeReviewComments

Declaring Empty Slices

good bad
var t []string t := []string{}
  • 注:前者是个nil类型slice 而后者是0长度类型,在json编码的时候会不一样,前者会是null而后者是[]

Error Strings

  • error string不应该开头大写,除非是以专有名词或首字母缩写开头。也不应该以标点符号结束。
  • 日志则不需要符合上面的要求,因为日志直接写一行而且不会有合并的情况。
good bad
use fmt.Errorf(“something bad”) fmt.Errorf(“Something bad.”)

Handle Errors

  • 不要使用 _ 来忽略错误,如果函数会返回错误,必须检查这个错误,并作出正确的处理:报错、返回、记录后无视、或者在必要的时候panic。

Imports

  • 避免包重名,如果重名,则更改最本地的包。 import要分组,系统包要在第一组,如下:
package main

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

	"appengine/foo"
	"appengine/user"

	"github.com/foo/bar"
	"rsc.io/goversion/version"
)

Indent Error Flow

  • 只有err需要使用的时候

GOOD:

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

BAD:

if err != nil {
	// error handling
} else {
	// normal code
}
  • 需要处理正常返回结果的时候

GOOD:

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

BAD:

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

Initialisms

  • 关于缩写:大小写一致。比如 URL 或者url 不要写成Url 。同理 ServeHTTP 不应ServeHttp、 appID 不应appId
good bad
sql.DB sql.SqlDB
http.Server http.HTTPServer
list.List list.NewList

Package Names

  • 避免如下包名: util, common, misc, api, types, and interfaces.
  • 包名应该小写,没有大写或者下划线。 包名和包内成员、函数不要有重,比如包名为chubby则其内部的全局变量应为File 而不是ChubbyFile
  • 不用复数。 比如 net/url 不是net/urls

File Name

  • 必要用驼峰,最好用也不用下划线,全部小写。
  • 对于测试文件或者平台、CPU类型、显卡类型等可以使用下划线

Chan

  • 在不在乎channel类型的情况下应使用chan struct{}

Receive Type

  • 如果接收器是 map,func或 chan,则不要使用指向它们的指针。如果接收器是 slice并且该方法不重新切片或不重新分配切片,则不要使用指向它的指针。
  • 如果该方法需要改变接收器的值,则接收器必须是指针。
  • 如果接收器是包含 sync.Mutex 或类似同步字段的 struct,则接收器必须是指针,以避免复制。
  • 如果接收器是大型结构或数组,则指针接收器更有效。多大才算大?假设它相当于将其包含的所有元素作为参数传递给方法。如果感觉太大,那么对接收器来说也太大了。
  • 函数或方法可以改变接收器吗(并发调用或调用某方法时继续调用相关方法或函数)?在调用方法时,值类型会创建接收器的副本,因此外部更新将不会应用于此接收器。如果必须在原始接收器中看到更改效果,则接收器必须是指针。
  • 如果接收器是 struct,数组或 slice,并且其任何元素是指向可能改变的对象的指针,则更倾向于使用指针接收器,因为它将使读者更清楚地意图。
  • 如果接收器是一个小型数组或 struct,那么它自然是一个值类型(例如,类似于time.Time类型),对于没有可变字段,没有指针的类型,或者只是一个简单的基本类型,如 int 或 string,值接收器是合适的。值接收器可以减少可以生成的垃圾量;如果将值作为参数传递给值类型方法,则可以使用堆栈上的副本而不需要在堆上进行分配。(编译器试图避免这种分配,但它不能总是成功)因此,在没有进行分析之前,不要选择值接收器类型。
  • 最后,如果以上没有解决你的问题,仍然对选择哪种方式有困惑,请使用指针接收器

Interface assert

  • 需要对interface的类型做判断,否则会在类型错误时panic

GOOD:

t,ok := i.(string)
if !ok {
    //xxxx
}

BAD:

t := i.(string) // panic when i is not string, such as nil

Switch

  • switch的时候必须要吧所有的可能性都包括到,特别是枚举值的情况下,必要时需要加上default
switch signal {
case syscall.SIGHUP:
    // xxxx
case syscall.SIGQUIT,syscall.SIGTERM,syscall.SIGINT:
    // xxx
default:
    // xxx
}

Table Driven Tests

package main

import (
	"testing"
)

func TestGetCurrTasksCount(t *testing.T) {
	testCases := []struct {
		name          string
		appID         int
		speed         int
		learnedWords  int64
		todayLearnCnt int64
		expect        int
	}{
		{
			name:          "测试今天任务数不满足用户设置每日速度",
			appID:         context.APPPinYin,
			speed:         10,
			learnedWords:  40,
			todayLearnCnt: 8,
			expect:        10,
		},
		{
			name:          "测试今天任务数满足用户设置每日速度",
			appID:         context.APPPinYin,
			speed:         10,
			learnedWords:  60,
			todayLearnCnt: 1,
			expect:        4,
		},
		{
			name:          "测试已经没有可学任务了",
			appID:         context.APPPinYin,
			speed:         10,
			learnedWords:  63,
			todayLearnCnt: 0,
			expect:        0,
		},
	}

	for _, tt := range testCases {
		t.Run(tt.name, func(t *testing.T) {
			resp, err := getCurrTasksCount(test.NewCtx(), tt.appID, tt.speed, tt.learnedWords, tt.todayLearnCnt)
			require.Nil(t, err)
			require.Equal(t, tt.expect, resp)
		})
	}
}
上一篇:常用技巧&写法


下一篇:JSONDecodeError: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)