Go 語法速覽與實踐清單(V0.5)

王下邀月熊發表於2018-03-29

Go 語法速覽與實踐清單(V0.5)

Go CheatSheet 是對於 Go 學習/實踐過程中的語法與技巧進行盤點,其屬於 Awesome CheatSheet 系列,致力於提升學習速度與研發效能,即可以將其當做速查手冊,也可以作為輕量級的入門學習資料。 本文參考了許多優秀的文章與程式碼示範,統一宣告在了 Go Links;如果希望深入瞭解某方面的內容,可以繼續閱讀 Go 開發:語法基礎與工程實踐,或者前往 coding-snippets/go 檢視使用 Go 解決常見的資料結構與演算法、設計模式、業務功能方面的程式碼實現。

環境配置與語法基礎

可以前往這裡下載 Go SDK 安裝包,或者使用 brew 等包管理器安裝。go 命令依賴於 $GOPATH 環境變數進行程式碼組織,多專案情況下也可以使用 ln 進行目錄對映以方便進行專案管理。GOPATH 允許設定多個目錄,每個目錄都會包含三個子目錄:src 用於存放原始碼,pkg 用於存放編譯後生成的檔案,bin 用於存放編譯後生成的可執行檔案。

環境配置完畢後,可以使用 go get 獲取依賴,go run 執行程式,go build 來編譯專案生成與包名(資料夾名)一致的可執行檔案。Golang 1.8 之後支援 dep 依賴管理工具,對於空的專案使用 dep init 初始化依賴配置,其會生成 Gopkg.toml Gopkg.lock vendor/ 這三個檔案(夾)。

我們可以使用 dep ensure -add github.com/pkg/errors 新增依賴,執行之後,其會在 toml 檔案中新增如下鎖:

[[constraint]]
  name = "github.com/pkg/errors"
  version = "0.8.0"
複製程式碼

簡單的 Go 中 Hello World 程式碼如下:

package main
import "fmt"
func main() {
    fmt.Println("hello world")
}
複製程式碼

也可以使用 Beego 實現簡單的 HTTP 伺服器:

package main
import "github.com/astaxie/beego"
func main() {
	beego.Run()
}
複製程式碼

Go 並沒有相對路徑引入,而是以資料夾為單位定義模組,譬如我們新建名為 math 的資料夾,然後使用 package math 來宣告該檔案中函式所屬的模組。

import (
        mongo "mywebapp/libs/mongodb/db" // 對引入的模組重新命名
        _ "mywebapp/libs/mysql/db" // 使用空白下劃線表示僅呼叫其初始化函式

)
複製程式碼

外部引用該模組是需要使用工作區間或者 vendor 相對目錄,其目錄索引情況如下:

cannot find package "sub/math" in any of:
    ${PROJECTROOT}/vendor/sub/math (vendor tree)
    /usr/local/Cellar/go/1.10/libexec/src/sub/math (from $GOROOT)
    ${GOPATH}/src/sub/math (from $GOPATH)
複製程式碼

Go 規定每個原始檔的首部需要進行包宣告,可執行檔案預設放在 main 包中;而各個包中預設首字母大寫的函式作為其他包可見的匯出函式,而小寫函式則預設外部不可見的私有函式。

表示式與控制流

變數宣告與賦值

作為強型別靜態語言,Go 允許我們在變數之後標識資料型別,也為我們提供了自動型別推導的功能。

// 宣告三個變數,皆為 bool 型別
var c, python, java bool

// 宣告不同型別的變數,並且賦值
var i bool, j int = true, 2

// 複雜變數宣告
var (
	ToBe   bool       = false
	MaxInt uint64     = 1<<64 - 1
	z      complex128 = cmplx.Sqrt(-5 + 12i)
)

// 短宣告變數
c, python, java := true, false, "no!"

// 宣告常量
const constant = "This is a constant"
複製程式碼

在 Go 中,如果我們需要比較兩個複雜物件的相似性,可以使用 reflect.DeepEqual 方法:

m1 := map[string]int{
    "a":1,
    "b":2,
}
m2 := map[string]int{
    "a":1,
    "b":2,
}
fmt.Println(reflect.DeepEqual(m1, m2))
複製程式碼

條件判斷

Go 提供了增強型的 if 語句進行條件判斷:

// 基礎形式
if x > 0 {
	return x
} else {
	return -x
}

// 條件判斷之前新增自定義語句
if a := b + c; a < 42 {
	return a
} else {
	return a - 42
}

// 常用的型別判斷
var val interface{}
val = "foo"
if str, ok := val.(string); ok {
	fmt.Println(str)
}
複製程式碼

Go 也支援使用 Switch 語句:

// 基礎格式
switch operatingSystem {
case "darwin":
	fmt.Println("Mac OS Hipster")
	// 預設 break,不需要顯式宣告
case "linux":
	fmt.Println("Linux Geek")
default:
	// Windows, BSD, ...
	fmt.Println("Other")
}

// 類似於 if,可以在條件之前新增自定義語句
switch os := runtime.GOOS; os {
case "darwin": ...
}

// 使用 switch 語句進行型別判斷:
switch v := anything.(type) {
  case string:
    fmt.Println(v)
  case int32, int64:
    ...
  default:
    fmt.Println("unknown")
}
複製程式碼

Switch 中也支援進行比較:

number := 42
switch {
	case number < 42:
		fmt.Println("Smaller")
	case number == 42:
		fmt.Println("Equal")
	case number > 42:
		fmt.Println("Greater")
}
複製程式碼

或者進行多條件匹配:

var char byte = '?'
switch char {
	case ' ', '?', '&', '=', '#', '+', '%':
		fmt.Println("Should escape")
}
複製程式碼

迴圈

Go 支援使用 for 語句進行迴圈,不存在 while 或者 until:

for i := 1; i < 10; i++ {
}

// while - loop
for ; i < 10;  {
}

// 單條件情況下可以忽略分號
for i < 10  {
}

// ~ while (true)
for {
}
複製程式碼

我們也可以使用 range 函式,對於 Arrays 與 Slices 進行遍歷:

// loop over an array/a slice
for i, e := range a {
    // i 表示下標,e 表示元素
}

// 僅需要元素
for _, e := range a {
    // e is the element
}

// 或者僅需要下標
for i := range a {
}

// 定時執行
for range time.Tick(time.Second) {
    // do it once a sec
}
複製程式碼

Function: 函式

定義,引數與返回值

// 簡單函式定義
func functionName() {}

// 含參函式定義
func functionName(param1 string, param2 int) {}

// 多個相同型別引數的函式定義
func functionName(param1, param2 int) {}

// 函式表示式定義
add := func(a, b int) int {
	return a + b
}
複製程式碼

Go 支援函式的最後一個引數使用 ... 設定為不定引數,即可以傳入一個或多個引數值:

func adder(args ...int) int {
	total := 0
	for _, v := range args { // Iterates over the arguments whatever the number.
		total += v
	}
	return total
}

adder(1, 2, 3) // 6
adder(9, 9) // 18

nums := []int{10, 20, 30}
adder(nums...) // 60
複製程式碼

我們也可以使用 Function Stub 作為函式引數傳入,以實現回撥函式的功能:

func Filter(s []int, fn func(int) bool) []int {
    var p []int // == nil
    for _, v := range s {
        if fn(v) {
            p = append(p, v)
        }
    }
    return p
}
複製程式碼

雖然 Go 不是函式式語言,但是也可以用其實現柯里函式(Currying Function):

func add(x, y int) int {
    return x+ y
}

func adder(x int) (func(int) int) {
    return func(y int) int {
        return add(x, y)
    }
}

func main() {
	add3 := adder(3)
	fmt.Println(add3(4))    // 7
}
複製程式碼

Go 支援多個返回值:

// 返回單個值
func functionName() int {
    return 42
}

// 返回多個值
func returnMulti() (int, string) {
    return 42, "foobar"
}
var x, str = returnMulti()

// 命名返回多個值
func returnMulti2() (n int, s string) {
    n = 42
    s = "foobar"
    // n and s will be returned
    return
}
var x, str = returnMulti2()
複製程式碼

閉包: Closure

Go 同樣支援詞法作用域與變數保留,因此我們可以使用閉包來訪問函式定義處外層的變數:

func scope() func() int{
    outer_var := 2
    foo := func() int { return outer_var}
    return foo
}
複製程式碼

閉包中並不能夠直接修改外層變數,而是會自動重定義新的變數值:

func outer() (func() int, int) {
    outer_var := 2
    inner := func() int {
        outer_var += 99
        return outer_var // => 101 (but outer_var is a newly redefined
    }
    return inner, outer_var // => 101, 2 (outer_var is still 2, not mutated by inner!)
}
複製程式碼

函式執行

Go 中提供了 defer 關鍵字,允許將某個語句的執行推遲到函式返回語句之前:

func read(...) (...) {
  f, err := os.Open(file)
  ...
  defer f.Close()
  ...
  return .. // f will be closed
複製程式碼

異常處理

Go 語言中並不存在 try-catch 等異常處理的關鍵字,對於那些可能返回異常的函式,只需要在函式返回值中新增額外的 Error 型別的返回值:

type error interface {
    Error() string
}
複製程式碼

某個可能返回異常的函式呼叫方式如下:

import (
    "fmt"
    "errors"
)

func main() {
    result, err:= Divide(2,0)

    if err != nil {
            fmt.Println(err)
    }else {
            fmt.Println(result)
    }
}

func Divide(value1 int,value2 int)(int, error) {
    if(value2 == 0){
        return 0, errors.New("value2 mustn't be zero")
    }
    return value1/value2  , nil
}
複製程式碼

Go 還為我們提供了 panic 函式,所謂 panic,即是未獲得預期結果,常用於丟擲異常結果。譬如當我們獲得了某個函式返回的異常,卻不知道如何處理或者不需要處理時,可以直接通過 panic 函式中斷當前執行,列印出錯誤資訊、Goroutine 追蹤資訊,並且返回非零的狀態碼:

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

資料型別與結構

型別繫結與初始化

Go 中的 type 關鍵字能夠對某個型別進行重新命名:

// IntSlice 並不等價於 []int,但是可以利用型別轉換進行轉換
type IntSlice []int
a := IntSlice{1, 2}
複製程式碼

可以使用 T(v) 或者 obj.(T) 進行型別轉換,obj.(T) 僅針對 interface{} 型別起作用:

t := obj.(T) // if obj is not T, error
t, ok := obj.(T) // if obj is not T, ok = false

// 型別轉換與判斷
str, ok := val.(string);
複製程式碼

基本資料型別

interface {} // ~ java Object
bool // true/false
string
int8  int16  int32  int64
int // =int32 on 32-bit, =int64 if 64-bit OS
uint8 uint16 uint32 uint64 uintptr
uint
byte // alias for uint8
rune // alias for int32, represents a Unicode code point
float32 float64
複製程式碼

字串

// 多行字串宣告
hellomsg := `
 "Hello" in Chinese is 你好 ('Ni Hao')
 "Hello" in Hindi is नमस्ते ('Namaste')
`
複製程式碼

格式化字串:

fmt.Println("Hello, 你好, नमस्ते, Привет, ᎣᏏᏲ") // basic print, plus newline
p := struct { X, Y int }{ 17, 2 }
fmt.Println( "My point:", p, "x coord=", p.X ) // print structs, ints, etc
s := fmt.Sprintln( "My point:", p, "x coord=", p.X ) // print to string variable

fmt.Printf("%d hex:%x bin:%b fp:%f sci:%e",17,17,17,17.0,17.0) // c-ish format
s2 := fmt.Sprintf( "%d %f", 17, 17.0 ) // formatted print to string variable
複製程式碼

序列型別

Array 與 Slice 都可以用來表示序列資料,二者也有著一定的關聯。

Array

其中 Array 用於表示固定長度的,相同型別的序列物件,可以使用如下形式建立:

[N]Type
[N]Type{value1, value2, ..., valueN}

// 由編譯器自動計算數目
[...]Type{value1, value2, ..., valueN}
複製程式碼

其具體使用方式為:

// 陣列宣告
var a [10]int

// 賦值
a[3] = 42

// 讀取
i := a[3]

// 宣告與初始化
var a = [2]int{1, 2}
a := [2]int{1, 2}
a := [...]int{1, 2}
複製程式碼

Go 內建了 len 與 cap 函式,用於獲取陣列的尺寸與容量:

var arr = [3]int{1, 2, 3}
arr := [...]int{1, 2, 3}

len(arr) // 3
cap(arr) // 3
複製程式碼

不同於 C/C++ 中的指標(Pointer)或者 Java 中的物件引用(Object Reference),Go 中的 Array 只是值(Value)。這也就意味著,當進行陣列拷貝,或者函式呼叫中的引數傳值時,會複製所有的元素副本,而非僅僅傳遞指標或者引用。顯而易見,這種複製的代價會較為昂貴。

Slice

Slice 為我們提供了更為靈活且輕量級地序列型別操作,可以使用如下方式建立 Slice:

// 使用內建函式建立
make([]Type, length, capacity)
make([]Type, length)

// 宣告為不定長度陣列
[]Type{}
[]Type{value1, value2, ..., valueN}

// 對現有陣列進行切片轉換
array[:]
array[:2]
array[2:]
array[2:3]
複製程式碼

不同於 Array,Slice 可以看做更為靈活的引用型別(Reference Type),它並不真實地存放陣列值,而是包含陣列指標(ptr),len,cap 三個屬性的結構體。換言之,Slice 可以看做對於陣列中某個段的描述,包含了指向陣列的指標,段長度,以及段的最大潛在長度,其結構如下圖所示:

group 2

// 建立 len 為 5,cap 為 5 的 Slice
s := make([]byte, 5)

// 對 Slice 進行二次切片,此時 len 為 2,cap 為 3
s = s[2:4]

// 恢復 Slice 的長度
s = s[:cap(s)]
複製程式碼

需要注意的是, 切片操作並不會真實地複製 Slice 中值,只是會建立新的指向原陣列的指標,這就保證了切片操作和運算元組下標有著相同的高效率。不過如果我們修改 Slice 中的值,那麼其會 真實修改底層陣列中的值,也就會體現到原有的陣列中:

d := []byte{'r', 'o', 'a', 'd'}
e := d[2:]
// e == []byte{'a', 'd'}
e[1] = 'm'
// e == []byte{'a', 'm'}
// d == []byte{'r', 'o', 'a', 'm'}
複製程式碼

Go 提供了內建的 append 函式,來動態為 Slice 新增資料,該函式會返回新的切片物件,包含了原始的 Slice 中值以及新增的值。如果原有的 Slice 的容量不足以存放新增的序列,那麼會自動分配新的記憶體:

// len=0 cap=0 []
var s []int

// len=1 cap=2 [0]
s = append(s, 0)

// len=2 cap=2 [0 1]
s = append(s, 1)

// len=5 cap=8 [0 1 2 3 4]
s = append(s, 2, 3, 4)

// 使用 ... 來自動展開陣列
a := []string{"John", "Paul"}
b := []string{"George", "Ringo", "Pete"}
a = append(a, b...) // equivalent to "append(a, b[0], b[1], b[2])"
// a == []string{"John", "Paul", "George", "Ringo", "Pete"}
複製程式碼

我們也可以使用內建的 copy 函式,進行 Slice 的複製,該函式支援對於不同長度的 Slice 進行復制,其會自動使用最小的元素數目。同時,copy 函式還能夠自動處理使用了相同的底層陣列之間的 Slice 複製,以避免額外的空間浪費。

func copy(dst, src []T) int

// 申請較大的空間容量
t := make([]byte, len(s), (cap(s)+1)*2)
copy(t, s)
s = t
複製程式碼

對映型別

var m map[string]int
m = make(map[string]int)
m["key"] = 42

// 刪除某個鍵
delete(m, "key")

// 測試該鍵對應的值是否存在
elem, has_value := m["key"]

// map literal
var m = map[string]Vertex{
    "Bell Labs": {40.68433, -74.39967},
    "Google":    {37.42202, -122.08408},
}
複製程式碼

Struct & Interface: 結構體與介面

Struct: 結構體

Go 語言中並不存在類的概念,只有結構體,結構體可以看做屬性的集合,同時可以為其定義方法。

// 宣告結構體
type Vertex struct {
    // 結構體的屬性,同樣遵循大寫匯出,小寫私有的原則
    X, Y int
    z bool
}

// 也可以宣告隱式結構體
point := struct {
	X, Y int
}{1, 2}

// 建立結構體例項
var v = Vertex{1, 2}

// 讀取或者設定屬性
v.X = 4;

// 顯示宣告鍵
var v = Vertex{X: 1, Y: 2}

// 宣告陣列
var v = []Vertex{{1,2},{5,2},{5,5}}
複製程式碼

方法的宣告也非常簡潔,只需要在 func 關鍵字與函式名之間宣告結構體指標即可,該結構體會在不同的方法間進行復制:

func (v Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

// Call method
v.Abs()
複製程式碼

對於那些需要修改當前結構體物件的方法,則需要傳入指標:

func (v *Vertex) add(n float64) {
    v.X += n
    v.Y += n
}
複製程式碼
var p *Person = new(Person) // pointer of type Person
複製程式碼

Pointer: 指標

// p 是 Vertex 型別
p := Vertex{1, 2}  

// q 是指向 Vertex 的指標
q := &p

// r 同樣是指向 Vertex 物件的指標
r := &Vertex{1, 2}

// 指向 Vertex 結構體物件的指標型別為 *Vertex
var s *Vertex = new(Vertex)
複製程式碼

Interface: 介面

Go 允許我們通過定義介面的方式來實現多型性:

// 介面宣告
type Awesomizer interface {
    Awesomize() string
}

// 結構體並不需要顯式實現介面
type Foo struct {}

// 而是通過實現所有介面規定的方法的方式,來實現介面
func (foo Foo) Awesomize() string {
    return "Awesome!"
}
複製程式碼
type Shape interface {
   area() float64
}

func getArea(shape Shape) float64 {
   return shape.area()
}

type Circle struct {
   x,y,radius float64
}

type Rectangle struct {
   width, height float64
}

func(circle Circle) area() float64 {
   return math.Pi * circle.radius * circle.radius
}

func(rect Rectangle) area() float64 {
   return rect.width * rect.height
}

func main() {
   circle := Circle{x:0,y:0,radius:5}
   rectangle := Rectangle {width:10, height:5}

   fmt.Printf("Circle area: %f\n",getArea(circle))
   fmt.Printf("Rectangle area: %f\n",getArea(rectangle))
}
//Circle area: 78.539816
//Rectangle area: 50.000000
複製程式碼

慣用的思路是先定義介面,再定義實現,最後定義使用的方法:

package animals

type Animal interface {
	Speaks() string
}

// implementation of Animal
type Dog struct{}
func (a Dog) Speaks() string { return "woof" }

/** 在需要的地方直接引用 **/

package circus

import "animals"

func Perform(a animal.Animal) { return a.Speaks() }
複製程式碼

Go 也為我們提供了另一種介面的實現方案,我們可以不在具體的實現處定義介面,而是在需要用到該介面的地方,該模式為:

func funcName(a INTERFACETYPE) CONCRETETYPE
複製程式碼

定義介面:

package animals

type Dog struct{}
func (a Dog) Speaks() string { return "woof" }

/** 在需要使用實現的地方定義介面 **/
package circus

type Speaker interface {
	Speaks() string
}

func Perform(a Speaker) { return a.Speaks() }
複製程式碼

Embedding

Go 語言中並沒有子類繼承這樣的概念,而是通過嵌入(Embedding)的方式來實現類或者介面的組合。

// ReadWriter 的實現需要同時滿足 Reader 與 Writer
type ReadWriter interface {
    Reader
    Writer
}

// Server 暴露了所有 Logger 結構體的方法
type Server struct {
    Host string
    Port int
    *log.Logger
}

// 初始化方式並未受影響
server := &Server{"localhost", 80, log.New(...)}

// 卻可以直接呼叫內嵌結構體的方法,等價於 server.Logger.Log(...)
server.Log(...)

// 內嵌結構體的名詞即是型別名
var logger *log.Logger = server.Logger
複製程式碼

併發程式設計

Goroutines

Goroutines 是輕量級的執行緒,可以參考併發程式設計導論一文中的程式、執行緒與協程的討論;Go 為我們提供了非常便捷的 Goroutines 語法:

// 普通函式
func doStuff(s string) {
}

func main() {
    // 使用命名函式建立 Goroutine
    go doStuff("foobar")

    // 使用匿名內部函式建立 Goroutine
    go func (x int) {
        // function body goes here
    }(42)
}
複製程式碼

Channels

通道(Channel)是帶有型別的管道,可以用於在不同的 Goroutine 之間傳遞訊息,其基礎操作如下:

// 建立型別為 int 的通道
ch := make(chan int)

// 向通道中傳送值
ch <- 42

// 從通道中獲取值
v := <-ch

// 讀取,並且判斷其是否關閉
v, ok := <-ch

// 讀取通道,直至其關閉
for i := range ch {
    fmt.Println(i)
}
複製程式碼

譬如我們可以在主執行緒中等待來自 Goroutine 的訊息,並且輸出:

// 建立通道
messages := make(chan string)

// 執行 Goroutine
go func() { messages <- "ping" }()

// 阻塞,並且等待訊息
msg := <-messages

// 使用通道進行併發地計算,並且阻塞等待結果
c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c // 從 c 中接收
複製程式碼

如上建立的是無緩衝型通道(Non-buffered Channels),其是阻塞型通道;當沒有值時讀取方會持續阻塞,而寫入方則是在無讀取時阻塞。我們可以建立緩衝型通道(Buffered Channel),其讀取方在通道被寫滿前都不會被阻塞:

ch := make(chan int, 100)

// 傳送方也可以主動關閉通道
close(ch)
複製程式碼

Channel 同樣可以作為函式引數,並且我們可以顯式宣告其是用於傳送資訊還是接收資訊,從而增加程式的型別安全度:

// ping 函式用於傳送資訊
func ping(pings chan<- string, msg string) {
    pings <- msg
}

// pong 函式用於從某個通道中接收資訊,然後傳送到另一個通道中
func pong(pings <-chan string, pongs chan<- string) {
    msg := <-pings
    pongs <- msg
}

func main() {
    pings := make(chan string, 1)
    pongs := make(chan string, 1)
    ping(pings, "passed message")
    pong(pings, pongs)
    fmt.Println(<-pongs)
}
複製程式碼

同步

同步,是併發程式設計中的常見需求,這裡我們可以使用 Channel 的阻塞特性來實現 Goroutine 之間的同步:

func worker(done chan bool) {
    time.Sleep(time.Second)
    done <- true
}

func main() {
    done := make(chan bool, 1)
    go worker(done)

	// 阻塞直到接收到訊息
    <-done
}
複製程式碼

Go 還為我們提供了 select 關鍵字,用於等待多個通道的執行結果:

// 建立兩個通道
c1 := make(chan string)
c2 := make(chan string)

// 每個通道會以不同時延輸出不同值
go func() {
	time.Sleep(1 * time.Second)
	c1 <- "one"
}()
go func() {
	time.Sleep(2 * time.Second)
	c2 <- "two"
}()

// 使用 select 來同時等待兩個通道的執行結果
for i := 0; i < 2; i++ {
	select {
	case msg1 := <-c1:
		fmt.Println("received", msg1)
	case msg2 := <-c2:
		fmt.Println("received", msg2)
	}
}
複製程式碼

Web 程式設計

HTTP Server

package main

import (
    "fmt"
    "net/http"
)

// define a type for the response
type Hello struct{}

// let that type implement the ServeHTTP method (defined in interface http.Handler)
func (h Hello) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "Hello!")
}

func main() {
    var h Hello
    http.ListenAndServe("localhost:4000", h)
}

// Here's the method signature of http.ServeHTTP:
// type Handler interface {
//     ServeHTTP(w http.ResponseWriter, r *http.Request)
// }
複製程式碼

Beego

利用 Beego 官方推薦的 bee 命令列工具,我們可以快速建立 Beego 專案,其目錄組織方式如下:

quickstart
├── conf
│   └── app.conf
├── controllers
│   └── default.go
├── main.go
├── models
├── routers
│   └── router.go
├── static
│   ├── css
│   ├── img
│   └── js
├── tests
│   └── default_test.go
└── views
    └── index.tpl
複製程式碼

在 main.go 檔案中,我們可以啟動 Beego 例項,並且呼叫路由的初始化配置檔案:

package main

import (
        _ "quickstart/routers"
        "github.com/astaxie/beego"
)

func main() {
        beego.Run()
}
複製程式碼

而在路由的初始化函式中,我們會宣告各個路由與控制器之間的對映關係:

package routers

import (
        "quickstart/controllers"
        "github.com/astaxie/beego"
)

func init() {
        beego.Router("/", &controllers.MainController{})
}
複製程式碼

也可以手動指定 Beego 專案中的靜態資源對映:

beego.SetStaticPath("/down1", "download1")
beego.SetStaticPath("/down2", "download2")
複製程式碼

在具體的控制器中,可以設定返回資料,或者關聯的模板名:

package controllers

import (
        "github.com/astaxie/beego"
)

type MainController struct {
        beego.Controller
}

func (this *MainController) Get() {
        this.Data["Website"] = "beego.me"
        this.Data["Email"] = "astaxie@gmail.com"
        this.TplNames = "index.tpl" // version 1.6 use this.TplName = "index.tpl"
}
複製程式碼

DevPractics: 開發實踐

檔案讀寫

import (
    "io/ioutil"
)
...
datFile1, errFile1 := ioutil.ReadFile("file1")
if errFile1 != nil {
	panic(errFile1)
}
...
複製程式碼

測試

VSCode 可以為函式自動生成基礎測試用例,並且提供了方便的用例執行與除錯的功能。

/** 交換函式 */
func swap(x *int, y *int) {
	x, y = y, x
}

/** 自動生成的測試函式 */
func Test_swap(t *testing.T) {
	type args struct {
		x *int
		y *int
	}
	tests := []struct {
		name string
		args args
	}{
		// TODO: Add test cases.
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			swap(tt.args.x, tt.args.y)
		})
	}
}
複製程式碼

相關文章