よくある間違い
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 では Getter と Setter の強制使用は慣例ではない。価値を追加しないなら使用すべきではない。Go はシンプルな設計を推奨し、効率とプログラミング慣習のバランスを取るべき。
5. Interface pollution
インターフェース汚染
不必要な抽象インターフェースを事前に作成すべきではなく、本当に必要な時にのみ作成し、複雑さを避けるべき。良い抽象は強制されるのではなく発見されるべき。
6. Interface on the producer side
Go ではインターフェースの実装は暗黙的であり、通常はコンシューマーがインターフェースが必要かどうかを決定すべきで、プロデューサーが抽象を強制すべきではない。プロデューサーがインターフェースを提供<rt ていきょうする必要がある場合は、再利用性と組み合わせ性を高めるために最小限に保つべき。
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()
}