反例:
//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) }
反例:
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() }
可能我们知道定义一个变量会开辟一块新的内存,有时候觉得自己重复使用一个变量,会让性能"好一些", 于是我们就会写出下面的代码
反例:
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)
循环本身就不好读,假如在循环中包含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 能避免混淆,能在使用的过程中避免出错。 } }
面条代码过程式代码
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 = ... }
思考: 为什么要使用纯函数?
人的左脑关心的是逻辑,右脑做的是快照(可能是伪科学),实际情况中,假如我们一眼能看完,是不是剩下的就是在想逻辑,而不是一边读代码,一边想逻辑,这样能让我们大脑一次性把看到的代码缓存起来,然后专注于想逻辑。简短的代码,也可以避免bug,所以方法建议都控制在100行之内。
这个放最后来写的原因是这个命名本来就很难,命名得好就会让代码清晰可读,命名不好,就会误导导致需要大量的注视来注释代码,本来维护代码已经是一件痛苦的事情了,假如在修改了代码后,注释没有同步修改,反而会引起误导。因为母语不是英文,很多同学跟我一样也都很痛苦,这里可以上网找找相关命名的资料,本人水平有限也只能是大概地举几个例子
bool isStart // 服务启动的状态,最好使用正向的表达,是否启动,不推荐使用 bool isNotStop func ComputeUserScore() //计算用户积分,假如这是一个耗时的操作,推荐在方法名上就表示出来,不推荐使用GetUserScore, func DownloadFile() //下载文件,不推荐使用GetFile
个人博客 https://youkale.github.io