如何提高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 { UserNamestring UserIdstring Role*Role Aliasstring } 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