1. 程式人生 > >Go程式設計基礎—程式碼規範

Go程式設計基礎—程式碼規範

專案目錄結構規範PROJECT_NAME

├── README.md 介紹軟體及文件入口
├── bin 編譯好的二進位制檔案,執行./build.sh自動生成,該目錄也用於程式打包
├── build.sh 自動編譯的指令碼
├── doc 該專案的文件
├── pack 打包後的程式放在此處
├── pack.sh 自動打包的指令碼,生成類似xxxx.20170713_14:45:35.tar.gz的檔案,放在pack檔案下
└── src 該專案的原始碼
    ├── main 專案主函式
    ├── models 專案程式碼
    ├── controllers 專案程式碼
    ├── research 在實現該專案中探究的一些程式
    └── vendor 存放go的庫
        ├── github.com
/xxx 第三方庫 └── xxx.com/obc 公司內部的公共庫

專案的目錄結構儘量做到簡明、層次清楚

檔名命名規範用小寫,儘量見名思義,看見檔名就可以知道這個檔案下的大概內容,對於原始碼裡的檔案,檔名要很好的代表了一個模組實現的功能。

命名規範包名,包名用小寫,使用短命名,儘量和標準庫不要衝突

介面名單個函式的介面名以”er”作為字尾,如Reader,Writer,介面的實現則去掉“er”

type Reader interface {
        Read(p []byte) (n int, err error)
}

兩個函式的介面名綜合兩個函式名

type
WriteFlusher interface { Write([]byte) (int, error) Flush() error }

三個以上函式的介面名,類似於結構體名

type Car interface {
    Start([]byte) 
    Stop() error
    Recover()
}

全域性變數:採用駝峰命名法,僅限在包內的全域性變數,包外引用需要寫介面,提供呼叫
區域性變數:駝峰式,小寫字母開頭
常量常量:大寫,採用下劃線
**import 規範**import在多行的情況下,goimports會自動幫你格式化,在一個檔案裡面引入了一個package,建議採用如下格式:

import (
    "fmt"
)

如果你的包引入了三種類型的包,標準庫包,程式內部包,第三方包,建議採用如下方式進行組織你的包:

import (
    "encoding/json"
    "strings"

    "myproject/models"
    "myproject/controller"
    "git.obc.im/obc/utils"

    "git.obc.im/dep/beego"
    "git.obc.im/dep/mysql"
)  

在專案中不要使用相對路徑引入包:

// 這是不好的匯入
import “../net”

// 這是正確的做法
import “xxxx.com/proj/net”

函式名函式名採用駝峰命名法,儘量不要使用下劃線

錯誤處理error作為函式的值返回,必須儘快對error進行處理

採用獨立的錯誤流進行處理

不要採用這種方式

    if err != nil {
        // error handling
    } else {
        // normal code
    }

而要採用下面的方式

    if err != nil {
        // error handling
        return // or continue, etc.
    }
    // normal code

如果返回值需要初始化,則採用下面的方式

x, err := f()
if err != nil {
    // error handling
    return
}
// use x

在邏輯處理中禁用panic

在main包中只有當實在不可執行的情況採用panic,例如檔案無法開啟,資料庫無法連線導致程式無法正常執行,但是對於其他的package對外的介面不能有panic,只能在包內採用。 建議在main包中使用log.Fatal來記錄錯誤,這樣就可以由log來結束程式。

Recoverrecover用於捕獲runtime的異常,禁止濫用recover,在開發測試階段儘量不要用recover,

recover一般放在你認為會有不可預期的異常的地方。

func server(workChan <-chan *Work) {
    for work := range workChan {
        go safelyDo(work)
    }
}

func safelyDo(work *Work) {
    defer func() {
        if err := recover(); err != nil {
            log.Println("work failed:", err)
        }
    }()
    // do 函式可能會有不可預期的異常
    do(work)
}

defer在函式return之前執行,對於一些資源的回收用defer是好的,但也禁止濫用defer,defer是需要消耗效能的,所以頻繁呼叫的函式儘量不要使用defer。

// Contents returns the file's contents as a string.
func Contents(filename string) (string, error) {
    f, err := os.Open(filename)
    if err != nil {
        return "", err
    }
    defer f.Close()  // f.Close will run when we're finished.

    var result []byte
    buf := make([]byte, 100)
    for {
        n, err := f.Read(buf[0:])
        result = append(result, buf[0:n]...) // append is discussed later.
        if err != nil {
            if err == io.EOF {
                break
            }
            return "", err  // f will be closed if we return here.
        }
    }
    return string(result), nil // f will be closed if we return here.
}

控制結構if,if接受初始化語句,約定如下方式建立區域性變數

if err := file.Chmod(0664); err != nil {
    return err
}

for採用短宣告建立區域性變數

sum := 0
for i := 0; i < 10; i++ {
    sum += i
}

range如果只需要第一項(key),就丟棄第二個:

for key := range m {
    if key.expired() {
        delete(m, key)
    }
}

如果只需要第二項,則把第一項置為下劃線

sum := 0
for _, value := range array {
    sum += value
}

return,儘早return:一旦有錯誤發生,馬上返回

f, err := os.Open(name)
if err != nil {
    return err
}
d, err := f.Stat()
if err != nil {
    f.Close()
    return err
}
codeUsing(f, d)

方法的接收器名稱 一般採用struct的第一個字母且為小寫,而不是this,me或者self

  type T struct{} 
  func (p *T)Get(){}

如果接收者是map,slice或者chan,不要用指標傳遞

//Mappackage main

import (
    "fmt"
)

type mp map[string]string

func (m mp) Set(k, v string) {
    m[k] = v
}

func main() {
    m := make(mp)
    m.Set("k", "v")
    fmt.Println(m)
}
//Channelpackage main

import (
    "fmt"
)

type ch chan interface{}

func (c ch) Push(i interface{}) {
    c <- i
}

func (c ch) Pop() interface{} {
    return <-c
}

func main() {
    c := make(ch, 1)
    c.Push("i")
    fmt.Println(c.Pop())
}

如果需要對slice進行修改,通過返回值的方式重新賦值

//Slicepackage main

import (
    "fmt"
)

type slice []byte

func main() {
    s := make(slice, 0)
    s = s.addOne(42)
    fmt.Println(s)
}

func (s slice) addOne(b byte) []byte {
    return append(s, b)
}

如果接收者是含有sync.Mutex或者類似同步欄位的結構體,必須使用指標傳遞避免複製

package main

import (
    "sync"
)

type T struct {
    m sync.Mutex
}

func (t *T) lock() {
    t.m.Lock()
}
/*
Wrong !!!
func (t T) lock() {
    t.m.Lock()
}
*/

func main() {
    t := new(T)
    t.lock()
}

如果接收者是大的結構體或者陣列,使用指標傳遞會更有效率。

package main

import (
    "fmt"
)

type T struct {
    data [1024]byte
}

func (t *T) Get() byte {
    return t.data[0]
}

func main() {
    t := new(T)
    fmt.Println(t.Get())
}