ch5-08 zh2tw

This commit is contained in:
D 2016-01-10 15:13:34 +08:00
parent 0374afd706
commit 41109c116f

View File

@ -1,8 +1,8 @@
## 5.8. Deferred函數
在findLinks的例子中们用http.Get的输出作为html.Parse的输入。只有url的内容的确是HTML格式的html.Parse才可以正常工作但实际上url指向的内容很丰富可能是图片纯文本或是其他。将这些格式的内容传递给html.parse会产生不良后果。
在findLinks的例子中們用http.Get的輸出作爲html.Parse的輸入。隻有url的內容的確是HTML格式的html.Parse才可以正常工作但實際上url指向的內容很豐富可能是圖片純文本或是其他。將這些格式的內容傳遞給html.parse會産生不良後果。
下面的例子获取HTML页面并输出页面的标题。title函数会检查服务器返回的Content-Type字段如果发现页面不是HTML将终止函数运行返回错误
下面的例子獲取HTML頁面併輸出頁面的標題。title函數會檢査服務器返迴的Content-Type字段如果發現頁面不是HTML將終止函數運行返迴錯誤
```Go
gopl.io/ch5/title1
@ -32,7 +32,7 @@ func title(url string) error {
}
```
下面展示了行效果:
下面展示了行效果:
```powershell
$ go build gopl.io/ch5/title1
@ -44,11 +44,11 @@ $ ./title1 https://golang.org/doc/gopher/frontpage.png
title: https://golang.org/doc/gopher/frontpage.png has type image/png, not text/html
```
resp.Body.close调用了多次这是为了确保title在所有执行路径下即使函数运行失败都关闭了网络连接。随着函数变得复杂需要处理的错误也变多维护清理逻辑变得越来越困难。而Go语言独有的defer机制可以让事情变得简单
resp.Body.close調用了多次這是爲了確保title在所有執行路徑下卽使函數運行失敗都關閉了網絡連接。隨着函數變得複雜需要處理的錯誤也變多維護清理邏輯變得越來越睏難。而Go語言獨有的defer機製可以讓事情變得簡單
只需要在调用普通函数或方法前加上关键字defer就完成了defer所需要的语法。当defer语句被执行时跟在defer后面的函数会被延迟执行。直到包含该defer语句的函数执行完毕时defer后的函数才会被执行不论包含defer语句的函数是通过return正常结束还是由于panic导致的异常结束。你可以在一个函数中执行多条defer语句它们的执行顺序与声明顺序相反。
隻需要在調用普通函數或方法前加上關鍵字defer就完成了defer所需要的語法。當defer語句被執行時跟在defer後面的函數會被延遲執行。直到包含該defer語句的函數執行完畢時defer後的函數才會被執行不論包含defer語句的函數是通過return正常結束還是由於panic導致的異常結束。你可以在一個函數中執行多條defer語句它們的執行順序與聲明順序相反。
defer语句经常被用于处理成对的操作如打开、关闭、连接、断开连接、加锁、释放锁。通过defer机制不论函数逻辑多复杂都能保证在任何执行路径下资源被释放。释放资源的defer应该直接跟在请求资源的语句后。在下面的代码中一条defer语句替代了之前的所有resp.Body.Close
defer語句經常被用於處理成對的操作如打開、關閉、連接、斷開連接、加鎖、釋放鎖。通過defer機製不論函數邏輯多複雜都能保證在任何執行路徑下資源被釋放。釋放資源的defer應該直接跟在請求資源的語句後。在下面的代碼中一條defer語句替代了之前的所有resp.Body.Close
```Go
gopl.io/ch5/title2
@ -71,7 +71,7 @@ func title(url string) error {
}
```
处理其他资源时也可以采用defer机制比如对文件的操作:
處理其他資源時也可以采用defer機製比如對文件的操作:
```Go
io/ioutil
@ -86,7 +86,7 @@ func ReadFile(filename string) ([]byte, error) {
}
```
或是处理互斥锁9.2章)
或是處理互斥鎖9.2章)
```Go
var mu sync.Mutex
@ -98,7 +98,7 @@ func lookup(key string) int {
}
```
调试复杂程序时defer机制也常被用于记录何时进入和退出函数。下例中的bigSlowOperation函数直接调用trace记录函数的被调情况。bigSlowOperation被调时trace会返回一个函数值该函数值会在bigSlowOperation退出时被调用。通过这种方式 我们可以只通过一条语句控制函数的入口和所有的出口甚至可以记录函数的运行时间如例子中的start。需要注意一点不要忘记defer语句后的圆括号否则本该在进入时执行的操作会在退出时执行而本该在退出时执行的永远不会被执行。
調試複雜程序時defer機製也常被用於記録何時進入和退出函數。下例中的bigSlowOperation函數直接調用trace記録函數的被調情況。bigSlowOperation被調時trace會返迴一個函數值該函數值會在bigSlowOperation退出時被調用。通過這種方式 我們可以隻通過一條語句控製函數的入口和所有的出口甚至可以記録函數的運行時間如例子中的start。需要註意一點不要忘記defer語句後的圓括號否則本該在進入時執行的操作會在退出時執行而本該在退出時執行的永遠不會被執行。
```Go
gopl.io/ch5/trace
@ -118,7 +118,7 @@ func trace(msg string) func() {
}
```
每一次bigSlowOperation被调用程序都会记录函数的进入退出持续时间。我们用time.Sleep模拟一个耗时的操作)
每一次bigSlowOperation被調用程序都會記録函數的進入退出持續時間。我們用time.Sleep模擬一個耗時的操作)
```powershell
$ go build gopl.io/ch5/trace
@ -126,9 +126,9 @@ $ ./trace
2015/11/18 09:53:26 enter bigSlowOperation
2015/11/18 09:53:36 exit bigSlowOperation (10.000589217s)
```
们知道defer语句中的函数会在return语句更新返回值变量后再执行又因为在函数中定义的匿名函数可以访问该函数包括返回值变量在内的所有变量所以对匿名函数采用defer机制可以使其观察函数的返回值。
們知道defer語句中的函數會在return語句更新返迴值變量後再執行又因爲在函數中定義的匿名函數可以訪問該函數包括返迴值變量在內的所有變量所以對匿名函數采用defer機製可以使其觀察函數的返迴值。
以double函数为例:
以double函數爲例:
```Go
func double(x int) int {
@ -136,7 +136,7 @@ func double(x int) int {
}
```
们只需要首先命名double的返回值再增加defer语句我们就可以在double每次被调用时输出参数以及返回值。
們隻需要首先命名double的返迴值再增加defer語句我們就可以在double每次被調用時輸出參數以及返迴值。
```Go
func double(x int) (result int) {
@ -148,9 +148,9 @@ _ = double(4)
// "double(4) = 8"
```
可能doulbe函数过于简单看不出这个小技巧的作用但对于有许多return语句的函数而言这个技巧很有用。
可能doulbe函數過於簡單看不出這個小技巧的作用但對於有許多return語句的函數而言這個技巧很有用。
被延迟执行的匿名函数甚至可以修改函数返回给调用者的返回值:
被延遲執行的匿名函數甚至可以脩改函數返迴給調用者的返迴值:
```Go
func triple(x int) (result int) {
@ -160,7 +160,7 @@ func triple(x int) (result int) {
fmt.Println(triple(4)) // "12"
```
在循环体中的defer语句需要特别注意因为只有在函数执行完毕后这些被延迟的函数才会执行。下面的代码会导致系统的文件描述符耗尽因为在所有文件都被处理之前没有文件会被关闭
在循環體中的defer語句需要特别註意因爲隻有在函數執行完畢後這些被延遲的函數才會執行。下面的代碼會導致繫統的文件描述符耗盡因爲在所有文件都被處理之前沒有文件會被關閉
```Go
for _, filename := range filenames {
@ -174,7 +174,7 @@ for _, filename := range filenames {
}
```
种解决方法是将循环体中的defer语句移至另外一个函数。在每次循环时调用这个函数
種解決方法是將循環體中的defer語句移至另外一個函數。在每次循環時調用這個函數
```Go
for _, filename := range filenames {
@ -192,7 +192,7 @@ func doFile(filename string) error {
}
```
下面的代码是fetch1.5节的改进版我们将http响应信息写入本地文件而不是从标准输出流输出。我们通过path.Base提出url路径的最后一段作为文件名。
下面的代碼是fetch1.5節的改進版我們將http響應信息寫入本地文件而不是從標準輸出流輸出。我們通過path.Base提出url路徑的最後一段作爲文件名。
```Go
gopl.io/ch5/fetch
@ -221,6 +221,6 @@ func fetch(url string) (filename string, n int64, err error) {
}
```
对resp.Body.Close延迟调用我们已经见过了在此不做解释。上例中通过os.Create打开文件进行写入在关闭文件时我们没有对f.close采用defer机制因为这会产生一些微妙的错误。许多文件系统尤其是NFS写入文件时发生的错误会被延迟到文件关闭时反馈。如果没有检查文件关闭时的反馈信息可能会导致数据丢失而我们还误以为写入操作成功。如果io.Copy和f.close都失败了我们倾向于将io.Copy的错误信息反馈给调用者因为它先于f,close发生更有可能接近问题的本质
對resp.Body.Close延遲調用我們已經見過了在此不做解釋。上例中通過os.Create打開文件進行寫入在關閉文件時我們沒有對f.close采用defer機製因爲這會産生一些微妙的錯誤。許多文件繫統尤其是NFS寫入文件時發生的錯誤會被延遲到文件關閉時反饋。如果沒有檢査文件關閉時的反饋信息可能會導致數據丟失而我們還誤以爲寫入操作成功。如果io.Copy和f.close都失敗了我們傾向於將io.Copy的錯誤信息反饋給調用者因爲它先於f,close發生更有可能接近問題的本質
**练习5.18**不修改fetch的行为重写fetch函数要求使用defer机制关闭文件。
**練習5.18**不脩改fetch的行爲重寫fetch函數要求使用defer機製關閉文件。