2021年1月23日 星期六

Go 語言筆記

  1. 空白標識符、匿名佔位符(blank identifier、anonymous placeholder),底線「_」
    佔位置,不用對不須要的資料多設一個變數
    例1:
    func test() (string, string) {
        return "X", "Y"
    }
    func main() {
        var a, _ = test()
        fmt.Println(a) //X
    }
    

    例2:
    arr := [3]int{10, 20, 30}
    sum := 0
    for _, v := range arr {
        sum += v
    }
    fmt.Println(sum)//60

  2.  不定數量參數,「...」
    例1:陣列元素不定數量
    arr := [...]string{"A", "B", "C"}
    fmt.Println(len(arr))//3

    例2:function參數不定數量
    func test(args ...string) {
        fmt.Println(args) //[A B C]
    }
    func main() {
        test("A", "B", "C")
    }

    例3:slice打散
    sliceA := []string{"A", "B", "C"}
    sliceB := []string{"D", "E"}
    sliceC := append(sliceA, sliceB...)
    fmt.Println(sliceC)//[A B C D E]
  3. [...]string{"A", "B", "C"} 和 []string{"A", "B", "C"} 的差異
    [...]string{"A", "B", "C"} 產生的是陣列 array
    []string{"A", "B", "C"} 產生的是切片 slice  (背後產生一個隱性陣列)
    參考:Confusion with “…” operator in golang
    https://stackoverflow.com/questions/28692410/confusion-with-operator-in-golang
  4. 沒有 while,用 for 實現
    例1:
    i := 0
    for i < 10 {
        i++
    }

    例2:無窮迴圈
    for {
        //break
    }
  5. 沒有 class,用結構(struct)實現。
    沒有繼承,用嵌入組合、介面解決,避免繼承產生的維護難度(多用合成/聚合,少用繼承)。
  6. struct指標語法糖。
    根據struct接受的參數型態,自動進行取址(reference &)或取值。(dereference *)進行操作
    例1:指標自動轉換取值操作
    type aa struct {
        m int
    }
    func main() {
        var ptr = &aa{10}
        fmt.Println(ptr) //&{10}
        ptr.m = 20       //自動等同 (*ptr).m=10
        fmt.Println(ptr) //&{20}
    }

    例2:指標自動轉換取址操作
    type aa struct {
        m int
    }
    func (x *aa) add(v int) { //aa須為指標
        x.m = x.m + v
    }
    func main() {
        var p = aa{10}
        fmt.Println(p) //{10}
        p.add(20)      //自動等同 (&p).add(20)
        fmt.Println(p) //{30}
    }

    例3:指標自動轉換取值操作
    type aa struct {
        m int
    }
    func (x aa) add(v int) { //aa須為值
        x.m = x.m + v
        fmt.Println(x) //{30}
    }
    func main() {
        var p = &aa{10}
        fmt.Println(p) //&{10}
        p.add(20)      //自動等同 (*p).add(20)
        fmt.Println(p) //&{10} 傳值過去,所以原本的變數仍為10
    }
  7. errors
    errors.New()
    fmt.Printf("%#v\n", errors.New("test err"))
    //&errors.errorString{s:"test err"}

    fmt.Errorf()
    fmt.Printf("%#v\n", fmt.Errorf("test err %d", 456))
    //&errors.errorString{s:"test err 456"}
    
    //使用 %w 包裹(嵌套)錯誤
    fmt.Printf("%#v\n", fmt.Errorf("test err2 %w", errors.New("test err")))
    //&fmt.wrapError{msg:"test err2 test err", err:(*errors.errorString)(0xc000032e70)}

    errors.Unwrap() 解開包裹的錯誤,取得裡層的錯誤
    fmt.Printf("%#v\n", errors.Unwrap(fmt.Errorf("test err2 %w", errors.New("test err"))))
    //&errors.errorString{s:"test err"}

    errors.Is(errA, errB) bool
    判斷 errA 是否為 errB 的錯誤
    如果 errA 是包裹的錯誤,會一層一層解開,跟 errB 比對,任一層相符即為true
    var errB = errors.New("test err")
    var errWrap1 = fmt.Errorf("w1 %w", errB)     //包1層
    var errWrap2 = fmt.Errorf("w2 %w", errWrap1) //包2層
    
    fmt.Printf("%#v\n", errB)     //&main.myErr{msg:"test err"}
    fmt.Printf("%#v\n", errWrap1) //&fmt.wrapError{msg:"w1 test err", err:(*errors.errorString)(0xc000032210)}
    fmt.Printf("%#v\n", errWrap2) //&fmt.wrapError{msg:"w2 w1 test err", err:(*fmt.wrapError)(0xc000004500)}
    
    fmt.Printf("%t\n", errors.Is(errB, errWrap1))     //false
    fmt.Printf("%t\n", errors.Is(errWrap1, errWrap2)) //false
    fmt.Printf("%t\n", errors.Is(errWrap1, errB))     //true
    fmt.Printf("%t\n", errors.Is(errWrap2, errB))     //true, errWrap2是包裹的錯誤,會一層一層解開比較
    fmt.Printf("%t\n", errors.Is(errWrap2, errWrap1)) //true

    errors.As(errA, targetB interface{}) bool
    判斷 errA 是否為 targetB 類型的錯誤,如果 errA 是包裹的錯誤,會一層一層解開,跟 targetB 比對
    將第一個比對符合的錯誤,指派到 targetB,並回傳 true
    type myErr struct {
        msg string
    }
    
    func (e *myErr) Error() string {
        return e.msg
    }
    func main() {
        var targetErr *myErr
        errWrap1 := fmt.Errorf("w1 %w", &myErr{msg: "test err"}) //包1層
        var errWrap2 = fmt.Errorf("w2 %w", errWrap1)             //包2層
    
        fmt.Printf("%#v\n", targetErr) //(*main.myErr)(nil)
        fmt.Printf("%#v\n", errWrap1)  //&mt.wrapError{msg:"w1 test err", err:(*main.myErr)(0xc0000321f0)}
        fmt.Printf("%#v\n", errWrap2)  //&fmt.wrapError{msg:"w2 w1 test err", err:(*fmt.wrapError)(0xc0000044a0)}
    
        fmt.Printf("%t\n", errors.As(errWrap1, &targetErr)) //true
        fmt.Printf("%#v\n", targetErr)                      //&main.myErr{msg:"test err"}
    
        fmt.Printf("%t\n", errors.As(errWrap2, &targetErr)) //true
        fmt.Printf("%#v\n", targetErr)                      //&main.myErr{msg:"test err"}
    }
  8. defer 延遲執行,採LIFO後進先出的順序(Last In, First out)
    通常可使用在解鎖互斥鎖,或關閉文件defer f.Close()避免最後忘記關閉原先開啟的文件。
    defer fmt.Println(1)
    defer fmt.Println(2)
    defer fmt.Println(3)
    //結果
    3
    2
    1
  9. panic() 讓程式失敗中斷,類似其他語言的 throw exception
    可使用在巢狀深層呼叫,發生錯誤時,便於向外傳播
    panic("發生錯誤")
    fmt.Println("hello,world") //不會執行到這裡,程式在前一行已崩潰停止
    //執行結果
    panic: 發生錯誤
    goroutine 1 [running]:
    main.main()
    	d:/Go/test/test.go:29 +0x45
    Process exiting with code: 0
  10. recover(),recover 搭配 defer 可捕捉 panic 產生的錯誤,避免程式中斷崩潰停止
    recover() 回傳值為傳給 panic() 的值,但(所以)在以下情況會是 nil
    A.panic's argument was nil;
    B.the goroutine is not panicking;
    C.recover was not called directly by a deferred function.

    例1:捕捉錯誤,避免系統中斷崩潰停止,但後續程式仍不會執行
    defer func() {
        log.Println("AA")
        err := recover()
        fmt.Println(err)
        log.Println("BB")
    }()
    panic("發生錯誤")
    fmt.Println("hello,world") //雖然程式不會崩潰停止,但依然不會執行到這裡
    //執行結果
    2021/01/18 23:23:29 AA
    2021/01/18 23:23:29 BB
    發生錯誤
    Process exiting with code: 0

    例2:捕捉錯誤,避免系統中斷崩潰停止,後續程式仍可執行
    參考 https://golang.org/ref/spec#Handling_panics
    Handling panics
    //定義一個 protect() 函式,可傳入另一個可能發生panic的函式執行,
    func protect(g func()) {
        defer func() {
            log.Println("done") // Println executes normally even if there is a panic
            if x := recover(); x != nil {
                log.Printf("run time panic: %v", x)
            }
        }()
        log.Println("start")
        g()
    }
    
    func main() {
        protect(func() {
            panic("發生錯誤")
        })
        log.Println("hello,world")
        
        //執行結果
        2021/01/18 23:41:03 start
        2021/01/18 23:41:03 done
        2021/01/18 23:41:03 run time panic: 發生錯誤
        2021/01/18 23:41:03 hello,world
        Process exiting with code: 0
    }
  11. 疑問:log.Printf()、fmt.Println() 穿插寫,每次執行輸出的順序可能不一樣?
    log.Printf("A")
    fmt.Println("B")
    log.Printf("C")
    
    //執行結果1
    2021/01/15 23:38:59 A
    B
    2021/01/15 23:38:59 C
    Process exiting with code: 0
    
    //執行結果2
    2021/01/15 23:38:36 A
    2021/01/15 23:38:36 C
    B
    Process exiting with code: 0
  12. goroutine 併發
    語法:
    go funciotn或method
  13. channel 通道
    多個goroutine併發,之間溝通的管道

    無緩衝通道,Ex:make(chan int)
    有緩衝通道,Ex:make(chan int, 緩衝大小)
  14. 通道 deadlock 死鎖
    • 從沒資料的通道中,接收資料
      ch := make(chan int, 1)
      //ch <- 5
      A := <-ch //
      log.Print(A)
      
    • 空的 select
      select {}
    • 無緩衝通道(或有緩衝通道滿了之後),再發資料給此通道,發送、接收須用goroutine併發分開處理,否則會發生deadlock死鎖

      Ex1:未超出緩衝,不用另一個goroutine接收
      ch := make(chan int, 1) //1個緩衝區
      ch <- 5 //剛好用完緩衝區
      A := <-ch 
      log.Print(A) //A
      Ex2:緩衝滿了之後,發送和接收,須用goroutine併發處理
      ch := make(chan int, 1) //1個緩衝區
      
      ch <- 5 //剛好用完緩衝區
      go func() {
          ch <- 6 //緩衝用完,須用goroutine併發處理
          ch <- 7
      }()
      
      A := <-ch
      log.Print(A) //5
      A = <-ch
      log.Print(A) //6
      A = <-ch
      log.Print(A) //7
    • select 若沒 default, case敘述句跟沒資料的通道取資料、或發送資料給無緩衝區的通道,會出現deadlock死鎖
      ch := make(chan int, 1)
      //ch <- 5
      select {
      case <-ch:
      default:
          log.Print("避免deaklock")
      }
  15. select 通道中的有同時多個 case 條件都符合時,是隨機執行的
    ch := make(chan int, 1)
    //ch <- 5
    select {
    case ch <- 1:
    case ch <- 2:
    case ch <- 3:
    default:
        log.Print("避免deaklock")
    }
    A := <-ch
    log.Print(A) //1、2、3 隨機出現
  16. sync.Mutex 加鎖,避免併發競爭問題
    競爭檢測分析
    go run -race ./main.go
    加入互斥鎖
    var mux sync.Mutex
    mux.Lock()
    defer mux.Unlock()
    唯讀鎖定
    var rwMux sync.RWMutex
    rwMux.Lock()
    defer rwMux.Unlock()
    
    rwMux.RLock() //唯讀鎖定
    defer rwMux.RUnlock()
  17. sync.WaitGroup,等待所有任務完成,可避免併發尚未執行完畢,主程式已結束
    var wg sync.WaitGroup
    wg.Add(1)
    go func() {
        defer wg.Done()
        ....
    }()
    wg.Wait()
  18. reflect 反射
  19. Unit Test 單元測試
    go test 指令會執行所有檔名後綴為「_test.go」檔案內的函式
    go test
    go test -v
    go test -v run 指定要執行的函式
    「_test.go」檔案內的函式須為Test開頭
    例如:
    func TestAdd(t *testing.T) { /* ... */ } //後面接英文,須大寫
    func Test_add(t *testing.T) { /* ... */ }
    範例:
    add.go 專案程式檔案
    package main
    
    import (
        "fmt"
    )
    
    func add(a, b int) int {
        return a + b
    }
    
    func main() {
        fmt.Println(add(1, 2))
    }
    add_test.go 單元測試檔案
    package main
    
    import "testing"
    
    func TestAddA(t *testing.T) {
        if add(1, 2) != 3 {
            t.Error("1+2 err")
        }
    }
    
    func TestAddB(t *testing.T) {
        if add(20, 30) != 50 {
            t.Error("20+30 err")
        }
    }
    執行單元測試
    >go test    
    PASS
    ok      hello/UnitTest  0.216s
    
    >go test -v
    === RUN   TestAddA
    --- PASS: TestAddA (0.00s)
    === RUN   TestAddB
    --- PASS: TestAddB (0.00s)
    PASS
    ok      hello/UnitTest  0.203s
    
    
    >go test -v -run TestAddB
    === RUN   TestAddB
    --- PASS: TestAddB (0.00s)
    PASS
    ok      hello/UnitTest  0.214s





參考:


沒有留言:

張貼留言