mirror of
https://github.com/gopl-zh/gopl-zh.github.com.git
synced 2024-11-05 05:53:45 +00:00
ch5-4: fmt code
This commit is contained in:
parent
cce44f4a91
commit
178cf14581
@ -3,15 +3,17 @@
|
|||||||
當一次函數調用返迴錯誤時,調用者有應該選擇何時的方式處理錯誤。根據情況的不同,有很多處理方式,讓我們來看看常用的五種方式。
|
當一次函數調用返迴錯誤時,調用者有應該選擇何時的方式處理錯誤。根據情況的不同,有很多處理方式,讓我們來看看常用的五種方式。
|
||||||
|
|
||||||
首先,也是最常用的方式是傳播錯誤。這意味着函數中某個子程序的失敗,會變成該函數的失敗。下面,我們以5.3節的findLinks函數作爲例子。如果findLinks對http.Get的調用失敗,findLinks會直接將這個HTTP錯誤返迴給調用者:
|
首先,也是最常用的方式是傳播錯誤。這意味着函數中某個子程序的失敗,會變成該函數的失敗。下面,我們以5.3節的findLinks函數作爲例子。如果findLinks對http.Get的調用失敗,findLinks會直接將這個HTTP錯誤返迴給調用者:
|
||||||
```
|
|
||||||
|
```Go
|
||||||
resp, err := http.Get(url)
|
resp, err := http.Get(url)
|
||||||
if err != nil{
|
if err != nil{
|
||||||
return nill, err
|
return nill, err
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
當對html.Parse的調用失敗時,findLinks不會直接返迴html.Parse的錯誤,因爲缺少兩條重要信息:1、錯誤發生在解析器;2、url已經被解析。這些信息有助於錯誤的處理,findLinks會構造新的錯誤信息返迴給調用者:
|
當對html.Parse的調用失敗時,findLinks不會直接返迴html.Parse的錯誤,因爲缺少兩條重要信息:1、錯誤發生在解析器;2、url已經被解析。這些信息有助於錯誤的處理,findLinks會構造新的錯誤信息返迴給調用者:
|
||||||
|
|
||||||
```
|
```Go
|
||||||
doc, err := html.Parse(resp.Body)
|
doc, err := html.Parse(resp.Body)
|
||||||
resp.Body.Close()
|
resp.Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -20,9 +22,11 @@ return nil, fmt.Errorf("parsing %s as HTML: %v", url,err)
|
|||||||
```
|
```
|
||||||
|
|
||||||
fmt.Errorf函數使用fmt.Sprintf格式化錯誤信息併返迴。我們使用該函數前綴添加額外的上下文信息到原始錯誤信息。當錯誤最終由main函數處理時,錯誤信息應提供清晰的從原因到後果的因果鏈,就像美国宇航局事故調査時做的那樣:
|
fmt.Errorf函數使用fmt.Sprintf格式化錯誤信息併返迴。我們使用該函數前綴添加額外的上下文信息到原始錯誤信息。當錯誤最終由main函數處理時,錯誤信息應提供清晰的從原因到後果的因果鏈,就像美国宇航局事故調査時做的那樣:
|
||||||
|
|
||||||
```
|
```
|
||||||
genesis: crashed: no parachute: G-switch failed: bad relay orientation
|
genesis: crashed: no parachute: G-switch failed: bad relay orientation
|
||||||
```
|
```
|
||||||
|
|
||||||
由於錯誤信息經常是以鏈式組合在一起的,所以錯誤信息中應避免大寫和換行符。最終的錯誤信息可能很長,我們可以通過類似grep的工具處理錯誤信息(譯者註:grep是一種文本蒐索工具)。
|
由於錯誤信息經常是以鏈式組合在一起的,所以錯誤信息中應避免大寫和換行符。最終的錯誤信息可能很長,我們可以通過類似grep的工具處理錯誤信息(譯者註:grep是一種文本蒐索工具)。
|
||||||
|
|
||||||
編寫錯誤信息時,我們要確保錯誤信息對問題細節的描述是詳盡的。尤其是要註意錯誤信息表達的一致性,卽相同的函數或同包內的同一組函數返迴的錯誤在構成和處理方式上是相似的。
|
編寫錯誤信息時,我們要確保錯誤信息對問題細節的描述是詳盡的。尤其是要註意錯誤信息表達的一致性,卽相同的函數或同包內的同一組函數返迴的錯誤在構成和處理方式上是相似的。
|
||||||
@ -33,7 +37,7 @@ genesis: crashed: no parachute: G-switch failed: bad relay orientation
|
|||||||
|
|
||||||
讓我們來看看處理錯誤的第二種策略。如果錯誤的發生是偶然性的,或由不可預知的問題導致的。一個明智的選擇是重新嚐試失敗的操作。在重試時,我們需要限製重試的時間間隔或重試的次數,防止無限製的重試。
|
讓我們來看看處理錯誤的第二種策略。如果錯誤的發生是偶然性的,或由不可預知的問題導致的。一個明智的選擇是重新嚐試失敗的操作。在重試時,我們需要限製重試的時間間隔或重試的次數,防止無限製的重試。
|
||||||
|
|
||||||
```
|
```Go
|
||||||
gopl.io/ch5/wait
|
gopl.io/ch5/wait
|
||||||
// WaitForServer attempts to contact the server of a URL.
|
// WaitForServer attempts to contact the server of a URL.
|
||||||
// It tries for one minute using exponential back-off.
|
// It tries for one minute using exponential back-off.
|
||||||
@ -47,8 +51,7 @@ func WaitForServer(url string) error {
|
|||||||
return nil // success
|
return nil // success
|
||||||
}
|
}
|
||||||
log.Printf("server not responding (%s);retrying…", err)
|
log.Printf("server not responding (%s);retrying…", err)
|
||||||
time.Sleep(time.Second << uint(tries)) //
|
time.Sleep(time.Second << uint(tries)) // exponential back-off
|
||||||
exponential back-off
|
|
||||||
}
|
}
|
||||||
return fmt.Errorf("server %s failed to respond after %s", url, timeout)
|
return fmt.Errorf("server %s failed to respond after %s", url, timeout)
|
||||||
}
|
}
|
||||||
@ -56,45 +59,57 @@ func WaitForServer(url string) error {
|
|||||||
|
|
||||||
如果錯誤發生後,程序無法繼續運行,我們就可以采用第三種策略:輸出錯誤信息併結束程序。需要註意的是,這種策略隻應在main中執行。對庫函數而言,應僅向上傳播錯誤,除非該錯誤意味着程序內部包含不一致性,卽遇到了bug,才能在庫函數中結束程序。
|
如果錯誤發生後,程序無法繼續運行,我們就可以采用第三種策略:輸出錯誤信息併結束程序。需要註意的是,這種策略隻應在main中執行。對庫函數而言,應僅向上傳播錯誤,除非該錯誤意味着程序內部包含不一致性,卽遇到了bug,才能在庫函數中結束程序。
|
||||||
|
|
||||||
```
|
```Go
|
||||||
// (In function main.)
|
// (In function main.)
|
||||||
if err := WaitForServer(url); err != nil {
|
if err := WaitForServer(url); err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Site is down: %v\n", err)
|
fmt.Fprintf(os.Stderr, "Site is down: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
調用log.Fatalf可以更簡潔的代碼達到與上文相同的效果。log中的所有函數,都默認會在錯誤信息之前輸出時間信息。
|
調用log.Fatalf可以更簡潔的代碼達到與上文相同的效果。log中的所有函數,都默認會在錯誤信息之前輸出時間信息。
|
||||||
```
|
|
||||||
|
```Go
|
||||||
if err := WaitForServer(url); err != nil {
|
if err := WaitForServer(url); err != nil {
|
||||||
log.Fatalf("Site is down: %v\n", err)
|
log.Fatalf("Site is down: %v\n", err)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
長時間運行的服務器常采用默認的時間格式,而交互式工具很少采用包含如此多信息的格式。
|
長時間運行的服務器常采用默認的時間格式,而交互式工具很少采用包含如此多信息的格式。
|
||||||
|
|
||||||
```
|
```
|
||||||
2006/01/02 15:04:05 Site is down: no such domain:
|
2006/01/02 15:04:05 Site is down: no such domain:
|
||||||
bad.gopl.io
|
bad.gopl.io
|
||||||
```
|
```
|
||||||
|
|
||||||
我們可以設置log的前綴信息屏蔽時間信息,一般而言,前綴信息會被設置成命令名。
|
我們可以設置log的前綴信息屏蔽時間信息,一般而言,前綴信息會被設置成命令名。
|
||||||
```
|
|
||||||
|
```Go
|
||||||
log.SetPrefix("wait: ")
|
log.SetPrefix("wait: ")
|
||||||
log.SetFlags(0)
|
log.SetFlags(0)
|
||||||
```
|
```
|
||||||
|
|
||||||
第四種策略:有時,我們隻需要輸出錯誤信息就足夠了,不需要中斷程序的運行。我們可以通過log包提供函數
|
第四種策略:有時,我們隻需要輸出錯誤信息就足夠了,不需要中斷程序的運行。我們可以通過log包提供函數
|
||||||
```
|
|
||||||
|
```Go
|
||||||
if err := Ping(); err != nil {
|
if err := Ping(); err != nil {
|
||||||
log.Printf("ping failed: %v; networking disabled",err)
|
log.Printf("ping failed: %v; networking disabled",err)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
或者標準錯誤流輸出錯誤信息。
|
或者標準錯誤流輸出錯誤信息。
|
||||||
```
|
|
||||||
|
```Go
|
||||||
if err := Ping(); err != nil {
|
if err := Ping(); err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "ping failed: %v; networking disabled\n", err)
|
fmt.Fprintf(os.Stderr, "ping failed: %v; networking disabled\n", err)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
log包中的所有函數會爲沒有換行符的字符串增加換行符。
|
log包中的所有函數會爲沒有換行符的字符串增加換行符。
|
||||||
|
|
||||||
第五種,也是最後一種策略:我們可以直接忽略掉錯誤。
|
第五種,也是最後一種策略:我們可以直接忽略掉錯誤。
|
||||||
```
|
|
||||||
|
```Go
|
||||||
dir, err := ioutil.TempDir("", "scratch")
|
dir, err := ioutil.TempDir("", "scratch")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create temp dir: %v",err)
|
return fmt.Errorf("failed to create temp dir: %v",err)
|
||||||
@ -102,6 +117,7 @@ if err != nil {
|
|||||||
// ...use temp dir…
|
// ...use temp dir…
|
||||||
os.RemoveAll(dir) // ignore errors; $TMPDIR is cleaned periodically
|
os.RemoveAll(dir) // ignore errors; $TMPDIR is cleaned periodically
|
||||||
```
|
```
|
||||||
|
|
||||||
盡管os.RemoveAll會失敗,但上面的例子併沒有做錯誤處理。這是因爲操作繫統會定期的清理臨時目録。正因如此,雖然程序沒有處理錯誤,但程序的邏輯不會因此受到影響。我們應該在每次函數調用後,都養成考慮錯誤處理的習慣,當你決定忽略某個錯誤時,你應該在清晰的記録下你的意圖。
|
盡管os.RemoveAll會失敗,但上面的例子併沒有做錯誤處理。這是因爲操作繫統會定期的清理臨時目録。正因如此,雖然程序沒有處理錯誤,但程序的邏輯不會因此受到影響。我們應該在每次函數調用後,都養成考慮錯誤處理的習慣,當你決定忽略某個錯誤時,你應該在清晰的記録下你的意圖。
|
||||||
|
|
||||||
在Go中,錯誤處理有一套獨特的編碼風格。檢査某個子函數是否失敗後,我們通常將處理失敗的邏輯代碼放在處理成功的代碼之前。如果某個錯誤會導致函數返迴,那麽成功時的邏輯代碼不應放在else語句塊中,而應直接放在函數體中。Go中大部分函數的代碼結構幾乎相同,首先是一繫列的初始檢査,防止錯誤發生,之後是函數的實際邏輯。
|
在Go中,錯誤處理有一套獨特的編碼風格。檢査某個子函數是否失敗後,我們通常將處理失敗的邏輯代碼放在處理成功的代碼之前。如果某個錯誤會導致函數返迴,那麽成功時的邏輯代碼不應放在else語句塊中,而應直接放在函數體中。Go中大部分函數的代碼結構幾乎相同,首先是一繫列的初始檢査,防止錯誤發生,之後是函數的實際邏輯。
|
||||||
|
@ -1,14 +1,19 @@
|
|||||||
### 5.4.2. 文件結尾錯誤(EOF)
|
### 5.4.2. 文件結尾錯誤(EOF)
|
||||||
|
|
||||||
函數經常會返迴多種錯誤,這對終端用戶來説可能會很有趣,但對程序而言,這使得情況變得複雜。很多時候,程序必鬚根據錯誤類型,作出不同的響應。讓我們考慮這樣一個例子:從文件中讀取n個字節。如果n等於文件的長度,讀取過程的任何錯誤都表示失敗。如果n小於文件的長度,調用者會重複的讀取固定大小的數據直到文件結束。這會導致調用者必鬚分别處理由文件結束引起的各種錯誤。基於這樣的原因,io包保證任何由文件結束引起的讀取失敗都返迴同一個錯誤——io.EOF,該錯誤在io包中定義:
|
函數經常會返迴多種錯誤,這對終端用戶來説可能會很有趣,但對程序而言,這使得情況變得複雜。很多時候,程序必鬚根據錯誤類型,作出不同的響應。讓我們考慮這樣一個例子:從文件中讀取n個字節。如果n等於文件的長度,讀取過程的任何錯誤都表示失敗。如果n小於文件的長度,調用者會重複的讀取固定大小的數據直到文件結束。這會導致調用者必鬚分别處理由文件結束引起的各種錯誤。基於這樣的原因,io包保證任何由文件結束引起的讀取失敗都返迴同一個錯誤——io.EOF,該錯誤在io包中定義:
|
||||||
```
|
|
||||||
|
```Go
|
||||||
package io
|
package io
|
||||||
|
|
||||||
import "errors"
|
import "errors"
|
||||||
|
|
||||||
// EOF is the error returned by Read when no more input is available.
|
// EOF is the error returned by Read when no more input is available.
|
||||||
var EOF = errors.New("EOF")
|
var EOF = errors.New("EOF")
|
||||||
```
|
```
|
||||||
|
|
||||||
調用者隻需通過簡單的比較,就可以檢測出這個錯誤。下面的例子展示了如何從標準輸入中讀取字符,以及判斷文件結束。(4.3的chartcount程序展示了更加複雜的代碼)
|
調用者隻需通過簡單的比較,就可以檢測出這個錯誤。下面的例子展示了如何從標準輸入中讀取字符,以及判斷文件結束。(4.3的chartcount程序展示了更加複雜的代碼)
|
||||||
```
|
|
||||||
|
```Go
|
||||||
in := bufio.NewReader(os.Stdin)
|
in := bufio.NewReader(os.Stdin)
|
||||||
for {
|
for {
|
||||||
r, _, err := in.ReadRune()
|
r, _, err := in.ReadRune()
|
||||||
@ -21,4 +26,5 @@ for {
|
|||||||
// ...use r…
|
// ...use r…
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
因爲文件結束這種錯誤不需要更多的描述,所以io.EOF有固定的錯誤信息——“EOF”。對於其他錯誤,我們可能需要在錯誤信息中描述錯誤的類型和數量,這使得我們不能像io.EOF一樣采用固定的錯誤信息。在7.11節中,我們會提出更繫統的方法區分某些固定的錯誤值。
|
因爲文件結束這種錯誤不需要更多的描述,所以io.EOF有固定的錯誤信息——“EOF”。對於其他錯誤,我們可能需要在錯誤信息中描述錯誤的類型和數量,這使得我們不能像io.EOF一樣采用固定的錯誤信息。在7.11節中,我們會提出更繫統的方法區分某些固定的錯誤值。
|
||||||
|
@ -10,19 +10,23 @@
|
|||||||
在Go的錯誤處理中,錯誤是軟件包API和應用程序用戶界面的一個重要組成部分,程序運行失敗僅被認爲是幾個預期的結果之一。
|
在Go的錯誤處理中,錯誤是軟件包API和應用程序用戶界面的一個重要組成部分,程序運行失敗僅被認爲是幾個預期的結果之一。
|
||||||
|
|
||||||
對於那些將運行失敗看作是預期結果的函數,它們會返迴一個額外的返迴值,通常是最後一個,來傳遞錯誤信息。如果導致失敗的原因隻有一個,額外的返迴值可以是一個布爾值,通常被命名爲ok。比如,cache.Lookup失敗的唯一原因是key不存在,那麽代碼可以按照下面的方式組織:
|
對於那些將運行失敗看作是預期結果的函數,它們會返迴一個額外的返迴值,通常是最後一個,來傳遞錯誤信息。如果導致失敗的原因隻有一個,額外的返迴值可以是一個布爾值,通常被命名爲ok。比如,cache.Lookup失敗的唯一原因是key不存在,那麽代碼可以按照下面的方式組織:
|
||||||
```
|
|
||||||
|
```Go
|
||||||
value, ok := cache.Lookup(key)
|
value, ok := cache.Lookup(key)
|
||||||
if !ok {
|
if !ok {
|
||||||
// ...cache[key] does not exist…
|
// ...cache[key] does not exist…
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
通常,導致失敗的原因不止一種,尤其是對I/O操作而言,用戶需要了解更多的錯誤信息。因此,額外的返迴值不再是簡單的布爾類型,而是error類型。
|
通常,導致失敗的原因不止一種,尤其是對I/O操作而言,用戶需要了解更多的錯誤信息。因此,額外的返迴值不再是簡單的布爾類型,而是error類型。
|
||||||
|
|
||||||
內置的error是接口類型。我們將在第七章了解接口類型的含義,以及它對錯誤處理的影響。現在我們隻需要明白error類型可能是nil或者non-nil。nil意味着函數運行成功,non-nil表示失敗。對於non-nil的error類型,我們可以通過調用error的Error函數或者輸出函數獲得字符串類型的錯誤信息。
|
內置的error是接口類型。我們將在第七章了解接口類型的含義,以及它對錯誤處理的影響。現在我們隻需要明白error類型可能是nil或者non-nil。nil意味着函數運行成功,non-nil表示失敗。對於non-nil的error類型,我們可以通過調用error的Error函數或者輸出函數獲得字符串類型的錯誤信息。
|
||||||
```
|
|
||||||
|
```Go
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
fmt.Printf("%v", err)
|
fmt.Printf("%v", err)
|
||||||
```
|
```
|
||||||
|
|
||||||
通常,當函數返迴non-nil的error時,其他的返迴值是未定義的(undefined),這些未定義的返迴值應該被忽略。然而,有少部分函數在發生錯誤時,仍然會返迴一些有用的返迴值。比如,當讀取文件發生錯誤時,Read函數會返迴可以讀取的字節數以及錯誤信息。對於這種情況,正確的處理方式應該是先處理這些不完整的數據,再處理錯誤。因此對函數的返迴值要有清晰的説明,以便於其他人使用。
|
通常,當函數返迴non-nil的error時,其他的返迴值是未定義的(undefined),這些未定義的返迴值應該被忽略。然而,有少部分函數在發生錯誤時,仍然會返迴一些有用的返迴值。比如,當讀取文件發生錯誤時,Read函數會返迴可以讀取的字節數以及錯誤信息。對於這種情況,正確的處理方式應該是先處理這些不完整的數據,再處理錯誤。因此對函數的返迴值要有清晰的説明,以便於其他人使用。
|
||||||
|
|
||||||
在Go中,函數運行失敗時會返迴錯誤信息,這些錯誤信息被認爲是一種預期的值而非異常(exception),這使得Go有别於那些將函數運行失敗看作是異常的語言。雖然Go有各種異常機製,但這些機製僅被使用在處理那些未被預料到的錯誤,卽bug,而不是那些在健壯程序中應該被避免的程序錯誤。對於Go的異常機製我們將在5.9介紹。
|
在Go中,函數運行失敗時會返迴錯誤信息,這些錯誤信息被認爲是一種預期的值而非異常(exception),這使得Go有别於那些將函數運行失敗看作是異常的語言。雖然Go有各種異常機製,但這些機製僅被使用在處理那些未被預料到的錯誤,卽bug,而不是那些在健壯程序中應該被避免的程序錯誤。對於Go的異常機製我們將在5.9介紹。
|
||||||
|
Loading…
Reference in New Issue
Block a user