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()
}
减少不必要的else代码块
注意下面两种写法的直观感受
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,
}
本来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进行类似日志记录的处理
错误案例
正确案例