常見錯誤
Go 語言常見錯誤與最佳實踐。
- Link: GitHub Pseudo Code
1. Unintended variable shadowing
變數遮蔽
當變數在內部區塊被重新宣告時,可能會導致錯誤或混淆,因為可能不小心引用到錯誤的變數。應避免使用同樣名稱的變數,以防止混淆。
2. Unnecessary nested code
不必要的巢狀程式碼
過度巢狀的程式碼會增加理解難度。應該保持「happy path」(正向流程)靠左並提早返回,減少巢狀層次。避免在
if-else 中使用 else,並盡量在不符合條件時直接返回。3. Misusing init functions
init 函數用於初始化,但其錯誤處理有限,並可能使狀態處理和測試變得複雜。初始化應該儘量使用專門的函數來處理,避免全域變數和不必要的依賴。4. Overusing getters and setters
在 Go 中,強制使用 Getters 和 Setters 並不是慣用做法。如果它們不增加價值,就不應該使用。Go 提倡簡潔的設計,應該平衡效率與遵循編程習慣。
5. Interface pollution
介面污染
不應該提前創建不必要的抽象介面,應在真正需要時才創建介面,避免增加複雜性。好的抽象應當是被發現而非被強行設計的。
6. Interface on the producer side
在 Go 中,介面實現是隱式的,通常應由消費者決定是否需要介面,避免由生產者強加抽象。如果確實需要生產者提供介面,應保持其最小化,以提高重用性與組合性。
7. Returning interfaces
accept interfaces, return structs
- 函式應該回傳具體類型,而不是介面
- 函式應該接收介面作為參數
8. any says nothing
any使用時機僅限於確實需要接受或返回任意型別的場景(如json.Marshal)- 不當使用
any的缺點:- 無法提供有意義的型別資訊
- 可能導致編譯時期的問題
- 允許呼叫者使用任意型別資料
- 避免過度泛化程式碼
- 適度的程式碼重複可能比使用
any更好 - 使用
any前應先評估是否真的需要處理任意型別
9. Being confused about when to use generics
不建議使用泛型的情況
- 只是呼叫型別參數的方法時
// Not Good: 不需要泛型,直接使用 interface 即可
func foo[T io.Writer](w T) {
b := getBytes()
_, _ = w.Write(b)
}
// Good: 直接使用 interface
func foo(w io.Writer) {
b := getBytes()
_, _ = w.Write(b)
}- 使程式碼更複雜時
// Not Good: 過度抽象化
func doSomething[T any](item T) T {
return item
}建議使用泛型的情況
- 通用資料結構
// Good: 泛型資料結構
type Node[T any] struct {
Val T
next *Node[T]
}
func (n *Node[T]) Add(next *Node[T]) {
n.next = next
}- 處理 slice, map, channel 等通用型別
// Good: 合併任意型別的 channel
func merge[T any](ch1, ch2 <-chan T) <-chan T {
// implementation
}
// Good: 取得任意型別 map 的 keys
func getKeys[K comparable, V any](m map[K]V) []K {
var keys []K
for k := range m {
keys = append(keys, k)
}
return keys
}- 抽象行為模式
// Good: 通用排序行為
type sliceFn[T any] struct {
s []T
compare func(T, T) bool
}
func (s sliceFn[T]) Len() int { return len(s.s) }
func (s sliceFn[T]) Less(i, j int) bool { return s.compare(s.s[i], s.s[j]) }
func (s sliceFn[T]) Swap(i, j int) { s.s[i], s.s[j] = s.s[j], s.s[i] }10. Not being aware of the possible problems with type embedding
Issue 1: 簡單 Embedding
- 僅為了語法糖而使用(
foo.Bazvsfoo.Bar.Baz) - 違反了「不應僅為簡化訪問而使用 embedding」的原則
type Foo struct {
Bar
}
type Bar struct {
Baz int
}Issue 2: Mutex Embedding
- 暴露了同步機制(Lock/Unlock 方法)
- 客戶端可以直接操作鎖,破壞封裝性
- 應該使用組合而非嵌入
// 不推薦
type InMem struct {
sync.Mutex
m map[string]int
}
// 推薦
type InMem struct {
mu sync.Mutex
data map[string]string
}Issue 3: 建構函數與接收器
- 更具描述性的命名(writer vs writeCloser)
- 添加建構函數
- 使用指標接收器
type Logger struct {
writer io.WriteCloser
}
// 建議:Pointer Receiver
func (l *Logger) Write(p []byte) (int, error) {
return l.writer.Write(p)
}
func (l *Logger) Close() error {
return l.writer.Close()
}