Merge remote-tracking branch 'origin/master'

This commit is contained in:
Teiva Harsanyi 2024-10-06 17:10:31 +02:00
commit 8d85d6cdef
9 changed files with 140 additions and 140 deletions

View file

@ -47,7 +47,7 @@ Custom implementations of `io.Writer` should write the data coming from a slice
What is the rationale for having these two interfaces in the language? What is the point of creating these abstractions? What is the rationale for having these two interfaces in the language? What is the point of creating these abstractions?
Lets assume we need to implement a function that should copy the content of one file to another. We could create a specific function that would take as input two `*os.Files`. Or, we can choose to create a more generic function using `io.Reader` and `io.Writer` abstractions: Lets assume we need to implement a function that should copy the content of one file to another. We could create a specific function that would take as input two `*os.File`. Or, we can choose to create a more generic function using `io.Reader` and `io.Writer` abstractions:
```go ```go
func copySourceToDest(source io.Reader, dest io.Writer) error { func copySourceToDest(source io.Reader, dest io.Writer) error {

View file

@ -2215,7 +2215,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" ```bash hl_lines="3 7 11"
================== ==================

View file

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

2
go.mod
View file

@ -2,4 +2,4 @@ module github.com/teivah/100-go-mistakes
go 1.18 go 1.18
require golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect require golang.org/x/sync v0.0.0-20210220032951-036812b2e83c

View file

@ -3,7 +3,7 @@ package main
import ( import (
"fmt" "fmt"
"github.com/teivah/100-go-mistakes/02-code-project-organization/3-init-functions/redis" "github.com/teivah/100-go-mistakes/src/02-code-project-organization/3-init-functions/redis"
) )
func init() { func init() {

View file

@ -1,6 +1,6 @@
package client package client
import "github.com/teivah/100-go-mistakes/02-code-project-organization/6-interface-producer/store" import "github.com/teivah/100-go-mistakes/src/02-code-project-organization/6-interface-producer/store"
type customersGetter interface { type customersGetter interface {
GetAllCustomers() ([]store.Customer, error) GetAllCustomers() ([]store.Customer, error)

View file

@ -5,7 +5,7 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/teivah/100-go-mistakes/08-concurrency-foundations/60-contexts/flight" "github.com/teivah/100-go-mistakes/src/08-concurrency-foundations/60-contexts/flight"
) )
type publisher interface { type publisher interface {

View file

@ -19,7 +19,7 @@ func main() {
} }
type Counter struct { type Counter struct {
mu sync.Mutex mu sync.Mutex // bad
counters map[string]int counters map[string]int
} }
@ -38,7 +38,7 @@ func (c *Counter) Increment2(name string) {
} }
type Counter2 struct { type Counter2 struct {
mu *sync.Mutex mu *sync.Mutex // good
counters map[string]int counters map[string]int
} }

View file

@ -3,7 +3,7 @@ package counter_test
import ( import (
"testing" "testing"
counter "github.com/teivah/100-go-mistakes/11-testing/90-testing-features/different-package" counter "github.com/teivah/100-go-mistakes/src/11-testing/90-testing-features/different-package"
) )
func TestCount(t *testing.T) { func TestCount(t *testing.T) {