Golang代碼規範

Golang代碼規範

參考 https://golang.org/doc/effective_go.html

本文參考blog

項目目錄結構規範

PROJECT_NAME
├── README.md 介紹軟件及文檔入口
├── bin 編譯好的二進制文件,執行./build.sh自動生成,該目錄也用於程序打包
├── build.sh 自動編譯的腳本
├── doc 該項目的文檔
├── pack 打包後的程序放在此處
├── pack.sh 自動打包的腳本,生成類似xxxx.20170713_14:45:35.tar.gz的文件,放在pack文件下
├── public 公共文件/靜態文件
└── src 該項目的源代碼
    ├── main 項目主函數
    ├── test 測試
    ├── app 項目代碼
    ├── research 在實現該項目中探究的一些程序
    └── vendor 存放go的庫
        ├── github.com/xxx 第三方庫
        └── xxx.com/abc 公司內部的公共庫

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

./app
├── bootstrap   //入口引導文件
├── cache
├── config  //項目配置 項目配置文件簡單,配置項少。大部分配置公司要用配置中心統一配置。
├── controller  //request請求處理中心 ——> controller ——> Response / view
├── library //項目工具庫
├── log         //日誌
├── middleware  //中間件
├── model       //data model.xorm -——> 數據庫表映射模型
├── plugin  //插件--自己開發/自己找的 --delete
├── route       //路由管理
├── service //前端數據獲取操作 service

文件名命名規範

用小寫,儘量見名思義,看見文件名就可以知道這個文件下的大概內容,對於源代碼裏的文件,文件名要很好的代表了一個模塊實現的功能。

命名規範

包名

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

接口名

單個函數的接口名以”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/model"
    "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

在邏輯處理中禁用panic

在main包中只有當實在不可運行的情況採用panic,例如文件無法打開,數據庫無法連接導致程序無法 正常運行,但是對於其他的package對外的接口不能有panic,只能在包內採用。 建議在main包中使用log.Fatal來記錄錯誤,這樣就可以由log來結束程序。

Recover

recover用於捕獲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

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)

方法的接收器

名稱 一般採用strcut的第一個字母且爲小寫,而不是this,me或者self

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

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

//Map
package 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)
}
//Channel
package 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進行修改,通過返回值的方式重新賦值

//Slice
package 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())
}

註釋

  1. 所有的 public 方法和變量都要添加註釋
  2. 註釋格式爲:name describeContent
    // TubeName1 channel 1
    TubeName1 = "通道一"
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章