go语言开发规范建议以及开发过程中各种各样的坑
bigegpt 2024-10-29 12:58 5 浏览
Go 箴言
- 不要通过共享内存进行通信,通过通信共享内存
- 并发不是并行
- 管道用于协调;互斥量(锁)用于同步
- 接口越大,抽象就越弱
- 利用好零值
- 空接口 interface{} 没有任何类型约束
- Gofmt 的风格不是人们最喜欢的,但 gofmt 是每个人的最爱
- 允许一点点重复比引入一点点依赖更好
- 系统调用必须始终使用构建标记进行保护
- 必须始终使用构建标记保护 Cgo
- Cgo 不是 Go
- 使用标准库的 unsafe 包,不能保证能如期运行
- 清晰比聪明更好
- 反射永远不清晰
- 错误是值
- 不要只检查错误,还要优雅地处理它们
- 设计架构,命名组件,(文档)记录细节
- 文档是供用户使用的
- 不要(在生产环境)使用 panic()
Author: Rob Pike
转自: https://go-proverbs.github.io/
参考go语言中文文档:www.topgoer.com
Go 之禅
- 每个 package 实现单一的目的
- 显式处理错误
- 尽早返回,而不是使用深嵌套
- 让调用者处理并发(带来的问题)
- 在启动一个 goroutine 时,需要知道何时它会停止
- 避免 package 级别的状态
- 简单很重要
- 编写测试以锁定 package API 的行为
- 如果你觉得慢,先编写 benchmark 来证明
- 适度是一种美德
- 可维护性
Author: Dave Cheney
See more: https://the-zen-of-go.netlify.com/
代码
使用 go fmt 格式化
让团队一起使用官方的 Go 格式工具,不要重新发明轮子。
尝试减少代码复杂度。 这将帮助所有人使代码易于阅读。
多个 if 语句可以折叠成 switch
// NOT BAD
if foo() {
// ...
} else if bar == baz {
// ...
} else {
// ...
}
// BETTER
switch {
case foo():
// ...
case bar == baz:
// ...
default:
// ...
}
用 chan struct{} 来传递信号, chan bool 表达的不够清楚
当你在结构中看到 chan bool 的定义时,有时不容易理解如何使用该值,例如:
type Service struct {
deleteCh chan bool // what does this bool mean?
}
但是我们可以将其改为明确的 chan struct {} 来使其更清楚:我们不在乎值(它始终是 struct {}),我们关心可能发生的事件,例如:
type Service struct {
deleteCh chan struct{} // ok, if event than delete something.
}
30 * time.Second 比 time.Duration(30) * time.Second 更好
你不需要将无类型的常量包装成类型,编译器会找出来。
另外最好将常量移到第一位:
// BAD
delay := time.Second * 60 * 24 * 60
// VERY BAD
delay := 60 * time.Second * 60 * 24
// GOOD
delay := 24 * 60 * 60 * time.Second
用 time.Duration 代替 int64 + 变量名
// BAD
var delayMillis int64 = 15000
// GOOD
var delay time.Duration = 15 * time.Second
按类型分组 const 声明,按逻辑和/或类型分组 var
// BAD
const (
foo = 1
bar = 2
message = "warn message"
)
// MOSTLY BAD
const foo = 1
const bar = 2
const message = "warn message"
// GOOD
const (
foo = 1
bar = 2
)
const message = "warn message"
这个模式也适用于 var。
- 每个阻塞或者 IO 函数操作应该是可取消的或者至少是可超时的
- 为整型常量值实现 Stringer 接口https://godoc.org/golang.org/x/tools/cmd/stringer
- 检查 defer 中的错误
defer func() {
err := ocp.Close()
if err != nil {
rerr = err
}
}()
- 不要在 checkErr 函数中使用 panic() 或 os.Exit()
- 仅仅在很特殊情况下才使用 panic, 你必须要去处理 error
- 不要给枚举使用别名,因为这打破了类型安全https://play.golang.org/p/MGbeDwtXN3
package main
type Status = int
type Format = int // remove `=` to have type safety
const A Status = 1
const B Format = 1
func main() {
println(A == B)
}
- 如果你想省略返回参数,你最好表示出来_ = f() 比 f() 更好
- 我们用 a := []T{} 来简单初始化 slice
- 用 range 循环来进行数组或 slice 的迭代for _, c := range a[3:7] {...} 比 for i := 3; i < 7; i++ {...} 更好
- 多行字符串用反引号(`)
- 用 _ 来跳过不用的参数
func f(a int, _ string) {}
- 如果你要比较时间戳,请使用 time.Before 或 time.After ,不要使用 time.Sub 来获得 duration (持续时间),然后检查它的值。
- 带有上下文的函数第一个参数名为 ctx,形如:func foo(ctx Context, ...)
- 几个相同类型的参数定义可以用简短的方式来进行
func f(a int, b int, s string, p string)
func f(a, b int, s, p string)
- 一个 slice 的零值是 nilhttps://play.golang.org/p/pNT0d_Bunqvar s []int fmt.Println(s, len(s), cap(s)) if s == nil { fmt.Println("nil!") } // Output: // [] 0 0 // nil!https://play.golang.org/p/meTInNyxtk
var a []string
b := []string{}
fmt.Println(reflect.DeepEqual(a, []string{}))
fmt.Println(reflect.DeepEqual(b, []string{}))
// Output:
// false
// true
- 不要将枚举类型与 <, >, <= 和 >= 进行比较使用确定的值,不要像下面这样做:
value := reflect.ValueOf(object)
kind := value.Kind()
if kind >= reflect.Chan && kind <= reflect.Slice {
// ...
}
- 用 %+v 来打印数据的比较全的信息
- 注意空结构 struct{}, 看 issue: https://github.com/golang/go/issues/23440more: https://play.golang.org/p/9C0puRUstrP
func f1() {
var a, b struct{}
print(&a, "\n", &b, "\n") // Prints same address
fmt.Println(&a == &b) // Comparison returns false
}
func f2() {
var a, b struct{}
fmt.Printf("%p\n%p\n", &a, &b) // Again, same address
fmt.Println(&a == &b) // ...but the comparison returns true
}
- 包装错误: http://github.com/pkg/errors例如: errors.Wrap(err, "additional message to a given error")
- 在 Go 里面要小心使用 range:for i := range a and for i, v := range &a ,都不是 a 的副本但是 for i, v := range a 里面的就是 a 的副本更多: https://play.golang.org/p/4b181zkB1O
- 从 map 读取一个不存在的 key 将不会 panicvalue := map["no_key"] 将得到一个 0 值value, ok := map["no_key"] 更好
- 不要使用原始参数进行文件操作而不是一个八进制参数 os.MkdirAll(root, 0700)使用此类型的预定义常量 os.FileMode
- 不要忘记为 iota 指定一种类型https://play.golang.org/p/mZZdMaI92cI
const (
_ = iota
testvar // testvar 将是 int 类型
)
vs
type myType int
const (
_ myType = iota
testvar // testvar 将是 myType 类型
)
不要在你不拥有的结构上使用 encoding/gob
在某些时候,结构可能会改变,而你可能会错过这一点。因此,这可能会导致很难找到 bug。
不要依赖于计算顺序,特别是在 return 语句中。
// BAD
return res, json.Unmarshal(b, &res)
// GOOD
err := json.Unmarshal(b, &res)
return res, err
防止结构体字段用纯值方式初始化,添加 _ struct {} 字段:
type Point struct {
X, Y float64
_ struct{} // to prevent unkeyed literals
}
对于 Point {X:1,Y:1} 都可以,但是对于 Point {1,1} 则会出现编译错误:
./file.go:1:11: too few values in Point literal
当在你所有的结构体中添加了 _ struct{} 后,使用 go vet 命令进行检查,(原来声明的方式)就会提示没有足够的参数。
为了防止结构比较,添加 func 类型的空字段
type Point struct {
_ [0]func() // unexported, zero-width non-comparable field
X, Y float64
}
http.HandlerFunc 比 http.Handler 更好
用 http.HandlerFunc 你仅需要一个 func,http.Handler 需要一个类型。
移动 defer 到顶部
这可以提高代码可读性并明确函数结束时调用了什么。
JavaScript 解析整数为浮点数并且你的 int64 可能溢出
用 json:"id,string" 代替
type Request struct {
ID int64 `json:"id,string"`
}
并发
- 以线程安全的方式创建单例(只创建一次)的最好选择是 sync.Once不要用 flags, mutexes, channels or atomics
- 永远不要使用 select{}, 省略通道, 等待信号
- 不要关闭一个发送(写入)管道,应该由创建者关闭往一个关闭的 channel 写数据会引起 panic
- math/rand 中的 func NewSource(seed int64) Source 不是并发安全的,默认的 lockedSource 是并发安全的, see issue: https://github.com/golang/go/issues/3611更多: https://golang.org/pkg/math/rand/
- 当你需要一个自定义类型的 atomic 值时,可以使用 atomic.Value
性能
- 不要省略 defer在大多数情况下 200ns 加速可以忽略不计
- 总是关闭 http body defer r.Body.Close()除非你需要泄露 goroutine
- 过滤但不分配新内存
b := a[:0]
for _, x := range a {
if f(x) {
b = append(b, x)
}
}
为了帮助编译器删除绑定检查,请参见此模式 _ = b [7]
- time.Time 有指针字段 time.Location 并且这对 go GC 不好只有使用了大量的 time.Time 才(对性能)有意义,否则用 timestamp 代替
- regexp.MustCompile 比 regexp.Compile 更好在大多数情况下,你的正则表达式是不可变的,所以你最好在 func init 中初始化它
- 请勿在你的热点代码中过度使用 fmt.Sprintf. 由于维护接口的缓冲池和动态调度,它是很昂贵的。如果你正在使用 fmt.Sprintf("%s%s", var1, var2), 考虑使用简单的字符串连接。如果你正在使用 fmt.Sprintf("%x", var), 考虑使用 hex.EncodeToString or strconv.FormatInt(var, 16)
- 如果你不需要用它,可以考虑丢弃它,例如io.Copy(ioutil.Discard, resp.Body)HTTP 客户端的传输不会重用连接,直到body被读完和关闭。
res, _ := client.Do(req)
io.Copy(ioutil.Discard, res.Body)
defer res.Body.Close()
- 不要在循环中使用 defer,否则会导致内存泄露因为这些 defer 会不断地填满你的栈(内存)
- 不要忘记停止 ticker, 除非你需要泄露 channel
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
- 用自定义的 marshaler 去加速 marshaler 过程但是在使用它之前要进行定制!例如:https://play.golang.org/p/SEm9Hvsi0r
func (entry Entry) MarshalJSON() ([]byte, error) {
buffer := bytes.NewBufferString("{")
first := true
for key, value := range entry {
jsonValue, err := json.Marshal(value)
if err != nil {
return nil, err
}
if !first {
buffer.WriteString(",")
}
first = false
buffer.WriteString(key + ":" + string(jsonValue))
}
buffer.WriteString("}")
return buffer.Bytes(), nil
}
- sync.Map 不是万能的,没有很强的理由就不要使用它。了解更多: https://github.com/golang/go/blob/master/src/sync/map.go#L12
- 在 sync.Pool 中分配内存存储非指针数据了解更多: https://github.com/dominikh/go-tools/blob/master/cmd/staticcheck/docs/checks/SA6002
- 为了隐藏逃生分析的指针,你可以小心使用这个函数::来源: https://go-review.googlesource.com/c/go/+/86976
// noescape hides a pointer from escape analysis. noescape is
// the identity function but escape analysis doesn't think the
// output depends on the input. noescape is inlined and currently
// compiles down to zero instructions.
//go:nosplit
func noescape(p unsafe.Pointer) unsafe.Pointer {
x := uintptr(p)
return unsafe.Pointer(x ^ 0)
}
- 对于最快的原子交换,你可以使用这个 m := (*map[int]int)(atomic.LoadPointer(&ptr))
- 如果执行许多顺序读取或写入操作,请使用缓冲 I/O减少系统调用次数
- 有 2 种方法清空一个 map:重用 map 内存 (但是也要注意 m 的回收)
for k := range m {
delete(m, k)
}
- 分配新的
m = make(map[int]int)
模块
- 如果你想在 CI 中测试 go.mod (和 go.sum)是否是最新 https://blog.urth.org/2019/08/13/testing-go-mod-tidiness-in-ci/
构建
- 用这个命令 go build -ldflags="-s -w" ... 去掉你的二进制文件
- 拆分构建不同版本的简单方法用 // +build integration 并且运行他们 go test -v --tags integration .
- 最小的 Go Docker 镜像https://twitter.com/bbrodriges/status/873414658178396160CGO_ENABLED=0 go build -ldflags="-s -w" app.go && tar C app | docker import - myimage:latest
- run go format on CI and compare diff这将确保一切都是生成的和承诺的
- 用最新的 Go 运行 Travis-CI,用 travis 1了解更多:https://github.com/travis-ci/travis-build/blob/master/public/version-aliases/go.json
- 检查代码格式是否有错误 diff -u <(echo -n) <(gofmt -d .)
测试
- 测试名称 package_test 比 package 要好
- go test -short 允许减少要运行的测试数
func TestSomething(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode.")
}
}
- 根据系统架构跳过测试
if runtime.GOARM == "arm" {
t.Skip("this doesn't work under ARM")
}
- 用 testing.AllocsPerRun 跟踪你的内存分配https://godoc.org/testing#AllocsPerRun
- 多次运行你的基准测试可以避免噪音。go test -test.bench=. -count=20
工具
- 快速替换 gofmt -w -l -r "panic(err) -> log.Error(err)" .
- go list 允许找到所有直接和传递的依赖关系go list -f '{{ .Imports }}' packagego list -f '{{ .Deps }}' package
- 对于快速基准比较,我们有一个 benchstat 工具。https://godoc.org/golang.org/x/perf/cmd/benchstat
- go-critic linter 从这个文件中强制执行几条建议
- go mod why -m <module> 告诉我们为什么特定的模块在 go.mod 文件中。
- GOGC=off go build ... 应该会加快构建速度 source
- 内存分析器每 512KB 记录一次分配。你能通过 GODEBUG 环境变量增加比例,来查看你的文件的更多详细信息。来源:https://twitter.com/bboreham/status/1105036740253937664
- go mod why -m <module> 告诉我们为什么特定的模块是在 go.mod 文件中。
其他
- dump goroutines https://stackoverflow.com/a/27398062/433041
go func() {
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGQUIT)
buf := make([]byte, 1<<20)
for {
<-sigs
stacklen := runtime.Stack(buf, true)
log.Printf("=== received SIGQUIT ===\n*** goroutine dump...\n%s\n*** end\n" , buf[:stacklen])
}
}()
- 在编译期检查接口的实现 var _ io.Reader = (*MyFastReader)(nil)
- len(nil) = 0https://golang.org/pkg/builtin/#len
- 匿名结构很酷
var hits struct {
sync.Mutex
n int
}
hits.Lock()
hits.n++
hits.Unlock()
- httputil.DumpRequest 是非常有用的东西,不要自己创建https://godoc.org/net/http/httputil#DumpRequest
- 获得调用堆栈,我们可以使用 runtime.Callerhttps://golang.org/pkg/runtime/#Caller
- 要 marshal 任意的 JSON, 你可以 marshal 为 map[string]interface{}{}
- 配置你的 CDPATH 以便你能在任何目录执行 cd github.com/golang/go添加这一行代码到 bashrc(或者其他类似的) export CDPATH=$CDPATH:$GOPATH/src
- 从一个 slice 生成简单的随机元素[]string{"one", "two", "three"}[rand.Intn(3)]
相关推荐
- 悠悠万事,吃饭为大(悠悠万事吃饭为大,什么意思)
-
新媒体编辑:杜岷赵蕾初审:程秀娟审核:汤小俊审签:周星...
- 高铁扒门事件升级版!婚宴上‘冲喜’老人团:我们抢的是社会资源
-
凌晨两点改方案时,突然收到婚庆团队发来的视频——胶东某酒店宴会厅,三个穿大红棉袄的中年妇女跟敢死队似的往前冲,眼瞅着就要扑到新娘的高额钻石项链上。要不是门口小伙及时阻拦,这婚礼造型团队熬了三个月的方案...
- 微服务架构实战:商家管理后台与sso设计,SSO客户端设计
-
SSO客户端设计下面通过模块merchant-security对SSO客户端安全认证部分的实现进行封装,以便各个接入SSO的客户端应用进行引用。安全认证的项目管理配置SSO客户端安全认证的项目管理使...
- 还在为 Spring Boot 配置类加载机制困惑?一文为你彻底解惑
-
在当今微服务架构盛行、项目复杂度不断攀升的开发环境下,SpringBoot作为Java后端开发的主流框架,无疑是我们手中的得力武器。然而,当我们在享受其自动配置带来的便捷时,是否曾被配置类加载...
- Seata源码—6.Seata AT模式的数据源代理二
-
大纲1.Seata的Resource资源接口源码2.Seata数据源连接池代理的实现源码3.Client向Server发起注册RM的源码4.Client向Server注册RM时的交互源码5.数据源连接...
- 30分钟了解K8S(30分钟了解微积分)
-
微服务演进方向o面向分布式设计(Distribution):容器、微服务、API驱动的开发;o面向配置设计(Configuration):一个镜像,多个环境配置;o面向韧性设计(Resista...
- SpringBoot条件化配置(@Conditional)全面解析与实战指南
-
一、条件化配置基础概念1.1什么是条件化配置条件化配置是Spring框架提供的一种基于特定条件来决定是否注册Bean或加载配置的机制。在SpringBoot中,这一机制通过@Conditional...
- 一招解决所有依赖冲突(克服依赖)
-
背景介绍最近遇到了这样一个问题,我们有一个jar包common-tool,作为基础工具包,被各个项目在引用。突然某一天发现日志很多报错。一看是NoSuchMethodError,意思是Dis...
- 你读过Mybatis的源码?说说它用到了几种设计模式
-
学习设计模式时,很多人都有类似的困扰——明明概念背得滚瓜烂熟,一到写代码就完全想不起来怎么用。就像学了一堆游泳技巧,却从没下过水实践,很难真正掌握。其实理解一个知识点,就像看立体模型,单角度观察总...
- golang对接阿里云私有Bucket上传图片、授权访问图片
-
1、为什么要设置私有bucket公共读写:互联网上任何用户都可以对该Bucket内的文件进行访问,并且向该Bucket写入数据。这有可能造成您数据的外泄以及费用激增,若被人恶意写入违法信息还可...
- spring中的资源的加载(spring加载原理)
-
最近在网上看到有人问@ContextConfiguration("classpath:/bean.xml")中除了classpath这种还有其他的写法么,看他的意思是想从本地文件...
- Android资源使用(android资源文件)
-
Android资源管理机制在Android的开发中,需要使用到各式各样的资源,这些资源往往是一些静态资源,比如位图,颜色,布局定义,用户界面使用到的字符串,动画等。这些资源统统放在项目的res/独立子...
- 如何深度理解mybatis?(如何深度理解康乐服务质量管理的5个维度)
-
深度自定义mybatis回顾mybatis的操作的核心步骤编写核心类SqlSessionFacotryBuild进行解析配置文件深度分析解析SqlSessionFacotryBuild干的核心工作编写...
- @Autowired与@Resource原理知识点详解
-
springIOCAOP的不多做赘述了,说下IOC:SpringIOC解决的是对象管理和对象依赖的问题,IOC容器可以理解为一个对象工厂,我们都把该对象交给工厂,工厂管理这些对象的创建以及依赖关系...
- java的redis连接工具篇(java redis client)
-
在Java里,有不少用于连接Redis的工具,下面为你介绍一些主流的工具及其特点:JedisJedis是Redis官方推荐的Java连接工具,它提供了全面的Redis命令支持,且...
- 一周热门
- 最近发表
- 标签列表
-
- mybatiscollection (79)
- mqtt服务器 (88)
- keyerror (78)
- c#map (65)
- resize函数 (64)
- xftp6 (83)
- bt搜索 (75)
- c#var (76)
- mybatis大于等于 (64)
- xcode-select (66)
- mysql授权 (74)
- 下载测试 (70)
- linuxlink (65)
- pythonwget (67)
- androidinclude (65)
- logstashinput (65)
- hadoop端口 (65)
- vue阻止冒泡 (67)
- oracle时间戳转换日期 (64)
- jquery跨域 (68)
- php写入文件 (73)
- kafkatools (66)
- mysql导出数据库 (66)
- jquery鼠标移入移出 (71)
- 取小数点后两位的函数 (73)