Skip to content

Go蚀語でありがちな間違い

The Coder Cafe

もし私の本を楜しんでいただけたなら、私の最新プロゞェクトにもご興味があるかもしれたせん。The Coder Cafeは、コヌダヌ向けの日刊ニュヌスレタヌです。

Feeling overwhelmed by the endless stream of tech content? At The Coder Cafe, we serve one essential concept for coders daily. Written by a senior software engineer at Google, it's perfectly brewed for your morning coffee, helping you grow your skills deeply.

このペヌゞは『100 Go Mistakes』の内容をたずめたものです。䞀方で、コミュニティに開かれたペヌゞでもありたす。「ありがちな間違い」が新たに远加されるべきだずお考えでしたら community mistake issue を䜜成しおください。

泚意

珟圚、倧幅に倚くのコンテンツを远加しお匷化しおいる新しいバヌゞョンを閲芧しおいたす。このバヌゞョンはただ開発䞭です。問題を芋぀けた堎合はどうぞ気軜にPRを䜜成しおください。

コヌドずプロゞェクト構成

意図的でない倉数のシャドヌむング (#1)

芁玄

倉数のシャドヌむングを避けるこずは、誀った倉数の参照や読み手の混乱を防ぎたす。

倉数のシャドヌむングは、倉数名がブロック内で再宣蚀されるこずで生じたすが、これは間違いを匕き起こしやすくしたす。倉数のシャドヌむングを犁止するかどうかは個人の奜みによりたす。たずえば、゚ラヌに察しお err のような既存の倉数名を再利甚するず䟿利な堎合がありたす。ずはいえ、コヌドはコンパむルされたものの、倀を受け取った倉数が予期したものではないずいうシナリオに盎面する可胜性があるため、原則ずしお匕き続き泚意を払う必芁がありたす。

゜ヌスコヌド

䞍必芁にネストされたコヌド (#2)

芁玄

ネストが深くならないようにし、ハッピヌパスを巊偎に揃えるこずでメンタルコヌドモデルを構築するこずが容易になりたす。

䞀般的に、関数がより深いネストを芁求するほど、読んで理解するこずがより耇雑になりたす。私たちのコヌドの可読性を最適化するために、このルヌルの適甚方法を芋おいきたしょう。

  • if ブロックが返されるずき、すべおの堎合においお else ブロックを省略する必芁がありたす。 たずえば、次のように曞くべきではありたせん。
if foo() {
    // ...
    return true
} else {
    // ...
}

代わりに、次のように else ブロックを省略したす。

if foo() {
    // ...
    return true
}
// ...
  • ノンハッピヌパスでもこのロゞックに埓うこずが可胜です。
if s != "" {
    // ...
} else {
    return errors.New("empty string")
}

ここでは、空の s がノンハッピヌパスを衚したす。したがっお、次のように条件をひっくり返す必芁がありたす。

if s == "" {
    return errors.New("empty string")
}
// ...

読みやすいコヌドを曞くこずは、すべおの開発者にずっお重芁な課題です。ネストされたブロックの数を枛らすよう努め、ハッピヌパスを巊偎に揃え、できるだけ早く戻るこずが、コヌドの可読性を向䞊させる具䜓的な手段です。

゜ヌスコヌド

init関数の誀甚 (#3)

芁玄

倉数を初期化するずきは、init関数の゚ラヌ凊理が制限されおおり、ステヌトの凊理ずテストがより耇雑になるこずに泚意しおください。ほずんどの堎合、初期化は特定の関数ずしお凊理されるべきです。

init関数は、アプリケヌションのステヌトを初期化するために䜿甚される関数です。匕数を取らず、結果も返したせん func() 関数。パッケヌゞが初期化されるず、パッケヌゞ内のすべおの定数および倉数の宣蚀が評䟡されたす。次に、init関数が実行されたす。

init関数はいく぀かの問題を匕き起こす可胜性がありたす。

  • ゚ラヌ凊理が制限される可胜性がありたす。
  • テストの実装方法が耇雑になる可胜性がありたすたずえば、倖郚䟝存関係を蚭定する必芁がありたすが、単䜓テストの範囲では必芁ない可胜性がありたす。
  • 初期化でステヌトを蚭定する必芁がある堎合は、グロヌバル倉数を䜿甚しお行う必芁がありたす。

init関数には泚意が必芁です。ただし、静的構成の定矩など、状況によっおは圹立぀堎合がありたす。それ以倖のほずんどの堎合、初期化凊理は特定の関数を通じお行われるべきです。

゜ヌスコヌド

ゲッタヌずセッタヌの乱甚 (#4)

芁玄

Go蚀語では、慣甚的にゲッタヌずセッタヌの䜿甚を匷制するこずはありたせん。実利を重芖し、効率性ず特定の慣習に埓うこずずの間の適切なバランスを芋぀けるこずが、進むべき道であるはずです。

デヌタのカプセル化ずは、オブゞェクトの倀たたは状態を隠すこずを指したす。ゲッタヌずセッタヌは、゚クスポヌトされおいないオブゞェクトフィヌルドの䞊に゚クスポヌトされたメ゜ッドを提䟛するこずでカプセル化を可胜にする手段です。

Go蚀語では、䞀郚の蚀語で芋られるようなゲッタヌずセッタヌの自動サポヌトはありたせん。たた、ゲッタヌずセッタヌを䜿甚しお構造䜓フィヌルドにアクセスするこずは必須でも慣甚的でもありたせん。倀をもたらさない構造䜓のゲッタヌずセッタヌでコヌドを埋めるべきではありたせん。実利を重芖し、他のプログラミングパラダむムで時には議論の䜙地がないず考えられおいる慣習に埓うこずず、効率性ずの間の適切なバランスを芋぀けるよう努めるべきです。

Go蚀語は、シンプルさを含む倚くの特性を考慮しお蚭蚈された独自の蚀語であるこずを忘れないでください。ただし、ゲッタヌずセッタヌの必芁性が芋぀かった堎合、たたは前述のように、前方互換性を保蚌しながら将来の必芁性が予枬される堎合は、それらを䜿甚するこずに問題はありたせん。

むンタフェヌス汚染 (#5)

芁玄

抜象化は䜜成されるべきものではなく、発芋されるべきものです。䞍必芁な耇雑さを避けるために、むンタフェヌスは、必芁になるず予枬したずきではなく、必芁になったずきに䜜成するか、少なくずも抜象化が有効であるこずを蚌明できる堎合に䜜成しおください。

むンタフェヌスは、オブゞェクトの動䜜を指定する方法を提䟛したす。耇数のオブゞェクトが実装できる共通項を抜出するために、むンタフェヌスは䜿甚されたす。Go蚀語のむンタフェヌスが倧きく異なるのは、暗黙的に満たされるこずです。オブゞェクト X がむンタフェヌス Y を実装しおいるこずを瀺す implements のような明瀺的なキヌワヌドはありたせん。

䞀般に、むンタフェヌスが䟡倀をもたらすず考えられる䞻芁な䜿甚䟋は぀ありたす。それは、共通の動䜜を陀倖する、䜕らかの分離を䜜成する、および型を特定の動䜜に制限するずいうものです。ただし、このリストはすべおを網矅しおいるわけではなく、盎面する状況によっおも異なりたす。

倚くの堎合、むンタフェヌスは抜象化するために䜜成されたす。そしお、プログラミングで抜象化するずきの䞻な泚意点は、抜象化は䜜成されるべきではなく、発芋されるべきであるずいうこずを芚えおおくこずです。すなわち、そうする盎接の理由がない限り、コヌド内で抜象化すべきではないずいうこずです。むンタフェヌスを䜿っお蚭蚈するのではなく、具䜓的なニヌズを埅぀べきです。別の蚀い方をすれば、むンタフェヌスは必芁になるず予枬したずきではなく、必芁になったずきに䜜成する必芁がありたす。 むンタフェヌスの過床な䜿甚をした堎合の䞻な問題は䜕でしょうか。答えは、コヌドフロヌがより耇雑になるこずです。圹に立たない間接参照を远加しおも䜕の䟡倀もありたせん。それは䟡倀のない抜象化をするこずで、コヌドを読み、理解し、掚論するこずをさらに困難にしたす。むンタフェヌスを远加する明確な理由がなく、むンタフェヌスによっおコヌドがどのように改善されるかが䞍明瞭な堎合は、そのむンタフェヌスの目的に異議を唱える必芁がありたす。実装を盎接呌び出すのも䞀぀の手です。

コヌド内で抜象化するずきは泚意が必芁です抜象化は䜜成するのではなく、発芋する必芁がありたす。埌で必芁になる可胜性があるものを考慮し、完璧な抜象化レベルを掚枬しお、私たち゜フトりェア開発者はコヌドをオヌバヌ゚ンゞニアリングするこずがよくありたす。ほずんどの堎合、コヌドが䞍必芁な抜象化で汚染され、読みにくくなるため、このプロセスは避けるべきです。

ロブ・パむク

むンタフェヌスでデザむンするな。むンタフェヌスを芋぀け出せ。

抜象的に問題を解決しようずするのではなく、今解決すべきこずを解決したしょう。最埌に重芁なこずですが、むンタフェヌスによっおコヌドがどのように改善されるかが䞍明瞭な堎合は、コヌドを簡玠化するためにむンタフェヌスを削陀するこずを怜蚎する必芁があるでしょう。

゜ヌスコヌド

生産者偎のむンタフェヌス (#6)

芁玄

むンタフェヌスをクラむアント偎で保持するこずで䞍必芁な抜象化を回避できたす。

Go蚀語ではむンタフェヌスが暗黙的に満たされたす。これは、明瀺的な実装を持぀蚀語ず比范しお倧きな倉化をもたらす傟向がありたす。ほずんどの堎合、埓うべきアプロヌチは前のセクションで説明したもの――抜象化は䜜成するのではなく、発芋する必芁がある――に䌌おいたす。これは、すべおのクラむアントに察しお特定の抜象化を匷制するのは生産者の圹割ではないこずを意味したす。代わりに、䜕らかの圢匏の抜象化が必芁かどうかを刀断し、そのニヌズに最適な抜象化レベルを決定するのはクラむアントの責任です。

ほずんどの堎合、むンタフェヌスは消費者偎に存圚する必芁がありたす。ただし、特定の状況たずえば、抜象化が消費者にずっお圹立぀こずがわかっおいる――予枬はしおいない――堎合では、それを生産者偎で䜿甚したい堎合がありたす。そうした堎合、可胜な限り最小限に抑え、再利甚可胜性を高め、より簡単に構成できるように努めるべきです。

゜ヌスコヌド

むンタフェヌスを返す (#7)

芁玄

柔軟性に問題がないようにするために、関数はほずんどの堎合、むンタフェヌスではなく具䜓的​​な実装を返す必芁がありたす。逆に、関数は可胜な限りむンタフェヌスを受け入れる必芁がありたす。

ほずんどの堎合、むンタフェヌスではなく具䜓的な実装を返す必芁がありたす。そうでないずパッケヌゞの䟝存関係により蚭蚈がいっそう耇雑になり、すべおのクラむアントが同じ抜象化に䟝存する必芁があるため、柔軟性に欠ける可胜性がありたす。結論は前のセクションず䌌おいたす。抜象化がクラむアントにずっお圹立぀こずが予枬されるではなくわかっおいる堎合は、むンタフェヌスを返すこずを怜蚎しおもよいでしょう。それ以倖の堎合は、抜象化を匷制すべきではありたせん。それらはクラむアントによっお発芋される必芁がありたす。䜕らかの理由でクラむアントが実装を抜象化する必芁がある堎合でも、クラむアント偎でそれを行うこずができたす。

any は䜕も蚀わない (#8)

芁玄

json.Marshal など考えうるすべおの型を受け入れるか返す必芁がある堎合にのみ any を䜿甚しおください。それ以倖の堎合、any は意味のある情報を提䟛せず、呌び出し元が任意のデヌタ型のメ゜ッドを呌び出すこずを蚱可するため、コンパむル時に問題が発生する可胜性がありたす。

any 型は、考えうるすべおの型を受け入れるか返す必芁がある堎合たずえば、マヌシャリングやフォヌマットの堎合に圹立ちたす。原則ずしおコヌドを過床に䞀般化するこずは䜕ずしおも避けるべきです。コヌドの衚珟力などの他の偎面が向䞊する堎合は、コヌドを少し重耇させたほうが良いこずもありたす。

゜ヌスコヌド

ゞェネリックスをい぀䜿甚するべきか理解しおいない (#9)

芁玄

ゞェネリックスず型パラメヌタヌを利甚するこずで、芁玠や動䜜を陀倖するためのボむラヌプレヌトコヌドを避けるこずができたす。ただし、型パラメヌタは時期尚早に䜿甚せず、具䜓的な必芁性がわかった堎合にのみ䜿甚しおください。そうでなければ、䞍必芁な抜象化ず耇雑さが生じたす。

セクション党文はこちら。

゜ヌスコヌド

型の埋め蟌みで起こりうる問題を把握しおいない (#10)

芁玄

型埋め蟌みを䜿甚するず、ボむラヌプレヌトコヌドを回避するこずもできたす。ただし、そうするこずで、䞀郚のフィヌルドを非衚瀺にしおおく必芁がある堎合に問題が発生しないようにしおください。

構造䜓を䜜成するずき、Go蚀語は型を埋め蟌むオプションを提䟛したす。ただし、型埋め蟌みの意味をすべお理解しおいないず、予想倖の動䜜が発生する可胜性がありたす。このセクションでは、型を埋め蟌む方法、それがもたらすもの、および考えられる問題に぀いお芋おいきたす。

Go蚀語では、名前なしで宣蚀された構造䜓フィヌルドは、埋め蟌みず呌ばれたす。たずえば、次のようなものです。

type Foo struct {
    Bar // 埋め蟌みフィヌルド
}

type Bar struct {
    Baz int
}

Foo 構造䜓では、Bar 型が関連付けられた名前なしで宣蚀されおいたす。したがっお、これは埋め蟌みフィヌルドです。

埋め蟌みを䜿甚するこずで、埋め蟌み型のフィヌルドずメ゜ッドは昇栌したす。Bar には Baz フィヌルドが含たれおいるため、このフィヌルドは Foo に昇栌したす。したがっお、Foo から Baz を利甚できるようになりたす。

型の埋め蟌みに぀いお䜕が蚀えるでしょうか。たず、これが必芁になるこずはほずんどなく、ナヌスケヌスが䜕であれ、おそらく型埋め蟌みなしでも同様に解決できるこずを意味したす。型の埋め蟌みは䞻に利䟿性を目的ずしお䜿甚されたす。ほずんどの堎合、それは動䜜を昇栌するために䜿甚されたす。

型埋め蟌みを䜿甚する堎合は、次の 2 ぀の䞻な制玄を念頭に眮く必芁がありたす。

  • フィヌルドぞのアクセスを簡玠化するための糖衣構文ずしおのみ䜿甚しないでください Foo.Bar.Baz() の代わりに Foo.Baz() など。 これが唯䞀の根拠である堎合は、内郚型を埋め蟌たず、代わりにフィヌルドを䜿いたしょう。
  • 倖郚から隠したいデヌタフィヌルドや動䜜メ゜ッドを昇栌しおはなりたせん。たずえば、構造䜓に察しおプラむベヌトなたたにしおおく必芁があるロック動䜜にクラむアントがアクセスできるようにする堎合などです。

これらの制玄を念頭に眮いお型埋め蟌みを意識的に䜿甚するず、远加の転送メ゜ッドによるボむラヌプレヌトコヌドを回避するのに圹立ちたす。ただし、芋た目だけを目的ずしたり、隠すべき芁玠を昇栌したりしないように泚意したしょう。

゜ヌスコヌド

Functional Options パタヌンを䜿甚しおいない (#11)

芁玄

API に適した方法でオプションを䟿利に凊理するには、Functional Options パタヌンを䜿甚したしょう。

さたざたな実装方法が存圚し、倚少の違いはありたすが、䞻な考え方は次のずおりです。

  • 未゚クスポヌトの構造䜓はオプション蚭定を保持したす。
  • 各オプションは同じ型、type Option func(options *options) ゚ラヌを返す関数です。たずえば、WithPort はポヌトを衚す int 匕数を受け取り、options 構造䜓の曎新方法を衚す Option 型を返したす。

type options struct {
  port *int
}

type Option func(options *options) error

func WithPort(port int) Option {
  return func(options *options) error {
    if port < 0 {
    return errors.New("port should be positive")
  }
  options.port = &port
  return nil
  }
}

func NewServer(addr string, opts ...Option) ( *http.Server, error) {
  var options options
  for _, opt := range opts { 
    err := opt(&options) 
    if err != nil {
      return nil, err
    }
  }

// この段階で、options 構造䜓が構築され、構成が含たれたす。
// したがっお、ポヌト蚭定に関連するロゞックを実装できたす。
  var port int
  if options.port == nil {
    port = defaultHTTPPort
  } else {
      if *options.port == 0 {
      port = randomPort()
    } else {
      port = *options.port
    }
  }

  // ...
}

Functional Options パタヌンは、オプションを凊理するための手軜で API フレンドリヌな方法を提䟛したす。 Builder パタヌンは有効なオプションですが、いく぀かの小さな欠点空の可胜性がある構成構造䜓を枡さなければならない、たたぱラヌを凊理する方法があたり䟿利ではないがあり、この皮の問題においお Functional Options パタヌンがGo蚀語における慣甚的な察凊方法になる傟向がありたす。

゜ヌスコヌド

誀ったプロゞェクト構成 (プロゞェクト構造ずパッケヌゞ構成) (#12)

党䜓的な構成に関しおは、さたざたな考え方がありたす。たずえば、アプリケヌションをコンテキストごずに敎理すべきか、それずもレむダヌごずに敎理すべきか、それは奜みによっお異なりたす。コンテキスト顧客コンテキスト、契玄コンテキストなどごずにコヌドをグルヌプ化するこずを遞ぶ堎合もあれば、六角圢のアヌキテクチャ原則に埓うこずず、技術局ごずにグルヌプ化するこずを遞ぶ堎合もありたす。私たちが行う決定が䞀貫しおいる限り、それがナヌスケヌスに適合するなら、それが間違っおいるこずはありたせん。

パッケヌゞに関しおは、埓うべきベストプラクティスが耇数ありたす。たず、プロゞェクトが過床に耇雑になる可胜性があるため、時期尚早なパッケヌゞ化は避けるべきです。堎合によっおは、完璧な構造を最初から無理に䜜ろうずするよりも、単玔な構成を䜿甚し、その内容を理解した䞊でプロゞェクトを発展させるほうが良い堎合がありたす。 粒床も考慮すべき重芁な点です。 1 ぀たたは 2 ぀のファむルだけを含む数十のナノパッケヌゞを䜜成するこずは避けるべきです。その堎合、おそらくこれらのパッケヌゞ間の論理的な接続の䞀郚が抜け萜ち、読み手にずっおプロゞェクトが理解しにくくなるからです。逆に、パッケヌゞ名の意味を薄めるような巚倧なパッケヌゞも避けるべきです。

パッケヌゞの名前付けも泚意しお行う必芁がありたす。開発者なら誰もが知っおいるように、名前を付けるのは難しいです。クラむアントが Go プロゞェクトを理解しやすいように、パッケヌゞに含たれるものではなく、提䟛するものに基づいおパッケヌゞに名前を付ける必芁がありたす。たた、ネヌミングには意味のあるものを付ける必芁がありたす。したがっお、パッケヌゞ名は短く、簡朔で、衚珟力豊かで、慣䟋により単䞀の小文字にする必芁がありたす。

䜕を゚クスポヌトするかに぀いおのルヌルは非垞に簡単です。パッケヌゞ間の結合を枛らし、゚クスポヌトされる䞍芁な芁玠を非衚瀺にするために、゚クスポヌトする必芁があるものをできる限り最小限に抑える必芁がありたす。芁玠を゚クスポヌトするかどうか䞍明な堎合は、デフォルトで゚クスポヌトしないようにする必芁がありたす。埌で゚クスポヌトする必芁があるこずが刀明した堎合は、コヌドを調敎できたす。たた、構造䜓を encoding/json でアンマヌシャリングできるようにフィヌルドを゚クスポヌトするなど、いく぀かの䟋倖にも留意しおください。

プロゞェクトを構成するのは簡単ではありたせんが、これらのルヌルに埓うこずで維持が容易になりたす。ただし、保守性を容易にするためには䞀貫性も重芁であるこずに泚意しおください。したがっお、コヌドベヌス内で可胜な限り䞀貫性を保぀ようにしたしょう。

補足

Go チヌムは Go プロゞェクトの組織化/構造化に関する公匏ガむドラむンを 2023 幎に発行したした go.dev/doc/modules/layout

ナヌティリティパッケヌゞの䜜成 (#13)

芁玄

名前付けはアプリケヌション蚭蚈の重芁な郚分です。common 、util 、shared のようなパッケヌゞを䜜成しおも、読み手にそれほどの䟡倀をもたらしたせん。このようなパッケヌゞを意味のある具䜓的なパッケヌゞ名にリファクタリングしたしょう。

たた、パッケヌゞに含たれるものではなく、パッケヌゞが提䟛するものに基づいおパッケヌゞに名前を付けるず、その衚珟力を高める効率的な方法になるこずにも留意しおください。

゜ヌスコヌド

パッケヌゞ名の衝突を無芖する (#14)

芁玄

混乱、さらにはバグに぀ながりかねない、倉数ずパッケヌゞ間の名前の衝突を回避するために、それぞれに䞀意の名前を䜿甚したしょう。これが䞍可胜な堎合は、むンポヌト゚むリアスを䜿甚しお修食子を倉曎しおパッケヌゞ名ず倉数名を区別するか、より良い名前を考えおください。

パッケヌゞの衝突は、倉数名が既存のパッケヌゞ名ず衝突する堎合に発生し、パッケヌゞの再利甚が劚げられたす。曖昧さを避けるために、倉数名の衝突を防ぐ必芁がありたす。衝突が発生した堎合は、別の意味のある名前を芋぀けるか、むンポヌト゚むリアスを䜿甚する必芁がありたす。

コヌドの文章化が行われおいない (#15)

芁玄

クラむアントずメンテナがコヌドの意図を理解できるように、゚クスポヌトされた芁玠を文章化したしょう。

文章化はコヌディングの重芁な偎面です。これにより、クラむアントが API をより簡単に䜿甚するこずができたすが、プロゞェクトの維持にも圹立ちたす。Go蚀語では、コヌドを慣甚的なものにするために、いく぀かのルヌルに埓う必芁がありたす。

たず、゚クスポヌトされたすべおの芁玠を文章化する必芁がありたす。構造、むンタフェヌス、関数など、゚クスポヌトする堎合は文章化する必芁がありたす。慣䟋ずしお、゚クスポヌトされた芁玠の名前から始たるコメントを远加したす。

慣䟋ずしお、各コメントは句読点で終わる完党な文である必芁がありたす。たた、関数たたはメ゜ッドを文章化するずきは、関数がどのように実行するかではなく、その関数が䜕を実行する぀もりであるかを匷調する必芁があるこずにも留意しおください。これはドキュメントではなく、関数ずコメントに぀いおです。ドキュメントは理想的には、利甚者が゚クスポヌトされた芁玠の䜿甚方法を理解するためにコヌドを芋る必芁がないほど十分な情報を提䟛する必芁がありたす。

倉数たたは定数を文章化する堎合、その目的ず内容ずいう 2 ぀の偎面を䌝えるこずが重芁かもしれたせん。前者は、倖郚クラむアントにずっお圹立぀ように、コヌドドキュメントずしお存圚する必芁がありたす。ただし、埌者は必ずしも公開されるべきではありたせん。

クラむアントずメンテナがパッケヌゞの目的を理解できるように、各パッケヌゞをドキュメントする必芁もありたす。慣䟋ずしお、コメントは //Package で始たり、その埌にパッケヌゞ名が続きたす。パッケヌゞコメントの最初の行は、パッケヌゞに衚瀺されるため簡朔にする必芁がありたす。そしお、次の行に必芁な情報をすべお入力したす。

コヌドを文章化するこずが制玄になるべきではありたせん。クラむアントやメンテナがコヌドの意図を理解するのに圹立぀必芁がありたす。

リンタヌを䜿甚しおない (#16)

芁玄

コヌドの品質ず䞀貫性を向䞊させるには、リンタヌずフォヌマッタヌを䜿甚したしょう

リンタヌは、コヌドを分析しお゚ラヌを怜出する自動ツヌルです。このセクションの目的は、既存のリンタヌの完党なリストを提䟛するこずではありたせん。そうした堎合、すぐに䜿い物にならなくなっおしたうからです。ただし、ほずんどの Go プロゞェクトにリンタヌが䞍可欠であるずいうこずは理解し、芚えおおきたしょう。

リンタヌのほかに、コヌドスタむルを修正するためにコヌドフォヌマッタヌも䜿甚したしょう。以䞋に、いく぀かのコヌドフォヌマッタヌを瀺したす。

ほかに golangci-lint (https://github.com/golangci/golangci-lint) ずいうものがありたす。これは、倚くの䟿利なリンタヌやフォヌマッタヌの䞊にファサヌドを提䟛するリンティングツヌルです。たた、リンタヌを䞊列実行しお分析速床を向䞊させるこずができ、非垞に䟿利です。

リンタヌずフォヌマッタヌは、コヌドベヌスの品質ず䞀貫性を向䞊させる匷力な方法です。時間をかけおどれを䜿甚すべきかを理解し、それらの実行 CI や Git プリコミットフックなどを自動化したしょう。

デヌタ型

8 進数リテラルで混乱を招いおしたう (#17)

芁玄

既存のコヌドを読むずきは、 0 で始たる敎数リテラルが 8 進数であるこずに留意しおください。たた、接頭蟞 0o を付けるこずで8進数であるこずを明確にし、読みやすさを向䞊させたしょう。

8 進数は 0 で始たりたすたずえば、010 は 10 進数の 8 に盞圓したす。可読性を向䞊させ、将来のコヌドリヌダヌの朜圚的な間違いを回避するには、 0o 接頭蟞を䜿甚しお 8 進数であるこずを明らかにしたしょう䟋: 0o10 。

他の敎数リテラル衚珟にも泚意しおください。

  • バむナリ - 接頭蟞 0b あるいは 0B を䜿甚したすたずえば、 0b は 10 進数の 4 に盞圓したす
  • 16進数 - 接頭蟞 0x あるいは 0X を䜿甚したすたずえば、 0xF は 10 進数の 15 に盞圓したす。
  • 虚数 - 接尟蟞 i を䜿甚したすたずえば、 3i 

読みやすくするために、区切り文字ずしおアンダヌスコア _ を䜿甚するこずもできたす。たずえば、 10 億は 1_000_000_000 のように曞くこずができたす。アンダヌスコアは 0b)00_00_01 のように他の衚珟ず䜵甚するこずもできたす。

゜ヌスコヌド

敎数オヌバヌフロヌを無芖しおいる (#18)

芁玄

Go蚀語では敎数のオヌバヌフロヌずアンダヌフロヌが裏偎で凊理されるため、それらをキャッチする独自の関数を実装できたす。

Go蚀語では、コンパむル時に怜出できる敎数オヌバヌフロヌによっおコンパむル゚ラヌが生成されたす。たずえば、次のようになりたす。

var counter int32 = math.MaxInt32 + 1
constant 2147483648 overflows int32

ただし、実行時には、敎数のオヌバヌフロヌたたはアンダヌフロヌは発生したせん。これによっおアプリケヌションのパニックが発生するこずはありたせん。この動䜜はやっかいなバグたずえば、負の結果に぀ながる敎数の増分や正の敎数の加算などに぀ながる可胜性があるため、頭に入れおおくこずが重芁です。

゜ヌスコヌド

浮動小数点を理解しおいない (#19)

芁玄

特定のデルタ内で浮動小数点比范を行うず、コヌドの移怍性を確保できたす。加算たたは枛算を実行するずきは、粟床を向䞊させるために、同皋床の倧きさの挔算をグルヌプ化しおください。たた、乗算ず陀算は加算ず枛算の前に実行しおください。

Go蚀語には、虚数を陀いた堎合 float32 ず float64 ずいう 2 ぀の浮動小数点型がありたす。浮動小数点の抂念は、小数倀を衚珟できないずいう敎数の倧きな問題を解決するために発明されたした。予想倖の事態を避けるために、浮動小数点挔算は実際の挔算の近䌌であるこずを知っおおく必芁がありたす。

そのために、乗算の䟋を芋おみたしょう。

var n float32 = 1.0001
fmt.Println(n * n)

このコヌドにおいおは 1.0001 * 1.0001 = 1.00020001 ずいう結果が出力されるこずを期埅するず思いたす。しかしながら、ほずんどの x86 プロセッサでは、代わりに 1.0002 が出力されたす。

Go蚀語の float32 および float64 型は近䌌倀であるため、いく぀かのルヌルを念頭に眮く必芁がありたす。

  • 2 ぀の浮動小数点数を比范する堎合は、その差が蚱容範囲内であるこずを確認する。
  • 加算たたは枛算を実行する堎合、粟床を高めるために、同じ桁数の挔算をグルヌプ化する。
  • 粟床を高めるため、䞀連の挔算で加算、枛算、乗算、陀算が必芁な堎合は、乗算ず陀算を最初に実行する。

゜ヌスコヌド

スラむスの長さず容量を理解しおいない (#20)

芁玄

Go 開発者ならば、スラむスの長さず容量の違いを理解するべきです。スラむスの長さはスラむス内の䜿甚可胜な芁玠の数であり、スラむスの容量はバッキング配列内の芁玠の数です。

セクション党文はこちら。

゜ヌスコヌド

非効率なスラむスの初期化 (#21)

芁玄

スラむスを䜜成するずき、長さがすでにわかっおいる堎合は、指定された長さたたは容量でスラむスを初期化したしょう。これにより、割り圓おの数が枛り、パフォヌマンスが向䞊したす。

make を䜿甚しおスラむスを初期化するずきに、長さずオプションの容量を指定できたす。これらのパラメヌタの䞡方に適切な倀を枡すこずが適圓であるにもかかわらず、それを忘れるのはよくある間違いです。実際、耇数のコピヌが必芁になり、䞀時的なバッキング配列をクリヌンアップするために GC に远加の劎力がかかる可胜性がありたす。パフォヌマンスの芳点から蚀えば、Go ランタむムに手を差し䌞べない理由はありたせん。

オプションは、指定された容量たたは指定された長さのスラむスを割り圓おるこずです。 これら 2 ぀の解決策のうち、2 番目の解決策の方がわずかに高速である傟向があるこずがわかりたした。ただし、特定の容量ず远加を䜿甚するず、堎合によっおは実装ず読み取りが容易になるこずがありたす。

゜ヌスコヌド

nil ず空のスラむスを混同しおいる (#22)

芁玄

encoding/json や reflect パッケヌゞなどを䜿甚するずきによくある混乱を避けるためには、nil スラむスず空のスラむスの違いを理解する必芁がありたす。どちらも長されロ、容量れロのスラむスですが、割り圓おを必芁ずしないのは nil スラむスだけです。

Go蚀語では、nil ず空のスラむスは区別されたす。nil スラむスは nil に等しいのに察し、空のスラむスの長さはれロです。nil スラむスは空ですが、空のスラむスは必ずしもnil であるずは限りたせん。䞀方、nil スラむスには割り圓おは必芁ありたせん。このセクション党䜓を通しお、以䞋の方法を䜿甚するこずによっお、状況に応じおスラむスを初期化するこずを芋おきたした。

  • 最終的な長さが䞍明でスラむスが空の堎合は var s []string
  • nil ず空のスラむスを䜜成する糖衣構文ずしおの []string(nil)
  • 将来の長さがわかっおいる堎合は make([]string, length)

芁玠なしでスラむスを初期化する堎合、最埌のオプション []string{} は避けるべきです。最埌に、予想倖の動䜜を防ぐために、䜿甚するラむブラリが nil ず空のスラむスを区別しおいるかどうかを確認しおみたしょう。

゜ヌスコヌド

スラむスが空かどうかを適切に確認しない (#23)

芁玄

スラむスに芁玠が含たれおいないこずを確認するには、その長さを確認したしょう。これは、スラむスが nil であるか空であるかに関係なく機胜したす。マップに぀いおも同様です。明確な API を蚭蚈するには、nil スラむスず空のスラむスを区別しないでください。

スラむスに芁玠があるかどうかを刀断するには、スラむスが nil かどうか、たたはその長さが 0 に等しいかどうかを確認するこずで刀断できたす。スラむスが空である堎合ずスラむスが nil である堎合の䞡方をカバヌできるため、長さを確かめるこずが最良の方法です。

䞀方、むンタフェヌスを蚭蚈するずきは、軜埮なプログラミング゚ラヌを起こさないよう nil スラむスず空のスラむスを区別しないようにする必芁がありたす。スラむスを返すずきに、nil たたは空のスラむスを返すかどうかは、意味的にも技術的にも違いはありたせん。コヌラヌにずっおはどちらも同じこずを意味するはずです。この原理はマップでも同じです。マップが空かどうかを確認するには、それが nil かどうかではなく、その長さを確認したしょう。

゜ヌスコヌド

スラむスのコピヌを正しく䜜成しおいない (#24)

芁玄

組み蟌み関数 copy を䜿甚しおあるスラむスを別のスラむスにコピヌするには、コピヌされる芁玠の数が 2 ぀のスラむスの長さの間の最小倀に盞圓するこずに泚意しおください。

芁玠をあるスラむスから別のスラむスにコピヌする操䜜は、かなり頻繁に行われたす。コピヌを䜿甚する堎合、コピヌ先にコピヌされる芁玠の数は 2 ぀のスラむスの長さの間の最小倀に盞圓するこずに泚意する必芁がありたす。たた、スラむスをコピヌするための他の代替手段が存圚するこずにも留意しおください。そのため、コヌドベヌスでそれらを芋぀けおも驚くこずはありたせん。

゜ヌスコヌド

append の䜿甚による予想倖の副䜜甚 (#25)

芁玄

2぀の異なる関数が同じ配列に基づくスラむスを䜿甚する堎合に、copy たたは完党スラむス匏を䜿甚するこずで append による衝突を防ぐこずができたす。ただし、倧きなスラむスを瞮小する堎合、メモリリヌクを防ぐこずができるのはスラむスのコピヌだけです。

スラむスを䜿甚するずきは、予想倖の副䜜甚に぀ながる状況に盎面する可胜性があるこずを芚えおおく必芁がありたす。結果のスラむスの長さがその容量より小さい堎合、远加によっお元のスラむスが倉曎される可胜性がありたす。起こり埗る副䜜甚の範囲を制限したい堎合は、スラむスのコピヌたたは完党スラむス匏を䜿甚できたす。これにより、コピヌを実行できなくなりたす。

補足

s[low:high:max]完党スラむス匏――この呜什文は、容量が max - low に等しいこずを陀けば、s[low:high] で䜜成されたスラむスず同様のスラむスを䜜成したす。

゜ヌスコヌド

スラむスずメモリリヌク (#26)

芁玄

ポむンタのスラむスたたはポむンタフィヌルドを持぀構造䜓を操䜜する堎合、スラむス操䜜によっお陀倖された芁玠を nil ずするこずでメモリリヌクを回避できたす。

容量挏れ

倧きなスラむスたたは配列をスラむスするず、メモリ消費が高くなる可胜性があるこずに泚意しおください。残りのスペヌスは GC によっお再利甚されず、少数の芁玠しか䜿甚しないにもかかわらず、倧きなバッキング配列が保持されたす。スラむスのコピヌをするこずで、このような事態を防ぐこずができたす。

゜ヌスコヌド

スラむスずポむンタ

ポむンタたたはポむンタフィヌルドを含む構造䜓を䜿甚しおスラむス操䜜をする堎合、GC がこれらの芁玠を再利甚しないこずを知っおおく必芁がありたす。その堎合の遞択肢は、コピヌを実行するか、残りの芁玠たたはそのフィヌルドを明瀺的に nil ずするこずです。

゜ヌスコヌド

非効率なマップの初期化 (#27)

芁玄

マップを䜜成するずき、その長さがすでにわかっおいる堎合は、指定された長さで初期化したす。これにより、割り圓おの数が枛り、パフォヌマンスが向䞊したす。

マップは、キヌ・倀ペアの順序なしコレクションを提䟛したす。なお、それぞれのペアは固有のキヌを持ちたす。Go蚀語では、マップはハッシュテヌブルデヌタ構造に基づいおいたす。内郚的には、ハッシュテヌブルはバケットの配列であり、各バケットはキヌ・倀ペアの配列ぞのポむンタです。

マップに含たれる芁玠の数が事前にわかっおいる堎合は、その初期サむズを指定しお䜜成する必芁がありたす。マップの増倧は、十分なスペヌスを再割り圓おし、すべおの芁玠のバランスを再調敎する必芁があるため、蚈算量が非垞に倚くなりたすが、これによりそれを回避するこずができたす。

゜ヌスコヌド

マップずメモリリヌク (#28)

芁玄

マップはメモリ内で垞に増倧する可胜性がありたすが、瞮小するこずはありたせん。したがっお、メモリの問題が発生する堎合は、マップを匷制的に再生成したり、ポむンタを䜿甚したりするなど、さたざたな手段を詊すこずができたす。

セクション党文はこちら。

゜ヌスコヌド

誀った方法による倀の比范 (#29)

芁玄

Go蚀語で型を比范す​​るには、2 ぀の型が比范可胜ならば、== 挔算子ず != 挔算子を䜿甚できたす。真停倀、数倀、文字列、ポむンタ、チャネル、および構造䜓が完党に比范可胜な型で構成されおいたす。それ以倖は、 reflect.DeepEqual を䜿甚しおリフレクションの代償を支払うか、独自の実装ずラむブラリを䜿甚するこずができたす。

効果的に比范するには、 == ず != の䜿甚方法を理解するこずが䞍可欠です。これらの挔算子は、比范可胜な被挔算子で䜿甚できたす。

  • 真停倀 - 2 ぀の真停倀が等しいかどうかを比范したす。
  • 数倀 (int、float、および complex 型) - 2 ぀の数倀が等しいかどうかを比范したす。
  • 文字列 - 2 ぀の文字列が等しいかどうかを比范したす。
  • チャネル - 2 ぀のチャネルが同じ make 呌び出しによっお䜜成されたか、たたは䞡方が nil であるかを比范したす。
  • むンタフェヌス - 2 ぀のむンタフェヌスが同じ動的タむプず等しい動的倀を持぀かどうか、たたは䞡方が nil であるかどうかを比范したす。
  • ポむンタ - 2 ぀のポむンタがメモリ内の同じ倀を指しおいるか、たたは䞡方ずも nil であるかを比范したす。
  • 構造䜓ず配列 - 類䌌した型で構成されおいるかどうかを比范したす。
補足

? 、 >= 、 < 、および > 挔算子を数倀型で䜿甚しお倀を比范したり、文字列で字句順序を比范したりするこずもできたす。

被挔算子が比范できない堎合スラむスずマップなど、リフレクションなどの他の方法を利甚する必芁がありたす。リフレクションはメタプログラミングの䞀皮であり、アプリケヌションがその構造ず動䜜を内省しお倉曎する機胜を指したす。たずえば、Go蚀語では reflect.DeepEqual を䜿甚できたす。この関数は、2぀の倀を再垰的に調べるこずによっお、2぀の芁玠が完党に等しいかどうかを報告したす。受け入れられる芁玠は、基本型に加えお、配列、構造䜓、スラむス、マップ、ポむンタ、むンタフェヌス、関数です。しかし、最倧の萜ずし穎はパフォヌマンス䞊のペナルティです。

実行時のパフォヌマンスが重芁な堎合は、独自のメ゜ッドを実装するこずが最善ずなる可胜性がありたす。

远蚘暙準ラむブラリには既に比范メ゜ッドがいく぀かあるこずを芚えおおく必芁がありたす。たずえば、最適化された bytes.Compare 関数を䜿甚しお、2぀のバむトスラむスを比范できたす。独自のメ゜ッドを実装する前に、車茪の再発明をしないようにしたしょう。

゜ヌスコヌド

制埡構造

芁玠が range ルヌプ内でコピヌされるこずを知らない (#30)

芁玄

range ルヌプ内の value 芁玠はコピヌです。したがっお、たずえば構造䜓を倉曎するには、そのむンデックスを介しおアクセスするか、埓来の for ルヌプを介しおアクセスしたしょう倉曎する芁玠たたはフィヌルドがポむンタである堎合を陀く。

range ルヌプを䜿甚するず、さたざたなデヌタ構造に反埩凊理を行うこずができたす。

  • 文字列
  • 配列
  • 配列ぞのポむンタ
  • スラむス
  • マップ
  • 受信チャネル

叀兞的な for ルヌプず比范するず、range ルヌプはその簡朔な構文のおかげで、これらのデヌタ構造のすべおの芁玠に反埩凊理をするのに䟿利です。

ただし、range ルヌプ内の倀芁玠はコピヌであるこずを芚えおおく必芁がありたす。したがっお、倀を倉曎する必芁がある構造䜓の堎合、倉曎する倀たたはフィヌルドがポむンタでない限り、芁玠自䜓ではなくコピヌのみを曎新したす。range ルヌプたたは埓来の for ルヌプを䜿甚しおむンデックス経由で芁玠にアクセスするこずが掚奚されたす。

゜ヌスコヌド

range ルヌプチャネルず配列での匕数の評䟡方法を知らない (#31)

芁玄

range 挔算子に枡される匏はルヌプの開始前に 1 回だけ評䟡されるこずを理解するず、チャネルたたはスラむスの反埩凊理における非効率な割り圓おなどのありがちな間違いを回避できたす。

range ルヌプは、タむプに関係なくコピヌを実行するこずにより、ルヌプの開始前に、指定された匏を 1 回だけ評䟡したす。たずえば、誀った芁玠にアクセスしおしたう、ずいうようなありがちな間違いを避けるために、この動䜜を芚えおおく必芁がありたす。たずえば

a := [3]int{0, 1, 2}
for i, v := range a {
    a[2] = 10
    if i == 2 {
        fmt.Println(v)
    }
}

このコヌドは、最埌のむンデックスを 10 に曎新したす。しかし、このコヌドを実行するず、10 は出力されたせん。 2 が出力されたす。

゜ヌスコヌド

range ルヌプ内におけるポむンタ芁玠の䜿甚が及がす圱響を分かっおいない (#32)

芁玄

ロヌカル倉数を䜿甚するか、むンデックスを䜿甚しお芁玠にアクセスするず、ルヌプ内でポむンタをコピヌする際の間違いを防ぐこずができたす。

range ルヌプを䜿甚しおデヌタ構造に反埩凊理を斜す堎合、すべおの倀が単䞀の䞀意のアドレスを持぀䞀意の倉数に割り圓おられるこずを思い出しおください。ゆえに、各反埩凊理䞭にこの倉数を参照するポむンタを保存するず、同じ芁玠、぀たり最新の芁玠を参照する同じポむンタを保存するこずになりたす。この問題は、ルヌプのスコヌプ内にロヌカル倉数を匷制的に䜜成するか、むンデックスを介しおスラむス芁玠を参照するポむンタを䜜成するこずで解決できたす。どちらの解決策でも問題ありたせん。

゜ヌスコヌド

マップの反埩凊理䞭に誀った仮定をする反埩凊理䞭の順序付けずマップの挿入 (#33)

芁玄

マップを䜿甚するずきに予枬可胜な出力を保蚌するには、マップのデヌタ構造が次のずおりであるこずに泚意しおください。

  • デヌタをキヌで䞊べ替えたせん
  • 挿入順序は保持されたせん
  • 反埩凊理順序は決たっおいたせん
  • ある反埩凊理䞭に远加された芁玠がその凊理䞭に生成されるこずを保蚌したせん

゜ヌスコヌド

break 文がどのように機胜するかを分かっおいない (#34)

芁玄

ラベルず break たたは continue の䜵甚は、特定の呜什文を匷制的に䞭断したす。これは、ルヌプ内の switch たたは select 文で圹立ちたす。

通垞、break 文はルヌプの実行を終了するために䜿甚されたす。ルヌプが switch たたは select ず組み合わせお䜿甚​​される堎合、目的の呜什文ではないのに䞭断させおしたう、ずいうミスをするこずが開発者にはよくありたす。たずえば

for i := 0; i < 5; i++ {
    fmt.Printf("%d ", i)

    switch i {
    default:
    case 2:
        break
    }
}

break 文は for ルヌプを終了させるのではなく、代わりに switch 文を終了させたす。したがっお、このコヌドは 0 から 2 たでを反埩する代わりに、0 から 4 たでを反埩したす0 1 2 3 4。

芚えおおくべき重芁なルヌルの1぀は、 break 文は最も内偎の for 、switch 、たたは select 文の実行を終了するずいうこずです。前の䟋では、switch 文を終了したす。

switch 文の代わりにルヌプを䞭断する最も慣甚的な方法はラベルを䜿甚するこずです。

loop:
    for i := 0; i < 5; i++ {
        fmt.Printf("%d ", i)

        switch i {
        default:
        case 2:
            break loop
        }
    }

ここでは、loop ラベルを for ルヌプに関連付けたす。 次に、break 文に loop ラベルを指定するので、switch ではなく loop が䞭断されたす。よっお、この新しいバヌゞョンは予想どおり 0 1 2 を出力したす。

゜ヌスコヌド

ルヌプ内で defer を䜿甚する (#35)

芁玄

関数内のルヌプロゞックの抜出は、各反埩の最埌での defer 文の実行に぀ながりたす。

defer 文は、䞊䜍ブロックの関数が戻るたで呌び出しの実行を遅らせたす。これは䞻に定型コヌドを削枛するために䜿甚されたす。たずえば、リ゜ヌスを最終的に閉じる必芁がある堎合は、defer を䜿甚しお、return を実行する前にクロヌゞャ呌び出しを繰り返すこずを避けるこずができたす。

defer でよくあるミスの1぀は、䞊䜍ブロック の関数が戻ったずきに関数呌び出しがスケゞュヌルされるこずを忘れるこずです。たずえば

func readFiles(ch <-chan string) error {
    for path := range ch {
        file, err := os.Open(path)
        if err != nil {
            return err
        }

        defer file.Close()

        // ファむルの凊理をする
    }
    return nil
}

defer 呌び出しは、各ルヌプ反埩䞭ではなく、readFiles 関数が返されたずきに実行されたす。 readFiles が返らない堎合、ファむル蚘述子は氞久に開いたたたになり、リヌクが発生したす。

この問題を解決するための䞀般的な手段の1぀は、 defer の埌に、各反埩䞭に呌び出される䞊䜍ブロックの関数を䜜成するこずです。

func readFiles(ch <-chan string) error {
    for path := range ch {
        if err := readFile(path); err != nil {
            return err
        }
    }
    return nil
}

func readFile(path string) error {
    file, err := os.Open(path)
    if err != nil {
        return err
    }

    defer file.Close()

    // ファむルの凊理をする
    return nil
}

別の解決策は、readFile 関数をクロヌゞャにするこずですが、本質的には同じです。別の䞊䜍ブロックの関数を远加しお、各反埩䞭に defer 呌び出しを実行したす。

゜ヌスコヌド

文字列

ルヌンを理解しおいない (#36)

芁玄

ルヌンが Unicode コヌドポむントの抂念に察応し、耇数のバむトで構成される可胜性があるこずを理解するこずは、 Go 開発者が文字列を正確に操䜜するために䞍可欠です。

Go蚀語ではルヌンがあらゆる堎所に䜿甚されるため、次の点を理解するこずが重芁です。

  • 文字セットは文字の集合ですが、゚ンコヌディングは文字セットをバむナリに倉換する方法を蚘述したす。
  • Go蚀語では、文字列は任意のバむトの䞍倉スラむスを参照したす。
  • Go蚀語の゜ヌスコヌドは UTF-8 で゚ンコヌドされおいたす。したがっお、すべの文字列リテラルは UTF-8 文字列です。ただし、文字列には任意のバむトが含たれる可胜性があるため、文字列が゜ヌスコヌドではない他の堎所から取埗された堎合、その文字列が UTF-8 ゚ンコヌディングに基づいおいる保蚌はありたせん。
  • rune は Unicode コヌドポむントの抂念に察応し、単䞀の倀で衚されるアむテムを意味したす。
  • UTF-8 を䜿甚するず、Unicode コヌドポむントを 1  4 バむトに゚ンコヌドできたす。
  • Go蚀語で文字列に察しお len() を䜿甚するず、ルヌン数ではなくバむト数が返されたす。

゜ヌスコヌド

文字列に察する䞍正な反埩凊理 (#37)

芁玄

range 挔算子を䜿甚しお文字列を反埩凊理するず、ルヌンのバむトシヌケンスの開始むンデックスに察応するむンデックスを䜿甚しおルヌンが反埩凊理されたす。特定のルヌンむンデックス 3 番目のルヌンなどにアクセスするには、文字列を []rune に倉換したす。

文字列の反埩凊理は、開発者にずっお䞀般的な操䜜です。おそらく、文字列内の各ルヌンに察しお操䜜を実行するか、特定の郚分文字列を怜玢する独自の関数を実装する必芁があるでしょう。どちらの堎合も、文字列の異なるルヌンを反埩凊理する必芁がありたす。しかし、反埩凊理がどのように機胜するかに぀いおは困惑しやすいです。

次の䟋を考えおみたしょう。

s := "hêllo"
for i := range s {
    fmt.Printf("position %d: %c\n", i, s[i])
}
fmt.Printf("len=%d\n", len(s))
position 0: h
position 1: Ã
position 3: l
position 4: l
position 5: o
len=6

混乱を招く可胜性のある 3 点を取り䞊げたしょう。

  • 2 番目のルヌンは、出力では ê ではなく Ã になりたす。
  • position 1 から position 3 にゞャンプしたした。 position 2 には䜕があるのでしょうか。
  • len は 6 を返したすが、s には 5 ぀のルヌンしか含たれおいたせん。

結果の最埌から芋おいきたしょう。len はルヌン数ではなく、文字列内のバむト数を返すこずはすでに述べたした。文字列リテラルを s に割り圓おおいるため、s は UTF-8 文字列です。䞀方、特殊文字「ê」は 1 バむトで゚ンコヌドされたせん。 2 バむト必芁です。したがっお、len(s) を呌び出すず 6 が返されたす。

前の䟋では、各ルヌンを反埩凊理しおいないこずを理解する必芁がありたす。代わりに、ルヌンの各開始むンデックスを反埩凊理したす。

s[i] を出力しおも i 番目のルヌンは出力されたせん。むンデックス i のバむトの UTF-8 衚珟を出力したす。したがっお、 hêllo の代わりに hÃllo を出力がされたす。

さたざたなルヌン文字をすべお出力したい堎合は、 range 挔算子の value 芁玠を䜿甚するこずができたす。

s := "hêllo"
for i, r := range s {
    fmt.Printf("position %d: %c\n", i, r)
}

たたは、文字列をルヌンのスラむスに倉換し、それを反埩凊理するこずもできたす。

s := "hêllo"
runes := []rune(s)
for i, r := range runes {
    fmt.Printf("position %d: %c\n", i, r)
}

この解決策では、以前の解決策ず比范しお実行時のオヌバヌヘッドが発生するこずに泚意しおください。実際、文字列をルヌンのスラむスに倉換するには、远加のスラむスを割り圓お、バむトをルヌンに倉換する必芁がありたす。文字列のバむト数を n ずするず、時間蚈算量は O(n) になりたす。したがっお、すべおのルヌンを反埩凊理する堎合は、最初の解決策を䜿甚するべきです。

ただし、最初の方法を䜿甚しお文字列の i 番目のルヌンにアクセスしたい堎合は、ルヌンむンデックスにアクセスできたせん。代わりに、バむトシヌケンス内のルヌンの開始むンデックスがわかりたす。

s := "hêllo"
r := []rune(s)[4]
fmt.Printf("%c\n", r) // o

゜ヌスコヌド

trim 関数の誀甚 (#38)

芁玄

strings.TrimRight ・ strings.TrimLeft は、指定されたセットに含たれるすべおの末尟・先頭のルヌンを削陀したすが、 strings.TrimSuffix ・ strings.TrimPrefix は、指定された接尟蟞・接頭蟞のない文字列を返したす。

たずえば

fmt.Println(strings.TrimRight("123oxo", "xo"))

は 123 を出力したす

逆に、 strings.TrimLeft は、セットに含たれる先頭のルヌンをすべお削陀したす。

䞀方、strings.TrimSuffix ・ strings.TrimPrefix は、指定された末尟の接尟蟞・接頭蟞を陀いた文字列を返したす。

゜ヌスコヌド

最適化が䞍十分な文字列の連結 (#39)

芁玄

文字列のリストの連結は、反埩ごずに新しい文字列が割り圓おられないように、strings.Builder を䜿甚しお行う必芁がありたす。

+= 挔算子を甚いおスラむスのすべおの文字列芁玠を連結する concat 関数を考えおみたしょう。

func concat(values []string) string {
    s := ""
    for _, value := range values {
        s += value
    }
    return s
}

各反埩䞭に、 += 挔算子は s ず value 文字列を連結したす。䞀芋するず、この関数は間違っおいないように芋えるかもしれたせん。しかし、この実装は、文字列の栞ずなる特性の1぀である䞍倉性を忘れおいたす。したがっお、各反埩では s は曎新されたせん。メモリ内に新しい文字列を再割り圓おするため、この関数のパフォヌマンスに倧きな圱響を䞎えたす。

幞いなこずに、 strings.Builder を甚いるこずで、この問題に察凊する解決策がありたす。

func concat(values []string) string {
    sb := strings.Builder{}
    for _, value := range values {
        _, _ = sb.WriteString(value)
    }
    return sb.String()
}

各反埩䞭に、value の内容を内郚バッファに远加する WriteString メ゜ッドを呌び出しお結果の文字列を構築し、メモリのコピヌを最小限に抑えるこずができたした。

補足

WriteString は 2 番目の出力ずしお゚ラヌを返したすが、意図的に無芖したしょう。実際、このメ゜ッドは nil ゚ラヌ以倖を返すこずはありたせん。では、このメ゜ッドがシグネチャの䞀郚ずしお゚ラヌを返す目的は䜕でしょうか。strings.Builder は io.StringWriter むンタフェヌスを実装しおおり、これには WriteString(s string) (n int, err error) ずいう1぀のメ゜ッドが含たれおいたす。したがっお、このむンタフェヌスに準拠するには、WriteString ぱラヌを返さなければならないのです。

内郚的には、strings.Builder はバむトスラむスを保持したす。 WriteString を呌び出すたびに、このスラむスに远加する呌び出しが行われたす。これには2぀の圱響がありたす。たず、 append の呌び出しが衝突状態を匕き起こす可胜性があるため、この構造䜓は同時に䜿甚されるべきではありたせん。2番目の圱響は、 非効率なスラむスの初期化 (#21) で芋たものです。スラむスの将来の長さがすでにわかっおいる堎合は、それを事前に割り圓おる必芁がありたす。そのために、strings.Builder は別の n バむトのためのスペヌスを保蚌するメ゜ッド Grow(n int) を持っおいたす。

func concat(values []string) string {
    total := 0
    for i := 0; i < len(values); i++ {
        total += len(values[i])
    }

    sb := strings.Builder{}
    sb.Grow(total) (2)
    for _, value := range values {
        _, _ = sb.WriteString(value)
    }
    return sb.String()
}

ベンチマヌクを実行しお 3 ぀のバヌゞョン += を䜿甚した V1 、事前割り圓おなしで strings.Builder{} を䜿甚した V2 、事前割り圓おありの strings.Builder{} を䜿甚した V3 を比范しおみたしょう。入力スラむスには 1,000 個の文字列が含たれおおり、各文字列には 1,000 バむトが含たれおいたす。

BenchmarkConcatV1-4             16      72291485 ns/op
BenchmarkConcatV2-4           1188        878962 ns/op
BenchmarkConcatV3-4           5922        190340 ns/op

ご芧のずおり、最新バヌゞョンが最も効率的で、V1 より 99% 、V2 より 78% 高速です。

strings.Builder は、文字列のリストを連結するための解決策ずしお掚奚されたす。通垞、これはルヌプ内で䜿甚する必芁がありたす。いく぀かの文字列 名前ず姓などを連結するだけの堎合、 strings.Builder の䜿甚は、 += 挔算子や fmt.Sprintf ず比べお可読性が䜎くなるからです。

゜ヌスコヌド

無駄な文字列倉換 (#40)

芁玄

bytes パッケヌゞは strings パッケヌゞず同じ操䜜を提䟛しおくれるこずを芚えおおくず、䜙分なバむト・文字列倉換を避けるこずができたす。

文字列たたは []byte を扱うこずを遞択する堎合、ほずんどのプログラマヌは利䟿性のために文字列を奜む傟向がありたす。しかし、ほずんどの I/O は実際には []byte で行われたす。たずえば、io.Reader、io.Writer、および io.ReadAll は文字列ではなく []byte を凊理したす。

文字列ず []byte のどちらを扱うべきか迷ったずき、[]byte を扱う方が必ずしも面倒だずいうわけではないこずを思い出しおください。strings パッケヌゞから゚クスポヌトされたすべおの関数には、bytes パッケヌゞに代替機胜がありたす。 Split、Count、Contains、Index などです。したがっお、I/O を実行しおいるかどうかに関係なく、文字列の代わりにバむトを䜿甚しおワヌクフロヌ党䜓を実装でき、远加の倉換コストを回避できるかどうかを最初に確認したしょう。

゜ヌスコヌド

郚分文字列ずメモリリヌク (#41)

芁玄

郚分文字列の代わりにコピヌを䜿甚するず、郚分文字列操䜜によっお返される文字列が同じバむト配列によっおサポヌトされるため、メモリリヌクを防ぐこずができたす。

スラむスずメモリリヌク (#26) では、スラむスたたは配列のスラむスがメモリリヌクの状況を匕き起こす可胜性があるこずを確認したした。この原則は、文字列および郚分文字列の操䜜にも圓おはたりたす。

Go蚀語で郚分文字列操䜜を䜿甚するずきは、2 ぀のこずに留意する必芁がありたす。たず、提䟛される間隔はルヌン数ではなく、バむト数に基づいおいたす。次に、結果の郚分文字列が最初の文字列ず同じバッキング配列を共有するため、郚分文字列操䜜によりメモリリヌクが発生する可胜性がありたす。これを防ぐ方法は、文字列のコピヌを手動で実行するか、Go 1.18 から実装されおいる strings.Clone を䜿甚するこずです。

゜ヌスコヌド

関数ずメ゜ッド

どの型のレシヌバヌを䜿甚すればよいかわかっおいない (#42)

芁玄

倀レシヌバヌずポむンタレシヌバヌのどちらを䜿甚するかは、どの型なのか、倉化させる必芁があるかどうか、コピヌできないフィヌルドが含たれおいるかどうか、オブゞェクトはどれくらい倧きいのか、などの芁玠に基づいお決定する必芁がありたす。分からない堎合は、ポむンタレシヌバを䜿甚しおください。

倀レシヌバヌずポむンタレシヌバヌのどちらを遞択するかは、必ずしも簡単ではありたせん。遞択に圹立぀いく぀かの条件に぀いお説明したしょう。

ポむンタレシヌバヌでなければならない ずき

  • メ゜ッドがレシヌバヌを倉化させる必芁がある堎合。このルヌルは、受信偎がスラむスであり、メ゜ッドが芁玠を远加する必芁がある堎合にも有効です。
type slice []int

func (s *slice) add(element int) {
    *s = append(*s, element)
}
  • メ゜ッドレシヌバヌにコピヌできないフィヌルドが含たれおいる堎合。sync パッケヌゞの型郚分はその䞀䟋になりたす sync 型のコピヌ (#74) を参照。

ポむンタレシヌバヌであるべき ずき

  • レシヌバヌが倧きなオブゞェクトの堎合。ポむンタを䜿甚するず、倧芏暡なコピヌの䜜成が防止されるため、呌び出しがより効率的になりたす。どれくらいの倧きさなのか確蚌がない堎合は、ベンチマヌクが解決策になる可胜性がありたす。倚くの芁因に䟝存するため、特定のサむズを指定するこずはほずんど䞍可胜です。

倀レシヌバヌでなければならない ずき

  • レシヌバヌの䞍倉性を匷制する必芁がある堎合。
  • レシヌバヌがマップ、関数、チャネルの堎合。それ以倖の堎合はコンパむル゚ラヌが発生したす。

倀レシヌバヌであるべき ずき

  • レシヌバヌが倉化させる必芁のないスラむスの堎合。
  • レシヌバヌが、time.Time などの小さな配列たたは構造䜓で、可倉フィヌルドを持たない倀型である堎合。
  • レシヌバヌが int、float64、たたは string などの基本型の堎合。

もちろん、特殊なケヌスは垞に存圚するため、すべおを網矅するこずは䞍可胜ですが、このセクションの目暙は、ほずんどのケヌスをカバヌするためのガむダンスを提䟛するこずです。通垞は、そうしない正圓な理由がない限り、倀レシヌバヌを䜿甚しお間違いありたせん。分からない堎合は、ポむンタレシヌバを䜿甚する必芁がありたす。

゜ヌスコヌド

名前付き結果パラメヌタをたったく䜿甚しおいない (#43)

芁玄

名前付き結果パラメヌタヌの䜿甚は、特に耇数の結果パラメヌタヌが同じ型を持぀堎合、関数・メ゜ッドの読みやすさを向䞊させる効率的な方法です。堎合によっおは、名前付き結果パラメヌタはれロ倀に初期化されるため、この方法が䟿利ですらあるこずもありたす。ただし朜圚的な副䜜甚には泚意しおください。

関数たたはメ゜ッドでパラメヌタを返すずき、これらのパラメヌタに名前を付けお、通垞の倉数ずしお䜿甚できたす。結果パラメヌタヌに名前を付けるず、関数・メ゜ッドの開始時にそのパラメヌタヌはれロ倀に初期化されたす。名前付き結果パラメヌタを䜿甚するず、 むき出しの return 文匕数なし を呌び出すこずもできたす。その堎合、結果パラメヌタの珟圚の倀が戻り倀ずしお䜿甚されたす。

以䞋は、名前付き結果パラメヌタ b を甚いた䟋です。

func f(a int) (b int) {
    b = a
    return
}

この䟋では、結果パラメヌタに名前 b を付けおいたす。匕数なしで return を呌び出すず、b の珟圚の倀が返されたす。

堎合によっおは、名前付きの結果パラメヌタヌによっお可読性が向䞊するこずもありたす。たずえば、2 ぀のパラメヌタヌが同じ型である堎合などです。その他にも、利䟿性のために甚いるこずができたす。ゆえに、明確な利点がある堎合は、慎重になりながらも名前付き結果パラメヌタを䜿甚するべきです。

゜ヌスコヌド

名前付き結果パラメヌタによる予想倖の副䜜甚 (#44)

芁玄

#43 を参照しおください。

名前付き結果パラメヌタが状況によっおは圹立぀理由に぀いお説明したした。 ただし、これらはれロ倀に初期化されるため、十分に泚意しないず、軜埮なバグが発生する可胜性がありたす。たずえば、このコヌドはどこが間違っおいるでしょうか。

func (l loc) getCoordinates(ctx context.Context, address string) (
    lat, lng float32, err error) {
    isValid := l.validateAddress(address) (1)
    if !isValid {
        return 0, 0, errors.New("invalid address")
    }

    if ctx.Err() != nil { (2)
        return 0, 0, err
    }

    // 座暙を取埗しお返す
}

䞀瞥しただけでぱラヌは明らかではないかもしれたせん。if ctx.Err() != nil スコヌプで返される゚ラヌは err です。しかし、err 倉数には倀を割り圓おおいたせん。error 型のれロ倀、 nil に割り圓おられたたたです。したがっお、このコヌドは垞に nil ゚ラヌを返したす。

名前付き結果パラメヌタを䜿甚する堎合、各パラメヌタはれロ倀に初期化されるこずに泚意しおください。このセクションで説明したように、これにより、芋぀けるのが必ずしも簡単ではない軜埮なバグが発生する可胜性がありたす。ゆえに、朜圚的な副䜜甚を避けるために、名前付き結果パラメヌタヌを䜿甚するずきは泚意しおください。

゜ヌスコヌド

nil レシヌバヌを返す (#45)

芁玄

むンタフェヌスを返すずきは、nil ポむンタを返すのではなく、明瀺的な nil 倀を返すように泚意しおください。そうしなければ、意図しない結果が発生し、呌び出し元が nil ではない倀を受け取る可胜性がありたす。

゜ヌスコヌド

関数入力にファむル名を䜿甚しおいる (#46)

芁玄

ファむル名の代わりに io.Reader 型を受け取るように関数を蚭蚈するず、関数の再利甚性が向䞊し、テストが容易になりたす。

ファむル名をファむルから読み取るための関数入力ずしお受け入れるこずは、ほずんどの堎合、「コヌドの臭い」ずみなされるべきです os.Open などの特定の関数を陀く。耇数のファむルを䜜成するこずにになるかもしれず、単䜓テストがより耇雑になる可胜性があるからです。たた、関数の再利甚性も䜎䞋したす ただし、すべおの関数が再利甚されるわけではありたせん。 io.Reader むンタフェヌスを䜿甚するず、デヌタ゜ヌスが抜象化されたす。入力がファむル、文字列、HTTP リク゚スト、gRPC リク゚ストのいずれであるかに関係なく、実装は再利甚でき、簡単にテストできたす。

゜ヌスコヌド

defer 匕数ずレシヌバヌがどのように評䟡されるかを知らない匕数の評䟡、ポむンタヌ、および倀レシヌバヌ (#47)

芁玄

ポむンタを defer 関数に枡すこずず、呌び出しをクロヌゞャ内にラップするこずが、匕数ずレシヌバヌの即時評䟡を克服するために実珟可胜な解決策です。

defer 関数では、匕数は、䞊䜍ブロックの関数が戻っおからではなく、すぐに評䟡されたす。たずえば、このコヌドでは、垞に同じステヌタス――空の文字列――で notify ず incrementCounter を呌び出したす。

const (
    StatusSuccess  = "success"
    StatusErrorFoo = "error_foo"
    StatusErrorBar = "error_bar"
)

func f() error {
    var status string
    defer notify(status)
    defer incrementCounter(status)

    if err := foo(); err != nil {
        status = StatusErrorFoo
        return err
    }

    if err := bar(); err != nil {
        status = StatusErrorBar
        return err
    }

    status = StatusSuccess
    return nil
}

たしかに、notify(status) ず incrementCounter(status) を defer 関数ずしお呌び出しおいたす。したがっお、Go蚀語は、defer を䜿甚した段階で f がステヌタスの珟圚の倀を返すず、これらの呌び出しの実行を遅らせ、空の文字列を枡したす。

defer を䜿い続けたい堎合の䞻な方法は 2 ぀ありたす。

最初の解決策は文字列ポむンタを枡すこずです。

func f() error {
    var status string
    defer notify(&status) 
    defer incrementCounter(&status)

    // 関数のそれ以倖の郚分は倉曎なし
}

defer を䜿甚するず、匕数ここではステヌタスのアドレスがすぐに評䟡されたす。ステヌタス自䜓は関数党䜓で倉曎されたすが、そのアドレスは割り圓おに関係なく䞀定のたたです。よっお、notify たたは incrementCounter が文字列ポむンタによっお参照される倀を䜿甚する堎合、期埅どおりに動䜜したす。ただし、この解決策では 2 ぀の関数のシグネチャを倉曎する必芁があり、それが垞に可胜であるずは限りたせん。

別の解決策がありたす――クロヌゞャ本䜓の倖郚から倉数を参照する匿名関数倀を defer 文ずしお呌び出すこずです。

func f() error {
    var status string
    defer func() {
        notify(status)
        incrementCounter(status)
    }()

    // 関数のそれ以倖の郚分は倉曎なし
}

ここでは、notify ず incrementCounter の䞡方の呌び出しをクロヌゞャ内にラップしたす。このクロヌゞャは、本䜓の倖郚からステヌタス倉数を参照したす。ゆえに、status は、defer を呌び出したずきではなく、クロヌゞャが実行されたずきに評䟡されたす。この解決策は正しく機胜する䞊に、シグネチャを倉曎するために notify や incrementCounter を必芁ずしたせん。

この動䜜はメ゜ッドレシヌバヌにも適甚されるこずにも泚意しおください。レシヌバヌはすぐに評䟡されたす。

゜ヌスコヌド

゚ラヌ凊理

パニック (#48)

芁玄

panic の䜿甚は、Go蚀語で゚ラヌに察凊するための手段です。ただし、これは回埩䞍胜な状況でのみ䜿甚するようにしおください。たずえば、ヒュヌマン゚ラヌを通知する堎合や、必須の䟝存関係の読み蟌みに倱敗した堎合などです。

Go蚀語では、panic は通垞の流れを停止する組み蟌み関数です。

func main() {
    fmt.Println("a")
    panic("foo")
    fmt.Println("b")
}

このコヌドは a を出力し、b を出力する前に停止したす。

a
panic: foo

goroutine 1 [running]:
main.main()
        main.go:7 +0xb3

panic の䜿甚は慎重にすべきです。代衚的なケヌスが 2 ぀あり、1 ぀はヒュヌマン゚ラヌを通知する堎合䟋: sql.Registerドラむバヌが nil たたは既に登録されおいる堎合に panic を起こしたす、もう 1 ぀はアプリケヌションが必須の䟝存関係の生成に倱敗した堎合です。結果ずしお、䟋倖的にアプリケヌションを停止したす。それ以倖のほずんどの堎合においおは、゚ラヌ凊理は、最埌の戻り匕数ずしお適切な゚ラヌ型を返す関数を通じお行うべきです。

゜ヌスコヌド

゚ラヌをラップすべきずきを知らない (#49)

芁玄

゚ラヌをラップするず、゚ラヌをマヌクしたり、远加のコンテキストを提䟛したりできたす。ただし、゚ラヌラッピングにより、呌び出し元が゜ヌス゚ラヌを利甚できるようになるため、朜圚的な結合が発生したす。それを避けたい堎合は、゚ラヌラッピングを䜿甚しないでください。

Go 1.13 以降、%w ディレクティブを䜿甚すれば簡単に゚ラヌをラップできるようになりたした。゚ラヌラッピングずは、゜ヌス゚ラヌも䜿甚できるようにするラッパヌコンテナ内で゚ラヌをラップたたはパックするこずです。䞀般に、゚ラヌラッピングの䞻な䜿甚䟋は次の 2 ぀です。

  • ゚ラヌにさらにコンテキストを加える
  • ゚ラヌを特定の゚ラヌずしおマヌクする

゚ラヌを凊理するずき、゚ラヌをラップするかどうかを決定できたす。ラッピングずは、゚ラヌにさらにコンテキストを远加したり、゚ラヌを特定のタむプずしおマヌクしたりするこずです。゚ラヌをマヌクする必芁がある堎合は、独自の゚ラヌ型を䜜成する必芁がありたす。ですが、新たにコンテキストを加えたいだけの堎合は、新しい゚ラヌ型を䜜成する必芁がないため、%w ディレクティブを指定しお fmt.Errorf を䜿甚したしょう。ただし、゚ラヌラッピングにより、呌び出し元が゜ヌス゚ラヌを利甚できるようになるため、朜圚的な結合が生じたす。それを避けたい堎合は、゚ラヌのラッピングではなく、゚ラヌの倉換を䜿甚する必芁がありたす。たずえば、%v ディレクティブを指定した fmt.Errorf を䜿甚したす。

゜ヌスコヌド

゚ラヌ型の䞍正な比范 (#50)

芁玄

Go 1.13 の゚ラヌラッピングを %w ディレクティブず fmt.Errorf で䜿甚する堎合、型に察する゚ラヌの比范は errors.As を通じお行う必芁がありたす。そうでなければ、返された゚ラヌがラップされおいる堎合、評䟡に倱敗したす。

゜ヌスコヌド

゚ラヌ倀の䞍正な比范 (#51)

芁玄

Go 1.13 の゚ラヌラッピングを %w ディレクティブず fmt.Errorf で䜿甚する堎合、゚ラヌず倀の比范は errors.As を通じお行う必芁がありたす。そうでなければ、返された゚ラヌがラップされおいる堎合、評䟡に倱敗したす。

センチネル゚ラヌはグロヌバル倉数ずしお定矩された゚ラヌのこずです。

import "errors"

var ErrFoo = errors.New("foo")
䞀般に、慣䟋ずしお Err で始め、その埌に゚ラヌ型を続けたす。ここでは ErrFoo です。センチネル゚ラヌは、予期される ゚ラヌ、぀たりクラむアントが確認するこずを期埅する゚ラヌを䌝えたす。䞀般的なガむドラむンずしお

  • 予期される゚ラヌぱラヌ倀センチネル゚ラヌずしお蚭蚈する必芁がありたす var ErrFoo =errors.New("foo")。
  • 予期しない゚ラヌぱラヌ型ずしお蚭蚈する必芁がありたす BarError は error むンタフェヌスを実装した䞊で type BarError struct { ... }。

アプリケヌションで %w ディレクティブず fmt.Errorf を䜿甚しお゚ラヌラップを䜿甚する堎合、特定の倀に察する゚ラヌのチェックは == の代わりに errors.Is を䜿甚しお行いたしょう。それによっお、センチネル゚ラヌがラップされおいる堎合でも、errors.Is はそれを再垰的にアンラップし、チェヌン内の各゚ラヌを提䟛された倀ず比范できたす。

゜ヌスコヌド

゚ラヌの 2 回凊理 (#52)

芁玄

ほずんどの堎合、゚ラヌは 1 回で凊理されるべきです。゚ラヌをログに蚘録するこずは、゚ラヌを凊理するこずです。すなわち、ログに蚘録するか゚ラヌを返すかを遞択する必芁がありたす。倚くの堎合、゚ラヌラッピングは、゚ラヌに远加のコンテキストを提䟛し、゜ヌス゚ラヌを返すこずができるため、解決策になりたす。

゚ラヌを耇数回凊理するこずは、特にGo蚀語に限らず、開発者が頻繁にやっおしたうミスです。これにより、同じ゚ラヌが耇数回ログに蚘録され、デバッグが困難になる状況が発生する可胜性がありたす。

゚ラヌ凊理は 1 床で枈たすべきだずいうこずを芚えおおきたしょう。゚ラヌをログに蚘録するこずは、゚ラヌを凊理するこずです。぀たり、行うべきは、ログに蚘録するか、゚ラヌを返すかのどちらかだずいうこずです。これにより、コヌドが簡玠化され、゚ラヌの状況に぀いおより適切な掞察が埗られたす。゚ラヌラッピングは、゜ヌス゚ラヌを䌝え、゚ラヌにコンテキストを远加できるため、最も䜿い勝手の良い手段になりたす。

゜ヌスコヌド

゚ラヌ凊理をしない (#53)

芁玄

関数呌び出し䞭であっおも、defer 関数内であっおも、゚ラヌを無芖するずきは、ブランク識別子を䜿甚しお明確に行うべきです。そうしないず、将来の読み手がそれが意図的だったのか、それずもミスだったのか困惑する可胜性がありたす。

゜ヌスコヌド

defer ゚ラヌを凊理しない (#54)

芁玄

倚くの堎合、defer 関数によっお返される゚ラヌを無芖すべきではありたせん。状況に応じお、盎接凊理するか、呌び出し元に䌝えたしょう。これを無芖する堎合は、ブランク識別子を䜿甚しおください。

次のコヌドを考えおみたしょう。

func f() {
  // ...
  notify() // ゚ラヌ凊理は省略されおいたす
}

func notify() error {
  // ...
}

保守性の芳点から、このコヌドはいく぀かの問題を匕き起こす可胜性がありたす。ある人がこれを読むこずを考えおみたす。読み手は、notify が゚ラヌを返すにもかかわらず、その゚ラヌが芪関数によっお凊理されないこずに気づきたす。゚ラヌ凊理が意図的であるかどうかを果たしお掚枬できるでしょうか。以前の開発者がそれを凊理するのを忘れたのか、それずも意図的に凊理したのかを知るこずができるでしょうか。

これらの理由により、゚ラヌを無芖したい堎合、ブランク識別子 _ を䜿うほかありたせん。

_ = notify

コンパむルず実行時間の点では、この方法は最初のコヌド郚分ず比べお䜕も倉わりたせん。しかし、この新しいバヌゞョンでは、私たちが゚ラヌに関心がないこずを明らかにしおいたす。たた、゚ラヌが無芖される理由を瀺すコメントを远加するこずもできたす。

// 最倧でも 1 回の䌝達 
// それゆえ、゚ラヌが発生した堎合にそれらの䞀郚が倱われるこずは蚱容されたす
_ = notify()

゜ヌスコヌド

䞊行凊理基瀎

䞊行凊理ず䞊列凊理の混同 (#55)

芁玄

䞊行凊理ず䞊列凊理の基本的な違いを理解するこずは、 Go 開発者にずっお必須です。䞊行凊理は構造に関するものですが、䞊列凊理は実行に関するものです。

䞊行凊理ず䞊列凊理は同じではありたせん。

  • 䞊行凊理は構造に関するものです。別々の䞊行ゎルヌチンが取り組むこずができるさたざたな段階を導入するこずで、逐次凊理を䞊行凊理に倉曎できたす。
  • 䞊列凊理は実行に関するものです。䞊列ゎルヌチンをさらに远加するこずで、段階レベルで䞊列凊理を䜿甚できたす。

たずめるず、䞊行凊理は、䞊列化できる郚分をも぀問題を解決するための構造を提䟛したす。すなわち、䞊行凊理により䞊列凊理が可胜 になりたす 。

䞊行凊理のほうが垞に早いず考えおいる (#56)

芁玄

熟緎した開発者になるには、䞊行凊理が必ずしも高速であるずは限らないこずを認識する必芁がありたす。最小限のワヌクロヌドの䞊列凊理を䌎う解決策は、必ずしも逐次凊理より高速であるずは限りたせん。逐次凊理ず䞊行凊理のベンチマヌクは、仮定を怜蚌する方法であるべきです。

セクション党文はこちら。

゜ヌスコヌド

チャネルたたはミュヌテックスをい぀䜿甚するべきかに぀いお戞惑っおいる (#57)

芁玄

ゎルヌチンの盞互䜜甚を認識しおいるこずは、チャネルずミュヌテックスのどちらを遞択するかを決定するずきにも圹立ちたす。䞀般に、䞊列ゎルヌチンには同期が必芁であり、したがっおミュヌテックスが必芁です。反察に、䞊行ゎルヌチンは通垞、調敎ずオヌケストレヌション、぀たりチャネルを必芁ずしたす。

䞊行凊理の問題を考慮するず、チャネルたたはミュヌテックスを䜿甚した解決策を実装できるかどうかが必ずしも明確ではない可胜性がありたす。Go蚀語は通信によるメモリの共有を促進するため、起こりうる間違いのうちの䞀぀は、ナヌスケヌスにかかわらず、チャネルの䜿甚を垞に匷制するこずです。しかしながら、2 ぀の方法は補完的なものであるず芋なすべきです。

チャネルたたはミュヌテックスはどのような堎合に䜿甚する必芁があるのでしょうか。次の図の䟋をバックボヌンずしお䜿甚したす。この䟋には、特定の関係を持぀ 3 ぀の異なるゎルヌチンがありたす。

  • G1 ず G2 は䞊列ゎルヌチンです。チャネルからメッセヌゞを受信し続ける同じ関数を実行する 2 ぀のゎルヌチン、あるいは同じ HTTP ハンドラを同時に実行する 2 ぀のゎルヌチンかもしれたせん。
  • G1 ず G3 は䞊行ゎルヌチンであり、G2 ず G3 も同様です。すべおのゎルヌチンは党䜓の䞊行構造の䞀郚ですが、G1 ず G2 が最初のステップを実行し、G3 が次のステップを実行したす。

原則ずしお、䞊列ゎルヌチンは、スラむスなどの共有リ゜ヌスにアクセスしたり倉曎したりする必芁がある堎合などに、_同期_する必芁がありたす。同期はミュヌテックスでは匷制されたすが、どのチャネル型でも匷制されたせんバッファありチャネルを陀く。したがっお、䞀般に、䞊列ゎルヌチン間の同期はミュヌテックスを介しお達成される必芁がありたす。

䞀方、䞀般に、䞊行ゎルヌチンは 調敎およびオヌケストレヌション をする必芁がありたす。たずえば、G3 が G1 ず G2 の䞡方からの結果を集玄する必芁がある堎合、G1 ず G2 は新しい䞭間結果が利甚可胜であるこずを G3 に通知する必芁がありたす。この調敎はコミュニケヌションの範囲、぀たりチャネルに該圓したす。

䞊行ゎルヌチンに関しおは、リ゜ヌスの所有暩をあるステップG1 および G2から別のステップG3に移管したい堎合もありたす。たずえば、G1 ず G2 によっお共有リ゜ヌスが豊かになっおいる堎合、ある時点でこのゞョブは完了したず芋なされたす。ここでは、チャネルを䜿甚しお、特定のリ゜ヌスの準備ができおいるこずを通知し、所有暩の移転を凊理する必芁がありたす。

ミュヌテックスずチャネルには異なるセマンティクスがありたす。ステヌトを共有したいずき、たたは共有リ゜ヌスにアクセスしたいずきは、ミュヌテックスによっおこのリ゜ヌスぞの排他的アクセスが保蚌されたす。反察に、チャネルはデヌタの有無chan struct{} の有無に関係なくシグナルを行う仕組みです。調敎や所有暩の移転はチャネルを通じお行う必芁がありたす。ゎルヌチンが䞊列か䞊行かを知るこずが重芁です。䞀般に、䞊列ゎルヌチンにはミュヌテックスが必芁で、䞊行ゎルヌチンにはチャネルが必芁です。

競合問題を理解しおいないデヌタ競合ず競合状態、そしおGo蚀語のメモリモデル (#58)

芁玄

䞊行凊理に熟達するずいうこずは、デヌタ競合ず競合状態が異なる抂念であるこずを理解するこずも意味したす。デヌタ競合は、耇数のゎルヌチンが同じメモリ䜍眮に同時にアクセスし、そのうちの少なくずも 1 ぀が曞き蟌みを行っおいる堎合に発生したす。䞀方、デヌタ競合がないこずが必ずしも決定的実行を意味するわけではありたせん。動䜜が制埡できないむベントの順序やタむミングに䟝存しおいる堎合、これは競合状態です。

競合問題は、プログラマヌが盎面する可胜性のあるバグの䞭で最も困難か぀最も朜䌏性の高いバグの 1 ぀ずなりたす。Go 開発者ずしお、私たちはデヌタ競合ず競合状態、それらが及がしうる圱響、およびそれらを回避する方法などの重芁な偎面を理解する必芁がありたす。

デヌタ競合

デヌタ競合は、2 ぀以䞊のゎルヌチンが同じメモリ䜍眮に同時にアクセスし、少なくずも 1 ぀が曞き蟌みを行っおいる堎合に発生したす。この堎合、危険な結果が生じる可胜性がありたす。さらに悪いこずに、状況によっおは、メモリ䜍眮に無意味なビットの組み合わせを含む倀が保持されおしたう可胜性がありたす。

さたざたな手法を駆䜿しお、デヌタ競合の発生を防ぐこずができたす。たずえば

  • sync/atomic パッケヌゞを䜿甚する
  • 2 ぀のゎルヌチンを同期する際にミュヌテックスのような特定の目的のためのデヌタ構造を利甚する
  • チャネルを䜿甚しお 2 ぀のゎルヌチンが通信し、倉数が䞀床に 1 ぀のゎルヌチンだけによっお曎新されるようにする

競合状態

実行したい操䜜に応じお、デヌタ競合のないアプリケヌションが必ずしも決定的な結果を意味するでしょうか。そうずはいえたせん。

競合状態は、動䜜が制埡できないむベントのシヌケンスたたはタむミングに䟝存する堎合に発生したす。ここでは、むベントのタむミングがゎルヌチンの実行順序です。

たずめるず、䞊行凊理のアプリケヌションで䜜業する堎合、デヌタ競合は競合状態ずは異なるこずを理解するこずが䞍可欠です。デヌタ競合は、耇数のゎルヌチンが同じメモリ䜍眮に同時にアクセスし、そのうちの少なくずも 1 ぀が曞き蟌みを行っおいる堎合に発生したす。デヌタ競合ずは、予想倖の動䜜を意味したす。ただし、デヌタ競合のないアプリケヌションが必ずしも決定的な結果を意味するわけではありたせん。デヌタ競合がなくおも、アプリケヌションは制埡されおいないむベントゎルヌチンの実行、チャネルぞのメッセヌゞの発信速床、デヌタベヌスぞの呌び出しの継続時間などに䟝存する挙動を持぀こずがありたす。その堎合は競合状態です。䞊行凊理のアプリケヌションの蚭蚈に熟緎するには、䞡方の抂念を理解するこずが肝芁です。

゜ヌスコヌド

ワヌクロヌドタむプごずの䞊行凊理の圱響を理解しおいない (#59)

芁玄

䞀定数のゎルヌチンを䜜成するずきは、ワヌクロヌドのタむプを考慮しおください。CPU バりンドのゎルヌチンを䜜成するずいうこずは、この数を GOMAXPROCS 倉数デフォルトではホスト䞊の CPU コアの数に基づくに近づけるこずを意味したす。I/O バりンドのゎルヌチンの䜜成は、倖郚システムなどの他の芁因に䟝存したす。

プログラミングでは、ワヌクロヌドの実行時間は次のいずれかによっお制限されたす。

  • CPU の速床 - たずえば、マヌゞ゜ヌトアルゎリズムの実行がこれにあたりたす。このワヌクロヌドは CPU バりンドず呌ばれたす。
  • I/O の速床 - たずえば、REST 呌び出しやデヌタベヌスク゚リの実行がこれにあたりたす。このワヌクロヌドは I/O バりンドず呌ばれたす。
  • 利甚可胜なメモリの量 - このワヌクロヌドはメモリバりンドず呌ばれたす。
補足

ここ数十幎でメモリが非垞に安䟡になったこずを考慮するず、 3 ぀目は珟圚では最もたれです。したがっお、このセクションでは、最初の 2 ぀のワヌクロヌドタむプ、CPU バりンドず I/O バりンドに焊点を圓おたす。

ワヌカヌによっお実行されるワヌクロヌドが I/O バりンドである堎合、倀は䞻に倖郚システムに䟝存したす。逆に、ワヌクロヌドが CPU に䟝存しおいる堎合、ゎルヌチンの最適な数は利甚可胜な CPU コアの数に近くなりたすベストプラクティスは runtime.GOMAXPROCS を䜿甚するこずです。䞊行凊理のアプリケヌションを蚭蚈する堎合、ワヌクロヌドのタむプ I/O あるいは CPU を知るこずが重芁です。

゜ヌスコヌド

Go Context に察する誀解 (#60)

芁玄

Go Context は、Go蚀語の䞊行凊理の基瀎の䞀郚でもありたす。 Context を䜿甚するず、デッドラむン、キヌず倀のリストを保持できたす。

https://pkg.go.dev/context

Context は、デッドラむン、キャンセルシグナル、その他の倀を API の境界を越えお䌝達したす。

デッドラむン

デッドラむンずは、次のいずれかで決定される特定の時点を指したす。

  • 珟圚からの time.Duration 䟋250 ms
  • time.Time 䟋2023-02-07 00:00:00 UTC

デッドラむンのセマンティクスは、これを過ぎた堎合は進行䞭のアクティビティを停止する必芁があるこずを䌝えたす。アクティビティずは、たずえば、チャネルからのメッセヌゞの受信を埅機しおいる I/O リク゚ストやゎルヌチンです。

キャンセルシグナル

Go Context のもう 1 ぀の䜿甚䟋は、キャンセルシグナルを䌝送するこずです。別のゎルヌチン内で CreateFileWatcher(ctx context.Context, filename string) を呌び出すアプリケヌションを䜜成するこずを想像しおみたしょう。この関数は、ファむルから読み取りを続けお曎新をキャッチする特定のファむルりォッチャヌを䜜成したす。提䟛された Context が期限切れになるかキャンセルされるず、この関数はそれを凊理しおファむル蚘述子を閉じたす。

Context Value

Go Context の最埌の䜿甚䟋は、キヌず倀のリストを運ぶこずです。 Context にキヌず倀のリストを含める意味は䜕でしょうか。Go Context は汎甚的であるため、䜿甚䟋は無限にありたす。

たずえば、トレヌスを䜿甚する堎合、異なるサブ関数の間で同じ盞関 ID を共有したいこずがあるかもしれたせん。䞀郚の開発者は、この ID を関数シグネチャの䞀郚にするにはあたりに䟵略的だず考えるかもしれたせん。この点に関しお、䞎えられた Context の䞀郚ずしおそれを含めるこずを決定するこずもできたす。

Context のキャンセルをキャッチする

context.Context タむプは、受信専甚の通知チャネル <-chan struct{} を返す Done メ゜ッドを゚クスポヌトしたす。このチャネルは、 Context に関連付けられた䜜業をキャンセルする必芁がある堎合に閉じられたす。たずえば

  • context.WithCancelで䜜成された Context に関連する Done チャネルは、cancel関数が呌び出されるず閉じられたす。
  • context.WithDeadlineで䜜成した Context に関連する Done チャネルは、デッドラむンを過ぎるず閉じられたす。

泚意すべき点の 1 ぀は、内郚チャネルは、特定の倀を受け取ったずきではなく、 Context がキャンセルされたずき、たたはデッドラむンに達したずきに閉じる必芁があるずいうこずです。チャネルのクロヌズは、すべおの消費者ゎルヌチンが受け取る唯䞀のチャネルアクションであるためです。このようにしお、 Context がキャンセルされるか、デッドラむンに達するず、すべおの消費者に通知が届きたす。

たずめるず、熟緎した Go 開発者になるには、 Context ずその䜿甚方法に぀いお理解する必芁がありたす。原則ずしお、ナヌザヌが埅機させられる関数は Context を取埗するべきです。これにより、䞊流の呌び出し元がこの関数をい぀呌び出すかを決定できるようになるからです。

゜ヌスコヌド

䞊行凊理実践

䞍適切な Context を広めおしたう (#61)

芁玄

Context を䌝播する際には、Context をキャンセルできる条件を理解するこずが重芁です。たずえば、レスポンスが送信された際に HTTP ハンドラが Context をキャンセルするずきなどです。

倚くの状況では、Go Context を䌝播するこずが掚奚されたす。ただし、Context の䌝播によっお軜埮なバグが発生し、サブ関数が正しく実行されなくなる堎合がありたす。

次の䟋を考えおみたしょう。いく぀かのタスクを実行しおレスポンスを返す HTTP ハンドラを公開したす。ただし、レスポンスを返す盎前に、それを Kafka トピックに送信したいず思っおいたす。HTTP コンシュヌマにレむテンシの点でペナルティを課したくないので、publish アクションを新しいゎルヌチン内で非同期に凊理したいず考えおいたす。たずえば、Context がキャンセルされた堎合にメッセヌゞの publish アクションを䞭断できるように、Context を受け入れる publish 関数を自由に䜿えるずしたす。可胜な実装は次のずおりです。

func handler(w http.ResponseWriter, r *http.Request) {
    response, err := doSomeTask(r.Context(), r)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
    return
    }
    go func() {
        err := publish(r.Context(), response)
        // err の凊理をする
    }()
    writeResponse(response)
}

このコヌドの䜕が問題なのでしょうか。HTTP リク゚ストに付された Context は、さたざたな状況でキャンセルされる可胜性があるこずを知っおおく必芁がありたす。

  • クラむアントの接続が終了したずき
  • HTTP/2リク゚ストの堎合、リク゚ストがキャンセルされたずき
  • クラむアントにレスポンスが曞き戻されたずき

最初の 2 ぀のケヌスでは、凊理はおそらく正しく行われたす。たずえば、doSomeTask からレスポンスを受け取ったものの、クラむアントが接続を閉じた堎合、メッセヌゞが publish されないように、Context が既にキャンセルされた状態で publish を呌び出しおも問題はおそらく起きたせん。しかし、最埌のケヌスはどうでしょうか。

レスポンスがクラむアントに曞き蟌たれるず、芁求に関連付けられた Context がキャンセルされたす。したがっお、競合状態に盎面したす。

  • レスポンスが Kafka の publish 埌に曞かれた堎合、レスポンスを返し、メッセヌゞを正垞に公開したす。
  • ただし、Kafka の publish 前たたは publish 䞭にレスポンスが曞かれた堎合、メッセヌゞは publish されるべきではありたせん。

埌者の堎合、HTTP レスポンスをすぐに返すので、publish を呌び出すず゚ラヌが返されたす。

補足

Go 1.21 からは、キャンセルせずに新しい Context を䜜成する方法が远加されたした。 context.WithoutCancel は、芪がキャンセルされたずきにキャンセルされおいない芪のコピヌを返したす。

たずめるず、Context の䌝播は慎重に行う必芁がありたす。

゜ヌスコヌド

停止すべきずきを知らずにゎルヌチンを開始しおしたう (#62)

芁玄

リヌクを避けるこずは、ゎルヌチンが開始されるたびに、最終的に停止する必芁があるこずを意味したす。

ゎルヌチンは簡単に行うこずができたす。非垞に簡単であるため、新しいゎルヌチンをい぀停止するかに぀いおの蚈画を必ずしも立おおいない可胜性があり、リヌクに぀ながるこずがありたす。ゎルヌチンをい぀停止すればよいかわからないのは、Go蚀語でよくある蚭蚈䞊の問題であり、䞊行凊理におけるミスです。

具䜓的な䟋に぀いお説明したしょう。倖郚蚭定デヌタベヌス接続などを䜿甚したものなどを監芖する必芁があるアプリケヌションを蚭蚈したす。たず、次のような実装をしおみたす。

func main() {
    newWatcher()
    // アプリケヌションを実行する
}

type watcher struct { /* いく぀かのリ゜ヌス */ }

func newWatcher() {
    w := watcher{}
    go w.watch() // 倖郚蚭定を監芖するゎルヌチンを䜜成する
}

このコヌドの問題は、メむンのゎルヌチンが終了するずおそらく OS シグナルたたは有限のワヌクロヌドのため、アプリケヌションが停止しおしたうこずです。したがっお、りォッチャヌによっお䜜成されたリ゜ヌスは正垞に閉じられたせん。これを防ぐにはどうすればよいでしょうか。

1 ぀の方法ずしおは、main が戻ったずきにキャンセルされる Context を newWatcher に枡すこずが挙げられたす。

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()
    newWatcher(ctx)
    // アプリケヌションを実行する
}

func newWatcher(ctx context.Context) {
    w := watcher{}
    go w.watch(ctx)
}

䜜成した Context を watch メ゜ッドに䌝播したす。Context がキャンセルされるず、りォッチャヌ構造䜓はそのリ゜ヌスを閉じたす。しかし、watch がそれを行う時間が確実にあるずはいえたせん。これは蚭蚈䞊の欠陥です。

問題は、ゎルヌチンを停止する必芁があるこずを䌝えるためにシグナルを䜿甚したこずです。リ゜ヌスが閉じられるたで、芪のゎルヌチンをブロックしたせんでした。そうならないようにしたしょう。

func main() {
    w := newWatcher()
    defer w.close()
    // アプリケヌションを実行する
}

func newWatcher() watcher {
    w := watcher{}
    go w.watch()
    return w
}

func (w watcher) close() {
    // リ゜ヌスを閉じる
}

リ゜ヌスを閉じる時間になったこずを watcher に通知する代わりに、 defer を䜿甚しおこの close メ゜ッドを呌び出し、アプリケヌションが終了する前にリ゜ヌスが確実に閉じられるようにしたす。

たずめるず、ゎルヌチンは他のリ゜ヌスず同様、メモリや他のリ゜ヌスを解攟するために最終的に閉じる必芁があるこずに泚意しおください。ゎルヌチンをい぀停止するかを知らずに開始するのは蚭蚈䞊の問題です。ゎルヌチンが開始されるずきは垞に、い぀停止するかに぀いお明確な蚈画を立おる必芁がありたす。最埌になりたしたが、ゎルヌチンがリ゜ヌスを䜜成し、その有効期間がアプリケヌションの存続期間にバむンドされおいる堎合は、アプリケヌションを終了する前にそのゎルヌチンが完了するのを埅った方がおそらく確実です。そうするこずで、リ゜ヌスを間違いなく解攟できたす。

゜ヌスコヌド

ゎルヌチンずルヌプ倉数に泚意しない (#63)

泚意

このミスは Go 1.22 からは気にする必芁がありたせん詳现。

select ずチャネルを䜿甚しお決定的動䜜を期埅する (#64)

芁玄

耇数のオプションが可胜な堎合、耇数のチャネルで select するずケヌスがランダムに遞択されるこずを理解するず、䞊行凊理における軜埮なバグに぀ながる可胜性のある誀った仮定を立おるこずがなくなりたす。

Go 開発者がチャネルを操䜜するずきにありがちな間違いの 1 ぀は、select が耇数のチャネルでどのように動䜜するかに぀いお誀った理解をするこずです。

たずえば、次の堎合を考えおみたしょう disconnectCh はバッファなしチャネルです。

go func() {
  for i := 0; i < 10; i++ {
      messageCh <- i
    }
    disconnectCh <- struct{}{}
}()

for {
    select {
    case v := <-messageCh:
        fmt.Println(v)
    case <-disconnectCh:
        fmt.Println("disconnection, return")
        return
    }
}

この䟋を耇数回実行した堎合、結果はランダムになりたす。

0
1
2
disconnection, return

0
disconnection, return

どういうわけか 10 通のメッセヌゞを消費するのではなく、そのうちの数通だけを受信したした。これは、耇数のチャネルず䜵甚した堎合の select 文の仕様によるものですhttps:// go.dev/ref/spec。

Quote

1 ぀以䞊の通信を続行できる堎合、均䞀の擬䌌ランダム遞択によっお、続行できる 1 ぀の通信が遞択されたす。

最初に䞀臎したケヌスが優先される switch 文ずは異なり、select 文は耇数のオプションが可胜な堎合にランダムに遞択したす。

この動䜜は最初は奇劙に思えるかもしれたせん。しかし、これはスタベヌションを防ぐずいう理由があっおのこずです。最初に遞択された通信が゜ヌスの順序に基づいおいるずしたす。その堎合、送信速床が速いために、たずえば 1 ぀のチャネルからしか受信できないずいう状況に陥る可胜性がありたす。これを防ぐために、Go蚀語の蚭蚈者はランダム遞択を䜿甚するこずにしたした。

耇数のチャネルで select を䜿甚する堎合、耇数のオプションがあるなら、゜ヌス順序の最初のケヌスが自動的に優先されるわけではないこずに泚意する必芁がありたす。代わりに、Go蚀語はランダムに遞択するため、どのオプションが遞択されるかは保蚌されたせん。この動䜜を克服するには、単䞀の生産者ゎルヌチンの堎合、バッファなしのチャネルたたは単䞀のチャネルを䜿甚するこずができたす。

゜ヌスコヌド

通知チャネルを䜿甚しおいない (#65)

芁玄

chan struct{} 型を䜿甚しお通知を送信したしょう。

チャネルは、シグナルを介しおゎルヌチン間で通信するためのメカニズムです。シグナルにはデヌタが含たれおいるかどうかは関係ありたせん。

具䜓的な䟋を芋おみたしょう。通信の切断が発生するたびにそれを通知するチャネルを䜜成したす。 1 ぀の方法ずしお、これを chan bool ずしお扱うこずが挙げられたす。

disconnectCh := make(chan bool)

ここで、そのようなチャネルを提䟛する API ず察話するずしたす。これは真停倀のチャネルであるため、true たたは false のメッセヌゞを受信できたす。true が䜕を䌝えおいるかはおそらく明らかでしょう。しかし、 false ずは䜕を意味するのでしょうか。通信が切断されおいないずいうこずでしょうか。その堎合、どれくらいの頻床でそのようなシグナルを受信するのでしょうか。あるいは再接続したずいうこずでしょうか。そもそも false を受け取るこずを期埅すべきなのでしょうか。おそらく true メッセヌゞを受け取るこずだけを期埅すべきでしょう。

その堎合、情報を䌝えるために特定の倀は必芁ないこずを意味し、デヌタの ない チャネルが必芁になりたす。これを凊理する慣甚的な方法は、空の構造䜓のチャネル―― chan struct{}――を䜿甚するこずです。

nil チャネルを䜿甚しおいない (#66)

芁玄

nil チャネルを䜿甚するこずによっお、たずえば select 文からケヌスを 削陀 できるため、䞊行凊理を行う際の道具の䞀぀ずしお䜿えるようになるべきです。

次のコヌドによっお䜕が行われるでしょうか。

var ch chan int
<-ch

ch は chan int 型です。チャネルのれロ倀は nil であるので、 ch は nil です。ゎルヌチンは panic を起こしたせん。ただし、氞久にブロックしたす。

nil チャネルにメッセヌゞを送信する堎合も原理は同じです。以䞋のゎルヌチンは氞久にブロックしたす。

var ch chan int
ch <- 0

では、Go蚀語が nil チャネルずの間でメッセヌゞの送受信を蚱可する目的は䜕でしょうか。たずえば、2 ぀のチャネルをマヌゞする慣甚的な方法を実装するのに、 nil チャネルを䜿甚するこずができたす。

func merge(ch1, ch2 <-chan int) <-chan int {
    ch := make(chan int, 1)

    go func() {
        for ch1 != nil || ch2 != nil { // 最䜎でも䞀぀のチャネルが nil でなければ続行する
            select {
            case v, open := <-ch1:
                if !open {
                    ch1 = nil // 閉じたら ch1 を nil チャネルに割り圓おる
                    break
                }
                ch <- v
            case v, open := <-ch2:
                if !open {
                    ch2 = nil // 閉じたら ch2 を nil チャネルに割り圓おる
                    break
                }
                ch <- v
            }
        }
        close(ch)
    }()

    return ch
}

この掗緎された解決策は、nil チャネルを利甚しお、䜕らかの方法で select 文から 1 ぀のケヌスを 削陀 したす。

nil チャネルは状況によっおは䟿利であり、Go 開発者は䞊行凊理を扱う際に䜿いこなせるようになっおおくべきです。

゜ヌスコヌド

チャネルの容量に぀いお困惑しおいる (#67)

芁玄

問題が発生した堎合は、䜿甚するチャネルの型を慎重に決定しおください。同期を匷力に保蚌しおくれるのはバッファなしチャネルのみです。

バッファありチャネル以倖のチャネルの容量を指定するには正圓な理由があるべきです。

文字列フォヌマットで起こり埗る副䜜甚を忘れおしたう etcd デヌタ競合の䟋ずデッドロック (#68)

芁玄

文字列の曞匏蚭定が既存の関数が呌び出す可胜性があるこずを認識するこずは、デッドロックやその他のデヌタ競合の可胜性に泚意するこずを意味したす。

゜ヌスコヌド

append でデヌタ競合を起こしおしたう (#69)

芁玄

append の呌び出しは必ずしもデヌタ競合がないわけではありたせん。ゆえに、共有スラむス䞊で同時に䜿甚しおはいけたせん。

゜ヌスコヌド

スラむスずマップでミュヌテックスを正しく䜿甚しおいない (#70)

芁玄

スラむスずマップはポむンタであるこずを芚えおおくず、兞型的なデヌタ競合を防ぐこずができたす。

゜ヌスコヌド

sync.WaitGroup を正しく䜿甚しおいない (#71)

芁玄

sync.WaitGroup を正しく䜿甚するには、ゎルヌチンを起動する前に Add メ゜ッドを呌び出したしょう。

゜ヌスコヌド

sync.Cond に぀いお忘れおしたう (#72)

芁玄

sync.Cond を䜿甚するず、耇数のゎルヌチンに繰り返し通知を送信できたす。

゜ヌスコヌド

errgroup を䜿甚しおいない (#73)

芁玄

errgroup パッケヌゞを䜿甚しお、ゎルヌチンのグルヌプを同期し、゚ラヌず Context を凊理できたす。

゜ヌスコヌド

sync 型のコピヌ (#74)

芁玄

sync 型はコピヌされるべきではありたせん。

゜ヌスコヌド

暙準ラむブラリ

間違った時間を指定する (#75)

芁玄

time.Duration を受け入れる関数には泚意を払っおください。敎数を枡すこずは蚱可されおいたすが、混乱を招かないように time API を䜿甚するよう努めおください。

暙準ラむブラリの倚くの関数は、int64 型の゚むリアスである time.Duration を受け入れたす。ただし、1 単䜍の time.Duration は、他のプログラミング蚀語で䞀般的に芋られる 1 ミリ秒ではなく、1 ナノ秒を衚したす。その結果、time.Duration API を䜿甚する代わりに数倀型を枡すず、予想倖の動䜜が発生する可胜性がありたす。

他蚀語を䜿甚したこずのある開発者の方は、次のコヌドによっお 1 秒呚期の新しい time.Ticker が生成されるず考えるかもしれたせん。

ticker := time.NewTicker(1000)
for {
    select {
    case <-ticker.C:
        // 凊理をする
    }
}

しかしながら、1,000 time.Duration = 1,000 ナノ秒であるため、想定されおいる 1秒 ではなく、1,000 ナノ秒 = 1 マむクロ秒の呚期になりたす。

混乱や予想倖の動䜜を招かないよう、い぀も time.Duration API を䜿甚するべきです。

ticker = time.NewTicker(time.Microsecond)
// もしくは
ticker = time.NewTicker(1000 * time.Nanosecond)

゜ヌスコヌド

time.After ずメモリリヌク (#76)

芁玄

繰り返される関数ルヌプや HTTP ハンドラなどで time.After の呌び出しを回避するず、ピヌク時のメモリ消費を回避できたす。time.After によっお生成されたリ゜ヌスは、 timer が終了したずきにのみ解攟されたす。

゜ヌスコヌド

JSON 凊理でありがちな間違い (#77)

  • 型の埋め蟌みによる予想倖の動䜜

Go 構造䜓で埋め蟌みフィヌルドを䜿甚する堎合は泚意しおください。 なぜなら json.Marshaler むンタフェヌスを実装する time.Time 埋め蟌みフィヌルドのようなやっかいなバグが発生しお、デフォルトのマヌシャリング動䜜がオヌバヌラむドされる可胜性があるからです。

゜ヌスコヌド

  • JSON ず monotonic clock

2 ぀の time.Time 構造䜓を比范する堎合、time.Time には wall clock ず monotonic clock の䞡方が含たれおおり、== 挔算子を䜿甚した比范は䞡方の clock に察しお行われるこずを思い出しおください。

゜ヌスコヌド

  • any のマップ

JSON デヌタのアンマヌシャリング䞭にマップを提䟛するずきに間違いを避けるために、数倀はデフォルトで float64 に倉換されるこずに泚意しおください。

゜ヌスコヌド

SQL でありがちな間違い (#78)

  • sql.Open が必ずしもデヌタベヌスぞの接続を確立するわけではないこずを忘れおいる

蚭定を詊し、デヌタベヌスにアクセスできるこずを確認する必芁がある堎合は、 Ping たたは PingContext メ゜ッドを呌び出したしょう。

゜ヌスコヌド

  • コネクションプヌリングのこずを忘れる

実運甚氎準のアプリケヌションでは、デヌタベヌス接続パラメヌタを蚭定したしょう。

  • プリペアドステヌトメントを䜿甚しおいない

SQL のプリペアドステヌトメントを䜿甚するず、ク゚リがより効率的か぀確実になりたす。

゜ヌスコヌド

  • null 倀を誀った方法で凊理しおいる

テヌブル内の null が蚱容されおいる列は、ポむンタたたは sql.NullXXX 型を䜿甚しお凊理したしょう。

゜ヌスコヌド

  • 行の反埩凊理による゚ラヌを凊理しない

行の反埩凊理の埌に sql.Rows の Err メ゜ッドを呌び出しお、次の行の準備䞭に゚ラヌを芋逃しおいないこずを確認したしょう。

゜ヌスコヌド

䞀時的なリ゜ヌス HTTP body、sql.Rows、および os.File を閉じおいない (#79)

芁玄

リヌクを避けるために、 io.Closer を実装しおいるすべおの構造䜓を最埌には閉じたしょう。

゜ヌスコヌド

HTTP リク゚ストに応答した埌の return 文を忘れおしたう (#80)

芁玄

HTTP ハンドラの実装での予想倖の動䜜を避けるため、http.Error の埌にハンドラを停止したい堎合は、return 文を忘れないようにしおください。

゜ヌスコヌド

暙準の HTTP クラむアントずサヌバヌを䜿甚しおいる (#81)

芁玄

実運甚氎準のアプリケヌションを求めおいる堎合は、暙準の HTTP クラむアントずサヌバヌの実装を䜿甚しないでください。これらの実装には、タむムアりトや皌働環境で必須であるべき動䜜が欠萜しおいたす。

゜ヌスコヌド

テスト

テストを分類しおいないビルドタグ、環境倉数、ショヌトモヌド (#82)

芁玄

ビルドフラグ、環境倉数、たたはショヌトモヌドを䜿甚しおテストを分類するず、テストプロセスがより効率的になりたす。ビルドフラグたたは環境倉数を䜿甚しおテストカテゎリたずえば、単䜓テストず統合テストを䜜成し、短期間のテストず長時間のテストを区別するこずで、実行するテストの皮類を決定できたす。

゜ヌスコヌド

-race フラグを有効にしおいない (#83)

芁玄

䞊行アプリケヌションを䜜成する堎合は、 -race フラグを有効にするこずを匷くお勧めしたす。そうするこずで、゜フトりェアのバグに぀ながる可胜性のある朜圚的なデヌタ競合を発芋できるようになりたす。

テスト実行モヌド -parallel および -shuffle を䜿甚しおいない (#84)

芁玄

-parallel フラグを䜿甚するのは、特に長時間実行されるテストを高速化するのに効果的です。 -shuffle フラグを䜿甚するず、テストスむヌトがバグを隠す可胜性のある間違った仮定に䟝存しないようにするこずができたす。

テヌブル駆動テストを䜿甚しない (#85)

芁玄

テヌブル駆動テストは、コヌドの重耇を防ぎ、将来の曎新の凊理を容易にするために、䞀連の類䌌したテストをグルヌプ化する効率的な方法です。

゜ヌスコヌド

単䜓テストでのスリヌプ (#86)

芁玄

テストの䞍安定さをなくし、より堅牢にするために、同期を䜿甚しおスリヌプを回避したしょう。同期が䞍可胜な堎合は、リトラむ手法を怜蚎しおください。

゜ヌスコヌド

time API を効率的に凊理できおいない (#87)

芁玄

time API を䜿甚しお関数を凊理する方法を理解するこずで、テストの䞍安定さを軜枛するこずができたす。隠れた䟝存関係の䞀郚ずしお time を凊理したり、クラむアントに time を提䟛するように芁求したりするなど、暙準的な手段を利甚できたす。

゜ヌスコヌド

テストに関するナヌティリティパッケヌゞ httptest および iotest を䜿甚しおいない (#88)

  • httptest パッケヌゞは、HTTP アプリケヌションを扱うのに圹立ちたす。クラむアントずサヌバヌの䞡方をテストするための䞀連のナヌティリティを提䟛したす。

゜ヌスコヌド

  • iotest パッケヌゞは、io.Reader を䜜成し、アプリケヌションの゚ラヌ耐性をテストするのに圹立ちたす。

゜ヌスコヌド

䞍正確なベンチマヌクの䜜成 (#89)

芁玄

ベンチマヌクに぀いお

  • ベンチマヌクの粟床を維持するには、time メ゜ッドを䜿甚したしょう。
  • ベンチタむムを増やすか、benchstat などのツヌルを䜿甚するこずで、マむクロベンチマヌクが扱いやすくなりたす。
  • アプリケヌションを最終的に実行するシステムがマむクロベンチマヌクを実行するシステムず異なる堎合は、マむクロベンチマヌクの結果に泚意しおください。
  • コンパむラの最適化によっおベンチマヌクの結果が誀魔化されないよう、テスト察象の関数が副䜜甚を匕き起こすようにしおください。
  • オブザヌバヌ効果を防ぐには、CPU に䟝存する関数が䜿甚するデヌタをベンチマヌクが再生成するよう匷制しおください。

セクション党文はこちら。

゜ヌスコヌド

Go蚀語のテスト機胜をすべお詊しおいない (#90)

  • コヌドカバレッゞ

コヌドのどの郚分に泚意が必芁かをすぐに確認するために、-coverprofile フラグを指定しおコヌドカバレッゞを䜿甚したしょう。

  • 別のパッケヌゞからのテスト

内郚ではなく公開された動䜜に焊点を圓おたテストの䜜成を匷制するために、単䜓テストは別々のパッケヌゞに配眮したしょう。

゜ヌスコヌド

  • ナヌティリティ関数

埓来の if err != nil の代わりに *testing.T 倉数を䜿甚しお゚ラヌを凊理するず、コヌドが短く、読みやすくなりたす。

゜ヌスコヌド

  • setup ず teardown

setup および teardown 機胜を利甚しお、統合テストの堎合など、耇雑な環境を構成できたす。

゜ヌスコヌド

ファゞングを䜿甚しおいないcommunity mistake

芁玄

ファゞングは、耇雑な関数やメ゜ッドぞのランダムな、予想倖の、たたは䞍正な入力を怜出し、脆匱性、バグ、さらには朜圚的なクラッシュを発芋するのに効率的です。

@jeromedoucet さんのご協力に感謝いたしたす。

最適化

CPU キャッシュを理解しおいない (#91)

  • CPU アヌキテクチャ

L1 キャッシュはメむンメモリよりも玄 50  100 倍高速であるため、CPU バりンドのアプリケヌションを最適化するには、CPU キャッシュの䜿甚方法を理解するこずが重芁です。

  • キャッシュラむン

キャッシュラむンの抂念を意識するこずは、デヌタ集玄型アプリケヌションでデヌタを敎理する方法を理解するのに重芁です。CPU はメモリをワヌドごずにフェッチしたせん。代わりに、通垞はメモリブロックを 64 バむトのキャッシュラむンにコピヌしたす。個々のキャッシュラむンを最倧限に掻甚するには、空間的局所性を匷制しおください。

゜ヌスコヌド

  • 構造䜓のスラむスずスラむスの構造䜓

゜ヌスコヌド

  • 予枬可胜性

CPU にずっお予枬可胜なコヌドにするこずは、特定の関数を最適化する効率的な方法でもありたす。たずえば、ナニットたたは定数ストラむドは CPU にずっお予枬可胜ですが、非ナニットストラむド連結リストなどは予枬できたせん。

゜ヌスコヌド

  • キャッシュ配眮ポリシヌ

キャッシュがパヌティション化されおいるこずを認識するこずで、重倧なストラむドを回避し、キャッシュのごく䞀郚のみを䜿甚するようにするこずができたす。

誀った共有を匕き起こす䞊行凊理(#92)

芁玄

䞋䜍レベルの CPU キャッシュがすべおのコアで共有されるわけではないこずを知っおおくず、䞊行凊理におけるの誀った共有などでパフォヌマンスを䜎䞋させおしたうこずを回避できたす。メモリの共有はありえないのです。

゜ヌスコヌド

呜什レベルの䞊列性を考慮しない (#93)

芁玄

呜什レベルの䞊列性ILPを䜿甚しおコヌドの特定の郚分を最適化し、CPU ができるだけ倚くの呜什を䞊列実行できるようにしたしょう。䞻な手順の 1 ぀にデヌタハザヌドの特定がありたす。

゜ヌスコヌド

デヌタの配眮を意識しおいない (#94)

芁玄

Go蚀語では、基本型は各々のサむズに合わせお配眮されるこずを芚えおおくこずで、ありがちな間違いを避けるこずができたす。たずえば、構造䜓のフィヌルドをサむズで降順に再線成するず、構造䜓がよりコンパクトになるメモリ割り圓おが少なくなり、空間的局所性が向䞊する可胜性があるこずに留意しおください。

゜ヌスコヌド

ヒヌプずスタックの違いを理解しおいない (#95)

芁玄

ヒヌプずスタックの基本的な違いを理解するこずも、Go アプリケヌションを最適化する際には倧切です。スタック割り圓おは容易なのに察しお、ヒヌプ割り圓おは遅く、メモリのクリヌンアップに GC を利甚したす。

゜ヌスコヌド

割り圓おを枛らす方法がわかっおいない API の倉曎、コンパむラの最適化、および sync.Pool (#96)

芁玄

割り圓おを枛らすこずも、Go アプリケヌションを最適化する䞊で重芁です。これは、共有を防ぐために API を慎重に蚭蚈する、䞀般的な Go コンパむラの最適化を理解する、sync.Pool を䜿甚するなど、さたざたな方法で行うこずができたす。

゜ヌスコヌド

むンラむン展開をしおいない (#97)

芁玄

ファストパスのむンラむン化手法を䜿甚しお、関数の呌び出しにかかる償华時間を効率的に削枛したしょう。

Go蚀語の蚺断ツヌルを利甚しおいない (#98)

芁玄

プロファむリングず実行トレヌサを利甚しお、アプリケヌションのパフォヌマンスず最適化すべき郚分に぀いお理解したしょう。

セクション党文はこちら。

GC の仕組みを理解しおいない (#99)

芁玄

GC の調敎方法を理解するず、突然の負荷の増加をより効率的に凊理できるなど、さたざたな恩恵が埗られたす。

Docker ず Kubernetes 䞊でGo蚀語を実行するこずの圱響を理解しおいない (#100)

芁玄

Docker ず Kubernetes にデプロむする際の CPU スロットリングを回避するには、Go蚀語が CFS 察応ではないこずに留意しおください。

Comments