幾個祖?zhèn)鞔a不遵守就想罵的代碼規(guī)范
今天說幾個我曾經(jīng)在管理項目和團隊要求的基本編碼規(guī)范。實際執(zhí)行下來成本比較低,長期堅持下來的確有助于項目的維護。
雖然是幾個非常基本的代碼規(guī)范,但我們只在團隊比較穩(wěn)定的時候堅持下來過,后來隨著人員更迭,懂得都懂。 這里也不是吐槽誰的代碼習慣不好, 我也干過復制舊代碼過來就能用,媽呀真香,趕緊上線吧這種事情。
數(shù)據(jù)表和Model的命名規(guī)范
類型 | 規(guī)則 | 正確示例 | 錯誤示例 |
數(shù)據(jù)表名 | 使用SnakeCase 命名法多個單詞用下劃線 _ 分割使用單詞的復數(shù)形式命名 | vip_members | vipMembers vipMember vip_member |
數(shù)據(jù)表字段名 | 使用SnakeCase 命名法多個單詞用下劃線 _ 分割 | user_name | userName UserName _user_name |
數(shù)據(jù)表在代碼中的Model 名 | 使用CamelCase命名 單詞使用單數(shù)形式 | VipMember vipMember | VipMembers Members vip_member |
關于為啥數(shù)據(jù)表用復數(shù),Model用單數(shù),我的理解是Model代表的是這類東西,在英語里應該用復數(shù)。
下面說幾個Go語言里比較容易堅持執(zhí)行下去且能有助于我們減少BUG的編碼規(guī)范。其他語言像Java的話,看阿里出的《阿里巴巴Java手冊》就可以,里面要求的比較細致。
Go語言編碼規(guī)范
1.函數(shù)簽名要避免歧義
函數(shù)名、參數(shù)名、參數(shù)類型、返回值類型要表達清楚要做的事情,避免產(chǎn)生歧義。這一條,感覺說簡單非常簡單,但是實際項目開發(fā)中,總是有不少人直接copy類似的函數(shù),名字也不按使用場景去調(diào)整,讓看代碼的人就很難受。
錯誤案例:
func handleSomething(delay int) {
for {
// ...
time.Sleep(time.Duration(delay) * time.Millisecond)
}
}
poll(10) // delay參數(shù)定義成int 每次加的延遲是10毫秒還是10秒,還需要看poll函數(shù)的實現(xiàn)才知道
正確案例:
func handleSomething(delay time.Duration) {
for {
// ...
time.Sleep(delay)
}
}
poll(10 * time.Second) //delay參數(shù)定義成time.Duration類型, 調(diào)用時根據(jù)需求傳遞執(zhí)行任務時要延遲的時間段
// 或者用參數(shù)名,明確告訴調(diào)用者,傳遞要延遲的秒數(shù)
func handleSomething(delaySeconds int) {
for {
// ...
time.Sleep(delaySeconds * time.Second)
}
}
2.禁止使用硬編碼的魔術數(shù)字或字符串進行邏輯判斷
在邏輯判斷里使用類似判斷屬性值是否等于某個硬編碼的值時會使得代碼晦澀難懂,應該使用更能從字面上看明白含義的常量來代替這些邏輯判斷里硬編碼的值。
錯誤案例
if prize.Type != 1 && prize.Type != 2{
......
}
正確案例:
const (
PRIZE_TYPE_COUPON = 1
PRIZE_TYPE_MONEY = 2
PRIZE_TYPE_VIPSCORE = 3
)
if prize.Type != PRIZE_TYPE_COUPON && prize.Type != PRIZE_TYPE_MONEY {
......
}
3.避免在init中修改已初始化好的數(shù)據(jù)
注意程序的完全確定性,不要依賴init執(zhí)行的順序?qū)崿F(xiàn)功能,比如在后執(zhí)行的init函數(shù)中對前面已初始化后的全局變量進行更改。
4.slice、map、chan、struct指針使用前必須先初始化
未初始化的map 默認值是nil , 可以對nil map進行讀取,但是寫入會直接panic:
var aMap map[string]string
aMap["foo"] = "bar" // panic
未初始化的slice,可以進行讀取和append操作,但不做初始化遇到接口中要返回的某個字段查不到數(shù)據(jù)直接返回,該字段在JSON里會用null表示而不是[], 有一定幾率造成前端錯誤。
type Person struct {
Friends []string
}
func main() {
var f1 []string
f2 := make([]string, 0)
json1, _ := json.Marshal(Person{f1})
json2, _ := json.Marshal(Person{f2})
fmt.Printf("%s\n", json1)
fmt.Printf("%s\n", json2)
}
{"Friends":null}
{"Friends":[]}
向未初始化的nil chan 寫入會造成goroutine阻塞,程序最終會死鎖:
func main() {
//fmt.Println("Called heapAnalysis", heapAnalysis())
var achan chan struct{}
achan <- struct{}{} // fatal error: all goroutines are asleep - deadlock!
}
struct指針默認為nil , 未初始化直接使用,假如程序邏輯里是查不到數(shù)據(jù)就不對指針指向的struct進行復制,后續(xù)邏輯代碼再使用指針引用struct里的字段進行判斷時會因為嘗試對nil pointer 解引用直接panic
func QueryData(a int) (data *Data, err error) {
// data 返回值直接使用時,默認是nil
// 確保安全應該先對data 進行初始化 data = new(Data)
data, err := querySomeData()
if errors.IsNotFoundErr(err) {
return;
}
}
func main() {
dataP, err := QueryData()
if err != nil {
return err
}
if dataP.State == STATE_ACTIVE { // 此處有可能嘗試對nil pointer進行解引用,會造成空指針問題程序崩潰。
// active logic
}
}
5.代碼邏輯要盡量減少嵌套
代碼應通過盡可能先處理錯誤情況/特殊情況并盡早返回或繼續(xù)循環(huán)來減少嵌套。減少嵌套多個級別的代碼的代碼量。
錯誤案例:
for _, v := range data {
if v.F1 == 1 {
v = process(v)
if err := v.Call(); err == nil {
v.Send()
} else {
return err
}
} else {
log.Printf("Invalid v: %v", v)
}
}
正確案例:
for _, v := range data {
if v.F1 != 1 {
log.Printf("Invalid v: %v", v)
continue
}
v = process(v)
if err := v.Call(); err != nil {
return err
}
v.Send()
}
6.減少不必要的else代碼塊
注意下面兩種寫法的直觀感受:
var a int
if b {
a = 100
} else {
a = 10
}
// 減少了不必要的else塊
// 如果在 if 和 else 兩個分支中都設置了變量,則可以將其替換為單個 if。
a := 10
if b {
a = 100
}
7.盡量避免使用map[string]interface{} 類型的參數(shù)
在函數(shù)的參數(shù)中盡量不使用map[string]interface{}, map[string][string]這種類型的參數(shù),IDE沒法幫助提示這些參數(shù)的內(nèi)部結(jié)構,這讓其他人使用這個代碼時就會很苦惱,還需要先看看函數(shù)實現(xiàn)里具體用到了字典的哪些鍵。
針對比較復雜的代表一類事物的參數(shù),應該先定義結(jié)構體,然后使用結(jié)構體指針或者結(jié)構體指針切片作為參數(shù)。
錯誤案例:
func AuthenticateUser(input map[string]interface{}) error {
name, _ := input[name].(string)
password, _ := input[name].(string)
findUser(input["name"], input["password"])
...
}
正確案例:
type UserAuth struct{
Name string
Age int32
Password string
}
func AuthenticateUser(input *UserAuth) error {
findUser(input.Name, input.Password)
...
}