100-go-mistakes/docs/ja.md
2023-10-08 18:47:11 +09:00

15 KiB

title comments
Japanese Version true

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

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

???+ warning "注意"

現在、大幅に多くのコンテンツを追加して強化している新しいバージョンを閲覧しています。このバージョンはまだ開発中です。問題を見つけた場合はどうぞ気軽にPRを作成してください。

コードとプロジェクト構成

意図的でない変数のシャドーイング (#1)

???+ info "要約"

変数のシャドーイングを避けることは、誤った変数の参照や読者の混乱を防ぎます。

変数のシャドーイングは、変数名がブロック内で再宣言されることで生じますが、これは間違いを引き起こしやすくします。変数のシャドーイングを禁止するかどうかは個人の好みによります。たとえば、エラーに対して err のような既存の変数名を再利用すると便利な場合があります。とはいえ、コードはコンパイルされたものの、値を受け取った変数が予期したものではないというシナリオに直面する可能性があるため、原則として引き続き注意を払う必要があります。

ソースコード :simple-github:

不必要にネストされたコード (#2)

???+ info "要約"

ネストが深くならないようにし、ハッピーパスを左側に揃えることでメンタルコードモデルを構築することが容易になります。

一般的に、関数がより深いネストを要求するほど、読んで理解することがより複雑になります。私たちのコードの可読性を最適化するために、このルールの適用方法を見ていきましょう。

  • 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")
}
// ...

読みやすいコードを書くことは、すべての開発者にとって重要な課題です。ネストされたブロックの数を減らすよう努め、ハッピーパスを左側に揃え、できるだけ早く戻ることが、コードの可読性を向上させる具体的な手段です。

ソースコード :simple-github:

init関数の誤用 (#3)

???+ info "要約"

変数を初期化するときは、init関数のエラー処理が制限されており、状態の処理とテストがより複雑になることに注意してください。ほとんどの場合、初期化は特定の関数として処理されるべきです。

init関数は、アプリケーションの状態を初期化するために使用される関数です。引数を取らず、結果も返しません ( func() 関数)。 パッケージが初期化されると、パッケージ内のすべての定数および変数の宣言が評価されます。次に、init関数が実行されます。

init関数はいくつかの問題を引き起こす可能性があります。

  • エラー管理が制限される可能性があります。
  • テストの実装方法が複雑になる可能性があります (たとえば、外部依存関係を設定する必要がありますが、単体テストの範囲では必要ない可能性があります)。
  • 初期化で状態を設定する必要がある場合は、グローバル変数を使用して行う必要があります。

init関数には注意が必要です。ただし、静的構成の定義など、状況によっては役立つ場合があります。それ以外のほとんどの場合、初期化処理はアドホック関数を通じて行われるべきです。

ソースコード :simple-github:

ゲッターとセッターの乱用 (#4)

???+ info "要約"

Go言語では、慣用的にゲッターとセッターの使用を強制することはありません。 実利を重視し、効率性と特定の慣習に従うこととの間の適切なバランスを見つけることが、進むべき道であるはずです。

データのカプセル化とは、オブジェクトの値または状態を隠すことを指します。ゲッターとセッターは、エクスポートされていないオブジェクトフィールドの上にエクスポートされたメソッドを提供することでカプセル化を可能にする手段です。

Go言語では、一部の言語で見られるようなゲッターとセッターの自動サポートはありません。また、ゲッターとセッターを使用して構造体フィールドにアクセスすることは必須でも慣用的でもありません。値をもたらさない構造体のゲッターとセッターでコードを埋めるべきではありません。実利を重視し、他のプログラミングパラダイムで時には議論の余地がないと考えられている慣習に従うことと、効率性との間の適切なバランスを見つけるよう努めるべきです。

Go言語は、シンプルさを含む多くの特性を考慮して設計された独自の言語であることを忘れないでください。ただし、ゲッターとセッターの必要性が見つかった場合、または前述のように、前方互換性を保証しながら将来の必要性が予測される場合は、それらを使用することに問題はありません。

インターフェイス汚染 (#5)

???+ info "要約"

抽象化は作成されるべきものではなく、発見されるべきものです。 不必要な複雑さを避けるために、インターフェイスは、必要になると予測したときではなく、必要になったときに作成するか、少なくとも抽象化が有効であることを証明できる場合に作成してください。

インターフェイスは、オブジェクトの動作を指定する方法を提供します。複数のオブジェクトが実装できる共通項を抽出するために、インターフェイスは使用されます。Go言語のインターフェイスが大きく異なるのは、暗黙的に満たされることです。オブジェクト X がインターフェイス Y を実装していることを示す implements のような明示的なキーワードはありません。

一般に、インターフェイスが価値をもたらすと考えられる主要な使用例は3つあります。それは、共通の動作を除外する、何らかの分離を作成する、および型を特定の動作に制限するというものです。ただし、このリストはすべてを網羅しているわけではなく、直面する状況によっても異なります。

多くの場合、インターフェイスは抽象化するために作成されます。そして、プログラミングで抽象化するときの主な注意点は、抽象化は作成されるべきではなく、発見されるべきであるということを覚えておくことです。すなわち、そうする直接の理由がない限り、コード内で抽象化すべきではないということです。 インターフェイスを使って設計するのではなく、具体的なニーズを待つべきです。別の言い方をすれば、インターフェイスは必要になると予測したときではなく、必要になったときに作成する必要があります。 インターフェイスの過度な使用をした場合の主な問題は何でしょうか。答えは、コードフローがより複雑になることです。役に立たない間接参照を追加しても何の価値もありません。それは価値のない抽象化をすることで、コードを読み、理解し、推論することをさらに困難にします。インターフェイスを追加する明確な理由がなく、インターフェイスによってコードがどのように改善されるかが不明瞭な場合は、そのインターフェイスの目的に異議を唱える必要があります。実装を直接呼び出すのも一つの手です。

コード内で抽象化するときは注意が必要です (抽象化は作成するのではなく、発見する必要があります)。後で必要になる可能性があるものを考慮し、完璧な抽象化レベルを推測して、私たちソフトウェア開発者はコードをオーバーエンジニアリングすることがよくあります。ほとんどの場合、コードが不必要な抽象化で汚染され、読みにくくなるため、このプロセスは避けるべきです。

!!! quote "ロブ・パイク"

インターフェイスでデザインするな。インターフェイスを見つけ出せ。 

抽象的に問題を解決しようとするのではなく、今解決すべきことを解決しましょう。最後に重要なことですが、インターフェイスによってコードがどのように改善されるかが不明瞭な場合は、コードを簡素化するためにインターフェイスを削除することを検討する必要があるでしょう。

ソースコード :simple-github:

プロデューサー側のインターフェイス (#6)

???+ info "要約"

インターフェイスをクライアント側で保持することで不必要な抽象化を回避できます。

Go言語ではインターフェイスが暗黙的に満たされます。これは、明示的な実装を持つ言語と比較して大きな変化をもたらす傾向があります。ほとんどの場合、従うべきアプローチは前のセクションで説明したもの――抽象は作成するのではなく、発見する必要がある――に似ています。これは、すべてのクライアントに対して特定の抽象化を強制するのはプロデューサーの役割ではないことを意味します。代わりに、何らかの形式の抽象化が必要かどうかを判断し、そのニーズに最適な抽象化レベルを決定するのはクライアントの責任です。

ほとんどの場合、インターフェイスはコンシューマー側に存在する必要があります。ただし、特定の状況 (たとえば、抽象化がコンシューマーにとって役立つことがわかっている――予測はしていない――場合) では、それをプロデューサー側で使用したい場合があります。そうした場合、可能な限り最小限に抑え、再利用可能性を高め、より簡単に構成できるように努めるべきです。

ソースコード :simple-github:

インターフェイスを返す (#7)

???+ info "要約"

柔軟性に問題がないようにするために、関数はほとんどの場合、インターフェイスではなく具体的​​な実装を返す必要があります。逆に、関数は可能な限りインターフェイスを受け入れる必要があります。

ほとんどの場合、インターフェイスではなく具体的​​な実装を返す必要があります。そうでないとパッケージの依存関係により設計がいっそう複雑になり、すべてのクライアントが同じ抽象化に依存する必要があるため、柔軟性に欠ける可能性があります。結論は前のセクションと似ています。抽象化がクライアントにとって役立つことが (予測ではなく) わかっている場合は、インターフェイスを返すことを検討してもよいでしょう。それ以外の場合は、抽象化を強制すべきではありません。それらはクライアントによって発見される必要があります。何らかの理由でクライアントが実装を抽象化する必要がある場合でも、クライアント側でそれを行うことができます。

インライン展開をしていない (#97)

???+ info "要約"

ファストパスのインライン化手法を使用して、関数の呼び出しにかかる償却時間を効率的に削減します。

Go言語の診断ツールを利用していない (#98)

???+ info "要約"

プロファイリングと実行トレーサを利用して、アプリケーションのパフォーマンスと最適化すべき部分について理解しましょう。

セクション全文はこちら

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

???+ info "要約"

GCの調整方法を理解すると、突然の負荷の増加をより効率的に処理できるなど、さまざまな恩恵が得られます。

DockerとKubernetes上でGo言語を実行することの影響を理解していない (#100)

???+ info "要約"

DockerとKubernetesにデプロイする際のCPUスロットリングを回避するには、Go言語がCFS対応ではないことに留意してください。