很多项目连这几个基础代码规范都坚持不下来,谁的问题?

今天说几个我曾经在管理项目和团队,要求的几个基本的编码规范。实际执行下来要求的成本比较低,建议大家编码时养成这些习惯,不然review代码时因为这种被拿出来说就有点尴尬啦。不过你也可以提高自己的钝感力,成年人只要自己不尴尬就不是事儿。

虽然是几个非常基本的代码规范,但我们只在团队比较稳定的时候坚持下来过,后来随着人员更迭,懂得都懂。 这里也不是吐槽谁的代码习惯不好, 我也干过复制旧代码过来就能用---妈呀真香,赶紧上线吧这种事情。

开发中的各种命名规范

类型 规则 正确示例 错误示例
数据表名 使用SnakeCase 命名法多个单词用下划线 _ 分割使用单词的复数形式命名 vip_members vipMembers vipMember vip_member
数据表字段名 使用SnakeCase 命名法多个单词用下划线 _ 分割 user_name userName UserName _user_name
数据表在代码中的Model 名 使用CamelCase命名 单词使用单数形式 VipMember vipMember VipMembers Members vip_member
包名 使用SnakeCase命名法 单词的单数形式, 多个单词之间不要加分割符 member memberclub members memberClub member_club
Go程序文件名 使用SnakeCase 命名法多个单词用下划线 _ 分割(仅针对Go语言) family_relation.go familyRelation.go
路由/url 命名 路由里统一使用小写格式的单词,多个单词用短横线 - 分割。 路由定义需遵守RESTful 设计规范。 /wechat/set-phone WeChat/setPhone /wechat/set_phone

下面说几个Go语言里比较容易坚持执行下去且能有助于我们减少BUG的编码规范。其他语言像Java的话,看阿里出的《阿里巴巴Java手册》就可以,里面要求的比较细致。

Go语言编码规范

函数签名要避免歧义

函数名、参数名、参数类型、返回值类型要表达清楚要做的事情,避免产生歧义。这一条,感觉说简单非常简单,但是实际项目开发中,总是有不少人直接copy类似的函数,名字也不按使用场景去调整,让看代码的人就很难受。

错误案例

scss 复制代码
func handleSomething(delay int) {
  for {
    // ...
    time.Sleep(time.Duration(delay) * time.Millisecond)
  }
}
poll(10) // delay参数定义成int 每次加的延迟是10毫秒还是10秒,还需要看poll函数的实现才知道

正确案例

scss 复制代码
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)
  }
}

禁止使用硬编码的魔术数字或字符串进行逻辑判断

在逻辑判断里使用类似判断属性值是否等于某个硬编码的值时会使得代码晦涩难懂,应该使用更能从字面上看明白含义的常量来代替这些逻辑判断里硬编码的值。

错误案例

python 复制代码
if prize.Type != 1 && prize.Type != 2{
    ......
}

正确案例

ini 复制代码
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执行的顺序实现功能,比如在后执行的init函数中对前面已初始化后的全局变量进行更改。

slice、map、chan、struct指针使用前必须先初始化

  • 未初始化的map 默认值是nil , 可以对nil map进行读取,但是写入会直接panic
go 复制代码
   var aMap map[string]string


aMap["foo"] = "bar" // panic
  • 未初始化的slice,可以进行读取和append操作,但不做初始化遇到接口中要返回的某个字段查不到数据直接返回,该字段在JSON里会用null表示而不是[], 有一定几率造成前端错误。
go 复制代码
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阻塞,程序最终会死锁
go 复制代码
func main() {
   //fmt.Println("Called heapAnalysis", heapAnalysis())
   var achan chan struct{}
   achan <- struct{}{} // fatal error: all goroutines are asleep - deadlock!
 
}
  • struct指针默认为nil , 未初始化直接使用,假如程序逻辑里是查不到数据就不对指针指向的struct进行复制,后续逻辑代码再使用指针引用struct里的字段进行判断时会因为尝试对nil pointer 解引用直接panic
go 复制代码
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
 
    }
}

代码逻辑要尽量减少嵌套

代码应通过尽可能先处理错误情况/特殊情况并尽早返回或继续循环来减少嵌套。减少嵌套多个级别的代码的代码量。

错误案例

go 复制代码
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)
  }
}

正确案例

go 复制代码
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代码块

注意下面两种写法的直观感受

less 复制代码
var a int
if b {
  a = 100
} else {
  a = 10
}
 
 
// 减少了不必要的else块
// 如果在 if 和 else 两个分支中都设置了变量,则可以将其替换为单个 if。
a := 10
if b {
  a = 100
}

尽量避免使用map[string]interface{} 类型的参数

在函数的参数中尽量不使用map[string]interface{}, map[string][string]这种类型的参数,IDE没法帮助提示这些参数的内部结构,这让其他人使用这个代码时就会很苦恼,还需要先看看函数实现里具体用到了字典的哪些键。

针对比较复杂的代表一类事物的参数,应该先定义结构体,然后使用结构体指针或者结构体指针切片作为参数。

错误案例

go 复制代码
func AuthenticateUser(input map[string]interface{}) error {
    name, _ := input[name].(string)
    password, _ := input[name].(string)
    findUser(input["name"], input["password"])
    ...
}

正确案例

go 复制代码
type UserAuth struct{
  Name     string
  Age      int32
  Password string
}
func AuthenticateUser(input *UserAuth) error {
    findUser(input.Name, input.Password)
    ...
}

最后

虽然是几个非常基本的代码规范,但我们只在团队比较稳定的时候坚持下来过,后来随着人员更迭,或者换到了其他业务的祖传项目并没有坚持下来。

确实项目很紧但也不是完全连坚持这些规范的时间都没有,更多的时候是那些老项目本来就已经积重难返,现在要让你在上面再雕花心里就很别扭,有的时候发现老程序拿过来改改真能用就算了,毕竟OKR考核的时候也不考核这个,写的慢了反而会被挑刺。

你觉得造成这种现象是谁的问题呢?

相关推荐
古拉拉明亮之神3 天前
scala的统计词频
scala·命令模式·代码规范·源代码管理
沉默是金~4 天前
Vue 前端代码规范
前端·vue.js·代码规范
CreditFE信用前端5 天前
如何更好的应对技术债?
程序员·架构·代码规范
litGrey7 天前
【规范七】Git管理规范
git·代码规范
三原7 天前
写给我前端同事,从事一年多应该要怎么成长的路线
前端·代码规范
方圆想当图灵8 天前
由 Mybatis 源码畅谈软件设计(三):简单查询 SQL 执行流程
后端·代码规范
看山还是山,看水还是。9 天前
软件工程 设计的复杂性
笔记·流程图·软件工程·团队开发·代码规范·内容运营·代码覆盖率
古拉拉明亮之神9 天前
Scala的链式风格
scala·命令模式·代码规范·源代码管理
狂炫一碗大米饭11 天前
响应式设计:打造一个自动适应用户设备屏幕大小和方向的页面。
前端·javascript·代码规范
程序猿进阶11 天前
Dead Code Clean
java·spring boot·后端·kafka·代码规范·质量管理·代码清理