leo преди 10 години
родител
ревизия
25f6863611
променени са 3 файла, в които са добавени 151 реда и са изтрити 0 реда
  1. 79 0
      eBook/16.10.md
  2. 70 0
      eBook/16.9.md
  3. 2 0
      eBook/directory.md

+ 79 - 0
eBook/16.10.md

@@ -0,0 +1,79 @@
+# 16.10 糟糕的错误处理
+
+依附于[第13章](13.0.md)模式的描述和[第17.1小节](17.1.md)与[第17.2.4小节](17.2.md)的总结。
+
+- 16.10.1 不要使用布尔值:
+
+像下面代码一样,创建一个布尔型变量用于测试错误条件是多余的:
+
+```go
+var good bool
+    // 测试一个错误,`good`被赋为`true`或者`false`
+    if !good {
+        return errors.New("things aren’t good")
+    }
+```
+
+立即检测一个错误:
+
+```go
+... err1 := api.Func1()
+if err1 != nil { … }
+```
+
+- 16.10.2 避免错误检测使代码变得混乱:
+
+避免写出这样的代码:
+
+```go
+... err1 := api.Func1()
+if err1 != nil {
+    fmt.Println(“err: “ + err.Error())
+    return
+}
+err2 := api.Func2()
+if err2 != nil {
+...
+    return
+}    
+```
+
+首先,包括在一个初始化的`if`语句中对函数的调用。
+
+但即使代码中到处都是以`if`语句的形式通知错误(通过打印错误信息)。通过这种方式,很难分别什么是正常的程序逻辑,什么是错误检测或错误通知。还需注意的是,大部分代码都是致力于错误的检测。通常解决此问题的好办法是尽可能以闭包的形式封装你的错误检测,例如下面的代码:
+
+```go
+
+func httpRequestHandler(w http.ResponseWriter, req *http.Request) {
+    err := func () error {
+        if req.Method != "GET" {
+            return errors.New("expected GET")
+        }
+        if input := parseInput(req); input != "command" {
+            return errors.New("malformed command")
+        }
+    // 可以在此进行其他的错误检测
+        } ()
+
+        if err != nil {
+            w.WriteHeader(400)
+            io.WriteString(w, err)
+            return
+        }
+        doSomething() ...
+```
+
+这种方法清晰的区别了错误检测、错误通知和正常的程序逻辑(更详细的方式参考[第13.5小节](13.5.md))。
+
+**在开始阅读[第17章](17.0.md)前,先回答下列2个问题:**
+
+    - 问题 16.1:总结你能记住的所有关于`,ok`模式的情况。
+
+    - 问题 16.2:总结你能记住的所有关于`defer`模式的情况。
+
+
+## 链接
+
+- [目录](directory.md)
+- 上一节:[闭包和协程的使用](16.9.md)
+- 下一章:[模式](17.0.md)

+ 70 - 0
eBook/16.9.md

@@ -0,0 +1,70 @@
+# 16.9 闭包和协程的使用
+
+请看下面代码:
+
+```go
+package main
+
+import (
+    "fmt"
+    "time"
+)
+
+var values = [5]int{10, 11, 12, 13, 14}
+
+func main() {
+    // 版本A:
+    for ix := range values { // ix是索引值
+        func() {
+            fmt.Print(ix, " ")
+        }() // 调用闭包打印每个索引值
+    }
+    fmt.Println()
+    // 版本B: 和A版本类似,但是通过调用闭包作为一个协程
+    for ix := range values {
+        go func() {
+            fmt.Print(ix, " ")
+        }()
+    }
+    fmt.Println()
+    time.Sleep(5e9)
+    // 版本C: 正确的处理方式
+    for ix := range values {
+        go func(ix interface{}) {
+            fmt.Print(ix, " ")
+        }(ix)
+    }
+    fmt.Println()
+    time.Sleep(5e9)
+    // 版本D: 输出值:
+    for ix := range values {
+        val := values[ix]
+        go func() {
+            fmt.Print(val, " ")
+        }()
+    }
+    time.Sleep(1e9)
+}
+
+```
+
+/* 输出     0 1 2 3 4
+            4 4 4 4 4
+            1 0 3 4 2
+            10 11 12 13 14
+*/
+
+版本A调用闭包5次打印每个索引值,版本B也做相同的事,但是通过协程调用每个闭包。按理说这将执行得更快,因为闭包是并发执行的。如果我们阻塞足够多的时间,让所有协程执行完毕,版本B的输出是:`4 4 4 4 4`。为什么会这样?在版本B的循环中,`ix`变量
+实际是一个单变量,表示每个数组元素的索引值。因为这些闭包都只绑定到一个变量,这是一个比较好的方式,当你运行这段代码时,你将看见每次循环都打印最后一个索引值`4`,而不是每个元素的索引值。因为协程可能在循环结束后还没有开始执行,而此时`ix`值是`4`。
+
+版本C的循环写法才是正确的:调用每个闭包是将`ix`作为参数传递给闭包。`ix`在每次循环时都被重新赋值,并将每个协程的`ix`放置在栈中,所以当协程最终被执行时,每个索引值对协程都是可用的。注意这里的输出可能是`0 2 1 3 4`或者`0 3 1 2 4`或者其他类似的序列,这主要取决于每个协程何时开始被执行。
+
+在版本D中,我们输出这个数组的值,为什么版本B不能而版本D可以呢?
+
+因为版本D中的变量声明是在循环体内部,所以在每次循环时,这些变量相互之间是不共享的,所以这些变量可以单独的被每个闭包使用。
+
+## 链接
+
+- [目录](directory.md)
+- 上一节:[误用协程和通道](16.8.md)
+- 下一节:[糟糕的错误处理](16.10.md)

+ 2 - 0
eBook/directory.md

@@ -159,6 +159,8 @@
     - 16.6 [使用指针指向接口类型](16.6.md)
     - 16.7 [使用值类型时误用指针](16.7.md)
     - 16.8 [误用协程和通道](16.8.md)
+    - 16.9 [闭包和协程的使用](16.9.md)
+    - 16.10 [糟糕的错误处理](16.10.md)
 - 第17章:模式
 - 第18章:[出于性能考虑的实用代码片段](18.0.md)
     - 18.1 [字符串](18.1.md)