Go語言規范1 - 統一規范篇


序言

看過很多方面的編碼規范,可能每一家公司都有不同的規范,這份編碼規范是寫給我自己的,同時希望我們公司內部同事也能遵循這個規范來寫Go代碼。

如果你的代碼沒有辦法找到下面的規范,那么就遵循標准庫的規范,多閱讀標准庫的源碼,標准庫的代碼可以說是我們寫代碼參考的標桿。

本文中凡是【】內為規則的都是參考的標准庫的源碼,和【】內為原則的一樣都是必須遵守的,【】內為建議的,是僅做建議遵守的。

本文參考了網上流傳的眾多開發規范帖子和標准款的規范代碼,最后梳理而成,因內容過多分為幾大篇,可選看,最后感謝那些原帖作者,文末給出了參考帖子的鏈接

目錄

  • 統一規范篇
  • 命名篇
  • 開發篇
  • 優化篇

統一規范篇

本篇主要描述了公司內部同事都必須遵守的一些開發規矩,如統一開發空間,既使用統一的開發工具來保證代碼最后的格式的統一,開發中對文件和代碼長度的控制,必須經過go語言自帶的檢測機制等。

1.1 合理規划目錄

【原則1.1】合理規划目錄,一個目錄中只包含一個包(實現一個模塊的功能),如果模塊功能復雜考慮拆分子模塊,或者拆分目錄。

說明:在Go中對於模塊的划分是基於package這個概念,可以在一個目錄中可以實現多個package,但是並不建議這樣的實現方式。主要的缺點是模塊之間的關系不清晰,另外不利於模塊功能擴展。

錯誤示例:

project
│  config.go
│  controller.go
│  filter.go
│  flash.go
│  log.go
│  memzipfile.go
│  mime.go
│  namespace.go
│  parser.go
│  router.go
│  staticfile.go
│  template.go
│  templatefunc.go
│  tree.go
│  util.go
|  validation.go
|  validators.go

推薦做法:

project  
├─cache  
│  │  cache.go  
│  │  conv.go  
│  │        
│  └─redis  
│          redis.go  
├─config  
│  │  config.go  
│  │  fake.go  
│  │  ini.go  
│  └─yaml  
│          yaml.go  
├─logs  
│      conn.go  
│      console.go  
│      file.go  
│      log.go  
│      smtp.go  
└─validation  
        util.go  
        validation.go  
        validators.go

1.2 GOPATH設置

【建議1.2】使用單一的 GOPATH

雖說Go語言支持擁有多個 GOPATH,但多個GOPATH的情況並不具有彈性。GOPATH本身就是高度自我完備的(通過導入路徑)。有多個 GOPATH 會導致某些副作用,例如可能使用了給定的庫的不同的版本。你可能在某個地方升級了它,但是其他地方卻沒有升級。而且,我還沒遇到過任何一個需要使用多個 GOPATH 的情況。所以只使用單一的 GOPATH,這會提升你 Go 的開發進度。

許多人不同意這一觀點,接下來我會做一些澄清。像 etcd 或 camlistore 這樣的大項目使用了像 godep 這樣的工具,將所有依賴保存到某個目錄中。也就是說,這些項目自身有一個單一的 GOPATH。它們只能在這個目錄里找到對應的版本。除非你的項目很大並且極為重要,否則不要為每個項目使用不同的 GOPAHT。如果你認為項目需要一個自己的 GOPATH 目錄,那么就創建它,否則不要嘗試使用多個 GOPATH。它只會拖慢你的進度。

 所有項目共用一個workspace,如下圖所示:

workspace/  
├── bin  
├── pkg  
│   └── linux_amd64  
│         
└── src  
    ├── project1  
    │     
└── project2  
    │     
    └── project3  
    │     
    └── …

優點: 方便發布到github.com, 讓第三方通過go get等工具獲取。

內部項目,建議采用第一種工程結構。公開項目、提供給第三方集成的項目采用第二種項目結構。

1.3 import 規范

import路徑是一個唯一標示的字符串

import在多行的情況下,goimports會自動幫你格式化,但是我們這里還是規范一下import的一些規范,如果你在一個文件里面引入了一個package,還是建議采用如下格式:

import (
    "fmt"
)

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

import (
    "encoding/json"
    "strings"

    "myproject/models"
    "myproject/controller"
    "myproject/utils"

    "github.com/astaxie/beego"
    "github.com/go-sql-driver/mysql"
)

有順序的引入包,不同的類型采用空格分離,第一種實標准庫,第二是項目包,第三是第三方包。

【規則1.3.1】在非測試文件(*_test.go)中,禁止使用 . 來簡化導入包的對象調用。

錯誤示例:

	// 這是不好的導入
	import . " pubcode/api/broker"

這種寫法不利於閱讀,因而不提倡。

【規則1.3.2】禁止使用相對路徑導入(./subpackage),所有導入路徑必須符合 go get 標准。

錯誤示例:

	// 這是不好的導入
	import "../net"

正確做法:

	// 這是正確的做法
	import "github.com/repo/proj/src/net"

【建議1.3.3】建議使用goimports工具或者IDE工具來管理多行import

go默認已經有了gofmt工具,但是我們強烈建議使用goimport工具,這個在gofmt的基礎上增加了自動刪除和引入包.

go get golang.org/x/tools/cmd/goimports

不同的編輯器有不同的配置, sublime的配置教程:http://michaelwhatcott.com/gosublime-goimports/

LiteIDE和GoLand默認已經支持了goimports,如果你的不支持請點擊屬性配置->golangfmt->勾選goimports

保存之前自動fmt你的代碼。

好處:import在多行的情況下,goimports工具會自動幫你格式化,自動刪除和引入包。很多IDE工具也可以自動檢查並糾正import路徑

1.4 代碼風格

Go語言對代碼風格作了很多強制的要求,並提供了工具gofmt, golint, go tool vet等工具檢查。

【規則1.4.1】提交代碼時,必須使用gofmt對代碼進行格式化。

大部分的格式問題可以通過 gofmt 來解決,gofmt 自動格式化代碼,保證所有的 go 代碼與官方推薦的格式保持一致,所有格式有關問題,都以gofmt的結果為准。所以,建議在提交代碼庫之前先運行一下這個命令。

gofmt(也可以用go fmt,其操作於程序包的級別,而不是源文件級別),讀入Go的源代碼,然后輸出按照標准風格縮進和垂直對齊的源碼,並且保留了根據需要進行重新格式化的注釋。如果你想知道如何處理某種新的布局情況,可以運行gofmt;如果結果看起來不正確,則需要重新組織你的程序,不要把問題繞過去。標准程序包中的所有Go代碼,都已經使用gofmt進行了格式化。

不需要花費時間對結構體中每個域的注釋進行排列,如下面的代碼,

	type T struct {
	    name string     // name of the object
	    value int     // its value
	}

gofmt將會按列進行排列:

	type T struct {
	    name string   // name of the object
	    value int     // its value
	}

【規則1.4.2】提交代碼時,必須使用golint對代碼進行檢查。

golint 會檢測的方面:

  • 變量名規范
  • 變量的聲明,像var str string = "test",會有警告,應該var str = "test"
  • 大小寫問題,大寫導出包的要有注釋
  • x += 1 應該 x++

等等
詳細可以看官方庫示例,https://github.com/golang/lint/tree/master/testdata

想速成的可以看Golang lint簡易使用方法自行學習使用

【建議1.4.3】提交代碼前,必須使用go vet對代碼進行檢查。

如果說golint是檢查我們的代碼規范的話,那么vet工具則是可以幫我們靜態分析我們的源碼存在的各種問題,例如多余的代碼,提前return的邏輯,struct的tag是否符合標准等。

go get golang.org/x/tools/cmd/vet

使用如下:

go vet .

1.5 大小約定

【建議1.5.1】單個文件長度不超過500行。

對開源引入代碼可以降低約束,新增代碼必須遵循。

【建議1.5.2】單個函數長度不超過50行。

函數兩個要求:單一職責、要短小

【規則1.5.3】單個函數圈復雜度最好不要超過10,禁止超過15。

說明:圈復雜度越高,代碼越復雜,就越難以測試和維護,同時也說明函數職責不單一。

【規則1.5.4】單行語句不能過長,如不能拆分需要分行寫。一行最多120個字符。

換行時有如下建議:
換行時要增加一級縮進,使代碼可讀性更好;
低優先級操作符處划分新行;換行時操作符應保留在行尾; 
換行時建議一個完整的語句放在一行,不要根據字符數斷行

示例:

	if ((tempFlag == TestFlag) &&
	(((counterVar - constTestBegin) % constTestModules) >= constTestThreshold)) { 
	    // process code 
	}

【建議1.5.5】函數中縮進嵌套必須小於等於3層。

舉例,禁止出現以下這種鋸齒形的函數:

	func testUpdateOpts PushUpdateOptions) (err error) {
	    isNewRef := opts.OldCommitID == git.EMPTY_SHA
	    isDelRef := opts.NewCommitID == git.EMPTY_SHA
	    if isNewRef && isDelRef {
	        if isDelRef {
	            repo, err := GetRepositoryByName(owner.ID, opts.RepoName)
	            if err != nil {
	                if strings.HasPrefix(opts.RefFullName, git.TAG_PREFIX) {
	                    if err := CommitRepoAction(CommitRepoActionOptions{
	                        PusherName:  opts.PusherName,
	                        RepoOwnerID: owner.ID,
	                        RepoName:    repo.Name,
	                        RefFullName: opts.RefFullName,
	                        OldCommitID: opts.OldCommitID,
	                        NewCommitID: opts.NewCommitID,
	                        Commits:     &PushCommits{},
	                    }); err != nil {
	                        return fmt.Errorf("CommitRepoAction (tag): %v", err)
	                    }
	                    return nil
	                }
	            }
	            else {
	                owner, err := GetUserByName(opts.RepoUserName)
	                if err != nil {
	                    return fmt.Errorf("GetUserByName: %v", err)
	                }
	
	                return nil
	            }
	        }
	    }
	
	    // other code
	}

提示:如果發現鋸齒狀函數,應通過盡早通過return等方法重構。

【原則1.5.6】保持函數內部實現的組織粒度是相近的。

舉例,不應該出現如下函數:

	func main() {
	    initLog()
	
	    //這一段代碼的組織粒度,明顯與其他的不均衡
	    orm.DefaultTimeLoc = time.UTC
	    sqlDriver := beego.AppConfig.String("sqldriver")
	    dataSource := beego.AppConfig.String("datasource")
	    modelregister.InitDataBase(sqlDriver, dataSource)
	
	    Run()
	}

應該改為:

	func main() {
	    initLog()
	
	    initORM()  //修改后,函數的組織粒度保持一致 
	
	    Run()
	}

參考鏈接

https://studygolang.com/articles/2059
https://studygolang.com/articles/12033
https://blog.csdn.net/shuanger_/article/details/48241767
https://blog.csdn.net/tanzhe2017/article/list


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM