ในการศึกษา Go ในส่วนของ function ต่างๆ ที่น่าสนใจ
ประกอบไปด้วย Multiple return value
Defer statement, panic function และ recovery function
ในส่วนของ function ใน Go นั้น สิ่งที่ไม่ธรรมดาเลยก็คือ Mutiple return value
นั่นคือ function สามารถ return ค่ากลับมาได้มากกว่า 1 ค่า
ตัวอย่างการ Write file ในภาษา C จะตรวจสอบว่าทำงานสำเร็จไหมนั้น
ต้องตรวจสอบค่าตัวเลขที่ return กลับมา เป็นเลขติดลบหรือไม่
ถ้าใช่แสดงว่ามี error เกิดขึ้น
ส่วนใน Go นั้นจะให้ทำการ return ค่า error กลับมาด้วย
ตัวอย่าง function Write จาก package os
func (file *File) Write( b []byte) (n int, err error)
คำธิบาย
function Write ทำการ return จำนวน byte ที่ถูกเขียน
และ error เมื่อค่า n กับจำนวนความยาวของ b ไม่เท่ากัน
ทำให้การจัดการ error นั้นสะดวกยิ่งขึ้น
การเรียกใช้งานก็ง่ายๆ ดังนี้
n, error := Write(b)
if error != nil {
fmt.Println(error)
}
//TODO next
ส่วนการ return
นั้นสามารถใส่หรือไม่ต้องใส่ variable ก็ได้
เนื่องจาก variable ของการ return จะถูก init ค่าตั้งแต่เข้ามาทำงานที่ function แล้ว
ดังนั้นถ้าไม่ได้กำหนดค่าอะไรไปตอน return จะใช้ค่า init นั้นเลย
หรือมองได้ว่า variable ในการประกาศของ function คือ incomming variable นั่นเอง
function ต่อมาที่มักจะเจอมากก็คือ defer, panic และ recovery
ซึ่งมักถูกเรียกใช้งานเสมอ ดังนั้นมาดูว่าแต่ละ function คืออะไร
defer
จะถูกเรียกทันทีหลังจากที่ function ก่อนหน้าทำการ return
โดยมักจะถูกใช้สำหรับการคืน resource ต่างๆ ไปยังระบบ
ทำให้มั่นใจได้ว่าคุณไม่ได้สร้างปัญหาขึ้นมา เช่น Memory leak เป็นต้น
ตัวอย่างเช่นการสร้าง file
จะทำ Close file ที่เปิดขึ้นมาทุกๆ ครั้ง หลังจากการทำงานเสร็จสิ้น
package main
import (
"fmt"
"os"
)
func main() {
fileOpen, error := os.Create("test.txt")
if error != nil {
fmt.Println(error)
}
defer func() {
fmt.Println("defer")
if error := fileOpen.Close(); error != nil {
fmt.Println(error)
}
}()
}
ในการใช้งาน defer มีกฎการใช้งานง่ายๆ 3 แบบ คือ
1. ค่าของ argument ของ function ใน defer จะถูกกำหนดเมื่อเรียกใช้งาน
func a() {
i := 0
defer fmt.Println(i)
i++
return
}
ในตัวอย่างนี้ จะแสดงค่า 0 ออกมา เพราะว่าตอนเรียกใช้งาน defer นั้นค่าของ i=0
2. Defer function จะประมวลผลจากการเรียกครั้งสุดท้ายก่อน เหมือนการทำ recusive เลย
func b() {
for i := 0; i < 4; i++ {
defer fmt.Print(i)
}
}
ในตัวอย่างนี้ จะแสดงค่า 3210 ออกมา
3. Defer function จะถูกประมวลผลหลังจากคำสั่ง return ใน function นั้นๆ เสมอ
func c() (i int) {
defer func() { i++ }()
return 1
}
ในตัวอย่างนี้ จะแสดงค่า 2 ออกมา ไม่ใช่ 1 นะ
ดังนั้นเราสามารถแก้ไขค่า return ได้ด้วยนะ สะดวกดีมาก
Panic
คือ build-in function สำหรับหยุดการทำงาน และเข้ากระบวนการที่เรียกว่า panicking
กระบวนการนี้ทำงานดังนี้
เมื่อ panic ถูกเรียกใน function A แล้ว
การทำงานใน function A จะหยุดลง
และจะทำงานใน defer function ของ function A
หลังจากนั้นทำการ return ค่ากลับไปยังคนเรียก function A
ตัวอย่าง code
package main
import "fmt"
func main() {
f()
fmt.Println("Returned normally from f.")
}
func f() {
defer func() {
fmt.Println("Defer g.")
}()
fmt.Println("Calling g.")
g(0)
fmt.Println("Returned normally from g.")
}
func g(i int) {
if i > 3 {
fmt.Println("Panicking!")
panic(fmt.Sprintf("%v", i))
}
defer fmt.Println("Defer in g", i)
fmt.Println("Printing in g", i)
g(i + 1)
}
ผลการทำงานเป็นดังนี้
Calling g. Printing in g 0 Printing in g 1 Printing in g 2 Printing in g 3 Panicking! Defer in g 3 Defer in g 2 Defer in g 1 Defer in g 0 Defer g. panic: 4 goroutine 1 [running]: runtime.panic(0x80e40, 0x2101fa200) /usr/local/go/src/pkg/runtime/panic.c:266 +0xb6 main.g(0x4) /Users/spock/somkiat/research/go/learn_005.go:22 +0x17d main.g(0x3) /Users/spock/somkiat/research/go/learn_005.go:26 +0x333 main.g(0x2) /Users/spock/somkiat/research/go/learn_005.go:26 +0x333 main.g(0x1) /Users/spock/somkiat/research/go/learn_005.go:26 +0x333 main.g(0x0) /Users/spock/somkiat/research/go/learn_005.go:26 +0x333 main.f() /Users/spock/somkiat/research/go/learn_005.go:15 +0xcf main.main() /Users/spock/somkiat/research/go/learn_005.go:6 +0x1e exit status 2
Recovery
คือ build-in function ทำการ recovery การทำงานของ panicking ใน go runtime
โดยใช้ใน defer function เท่านั้น
ทำงานโดยดึงข้อมูลที่ทำให้เกิดการเรียก panic และกลับมาทำงานปกติ
ตัวอย่าง code
package main
import "fmt"
func main() {
f()
fmt.Println("Returned normally from f.")
}
func f() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered in f", r)
}
}()
fmt.Println("Calling g.")
g(0)
fmt.Println("Returned normally from g.")
}
func g(i int) {
if i > 3 {
fmt.Println("Panicking!")
panic(fmt.Sprintf("%v", i))
}
defer fmt.Println("Defer in g", i)
fmt.Println("Printing in g", i)
g(i + 1)
}
ผลการทำงานเป็นดังนี้
Calling g. Printing in g 0 Printing in g 1 Printing in g 2 Printing in g 3 Panicking! Defer in g 3 Defer in g 2 Defer in g 1 Defer in g 0 Recovered in f 4 Returned normally from f.
ข้อสังเกตจากการใช้ function recovery คือ จะไม่โยน error ออกมา แต่จะกลับไปทำงานในโปรแกรมตามปกติ
ส่วนตัวอย่างการใช้งาน panic และ recovery ก็คือ JSON package ของ go นั่นเอง
สรุป
defer นั้นเป็นคำสั่งที่มีประสิทธิภาพสำหรับการควบคุมการทำงาน
ทำให้สามารถนำมาใช้เพื่อสร้างความสามารถต่างๆ ตามที่ต้องการได้เยอะ
ลองใช้งานกันดู
เพิ่มเติม
function init()
เป็น function เพิ่มเติม สำหรับให้เราทำสิ่งต่างๆ ต่อไปนี้
การ init ค่าต่างๆ ก่อนการทำงาน
การตรวจสอบค่าก่อนเริ่มทำงาน
การแก้ไขค่า หรือสถานะต่างๆ ก่อนเริ่มทำงาน
โดยจะทำงานหลังจากทำงานในส่วนการประกาศตัวแปรแล้ว
package main
import "fmt"
func init() {
fmt.Println("Call init")
}
func main() {
fmt.Println("Call main")
}
ผลการทำงาน
Call init
Call main
ต่อไปทำการศึกษาเรื่อง Data
Reference Websites
http://golang.org/doc/effective_go.html#functions
http://golang.org/doc/effective_go.html#init
http://blog.golang.org/defer-panic-and-recover
http://www.goinggo.net/2013/06/understanding-defer-panic-and-recover.html
http://stackoverflow.com/questions/1821811/how-to-read-write-from-to-file
http://golang.org/doc/articles/wiki/
