关于Go你不得不知道的小技巧
- Go 箴言
- Go 之禅
- 代码
- 使用
go fmt
格式化 - 多个 if 语句可以折叠成 switch
- 用
chan struct{}
来传递信号,chan bool
表达的不够清楚 30 * time.Second
比time.Duration(30) * time.Second
更好- 用
time.Duration
代替int64
+ 变量名 - 按类型分组
const
声明,按逻辑和/或类型分组var
- 不要在你不拥有的结构上使用
encoding/gob
- 不要依赖于计算顺序,特别是在 return 语句中。
- 防止结构体字段用纯值方式初始化,添加
_ struct {}
字段: - 为了防止结构比较,添加
func
类型的空字段 http.HandlerFunc
比http.Handler
更好- 移动
defer
到顶部 - JavaScript 解析整数为浮点数并且你的 int64 可能溢出
- 使用
- 并发
- 性能
- 构建
- 测试
- 工具
- 其他
Go 箴言
- 不要通过共享内存进行通信,通过通信共享内存
- 并发不是并行
- 管道用于协调;互斥量(锁)用于同步
- 接口越大,抽象就越弱
- 利用好零值
- 空接口
interface{}
没有任何类型约束 - Gofmt 的风格不是人们最喜欢的,但 gofmt 是每个人的最爱
- 允许一点点重复比引入一点点依赖更好
- 系统调用必须始终使用构建标记进行保护
- 必须始终使用构建标记保护 Cgo
- Cgo 不是 Go
- 使用标准库的
unsafe
包,不能保证能如期运行 - 清晰比聪明更好
- 反射永远不清晰
- 错误是值
- 不要只检查错误,还要优雅地处理它们
- 设计架构,命名组件,(文档)记录细节
- 文档是供用户使用的
- 不要(在生产环境)使用
panic()
Go 之禅
- 每个 package 实现单一的目的
- 显式处理错误
- 尽早返回,而不是使用深嵌套
- 让调用者处理并发(带来的问题)
- 在启动一个 goroutine 时,需要知道何时它会停止
- 避免 package 级别的状态
- 简单很重要
- 编写测试以锁定 package API 的行为
- 如果你觉得慢,先编写 benchmark 来证明
- 适度是一种美德
- 可维护性
代码
使用 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
接口** 检查
defer
中的错误
defer func() {
err := ocp.Close()
if err != nil {
rerr = err
}
}()
** 不要在
checkErr
函数中使用panic()
或os.Exit()
** 仅仅在很特殊情况下才使用 panic, 你必须要去处理 error
** 不要给枚举使用别名,因为这打破了类型安全
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 的零值是 nil
```
var s []int
fmt.Println(s, len(s), cap(s))
if s == nil {
fmt.Println("nil!")
}
// Output:
// [] 0 0
// nil!
```
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{}
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
}
**
- 例如:
errors.Wrap(err, "additional message to a given error")
- 例如:
**
在 Go 里面要小心使用
range
:for i := range a
andfor i, v := range &a
,都不是a
的副本- 但是
for i, v := range a
里面的就是a
的副本
**
从 map 读取一个不存在的 key 将不会 panic
value := map["no_key"]
将得到一个 0 值value, ok := map["no_key"]
更好
**
不要使用原始参数进行文件操作
- 而不是一个八进制参数
os.MkdirAll(root, 0700)
- 使用此类型的预定义常量
os.FileMode
- 而不是一个八进制参数
**
不要忘记为
iota
指定一种类型
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
是并发安全的。** 当你需要一个自定义类型的 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
orstrconv.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 过程
- 但是在使用它之前要进行定制!
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
不是万能的,没有很强的理由就不要使用它。**
在
sync.Pool
中分配内存存储非指针数据**
为了隐藏逃生分析的指针,你可以小心使用这个函数::
// 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)
构建
** 用这个命令
go build -ldflags="-s -w" ...
去掉你的二进制文件** 拆分构建不同版本的简单方法
- 用
// +build integration
并且运行他们go test -v --tags integration .
- 用
** 最小的 Go Docker 镜像
- https://twitter.com/bbrodriges/status/873414658178396160
CGO_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
** 检查代码格式是否有错误
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
跟踪你的内存分配** 多次运行你的基准测试可以避免噪音。
go test -test.bench=. -count=20
工具
**
快速替换
gofmt -w -l -r "panic(err) -> log.Error(err)" .
**
go list
允许找到所有直接和传递的依赖关系go list -f '{{ .Imports }}' package
go list -f '{{ .Deps }}' package
**
对于快速基准比较,我们有一个
benchstat
工具。**
go-critic linter 从这个文件中强制执行几条建议
**
go mod why -m <module>
告诉我们为什么特定的模块在go.mod
文件中。**
GOGC=off go build ...
应该会加快构建速度 source**
内存分析器每 512KB 记录一次分配。你能通过
GODEBUG
环境变量增加比例,来查看你的文件的更多详细信息。**
go mod why -m <module>
告诉我们为什么特定的模块是在go.mod
文件中。
其他
- ** dump goroutines
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) = 0
** 匿名结构很酷
var hits struct {
sync.Mutex
n int
}
hits.Lock()
hits.n++
hits.Unlock()
**
httputil.DumpRequest
是非常有用的东西,不要自己创建**
获得调用堆栈,我们可以使用
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)]
参考资料:
https://github.com/cristaloleg/go-advice/blob/master/README_ZH.md
关于Go你不得不知道的小技巧的更多相关文章
- 漫谈程序员(十一)老鸟程序员知道而新手不知道的小技巧之Web 前端篇
老鸟程序员知道而新手不知道的小技巧 Web 前端篇 常充电!程序员只有一种死法:土死的. 函数不要超过50行. 不要一次性写太多来不及测的代码,而是要写一段调试一段. UI和编码要同步做. 多写注释方 ...
- iPhone 上你可能还不知道的小技巧
用了这么久的 iPhone,这些技巧你可能都还不知道哦. 1.怎么用耳机切歌? 将耳机的话筒部位的中间(平时暂停用的,按一下)连按两下 即可. 连按两下,下一首. 连按三下,上一首. 2.摇一摇,相当 ...
- [iOS翻译]《iOS 7 Programming Pushing the Limits》系列:你可能不知道的Objective-C技巧
简介: 如果你阅读这本书,你可能已经牢牢掌握iOS开发的基础,但这里有一些小特点和实践是许多开发者并不熟悉的,甚至有数年经验的开发者也是.在这一章里,你会学到一些很重要的开发技巧,但这仍远远不够,你还 ...
- [No0000194]聊聊 Chrome DevTools 中你可能不知道的调试技巧
对于前端开发者来说,ChromeDevTools 绝对是不可或缺的调试工具,我们常用的调试方法包含一些console等,而ChromeDevTools 其实很强大,下面来聊聊一些你可能不知道的debu ...
- 你可能不知道的 Mac 技巧 - 文本操作
找不到 Mac 上的 Home,End,PageUp?想截图还得打开 QQ?不知道 Mac 如何剪切文件?找不到全屏窗口的按钮?找不到隐藏文件夹?不知道如何向后删除?想少用鼠标,多用键盘?…… 希望我 ...
- 初学者可能不知道的vue技巧
前言 大家好,这里是@IT·平头哥联盟,我是首席甩锅官——老金,今天给大家分享的,一些日常中神秘而又简单的vue的实用小技巧,以及我在我司项目中实用vue的总结和坑,跟大家一起分享,希望能给其他攻城狮 ...
- 你可能不知道的 transition 技巧与细节
CSS 中,transition 属性用于指定为一个或多个 CSS 属性添加过渡效果. 最为常见的用法,也就是给元素添加一个 transition,让其某个属性从状态 A 变化到状态 B 时,不再是非 ...
- 你可能不知道的 Python 技巧
英文 | Python Tips and Trick, You Haven't Already Seen 原作 | Martin Heinz (https://martinheinz.dev) 译者 ...
- 【Linux】- 不可不知的小技巧
1.Tab键:输入文件或目录名的前几个字符,然后按TAB键,如无相重的,完整的文件名立即自动在命令行出现:如有相重的,再按一下TAB键,系统会列出当前目录下所有以这几个字符开头的名字. 在命令行下,只 ...
- 初始CSS3小知识【99%人不知道的小技巧】
一.引入样式 1.行内样式表 <h1 style="color: red;font-size: 18px;">10-30</h1> 2.内 ...
随机推荐
- Mac根据端口找进程id
lsof -i:20942 以后认真的学习一下这个命令
- 邮箱的代理发送Send as权限不生效
邮箱的代理发送Sendas权限不生效 最近,有需求为用户添加其它邮箱的代理发送Sendas权限.在Exchange的管理单元里添加完毕后,发现没有效果,客户端提示你没有权限以用户的名义发送邮件 ...
- 2021年1月-第02阶段-前端基础-HTML+CSS阶段-Day01
HTML5 第一天 一.什么是 HTML5 1.HTML5 的概念与定义 定义:HTML5 定义了 HTML 标准的最新版本,是对 HTML 的第五次重大修改,号称下一代的 HTML 两个概念: 是一 ...
- 利用Kafka的Assign模式实现超大群组(10万+)消息推送
引言 IM即时通信场景下,最重要的一个能力就是推送:在线的直接通过长连接网关服务转发,离线的通过APNS或者极光等系统进行推送. 本文主要是针对在线用户推送场景来进行总结和探讨:如何利用Kafka ...
- Traefik 控制面板 SaaS 服务 Pilot
文章转载自:https://mp.weixin.qq.com/s?__biz=MzU4MjQ0MTU4Ng==&mid=2247485572&idx=1&sn=8ffa2bc7 ...
- linux系统安装Confluence
转载网址:https://blog.yupaits.com/blog/record/linux-confluence.html#安装步骤 Confluence简介 Confluence是一个专业的企业 ...
- 《HelloGitHub》第 78 期
兴趣是最好的老师,HelloGitHub 让你对编程感兴趣! 简介 HelloGitHub 分享 GitHub 上有趣.入门级的开源项目. https://github.com/521xueweiha ...
- PAT (Basic Level) Practice 1006 换个格式输出整数 分数 15
让我们用字母 B 来表示"百".字母 S 表示"十",用 12...n 来表示不为零的个位数字 n(<10),换个格式来输出任一个不超过 3 位的正整数. ...
- androidmanifest.xml 反编译
androidmanifest.xml 反编译 去除更新只修改androidmanifest.xml内容 解压apk文件后得到这个文件androidmanifest.xml windwos安装java ...
- 关于JDK8中stream的用法小总结。
import java.io.Serializable; import java.util.*; import java.util.stream.Collectors; public class Ma ...