当前位置:网站首页>go语言编程规范梳理总结
go语言编程规范梳理总结
2022-07-05 15:07:00 【dnice】
本篇文章梳理总结了20条go语言编程经验,参考链接放在了文末,感兴趣的朋友可以查看。
1.规范并且统一统一定义package,避免出现循环依赖
go不支持循环依赖,所以我们要在package设计上多下功夫,避免多人协作开发时出现循环依赖。
2.避免过长的代码行
可以统一限定单行代码阈值,uber_go_guide中建议将行长限制为99个字符,但不做硬性限制,可以超过此限制。
3.相似的声明放在一组
Go语言支持将相似的声明放在一个组内。
import "a"
import "b"
import (
"a"
"b"
)
这同样适用于常量、变量和类型声明:
const a = 1
const b = 2
var a = 1
var b = 2
type Area float64
type Volume float64
const (
a = 1
b = 2
)
var (
a = 1
b = 2
)
type (
Area float64
Volume float64
)
仅将相关的声明放在一组。不要将不相关的声明放在一组。
type Operation int
const (
Add Operation = iota + 1
Subtract
Multiply
EnvVar = "MY_ENV"
)
type Operation int
const (
Add Operation = iota + 1
Subtract
Multiply
)
const EnvVar = "MY_ENV"
分组使用的位置没有限制,例如:你可以在函数内部使用它们:
func f() string {
red := color.New(0xff0000)
green := color.New(0x00ff00)
blue := color.New(0x0000ff)
...
}
func f() string {
var (
red = color.New(0xff0000)
green = color.New(0x00ff00)
blue = color.New(0x0000ff)
)
...
}
4.import分组
import应该分为两组:
- 标准库
- 其它库
import (
"fmt"
"os"
"go.uber.org/atomic"
"golang.org/x/sync/errgroup"
)
import (
"fmt"
"os"
"go.uber.org/atomic"
"golang.org/x/sync/errgroup"
)
5.包名
当命名包时,请按下面规则选择一个名称:
- 全部小写。没有大写或下划线。
- 大多数使用命名导入的情况下,不需要重命名。
- 简短而简洁。
- 不用复数。例如net/url,而不是net/urls。
- 不要用“common”,“util”,“shared”或“lib”。这些是不好的,信息量不足的名称。
6.函数名
遵循 Go 社区关于使用驼峰命名作为函数名的约定。有一个例外,为了对相关的测试用例进行分组,函数名可能包含下划线,如:TestMyFunction_WhatIsBeingTested。
7.导入别名
两种情况下需要使用导入别名,否则应该避免使用导入别名。
- 如果程序包名称与导入路径的最后一个元素不匹配,则必须使用导入别名。
import (
"net/http"
client "example.com/client-go"
trace "example.com/trace/v2"
)
- 导入多个包名有直接冲突时,应使用导入别名。
import (
"fmt"
"os"
nettrace "golang.net/x/trace"
)
import (
"fmt"
"os"
"runtime/trace"
nettrace "golang.net/x/trace"
)
8.函数分组与顺序
- 函数应按粗略的调用顺序排序。
- 同一文件中的函数应按接收者分组。
9.减少嵌套
代码应通过尽可能先处理错误情况/特殊情况并尽早返回或继续循环来减少嵌套。减少嵌套多个级别的代码的代码量。
for _, v := range data {
if v.F1 == 1 {
v = process(v)
if err := v.Call(); err == nil {
v.Send()
} else {
return err
}
} else {
log.Printf("Invalid v: %v", v)
}
}
for _, v := range data {
if v.F1 != 1 {
log.Printf("Invalid v: %v", v)
continue
}
v = process(v)
if err := v.Call(); err != nil {
return err
}
v.Send()
}
10.减少不必要的 else
如果在 if 的两个分支中都设置了变量,则可以将其替换为单个 if。
var a int
if b {
a = 100
} else {
a = 10
}
a := 10
if b {
a = 100
}
11.顶层变量声明
在顶层,使用标准var关键字。请勿指定类型,除非它与表达式的类型不同。
var _s string = F()
func F() string {
return "A" }
var _s = F()
// 由于 F 已经明确了返回一个字符串类型,因此我们没有必要显式指定_s 的类型
// 还是那种类型
func F() string {
return "A" }
如果表达式的类型与所需的类型不完全匹配,请指定类型。
type myError struct{
}
func (myError) Error() string {
return "error" }
func F() myError {
return myError{
} }
var _e error = F()
// F 返回一个 myError 类型的实例,但是我们要 error 类型
12.本地变量声明
如果将变量明确设置为某个值,则应使用短变量声明形式 (:=)。
var s = "foo"
s := "foo"
但是,在某些情况下,var 使用关键字时默认值会更清晰。例如,声明空切片。
func f(list []int) {
filtered := []int{
}
for _, v := range list {
if v > 10 {
filtered = append(filtered, v)
}
}
}
func f(list []int) {
var filtered []int
for _, v := range list {
if v > 10 {
filtered = append(filtered, v)
}
}
}
13.nil 是一个有效的 slice
nil 是一个有效的长度为 0 的 slice,这意味着:
- 应该使用返回nil来代替返回长度为零的切片。
if x == "" {
return []int{
}
}
if x == "" {
return nil
}
- 要检查切片是否为空,请始终使用len(s) == 0。而非 nil。
func isEmpty(s []string) bool {
return s == nil
}
func isEmpty(s []string) bool {
return len(s) == 0
}
- 零值切片(用var声明的切片)可立即使用,无需调用make()创建。
nums := []int{
}
// or, nums := make([]int)
if add1 {
nums = append(nums, 1)
}
if add2 {
nums = append(nums, 2)
}
var nums []int
if add1 {
nums = append(nums, 1)
}
if add2 {
nums = append(nums, 2)
}
注意:虽然nil切片是有效的切片,但它不等于长度为0的切片(一个为nil,另一个不是),并且在不同的情况下(例如序列化),这两个切片的处理方式可能不同。
14.避免参数语义不明确
函数调用中的意义不明确的参数可能会损害可读性。当参数名称的含义不明显时,请为参数添加 C 样式注释 (/* … */)
// func printInfo(name string, isLocal, done bool)
printInfo("foo", true, true)
// func printInfo(name string, isLocal, done bool)
printInfo("foo", true /* isLocal */, true /* done */)
对于上面的示例代码,还有一种更好的处理方式是将上面的 bool 类型换成自定义类型。将来,该参数可以支持不仅仅局限于两个状态(true/false)。
type Region int
const (
UnknownRegion Region = iota
Local
)
type Status int
const (
StatusReady Status= iota + 1
StatusDone
// Maybe we will have a StatusInProgress in the future.
)
func printInfo(name string, region Region, status Status)
15.使用原始字符串字面值,避免转义
Go 支持使用 原始字符串字面值,也就是 " ` " 来表示原生字符串,在需要转义的场景下,我们应该尽量使用这种方案来替换。
可以跨越多行并包含引号。使用这些字符串可以避免更难阅读的手工转义的字符串。
wantError := "unknown name:\"test\""
wantError := `unknown error:"test"`
16.初始化结构体
- 使用字段名初始化结构
初始化结构时,几乎应该始终指定字段名。
k := User{
"John", "Doe", true}
k := User{
FirstName: "John",
LastName: "Doe",
Admin: true,
}
例外:当有3个或更少的字段时,测试表中的字段名可以省略。
tests := []struct{
op Operation
want string
}{
{
Add, "add"},
{
Subtract, "subtract"},
}
- 省略结构中的零值字段
初始化具有字段名的结构时,除非提供有意义的上下文,否则忽略值为零的字段。 也就是,让我们自动将这些设置为零值。
user := User{
FirstName: "John",
LastName: "Doe",
MiddleName: "",
Admin: false,
}
user := User{
FirstName: "John",
LastName: "Doe",
}
这有助于通过省略该上下文中的默认值来减少阅读的障碍。只指定有意义的值。
- 对零值结构使用 var
如果在声明中省略了结构的所有字段,请使用 var 声明结构。
user := User{
}
var user User
- 初始化 Struct 引用
在初始化结构引用时,请使用&T{}代替new(T),以使其与结构体初始化一致。
sval := T{
Name: "foo"}
// inconsistent
sptr := new(T)
sptr.Name = "bar"
sval := T{
Name: "foo"}
sptr := &T{
Name: "bar"}
17.指定容器容量
尽可能指定容器容量,以便为容器预先分配内存。这将在添加元素时最小化后续分配(通过复制和调整容器大小)。
- 指定Map容量提示
在尽可能的情况下,在使用 make() 初始化的时候提供容量信息
make(map[T1]T2, hint)
向make()提供容量提示会在初始化时尝试调整map的大小,这将减少在将元素添加到map时为map重新分配内存。
注意,与slices不同。map capacity提示并不保证完全的抢占式分配,而是用于估计所需的hashmap bucket的数量。 因此,在将元素添加到map时,甚至在指定map容量时,仍可能发生分配。
m := make(map[string]os.FileInfo)
files, _ := ioutil.ReadDir("./files")
for _, f := range files {
m[f.Name()] = f
}
// m 是在没有大小提示的情况下创建的; 在运行时可能会有更多分配。
files, _ := ioutil.ReadDir("./files")
m := make(map[string]os.FileInfo, len(files))
for _, f := range files {
m[f.Name()] = f
}
// m 是有大小提示创建的;在运行时可能会有更少的分配。
- 指定切片容量
在尽可能的情况下,在使用make()初始化切片时提供容量信息,特别是在追加切片时。
make([]T, length, capacity)
与maps不同,slice capacity不是一个提示:编译器将为提供给make()的slice的容量分配足够的内存, 这意味着后续的append()`操作将导致零分配(直到slice的长度与容量匹配,在此之后,任何append都可能调整大小以容纳其他元素)。
for n := 0; n < b.N; n++ {
data := make([]int, 0)
for k := 0; k < size; k++{
data = append(data, k)
}
}
BenchmarkBad-4 100000000 2.48s
for n := 0; n < b.N; n++ {
data := make([]int, 0, size)
for k := 0; k < size; k++{
data = append(data, k)
}
}
BenchmarkGood-4 100000000 0.21s
18.优先使用 strconv 而不是 fmt
将原语转换为字符串或从字符串转换时,strconv速度比fmt快。
for i := 0; i < b.N; i++ {
s := fmt.Sprint(rand.Int())
}
BenchmarkFmtSprint-4 143 ns/op 2 allocs/op
for i := 0; i < b.N; i++ {
s := strconv.Itoa(rand.Int())
}
BenchmarkStrconv-4 64.2 ns/op 1 allocs/op
19.避免字符串到字节的转换
不要反复从固定字符串创建字节 slice。相反,请执行一次转换并捕获结果。
for i := 0; i < b.N; i++ {
w.Write([]byte("Hello world"))
}
BenchmarkBad-4 50000000 22.2 ns/op
data := []byte("Hello world")
for i := 0; i < b.N; i++ {
w.Write(data)
}
BenchmarkGood-4 500000000 3.25 ns/op
20.字符串 string format
如果你在函数外声明Printf-style 函数的格式字符串,请将其设置为const常量。
这有助于go vet对格式字符串执行静态分析。
msg := "unexpected values %v, %v\n"
fmt.Printf(msg, 1, 2)
const msg = "unexpected values %v, %v\n"
fmt.Printf(msg, 1, 2)
参考链接
本篇内容参考链接:https://github.com/uber-go/guide/blob/master/style.md
边栏推荐
- Reasons and solutions for redis cache penetration and cache avalanche
- Aike AI frontier promotion (7.5)
- mapper. Comments in XML files
- Bugku easy_ nbt
- Common MySQL interview questions (1) (written MySQL interview questions)
- What are CSRF, XSS, SQL injection, DDoS attack and timing attack respectively and how to prevent them (PHP interview theory question)
- MySQL5.7的JSON基本操作
- Where is the operation of convertible bond renewal? Is it safer and more reliable to open an account
- Ctfshow web entry information collection
- JMeter performance test: serveragent resource monitoring
猜你喜欢
随机推荐
lv_ font_ Conv offline conversion
MySQL 巨坑:update 更新慎用影响行数做判断!!!
我这边同时采集多个oracle表,采集一会以后,会报oracle的oga内存超出,大家有没有遇到的?
keep-alive
Bugku alert
sql server char nchar varchar和nvarchar的区别
sql server学习笔记
Creation and use of thymeleaf template
Leetcode: Shortest Word Distance II
MySQL之CRUD
Ecotone technology has passed ISO27001 and iso21434 safety management system certification
Common redis data types and application scenarios
Bubble sort, insert sort
Install PHP extension spoole
Photoshop plug-in - action related concepts - actions in non loaded execution action files - PS plug-in development
Ctfshow web entry command execution
P6183 [USACO10MAR] The Rock Game S
Thymeleaf uses background custom tool classes to process text
Does maxcompute have SQL that can query the current storage capacity (KB) of the table?
Common PHP interview questions (1) (written PHP interview questions)