雲原生時代崛起的程式語言Go基礎實戰

2023-04-29 06:00:36

@

概述

定義

Go 官網地址 https://golang.google.cn/ 最新版本1.20.3

Go 官網檔案地址 https://golang.google.cn/doc/

Go 原始碼地址 https://github.com/golang

Golang簡稱Go,是谷歌開源的程式語言,旨在提供程式設計師程式設計效率,易於學習,非常適合團隊使用,天然支援高並行,有垃圾收集機制,且自帶功能完善健壯的標準庫。

Go語言表現力強、簡潔、乾淨、高效。它的並行機制使得其容易編寫出充分利用多核和網路機器的程式,其型別系統使程式構造變得靈活和模組化。Go可以快速編譯為機器碼,並且具有垃圾收集的便利性和執行時反射的強大功能;是一種快速的、靜態型別的編譯語言,感覺像是一種動態型別的解釋語言。

Go語言由於來自全球技術大廠谷歌創造及推動,其生態發展極其迅速,從業界聲音看大有可能成為接下來10年內最具統治力的語言,也即是替代Java霸主地位,至於未來是否可以靜待結果。至少從目前國內大廠阿里、騰訊、百度、位元組的使用趨勢極其效應可以看到其迅速擴張的可能,越受開發者喜愛其生態完整就會越好,如果從事企業級開發的夥伴有時間精力建議的話不煩可以開始深入學學Go語言開發。

使用場景

  • 雲上和網路的應用:如雲端計算領域、區塊鏈、並行網路程式設計,有主要的雲提供商強大的工具和api生態系統,用Go構建服務很容易。下面列舉小部分流行包:
    • cloud.google.com/go
    • aws/client
    • Azure/azure-sdk-for-go
  • Cli命令列介面:cli是純文字的。雲和基礎設施應用程式主要基於cli,這樣易於自動化和遠端功能。使用流行的開放原始碼包和健壯的標準庫,可以使用Go建立快速而優雅的cli。下面列舉小部分流行包:
    • spf13/cobra
    • spf13/viper
    • urfave/cli
    • delve
    • chzyer/readline
  • Web開發:Go支援快速和可延伸的web應用程式。下面列舉小部分流行包:
    • net/http
    • html/template
    • flosch/pongo2
    • database/sql
    • elastic/go-elasticsearch
  • 開發運營和網站可靠性工程(雲原生運維方向,特別是基於k8s的運維開發):Go擁有快速的構建時間、簡潔的語法、自動格式化器和檔案生成器,可以同時支援DevOps和SRE。下面列舉小部分流行包:
    • open-telemetry/opentelemetry-go
    • istio/istio
    • urfave/cli

Go 安全

Go語言編寫安全可靠的軟體,主要有如下資訊:

  • Go安全策略:解釋瞭如何向Go團隊報告Go標準庫和子庫中的安全問題。
  • Go安全釋出:Go版本歷史包含了過去安全問題的釋出說明。根據釋出策略釋出了兩個最新的Go主要版本的安全修補程式。
  • Go漏洞管理:支援幫助開發人員找到可能影響其Go專案的已知公共漏洞。
  • Go Fuzzing:提供一種自動測試,持續地操作程式的輸入以發現錯誤。
  • Go加密:Go加密庫是Go標準庫和子庫中的crypto/…和golang.org/x/crypto/…包,並遵循這些原則開發。

使用須知

搜尋工具

Go 開發包搜尋網站 https://pkg.go.dev/

先安裝最新版本的Go。有關下載和安裝Go編譯器、工具和庫的說明,請自行查詢安裝檔案,版本下載(1.20.3),https://golang.google.cn/dl/

由於之前使用1.20.2安裝,安裝後檢視版本

開發過程有需要了解相當包的使用,可以在Go開發官網上查詢,這也是一個良好學習和使用方式。

IDE整合開發工具可以選擇JetBrains的GoLand,一個擴充套件支援JavaScript, TypeScript和資料庫的Go IDE,JetBrains Go地址

Go基礎命令

# 通過命令列輸入go可以檢視支援命令
go
go help build

  • go bug:開啟Bug報告。
  • go build:用於編譯指定的原始碼檔案或程式碼包以及它們的依賴包。常用
  • go clean:移除當前原始碼包和關聯原始碼包裡面編譯生成的檔案,即刪除掉執行其它命令時產生的一些檔案和目錄,go 命令會在臨時目錄中構建物件,因此 go clean 主要關注其他工具或手動呼叫 go build 留下的物件檔案。常用
  • go doc:列印與由其引數(包、const、func、型別、var、方法或結構欄位)標識的專案相關聯的檔案註釋。
  • go env:列印Go語言的環境資訊。
  • go fix:把指定程式碼包的所有Go語言原始碼檔案中的舊版本程式碼修正為新版本的程式碼。
  • go fmt:go程式格式化,自動對齊、空格等。如果用了IDE這個命令就不需要了。
  • go generate:⽣成由現有⽂件中的指令描述的運⾏命令。這些命令可以運⾏任何程序,但⽬的是建立或更新 Go 原始檔。
  • go get:根據要求和實際情況從網際網路上下載或更新指定的程式碼包及其依賴包,並對它們進行編譯和安裝。常用
  • go install:用於編譯並安裝指定的程式碼包及它們的依賴包。常用
  • go list:列出指定的程式碼包的資訊。
  • go mod
    • go mod init:生成go.mod檔案。常用
    • go mod download :下載go.mod檔案中指明的所有依賴。
    • go mod tidy:整理現有的依賴。常用
    • go mod graph:檢視現有的依賴結構。
    • go mod edit:編輯go.mod檔案。
    • go mod vendor :匯出專案所有的依賴到vendor目錄。
    • go mod verify:校驗一個模組是否被篡改過。
    • go mod why:檢視為什麼需要依賴某模組。
  • go work:跨檔案目錄操作本地包、多個mod模組包呼叫、本地測試。
  • go run:可以編譯並執行命令原始碼檔案,並把編譯後的可執行檔案存放到臨時工作目錄。常用
  • go test:用於以程式碼包為單位對Go語言編寫的程式進行測試。常用
  • go tool:執行go自帶的工具。go tool pprof對cpu、記憶體和協程進行監控;go tool trace跟蹤協程的執行過程。
  • go version:列印 Go 可執⾏⽂件的構建資訊。
  • go vet:用於檢查Go語言原始碼中靜態錯誤的簡單工具。

標準庫

Go 標準庫 https://golang.google.cn/pkg/

精心策劃的Go專案列表 https://github.com/golang/go/wiki/Projects

目前官方提供190多種標準庫包(包括cmd包),涵蓋大部分的使用場景,如壓縮、加密、資料庫、編碼、異常、畫圖、列印、IO操作、網路、作業系統、反射HTML模版等,可以先大概看下官方每個包功能大體說明,需要使用某些功能細節時再具體檢視官網。相對於標準庫,其他包也稱為子庫,這些包也是Go專案的一部分,但在Go主幹之外。它們是在比Go核心更寬鬆的相容性要求下開發的。可以用「go get」安裝它們。

基礎語法

Effective Go 概覽

Go是一門新語言,儘管它借鑑了現有語言的思想,但它具有不同尋常的特性,使有效的Go程式與用其相關語言編寫的程式在性質上有所不同。直接將c++或Java程式翻譯成Go不太可能產生令人滿意的結果。重要的是要先了解它的性質和習慣用法,瞭解用Go程式設計的既定約定比如命名、格式、程式結構等等。比如拿分號(Semicolons)來說,像C一樣,Go的形式語法使用分號來終止語句,但與C不同的是,這些分號不會出現在原始碼中。相反,詞法分析器使用一個簡單的規則在掃描時自動插入分號,因此輸入文字基本上沒有分號,也即是在Go語言中不需要採用分號結尾。

命名規範

Go語言核心宗旨就是簡潔,在Go語言的相關命名也是推薦如此,命名規則涉及變數、常數、全域性函數、結構、介面、方法等的命名,從語法層面進行了以下限定;任何需要對外暴露的名字必須以大寫字母開頭類似public,不需要對外暴露的則應該以小寫字母開頭類似private。

  • 一般命名:推薦駝峰命名方式,如userManger或UserManager;單詞縮寫預設全大寫或全小寫,如userID、baiduCDN、id、cdn。
  • 專案名:小寫,多個單詞建議採用中劃線分隔,比如github.com/gin-gonic。
  • 包名:package的名字和目錄保持一致,儘量採取有意義的包名,簡短,有意義,儘量和標準庫不要衝突。包名應該為小寫單詞,不要使用下劃線或者混合大小寫,統一使用單數形式。如domain、main。
  • 檔名:檔案命名一律採用小寫,見名思義,測試檔案以test.go結尾,如stringutil.go, stringutil_test.go。
  • Local變數:保持簡短;如索引採用i而不採用index,reader簡寫r,buffer簡寫為b;避免冗餘,命名不要和上下文重疊,在方法體內變數名count 會比runeCount更簡潔,在map的上下文下可以簡寫:v,ok=m[k]。
func RuneCount(b []byte) int {
    count := 0
    for i := 0; i < len(b); {
        if b[i] < RuneSelf {
            i++
        } else {
            _, n := DecodeRune(b[i:])
            i += n
        }
        count++
    }
    return count
}
func Read(b *Buffer, p []byte) (n int, err error) {
        if b.empty() {
                b.Reset()
        }
        n = copy(p, b.buf[b.off:])
        b.off += n
        return n, nil
}
  • 結構體:名詞或名詞短語,如Account、Book,避免使用Manager這樣的。 如果該資料結構需要序列化,如json, 則首字母大寫, 包括裡面的欄位。
type Host struct {
    Port string `json:"port"`
    Address string `json:"address"`
}
  • 介面:介面單個函數的介面名以 er 為字尾,兩個函數的介面名可以綜合兩個函數名,三個以上函數的介面名類似於結構體名。
type Reader interface {
    Read(p []byte) (n int, err error)
}
type WriteFlusher interface {
    Write([]byte) (int, error)
    Flush() error
}
type Car interface {
    Start() 
    Stop()
    Drive()
}
  • 方法:動詞或動詞短語;如果是結構體方法,那麼 Receiver 的名稱應該縮寫, 要麼都使用值, 要麼都用指標;一般用一個或兩個字母(優先r);對於Receiver命名應該統一, 要麼都使用值, 要麼都用指標,如果 Receiver 是指標可統一使用p。
func (b *Buffer) Read(p []byte) (n int, err error){
}
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request){
}
func (r Rectangle) Size() Point{
}
func (f foo) method() {
}
func (p *foo) method() {
}

Go沒對get/set方法特別支援,必要的時候可以自己定義,Go對get有不同建議,如

// 推薦
p.FirstName()
// 不推薦
p.GetFirstName()
  • error:自定義error命名通常以 「名稱+Error」 作為結構體的名字,變數時會用簡寫err + 名稱。
type TypeError struct {
	Errors []string
}
ErrShortDst = errors.New("transform: short destination buffer")
  • 常用縮寫
src = source
srv = server
arg = argument
conn = connect, connection
attr = attribute
abs = absolute
min = minimum
len = length
auth = authenticate
buf = buffer
ctl = control
ctx = context
str = string
msg = message
fmt = format
dest = destination
diff = difference
orig = original
recv = receive
ref = reference
repo = repository
util = utility

註釋

Go提供C風格的/* */塊註釋和c++風格的//行註釋。行註釋是常態;塊註釋主要作為包註釋出現,但在表示式中或禁用大量程式碼時很有用。

變數

在Go語言中,變數被顯式宣告並被編譯器用來檢查函數呼叫的型別正確性;var宣告1個或多個變數。

package main

import "fmt"

func main() {

    var a = "initial"
    fmt.Println(a)

    var b, c int = 1, 2
    fmt.Println(b, c)

    var d = true
    fmt.Println(d)

    var e int
    fmt.Println(e)

    f := "apple"
    fmt.Println(f)
}

常數(const)

Go支援字元常數、字串常數、布林常數和數值常數;const宣告一個常數值。

package mainimport (    "fmt"    "math")const s string = "constant"func main() {    fmt.Println(s)    const n = 500000000    const d = 3e20 / n    fmt.Println(d)    fmt.Println(int64(d))    fmt.Println(math.Sin(n))}

控制結構

  • 迴圈(For):for是Go唯一的迴圈結構。
package mainimport "fmt"func main() {    i := 1    for i <= 3 {        fmt.Println(i)        i = i + 1    }    for j := 7; j <= 9; j++ {        fmt.Println(j)    }    for {        fmt.Println("loop")        break    }    for n := 0; n <= 5; n++ {        if n%2 == 0 {            continue        }        fmt.Println(n)    }}

  • 選擇(If/Else):在Go中沒有三元if,所以即使對於基本條件,也需要使用完整的if語句。
package mainimport "fmt"func main() {    if 7%2 == 0 {        fmt.Println("7 is even")    } else {        fmt.Println("7 is odd")    }    if 8%4 == 0 {        fmt.Println("8 is divisible by 4")    }    if num := 9; num < 0 {        fmt.Println(num, "is negative")    } else if num < 10 {        fmt.Println(num, "has 1 digit")    } else {        fmt.Println(num, "has multiple digits")    }}

  • Switch:跨多個分支表達條件。
package mainimport (    "fmt"    "time")func main() {    i := 2    fmt.Print("Write ", i, " as ")    switch i {    case 1:        fmt.Println("one")    case 2:        fmt.Println("two")    case 3:        fmt.Println("three")    }    switch time.Now().Weekday() {    case time.Saturday, time.Sunday:        fmt.Println("It's the weekend")    default:        fmt.Println("It's a weekday")    }    t := time.Now()    switch {    case t.Hour() < 12:        fmt.Println("It's before noon")    default:        fmt.Println("It's after noon")    }    whatAmI := func(i interface{}) {        switch t := i.(type) {        case bool:            fmt.Println("I'm a bool")        case int:            fmt.Println("I'm an int")        default:            fmt.Printf("Don't know type %T\n", t)        }    }    whatAmI(true)    whatAmI(1)    whatAmI("hey")}

資料型別

  • 陣列(Arrays):陣列是特定長度的元素的編號序列。單在典型的Go程式碼中,切片更為常見,後面介紹。
package mainimport "fmt"func main() {    var a [5]int    fmt.Println("emp:", a)    a[4] = 100    fmt.Println("set:", a)    fmt.Println("get:", a[4])    fmt.Println("len:", len(a))    b := [5]int{1, 2, 3, 4, 5}    fmt.Println("dcl:", b)    var twoD [2][3]int    for i := 0; i < 2; i++ {        for j := 0; j < 3; j++ {            twoD[i][j] = i + j        }    }    fmt.Println("2d: ", twoD)}

  • 切片(Slices):是Go中的一種重要資料型別,它為序列提供了比陣列更強大的介面。
package mainimport "fmt"func main() {    s := make([]string, 3)    fmt.Println("emp:", s)    s[0] = "a"    s[1] = "b"    s[2] = "c"    fmt.Println("set:", s)    fmt.Println("get:", s[2])    fmt.Println("len:", len(s))    s = append(s, "d")    s = append(s, "e", "f")    fmt.Println("apd:", s)    c := make([]string, len(s))    copy(c, s)    fmt.Println("cpy:", c)    l := s[2:5]    fmt.Println("sl1:", l)    l = s[:5]    fmt.Println("sl2:", l)    l = s[2:]    fmt.Println("sl3:", l)    t := []string{"g", "h", "i"}    fmt.Println("dcl:", t)    twoD := make([][]int, 3)    for i := 0; i < 3; i++ {        innerLen := i + 1        twoD[i] = make([]int, innerLen)        for j := 0; j < innerLen; j++ {            twoD[i][j] = i + j        }    }    fmt.Println("2d: ", twoD)}

  • 對映(Maps):Go內建的關聯資料型別,在其他語言中有時稱為雜湊或字典。
package mainimport "fmt"func main() {    m := make(map[string]int)    m["k1"] = 7    m["k2"] = 13    fmt.Println("map:", m)    v1 := m["k1"]    fmt.Println("v1:", v1)    v3 := m["k3"]    fmt.Println("v3:", v3)    fmt.Println("len:", len(m))    delete(m, "k2")    fmt.Println("map:", m)    _, prs := m["k2"]    fmt.Println("prs:", prs)    n := map[string]int{"foo": 1, "bar": 2}    fmt.Println("map:", n)}

迭代(range)

Range遍歷各種資料結構中的元素

package mainimport "fmt"func main() {    nums := []int{2, 3, 4}    sum := 0    for _, num := range nums {        sum += num    }    fmt.Println("sum:", sum)    for i, num := range nums {        if num == 3 {            fmt.Println("index:", i)        }    }    kvs := map[string]string{"a": "apple", "b": "banana"}    for k, v := range kvs {        fmt.Printf("%s -> %s\n", k, v)    }    for k := range kvs {        fmt.Println("key:", k)    }    for i, c := range "go" {        fmt.Println(i, c)    }}

函數

Go函數返回值需要顯式返回,即它不會自動返回最後一個表示式的值。有多個相同型別的連續引數時可以省略型別相似的引數的型別名稱,直到宣告該型別的最後一個引數。

package main

import "fmt"

// 多個引數
func plus(a int, b int) int {

	return a + b
}
// 多個相同型別引數
func plusPlus(a, b, c int) int {
	return a + b + c
}
// 多個返回值
func vals() (int, int) {
	return 3, 7
}
// 可變引數
func sum(nums ...int) {
    fmt.Print(nums, " ")
    total := 0

    for _, num := range nums {
        total += num
    }
    fmt.Println(total)
}
// 閉包,Go支援匿名函數,它可以形成閉包。當想要內聯定義一個函數而不必命名它時可以使用匿名函數很。
func intSeq() func() int {
    i := 0
    return func() int {
        i++
        return i
    }
}
// 遞迴函數
func fact(n int) int {
    if n == 0 {
        return 1
    }
    return n * fact(n-1)
}

func main() {

	res := plus(1, 2)
	fmt.Println("1+2 =", res)

	res = plusPlus(1, 2, 3)
	fmt.Println("1+2+3 =", res)
    
    a, b := vals()
	fmt.Println(a)
	fmt.Println(b)

	_, c := vals()
	fmt.Println(c)
    
    sum(1, 2)
    sum(1, 2, 3)

    nums := []int{1, 2, 3, 4}
    sum(nums...)
    
    nextInt := intSeq()

    fmt.Println(nextInt())
    fmt.Println(nextInt())
    fmt.Println(nextInt())

    newInts := intSeq()
    fmt.Println(newInts())
    
    fmt.Println(fact(7))

    var fib func(n int) int

    fib = func(n int) int {
        if n < 2 {
            return n
        }

        return fib(n-1) + fib(n-2)
    }

    fmt.Println(fib(7))
}

指標

Go支援指標,允許在程式中傳遞對值和記錄的參照。

package main

import "fmt"

func zeroval(ival int) {
    ival = 0
}

func zeroptr(iptr *int) {
    *iptr = 0
}

func main() {
    i := 1
    fmt.Println("initial:", i)

    zeroval(i)
    fmt.Println("zeroval:", i)

    zeroptr(&i)
    fmt.Println("zeroptr:", i)

    fmt.Println("pointer:", &i)
}

字串和符文

Go字串是位元組的唯讀切片。該語言和標準庫將字串特殊地視為UTF-8編碼文字的容器。在其他語言中,字串是由「字元」組成的。在go中,字元的概念被稱為符文——它是一個表示Unicode碼點的整數。

package main

import (
    "fmt"
    "unicode/utf8"
)

func main() {

    const s = "สวัสดี"

    fmt.Println("Len:", len(s))

    for i := 0; i < len(s); i++ {
        fmt.Printf("%x ", s[i])
    }
    fmt.Println()

    fmt.Println("Rune count:", utf8.RuneCountInString(s))

    for idx, runeValue := range s {
        fmt.Printf("%#U starts at %d\n", runeValue, idx)
    }

    fmt.Println("\nUsing DecodeRuneInString")
    for i, w := 0, 0; i < len(s); i += w {
        runeValue, width := utf8.DecodeRuneInString(s[i:])
        fmt.Printf("%#U starts at %d\n", runeValue, i)
        w = width

        examineRune(runeValue)
    }
}

func examineRune(r rune) {

    if r == 't' {
        fmt.Println("found tee")
    } else if r == 'ส' {
        fmt.Println("found so sua")
    }
}

結構體(struct)

Go的結構體是欄位的型別化集合,將資料分組形成記錄;結構體是可變的。

package main

import "fmt"

type person struct {
    name string
    age  int
}

func newPerson(name string) *person {

    p := person{name: name}
    p.age = 42
    return &p
}

func main() {

    fmt.Println(person{"Bob", 20})

    fmt.Println(person{name: "Alice", age: 30})

    fmt.Println(person{name: "Fred"})

    fmt.Println(&person{name: "Ann", age: 40})

    fmt.Println(newPerson("Jon"))

    s := person{name: "Sean", age: 50}
    fmt.Println(s.name)

    sp := &s
    fmt.Println(sp.age)

    sp.age = 51
    fmt.Println(sp.age)
}

方法

Go支援在結構型別上定義的方法。方法的接受者可以是結構體也可以是指向結構體的指標。

package mainimport "fmt"type rect struct {	width, height int}func (r *rect) area() int {	return r.width * r.height}func (r rect) perim() int {	return 2*r.width + 2*r.height}func main() {	r := rect{width: 10, height: 5}	fmt.Println("area: ", r.area())	fmt.Println("perim:", r.perim())	rp := &r	fmt.Println("area: ", rp.area())	fmt.Println("perim:", rp.perim())}

介面(interface)

介面是方法簽名的命名集合。

package mainimport (    "fmt"    "math")type geometry interface {    area() float64    perim() float64}type rect struct {    width, height float64}type circle struct {    radius float64}func (r rect) area() float64 {    return r.width * r.height}func (r rect) perim() float64 {    return 2*r.width + 2*r.height}func (c circle) area() float64 {    return math.Pi * c.radius * c.radius}func (c circle) perim() float64 {    return 2 * math.Pi * c.radius}func measure(g geometry) {    fmt.Println(g)    fmt.Println(g.area())    fmt.Println(g.perim())}func main() {    r := rect{width: 3, height: 4}    c := circle{radius: 5}    measure(r)    measure(c)}

Go支援嵌入結構體和介面,以表達更豐富的型別組合。

package mainimport "fmt"type base struct {    num int}func (b base) describe() string {    return fmt.Sprintf("base with num=%v", b.num)}type container struct {    base    str string}func main() {    co := container{        base: base{            num: 1,        },        str: "some name",    }    fmt.Printf("co={num: %v, str: %v}\n", co.num, co.str)    fmt.Println("also num:", co.base.num)    fmt.Println("describe:", co.describe())    type describer interface {        describe() string    }    var d describer = co    fmt.Println("describer:", d.describe())}

泛型

Go 1.18 引入了泛型,為 Go 語言帶來了更強大的型別系統,使其可以更好地支援各種資料型別和演演算法。Go 泛型可以應用於各種資料型別和演演算法,使程式碼更加通用、簡潔、易讀和易維護。

package mainimport "fmt"func MapKeys[K comparable, V any](m map[K]V) []K {    r := make([]K, 0, len(m))    for k := range m {        r = append(r, k)    }    return r}type List[T any] struct {    head, tail *element[T]}type element[T any] struct {    next *element[T]    val  T}func (lst *List[T]) Push(v T) {    if lst.tail == nil {        lst.head = &element[T]{val: v}        lst.tail = lst.head    } else {        lst.tail.next = &element[T]{val: v}        lst.tail = lst.tail.next    }}func (lst *List[T]) GetAll() []T {    var elems []T    for e := lst.head; e != nil; e = e.next {        elems = append(elems, e.val)    }    return elems}func main() {    var m = map[int]string{1: "2", 2: "4", 4: "8"}    fmt.Println("keys:", MapKeys(m))    _ = MapKeys[int, string](m)    lst := List[int]{}    lst.Push(10)    lst.Push(13)    lst.Push(23)    fmt.Println("list:", lst.GetAll())}

以下是一些使用 Go 泛型的場景:

  • 集合資料型別:Go 泛型可以方便地定義各種集合資料型別,如棧、佇列、連結串列、二元樹等。下面使用泛型定義棧的例子定義了一個 Stack[T] 型別,表示一個可以儲存任意型別 T 的棧。Push 方法用於將一個元素壓入棧中,Pop 方法用於彈出棧頂的元素。
  • 演演算法實現:泛型還可以用於實現各種演演算法,如排序、查詢、字串匹配等。以下是一個使用泛型實現快速排序的例子:在這個例子中,使用泛型定義 QuickSort 函數,可以對任意型別 T 的切片進行排序。該函數採用經典的快速排序演演算法實現。

錯誤(errors)

在Go中,習慣上通過顯式的單獨返回值來傳達錯誤。這與Java和Ruby等語言中使用的異常以及c中有時使用的過載的單個結果/錯誤值形成對比。Go的方法可以很容易地看到哪些函數返回錯誤,並使用用於任何其他無錯誤任務的相同語言結構來處理它們。

package main

import (
    "errors"
    "fmt"
)

func f1(arg int) (int, error) {
    if arg == 42 {

        return -1, errors.New("can't work with 42")

    }

    return arg + 3, nil
}

type argError struct {
    arg  int
    prob string
}

func (e *argError) Error() string {
    return fmt.Sprintf("%d - %s", e.arg, e.prob)
}

func f2(arg int) (int, error) {
    if arg == 42 {

        return -1, &argError{arg, "can't work with it"}
    }
    return arg + 3, nil
}

func main() {

    for _, i := range []int{7, 42} {
        if r, e := f1(i); e != nil {
            fmt.Println("f1 failed:", e)
        } else {
            fmt.Println("f1 worked:", r)
        }
    }
    for _, i := range []int{7, 42} {
        if r, e := f2(i); e != nil {
            fmt.Println("f2 failed:", e)
        } else {
            fmt.Println("f2 worked:", r)
        }
    }

    _, e := f2(42)
    if ae, ok := e.(*argError); ok {
        fmt.Println(ae.arg)
        fmt.Println(ae.prob)
    }
}

恐慌(pinic)

恐慌通常意味著事情出乎意料地出錯了;大多數情況下,使用它來快速處理在正常操作中不應該發生的錯誤,或者沒有準備好優雅地處理的錯誤。

package main

import "os"

func main() {

    panic("a problem")

    _, err := os.Create("/tmp/file")
    if err != nil {
        panic(err)
    }
}

推遲(defer)

Defer用於確保函數呼叫在程式執行的後期執行,通常是出於清理目的。Defer通常用於其他語言中使用的地方,例如ensure和finally。比如用於釋放或關閉連線、釋放或關閉檔案控制程式碼等

package main

import (
    "fmt"
    "os"
)

func main() {

    f := createFile("d:/tmp/defer.txt")
    defer closeFile(f)
    writeFile(f)
}

func createFile(p string) *os.File {
    fmt.Println("creating")
    f, err := os.Create(p)
    if err != nil {
        panic(err)
    }
    return f
}

func writeFile(f *os.File) {
    fmt.Println("writing")
    fmt.Fprintln(f, "data")

}

func closeFile(f *os.File) {
    fmt.Println("closing")
    err := f.Close()

    if err != nil {
        fmt.Fprintf(os.Stderr, "error: %v\n", err)
        os.Exit(1)
    }
}

恢復(recover)

通過使用內建的recover函數,Go使從恐慌中恢復成為可能。恢復可以阻止因恐慌而中止程式,而是讓程式繼續執行。

package main

import "fmt"

func mayPanic() {
    panic("a problem")
}

func main() {

    defer func() {
        if r := recover(); r != nil {

            fmt.Println("Recovered. Error:\n", r)
        }
    }()

    mayPanic()

    fmt.Println("After mayPanic()")
}

  • 本人部落格網站IT小神 www.itxiaoshen.com