GO語言————6.4 defer 和追蹤

FLy_鵬程萬里發表於2018-06-30

6.4 defer 和追蹤

關鍵字 defer 允許我們推遲到函式返回之前(或任意位置執行 return 語句之後)一刻才執行某個語句或函式(為什麼要在返回之後才執行這些語句?因為 return 語句同樣可以包含一些操作,而不是單純地返回某個值)。

關鍵字 defer 的用法類似於物件導向程式語言 Java 和 C# 的 finally 語句塊,它一般用於釋放某些已分配的資源。

示例 6.8 defer.go

package main
import "fmt"

func main() {
	function1()
}

func function1() {
	fmt.Printf("In function1 at the top\n")
	defer function2()
	fmt.Printf("In function1 at the bottom!\n")
}

func function2() {
	fmt.Printf("Function2: Deferred until the end of the calling function!")
}

輸出:

In Function1 at the top
In Function1 at the bottom!
Function2: Deferred until the end of the calling function!

請將 defer 關鍵字去掉並對比輸出結果。

使用 defer 的語句同樣可以接受引數,下面這個例子就會在執行 defer 語句時列印 0

func a() {
	i := 0
	defer fmt.Println(i)
	i++
	return
}

當有多個 defer 行為被註冊時,它們會以逆序執行(類似棧,即後進先出):

func f() {
	for i := 0; i < 5; i++ {
		defer fmt.Printf("%d ", i)
	}
}

上面的程式碼將會輸出:4 3 2 1 0

關鍵字 defer 允許我們進行一些函式執行完成後的收尾工作,例如:

  1. 關閉檔案流 (詳見 第 12.2 節
// open a file  
defer file.Close()
  1. 解鎖一個加鎖的資源 (詳見 第 9.3 節
mu.Lock()  
defer mu.Unlock() 
  1. 列印最終報告
printHeader()  
defer printFooter()
  1. 關閉資料庫連結
// open a database connection  
defer disconnectFromDB()

合理使用 defer 語句能夠使得程式碼更加簡潔。

以下程式碼模擬了上面描述的第 4 種情況:

package main

import "fmt"

func main() {
	doDBOperations()
}

func connectToDB() {
	fmt.Println("ok, connected to db")
}

func disconnectFromDB() {
	fmt.Println("ok, disconnected from db")
}

func doDBOperations() {
	connectToDB()
	fmt.Println("Defering the database disconnect.")
	defer disconnectFromDB() //function called here with defer
	fmt.Println("Doing some DB operations ...")
	fmt.Println("Oops! some crash or network error ...")
	fmt.Println("Returning from function here!")
	return //terminate the program
	// deferred function executed here just before actually returning, even if
	// there is a return or abnormal termination before
}

輸出:

ok, connected to db
Defering the database disconnect.
Doing some DB operations ...
Oops! some crash or network error ...
Returning from function here!
ok, disconnected from db

使用 defer 語句實現程式碼追蹤

一個基礎但十分實用的實現程式碼執行追蹤的方案就是在進入和離開某個函式列印相關的訊息,即可以提煉為下面兩個函式:

func trace(s string) { fmt.Println("entering:", s) }
func untrace(s string) { fmt.Println("leaving:", s) }

以下程式碼展示了何時呼叫這兩個函式:

示例 6.10 defer_tracing.go:

package main

import "fmt"

func trace(s string)   { fmt.Println("entering:", s) }
func untrace(s string) { fmt.Println("leaving:", s) }

func a() {
	trace("a")
	defer untrace("a")
	fmt.Println("in a")
}

func b() {
	trace("b")
	defer untrace("b")
	fmt.Println("in b")
	a()
}

func main() {
	b()
}

輸出:

entering: b
in b
entering: a
in a
leaving: a
leaving: b

上面的程式碼還可以修改為更加簡便的版本(示例 6.11 defer_tracing2.go):

package main

import "fmt"

func trace(s string) string {
	fmt.Println("entering:", s)
	return s
}

func un(s string) {
	fmt.Println("leaving:", s)
}

func a() {
	defer un(trace("a"))
	fmt.Println("in a")
}

func b() {
	defer un(trace("b"))
	fmt.Println("in b")
	a()
}

func main() {
	b()
}

使用 defer 語句來記錄函式的引數與返回值

下面的程式碼展示了另一種在除錯時使用 defer 語句的手法(示例 6.12 defer_logvalues.go):

package main

import (
	"io"
	"log"
)

func func1(s string) (n int, err error) {
	defer func() {
		log.Printf("func1(%q) = %d, %v", s, n, err)
	}()
	return 7, io.EOF
}

func main() {
	func1("Go")
}

相關文章