承上啟下
? ? ? ? 上一篇文章中介紹了struct和interface,在Go語言中,是沒有Class這個概念的,我們可以通過Struct和方法的組合,來實現Class。我們通過Struct嵌套來實現繼承這樣的一種機制,并且不用設置虛函數這樣的特殊說明。同時,實現接口的方式也是隱式實現,當我們實現了一組接口的所有的方法時,默認一定使用了這組接口了。只能說這樣的方法很不直觀,但是編譯器的靜態代碼分析還是能夠發現的。但是如果是對于程序中的error,Go又會怎么處理呢?
開始學習
錯誤類型(error
)
在Go語言中,error
?是一個內建的接口類型,用于表示錯誤狀態。它是Go錯誤處理的核心部分,提供了一種標準的方式來傳達函數或方法執行失敗的信息。以下是?error
?接口的定義:
type error interface {Error() string
}
這個接口非常簡單,只包含一個?Error()
?方法,該方法返回一個描述錯誤的字符串。
創建錯誤
創建錯誤通常有以下幾種方式:
errors.New:這是最簡單的方式,用于創建一個包含給定錯誤消息的新錯誤。
err := errors.New("something went wrong")
??????fmt.Errorf:這個函數允許你使用格式化字符串創建錯誤,類似于?fmt.Sprintf
。
err := fmt.Errorf("invalid argument: %v", value)
自定義錯誤類型:你可以通過實現?error
?接口來自定義錯誤類型。
type MyError struct {Msg stringCode int
}func (e *MyError) Error() string {return fmt.Sprintf("error %d: %s", e.Code, e.Msg)
}func doSomething() error {return &MyError{Msg: "operation failed", Code: 42}
}
錯誤處理
在Go中,錯誤處理通常是顯式的,以下是一些常見的錯誤處理模式:
檢查錯誤:在調用可能返回錯誤的函數或方法后,應該立即檢查錯誤。
result, err := doSomething()
if err != nil {// 處理錯誤return err
}
// 使用result
錯誤傳遞:如果當前函數無法處理錯誤,它應該將錯誤返回給調用者。
func doSomething() error {result, err := doAnotherThing()if err != nil {return err // 傳遞錯誤}// 使用resultreturn nil
}
錯誤包裝:有時,你可能想在返回錯誤時添加額外的上下文信息。可以使用?fmt.Errorf
?和?%w
?標志來包裝錯誤。
func doSomething() error {err := doAnotherThing()if err != nil {return fmt.Errorf("failed to do something: %w", err)}return nil
}
使用?%w
?標志包裝的錯誤可以通過?errors.Is
?或?errors.As
?進行檢查。
錯誤檢查和斷言
- errors.Is:用于檢查錯誤鏈中是否包含特定錯誤。
if errors.Is(err, targetErr) {// err 是 targetErr 或其包裝
}
- errors.As:用于將錯誤斷言為特定類型。
var targetErr *MyError
if errors.As(err, &targetErr) {// err 是 *MyError 類型
}
Defer函數?
在Go語言中,defer
?語句用于確保在函數返回之前執行特定的函數調用(延遲調用)。這對于資源清理、解鎖、關閉文件描述符等場景非常有用,因為它可以確保這些操作總是被執行,即使在發生錯誤或者提前返回的情況下。
defer
?語句的語法
defer
?語句的語法非常簡單:
defer 函數調用
這里的“函數調用”可以是任何函數或方法調用,包括內置函數、用戶定義的函數以及方法。
defer
?語句的行為
以下是?defer
?語句的一些關鍵行為:
-
延遲執行:
defer
?語句會在包含它的函數即將返回之前執行。無論函數是正常返回還是由于錯誤返回,defer
?語句都會被執行。 -
參數評估:
defer
?語句中的參數(如果有的話)是在執行?defer
?語句時評估的,而不是在執行延遲函數時。 -
執行順序:如果同一個函數中有多個?
defer
?語句,它們會按照后進先出(LIFO)的順序執行。也就是說,最后一個?defer
?的函數調用會最先執行。
下面是一個使用?defer
?的示例,展示了如何在文件操作后確保文件被關閉:
func main() {f, err := os.Open("file.txt")if err != nil {log.Fatalf("failed to open file: %s", err)}defer f.Close() // 延遲關閉文件// 讀取文件內容...
}
在這個例子中,無論后續的文件操作是否成功,f.Close()
?都會在?main
?函數返回之前被調用。
嵌套?defer
你可以在?defer
?語句中調用另一個函數,該函數內部也可以有?defer
?語句:
func main() {defer func() {// 這里的 defer 會最后執行defer fmt.Println("Deferred inside defer")fmt.Println("Outer defer")}()fmt.Println("Hello")
}
在這個例子中,輸出順序將是:
Hello
Outer defer
Deferred inside defer
這是因為內部的?defer
?語句在執行外部的?defer
?時被調用,然后才執行外部的?defer
?語句。
Recover
在Go語言中,recover
?是一個內建函數,它用于捕獲運行時的恐慌(panic)。當程序發生恐慌時,正常的函數執行流程會被立即停止,程序將進入恐慌狀態,執行所有已注冊的延遲函數(deferred functions)。如果在延遲函數中調用了?recover
,則可以捕獲當前的恐慌,并且使程序恢復正常執行而不是崩潰。
recover
?的語法
recover
?的語法非常簡單,它不接受任何參數,并且返回一個?interface{}
?類型的值:
recover()
如果?recover
?被直接調用(不在延遲函數中),它將不會做任何事情,并且返回?nil
。
recover
?的行為
下面是?recover
?的一些關鍵行為:
-
只能在延遲函數中生效:
recover
?只能在?defer
?語句調用的延遲函數中使用。如果?recover
?在其他任何地方被調用,它將不會捕獲任何恐慌。 -
返回恐慌值:如果當前的goroutine正處于恐慌狀態,
recover
?將捕獲到恐慌值,并返回該值,使得程序可以繼續執行正常的流程。 -
恢復正常執行:一旦?
recover
?成功捕獲了恐慌,程序將不會繼續傳播恐慌,而是會繼續執行延遲函數中的剩余代碼,然后返回到恐慌發生點的函數調用者。
下面是一個使用?recover
?的示例:
package mainimport "fmt"func main() {defer func() {if r := recover(); r != nil {fmt.Println("Recovered in main:", r)}}()fmt.Println("Start")panic("Something bad happened")fmt.Println("End") // 這行代碼不會被執行
}// 輸出:
// Start
// Recovered in main: Something bad happened
在這個例子中,panic
?觸發了一個錯誤,但隨后在延遲函數中通過?recover
?被捕獲。因此,程序沒有崩潰,而是打印了恢復信息。
?異常處理對比
??defer
?和?recover
?是Go語言中用于錯誤處理的機制,而?try-catch
?是許多其他面向對象編程語言(如Java、C++、C#等)中用于異常處理的機制。Go語言最為人所詬病的就是滿屏的if err != nil這樣的語法,而在其他的高級語言中大多是一個try{}catch{}finally{}直接捕獲了所有的代碼。這是由于在Go中建議對每個error都單獨做處理,如果非要實現try catch的方法,更多時候是通過defer recover實現的。
defer
?和?recover
-
錯誤處理:在Go中,
defer
?和?recover
?通常用于處理恐慌(panic),這類似于其他語言中的異常。defer
?語句用于延遲執行一個函數調用,而?recover
?用于捕獲恐慌。 -
使用場景:
defer
?和?recover
?通常用于處理運行時錯誤,這些錯誤是意外的,并且可能導致程序無法繼續執行。 -
語法:
func someFunction() {defer func() {if r := recover(); r != nil {// 處理恐慌}}()// 可能發生恐慌的代碼 }
-
顯式性:
defer
?和?recover
?需要顯式地聲明,它們不會自動捕獲錯誤。 -
性能:
defer
?和?recover
?的性能開銷相對較低,因為它們只在發生恐慌時才執行。
try-catch
-
異常處理:
try-catch
?用于捕獲和處理異常。異常通常表示程序運行時的錯誤或異常情況。 -
使用場景:
try-catch
?用于處理各種錯誤,包括運行時錯誤和可預見的錯誤情況。 -
語法(以Java為例):
try {// 可能拋出異常的代碼 } catch (SomeException e) {// 處理異常 }
-
自動捕獲:
try-catch
?塊自動捕獲在?try
?塊中拋出的異常,無需顯式聲明。 -
性能:
try-catch
?可能會有更高的性能開銷,因為異常的拋出和捕獲涉及堆棧跟蹤的創建。
對比
-
錯誤/異常類型:Go使用?
error
?類型來表示可預見的錯誤,而?panic
?和?recover
?用于處理意外情況。try-catch
?通常用于處理所有類型的錯誤和異常。 -
傳播機制:在Go中,錯誤必須顯式地返回和檢查。恐慌會自動傳播,直到被?
recover
?捕獲。在?try-catch
?機制中,異常會自動向上傳播,直到遇到匹配的?catch
?塊。 -
資源管理:Go使用?
defer
?來確保資源被正確釋放,而其他語言可能使用?finally
?塊或RAII(Resource Acquisition Is Initialization)。 -
控制流:
try-catch
?允許異常改變程序的控制流,而Go的?defer
?和?recover
?主要用于恢復程序狀態,而不是改變控制流。 -
代碼風格:Go鼓勵通過返回錯誤值來進行錯誤處理,這有助于保持代碼的清晰和簡潔。
try-catch
?可能導致代碼中異常處理邏輯的分散。