Software engineering notes

Go Basics

Declare

var t *T = new(T)   // t := new(T)
var a uint64 = 22   // a := uint64(22)
a := 0x12           // 18

Multiple variables

ff, xx := 3, "cc"

[]interface{}

hMap := make(map[string]interface{})
hMap["ID"] = "06"
hMap["Info"] = map[string]string{
    "Name":       "Jack"
}

resque2 := map[string]interface{}{
    "class": "hnap",
    "args":  []interface{}{hMap},
}

map[string]interface{}

x := map[string]interface{}{
    "foo": []string{"a","b"},
    "bar": "foo",
    "baz": 10.4,
}

map[string]interface{}

t := map[string]interface{}{}
t["id"] = 312
t["type"] = "realtime"
t["data"] = []map[string]string{
    {
        "did":    "did1",
        "action": "action1",
    },
    {
        "did":    "did2",
        "action": "action2",
    },
}

但不可以, 無法這樣給值
var t map[string]interface{}
t["xxx"] = "xxx"

map[string]map[string]string

elements := map[string]map[string]string{
    "A": map[string]string{
        "field1": "val1",
        "field2": "val2",
    },
    "B": map[string]string{
        "field1": "val1",
        "field2": "val2",
    },
}

[]struct

type Target struct {
    Topic   string `json:"topic"`
    Message string `json:"message"`
}
type Payload struct {
    Targets []Target `json:"targets"`
    From    string   `json:"from"`
}

var payload = Payload{
    Targets: []Target{
        Target{
            Topic:   "topic1",
            Message: "message1",
        },
        Target{
            Topic:   "topic2",
            Message: "message2",
        },
    },
    From: "api",
}

Declare empty slice - var vs :=

宣告出來為 nil, 長度 0 (建議)

var q []string

宣告出來為 [], 長度 0, 如果要 json encode 想避免生成 null 的話, 建議使用這個

q := []string{}

const 對應 int (有點像 enum)

const (
    LevelCritical = iota    // 0
    LevelError
    LevelWarning
    LevelNotice
    LevelInfo               // 4
    LevelDebug
)

Pointer

Example :

package main
import "fmt"
func main() {
    var a int = 1
    var b *int = &a
    var c **int = &b
    var x int = *b
    fmt.Println("a = ",a)                           // 1
    fmt.Println("&a = ",&a)                         // &a =  0xf840037100
    fmt.Println("*&a = ",*&a)                       // *&a =  1
    fmt.Println("b = ",b)                           // b =  0xf840037100
    fmt.Println("&b = ",&b)                         // &b =  0xf840037108
    fmt.Println("*&b = ",*&b)                       // *&b =  0xf840037100
    fmt.Println("*b = ",*b)                         // *b =  1
    fmt.Println("c = ",c)                           // c =  0xf840037108
    fmt.Println("*c = ",*c)                         // *c =  0xf840037100
    fmt.Println("&c = ",&c)                         // &c =  0xf840037110
    fmt.Println("*&c = ",*&c)                       // *&c =  0xf840037108
    fmt.Println("**c = ",**c)                       // **c =  1
    fmt.Println("***&*&*&*&c = ",***&*&*&*&*&c)     // ***&*&*&*&c =  1
    fmt.Println("x = ",x)                           // x =  1
}

什麼時候用指標?

  1. When calling struct’s function by value, it copy itself. If struct is big, use pointer.
  2. If function has to changed its struct’s value outside, use pointer.
  3. Make code consistent (if call by value or reference mixed up), use pointer.

指標行為

code :

func main() {
    a := QQ{}
    b := &a
    c := *b

    fmt.Printf("a: %p\n", &a)
    fmt.Printf("b: %p (address same as a) \n", b)
    fmt.Printf("c: %p (different address from a, b)\n", &c)

    d := xx()
    fmt.Printf("d: %p (different address from x)\n", &d)

    e := zz()
    fmt.Printf("e: %p (different address from z)\n", &e)
}

func xx() QQ {
    x := QQ{}
    fmt.Printf("x: %p (return instance)\n", &x)
    return x
}

func zz() *QQ {
    z := QQ{}
    fmt.Printf("z: %p (return pointer)\n", &z)
    return &z
}

如果 struct 定義沒有欄位, 結果 :

type QQ struct {}

a: 0x1127a88
b: 0x1127a88 (address same as a)
c: 0x1127a88 (different address from a, b)
x: 0x1127a88 (return instance)
d: 0x1127a88 (different address from x)
z: 0x1127a88 (return pointer)
e: 0xc42000c030 (different address from z)

如果 struct 定義有欄位, 結果 :

type QQ struct {
    Name string
}

a: 0xc42000e290
b: 0xc42000e290 (address same as a)
c: 0xc42000e2a0 (different address from a, b)
x: 0xc42000e2e0 (return instance)
d: 0xc42000e2d0 (different address from x)
z: 0xc42000e2f0 (return pointer)
e: 0xc42000c030 (different address from z)

關於回傳 instance or pointer 的記憶體 : call func 拿到的都是新的記憶體, 不管 func 裡面回傳的是不是指標

dereference pointer

Error invalid operation: cannot index this.small (variable of type *[]int)

var small *[]int
value := this.small[0]              // doesn't work
value := (*this.small)[0]           // work

swtich

map + switch

m := map[string]int{"foo":1}
f := func(key string) bool { _, ok := m[key]; return ok }
switch {
    case f(key):
        // whatever

or

switch category {
case
    "auto",
    "news",
    "sport",
    "music":
    return true
}

斷言 (type assertion)

分辨型別

var anything interface{} = "string"
switch v := anything.(type) {
case string:
    fmt.Println(v)
case int32, int64:
    fmt.Println(v)
case interface{}:
    fmt.Println(v)
default:
    fmt.Println("unknown")
}

已知道是什麼型別

value, ok := a.(string)
if !ok {
    fmt.Println("It's not ok for type string")
    return
}

// or
if str, ok := a.(string); ok {

轉型

int to int64

i64 := int64(23)

int to float64

i := 5;
f := float64(i)

int to string

s := strconv.Itoa(123)

int64 to int

i := int64(5)
int(i)

int64 to string

s := strconv.FormatInt(int64(5), 10)

int64 to float64

float64(1)

int64 to uint64

u, err := strconv.ParseUint(strconv.FormatInt(int64(123), 10), 10, 64)

float64 to string

s64 := strconv.FormatFloat(v, 'E', -1, 64)

float64 to int64

var f float64 = 55.3
i = int64(f)

float64 to uint

uint(user["age"].(float64))

string to byte

dd := "dcf"
fmt.Println([]byte(dd))

result : [100 99 102]

string to int

v, err = strconv.Atoi(s)

string to int64

v, err := strconv.ParseInt(s, 10, 64)

string to uint32/uint64

v := "42"
if s, err := strconv.ParseUint(v, 10, 32); err == nil {
    fmt.Printf("%T, %v\n", s, s)
}
if s, err := strconv.ParseUint(v, 10, 64); err == nil {
    fmt.Printf("%T, %v\n", s, s)
}

string to float64

v, err := strconv.ParseFloat("55.74", 64)

*string to string

value := *pointer_str

byte to string

s := string(byteArray)
s := string(byteArray[:])

rune to string

r := rune('a')
fmt.Println(reflect.TypeOf(r))  // int32
fmt.Println(r, string(r))       // 97 a

array to slice

x[:]

interface{} to int

a := job["retryTimes"].(int)

[]interface{} 轉成 interface{}

interface{}([]interface{}{reactor, sensor})

[]interface{} to []int ref : InterfaceSlice

var dataSlice []int = foo()
var interfaceSlice []interface{} = make([]interface{}, len(dataSlice))
for i, d := range dataSlice {
    interfaceSlice[i] = d  // 或用 append 的方式
}

interface{} conver to bytes.Buffer

switch v := any.(type) {
case bytes.Buffer:          // return as is
    return v.String()       // Here v is of type bytes.Buffer
}

interfacer{} (*Server) to (*Server)

type Server struct {
    Name string
}

var v interface{}
if v == nil {
    v = &Server{Name: "xx"}
}
fmt.Println(reflect.TypeOf(v))      // *main.Server
s := v.(*Server)
fmt.Println(s.Name)                 // xx

map[string]interface{} 變成陣列 []interface{}

[]interface{}{reactor, sensor}

struct to another struct

type A struct{ Name string }
type B struct{ Name string }
a := A{"aa"}
b := B(a)
b.Name = "bb"
fmt.Println(a)
fmt.Println(b)

bytes to io.Reader

bytes.NewReader(b)

bytes.Buffer to io.Writer

var b bytes.Buffer
writer := bufio.NewWriter(&b)

strings to io.Reader

strings.NewReader(s)

file to bytes

b, err := ioutil.ReadFile("/tmp/ff.tmp")

file to io.Writer

file, err = os.Open("/tmp/ff.tmp")
defer file.Close()

image to bytes

buf := new(bytes.Buffer)
err := jpeg.Encode(buf, new_image, nil)
send_s3 := buf.Bytes()

convert to another interface

dimg, ok := img.(draw.Image)

指標(pointer)轉實體

*f is msg := sqs.Message(*m)

(*f)["cc"]

(map[string]interface{})(*f)["cc"]

msg := sqs.Message(*m)

a := (map[string]interface{})(args[0].(interface{}).(map[string]interface{}))

loop

Infinite

for {
    // ...
}

Range, like other languages

for i:=1; i<=5; i++ {
    // ...
}

Until the specific time

for time.Now().Unix() < 1481089195 {
    time.Sleep(1 * time.Second)
}

slice

slice結構

結構

     []byte
     ---------
ptr  | *elem |
     ---------
len  |  int  |
     ---------
cap  |  int  |
     ---------

a slice with five elements

s := make([]byte, 5)  // 指定 len, 不指定 cap

     []byte
     ---------
ptr  |       |  -> [5] bytes | 0 | 0 | 0 | 0 | 0 |
     ---------
len  |  5    |
     ---------
cap  |  5    |
     ---------

nil slice

var s []byte

     []byte
     ---------
ptr  |  nil  |
     ---------
len  |   0   |
     ---------
cap  |   0   |
     ---------

cap and len

The slice abstraction in Go will resize the underlying array for you.

Example:

s := make([]int, 0, 3)
for i := 0; i < 5; i++ {
    s = append(s, i)
    fmt.Printf("cap %v, len %v, %p\n", cap(s), len(s), s)
}

output:

cap 3, len 1, 0xc000088000
cap 3, len 2, 0xc000088000
cap 3, len 3, 0xc000088000
cap 6, len 4, 0xc00006a030
cap 6, len 5, 0xc00006a030

The difference in performance between A and B.

Code

// var numbers []int
// numbers := make([]int, 5)
for i := 0; i < 5; i++ {
    numbers = append(numbers, i)
    fmt.Printf("address: %p, length: %d, capacity: %d, items: %v\n", numbers, len(numbers), cap(numbers), numbers)
}

var numbers []int:

2019-09-30 12:58:34 jack@jack-lin /tmp $ go run slice.go
address: 0xc0000140c0, length: 1, capacity: 1, items: [0]
address: 0xc0000140f0, length: 2, capacity: 2, items: [0 1]
address: 0xc000094000, length: 3, capacity: 4, items: [0 1 2]
address: 0xc000094000, length: 4, capacity: 4, items: [0 1 2 3]
address: 0xc00009a000, length: 5, capacity: 8, items: [0 1 2 3 4]

numbers := make([]int, 5):

2019-09-30 12:58:44 jack@jack-lin /tmp $ go run slice.go
address: 0xc000096000, length: 6, capacity: 10, items: [0 0 0 0 0 0]
address: 0xc000096000, length: 7, capacity: 10, items: [0 0 0 0 0 0 1]
address: 0xc000096000, length: 8, capacity: 10, items: [0 0 0 0 0 0 1 2]
address: 0xc000096000, length: 9, capacity: 10, items: [0 0 0 0 0 0 1 2 3]
address: 0xc000096000, length: 10, capacity: 10, items: [0 0 0 0 0 0 1 2 3 4]

基本操作

append item

list = append(list, item)

append slice

list = append(list, list2...)

其他

Remove an item with a specific index from an array

slice = append(slice[:index], slice[index+1:]...)

重新切一個 slice, 新 slice 會使用原本 slice 的底層, 改成使用 copy 才會是一個新的 slice

func main() {
    data := get()
    fmt.Println(len(data), cap(data), &data[0])
}

func get() []byte {
    raw := make([]byte, 10000)
    fmt.Println(len(raw), cap(raw), &raw[0])
    return raw[:3]
}
// 10000 10000 0xc42005e000
// 3 10000 0xc42005e000     (記憶體位置一樣)

func get() (res []byte) {
    raw := make([]byte, 10000)
    fmt.Println(len(raw), cap(raw), &raw[0])
    res = make([]byte, 3)
    copy(res, raw[:3])
    return
}
// 10000 10000 0xc42005e000
// 3 3 0xc42000e280

array vs slice

差別主要是 array 是有固定長度, slice 沒有

code:

// array
var List = [2]int{1, 2}  // or [...]int{1,2}
copyList := List
copyList[1] = 4
fmt.Printf("new=%v, old=%v\n", copyList, List)

// slice
var oldSlice = []int{1, 2, 3, 4}
newSlice := oldSlice
newSlice[3] = 10
newSlice = append(newSlice, 100)
fmt.Printf("new=%v, old=%v\n", newSlice, oldSlice)

result:

new=[1 4], old=[1 2]
new=[1 2 3 10 100], old=[1 2 3 10]

array 是直接操作記憶體位置

傳遞 slice 是傳遞記憶體位置

感謝 chris:

Example

func main() {
    d := []string{"xxx"}
    fmt.Println("original:", d)
    nonPointer(d)
    fmt.Println("non-pointer:", d)
    change(d)
    fmt.Println("change:", d)
    pointer(&d)
    fmt.Println("pointer:", d)
}

func nonPointer(d []string) {
    d = append(d, "vvvv")
}

func change(d []string) {
    d[0] = "ccc"                // 可修改到外部
}

func pointer(d *[]string) {
    *d = append(*d, "vvvv")
}

Result

original: [xxx]
non-pointer: [xxx]
change: [ccc]
pointer: [ccc vvvv]

Example 2

func main() {
    d := [][]string{
        []string{"foo", "bar"},
        []string{"foo1", "bar1"},
    }
    byValue(d)
    fmt.Println(d) // [[foo bar] [foo1 baz1]]
    byRef(&d)
    fmt.Println(&d) // &[[foo bar] [foo1 baz2]]
}

func byValue(d [][]string) {
    d[1][1] = "baz1"
    fmt.Println(d) // [[foo bar] [foo1 baz1]]
}

func byRef(d *[][]string) {
    (*d)[1][1] = "baz2"
    fmt.Println(d) // &[[foo bar] [foo1 baz2]]
}

Array is passed by value by default, not like slice by reference by default

func main() {
    d := [2]string{"foo", "bar"}
    byValue(d)
    fmt.Println(d) // foo, bar
    byRef(&d)
    fmt.Println(&d) // foo, baz
}

func byValue(d [2]string) {
    d[1] = "baz"
    fmt.Println(d) // foo, baz
}

func byRef(d *[2]string) {
    d[1] = "baz"
    fmt.Println(d) // foo, baz
}

宣告獨立乾淨的 slice

不論是參數傳遞 (call by value) 還是 assign 給一個新的變數, *elem (pointer) 都會指向同一個, 而改其中一個也會改動到另一個

如果沒有 append 新的 item, 只改變原本 slice 長度內的值 e.g. s[2] = 3 (如果s總長度是5), 都會一直是共用指標

如果要打破這個規則, 就將其中一個 append 新的 item 那麼 *elem (pointer) 就會是不同的

如果要將 slice 指給一個新的變數, 但又不想要將原本的 *elem (pointer) 參照到原本的變數, 除了用 make 也可以 new 全新的再用 append 給值

code

s := []int{1, 2, 3}

// Old *elem
s1 := s

// New *elem
s2 := s
s2 = append(s2, 4)

// New *elem
s3 := []int{}
s3 = append(s3, s...)

// New *elem
s4 := make([]int, 0)
s4 = append(s4, s...)

s[1] = 999

fmt.Println(s)      // [1 999 3]
fmt.Println(s1)     // [1 999 3]
fmt.Println(s2)     // [1 2 3 4]
fmt.Println(s3)     // [1 2 3]
fmt.Println(s4)     // [1 2 3]

check if two slices are the same

reflect.DeepEqual(tmp, item.ans)

map others

傳入接收 actionData map[string]interface{} 參數的 func

send_var(jobData["ActionData"].(map[string]interface{}))

func send_var(actionData []interface{}) (err error) {
    fmt.Println(actionData[0].(map[string]interface{})["Did"])

[]map[string]interface{} 傳入型態接受 []interface{} 的 func

test(&map[string]interface{})
...
func test(rec *[]interface{})

用指標方式傳入可避免型態轉換發生的問題

map[string]interface{} 一直輸出它的 index 順序不一定會一樣

qq := map[string]interface{}{
    "0": "111111",
    "1": "xxxxxxxx",
}

[]interface{} 一直輸出它的 index 順序會一樣

用 mi 定義 type, 讓 code 更乾淨

type mi map[string]interface{}
res["Envelope"].(interface{}).(mi)["Body"].(interface{}).(mi)[actionData["Name"].(string)+"Response"].(interface{})

assignment to entry in nil map : 會發生此原因是你在賦值給 map 時沒有初始化

var d []map[string]interface{}

(Wrong)
    var f map[string][]map[string]interface{}
    f["ss"] = d

(Correct)
    f := map[string][]map[string]interface{}{}
    f["ss"] = d

(Correct)
    var f map[string][]map[string]interface{}
    f = make(map[string][]map[string]interface{})
    f["ss"] = d

map 是否為空

if len(map) == 0 {
    ....
}

判斷 key 是否存在

a := map[string]interface{}{
    "a": "A",
}

if t1, matched := a["a"]; matched {
    fmt.Println(t1.(string))
}

if t2 := a["a"]; t2 != nil {
    fmt.Println(t2.(string))
}

判斷 key 及型態

a := map[string]interface{}{"fff": "xx"}
ff, ok := a["fff"].(int)   // 即使來源跟對象的型態不一樣, 不會造成 panic
if !ok {
    fmt.Println("no")
    // return
}
fmt.Println(ff)

// result
no
0

Slice Tricks

判斷 slice key 是否存在

a := []string{"zero", "one", "two"}
fmt.Println(a, len(a))
x, v := 3, "nothing"
if len(a)-1 >= x  {
    v = a[x]
}
fmt.Printf("%s", v)

type *map[string]interface {} does not support indexing

func tt(dd *map[string]interface{}) {
    (*dd)["qq"] = "qqqq"
}

傳遞 map 是傳址非傳值

func main() {
    dd := map[string]interface{}{
        "ff": "ffff",
    }
    fmt.Printf("original, mem: %p val: %v\n", &dd, dd)
    nonPointer(dd)
    fmt.Printf("non-pointer, val: %v\n", dd)
    change(dd)
    fmt.Printf("change, val: %v\n", dd)
    pointer(&dd)
    fmt.Printf("pointer, val: %v\n", dd)
    newMap(dd)
    fmt.Printf("newMap, val: %v\n", dd)
    makeMap(dd)
    fmt.Printf("makeMap, val: %v\n", dd)
}

// 會修改到外部
func nonPointer(dd map[string]interface{}) {
    fmt.Printf("non-pointer, mem: %p\n", &dd)
    dd["dd"] = "ddd"
}

// 會修改到外部
func change(dd map[string]interface{}) {
    fmt.Printf("non-pointer, mem: %p\n", &dd)
    dd["ff"] = "cccc"
}

// 會修改到外部
func pointer(dd *map[string]interface{}) {
    fmt.Printf("pointer, mem: %p\n", &dd)
    (*dd)["qq"] = "qqqq"
}

// 即使 qq 裡, new 一個新變數 ff, 並將 dd 的值給 ff, 修改仍會改到外面傳進來的 map
func newMap(dd map[string]interface{}) {
    ff := dd
    ff["ff"] = "zzzz"
}

// 必須要用 make new 一個實體, 才不會參照到原本的記憶體位址
func makeMap(dd map[string]interface{}) {
    cc := make(map[string]interface{})
    for k, v := range dd {
        cc[k] = v
    }
    cc["bbb"] = "bbb"
}

結果

original, mem: 0xc42000c028 val: map[ff:ffff]
non-pointer, mem: 0xc42000c038
non-pointer, val: map[ff:ffff dd:ddd]
non-pointer, mem: 0xc42000c040
change, val: map[ff:cccc dd:ddd]
pointer, mem: 0xc42000c048
pointer, val: map[ff:cccc dd:ddd qq:qqqq]
newMap, val: map[dd:ddd qq:qqqq ff:zzzz]
makeMap, val: map[dd:ddd qq:qqqq ff:zzzz]

func params

傳入/接收 map pointer

error: (type *map[string]interface {} does not support indexing)

solution:

func main() {
    a := map[string]interface{}{
        "xxx": "XXX",
    }

    qq(&a)
}

func qq(tmp *map[string]interface{}) {
    fmt.Println((*tmp)["xxx"])
}

傳入/接收 map pointer

var args = map[string]interface{}{
    "A": "1",
    "B": "2",
}
foo(&args)
func foo(args *map[string]interface{}) {
    options := (map[string]interface{})(*args)
    fmt.Println(options["A"])
}

foo(args)
func foo(name string, ...args map[string]interface{}) {
    options := (map[string]interface{})(args[0])
    fmt.Println(options["A"])
}

傳入 Optional 參數

T1("A", "B")
func T1(str ...string) {
    fmt.Println(str[0]) // A
}

T2([]string{"A", "B"})
func T2(str ...[]string) {
    fmt.Println(str[0]) // [A B]
}

map

test("test")
test("test", map[string]interface{}{"dd": "DD", "cc": "CC"})

func test(required string, optional ...map[string]interface{}) {
    fmt.Println(required)
    fmt.Println(len(optional))
    if len(optional) > 0 {
        if val, existed := optional[0]["dd"]; existed {
            fmt.Println(val)
        }
    }
}

將 slice params 傳入 args…

func echo(strings ...string) {
    for _, s := range strings {
        fmt.Println(s)
    }
}

func main() {
    strings := []string{"a", "b", "c"}
    echo(strings...) // Treat input to function as variadic
}

interface & struct

Behaviours

scenario                        by value    by reference
pointer method                      yes         yes
value method                        yes         yes
interface pointer method            no          yes
interface value method              yes         yes

ref:

[struct] 成員大小寫

跟 func 一樣,大寫代表 public,小寫是 private

[struct] map

type AWS struct {
   SQS  map[string]*aws_sqs.SQS
}

var qq AWS
qq.SQS = make(map[string]*aws_sqs.SQS)
qq.SQS[xx] = &ws_sqs.SQS{ ... }

[struct] function pointer

type Job struct {
    Done func() error
}

var job Job
job.Done = done

func done() error {

}

[struct] 定義 + 賦值

resp := Music{
    Genre: struct {
        Country string
        Rock    string
    }{
        Country: "Taylor Swift",
        Rock:    "Aimee",
    },
}

Assign a struct to an interface

type Interface interface{}

type Struct struct{}

func main() {
        var ps *Struct
        var pi *Interface
        pi = new(Interface)
        *pi = ps

        _, _ = pi, ps
}

關於 interface 及 struct 的用法

package main

import "fmt"

# 用 interface 定義一個抽象層,只負責說這個類別有什麼動作
type PeopleAction interface {
    AnimalAction
    Stand()
}

# 本身也可以被繼承
type AnimalAction interface {
    Run()
    Eat()
}

# 定義資料結構
type Animal struct {
    Name string
}

func (animal *Animal) Run() {
    fmt.Println(animal.Name + " is running")
}

func (animal *Animal) Eat() {
    fmt.Println(animal.Name + " is eatting")
}

func (animal *Animal) Stand() {
    fmt.Println(animal.Name + " is standing")
}

func main() {
    var jack PeopleAction = &Animal{"Jack"}
    var bob AnimalAction = &Animal{"Bob"}
    jack.Run()               // Jack is running
    jack.Eat()               // Jack is eatting
    jack.Stand()             // Jack is standing
    bob.Run()               // Bob is running
    bob.Eat()               // Bob is eatting
    // bob.Stand()          // bob.Stand()  Error : bob.Stand undefined (type AnimalAction has no field or method Stand)
}

Receive interface as param

type A interface{}

type B struct {
    A
    Age int
}

func main() {
    var a A
    fmt.Println("interface A type: ", reflect.TypeOf(a))
    b := B{a, 30}
    fmt.Println("struct B type: ", reflect.TypeOf(b))
    Show(b)
}

func Show(a A) {
    fmt.Println("show age: ", a.(B).Age)
    fmt.Println("interface param type: ", reflect.TypeOf(a.(B)))
}

Result

interface A type:  <nil>
struct B type:  main.B
show age:  30
interface param type:  main.B

Type T satisfies interface I defined in the first snippet. Values of type T can be f.ex. passed to any function accepting I as a parameter

type I interface {
    M() string
}
type T struct {
    name string
}
func (t T) M() string {
    return t.name
}
func Hello(i I) {
    fmt.Printf("Hi, my name is %s\n", i.M())
}
func main() {
    Hello(T{name: "Michał"}) // "Hi, my name is Michał"
}

single type can implement many interfaces

type I1 interface {
    M1()
}
type I2 interface {
    M2()
}
type T struct{}
func (T) M1() { fmt.Println("T.M1") }
func (T) M2() { fmt.Println("T.M2") }
func f1(i I1) { i.M1() }
func f2(i I2) { i.M2() }
func main() {
    t := T{}
    f1(t) // "T.M1"
    f2(t) // "T.M2"
}

different structs, call same name but different func

type I interface {
    M() string
}
type T1 struct{ Name string }
type T2 struct{ Name string }

func main() {
    // Different structs call its own func
    var i I

    t1 := T1{Name: "t1"}
    t2 := T2{Name: "t2"}
    i = &t1 // Must use pointer
    fmt.Println(i.M())
    i = &t2
    fmt.Println(i.M())
}

func (t *T1) M() string { return t.Name + " T1" }
func (t *T2) M() string { return t.Name + " T2" }

same interface, different structs, call same func

No, you can’t!

Difference between passing by reference and passing by value

var a = &A{Val: "aaa"}
fmt.Println("a: ", a)
fmt.Println("b = a")
b := a
b.Val = "bbb"
fmt.Println("b: ", b, " (set new value to b)")
fmt.Println("a: ", a, " (affected by b)")

var c = &A{Val: "ccc"}
fmt.Println("c: ", c)
fmt.Println("b = c")
b = c
b.Val = "bbbbbbbbbbb"
fmt.Println("b: ", b, " (set new value to b)")
fmt.Println("c: ", c, " (affected by b)")
fmt.Println("a: ", a, " (unaffected)")

Result:

a:  &{aaa}
b = a
b:  &{bbb}  (set new value to b)
a:  &{bbb}  (affected by b)
c:  &{ccc}
b = c
b:  &{bbbbbbbbbbb}  (set new value to b)
c:  &{bbbbbbbbbbb}  (affected by b)
a:  &{bbb}  (unaffected)

struct + json

json.Marshal(struct {
        Data     *MyStruct   `json:"data,omitempty"`
        Status   string      `json:"status,omitempty"`
        Reason   string      `json:"reason,omitempty"`
    }{
        Data:   &r.Data,
        Status: r.Status,
        Reason: r.Reason,
    })
}

Assign func

type Job struct {
    ID string
    Do func(*Job)
}

func main() {
    var j1 = &Job{ID: "J01", Do: do}
    var j2 = &Job{ID: "J02", Do: do}
    j1.Do(j1)
    j2.Do(j2)
}

func do(j *Job) {
    fmt.Println("Job ID:", j.ID)
}

巢狀

type Resp struct {
    UserID struct {
    } `json:"user_id"`
    Data struct {
        Info struct {
            Name        string   `json:"name"`
            Friends     []Friend `json:"friends"`
        } `json:"info"`
    } `json:"data"`
}

輸出

"user_id": {},
"data": {
    "info": {
        "name": "XXX",
        "friends": [
            { },
            { },
        ],
    },
},

傳遞參數不像 map 及 slice 以指標傳遞, 而是傳遞實體

func main() {
    q := qq{name: "ori"}
    fmt.Println(q)
    dd(q)
    fmt.Println(q)
    cc(&q)
    fmt.Println(q)
}

func dd(q qq) {
    q.name = "xx"
}

func cc(q *qq) {
    q.name = "zz"
}

result

{ori}
{ori}
{zz}

JSON tag 補充

// 接收跟輸出都會忽略
Field int `json:"-"`

// Field appears in JSON as key "myName" and the field is omitted from the object if its value is empty, as defined above.
Field int `json:"myName,omitempty"`

// Field appears in JSON as key "Field" (the default), but the field is skipped if empty.
Field int `json:",omitempty"`

type Ticket struct {
    Places []Place `json:"places"`  // json 傳過來時的 places 也要是陣列
    Name string `json:"name"`       // 單純接收字串
    UID string                      // 注意! 即使沒設定 tag, 如果傳進來是 "uid": "123" 這樣也會 map 得到
    FriendID string                 // 如果傳進來是 "friend_id": "123" 這樣 map 不到
}

Nested struct 即使有設 omitempty 也 json 仍會輸出 "field": {}, 解決方法是指定 pointerstruct

type A struct { Val  L2 `json:"val,omitempty"` }
type B struct { Val *L2 `json:"val,omitempty"` }        // pointer 是關鍵
type L2 struct { Val string `json:"val,omitempty"` }

json.Marshal 輸出 :

a := &A{Val: L2{Val: ""}}   // {"val":{}}
a := &A{}                   // {"val":{}}
a := &B{}                   // {}

避免空的 slice json marshal 後為 null

var users []map[string]interface{}
if users == nil {
    users = make([]map[string]interface{}, 0)
}

import

運作流程

當 import A 時, 會先執行 A 的 init(), 但如果 A 裡面還有 import B, 則會優先執行 B 的 init(),

當 package 的 init() 都執行完後, 才會執行 main 的 init(), 最後才會執行 main()

import 的幾種類型

  1. 正常引入

    import( “fmt” )

這是我們平常最常引入的方法

  1. 省略前綴的 package name

    import ( . “fmt” )

ex : fmt.Println("hello world") 可以省略寫成 Println("hello world")

  1. Alias

    import ( f “fmt” )

ex : f.Println("hello world")

  1. 不引用 package 內部函數

    import ( _ “fmt” )

當引入這個 package 只會執行它的 init()

套件管理

dep (官方)

Vendor

govendor,用起來挺直覺的,如果使用過 npm 或 gem 的經驗,它的概念是一樣的

govendor cheat sheet

第一次使用

govendor init               # 會產生 vendor/vendor.json, 但裡面是空的
govendor add +external      # 幫你把目前使用到 GOPATH 套件版本填到 vendor.json,也會產生 github.com, etc. 第三方套件

第 N 次使用, 更新 govendor.json

govendor remove +v          # 清空 `vendor.json` 及刪除 `github.com`, .. etc.
govendor add +external      # 再重加有使用到的套件

依據 vendor/vendor.json 使用到的套件從 local 的 GOPATH 加到 vendor/, 如果 local 沒有要先下 go get

govendor sync

如果 github 某個套件更新了, 想更新 vendor 這個套件

govendor fetch golang.org/x/net/context

Error: Remotes failed for

如果有個 local repo (private repo) 加不進 vendor/

$ govendor sync
Error: Remotes failed for:
        Failed for "relog" (failed to ping remote repo): unrecognized import path "relog"

直接 add 那個 local repo

govendor add relog

升級某一套件版本

govendor fetch github.com/satori/go.uuid

(不要採用, 僅保留)

  1. 先從 govendor 移除掉 govendor remove github.com/parnurzeal/gorequest
  2. 移除 local 的 github.com/parnurzeal/gorequest
  3. go get github.com/parnurzeal/gorequest
  4. govendor add +external 更新到 vendor.json

不管怎麼重 build, code 一直是舊的

有可能你將目前專案加到 vendor/ 下了, 把它刪掉, 再重編就可以了

Panic and Recover

You can only use recover() to catch panic. You can’t catch fmt.Fatal("something went wrong").

攔截並印出 error

如果不想因為程式發生 index out of range 或由 panic 擲出的 error 導致整個程序強制中止的話可以用 recover 來攔截, 另外有時候程式的 error message 不夠明確時, 用 recover 有時候可以得到較明確的訊息

defer func() {
    if e := recover(); e != nil {
        // e is the interface{} typed-value we passed to panic()
        fmt.Println("Whoops: ", e) // Prints "Whoops: boom!"
    }
}()
var qq []string
fmt.Println(qq[0]) // 或直接用 panic("boom!")
fmt.Println("This will never be called")

recover() 要寫在最前面,否則會無法攔截到

將 stack 裡的東西印出來

func main() {
    defer func() {
        if e := recover(); e != nil {
            for i := 1; ; i++ {
                _, file, line, ok := runtime.Caller(i)
                if !ok {
                    break
                }
                log.Println(fmt.Sprintf("%s:%d", file, line))
            }
        }
    }()
    qq() // :21
    log.Println("Done!")
}

func qq() {
    ff() // :26
}

func ff() {
    panic("ff") // :30
}

Result:

2017/09/20 19:16:22 /usr/local/go/src/runtime/asm_amd64.s:514
2017/09/20 19:16:22 /usr/local/go/src/runtime/panic.go:489
2017/09/20 19:16:22 /tmp/qq.go:30
2017/09/20 19:16:22 /tmp/qq.go:26
2017/09/20 19:16:22 /tmp/qq.go:21
2017/09/20 19:16:22 /usr/local/go/src/runtime/proc.go:185
2017/09/20 19:16:22 /usr/local/go/src/runtime/asm_amd64.s:2197

其他

tag

type User struct {
    _    struct{} `type:"structure"`
    name string   `json:"name-field"`
    age  int
}

func main() {
    user := &User{name: "John Doe The Fourth", age: 20}

    if field, ok := reflect.TypeOf(user).Elem().FieldByName("_"); ok {
        fmt.Println(field.Tag)
    } else {
        panic("Field not found")
    }

    if field, ok := reflect.TypeOf(user).Elem().FieldByName("name"); ok {
        fmt.Println(field.Tag)
    } else {
        panic("Field not found")
    }
}

結果:

type:"structure"
json:"name-field"

runtime

Pointer guildeline

盡量減少在其他子 package 用 var 定義全域變數

多使用全域來定義 struct, func, const,不得以才用 var 定義全域變數;如果有共用的 func 都會用到共同的變數,就用 struct 定義,否則容易產生 Memory Leak 的問題

go env

$ go env
GOARCH="amd64"
GOBIN=""
GOCHAR="6"
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOOS="darwin"
GOPATH=""
GORACE=""
GOROOT="/usr/local/go"
GOTOOLDIR="/usr/local/go/pkg/tool/darwin_amd64"
CC="gcc"
GOGCCFLAGS="-g -O2 -fPIC -m64 -pthread -fno-common"
CGO_ENABLED="1"

show variable type

import "reflect"
fmt.Println(reflect.TypeOf(tst))

判斷 interface{} 是否是 slice

if reflect.TypeOf(test["slice"]).Kind() == reflect.Slice {

}

debug

如同 PHP 的 print_r(), var_dump(), var_export(), 如果型態是 map 它也會把 key value 印出來

fmt.Printf("%v", whatever)

用上面的不會 struct 的欄位名稱,如果印出 struct 的欄位及值要用 :

fmt.Printf("%+v", whatever)

string literals are character sequences

string("Hello"[1])                  // ASCII only       e
string([]rune("Hello, 世界")[1])    // UTF-8            e
string([]rune("Hello, 世界")[8])    // UTF-8            界

指令

在 if 裡面 new 參數無法傳遞到外面

裡面有 new

var a bool
if true {
    a := true
    fmt.Println(a)
}
fmt.Println(a)

true
false

裡面沒 new

var a bool
if true {
    a = true
    fmt.Println(a)
}
fmt.Println(a)

true
true

MySQL NULL

null

string sql.NullString
int64 sql.NullInt64
float64 sql.NullFloat64

time 加上 tag

time.Time `sql:"default:null"`

或是用指標

string *string

for range 下在傳遞指標(pointer)需要注意

ii := []int{1, 2, 3}
dd := map[int]*int{}
dd2 := map[int]*int{}
for k, i := range ii {      // 不要使用 for range new 出來的 value, 因為最後都會指向同一個位置
    dd[k] = &i
}
for k, _ := range ii {      // 明確指定記憶體位址
    dd2[k] = &ii[k]
}
fmt.Println(dd)         // map[0:0x41602c 1:0x41602c 2:0x41602c]
fmt.Println(dd2)        // map[0:0x416020 1:0x416024 2:0x416028]

go mod

finding module for package
go mod tidy
go: finding module for package github.com/user_name/private_repo_name/package_name
test/controller imports
        github.com/user_name/private_repo_name/package_name: cannot find module providing package github.com/user_name/private_repo_name/package_name: module
 github.com/user_name/private_repo_name/package_name: git ls-remote -q origin in /Users/jacklin/go/pkg/mod/cache/vcs/697da452d33316e5203c4e98f8eb82049f3cb1b48b123194bc15
8a0a4d78af7e: exit status 128:
        fatal: could not read Username for 'https://github.com': terminal prompts disabled
Confirm the import path was entered correctly.
If this is a private repository, see https://golang.org/doc/faq#git_https for additional information.

The reason why these issues exist is using go module and GOPATH at the same time. Move all go projects out of GOPATH/src, use go module only

package ... is not in GOROOT

It might be caused by importing another module in the same file systems

You need to tell go modules where to file that module

go mod edit -replace example.com/greetings=../greetings
go mod tidy

go get private repo

Add

go env -w GOPRIVATE=github.com/user_name/pkg_name

.gitconfig

[url "https://user_name:{ACCESS TOKEN}@github.com"]
    insteadOf = https://github.com