如何提高golang的可读性

1. 尽早返回

反例:

//UserCtrl
func UserInfo(userId string){
  user.UserInfo(userId)
  ....
  ....
  //resp result ...
}

//UserService
func UserInfo(userId string){
  if len(userId) > 0 { 
  	//do query database 
    .....
  } 
}
// repo
func queryUserInfo(userId string){
  if len(userId) > 0{
    //select * from user where user_id = ?
  }
}

从这个例子来看,在service层和数据库查询,我们都进行了userId的判断. 因为当我们经常会忘记,我们是否在上一层入参的时候进行了userId为空的判断. 为了避免空指针,我们不得已一层层进行判断.

假如我们尽早地返回,那么就可以避免后续的层层判空

推荐写法:

//userCtrl
func UserInfo(userId string){
  if len(userId) == 0 {
    //resp some error
  }
  user.UserInfo(userId)
}

2. 写好分支语句

反例:

func xxx(lang string){
  if lang == "java"{
    doA()
  } else {
    doB()
  }
  
  if lang == "java"{
    doC()
  }else{
    doD()
  }
}

推荐写法:

func java(lang string){
  doA()
  doC()
}

func other(lang string){
  doB()
  doC()
}

if都需要包含else

反例:

if cmd == "1" {
  if status == 0 {
    doSome()
  }
}else {
  if tag == 1 {
    doSomeB()
  }
}

推荐写法:

if cmd == "1" {
  if status == 0 {
    
  }else{
    
  }
}else {
  if tag == 1{
    
  }else {
    
  }
}

避免啰嗦的条件

if isDone() == true {
  do()
}

推荐:

if isDone() {
  doSome()
}

使用switch语句

反例:

if lang == "java"{
  
}else if lang == "c#" {
  
}else if lang == "clojure"{
  
}

推荐写法:

switch lang:
case "java":
case "c#":
case "clojure":

减少逻辑表达式:

逻辑表达式是门电路的表达式,总会有人不能记得他的先后执行顺序。

反例:

if lang == "java" || lang == "c#" && lang == "clojure" {
  doSome()
} 

如果非得使用逻辑表达式,推荐使用括号,显示地说明调用的顺序.

if (lang == "java" || lang == "c#") && lang == "clojure" {
  doSome()
} 

使用正序的逻辑

反例:

if !isUserInfoSaved() {
  doA()
}else {
  doB()
}

if !isNotStop() {
  doSome()
}

用一个符合人类思考顺序方式来写分支,减少阅读代码时的时间

if isUserInfoSaved() {
  doB()
}else {
  doA()
}

if isStart(){
  doSome()
}

3.合理使用局部变量

可能我们知道定义一个变量会开辟一块新的内存,有时候觉得自己重复使用一个变量,会让性能"好一些", 于是我们就会写出下面的代码

反例:

var name
if login {
  name = "user"
  doSome(name)
}else {
  name = "guest"
  doSome(name)
}

其实在栈上开辟内存的成本很低,编译器会对代码进行逃逸分析,而且执行完这个方法后,内存就会被回收掉,所以不用担心这个性能问题.

推荐写法:


if login {
	//使用局部变量
  name := "user"
  doSome(name)
}else {
  name := "guest"
  doSome(name)
}

有的时候我们会想耍个酷,那么牛逼的调用一行代码就写完了,可是这时候阅读起来是非常痛苦的一件事情.

反例:

saveXXX(queryRole(),queryOrder(),saveXXX(queryUserInfo(genUserId())))

推荐写法:

我们把参数通过一个中间变量存起来,这样会很明显地说明,我们都干了些什么.

userInfo := queryUserInfo(genUserId)
u := saveXXX(userInfo)
saveXXX(queryRole(),queryOrder(),u)

4.循环

循环本身就不好读,假如在循环中包含continue,break之类的,让原本的代码更难读

反例:

for i,itm := range Users {
  if item.Name != "admin" {
    continue
  }else {
    doSome()
  }
}

推荐写法:

for i,itm := range Users {
  if item.Name == "admin" {
    doSome()
  }
}

使用i,j之类的下标,本身就比较相似,一不小心就会造成了下标越界,推荐使用foreach

反例:

for i:=0 ; i< len(users); i++ {
   for j:=0 ; j < len(users[i].children); j++ {
     // doSome
   }
}

推荐写法

for _,itm := range users {
  for _, c := range item{
    //dosome
  }
}

假如非得使用下标操作,也要避免使用i,j之类的变量

for uidx :=0 ; uidx< len(users); uidx++ {
   childen := users[uidx].children  //使用中间变量,避免臃肿
   for chidx:=0 ; chidx < len(childen); chidx++ {
       // chidx 和 uidx 能避免混淆,能在使用的过程中避免出错。
   }
}

5.面条代码, 过程式代码

面条代码过程式代码

type UserInf struct {
  UserName   string
  UserId     string
  Role       *Role
  Alias     string
}

type Role struct {
  Id string
  Name string
}

func save(inf *UserInf){
  Role(inf)
  inf.Alias = genAlias()
  update(inf)
}

func update(inf *UserInf){
  //update ....
}

func Role(inf *UserInfo) {
  queryUser(inf)
  inf.Role = queryRole(inf.UserId)
}

func queryUser(inf *UserInf){
  //select * from user where user_id = ?
  inf.UserName = ...
  inf.xxx = ...
}

思考: 为什么要使用纯函数?

6. 控制代码的长度

人的左脑关心的是逻辑,右脑做的是快照(可能是伪科学),实际情况中,假如我们一眼能看完,是不是剩下的就是在想逻辑,而不是一边读代码,一边想逻辑,这样能让我们大脑一次性把看到的代码缓存起来,然后专注于想逻辑。简短的代码,也可以避免bug,所以方法建议都控制在100行之内。

7. 命名

这个放最后来写的原因是这个命名本来就很难,命名得好就会让代码清晰可读,命名不好,就会误导导致需要大量的注视来注释代码,本来维护代码已经是一件痛苦的事情了,假如在修改了代码后,注释没有同步修改,反而会引起误导。因为母语不是英文,很多同学跟我一样也都很痛苦,这里可以上网找找相关命名的资料,本人水平有限也只能是大概地举几个例子

bool isStart // 服务启动的状态,最好使用正向的表达,是否启动,不推荐使用  bool isNotStop
func ComputeUserScore() //计算用户积分,假如这是一个耗时的操作,推荐在方法名上就表示出来,不推荐使用GetUserScore,
func DownloadFile() //下载文件,不推荐使用GetFile