mirror of
https://github.com/teivah/100-go-mistakes.git
synced 2026-06-21 00:47:11 +08:00
Merge pull request #89 from hezhizhen/chinese
Update Chinese translation
This commit is contained in:
commit
9990a8c753
2 changed files with 132 additions and 132 deletions
|
|
@ -2214,7 +2214,7 @@ func main() {
|
|||
}
|
||||
```
|
||||
|
||||
Runnig this code with the `-race` logs the following warning:
|
||||
Running this code with the `-race` logs the following warning:
|
||||
|
||||
```bash hl_lines="3 7 11"
|
||||
==================
|
||||
|
|
|
|||
90
docs/zh.md
90
docs/zh.md
|
|
@ -51,7 +51,7 @@ comments: true
|
|||
|
||||
### 未意识到类型嵌套的可能问题 (#10)
|
||||
|
||||
使用类型嵌套也可以避免写一些重复代码,然而,在使用时需要确保不会导致不合理的可见性问题,比如有些字段应该对外隐藏不应该被暴漏。
|
||||
使用类型嵌套也可以避免写一些重复代码,然而,在使用时需要确保不会导致不合理的可见性问题,比如有些字段应该对外隐藏不应该被暴露。
|
||||
|
||||
### 不使用 function option 模式 (#11)
|
||||
|
||||
|
|
@ -59,23 +59,23 @@ comments: true
|
|||
|
||||
### 工程组织不合理 (工程结构和包的组织) (#12)
|
||||
|
||||
遵循像 project-layout 的建议来组织Go工程是一个不错的方法,特别是你正在寻找一些类似的经验、惯例来组织一个新的Go工程的时候。
|
||||
遵循像 project-layout 的建议来组织 Go 工程是一个不错的方法,尤其是你正在寻找一些类似的经验、惯例来组织一个新的 Go 工程的时候。
|
||||
|
||||
### 创建工具包 (#13)
|
||||
|
||||
命名是软件设计开发中非常重要的一个部分,创建一些名如 `common`、`util`、`shared` 之类的包名并不会给读者带来太大价值,应该将这些包名重构为更清晰、更聚焦的包名。
|
||||
命名是软件设计开发中非常重要的一个部分,创建一些名如 `common`、`util`、`shared` 之类的包名并不会给读者带来太大价值,应该将这些包名重构为更清晰、更具体的包名。
|
||||
|
||||
### 忽略了包名冲突 (#14)
|
||||
|
||||
为了避免变量名和包名之间的冲突,导致混淆或甚至错误,应为每个变量和包使用唯一的名称。如果这不可行,可以考虑使用导入别名 `import importAlias 'importPath'`,以区分包名和变量名,或者考虑一个更好的变量名。
|
||||
为了避免变量名和包名之间的冲突,导致混淆或甚至错误,应为每个变量和包使用唯一的名称。如果这不可行,可以考虑使用导入别名 `import importAlias 'importPath'` 以区分包名和变量名,或者考虑一个更好的变量名。
|
||||
|
||||
### 代码缺少文档 (#15)
|
||||
|
||||
为了让使用方、维护人员能更清晰地了解你的代码的意图,导出的元素(函数、类型、字段)需要添加godoc注释。
|
||||
为了让使用方、维护人员能更清晰地了解你的代码的意图,导出的元素(函数、类型、字段)需要添加注释。
|
||||
|
||||
### 不使用 linters 检查 (#16)
|
||||
|
||||
为了改善代码质量、整体代码的一致性,应该使用linters、formatters。
|
||||
为了改善代码质量、整体代码的一致性,应该使用 linters 和 formatters。
|
||||
|
||||
## 数据类型
|
||||
|
||||
|
|
@ -99,7 +99,7 @@ comments: true
|
|||
|
||||
### 不高效的 slice 初始化 (#21)
|
||||
|
||||
当创建一个slice时,如果其长度可以预先确定,那么可以在定义时指定它的长度和容量。这可以改善后期append时一次或者多次的内存分配操作,从而改善性能。对于map的初始化也是这样的。
|
||||
当创建一个 slice 时,如果其长度可以预先确定,那么可以在定义时指定它的长度和容量。这可以改善后期 append 时一次或者多次的内存分配操作,从而改善性能。对于 map 的初始化也是如此。
|
||||
|
||||
### 困惑于 nil 和空 slice (#22)
|
||||
|
||||
|
|
@ -107,7 +107,7 @@ comments: true
|
|||
|
||||
### 没有适当检查 slice 是否为空 (#23)
|
||||
|
||||
检查一个slice的是否包含任何元素,可以检查其长度,不管slice是nil还是empty,检查长度都是有效的。这个检查方法也适用于map。
|
||||
检查一个 slice 是否包含任何元素,可以检查其长度,不管 slice 是 nil 还是 empty,检查长度都是有效的。这个检查方法也适用于 map。
|
||||
|
||||
为了设计更明确的 API,API 不应区分 nil 和空切片。
|
||||
|
||||
|
|
@ -117,11 +117,11 @@ comments: true
|
|||
|
||||
### slice append 带来的预期之外的副作用 (#25)
|
||||
|
||||
如果两个不同的函数操作的slice复用了相同的底层数组,它们对slice执行append操作时可能会产生冲突。使用copy来完整复制一个slice或者使用完整的slice表达式[low:high:max]限制最大容量,有助于避免产生冲突。当想对一个大slice进行shrink操作时,两种方式中,只有copy才可以避免内存泄漏。
|
||||
如果两个不同的函数操作的 slice 复用了相同的底层数组,它们对 slice 执行 append 操作时可能会产生冲突。使用 copy 来完整复制一个 slice 或者使用完整的 slice 表达式 `[low:high:max]` 限制最大容量,有助于避免产生冲突。当想对一个大 slice 进行 shrink 操作时,两种方式中,只有 copy 才可以避免内存泄漏。
|
||||
|
||||
### slice 和内存泄漏 (#26)
|
||||
|
||||
对于slice元素为指针,或者slice元素为struct但是该struct含有指针字段,当通过slice[low:high]操作取subslice时,对于那些不可访问的元素可以显示设置为nil来避免内存泄露。
|
||||
对于 slice 元素为指针,或者 slice 元素为 struct 但是该 struct 含有指针字段,当通过 `slice[low:high]` 操作取 subslice 时,对于那些不可访问的元素可以显式设置为 nil 来避免内存泄露。
|
||||
|
||||
### 不高效的 map 初始化 (#27)
|
||||
|
||||
|
|
@ -129,7 +129,7 @@ comments: true
|
|||
|
||||
### [map 和内存泄漏](https://100go.co/28-maps-memory-leaks/) (#28)
|
||||
|
||||
一个map的buckets占用的内存只会增长,不会缩减。因此,如果它导致了一些内存占用的问题,你需要尝试不同的选项来解决,比如重新创建一个map代替原来的(原来的map会被GC掉),或者map[keyType]valueType中的valueType使用指针代替长度固定的数组或者sliceHeader来缓解过多的内存占用。
|
||||
一个 map 的 buckets 占用的内存只会增长,不会缩减。因此,如果它导致了一些内存占用的问题,你需要尝试不同的方式来解决,比如重新创建一个 map 代替原来的(原来的 map 会被 GC 掉),或者 `map[keyType]valueType` 中的 valueType 使用指针代替长度固定的数组或者 sliceHeader 来缓解过多的内存占用。
|
||||
|
||||
### 不正确的值比较 (#29)
|
||||
|
||||
|
|
@ -143,16 +143,16 @@ Go中比较两个类型值时,如果是可比较类型,那么可以使用 `=
|
|||
|
||||
### 忽略了 `range` 循环中迭代目标值的计算方式 (channels 和 arrays) (#31)
|
||||
|
||||
传递给 `range` 操作的迭代目标对应的表达式的值,只会在循环执行前被计算一次,理解这个有助于避免犯一些常见的错误,例如不高效的channel赋值操作、slice迭代操作。
|
||||
传递给 `range` 操作的迭代目标对应的表达式的值,只会在循环执行前被计算一次,理解这个有助于避免犯一些常见的错误,例如不高效的 channel 赋值操作和 slice 迭代操作。
|
||||
|
||||
### 忽略了 `range` 循环中指针元素的影响 `range` loops (#32)
|
||||
### 忽略了 `range` 循环中指针元素的影响 (#32)
|
||||
|
||||
这里其实强调的是 `range` 迭代过程中,迭代变量实际上是一个拷贝,假设给另外一个容器元素(指针类型)赋值,且需要对迭代变量取地址转换成指针再赋值的话,这里潜藏着一个错误,就是for循环迭代变量是 per-variable-per-loop 而不是 per-variable-per-iteration。如果是通过局部变量(用迭代变量来初始化)或者使用索引值来直接引用迭代的元素,将有助于避免拷贝指针(迭代变量的地址)之类的bug。
|
||||
这里其实强调的是 `range` 迭代过程中,迭代变量实际上是一个拷贝。假设给另外一个容器元素(指针类型)赋值,且需要对迭代变量取地址转换成指针再赋值的话,这里潜藏着一个错误,就是 for 循环迭代变量是 per-variable-per-loop 而不是 per-variable-per-iteration。如果是通过局部变量(用迭代变量来初始化)或者使用索引值来直接引用迭代的元素,将有助于避免拷贝指针(迭代变量的地址)之类的 bug。
|
||||
|
||||
### map 迭代过程中的错误假设(遍历顺序和迭代过程中插入)(#33)
|
||||
|
||||
使用 map 时,为了能得到确定一致的结果,应该记住 Go 中的 map 数据结构:
|
||||
* 不会按照key对data进行排序,遍历时不是按key有序的;
|
||||
* 不会按照 key 对 data 进行排序,遍历时 key 不是有序的;
|
||||
* 遍历时的顺序,也不是按照插入时的顺序;
|
||||
* 没有一个确定性的遍历顺序,每次遍历顺序是不同的;
|
||||
* 不能保证迭代过程中新插入的元素,在当前迭代中能够被遍历到;
|
||||
|
|
@ -173,7 +173,7 @@ Go中比较两个类型值时,如果是可比较类型,那么可以使用 `=
|
|||
|
||||
### 不正确的字符串遍历 (#37)
|
||||
|
||||
使用 `range` 操作符对一个string进行遍历实际上是对string对应的 `[]rune` 进行遍历,迭代变量中的索引值,表示的当前rune对应的 `[]byte` 在整个 `[]byte(string)` 中的起始索引。如果要访问string中的某一个rune(比如第三个),首先要将字符串转换为 `[]rune` 然后再按索引值访问。
|
||||
使用 `range` 操作符对一个 string 进行遍历实际上是对 string 对应的 `[]rune` 进行遍历,迭代变量中的索引值,表示的当前 rune 对应的 `[]rune` 在整个 `[]rune(string)` 中的起始索引。如果要访问 string 中的某一个 rune(比如第三个),首先要将字符串转换为 `[]rune` 然后再按索引值访问。
|
||||
|
||||
### 误用 trim 函数 (#38)
|
||||
|
||||
|
|
@ -189,7 +189,7 @@ Go中比较两个类型值时,如果是可比较类型,那么可以使用 `=
|
|||
|
||||
### 子字符串和内存泄漏 (#41)
|
||||
|
||||
使用一个子字符串的拷贝,有助于避免内存泄漏,因为对一个字符串的s[low:high]操作返回的子字符串,其使用了和原字符串s相同的底层数组。
|
||||
使用一个子字符串的拷贝,有助于避免内存泄漏,因为对一个字符串的 `s[low:high]` 操作返回的子字符串,其使用了和原字符串 s 相同的底层数组。
|
||||
|
||||
## 函数和方法
|
||||
|
||||
|
|
@ -209,7 +209,7 @@ Go中比较两个类型值时,如果是可比较类型,那么可以使用 `=
|
|||
|
||||
### 返回一个 nil 接收器 (#45)
|
||||
|
||||
当返回一个interface参数时,需要小心,不要返回一个nil指针,而是应该显示返回一个nil值。否则,可能会发生一些预期外的问题,因为调用方会收到一个非nil的值。
|
||||
当返回一个 interface 参数时,需要小心,不要返回一个 nil 指针,而是应该显式返回一个 nil 值。否则,可能会发生一些预期外的问题,因为调用方会收到一个非 nil 的值。
|
||||
|
||||
### 使用文件名作为函数入参 (#46)
|
||||
|
||||
|
|
@ -217,7 +217,7 @@ Go中比较两个类型值时,如果是可比较类型,那么可以使用 `=
|
|||
|
||||
### 忽略 `defer` 语句中参数、接收器值的计算方式 (参数值计算, 指针, 和 value 类型接收器) (#47)
|
||||
|
||||
为了避免 `defer` 语句执行时就立即计算对defer要执行的函数的参数进行计算,可以考虑将要执行的函数放到闭包里面,然后通过指针传递参数给闭包内函数(或者通过闭包捕获外部变量),来解决这个问题。
|
||||
为了避免 `defer` 语句执行时就立即对 defer 要执行的函数的参数进行计算,可以考虑将要执行的函数放到闭包里面,然后通过指针传递参数给闭包内函数(或者通过闭包捕获外部变量),来解决这个问题。
|
||||
|
||||
## 错误管理
|
||||
|
||||
|
|
@ -239,17 +239,17 @@ Wrapping(包装)错误允许您标记错误、提供额外的上下文信息
|
|||
|
||||
为了表达一个预期内的错误,请使用错误值的方式,并通过 `==` 或者 `errors.Is` 来比较。而对于意外错误,则应使用特定的错误类型(可以通过 `errors.As` 来比较)。
|
||||
|
||||
### 处理同一个错误两次 (#52)
|
||||
### 两次处理同一个错误 (#52)
|
||||
|
||||
大多数情况下,错误仅需要处理一次。打印错误日志也是一种错误处理。因此,当函数内发生错误时,应该在打印日志和返回错误中选择其中一种。包装错误也可以提供问题发生的额外上下文信息,也包括了原来的错误(可考虑交给调用方负责打日志)。
|
||||
|
||||
### 不处理错误 (#53)
|
||||
|
||||
不管是在函数调用时,还是在一个 `defer` 函数执行时,如果想要忽略一个错误,应该显示地通过 `_` 来忽略(可注明忽略的原因)。否则,将来的读者就会感觉到困惑,忽略这个错误是有意为之还是无意中漏掉了。
|
||||
不管是在函数调用时,还是在一个 `defer` 函数执行时,如果想要忽略一个错误,应该显式地通过 `_` 来忽略(可注明忽略的原因)。否则,将来的读者就会感觉到困惑,忽略这个错误是有意为之还是无意中漏掉了。
|
||||
|
||||
### 不处理 `defer` 中的错误 (#54)
|
||||
|
||||
大多数情况下,你不应该忽略 `defer` 函数执行时返回的错误,或者显示处理它,或者将它传递给调用方处理,可以根据情景进行选择。如果你确定要忽略这个错误,请显示使用 `_` 来忽略。
|
||||
大多数情况下,你不应该忽略 `defer` 函数执行时返回的错误,或者显式处理它,或者将它传递给调用方处理,可以根据情景进行选择。如果你确定要忽略这个错误,请显式使用 `_` 来忽略。
|
||||
|
||||
## 并发编程: 基础
|
||||
|
||||
|
|
@ -267,7 +267,7 @@ Wrapping(包装)错误允许您标记错误、提供额外的上下文信息
|
|||
|
||||
### 不明白竞态问题 (数据竞态 vs. 竞态条件和 Go 内存模型) (#58)
|
||||
|
||||
掌握并发意味着要认识到数据竞争(data races)和竞态条件(race conditions)是两个不同的概念。数据竞争,指的是有多个goroutines同时访问相同内存区域时,没有必要的同步控制,且其中至少有一个goroutine是执行的写操作。同时要认识到,没有发生数据竞争不代表程序的执行是确定性的、没问题的。当在某个特定的操作顺序或者特定的事件发生顺序下,如果最终的行为是不可控的,这就是竞态条件。
|
||||
掌握并发意味着要认识到数据竞争(data races)和竞态条件(race conditions)是两个不同的概念。数据竞争,指的是有多个 goroutines 同时访问相同内存区域时,缺乏必要的同步控制,且其中至少有一个 goroutine 执行的是写操作。同时要认识到,没有发生数据竞争不代表程序的执行是确定性的、没问题的。当在某个特定的操作顺序或者特定的事件发生顺序下,如果最终的行为是不可控的,这就是竞态条件。
|
||||
|
||||
> ps:数据竞争是竞态条件的子集,竞态条件不仅局限于访存未同步,它可以发生在更高的层面。`go test -race` 检测的是数据竞争,需要同步来解决,而开发者还需要关注面更广的竞态条件,它需要对多个 goroutines 的执行进行编排。
|
||||
|
||||
|
|
@ -275,7 +275,7 @@ Wrapping(包装)错误允许您标记错误、提供额外的上下文信息
|
|||
|
||||
### 不理解不同工作负载类型对并发的影响 (#59)
|
||||
|
||||
当创建一定数量的goroutines是,需要考虑工作负载的类型。如果工作负载是CPU密集型的,那么goroutines数量应该接近于 `GOMAXPROCS` 的值(该值取决于主机处理器核心数)。如果工作负载是IO密集型的,goroutines数量就需要考虑多种因素,比如外部系统(考虑请求、响应速率)。
|
||||
当创建一定数量的 goroutines 时,需要考虑工作负载的类型。如果工作负载是 CPU 密集型的,那么 goroutines 数量应该接近于 `GOMAXPROCS` 的值(该值取决于主机处理器核心数)。如果工作负载是 IO 密集型的,goroutines 数量就需要考虑多种因素,比如外部系统(考虑请求、响应速率)。
|
||||
|
||||
### 误解了 Go contexts (#60)
|
||||
|
||||
|
|
@ -295,7 +295,7 @@ Go 的上下文(context)也是 Go 并发编程的基石之一。上下文允
|
|||
|
||||
### 不注意处理 goroutines 和循环中的迭代变量 (#63)
|
||||
|
||||
为了避免goroutines和循环中的迭代变量问题,可以考虑创建局部变量并将迭代变量赋值给局部变量,或者goroutine调用带参数的函数,将迭代变量值作为参数值传入,来代替goroutine调用闭包。
|
||||
为了避免 goroutines 和循环中的迭代变量问题,可以考虑创建局部变量并将迭代变量赋值给局部变量,或者 goroutines 调用带参数的函数,将迭代变量值作为参数值传入,来代替 goroutines 调用闭包。
|
||||
|
||||
### 使用 select + channels 时误以为分支选择顺序是确定的 (#64)
|
||||
|
||||
|
|
@ -305,7 +305,7 @@ Go 的上下文(context)也是 Go 并发编程的基石之一。上下文允
|
|||
|
||||
发送通知时使用 `chan struct{}` 类型。
|
||||
|
||||
> ps: 先明白什么是通知channels,一个通知channels指的是只是用来做通知,而其中传递的数据没有意义,或者理解成不传递数据的channels,这种称为通知channels。其中传递的数据的类型struct{}更合适。
|
||||
> ps: 先明白什么是通知 channels,一个通知 channels 指的是只是用来做通知,而其中传递的数据没有意义,或者理解成不传递数据的 channels,这种称为通知 channels。其中传递的数据的类型为 struct{} 更合适。
|
||||
|
||||
### 不使用 nil channels (#66)
|
||||
|
||||
|
|
@ -323,7 +323,7 @@ Go 的上下文(context)也是 Go 并发编程的基石之一。上下文允
|
|||
|
||||
意识到字符串格式化可能会导致调用现有函数,这意味着需要注意可能的死锁和其他数据竞争问题。
|
||||
|
||||
> ps: 核心是要关注 `fmt.Sprintf` + `%v` 进行字符串格式化时 `%v` 具体到不同的类型值时,实际上执行的操作是什么。比如 `%v` 这个placeholder对应的值时一个 `context.Context`,那么会就遍历其通过 `context.WithValue` 附加在其中的 values,这个过程可能涉及到数据竞争问题。书中提及的另一个导致死锁的案例本质上也是一样的问题,只不过又额外牵扯到了 `sync.RWMutex` 不可重入的问题。
|
||||
> ps: 核心是要关注 `fmt.Sprintf` + `%v` 进行字符串格式化时 `%v` 具体到不同的类型值时,实际上执行的操作是什么。比如 `%v` 这个 placeholder 对应的值是一个 `context.Context`,那么会就遍历其通过 `context.WithValue` 附加在其中的 values,这个过程可能涉及到数据竞争问题。书中提及的另一个导致死锁的案例本质上也是一样的问题,只不过又额外牵扯到了 `sync.RWMutex` 不可重入的问题。
|
||||
|
||||
### 使用 append 不当导致数据竞争 (#69)
|
||||
|
||||
|
|
@ -331,9 +331,9 @@ Go 的上下文(context)也是 Go 并发编程的基石之一。上下文允
|
|||
|
||||
### 误用 mutexes 和 slices、maps (#70)
|
||||
|
||||
请记住 slices 和 maps 引用类型,有助于避免常见的数据竞争问题。
|
||||
请记住 slices 和 maps 是引用类型,有助于避免常见的数据竞争问题。
|
||||
|
||||
> ps: 这里实际是因为错误理解了 slices 和 maps,导致写出了错误的拷贝 slices 和 maps 的代码,进而导致锁保护无效、出现数据竞争问题。
|
||||
> ps: 这里实际是因为错误理解了 slices 和 maps,导致写出了错误地拷贝 slices 和 maps 的代码,进而导致锁保护无效、出现数据竞争问题。
|
||||
|
||||
### 误用 `sync.WaitGroup` (#71)
|
||||
|
||||
|
|
@ -355,7 +355,7 @@ Go 的上下文(context)也是 Go 并发编程的基石之一。上下文允
|
|||
|
||||
### 使用了错误的 time.Duration (#75)
|
||||
|
||||
注意有些函数接收一个 `time.Duration` 类型的参数时,尽管直接传递一个整数是可以的,但最好还是使用 time API 中的方法来传递 duration,以避免可能造成的困惑、bug。
|
||||
注意有些函数接收一个 `time.Duration` 类型的参数时,尽管直接传递一个整数是可以的,但最好还是使用 time API 中的方法来传递 duration,以避免可能造成的困惑和 bug。
|
||||
|
||||
> ps: 重点是注意 time.Duration 定义的是 nanoseconds 数。
|
||||
|
||||
|
|
@ -367,7 +367,7 @@ Go 的上下文(context)也是 Go 并发编程的基石之一。上下文允
|
|||
|
||||
* 类型嵌套导致的预料外的行为
|
||||
|
||||
要当心在Go结构体中嵌入字段,这样做可能会导致诸如嵌入的 `time.Time` 字段实现 `json.Marshaler` 接口,从而覆盖默认的json序列。
|
||||
要当心在 Go 结构体中嵌入字段,这样做可能会导致诸如嵌入的 `time.Time` 字段实现 `json.Marshaler` 接口,从而覆盖默认的 JSON 序列。
|
||||
|
||||
* JSON 和单调时钟
|
||||
|
||||
|
|
@ -399,9 +399,9 @@ Go 的上下文(context)也是 Go 并发编程的基石之一。上下文允
|
|||
|
||||
调用 `sql.Rows` 的 `Err` 方法来确保在准备下一个行时没有遗漏错误。
|
||||
|
||||
### 不关闭临时资源(HTTP 请求体、`sql.Rows` 和 `os.File`) (#79)
|
||||
### 不关闭临时资源(HTTP 请求体、`sql.Rows` 和 `os.File`) (#79)
|
||||
|
||||
最终要注意关闭所有实现 `io.Closer` 接口的结构体,以避免可能的泄漏。
|
||||
最终要注意关闭所有实现 `io.Closer` 接口的结构体,以避免可能的泄漏。
|
||||
|
||||
### 响应 HTTP 请求后没有返回语句 (#80)
|
||||
|
||||
|
|
@ -421,17 +421,17 @@ Go 的上下文(context)也是 Go 并发编程的基石之一。上下文允
|
|||
|
||||
### 不打开 race 开关 (#83)
|
||||
|
||||
打开 `-race` 开关在编写并发应用时非常重要。这能帮助你捕获可能的数据竞争,从而避免软件bug。
|
||||
打开 `-race` 开关在编写并发应用时非常重要。这能帮助你捕获可能的数据竞争,从而避免软件 bug。
|
||||
|
||||
### 不打开测试的执行模式开关 (parallel 和 shuffle) (#84)
|
||||
|
||||
打开开关 `-parallel` 有助于加速测试的执行,特别是测试中包含一些需要长期运行的用例的时候。
|
||||
|
||||
打开开关 `-shuffle` 能够打乱测试用例执行的顺序,避免一个测试依赖于某些不符合真实情况的预设,有助于及早暴漏bug。
|
||||
打开开关 `-shuffle` 能够打乱测试用例执行的顺序,避免一个测试依赖于某些不符合真实情况的预设,有助于及早暴露 bug。
|
||||
|
||||
### 不使用表驱动的测试 (#85)
|
||||
|
||||
表驱动的测试是一种有效的方式,可以将一组相似的测试分组在一起,以避免代码重复和使未来的更新更容易处理。
|
||||
表驱动的测试是一种有效的方式,可以将一组相似的测试分组在一起,以避免代码重复和使未来的更新更容易处理。
|
||||
|
||||
### 在单元测试中执行 sleep 操作 (#86)
|
||||
|
||||
|
|
@ -457,7 +457,7 @@ Go 的上下文(context)也是 Go 并发编程的基石之一。上下文允
|
|||
|
||||
增加 `benchtime` 或者使用 `benchstat` 等工具可以有助于微基准测试。
|
||||
|
||||
小心微基准测试的结果,如果最终运行应用程序的系统与运行微基准测试的系统不同。
|
||||
小心微基准测试的结果,如果最终运行应用程序的系统与运行微基准测试的系统不同。
|
||||
|
||||
* 对编译期优化要足够小心
|
||||
|
||||
|
|
@ -465,7 +465,7 @@ Go 的上下文(context)也是 Go 并发编程的基石之一。上下文允
|
|||
|
||||
* 被观察者效应所欺骗
|
||||
|
||||
为了避免被观察者效应欺骗,强制重新创建CPU密集型函数使用的数据。
|
||||
为了避免被观察者效应欺骗,强制重新创建CPU密集型函数使用的数据。
|
||||
|
||||
### 没有去探索 go test 所有的特性 (#90)
|
||||
|
||||
|
|
@ -473,13 +473,13 @@ Go 的上下文(context)也是 Go 并发编程的基石之一。上下文允
|
|||
|
||||
使用 `-coverprofile` 参数可以快速查看代码的测试覆盖情况,方便快速查看哪个部分需要更多的关注。
|
||||
|
||||
* 在一个不同包中执行测试
|
||||
* 在不同的包中执行测试
|
||||
|
||||
单元测试组织到一个独立的包中,对于对外层暴漏的接口,需要写一些测试用例。测试应该关注公开的行为,而非内部实现细节。
|
||||
单元测试组织到一个独立的包中,对于对外层暴露的接口,需要写一些测试用例。测试应该关注公开的行为,而非内部实现细节。
|
||||
|
||||
* Utility 函数
|
||||
|
||||
处理错误时,使用 `*testing.T` 变量而不是经典的 `if err != nil` 可以让代码更加简洁易读。
|
||||
处理错误时,使用 `*testing.T` 变量而不是经典的 `if err != nil` 可以让代码更加简洁易读。
|
||||
|
||||
* 设置和销毁
|
||||
|
||||
|
|
@ -525,15 +525,15 @@ Go 的上下文(context)也是 Go 并发编程的基石之一。上下文允
|
|||
|
||||
### 不了解数据对齐 (#94)
|
||||
|
||||
记住Go中基本类型与其自身大小对齐,例如,按降序从大到小重新组织结构体的字段可以形成更紧凑的结构体(减少内存分配,更好的空间局部性),这有助于避免一些常见的错误。
|
||||
记住 Go 中基本类型与其自身大小对齐,例如,按大小降序重新组织结构体的字段可以形成更紧凑的结构体(减少内存分配,更好的空间局部性),这有助于避免一些常见的错误。
|
||||
|
||||
### 不了解 stack vs. heap (#95)
|
||||
|
||||
了解堆和栈之间的区别是开发人员的核心知识点,特别是要去优化一个 Go 程序时。栈分配的开销几乎为零,而堆分配则较慢,并且依赖 GC 来清理内存。
|
||||
|
||||
### 不知道如何减少内存分配次数 (API调整, compiler optimizations, and `sync.Pool`) (#96)
|
||||
### 不知道如何减少内存分配次数(API 调整,编译器优化和 `sync.Pool`) (#96)
|
||||
|
||||
减少内存分配次数也是优化Go应用的一个重要方面。这可以通过不同的方式来实现,比如仔细设计API来避免不必要的拷贝,以及使用 `sync.Pool` 来对待分配对象进行池化。
|
||||
减少内存分配次数也是优化 Go 应用的一个重要方面。这可以通过不同的方式来实现,比如仔细设计 API 来避免不必要的拷贝,以及使用 `sync.Pool` 来对分配对象进行池化。
|
||||
|
||||
### 不注意使用内联 (#97)
|
||||
|
||||
|
|
@ -549,4 +549,4 @@ Go 的上下文(context)也是 Go 并发编程的基石之一。上下文允
|
|||
|
||||
### 不了解 Docker 或者 K8S 对运行的 Go 应用的性能影响 (#100)
|
||||
|
||||
为了避免CPU throttling(CPU限频)问题,当我们在Docker和Kubernetes部署应用时,要知道Go语言对CFS(完全公平调度器)无感知。
|
||||
为了避免 CPU throttling(CPU 限频)问题,当我们在 Docker 和 Kubernetes 部署应用时,要知道 Go 语言对 CFS(完全公平调度器)无感知。
|
||||
|
|
|
|||
Loading…
Reference in a new issue