Golang代碼規範
參考 https://golang.org/doc/effective_go.html
文章目錄
項目目錄結構規範
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())
}
註釋
- 所有的 public 方法和變量都要添加註釋
- 註釋格式爲:name describeContent
// TubeName1 channel 1
TubeName1 = "通道一"