We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
本来CookBook里记录的都是开发时通用问题的解决方案,不过除了解决功能上的问题,项目的长期迭代的质量保证也是每个开发人员都拥有的基本常识,而编码规范则是保证质量的一个根本,下面列出来一些编码规范,只列出了关键的一些规范也是想避免矫枉过正。当我们所在的组织有自己的规范,不用怀疑去遵守就好,如果没有通过遵守下面这些编码规范能让我们的代码质量有个基础的保证。
函数名、参数名、参数类型、返回值类型要表达清楚要做的事情,避免产生歧义
错误案例
func handleSomething(delay int) { for { // ... time.Sleep(time.Duration(delay) * time.Millisecond) } } poll(10) // delay参数定义成int 每次加的延迟是10毫秒还是10秒,还需要看poll函数的实现才知道
正确案例
func handleSomething(delay time.Duration) { for { // ... time.Sleep(delay) } } poll(10 * time.Second) //delay参数定义成time.Duration类型, 调用时根据需求传递执行任务时要延迟的时间段 // 或者用参数名,明确告诉调用者,传递要延迟的秒数 func handleSomething(delaySeconds int) { for { // ... time.Sleep(delaySeconds * time.Second) } }
函数体过长会严重影响阅读体验和理解函数造成的心智负担。
因此约定将函数体的长度控制在200行以内,如果实现的逻辑超过200行代码就要考虑代码写的是否简练,以及考虑将部分逻辑抽象到单独的函数中去。
Go 允许结构体匿名嵌入另外一个结构体进行组合。 外部类型获取嵌入类型的方法和字段。
如果包导出的结构体内又匿名嵌入结构体,那么在包外部这些嵌入的类型的导出成员会泄漏实现细节,且不利于向后兼容。
type AbstractList struct {} // 添加将实体添加到列表中。 func (l *AbstractList) Add(e Entity) { // ... } // 移除从列表中移除实体。 func (l *AbstractList) Remove(e Entity) { // ... } // ConcreteList 是一个实体列表。 type ConcreteList struct { *AbstractList } cList := new(ConcreteList) cList.Add(e) // 会默认委托到嵌入类型
使用委托减少实现细节泄露
type AbstractList struct {} func (l *AbstractList) Add(e Entity) { // ... } func (l *AbstractList) Remove(e Entity) { // ... } // ConcreteList 是一个实体列表。 type ConcreteList struct { list *AbstractList } // 添加将实体添加到列表中。 func (l *ConcreteList) Add(e Entity) { l.list.Add(e) } // 移除从列表中移除实体。 func (l *ConcreteList) Remove(e Entity) { l.list.Remove(e) }
在逻辑判断里使用类似判断属性值是否等于某个硬编码的值时会使得代码晦涩难懂,应该使用更能从字面上看明白含义的常量来代替这些逻辑判断里硬编码的值。
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 { ...... }
注意程序的完全确定性,不要依赖init执行的顺序实现功能,比如在后执行的init函数中对前面已初始化后的全局变量进行更改。
var aMap map[string]string aMap["foo"] = "bar" // panic
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":[]}
func main() { //fmt.Println("Called heapAnalysis", heapAnalysis()) var achan chan struct{} achan <- struct{}{} // fatal error: all goroutines are asleep - deadlock! }
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 } }
import "a" import "b" const a = 1 const b = 2 var a = 1 var b = 2 type Area float64 type Volume float64 type Operation int const ( Add Operation = iota + 1 Subtract Multiply EnvVar = "MY_ENV" // 不相关的定义不要放在一组 )
import ( "a" "b" ) const ( a = 1 b = 2 ) var ( a = 1 b = 2 ) type ( Area float64 Volume float64 ) type Operation int const ( Add Operation = iota + 1 Subtract Multiply ) const EnvVar = "MY_ENV"
代码应通过尽可能先处理错误情况/特殊情况并尽早返回或继续循环来减少嵌套。减少嵌套多个级别的代码的代码量。
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() }
注意下面两种写法的直观感受
var a int if b { a = 100 } else { a = 10 } // 减少了不必要的else块 // 如果在 if 和 else 两个分支中都设置了变量,则可以将其替换为单个 if。 a := 10 if b { a = 100 }
在代码里做初始化结构体时,应该指定字段名称。
k := User{"John", "Doe", true}
k := User{ FirstName: "John", LastName: "Doe", Admin: true, }
在函数的参数中尽量不使用map[string]interface{}, map[string][string]这种类型的参数,IDE没法帮助提示这些参数的内部结构,这让其他人使用这个代码时就会很苦恼,还需要先看看函数实现里具体用到了字典的哪些键。
map[string]interface{}
map[string][string]
针对比较复杂的代表一类事物的参数,应该先定义结构体,然后使用结构体指针或者结构体指针切片作为参数。
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) ... }
package logic func AddUserMoney(userId int64, money int64) { ...... user, err := dao.GetUserById(userId) if err != nil { log.Error(...) } } package dao func GetUserById(userId int64) (user userModel err error) { ...... err = db.Where("user_id = ?", userId).Find(&userModel).Error if err != nil { log.Error("error msg", error) } return err }
The text was updated successfully, but these errors were encountered:
No branches or pull requests
本来CookBook里记录的都是开发时通用问题的解决方案,不过除了解决功能上的问题,项目的长期迭代的质量保证也是每个开发人员都拥有的基本常识,而编码规范则是保证质量的一个根本,下面列出来一些编码规范,只列出了关键的一些规范也是想避免矫枉过正。当我们所在的组织有自己的规范,不用怀疑去遵守就好,如果没有通过遵守下面这些编码规范能让我们的代码质量有个基础的保证。
命名规范
RESTful 规范指南:http://www.ruanyifeng.com/blog/2014/05/restful_api.html https://www.ruanyifeng.com/blog/2018/10/restful-api-best-practices.html
/check-member-identity
Go语言编码规范
函数签名要避免歧义
函数名、参数名、参数类型、返回值类型要表达清楚要做的事情,避免产生歧义
错误案例
正确案例
函数体的长度控制在200行以内
函数体过长会严重影响阅读体验和理解函数造成的心智负担。
因此约定将函数体的长度控制在200行以内,如果实现的逻辑超过200行代码就要考虑代码写的是否简练,以及考虑将部分逻辑抽象到单独的函数中去。
避免在包导出的结构体内进行匿名嵌套
Go 允许结构体匿名嵌入另外一个结构体进行组合。 外部类型获取嵌入类型的方法和字段。
如果包导出的结构体内又匿名嵌入结构体,那么在包外部这些嵌入的类型的导出成员会泄漏实现细节,且不利于向后兼容。
错误案例
正确案例
使用委托减少实现细节泄露
禁止使用硬编码的魔术数字或字符串进行逻辑判断
在逻辑判断里使用类似判断属性值是否等于某个硬编码的值时会使得代码晦涩难懂,应该使用更能从字面上看明白含义的常量来代替这些逻辑判断里硬编码的值。
错误案例
正确案例
避免在init中修改已初始化好的数据
注意程序的完全确定性,不要依赖init执行的顺序实现功能,比如在后执行的init函数中对前面已初始化后的全局变量进行更改。
slice、map、chan、struct指针使用前必须先初始化
相似的声明要放在一组
错误案例
正确案例
代码逻辑要尽量减少嵌套
代码应通过尽可能先处理错误情况/特殊情况并尽早返回或继续循环来减少嵌套。减少嵌套多个级别的代码的代码量。
错误案例
正确案例
减少不必要的else代码块
注意下面两种写法的直观感受
初始化结构体时要指定字段名
在代码里做初始化结构体时,应该指定字段名称。
错误案例
正确案例
尽量避免使用map[string]interface{} 类型的参数
在函数的参数中尽量不使用
map[string]interface{}
,map[string][string]
这种类型的参数,IDE没法帮助提示这些参数的内部结构,这让其他人使用这个代码时就会很苦恼,还需要先看看函数实现里具体用到了字典的哪些键。针对比较复杂的代表一类事物的参数,应该先定义结构体,然后使用结构体指针或者结构体指针切片作为参数。
错误案例
正确案例
底层代码只返回Error不对Error进行类似日志记录的处理
错误案例
正确案例
The text was updated successfully, but these errors were encountered: