Common anti-patterns in Go
go go go ole ole ole
前 言
對於一名程序員來說,編碼是一門藝術,就像每個精湛技藝併爲之感到驕傲的工匠一樣。爲了獲得最佳效果,藝術家不斷尋找可提高其手藝的方法和工具。同樣,作爲開發人員,我們不斷提高自己的技能,並好奇地想知道 如何編寫良好的代碼 🤯 ???
。
Frederick P. Brooks[1] 在他的鉅作中《人月神話》
中寫道:
The programmer, like the poet, works only slightly removed from pure thought-stuff. He builds his castles in the air, from air, creating by exertion of the imagination. Few media of creation are so flexible, so easy to polish and rework, so readily capable of realizing grand conceptual structures.
https://xkcd.com/844/
上面漫畫中大問號的答案,編寫良好代碼的最簡單方法是避免在我們編寫的代碼中包含 anti-pattern[2]😇。
anti-pattern
當編寫代碼時沒有考慮到未來的因素,就會出現反模式。反模式最初可能是一個合適的問題解決方案,但實際上,隨着代碼庫的擴展,這些模式會變得晦澀難懂,並給我們的代碼庫增加 Technical debt[3]。
通常在編寫代碼的時候一個簡單的anti-pattern
就是編寫一個簡單的API
時,只顧着滿足眼前的需求,沒有考慮到API
的消費者會如何使用它。如果在編寫的時候或者最初設計的時候就考慮到這個問題,意識到anti-pattern
並在編程時有意識地避免使用它們,肯定是向更可讀和可維護的代碼庫邁出的重要一步。
例如下面這個問題👇:
- 從導出的函數中返回未導出的類型的值
在Go
中,要導出任何字段
或變量
,我們需要確保其名稱以大寫字母開頭, 導出它們背後的動機是爲了使它們對其他軟件包可見。
// Bad practice
type unexportedType string
func ExportedFunc() unexportedType {
return unexportedType("some string")
}
// Recommended
type ExportedType string
func ExportedFunc() ExportedType {
return ExportedType("some string")
}
一個導出的函數或方法返回一個未導出的類型的值,使用起來可能會令人感受到噁心🤢,因爲從其他包中調用該函數的人將不得不再次定義一個類型來使用它。
- 不必要使用空白標識符
在各種情況下,爲空白標識符賦值是不需要的,也是不必要的,在for
循環中使用空白標識符的情況下,Go
規範中提到了:
If the last iteration variable is the blank identifier, the range clause is equivalent to the same clause without that identifier.
// Bad practice
for _ = range sequence
{
run()
}
x, _ := someMap[key]
_ = <-ch
// Recommended
for range something
{
run()
}
x := someMap[key]
<-ch
- 使用循環多次
append
串聯兩個切片
將多個切片附加到一個切片時,無需遍歷切片並一個接一個地附加每個元素,相反,在單個append
語句中執行此操作會更好,更有效。
例如,下面的代碼段通過迭代遍歷元素逐個附加元素來進行串聯sliceTwo
。
for _, v := range sliceTwo {
sliceOne = append(sliceOne, v)
}
這append
是一個variadic
函數,因此可以使用零個或多個參數來調用它。因此,可以僅使用一個append
函數調用來以更簡單的方式重寫上面的示例,如下所示:
sliceOne = append(sliceOne, sliceTwo…) // ... 語法糖拆分
make
調用中的冗餘參數
make
函數是一個特殊的內置函數,用於分配和初始化map
,slice
或chan
類型的對象,爲了使用初始化切片make
,我們必須提供切片的類型,切片的長度len
以及切片的容量cap
作爲參數。在初始化map
的情況下make
,我們需要傳遞map
的大小作爲參數。
-
對於
chan
,緩衝區容量默認爲零(無緩衝)。 -
對於
map
,分配的大小默認爲較小的起始大小。
ch = make(chan int, 0)
sl = make([]int, 1, 1)
完全可以改爲
ch = make(chan int)
sl = make([]int, 1)
如果像我一樣有編碼潔癖,看到不舒服代碼的時候就會飈一句垃圾代碼,寫得一坨💩一樣
,出於如果這個chan
在運行的時候緩衝區已經是明確確定的完全可以使用const
,常量 只是一個符號,會在編譯時替換爲具體的值,會被硬編碼到機器碼中。
const c = 0
ch = make(chan int, c) // Not an anti-pattern
return
功能沒用
return
在沒有返回值的函數中將語句作爲最終語句不是一種好習慣。
// Useless return, not recommended
func alwaysPrintFoofoo() {
fmt.Println("foofoo")
return
}
// Recommended
func alwaysPrintFoo() {
fmt.Println("foofoo")
}
然而命名的返回不應該與無用的返回相混淆,下面的返回語句實際上是返回一個值。
func printAndReturnFoofoo() (foofoo string) {
foofoo = "foofoo"
fmt.Println(foofoo)
return
}
- 切片上的多餘
nil
檢查
nil
切片的長度爲零時因此nil
在計算切片的長度之前,無需檢查切片是否爲切片。
if x != nil && len(x) != 0 {
// do something ❎
}
if len(x) != 0 {
// do something ✅
}
context.Context
應該是函數的第一個參數
本context.Context
應該是第一個參數,通常命名ctx
,ctx
應該是Go
代碼中許多函數的非常通用參數,並且從邏輯上來說,最好將通用參數放在參數列表的第一個或最後一個。
// Bad practice
func badPatternFunc(k favContextKey, ctx context.Context) {
// do something
}
// Recommended
func goodPatternFunc(ctx context.Context, k favContextKey) {
// do something
}
小 結
當涉及到團隊合作時,根據以往的經驗定製規範,審閱他人的代碼變得很重要,不要等着出了問題再去返工。
https://github.com/golang-standards/project-layout/issues/117
前不久因爲golang-standards
在上github
炸鍋了,如果你正在使用go
我覺得有必要去閱讀下面👇這幾個文檔:
-
https://blog.golang.org/package-names
-
https://golang.org/doc/effective_go
👨💻 Good luck~
參考資料
[1]
Frederick P. Brooks: https://www.cs.unc.edu/~brooks/
[2]
反模式: https://zh.wikipedia.org/wiki/%E5%8F%8D%E9%9D%A2%E6%A8%A1%E5%BC%8F
[3]
技術債務: https://zh.wikipedia.org/wiki/%E6%8A%80%E6%9C%AF%E8%B4%9F%E5%80%BA
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/TgZiOTyF-R3eyh4w08Niuw