よくある間違い

Go 言語げんご一般的いっぱんてき間違まちがいとベストプラクティス。

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

  1. 関数かんすうはインターフェースではなく具体的ぐたいてきかたかえすべき
  2. 関数かんすう引数ひきすうとしてインターフェースをるべき

8. any says nothing

  • any本当ほんとう任意にんいかたれたりかえしたりする必要ひつようがある場合ばあいにのみ使用しよう(例:json.Marshal
  • any不適切ふてきせつ使用しようのデメリット:
    • 意味いみのあるかた情報じょうほう提供ていきょうできない
    • コンパイル問題もんだいこす可能性かのうせい
    • もと任意にんいかたのデータを使用しよう可能かのう
  • 過度かど汎用化はんようかける
  • 適度てきどなコードの重複じゅうふくany使用しようするより場合ばあいがある
  • any使用しようするまえ本当ほんとう任意にんいかた処理しょりする必要ひつようがあるか評価ひょうかすべき

9. Being confused about when to use generics

ジェネリクスを使つかわないほう場合ばあい

  1. かたパラメータのメソッドをすだけの場合ばあい
// 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)
}
  1. コードを複雑ふくざつにする場合ばあい
// Not Good: 過度な抽象化
func doSomething[T any](item T) T {
    return item
}

ジェネリクスを使つかうべき場合ばあい

  1. 汎用はんようデータ構造こうぞう
// Good: ジェネリックデータ構造
type Node[T any] struct {
    Val  T
    next *Node[T]
}

func (n *Node[T]) Add(next *Node[T]) {
    n.next = next
}
  1. 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
}
  1. 動作どうさパターンを抽象化ちゅうしょうかする場合ばあい
// 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.Baz vs foo.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()
}