Golang 語言該用命名返回值嗎?
01介紹
Golang 語言支持命名返回值,它與使用普通(匿名)返回值不同的是,命名返回值會被視為定義在函數(shù)頂部的變量,并且在使用 return 語句返回時,不再必須在其后面指定參數(shù)名,也就是支持“裸”返回。
而使用普通返回值時,使用 return 語句返回時,需要在其后面指定與普通返回值相同類型的參數(shù)名。
實際上,命名返回值和普通返回值都有其適用的場景,本文我們介紹 Golang 語言函數(shù)或方法使用命名返回值和普通返回值各自的“好處”與“壞處”。
02命名返回值
使用命名返回值的“好處”是可以提升代碼可讀性,讀者朋友們試想一下,當函數(shù)或方法有多個返回值時,尤其是函數(shù)體中代碼比較長的函數(shù)或方法,如果我們使用普通返回值,那么我們想要知道返回值的含義,就需要先閱讀函數(shù)體中完整代碼。
而如果使用具有實際含義的命名返回值,我們只需要閱讀函數(shù)或方法的簽名,就可以知道其含義,甚至可以把它們作為文檔使用。
但是,命名返回值也不是沒有“壞處”,如果函數(shù)體內有變量與命名返回值同名,那么命名返回值會被覆蓋,所以我們也需要注意避免“踩坑”。
03普通返回值
普通(匿名)返回值的“好處”是簡潔,當我們寫一些簡短函數(shù)或方法時,使用普通返回值可以使代碼更加簡潔,在 Golang 語言官方標準庫中,有很多使用普通返回值的函數(shù)或方法。
但是如果返回值是指針類型時,使用普通返回值,就會使我們函數(shù)體中的代碼變得不優(yōu)雅,比如以下這段示例代碼。
- func c() *int {
- i := 0
- return &i
- }
- func d() (i *int) {
- return
- }
當然這里列舉的代碼片段是個極端示例,我們在編寫 Golang 代碼時,也并不會這么使用。
還有就是在編寫函數(shù)體代碼比較長的函數(shù)時,使用普通返回值的代碼,其可讀性比不上使用命名返回值的代碼。
04踩坑
defer 在命名返回值和普通返回值的函數(shù)或方法中,返回的結果不一樣。
- func main() {
- f := fmt.Println
- f(a())
- f(b())
- }
- func a() int {
- i := 0
- defer func() {
- i += 1
- fmt.Println("a defer:", i)
- }()
- return i
- }
- func b() (i int) {
- i = 0
- defer func() {
- i += 1
- fmt.Println("b defer:", i)
- }()
- return i
- }
輸出結果:
- a defer: 1
- 0
- b defer: 1
- 1
閱讀上面這段代碼,我們可以發(fā)現(xiàn)使用普通返回值的函數(shù) a(),返回結果是 0。使用命名返回值的函數(shù) b(),返回結果是 1。
我們在之前的文章中,也單獨介紹過 defer 。在這里我們簡述一下,當我們使用 defer 調用一個函數(shù)時,該函數(shù)的執(zhí)行,被推遲到周圍函數(shù)返回的那一刻,要么是因為周圍的函數(shù)執(zhí)行了 return 語句,要么是因為相應的 goroutine 崩潰。
在函數(shù) a() 中,因為我們沒有使用命名返回值,所以返回結果 return i,其中 i 是一個靜態(tài)值,即使我們在 defer 調用的函數(shù)中給變量 i 執(zhí)行 +1 操作,返回結果中的變量 i 是不可訪問的,所以也不會修改返回結果中的變量 i。
在函數(shù) b() 中,因為我們使用命名返回值,所以變量 i 已被分配,并且被初始化為類型零值。即使 defer 調用的函數(shù)在返回結果之后執(zhí)行,返回結果中的變量 i 仍然是可以被訪問的,所以其值仍然可以被修改。
05總結
在非簡短函數(shù)或方法的代碼中,建議優(yōu)先使用命名返回值。它不僅可以提升代碼的可讀性,也可以幫我們避免一些 “踩坑”。