mirror of
https://github.com/unknwon/the-way-to-go_ZH_CN.git
synced 2025-08-11 22:53:43 +08:00
@@ -1,27 +1,27 @@
|
|||||||
# 16.0 常见的陷阱与错误
|
# 16.0 常见的陷阱与错误
|
||||||
|
|
||||||
在之前的内容中,有时候使用 `!!...!!` 标记警告 go 语言中的一些错误使用方式。当你在编程时候遇到的一个困难,可以确定本书特定的章节能找到类似的主题。为了方便起见,这里列出了一些常见陷阱,以便于你能发现更多的解释和例子:
|
在之前的内容中,有时候使用 `!!...!!` 标记警告 Go 语言中的一些错误使用方式。当你在编程时候遇到的一个困难,可以确定本书特定的章节能找到类似的主题。为了方便起见,这里列出了一些常见陷阱,以便于你能发现更多的解释和例子:
|
||||||
|
|
||||||
- 永远不要使用形如 `var p*a` 声明变量,这会混淆指针声明和乘法运算(参考[4.9小节](04.9.md))
|
- 永远不要使用形如 `var p*a` 声明变量,这会混淆指针声明和乘法运算(参考 [4.9 小节](04.9.md))
|
||||||
- 永远不要在 `for` 循环自身中改变计数器变量(参考[5.4小节](05.4.md))
|
- 永远不要在 `for` 循环自身中改变计数器变量(参考 [5.4 小节](05.4.md))
|
||||||
- 永远不要在 `for-range` 循环中使用一个值去改变自身的值(参考[5.4.4小节](05.4.md))
|
- 永远不要在 `for-range` 循环中使用一个值去改变自身的值(参考 [5.4.4 小节](05.4.md))
|
||||||
- 永远不要将 `goto` 和前置标签一起使用(参考[5.6小节](05.6.md))
|
- 永远不要将 `goto` 和前置标签一起使用(参考 [5.6 小节](05.6.md))
|
||||||
- 永远不要忘记在函数名(参考[第6章](06.0.md))后加括号(),尤其调用一个对象的方法或者使用匿名函数启动一个协程时
|
- 永远不要忘记在函数名(参考[第 6 章](06.0.md))后加括号 `()`,尤其是调用一个对象的方法或者使用匿名函数启动一个协程时
|
||||||
- 永远不要使用 `new()` 一个 map,一直使用 make(参考[第8章](08.0.md))
|
- 永远不要使用 `new()` 一个 `map`,一直使用 `make()`(参考[第 8 章](08.0.md))
|
||||||
- 当为一个类型定义一个 String() 方法时,不要使用 `fmt.Print` 或者类似的代码(参考[10.7小节](10.7.md))
|
- 当为一个类型定义一个 `String()` 方法时,不要使用 `fmt.Print` 或者类似的代码(参考 [10.7 小节](10.7.md))
|
||||||
- 永远不要忘记当终止缓存写入时,使用 `Flush` 函数(参考[12.2.3小节](12.2.md))
|
- 永远不要忘记当终止缓存写入时,使用 `Flush()` 函数(参考 [12.2.3 小节](12.2.md))
|
||||||
- 永远不要忽略错误提示,忽略错误会导致程序崩溃(参考[13.1小节](13.1.md))
|
- 永远不要忽略错误提示,忽略错误会导致程序崩溃(参考 [13.1 小节](13.1.md))
|
||||||
- 不要使用全局变量或者共享内存,这会使并发执行的代码变得不安全(参考[14.1小节](14.1.md))
|
- 不要使用全局变量或者共享内存,这会使并发执行的代码变得不安全(参考 [14.1 小节](14.1.md))
|
||||||
- `println` 函数仅仅是用于调试的目的
|
- `println()` 函数仅仅是用于调试的目的
|
||||||
|
|
||||||
最佳实践:对比以下使用方式:
|
最佳实践:对比以下使用方式:
|
||||||
|
|
||||||
- 使用正确的方式初始化一个元素是切片的映射,例如 `map[type]slice`(参考[8.1.3小节](08.1.md))
|
- 使用正确的方式初始化一个元素是切片的映射,例如 `map[type]slice`(参考 [8.1.3 小节](08.1.md))
|
||||||
- 一直使用逗号,ok 或者 checked 形式作为类型断言(参考[11.3小节](11.3.md))
|
- 一直使用逗号 ok 模式或者 checked 形式作为类型断言(参考 [11.3 小节](11.3.md))
|
||||||
- 使用一个工厂函数创建并初始化自己定义类型(参考[10.2小节](10.2.md)-[18.4小节](18.4.md))
|
- 使用一个工厂函数创建并初始化自己定义类型(参考 [10.2 小节](10.2.md)-[18.4 小节](18.4.md))
|
||||||
- 仅当一个结构体的方法想改变结构体时,使用结构体指针作为方法的接受者,否则使用一个结构体值类型[10.6.3小节](10.6.md)
|
- 仅当一个结构体的方法想改变结构体时,使用结构体指针作为方法的接受者,否则使用一个结构体值类型 [10.6.3 小节](10.6.md)
|
||||||
|
|
||||||
本章主要汇总了 go 语言使用过程中最常见的错误和注意事项。在之前的章节已经涉及到了完整的示例和解释,你应该做的不仅仅是阅读这段的标题。
|
本章主要汇总了 Go 语言使用过程中最常见的错误和注意事项。在之前的章节已经涉及到了完整的示例和解释,你应该做的不仅仅是阅读这段的标题。
|
||||||
|
|
||||||
## 链接
|
## 链接
|
||||||
|
|
||||||
|
@@ -16,8 +16,7 @@ if something {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
此类错误也容易在 `for` 循环中出现,尤其当函数返回一个具名变量时难于察觉
|
此类错误也容易在 `for` 循环中出现,尤其当函数返回一个具名变量时难于察觉,例如以下的代码段:
|
||||||
,例如以下的代码段:
|
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func shadow() (err error) {
|
func shadow() (err error) {
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
# 16.10 糟糕的错误处理
|
# 16.10 糟糕的错误处理
|
||||||
|
|
||||||
译者注:该小结关于错误处理的观点,译者并不完全赞同,关于本小结的部分想法请参考[关于 16.10.2 小节错误处理的一些见解](Discussion_about_16.10.md)
|
译者注:该小结关于错误处理的观点,译者并不完全赞同,关于本小结的部分想法请参考 [关于 16.10.2 小节错误处理的一些见解](Discussion_about_16.10.md)。
|
||||||
|
|
||||||
|
|
||||||
依附于[第13章](13.0.md)模式的描述和[第17.1小节](17.1.md)与[第17.2.4小节](17.2.md)的总结。
|
依附于[第 13 章](13.0.md)模式的描述和[第 17.1 小节](17.1.md)与[第 17.2.4 小节](17.2.md)的总结。
|
||||||
|
|
||||||
## 16.10.1 不要使用布尔值:
|
## 16.10.1 不要使用布尔值:
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
```go
|
```go
|
||||||
var good bool
|
var good bool
|
||||||
// 测试一个错误,`good`被赋为`true`或者`false`
|
// 测试一个错误,`good` 被赋为 `true` 或者 `false`
|
||||||
if !good {
|
if !good {
|
||||||
return errors.New("things aren’t good")
|
return errors.New("things aren’t good")
|
||||||
}
|
}
|
||||||
@@ -63,11 +63,11 @@ func httpRequestHandler(w http.ResponseWriter, req *http.Request) {
|
|||||||
doSomething() ...
|
doSomething() ...
|
||||||
```
|
```
|
||||||
|
|
||||||
这种方法可以很容易分辨出错误检测、错误通知和正常的程序逻辑(更详细的方式参考[第13.5小节](13.5.md))。
|
这种方法可以很容易分辨出错误检测、错误通知和正常的程序逻辑(更详细的方式参考[第 13.5 小节](13.5.md))。
|
||||||
|
|
||||||
**在开始阅读[第17章](17.0.md)前,先回答下列 2 个问题:**
|
**在开始阅读[第 17 章](17.0.md)前,先回答下列 2 个问题:**
|
||||||
|
|
||||||
- 问题 16.1:总结你能记住的所有关于 `,ok` 模式的情况。
|
- 问题 16.1:总结你能记住的所有关于 `, ok` 模式的情况。
|
||||||
|
|
||||||
- 问题 16.2:总结你能记住的所有关于 `defer` 模式的情况。
|
- 问题 16.2:总结你能记住的所有关于 `defer` 模式的情况。
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
# 16.2 误用字符串
|
# 16.2 误用字符串
|
||||||
|
|
||||||
当需要对一个字符串进行频繁的操作时,谨记在 go 语言中字符串是不可变的(类似 java 和 c#)。使用诸如 `a += b` 形式连接字符串效率低下,尤其在一个循环内部使用这种形式。这会导致大量的内存开销和拷贝。**应该使用一个字符数组代替字符串,将字符串内容写入一个缓存中。** 例如以下的代码示例:
|
当需要对一个字符串进行频繁的操作时,谨记在 go 语言中字符串是不可变的(类似 Java 和 C#)。使用诸如 `a += b` 形式连接字符串效率低下,尤其在一个循环内部使用这种形式。这会导致大量的内存开销和拷贝。**应该使用一个字符数组代替字符串,将字符串内容写入一个缓存中。** 例如以下的代码示例:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
# 16.3 发生错误时使用 defer 关闭一个文件
|
# 16.3 发生错误时使用 defer 关闭一个文件
|
||||||
|
|
||||||
如果你在一个 for 循环内部处理一系列文件,你需要使用 defer 确保文件在处理完毕后被关闭,例如:
|
如果你在一个 `for` 循环内部处理一系列文件,你需要使用 `defer` 确保文件在处理完毕后被关闭,例如:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
@@ -14,7 +14,7 @@ for _, file := range files {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
但是在循环结尾处的 defer 没有执行,所以文件一直没有关闭!垃圾回收机制可能会自动关闭文件,但是这会产生一个错误,更好的做法是:
|
但是在循环内结尾处的 `defer` 没有执行,所以文件一直没有关闭!垃圾回收机制可能会自动关闭文件,但是这会产生一个错误,更好的做法是:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
@@ -28,7 +28,7 @@ for _, file := range files {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**defer 仅在函数返回时才会执行,在循环的结尾或其他一些有限范围的代码内不会执行。**
|
**`defer` 仅在函数返回时才会执行,在循环内的结尾或其他一些有限范围的代码内不会执行。**
|
||||||
|
|
||||||
## 链接
|
## 链接
|
||||||
|
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
# 16.4 何时使用 new() 和 make()
|
# 16.4 何时使用 new() 和 make()
|
||||||
|
|
||||||
在第[7.2.1小节](07.2.md)和第[10.2.2小节](10.2.md),我们已经讨论过此问题,并使用代码进行详细说明,观点如下:
|
在第 [7.2.1 小节](07.2.md)和第 [10.2.2 小节](10.2.md),我们已经讨论过此问题,并使用代码进行详细说明,观点如下:
|
||||||
|
|
||||||
- 切片、映射和通道,使用 make
|
- 切片、映射和通道,使用 make()
|
||||||
- 数组、结构体和所有的值类型,使用 new
|
- 数组、结构体和所有的值类型,使用 new()
|
||||||
|
|
||||||
## 链接
|
## 链接
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
# 16.5 不需要将一个指向切片的指针传递给函数
|
# 16.5 不需要将一个指向切片的指针传递给函数
|
||||||
|
|
||||||
在第[4.9小节](04.9.md),我们已经知道,切片实际是一个指向潜在数组的指针。我们常常需要把切片作为一个参数传递给函数是因为:实际就是传递一个指向变量的指针,在函数内可以改变这个变量,而不是传递数据的拷贝。
|
在第 [4.9 小节](04.9.md),我们已经知道,切片实际是一个指向潜在数组的指针。我们常常需要把切片作为一个参数传递给函数是因为:实际就是传递一个指向变量的指针,在函数内可以改变这个变量,而不是传递数据的拷贝。
|
||||||
|
|
||||||
因此应该这样做:
|
因此应该这样做:
|
||||||
|
|
||||||
|
@@ -1,9 +1,8 @@
|
|||||||
# 16.6 使用指针指向接口类型
|
# 16.6 使用指针指向接口类型
|
||||||
|
|
||||||
查看如下程序:`nexter` 是一个接口类型,并且定义了一个 `next()` 方法读取下一字节。函数 `nextFew1` 将 `nexter` 接口作为参数并读取接下来的 `num` 个字节,并返回一个切片:这是正确做法。但是 `nextFew2` 使用一个指向 `nexter` 接口类型的指针作为参数传递给函数:当使用 `next()` 函数时,系统会给出一个编译错误:**n.next undefined (type *nexter has no
|
查看如下程序:`nexter` 是一个接口类型,并且定义了一个 `next()` 方法读取下一字节。函数 `nextFew1` 将 `nexter` 接口作为参数并读取接下来的 `num` 个字节,并返回一个切片:这是正确做法。但是 `nextFew2` 使用一个指向 `nexter` 接口类型的指针作为参数传递给函数:当使用 `next()` 函数时,系统会给出一个编译错误:**`n.next undefined (type *nexter has no field or method next)`** (译者注:n.next 未定义(*nexter 类型没有 next 成员或 next 方法))
|
||||||
field or method next)** (译者注:n.next 未定义(*nexter 类型没有 next 成员或 next 方法))
|
|
||||||
|
|
||||||
例 16.1 [pointer_interface.go](examples/chapter_16/pointer_interface.go) (不能通过编译):
|
例 16.1 [pointer_interface.go](examples/chapter_16/pointer_interface.go) (不能通过编译):
|
||||||
|
|
||||||
```go
|
```go
|
||||||
package main
|
package main
|
||||||
|
@@ -1,9 +1,10 @@
|
|||||||
# 16.7 使用值类型时误用指针
|
# 16.7 使用值类型时误用指针
|
||||||
|
|
||||||
将一个值类型作为一个参数传递给函数或者作为一个方法的接收者,似乎是对内存的滥用,因为值类型一直是传递拷贝。但是另一方面,值类型的内存是在栈上分配,内存分配快速且开销不大。如果你传递一个指针,而不是一个值类型,go 编译器大多数情况下会认为需要创建一个对象,并将对象移动到堆上,所以会导致额外的内存分配:因此当使用指针代替值类型作为参数传递时,我们没有任何收获。
|
将一个值类型作为一个参数传递给函数或者作为一个方法的接收者,似乎是对内存的滥用,因为值类型一直是传递拷贝。但是另一方面,值类型的内存是在栈上分配,内存分配快速且开销不大。如果你传递一个指针,而不是一个值类型,Go 编译器大多数情况下会认为需要创建一个对象,并将对象移动到堆上,所以会导致额外的内存分配:因此当使用指针代替值类型作为参数传递时,我们没有任何收获。
|
||||||
|
|
||||||
## 链接
|
## 链接
|
||||||
|
|
||||||
- [目录](directory.md)
|
- [目录](directory.md)
|
||||||
- 上一节:[使用指针指向接口类型](16.6.md)
|
- 上一节:[使用指针指向接口类型](16.6.md)
|
||||||
- 下一节:[误用协程和通道](16.8.md)
|
- 下一节:[误用协程和通道](16.8.md)
|
||||||
|
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
# 16.8 误用协程和通道
|
# 16.8 误用协程和通道
|
||||||
|
|
||||||
由于教学需要和对协程的工作原理有一个直观的了解,在[第14章](14.0.md)使用了一些简单的算法,举例说明了协程和通道的使用,例如生产者或者迭代器。在实际应用中,你不需要并发执行,或者你不需要关注协程和通道的开销,在大多数情况下,通过栈传递参数会更有效率。
|
由于教学需要和对协程的工作原理有一个直观的了解,在[第 14 章](14.0.md) 使用了一些简单的算法,举例说明了协程和通道的使用,例如生产者或者迭代器。在实际应用中,你不需要并发执行,或者你不需要关注协程和通道的开销,在大多数情况下,通过栈传递参数会更有效率。
|
||||||
|
|
||||||
但是,如果你使用 `break`、`return` 或者 `panic` 去跳出一个循环,很有可能会导致内存溢出,因为协程正处理某些事情而被阻塞。在实际代码中,通常仅需写一个简单的过程式循环即可。**当且仅当代码中并发执行非常重要,才使用协程和通道。**
|
但是,如果你使用 `break`、`return` 或者 `panic()` 去跳出一个循环,很有可能会导致内存溢出,因为协程正处理某些事情而被阻塞。在实际代码中,通常仅需写一个简单的过程式循环即可。**当且仅当代码中并发执行非常重要,才使用协程和通道。**
|
||||||
|
|
||||||
## 链接
|
## 链接
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user