golang2

ในการศึกษา 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/

Tags: