From 901ed41800a2fa8386e741cd668d9ca800d88f92 Mon Sep 17 00:00:00 2001 From: rustinwelter Date: Thu, 19 Oct 2023 16:47:02 +0900 Subject: [PATCH 1/3] Add Japanese Translation --- docs/ja.md | 789 ++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 697 insertions(+), 92 deletions(-) diff --git a/docs/ja.md b/docs/ja.md index 618a4eb9..05217ac5 100644 --- a/docs/ja.md +++ b/docs/ja.md @@ -81,17 +81,17 @@ if s == "" { ???+ info "要約" - 変数を初期化するときは、init関数のエラー処理が制限されており、状態の処理とテストがより複雑になることに注意してください。ほとんどの場合、初期化は特定の関数として処理されるべきです。 + 変数を初期化するときは、init関数のエラー処理が制限されており、ステートの処理とテストがより複雑になることに注意してください。ほとんどの場合、初期化は特定の関数として処理されるべきです。 -init関数は、アプリケーションの状態を初期化するために使用される関数です。引数を取らず、結果も返しません( `func()` 関数)。パッケージが初期化されると、パッケージ内のすべての定数および変数の宣言が評価されます。次に、init関数が実行されます。 +init関数は、アプリケーションのステートを初期化するために使用される関数です。引数を取らず、結果も返しません( `func()` 関数)。パッケージが初期化されると、パッケージ内のすべての定数および変数の宣言が評価されます。次に、init関数が実行されます。 init関数はいくつかの問題を引き起こす可能性があります。 * エラー処理が制限される可能性があります。 -* テストの実装方法が複雑になる可能性があります (たとえば、外部依存関係を設定する必要がありますが、単体テストの範囲では必要ない可能性があります)。 -* 初期化で状態を設定する必要がある場合は、グローバル変数を使用して行う必要があります。 +* テストの実装方法が複雑になる可能性があります(たとえば、外部依存関係を設定する必要がありますが、単体テストの範囲では必要ない可能性があります)。 +* 初期化でステートを設定する必要がある場合は、グローバル変数を使用して行う必要があります。 -init関数には注意が必要です。ただし、静的構成の定義など、状況によっては役立つ場合があります。それ以外のほとんどの場合、初期化処理はそのためだけに存在する関数を通じて行われるべきです。 +init関数には注意が必要です。ただし、静的構成の定義など、状況によっては役立つ場合があります。それ以外のほとんどの場合、初期化処理は特定の関数を通じて行われるべきです。 [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/02-code-project-organization/3-init-functions/) @@ -136,7 +136,7 @@ Go言語は、シンプルさを含む多くの特性を考慮して設計され インタフェースをクライアント側で保持することで不必要な抽象化を回避できます。 -Go言語ではインタフェースが暗黙的に満たされます。これは、明示的な実装を持つ言語と比較して大きな変化をもたらす傾向があります。ほとんどの場合、従うべきアプローチは前のセクションで説明したもの――_抽象は作成するのではなく、発見する必要がある_――に似ています。これは、すべてのクライアントに対して特定の抽象化を強制するのは生産者の役割ではないことを意味します。代わりに、何らかの形式の抽象化が必要かどうかを判断し、そのニーズに最適な抽象化レベルを決定するのはクライアントの責任です。 +Go言語ではインタフェースが暗黙的に満たされます。これは、明示的な実装を持つ言語と比較して大きな変化をもたらす傾向があります。ほとんどの場合、従うべきアプローチは前のセクションで説明したもの――_抽象化は作成するのではなく、発見する必要がある_――に似ています。これは、すべてのクライアントに対して特定の抽象化を強制するのは生産者の役割ではないことを意味します。代わりに、何らかの形式の抽象化が必要かどうかを判断し、そのニーズに最適な抽象化レベルを決定するのはクライアントの責任です。 ほとんどの場合、インタフェースは消費者側に存在する必要があります。ただし、特定の状況(たとえば、抽象化が消費者にとって役立つことがわかっている――予測はしていない――場合)では、それを生産者側で使用したい場合があります。そうした場合、可能な限り最小限に抑え、再利用可能性を高め、より簡単に構成できるように努めるべきです。 @@ -166,7 +166,7 @@ Go言語ではインタフェースが暗黙的に満たされます。これは ジェネリックスと型パラメーターを利用することで、要素や動作を除外するためのボイラープレートコードを避けることができます。ただし、型パラメータは時期尚早に使用せず、具体的な必要性がわかった場合にのみ使用してください。そうでなければ、不必要な抽象化と複雑さが生じます。 -セクションの全文は[こちら](9-generics.md)。 +セクション全文は[こちら](9-generics.md)。 [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/02-code-project-organization/9-generics/main.go) @@ -176,7 +176,7 @@ Go言語ではインタフェースが暗黙的に満たされます。これは 型埋め込みを使用すると、ボイラープレートコードを回避することもできます。ただし、そうすることで、一部のフィールドを非表示にしておく必要がある場合に問題が発生しないようにしてください。 -構造体を作成するとき、Go言語は型を埋め込むオプションを提供します。ただし、型埋め込みの意味をすべて理解していないと、予期しない動作が発生する可能性があります。このセクションでは、型を埋め込む方法、それがもたらすもの、および考えられる問題について見ていきます。 +構造体を作成するとき、Go言語は型を埋め込むオプションを提供します。ただし、型埋め込みの意味をすべて理解していないと、予想外の動作が発生する可能性があります。このセクションでは、型を埋め込む方法、それがもたらすもの、および考えられる問題について見ていきます。 Go言語では、名前なしで宣言された構造体フィールドは、埋め込みと呼ばれます。たとえば、次のようなものです。 @@ -244,7 +244,7 @@ func NewServer(addr string, opts ...Option) ( *http.Server, error) { <1> } } -// この段階で、オプション構造体が構築され、構成が含まれます。 +// この段階で、options 構造体が構築され、構成が含まれます。 // したがって、ポート設定に関連するロジックを実装できます。 var port int if options.port == nil { @@ -280,7 +280,7 @@ Functional Options パターンは、オプションを処理するための手 ???+ note "補足" -Go チームは Go プロジェクトの組織化/構造化に関する公式ガイドラインを2023年に発行しました: [go.dev/doc/modules/layout](https://go.dev/doc/modules/layout) + Go チームは Go プロジェクトの組織化/構造化に関する公式ガイドラインを 2023 年に発行しました: [go.dev/doc/modules/layout](https://go.dev/doc/modules/layout) ### ユーティリティパッケージの作成 (#13) @@ -310,9 +310,9 @@ Go チームは Go プロジェクトの組織化/構造化に関する公式ガ まず、エクスポートされたすべての要素を文章化する必要があります。構造、インタフェース、関数など、エクスポートする場合は文章化する必要があります。慣例として、エクスポートされた要素の名前から始まるコメントを追加します。 -慣例として、各コメントは句読点で終わる完全な文である必要があります。また、関数(またはメソッド)を文章化するときは、関数がどのように実行するかではなく、その関数が何を実行するつもりであるかを強調する必要があることにも留意してください。これはドキュメントではなく、関数とコメントについてです。ドキュメントは理想的には、消費者がエクスポートされた要素の使用方法を理解するためにコードを見る必要がないほど十分な情報を提供する必要があります。 +慣例として、各コメントは句読点で終わる完全な文である必要があります。また、関数(またはメソッド)を文章化するときは、関数がどのように実行するかではなく、その関数が何を実行するつもりであるかを強調する必要があることにも留意してください。これはドキュメントではなく、関数とコメントについてです。ドキュメントは理想的には、利用者がエクスポートされた要素の使用方法を理解するためにコードを見る必要がないほど十分な情報を提供する必要があります。 -変数または定数を文章化する場合、その目的と内容という2つの側面を伝えることが重要かもしれません。前者は、外部クライアントにとって役立つように、コードドキュメントとして存在する必要があります。ただし、後者は必ずしも公開されるべきではありません。 +変数または定数を文章化する場合、その目的と内容という 2 つの側面を伝えることが重要かもしれません。前者は、外部クライアントにとって役立つように、コードドキュメントとして存在する必要があります。ただし、後者は必ずしも公開されるべきではありません。 クライアントとメンテナがパッケージの目的を理解できるように、各パッケージをドキュメントする必要もあります。慣例として、コメントは `//Package` で始まり、その後にパッケージ名が続きます。パッケージコメントの最初の行は、パッケージに表示されるため簡潔にする必要があります。そして、次の行に必要な情報をすべて入力します。 @@ -324,42 +324,44 @@ Go チームは Go プロジェクトの組織化/構造化に関する公式ガ コードの品質と一貫性を向上させるには、リンターとフォーマッターを使用しましょう -リンターは、コードを分析してエラーを検出する自動ツールです。このセクションの目的は、既存のリンターの完全なリストを提供することではありません。そうした場合、すぐに使い物にならなくなってしまうからです。しかし、ほとんどの Go プロジェクトにリンターが不可欠である理由を理解し、覚えておきましょう。 +リンターは、コードを分析してエラーを検出する自動ツールです。このセクションの目的は、既存のリンターの完全なリストを提供することではありません。そうした場合、すぐに使い物にならなくなってしまうからです。ただし、ほとんどの Go プロジェクトにリンターが不可欠であるということは理解し、覚えておきましょう。 * [https://golang.org/cmd/vet](https://golang.org/cmd/vet)――Go言語の標準コードアナライザー * [https://github.com/kisielk/errcheck](https://github.com/kisielk/errcheck)――エラーチェッカー * [https://github.com/fzipp/gocyclo](https://github.com/fzipp/gocyclo)――循環的複雑度アナライザー -* [https://github.com/jgautheron/goconst](https://github.com/jgautheron/goconst)――反復文字列定数アナライザー -* -リンターのほかに、コードスタイルを修正するためにコードフォーマッターーも使用しましょう。以下に、いくつかのコードフォーマッターを示します。 +* [https://github.com/jgautheron/goconst](https://github.com/jgautheron/goconst)――複数回使用文字列アナライザー + + +リンターのほかに、コードスタイルを修正するためにコードフォーマッターも使用しましょう。以下に、いくつかのコードフォーマッターを示します。 + * [https://golang.org/cmd/gofmt](https://golang.org/cmd/gofmt)――Go言語の標準コードフォーマッター * [https://godoc.org/golang.org/x/tools/cmd/goimports](https://godoc.org/golang.org/x/tools/cmd/goimports)――Go言語の標準インポートフォーマッター -* + ほかに golangci-lint ([https://github.com/golangci/golangci-lint](https://github.com/golangci/golangci-lint)) というものがあります。これは、多くの便利なリンターやフォーマッターの上にファサードを提供するリンティングツールです。また、リンターを並列実行して分析速度を向上させることができ、非常に便利です。 リンターとフォーマッターは、コードベースの品質と一貫性を向上させる強力な方法です。時間をかけてどれを使用すべきかを理解し、それらの実行( CI や Git プリコミットフックなど)を自動化しましょう。 ## データ型 -### 8進数リテラルで混乱を招く (#17) +### 8 進数リテラルで混乱を招いてしまう (#17) ???+ info "要約" - 既存のコードを読むときは、 `0` で始まる整数リテラルが8進数であることに留意してください。また、接頭辞 `0o` を付けることで8進整数であることを明確にし、読みやすさを向上させましょう。 + 既存のコードを読むときは、 `0` で始まる整数リテラルが 8 進数であることに留意してください。また、接頭辞 `0o` を付けることで8進数であることを明確にし、読みやすさを向上させましょう。 8 進数は 0 で始まります(たとえば、`010` は 10 進数の 8 に相当します)。可読性を向上させ、将来のコードリーダーの潜在的な間違いを回避するには、 `0o` 接頭辞を使用して 8 進数であることを明らかにしましょう(例: `0o10` )。 他の整数リテラル表現にも注意してください。 - * _バイナリ_ - 接頭辞 `0b` あるいは `0B` を使用します(たとえば、 `0b` は10進数の 4 に相当します) - * _16進数_ - 接頭辞 `0x` あるいは `0X` を使用します(たとえば、 `0xF` は10進数の 15 に相当します)。 + * _バイナリ_ - 接頭辞 `0b` あるいは `0B` を使用します(たとえば、 `0b` は 10 進数の 4 に相当します) + * _16進数_ - 接頭辞 `0x` あるいは `0X` を使用します(たとえば、 `0xF` は 10 進数の 15 に相当します)。 * _虚数_ - 接尾辞 `i` を使用します(たとえば、 `3i` ) - 読みやすくするために、区切り文字としてアンダースコア( _ )を使用することもできます。たとえば、 10 億は `1_000_000_000` のように書くことができます。アンダースコアは `0b)00_00_01` のように他の表現と併用することもできます。 +読みやすくするために、区切り文字としてアンダースコア( _ )を使用することもできます。たとえば、 10 億は `1_000_000_000` のように書くことができます。アンダースコアは `0b)00_00_01` のように他の表現と併用することもできます。 [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/03-data-types/17-octal-literals/main.go) -### 整数オーバーフローの無視 (#18) +### 整数オーバーフローを無視している (#18) ???+ info "要約" @@ -385,7 +387,7 @@ constant 2147483648 overflows int32 特定のデルタ内で浮動小数点比較を行うと、コードの移植性を確保できます。加算または減算を実行するときは、精度を向上させるために、同程度の大きさの演算をグループ化してください。また、乗算と除算は加算と減算の前に実行してください。 -Go言語には、(虚数を省略した場合) `float32` と `float64` という2つの浮動小数点型があります。浮動小数点の概念は、小数値を表現できないという整数の大きな問題を解決するために発明されました。予期せぬ事態を避けるために、浮動小数点演算は実際の演算の近似であることを知っておく必要があります。 +Go言語には、(虚数を除いた場合) `float32` と `float64` という 2 つの浮動小数点型があります。浮動小数点の概念は、小数値を表現できないという整数の大きな問題を解決するために発明されました。予想外の事態を避けるために、浮動小数点演算は実際の演算の近似であることを知っておく必要があります。 そのために、乗算の例を見てみましょう。 @@ -410,7 +412,7 @@ Go言語の `float32` および `float64` 型は近似値であるため、い Go 開発者ならば、スライスの長さと容量の違いを理解するべきです。スライスの長さはスライス内の使用可能な要素の数であり、スライスの容量はバッキング配列内の要素の数です。 -セクション全文は[こちら](20-slice.md). +セクション全文は[こちら](20-slice.md)。 [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/03-data-types/20-slice-length-cap/main.go) @@ -438,7 +440,7 @@ Go言語では、nil と空のスライスは区別されます。nil スライ * nil と空のスライスを作成する糖衣構文としての `[]string(nil)` * 将来の長さがわかっている場合は `make([]string, length)` -要素なしでスライスを初期化する場合、最後のオプション `[]string{}` は避けるべきです。最後に、予期しない動作を防ぐために、使用するライブラリが nil と空のスライスを区別しているかどうかを確認してみましょう。 +要素なしでスライスを初期化する場合、最後のオプション `[]string{}` は避けるべきです。最後に、予想外の動作を防ぐために、使用するライブラリが nil と空のスライスを区別しているかどうかを確認してみましょう。 [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/03-data-types/22-nil-empty-slice/) @@ -450,31 +452,31 @@ Go言語では、nil と空のスライスは区別されます。nil スライ スライスに要素があるかどうかを判断するには、スライスが nil かどうか、またはその長さが 0 に等しいかどうかを確認することで判断できます。スライスが空である場合とスライスが nil である場合の両方をカバーできるため、長さを確かめることが最良の方法です。 -一方、インタフェースを設計するときは、軽微なプログラミングエラーを起こさないよう nil スライスと空のスライスを区別しないようにする必要があります。スライスを返すときに、nil または空のスライスを返すかどうかは、意味的にも技術的にも違いはありません。コーラーにとってはどちらも同じことを意味するはずです。この原理は連想配列でも同じです。連想配列が空かどうかを確認するには、それが nil かどうかではなく、その長さを確認しましょう。 +一方、インタフェースを設計するときは、軽微なプログラミングエラーを起こさないよう nil スライスと空のスライスを区別しないようにする必要があります。スライスを返すときに、nil または空のスライスを返すかどうかは、意味的にも技術的にも違いはありません。コーラーにとってはどちらも同じことを意味するはずです。この原理はマップでも同じです。マップが空かどうかを確認するには、それが nil かどうかではなく、その長さを確認しましょう。 [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/03-data-types/23-checking-slice-empty/main.go) -### スライスコピーを正しく作成していない (#24) +### スライスのコピーを正しく作成していない (#24) ???+ info "要約" - 組み込み関数 `copy` を使用してあるスライスを別のスライスにコピーするには、コピーされる要素の数が2つのスライスの長さの間の最小値に相当することに注意してください。 + 組み込み関数 `copy` を使用してあるスライスを別のスライスにコピーするには、コピーされる要素の数が 2 つのスライスの長さの間の最小値に相当することに注意してください。 要素をあるスライスから別のスライスにコピーする操作は、かなり頻繁に行われます。コピーを使用する場合、コピー先にコピーされる要素の数は 2 つのスライスの長さの間の最小値に相当することに注意する必要があります。また、スライスをコピーするための他の代替手段が存在することにも留意してください。そのため、コードベースでそれらを見つけても驚くことはありません。 [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/03-data-types/24-slice-copy/main.go) -### スライス追加の使用による予期せぬ副作用 (#25) +### `append` の使用による予想外の副作用 (#25) ???+ info "要約" - 2つの異なる関数が同じ配列に基づくスライスを使用する場合に、copy または完全なスライス式を使用することで `append` による衝突を防ぐことができます。ただし、大きなスライスを縮小する場合、メモリリークを防ぐことができるのはスライスコピーだけです。 + 2つの異なる関数が同じ配列に基づくスライスを使用する場合に、copy または完全スライス式を使用することで `append` による衝突を防ぐことができます。ただし、大きなスライスを縮小する場合、メモリリークを防ぐことができるのはスライスのコピーだけです。 -スライスを使用するときは、予期せぬ副作用につながる状況に直面する可能性があることを覚えておく必要があります。結果のスライスの長さがその容量より小さい場合、追加によって元のスライスが変更される可能性があります。起こり得る副作用の範囲を制限したい場合は、スライスコピーまたは完全なスライス式を使用できます。これにより、コピーを実行できなくなります。 +スライスを使用するときは、予想外の副作用につながる状況に直面する可能性があることを覚えておく必要があります。結果のスライスの長さがその容量より小さい場合、追加によって元のスライスが変更される可能性があります。起こり得る副作用の範囲を制限したい場合は、スライスのコピーまたは完全スライス式を使用できます。これにより、コピーを実行できなくなります。 -???+ note "補足" +???+ note "補足" - `s[low:high:max]`(完全なスライス式):この命令文は、容量が `max - low` に等しいことを除けば、`s[low:high]` で作成されたスライスと同様のスライスを作成します。 + `s[low:high:max]`(完全スライス式)――この命令文は、容量が `max - low` に等しいことを除けば、`s[low:high]` で作成されたスライスと同様のスライスを作成します。 [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/03-data-types/25-slice-append/main.go) @@ -486,35 +488,35 @@ Go言語では、nil と空のスライスは区別されます。nil スライ #### 容量漏れ -大きなスライスまたは配列をスライスすると、メモリ消費が高くなる可能性があることに注意してください。残りのスペースは GC によって再利用されず、少数の要素しか使用しないにもかかわらず、大きなバッキング配列が保持されます。スライスコピーを使用することで、このような事態を防ぐことができます。 +大きなスライスまたは配列をスライスすると、メモリ消費が高くなる可能性があることに注意してください。残りのスペースは GC によって再利用されず、少数の要素しか使用しないにもかかわらず、大きなバッキング配列が保持されます。スライスのコピーをすることで、このような事態を防ぐことができます。 [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/03-data-types/26-slice-memory-leak/capacity-leak) -#### スライスとポインター +#### スライスとポインタ ポインタまたはポインタフィールドを含む構造体を使用してスライス操作をする場合、GC がこれらの要素を再利用しないことを知っておく必要があります。その場合の選択肢は、コピーを実行するか、残りの要素またはそのフィールドを明示的に `nil` とすることです。 [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/03-data-types/26-slice-memory-leak/slice-pointers) -### 非効率な連想配列の初期化 (#27) +### 非効率なマップの初期化 (#27) ???+ info "要約" - 連想配列を作成するとき、その長さがすでにわかっている場合は、指定された長さで初期化します。これにより、割り当ての数が減り、パフォーマンスが向上します。 + マップを作成するとき、その長さがすでにわかっている場合は、指定された長さで初期化します。これにより、割り当ての数が減り、パフォーマンスが向上します。 -連想配列は、キー・値ペアの順序なしコレクションを提供します。なお、それぞれのペアは固有のキーを持ちます。Go言語では、連想配列はハッシュテーブルデータ構造に基づいています。内部的には、ハッシュテーブルはバケットの配列であり、各バケットはキー・値ペアの配列へのポインタです。 +マップは、キー・値ペアの順序なしコレクションを提供します。なお、それぞれのペアは固有のキーを持ちます。Go言語では、マップはハッシュテーブルデータ構造に基づいています。内部的には、ハッシュテーブルはバケットの配列であり、各バケットはキー・値ペアの配列へのポインタです。 -連想配列に含まれる要素の数が事前にわかっている場合は、その初期サイズを指定して作成する必要があります。連想配列の増大は、十分なスペースを再割り当てし、すべての要素のバランスを再調整する必要があるため、計算量が非常に多くなりますが、これによりそれを回避することができます。 +マップに含まれる要素の数が事前にわかっている場合は、その初期サイズを指定して作成する必要があります。マップの増大は、十分なスペースを再割り当てし、すべての要素のバランスを再調整する必要があるため、計算量が非常に多くなりますが、これによりそれを回避することができます。 [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/03-data-types/27-map-init/main_test.go) -### 連想配列とメモリリーク (#28) +### マップとメモリリーク (#28) ???+ info "要約" - 連想配列はメモリ内で常に増大する可能性がありますが、縮小することはありません。 したがって、メモリの問題が発生する場合は、連想配列を強制的に再作成したり、ポインタを使用したりするなど、さまざまな手段を試すことができます。 + マップはメモリ内で常に増大する可能性がありますが、縮小することはありません。したがって、メモリの問題が発生する場合は、マップを強制的に再生成したり、ポインタを使用したりするなど、さまざまな手段を試すことができます。 -セクション全文は[こちら](28-maps-memory-leaks.md). +セクション全文は[こちら](28-maps-memory-leaks.md)。 [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/03-data-types/28-map-memory-leak/main.go) @@ -522,7 +524,7 @@ Go言語では、nil と空のスライスは区別されます。nil スライ ???+ info "要約" - Go言語で型を比較す​​るには、2 つの型が比較可能ならば、== 演算子と != 演算子を使用できます。真理値、数値、文字列、ポインタ、チャネル、および構造体が完全に比較可能な型で構成されています。それ以外は、 `reflect.DeepEqual` を使用してリフレクションの代償を支払うか、独自の実装とライブラリを使用することができます。 + Go言語で型を比較す​​るには、2 つの型が比較可能ならば、== 演算子と != 演算子を使用できます。真偽値、数値、文字列、ポインタ、チャネル、および構造体が完全に比較可能な型で構成されています。それ以外は、 `reflect.DeepEqual` を使用してリフレクションの代償を支払うか、独自の実装とライブラリを使用することができます。 効果的に比較するには、 `==` と `!=` の使用方法を理解することが不可欠です。これらの演算子は、比較可能な被演算子で使用できます。 @@ -536,11 +538,12 @@ Go言語では、nil と空のスライスは区別されます。nil スライ ???+ note "補足" - `?` 、 `>=` 、 `<` 、および `>` 演算子を数値型で使用して値を比較したり、文字列で字句順序を比較したりすることもできます。 + `?` 、 `>=` 、 `<` 、および `>` 演算子を数値型で使用して値を比較したり、文字列で字句順序を比較したりすることもできます。 + +被演算子が比較できない場合(スライスとマップなど)、リフレクションなどの他の方法を利用する必要があります。リフレクションはメタプログラミングの一種であり、アプリケーションがその構造と動作を内省して変更する機能を指します。たとえば、Go言語では `reflect.DeepEqual` を使用できます。この関数は、2つの値を再帰的に調べることによって、2つの要素が完全に等しいかどうかを報告します。受け入れられる要素は、基本型に加えて、配列、構造体、スライス、マップ、ポインタ、インタフェース、関数です。しかし、最大の落とし穴はパフォーマンス上のペナルティです。 -被演算子が比較できない場合(スライスと連想配列など)、リフレクションなどの他の方法を利用する必要があります。リフレクションはメタプログラミングの一種であり、アプリケーションがその構造と動作を内省して変更する機能を指します。たとえば、Go言語では `reflect.DeepEqual` を使用できます。この関数は、2つの値を再帰的に調べることによって、2つの要素が完全に等しいかどうかを報告します。受け入れられる要素は、基本的な型に加えて、配列、構造体、スライス、連想配列、ポインタ、インタフェース、関数です。しかし、最大の落とし穴はパフォーマンス上のペナルティです。 +実行時のパフォーマンスが重要な場合は、独自のメソッドを実装することが最善となる可能性があります。 -実行時のパフォーマンスが重要な場合は、独自のメソッドを実装することが最善の解決策となる可能性があります。 追記:標準ライブラリには既に比較メソッドがいくつかあることを覚えておく必要があります。たとえば、最適化された `bytes.Compare` 関数を使用して、2つのバイトスライスを比較できます。独自のメソッドを実装する前に、車輪の再発明をしないようにしましょう。 [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/03-data-types/29-comparing-values/main.go) @@ -559,7 +562,7 @@ range ループを使用すると、さまざまなデータ構造に反復処 * 配列 * 配列へのポインタ * スライス - * 連想配列 + * マップ * 受信チャネル 古典的な `for` ループと比較すると、`range` ループはその簡潔な構文のおかげで、これらのデータ構造のすべての要素に反復処理をするのに便利です。 @@ -601,15 +604,15 @@ for i, v := range a { [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/04-control-structures/32-range-loop-pointers/) -### 連想配列の反復処理中に誤った仮定をする(反復処理中の順序付けと連想配列の挿入) (#33) +### マップの反復処理中に誤った仮定をする(反復処理中の順序付けとマップの挿入) (#33) ???+ info "要約" - 連想配列を使用するときに予測可能な出力を保証するには、連想配列のデータ構造が次のとおりであることに注意してください。 + マップを使用するときに予測可能な出力を保証するには、マップのデータ構造が次のとおりであることに注意してください。 * データをキーで並べ替えません * 挿入順序は保持されません - * 決定的な反復処理順序はありません + * 反復処理順序は決まっていません * ある反復処理中に追加された要素がその処理中に生成されることを保証しません @@ -622,7 +625,7 @@ for i, v := range a { ラベルと `break` または `continue` の併用は、特定の命令文を強制的に中断します。これは、ループ内の `switch` または `select` 文で役立ちます。 - 通常、break 文はループの実行を終了するために使用されます。ループが switch または select と組み合わせて使用​​される場合、目的の命令文ではないのに中断させてしまう、というミスをすることが開発者にはよくあります。たとえば +通常、break 文はループの実行を終了するために使用されます。ループが switch または select と組み合わせて使用​​される場合、目的の命令文ではないのに中断させてしまう、というミスをすることが開発者にはよくあります。たとえば ```go for i := 0; i < 5; i++ { @@ -663,7 +666,7 @@ loop: ???+ info "要約" - 関数内のループロジックを抽出すると、各反復の最後に `defer` 文が実行されます。 + 関数内のループロジックの抽出は、各反復の最後での `defer` 文の実行につながります。 `defer` 文は、上位ブロックの関数が戻るまで呼び出しの実行を遅らせます。これは主に定型コードを削減するために使用されます。たとえば、リソースを最終的に閉じる必要がある場合は、`defer` を使用して、`return` を実行する前にクロージャ呼び出しを繰り返すことを避けることができます。 @@ -679,7 +682,7 @@ func readFiles(ch <-chan string) error { defer file.Close() - // ファイルに何らかの処理をする + // ファイルの処理をする } return nil } @@ -707,7 +710,7 @@ func readFile(path string) error { defer file.Close() - // ファイルに何らかの処理をする + // ファイルの処理をする return nil } ``` @@ -728,20 +731,20 @@ Go言語ではルーンがあらゆる場所に使用されるため、次の点 * 文字セットは文字の集合ですが、エンコーディングは文字セットをバイナリに変換する方法を記述します。 * Go言語では、文字列は任意のバイトの不変スライスを参照します。 -※Go言語のソースコードは UTF-8 でエンコードされています。したがって、すべの文字列リテラルは UTF-8 文字列です。ただし、文字列には任意のバイトが含まれる可能性があるため、文字列が(ソースコードではない)他の場所から取得された場合、その文字列が UTF-8 エンコーディングに基づいている保証はありません。 +* Go言語のソースコードは UTF-8 でエンコードされています。したがって、すべの文字列リテラルは UTF-8 文字列です。ただし、文字列には任意のバイトが含まれる可能性があるため、文字列が(ソースコードではない)他の場所から取得された場合、その文字列が UTF-8 エンコーディングに基づいている保証はありません。 * `rune` は Unicode コードポイントの概念に対応し、単一の値で表されるアイテムを意味します。 * UTF-8 を使用すると、Unicode コードポイントを 1 ~ 4 バイトにエンコードできます。 * Go言語で文字列に対して `len()` を使用すると、ルーン数ではなくバイト数が返されます。 [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/05-strings/36-rune/main.go) -### 文字列に対する不正確な反復処理 (#37) +### 文字列に対する不正な反復処理 (#37) ???+ info "要約" `range` 演算子を使用して文字列を反復処理すると、ルーンのバイトシーケンスの開始インデックスに対応するインデックスを使用してルーンが反復処理されます。特定のルーンインデックス( 3 番目のルーンなど)にアクセスするには、文字列を `[]rune` に変換します。 -文字列の反復処理は、開発者にとって一般的な操作です。おそらく、文字列内の各ルーンに対して操作を実行するか、特定の部分文字列を検索する独自の関数を実装する必要があるでしょう。どちらの場合も、文字列の異なるルーンを反復処理する必要があります。しかし、反復がどのように機能するかについては困惑しやすいです。 +文字列の反復処理は、開発者にとって一般的な操作です。おそらく、文字列内の各ルーンに対して操作を実行するか、特定の部分文字列を検索する独自の関数を実装する必要があるでしょう。どちらの場合も、文字列の異なるルーンを反復処理する必要があります。しかし、反復処理がどのように機能するかについては困惑しやすいです。 次の例を考えてみましょう。 @@ -762,13 +765,13 @@ position 5: o len=6 ``` -混乱を招く可能性のある3つの点を取り上げましょう。 +混乱を招く可能性のある 3 点を取り上げましょう。 * 2 番目のルーンは、出力では ê ではなく Ã になります。 -* 位置 1 から位置 3 にジャンプしました。位置 2 には何があるでしょうか。 +* position 1 から position 3 にジャンプしました。 position 2 には何があるのでしょうか。 * len は 6 を返しますが、s には 5 つのルーンしか含まれていません。 -最後の結果から見てきましょう。len はルーンの数ではなく、文字列内のバイト数を返すことはすでに述べました。文字列リテラルを `s` に割り当てているため、`s` は UTF-8 文字列です。一方、特殊文字「ê」は 1 バイトでエンコードされません。 2 バイト必要です。したがって、`len(s)` を呼び出すと 6 が返されます。 +結果の最後から見ていきましょう。len はルーン数ではなく、文字列内のバイト数を返すことはすでに述べました。文字列リテラルを `s` に割り当てているため、`s` は UTF-8 文字列です。一方、特殊文字「ê」は 1 バイトでエンコードされません。 2 バイト必要です。したがって、`len(s)` を呼び出すと 6 が返されます。 前の例では、各ルーンを反復処理していないことを理解する必要があります。代わりに、ルーンの各開始インデックスを反復処理します。 @@ -807,7 +810,7 @@ fmt.Printf("%c\n", r) // o [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/05-strings/37-string-iteration/main.go) -### trim関数の誤用 (#38) +### trim 関数の誤用 (#38) ???+ info "要約" @@ -867,7 +870,7 @@ func concat(values []string) string { `WriteString` は 2 番目の出力としてエラーを返しますが、意図的に無視しましょう。実際、このメソッドは nil エラー以外を返すことはありません。では、このメソッドがシグネチャの一部としてエラーを返す目的は何でしょうか。`strings.Builder` は `io.StringWriter` インタフェースを実装しており、これには `WriteString(s string) (n int, err error)` という1つのメソッドが含まれています。したがって、このインタフェースに準拠するには、`WriteString` はエラーを返さなければならないのです。 -内部的には、`strings.Builder` はバイトスライスを保持します。 `WriteString` を呼び出すたびに、このスライスに追加する呼び出しが行われます。これには2つの影響があります。まず、 `append` の呼び出しが衝突状態を引き起こす可能性があるため、この構造体は同時に使用されるべきではありません。2番目の影響は、 [非効率なスライスの初期化 (#21)](#非効率なスライスの初期化-21) で見たものです。スライスの将来の長さがすでにわかっている場合は、それを事前に割り当てる必要があります。そのために、`strings.Builder` は別の `n` バイトのためのスペースを保証するメソッド `Grow(n int)` を持っています。 +内部的には、`strings.Builder` はバイトスライスを保持します。 `WriteString` を呼び出すたびに、このスライスに追加する呼び出しが行われます。これには2つの影響があります。まず、 `append` の呼び出しが衝突状態を引き起こす可能性があるため、この構造体は同時に使用されるべきではありません。2番目の影響は、 [非効率なスライスの初期化 (#21)](#21) で見たものです。スライスの将来の長さがすでにわかっている場合は、それを事前に割り当てる必要があります。そのために、`strings.Builder` は別の `n` バイトのためのスペースを保証するメソッド `Grow(n int)` を持っています。 ```go func concat(values []string) string { @@ -885,7 +888,7 @@ func concat(values []string) string { } ``` -ベンチマークを実行して3つのバージョン(`+=`を使用した V1 、事前割り当てなしで `strings.Builder{}` を使用した V2 、事前割り当てありの `strings.Builder{}` を使用した V3 )を比較してみましょう。入力スライスには 1,000 個の文字列が含まれており、各文字列には 1,000 バイトが含まれています。 +ベンチマークを実行して 3 つのバージョン( `+=` を使用した V1 、事前割り当てなしで `strings.Builder{}` を使用した V2 、事前割り当てありの `strings.Builder{}` を使用した V3 )を比較してみましょう。入力スライスには 1,000 個の文字列が含まれており、各文字列には 1,000 バイトが含まれています。 ``` BenchmarkConcatV1-4 16 72291485 ns/op @@ -907,7 +910,7 @@ BenchmarkConcatV3-4 5922 190340 ns/op 文字列または `[]byte` を扱うことを選択する場合、ほとんどのプログラマーは利便性のために文字列を好む傾向があります。しかし、ほとんどの I/O は実際には `[]byte` で行われます。たとえば、`io.Reader`、`io.Writer`、および `io.ReadAll` は文字列ではなく `[]byte` を処理します。 -文字列と `[]byte` のどちらを扱うべきか迷ったとき、`[]byte` を扱う方が必ずしも面倒だというわけではないことを思い出してください。strings パッケージからエクスポートされたすべての関数には、`bytes` パッケージに代替機能があります。 `Split`、`Count`、`Contains`、`Index` などです。したがって、I/O を実行しているかどうかに関係なく、文字列の代わりにバイトを使用してワークフロー全体を実装でき、追加の変換のコストを回避できるかどうかを最初に確認する必要があります。 +文字列と `[]byte` のどちらを扱うべきか迷ったとき、`[]byte` を扱う方が必ずしも面倒だというわけではないことを思い出してください。strings パッケージからエクスポートされたすべての関数には、`bytes` パッケージに代替機能があります。 `Split`、`Count`、`Contains`、`Index` などです。したがって、I/O を実行しているかどうかに関係なく、文字列の代わりにバイトを使用してワークフロー全体を実装でき、追加の変換コストを回避できるかどうかを最初に確認しましょう。 [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/05-strings/40-string-conversion/main.go) @@ -917,15 +920,15 @@ BenchmarkConcatV3-4 5922 190340 ns/op 部分文字列の代わりにコピーを使用すると、部分文字列操作によって返される文字列が同じバイト配列によってサポートされるため、メモリリークを防ぐことができます。 -[スライスとメモリリーク (#26)](#スライスとメモリリーク-26) では、スライスまたは配列のスライスがメモリリークの状況を引き起こす可能性があることを確認しました。この原則は、文字列および部分文字列の操作にも当てはまります。 +[スライスとメモリリーク (#26)](#26) では、スライスまたは配列のスライスがメモリリークの状況を引き起こす可能性があることを確認しました。この原則は、文字列および部分文字列の操作にも当てはまります。 -Go言語で部分文字列操作を使用するときは、2 つのことに留意する必要があります。まず、提供される間隔はルーンの数ではなく、バイト数に基づいています。次に、結果の部分文字列が最初の文字列と同じバッキング配列を共有するため、部分文字列操作によりメモリリークが発生する可能性があります。これを防ぐ方法は、文字列のコピーを手動で実行するか、Go 1.18 から実装されている `strings.Clone` を使用することです。 +Go言語で部分文字列操作を使用するときは、2 つのことに留意する必要があります。まず、提供される間隔はルーン数ではなく、バイト数に基づいています。次に、結果の部分文字列が最初の文字列と同じバッキング配列を共有するため、部分文字列操作によりメモリリークが発生する可能性があります。これを防ぐ方法は、文字列のコピーを手動で実行するか、Go 1.18 から実装されている `strings.Clone` を使用することです。 [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/05-strings/41-substring-memory-leak/main.go) ## 関数とメソッド -### どの型のレシーバーを使用すればよいかわからない (#42) +### どの型のレシーバーを使用すればよいかわかっていない (#42) ???+ info "要約" @@ -945,7 +948,7 @@ _ポインタレシーバーでなければならない_ とき } ``` -* メソッドレシーバーにコピーできないフィールドが含まれている場合。sync パッケージの型部分はその一例になります( [sync 型のコピー (#74)](#sync-型のコピー-74) を参照)。 +* メソッドレシーバーにコピーできないフィールドが含まれている場合。sync パッケージの型部分はその一例になります( [sync 型のコピー (#74)](#sync-74) を参照)。 _ポインタレシーバーであるべき_ とき @@ -954,13 +957,13 @@ _ポインタレシーバーであるべき_ とき _値レシーバーでなければならない_ とき * レシーバーの不変性を強制する必要がある場合。 -* レシーバーが連想配列、関数、チャネルの場合。それ以外の場合はコンパイルエラーが発生します。 +* レシーバーがマップ、関数、チャネルの場合。それ以外の場合はコンパイルエラーが発生します。 _値レシーバーであるべき_ とき * レシーバーが変化させる必要のないスライスの場合。 * レシーバーが、`time.Time` などの小さな配列または構造体で、可変フィールドを持たない値型である場合。 -* レシーバーが `int`、`float64`、または `string` などの基本的な型の場合。 +* レシーバーが `int`、`float64`、または `string` などの基本型の場合。 もちろん、特殊なケースは常に存在するため、すべてを網羅することは不可能ですが、このセクションの目標は、ほとんどのケースをカバーするためのガイダンスを提供することです。通常は、そうしない正当な理由がない限り、値レシーバーを使用して間違いありません。分からない場合は、ポインタレシーバを使用する必要があります。 @@ -989,11 +992,11 @@ func f(a int) (b int) { [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/06-functions-methods/43-named-result-parameters/main.go) -### 名前付き結果パラメータによる予期せぬ副作用 (#44) +### 名前付き結果パラメータによる予想外の副作用 (#44) ???+ info "要約" - [#43](#名前付き結果パラメータをまったく使用していない-43) を参照してください。 + [#43](#43) を参照してください。 名前付き結果パラメータが状況によっては役立つ理由について説明しました。 ただし、これらはゼロ値に初期化されるため、十分に注意しないと、軽微なバグが発生する可能性があります。たとえば、このコードはどこが間違っているでしょうか。 @@ -1035,7 +1038,7 @@ func (l loc) getCoordinates(ctx context.Context, address string) ( ファイル名の代わりに `io.Reader` 型を受け取るように関数を設計すると、関数の再利用性が向上し、テストが容易になります。 -ファイル名をファイルから読み取るための関数入力として受け入れることは、ほとんどの場合、「コードの臭い」とみなされべきです( `os.Open` などの特定の関数を除く)。実際、複数のファイルを作成する必要があるかもしれないので、単体テストがより複雑になります。また、関数の再利用性も低下します (ただし、すべての関数が再利用されるわけではありません)。 `io.Reader` インタフェースを使用すると、データソースが抽象化されます。入力がファイル、文字列、HTTP リクエスト、gRPC リクエストのいずれであるかに関係なく、実装は再利用でき、簡単にテストできます。 +ファイル名をファイルから読み取るための関数入力として受け入れることは、ほとんどの場合、「コードの臭い」とみなされるべきです( `os.Open` などの特定の関数を除く)。複数のファイルを作成することにになるかもしれず、単体テストがより複雑になる可能性があるからです。また、関数の再利用性も低下します (ただし、すべての関数が再利用されるわけではありません)。 `io.Reader` インタフェースを使用すると、データソースが抽象化されます。入力がファイル、文字列、HTTP リクエスト、gRPC リクエストのいずれであるかに関係なく、実装は再利用でき、簡単にテストできます。 [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/06-functions-methods/46-function-input/) @@ -1141,7 +1144,7 @@ main.main() main.go:7 +0xb3 ``` -panic の使用は慎重にすべきです。代表的なケースが 2 つあり、1 つはヒューマンエラーを通知する場合(例: [`sql.Register`](https://cs.opensource.google/go/go/+/refs/tags/go1.20.7:src/database/sql/sql.go;l=44)ドライバーが `nil` または既に登録されている場合に panic を起こします)、もう 1 つはアプリケーションが必須の依存関係の作成に失敗した場合です。結果として、例外的にアプリケーションを停止します。それ以外のほとんどの場合においては、エラー処理は、最後の戻り引数として適切なエラー型を返す関数を通じて行うべきです。 +panic の使用は慎重にすべきです。代表的なケースが 2 つあり、1 つはヒューマンエラーを通知する場合(例: [`sql.Register`](https://cs.opensource.google/go/go/+/refs/tags/go1.20.7:src/database/sql/sql.go;l=44)ドライバーが `nil` または既に登録されている場合に panic を起こします)、もう 1 つはアプリケーションが必須の依存関係の生成に失敗した場合です。結果として、例外的にアプリケーションを停止します。それ以外のほとんどの場合においては、エラー処理は、最後の戻り引数として適切なエラー型を返す関数を通じて行うべきです。 [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/07-error-management/48-panic/main.go) @@ -1160,7 +1163,7 @@ Go 1.13 以降、%w ディレクティブを使用すれば簡単にエラーを [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/07-error-management/49-error-wrapping/main.go) -### エラー型の不正確な比較 (#50) +### エラー型の不正な比較 (#50) ???+ info "要約" @@ -1170,7 +1173,7 @@ Go 1.13 以降、%w ディレクティブを使用すれば簡単にエラーを [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/07-error-management/50-compare-error-type/main.go) -### エラー値の不正確な比較 (#51) +### エラー値の不正な比較 (#51) ???+ info "要約" @@ -1243,7 +1246,7 @@ _ = notify ```go // 最大でも 1 回の伝達 -// それゆえ、エラーが発生した場合にそれらの一部が失われることは許容されます。 +// それゆえ、エラーが発生した場合にそれらの一部が失われることは許容されます _ = notify() ``` @@ -1262,7 +1265,7 @@ _ = notify() * 並行処理は構造に関するものです。別々の並行ゴルーチンが取り組むことができるさまざまな段階を導入することで、逐次処理を並行処理に変更できます。 * 並列処理は実行に関するものです。並列ゴルーチンをさらに追加することで、段階レベルで並列処理を使用できます。 -まとめると、並行処理は、並列化できる部分をもつ問題を解決するための構造を提供します。すなわち、_並行処理により並列処理が可能になります_ 。 +まとめると、並行処理は、並列化できる部分をもつ問題を解決するための構造を提供します。すなわち、_並行処理により並列処理が可能_ になります 。 @@ -1272,7 +1275,7 @@ _ = notify() 熟練した開発者になるには、並行処理が必ずしも高速であるとは限らないことを認識する必要があります。最小限のワークロードの並列処理を伴う解決策は、必ずしも逐次処理より高速であるとは限りません。逐次処理と並行処理のベンチマークは、仮定を検証する方法であるべきです。 -セクション全文は[こちら](56-concurrency-faster.md). +セクション全文は[こちら](56-concurrency-faster.md)。 [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/08-concurrency-foundations/56-faster/) @@ -1286,7 +1289,7 @@ _ = notify() チャネルまたはミューテックスはどのような場合に使用する必要があるのでしょうか。次の図の例をバックボーンとして使用します。この例には、特定の関係を持つ 3 つの異なるゴルーチンがあります。 -* G1 と G2 は並列ゴルーチンです。チャネルからメッセージを受信し続ける同じ関数を実行する 2 つのゴルーチン、あるいは同じ HTTP ハンドラーを同時に実行する 2 つのゴルーチンかもしれません。 +* G1 と G2 は並列ゴルーチンです。チャネルからメッセージを受信し続ける同じ関数を実行する 2 つのゴルーチン、あるいは同じ HTTP ハンドラを同時に実行する 2 つのゴルーチンかもしれません。 * G1 と G3 は並行ゴルーチンであり、G2 と G3 も同様です。すべてのゴルーチンは全体の並行構造の一部ですが、G1 と G2 が最初のステップを実行し、G3 が次のステップを実行します。 @@ -1297,7 +1300,7 @@ _ = notify() 並行ゴルーチンに関しては、リソースの所有権をあるステップ(G1 および G2)から別のステップ(G3)に移管したい場合もあります。たとえば、G1 と G2 によって共有リソースが豊かになっている場合、ある時点でこのジョブは完了したと見なされます。ここでは、チャネルを使用して、特定のリソースの準備ができていることを通知し、所有権の移転を処理する必要があります。 -ミューテックスとチャネルには異なるセマンティクスがあります。状態を共有したいとき、または共有リソースにアクセスしたいときは、ミューテックスによってこのリソースへの排他的アクセスが保証されます。反対に、チャネルはデータの有無(`chan struct{}` の有無)に関係なくシグナリングを行う仕組みです。調整や所有権の移転はチャネルを通じて行う必要があります。ゴルーチンが並列か並行かを知ることが重要です。一般に、並列ゴルーチンにはミューテックスが必要で、並行ゴルーチンにはチャネルが必要です。 +ミューテックスとチャネルには異なるセマンティクスがあります。ステートを共有したいとき、または共有リソースにアクセスしたいときは、ミューテックスによってこのリソースへの排他的アクセスが保証されます。反対に、チャネルはデータの有無(`chan struct{}` の有無)に関係なくシグナルを行う仕組みです。調整や所有権の移転はチャネルを通じて行う必要があります。ゴルーチンが並列か並行かを知ることが重要です。一般に、並列ゴルーチンにはミューテックスが必要で、並行ゴルーチンにはチャネルが必要です。 ### 競合問題を理解していない(データ競合と競合状態、そしてGo言語のメモリモデル) (#58) @@ -1323,7 +1326,7 @@ _ = notify() 競合状態は、動作が制御できないイベントのシーケンスまたはタイミングに依存する場合に発生します。ここでは、イベントのタイミングがゴルーチンの実行順序です。 -まとめると、並行処理のアプリケーションで作業する場合、データ競合は競合状態とは異なることを理解することが不可欠です。データ競合は、複数のゴルーチンが同じメモリ位置に同時にアクセスし、そのうちの少なくとも 1 つが書き込みを行っている場合に発生します。データ競合とは、予期しない動作を意味します。ただし、データ競合のないアプリケーションが必ずしも決定的な結果を意味するわけではありません。データ競合がなくても、アプリケーションは制御されていないイベント(ゴルーチンの実行、チャネルへのメッセージの発信速度、データベースへの呼び出しの継続時間など)に依存する挙動を持つことがあります。その場合は競合状態です。並行処理のアプリケーションの設計に熟練するには、両方の概念を理解することが肝要です。 +まとめると、並行処理のアプリケーションで作業する場合、データ競合は競合状態とは異なることを理解することが不可欠です。データ競合は、複数のゴルーチンが同じメモリ位置に同時にアクセスし、そのうちの少なくとも 1 つが書き込みを行っている場合に発生します。データ競合とは、予想外の動作を意味します。ただし、データ競合のないアプリケーションが必ずしも決定的な結果を意味するわけではありません。データ競合がなくても、アプリケーションは制御されていないイベント(ゴルーチンの実行、チャネルへのメッセージの発信速度、データベースへの呼び出しの継続時間など)に依存する挙動を持つことがあります。その場合は競合状態です。並行処理のアプリケーションの設計に熟練するには、両方の概念を理解することが肝要です。 [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/08-concurrency-foundations/58-races/) @@ -1331,7 +1334,7 @@ _ = notify() ???+ info "要約" - 一定数のゴルーチンを作成するときは、ワークロードのタイプを考慮してください。CPU バウンドのゴルーチンを作成するということは、この数を `GOMAXPROCS` 変数(デフォルトではホスト上の CPU コアの数に基づく)に近づけることを意味します。 I/O バウンドのゴルーチンの作成は、外部システムなどの他の要因に依存します。 + 一定数のゴルーチンを作成するときは、ワークロードのタイプを考慮してください。CPU バウンドのゴルーチンを作成するということは、この数を `GOMAXPROCS` 変数(デフォルトではホスト上の CPU コアの数に基づく)に近づけることを意味します。I/O バウンドのゴルーチンの作成は、外部システムなどの他の要因に依存します。 プログラミングでは、ワークロードの実行時間は次のいずれかによって制限されます。 @@ -1341,7 +1344,7 @@ _ = notify() ???+ note "補足" - ここ数十年でメモリが非常に安価になったことを考慮すると、後者は現在では最もまれです。したがって、このセクションでは、最初の 2 つのワークロードタイプ、CPU バウンドと I/O バウンドに焦点を当てます。 + ここ数十年でメモリが非常に安価になったことを考慮すると、 3 つ目は現在では最もまれです。したがって、このセクションでは、最初の 2 つのワークロードタイプ、CPU バウンドと I/O バウンドに焦点を当てます。 ワーカーによって実行されるワークロードが I/O バウンドである場合、値は主に外部システムに依存します。逆に、ワークロードが CPU に依存している場合、ゴルーチンの最適な数は利用可能な CPU コアの数に近くなります(ベストプラクティスは `runtime.GOMAXPROCS` を使用することです)。並行処理のアプリケーションを設計する場合、ワークロードのタイプ( I/O あるいは CPU )を知ることが重要です。 @@ -1370,18 +1373,18 @@ _ = notify() Go Context のもう 1 つの使用例は、キャンセルシグナルを伝送することです。別のゴルーチン内で `CreateFileWatcher(ctx context.Context, filename string)` を呼び出すアプリケーションを作成することを想像してみましょう。この関数は、ファイルから読み取りを続けて更新をキャッチする特定のファイルウォッチャーを作成します。提供された Context が期限切れになるかキャンセルされると、この関数はそれを処理してファイル記述子を閉じます。 -#### コンテキスト値 +#### Context Value Go Context の最後の使用例は、キーと値のリストを運ぶことです。 Context にキーと値のリストを含める意味は何でしょうか。Go Context は汎用的であるため、使用例は無限にあります。 -たとえば、トレースを使用する場合、異なる副次機能間で同じ相関 ID を共有したいことがあるかもしれません。一部の開発者は、この ID を関数シグネチャの一部にするにはあまりに侵略的だと考えるかもしれません。この点に関して、与えられた Context の一部としてそれを含めることを決定することもできます。 +たとえば、トレースを使用する場合、異なるサブ関数の間で同じ相関 ID を共有したいことがあるかもしれません。一部の開発者は、この ID を関数シグネチャの一部にするにはあまりに侵略的だと考えるかもしれません。この点に関して、与えられた Context の一部としてそれを含めることを決定することもできます。 #### Context のキャンセルをキャッチする -`context.Context` タイプは、受信専用の通知チャネル `<-chan struct{}` を返す `Done` メソッドをエクスポートします。このチャネルは、 Context に関連付けられた作業をキャンセルする必要がある場合に閉じられます。たとえば +`context.Context` タイプは、受信専用の通知チャネル `<-chan struct{}` を返す `Done` メソッドをエクスポートします。このチャネルは、 Context に関連付けられた作業をキャンセルする必要がある場合に閉じられます。たとえば -* `context.WithCancel`で作成された Context に関連するDoneチャネルは、cancel関数が呼び出されると閉じられます。 -* `context.WithDeadline`で作成した Context に関連するDoneチャネルは、deadline を過ぎると閉じられます。 +* `context.WithCancel`で作成された Context に関連する Done チャネルは、cancel関数が呼び出されると閉じられます。 +* `context.WithDeadline`で作成した Context に関連する Done チャネルは、デッドラインを過ぎると閉じられます。 注意すべき点の 1 つは、内部チャネルは、特定の値を受け取ったときではなく、 Context がキャンセルされたとき、またはデッドラインに達したときに閉じる必要があるということです。チャネルのクローズは、すべての消費者ゴルーチンが受け取る唯一のチャネルアクションであるためです。このようにして、 Context がキャンセルされるか、デッドラインに達すると、すべての消費者に通知が届きます。 @@ -1389,6 +1392,319 @@ Go Context の最後の使用例は、キーと値のリストを運ぶことで [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/08-concurrency-foundations/60-contexts/main.go) +## 並行処理:実践 + +### 不適切な Context を広めてしまう (#61) + +???+ info "要約" + + Context を伝播する際には、Context をキャンセルできる条件を理解することが重要です。たとえば、レスポンスが送信された際に HTTP ハンドラが Context をキャンセルするときなどです。 + +多くの状況では、Go Context を伝播することが推奨されます。ただし、Context の伝播によって軽微なバグが発生し、サブ関数が正しく実行されなくなる場合があります。 + +次の例を考えてみましょう。いくつかのタスクを実行してレスポンスを返す HTTP ハンドラを公開します。ただし、レスポンスを返す直前に、それを Kafka トピックに送信したいと思っています。HTTP コンシューマにレイテンシの点でペナルティを課したくないので、publish アクションを新しいゴルーチン内で非同期に処理したいと考えています。たとえば、Context がキャンセルされた場合にメッセージの publish アクションを中断できるように、Context を受け入れる `publish` 関数を自由に使えるとします。可能な実装は次のとおりです。 + +```go +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 を呼び出すとエラーが返されます。 + +???+ note "補足" + + Go 1.21 からは、キャンセルせずに新しい Context を作成する方法が追加されました。 [`context.WithoutCancel`](https://pkg.go.dev/context#WithoutCancel) は、親がキャンセルされたときにキャンセルされていない親のコピーを返します。 + +まとめると、Context の伝播は慎重に行う必要があります。 + + [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/09-concurrency-practice/61-inappropriate-context/main.go) + +### 停止すべきときを知らずにゴルーチンを開始してしまう (#62) + +???+ info "要約" + + リークを避けることは、ゴルーチンが開始されるたびに、最終的に停止する必要があることを意味します。 + +ゴルーチンは簡単に行うことができます。非常に簡単であるため、新しいゴルーチンをいつ停止するかについての計画を必ずしも立てていない可能性があり、リークにつながることがあります。ゴルーチンをいつ停止すればよいかわからないのは、Go言語でよくある設計上の問題であり、並行処理におけるミスです。 + +具体的な例について説明しましょう。外部設定(データベース接続などを使用したものなど)を監視する必要があるアプリケーションを設計します。まず、次のような実装をしてみます。 + +```go +func main() { + newWatcher() + // アプリケーションを実行する +} + +type watcher struct { /* いくつかのリソース */ } + +func newWatcher() { + w := watcher{} + go w.watch() // 外部設定を監視するゴルーチンを作成する +} +``` + +このコードの問題は、メインのゴルーチンが終了すると(おそらく OS シグナルまたは有限のワークロードのため)、アプリケーションが停止してしまうことです。したがって、ウォッチャーによって作成されたリソースは正常に閉じられません。これを防ぐにはどうすればよいでしょうか。 + +1 つの方法としては、main が戻ったときにキャンセルされる Context を newWatcher に渡すことが挙げられます。 + +```go +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 がそれを行う時間が確実にあるとはいえません。これは設計上の欠陥です。 + +問題は、ゴルーチンを停止する必要があることを伝えるためにシグナルを使用したことです。リソースが閉じられるまで、親のゴルーチンをブロックしませんでした。そうならないようにしましょう。 + +```go +func main() { + w := newWatcher() + defer w.close() + // アプリケーションを実行する +} + +func newWatcher() watcher { + w := watcher{} + go w.watch() + return w +} + +func (w watcher) close() { + // リソースを閉じる +} +``` + +リソースを閉じる時間になったことを `watcher` に通知する代わりに、 `defer` を使用してこの `close` メソッドを呼び出し、アプリケーションが終了する前にリソースが確実に閉じられるようにします。 + +まとめると、ゴルーチンは他のリソースと同様、メモリや他のリソースを解放するために最終的に閉じる必要があることに注意してください。ゴルーチンをいつ停止するかを知らずに開始するのは設計上の問題です。ゴルーチンが開始されるときは常に、いつ停止するかについて明確な計画を立てる必要があります。最後になりましたが、ゴルーチンがリソースを作成し、その有効期間がアプリケーションの存続期間にバインドされている場合は、アプリケーションを終了する前にそのゴルーチンが完了するのを待った方がおそらく確実です。そうすることで、リソースを間違いなく解放できます。 + + [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/09-concurrency-practice/62-starting-goroutine/) + +### ゴルーチンとループ変数に注意しない (#63) + +???+ warning "注意" + + このミスは Go 1.22 からは気にする必要がありません([詳細](https://go.dev/blog/loopvar-preview))。 + +### `select` とチャネルを使用して決定的動作を期待する (#64) + +???+ info "要約" + + 複数のオプションが可能な場合、複数のチャネルで `select` するとケースがランダムに選択されることを理解すると、並行処理における軽微なバグにつながる可能性のある誤った仮定を立てることがなくなります。 + +Go 開発者がチャネルを操作するときにありがちな間違いの 1 つは、select が複数のチャネルでどのように動作するかについて誤った理解をすることです。 + +たとえば、次の場合を考えてみましょう( `disconnectCh` はバッファなしチャネルです)。 + +```go +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言語はランダムに選択するため、どのオプションが選択されるかは保証されません。この動作を克服するには、単一の生産者ゴルーチンの場合、バッファなしのチャネルまたは単一のチャネルを使用することができます。 + + [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/09-concurrency-practice/64-select-behavior/main.go) + +### 通知チャネルを使用していない (#65) + +???+ info "要約" + + `chan struct{}` 型を使用して通知を送信しましょう。 + +チャネルは、シグナルを介してゴルーチン間で通信するためのメカニズムです。シグナルにはデータが含まれているかどうかは関係ありません。 + +具体的な例を見てみましょう。通信の切断が発生するたびにそれを通知するチャネルを作成します。 1 つの方法として、これを `chan bool` として扱うことが挙げられます。 + +```go +disconnectCh := make(chan bool) +``` + +ここで、そのようなチャネルを提供する API と対話するとします。これは真偽値のチャネルであるため、`true` または `false` のメッセージを受信できます。`true` が何を伝えているかはおそらく明らかでしょう。しかし、 `false` とは何を意味するのでしょうか。通信が切断されていないということでしょうか。その場合、どれくらいの頻度でそのようなシグナルを受信するのでしょうか。あるいは再接続したということでしょうか。そもそも `false` を受け取ることを期待すべきなのでしょうか。おそらく `true` メッセージを受け取ることだけを期待すべきでしょう。 + +その場合、情報を伝えるために特定の値は必要ないことを意味し、データの _ない_ チャネルが必要になります。これを処理する慣用的な方法は、空の構造体のチャネル―― `chan struct{} `――を使用することです。 + +### nil チャネルを使用していない (#66) + +???+ info "要約" + + nil チャネルを使用することによって、たとえば `select` 文からケースを _削除_ できるため、並行処理を行う際の道具の一つとして使えるようになるべきです。 + +次のコードによって何が行われるでしょうか。 + +```go +var ch chan int +<-ch +``` + +`ch` は `chan int` 型です。チャネルのゼロ値は nil であるので、 `ch` は `nil` です。ゴルーチンは panic を起こしません。ただし、永久にブロックします。 + +nil チャネルにメッセージを送信する場合も原理は同じです。以下のゴルーチンは永久にブロックします。 + +```go +var ch chan int +ch <- 0 +``` + +では、Go言語が nil チャネルとの間でメッセージの送受信を許可する目的は何でしょうか。たとえば、2 つのチャネルをマージする慣用的な方法を実装するのに、 nil チャネルを使用することができます。 + +```go hl_lines="5 9 15" +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 開発者は並行処理を扱う際に使いこなせるようになっておくべきです。 + + [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/09-concurrency-practice/66-nil-channels/main.go) + +### チャネルの容量について困惑している (#67) + +???+ info "要約" + + 問題が発生した場合は、使用するチャネルの型を慎重に決定してください。同期を強力に保証してくれるのはバッファなしチャネルのみです。 + +バッファありチャネル以外のチャネルの容量を指定するには正当な理由があるべきです。 + +### 文字列フォーマットで起こり得る副作用を忘れてしまう( etcd データ競合の例とデッドロック) (#68) + +???+ info "要約" + + 文字列の書式設定が既存の関数が呼び出す可能性があることを認識することは、デッドロックやその他のデータ競合の可能性に注意することを意味します。 + + [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/09-concurrency-practice/68-string-formatting/main.go) + +### `append` でデータ競合を起こしてしまう (#69) + +???+ info "要約" + + `append` の呼び出しは必ずしもデータ競合がないわけではありません。ゆえに、共有スライス上で同時に使用してはいけません。 + + [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/09-concurrency-practice/69-data-race-append/main.go) + +### スライスとマップでミューテックスを正しく使用していない (#70) + +???+ info "要約" + + スライスとマップはポインタであることを覚えておくと、典型的なデータ競合を防ぐことができます。 + + [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/09-concurrency-practice/70-mutex-slices-maps/main.go) + +### `sync.WaitGroup` を正しく使用していない (#71) + +???+ info "要約" + + `sync.WaitGroup` を正しく使用するには、ゴルーチンを起動する前に `Add` メソッドを呼び出しましょう。 + + [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/09-concurrency-practice/71-wait-group/main.go) + +### `sync.Cond` について忘れてしまう (#72) + +???+ info "要約" + + `sync.Cond` を使用すると、複数のゴルーチンに繰り返し通知を送信できます。 + + [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/09-concurrency-practice/72-cond/main.go) + +### `errgroup` を使用していない (#73) + +???+ info "要約" + + `errgroup` パッケージを使用して、ゴルーチンのグループを同期し、エラーと Context を処理できます。 + + [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/09-concurrency-practice/73-errgroup/main.go) + ### `sync` 型のコピー (#74) @@ -1398,12 +1714,301 @@ Go Context の最後の使用例は、キーと値のリストを運ぶことで [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/09-concurrency-practice/74-copying-sync/main.go) +## 標準ライブラリ + +### 誤った `time.Duration` を指定する (#75) + +???+ info "要約" + + `time.Duration` を受け入れる関数には注意を払ってください。整数を渡すことは許可されていますが、混乱を招かないように time API を使用するよう努めてください。 + +標準ライブラリの多くの関数は、`int64` 型のエイリアスである `time.Duration` を受け入れます。ただし、1 単位の `time.Duration` は、他のプログラミング言語で一般的に見られる 1 ミリ秒ではなく、1 ナノ秒を表します。その結果、`time.Duration` API を使用する代わりに数値型を渡すと、予想外の動作が発生する可能性があります。 + +他言語を使用したことのある開発者の方は、次のコードによって 1 秒周期の新しい `time.Ticker` が生成されると考えるかもしれません。 + +```go +ticker := time.NewTicker(1000) +for { + select { + case <-ticker.C: + // 処理をする + } +} +``` + +しかしながら、1,000 `time.Duration` = 1,000 ナノ秒であるため、想定されている 1秒 ではなく、1,000 ナノ秒 = 1 マイクロ秒の周期になります。 + +混乱や予想外の動作を招かないよう、いつも `time.Duration` API を使用するべきです。 + +```go +ticker = time.NewTicker(time.Microsecond) +// もしくは +ticker = time.NewTicker(1000 * time.Nanosecond) +``` + + [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/10-standard-lib/75-wrong-time-duration/main.go) + +### `time.After` とメモリリーク (#76) + +???+ info "要約" + + 繰り返される関数(ループや HTTP ハンドラなど)で `time.After` の呼び出しを回避すると、ピーク時のメモリ消費を回避できます。`time.After` によって生成されたリソースは、 timer が終了したときにのみ解放されます。 + + [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/10-standard-lib/76-time-after/main.go) + +### JSON 処理でありがちな間違い (#77) + +* 型の埋め込みによる予想外の動作 + + Go 構造体で埋め込みフィールドを使用する場合は注意してください。 なぜなら `json.Marshaler` インタフェースを実装する time.Time 埋め込みフィールドのようなやっかいなバグが発生して、デフォルトのマーシャリング動作がオーバーライドされる可能性があるからです。 + + [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/10-standard-lib/77-json-handling/type-embedding/main.go) + +* JSON と monotonic clock + + 2 つの `time.Time` 構造体を比較する場合、`time.Time` には wall clock と monotonic clock の両方が含まれており、== 演算子を使用した比較は両方の clock に対して行われることを思い出してください。 + + [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/10-standard-lib/77-json-handling/monotonic-clock/main.go) + +* `any` のマップ + + JSON データのアンマーシャリング中にマップを提供するときに間違いを避けるために、数値はデフォルトで `float64` に変換されることに注意してください。 + + [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/10-standard-lib/77-json-handling/map-any/main.go) + +### SQL でありがちな間違い (#78) + +* `sql.Open` が必ずしもデータベースへの接続を確立するわけではないことを忘れている + + 設定を試し、データベースにアクセスできることを確認する必要がある場合は、 `Ping` または `PingContext` メソッドを呼び出しましょう。 + + [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/10-standard-lib/78-sql/sql-open) + +* コネクションプーリングのことを忘れる + + 実運用水準のアプリケーションでは、データベース接続パラメータを設定しましょう。 + +* プリペアドステートメントを使用していない + + SQL のプリペアドステートメントを使用すると、クエリがより効率的かつ確実になります。 + + [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/10-standard-lib/78-sql/prepared-statements) + +* null 値を誤った方法で処理している + + テーブル内の null が許容されている列は、ポインタまたは `sql.NullXXX` 型を使用して処理しましょう。 + + [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/10-standard-lib/78-sql/null-values/main.go) + +* 行の反復処理によるエラーを処理しない + + 行の反復処理の後に `sql.Rows` の `Err` メソッドを呼び出して、次の行の準備中にエラーを見逃していないことを確認しましょう。 + + [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/10-standard-lib/78-sql/rows-iterations-errors) + +### 一時的なリソース( HTTP body、`sql.Rows`、および `os.File` )を閉じていない (#79) + +???+ info "要約" + + リークを避けるために、 `io.Closer` を実装しているすべての構造体を最後には閉じましょう。 + + [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/10-standard-lib/79-closing-resources/) + +### HTTP リクエストに応答した後の return 文を忘れてしまう (#80) + +???+ info "要約" + + HTTP ハンドラの実装での予想外の動作を避けるため、`http.Error` の後にハンドラを停止したい場合は、`return` 文を忘れないようにしてください。 + + [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/10-standard-lib/80-http-return/main.go) + +### 標準の HTTP クライアントとサーバーを使用している (#81) + +???+ info "要約" + + 実運用水準のアプリケーションを求めている場合は、標準の HTTP クライアントとサーバーの実装を使用しないでください。これらの実装には、タイムアウトや稼働環境で必須であるべき動作が欠落しています。 + + [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/10-standard-lib/81-default-http-client-server/) + +## テスト + +### テストを分類していない(ビルドタグ、環境変数、ショートモード) (#82) + +???+ info "要約" + + ビルドフラグ、環境変数、またはショートモードを使用してテストを分類すると、テストプロセスがより効率的になります。ビルドフラグまたは環境変数を使用してテストカテゴリ(たとえば、単体テストと統合テスト)を作成し、短期間のテストと長時間のテストを区別することで、実行するテストの種類を決定できます。 + + [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/11-testing/82-categorizing-tests/) + +### `-race` フラグを有効にしていない (#83) + +???+ info "要約" + + 並行アプリケーションを作成する場合は、 `-race` フラグを有効にすることを強くお勧めします。そうすることで、ソフトウェアのバグにつながる可能性のある潜在的なデータ競合を発見できるようになります。 + +### テスト実行モード( `-parallel` および `-shuffle` )を使用していない (#84) + +???+ info "要約" + + `-parallel` フラグを使用するのは、特に長時間実行されるテストを高速化するのに効果的です。 `-shuffle` フラグを使用すると、テストスイートがバグを隠す可能性のある間違った仮定に依存しないようにすることができます。 + +### テーブル駆動テストを使用しない (#85) + +???+ info "要約" + + テーブル駆動テストは、コードの重複を防ぎ、将来の更新の処理を容易にするために、一連の類似したテストをグループ化する効率的な方法です。 + + [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/11-testing/85-table-driven-tests/main_test.go) + +### 単体テストでのスリープ (#86) + +???+ info "要約" + + テストの不安定さをなくし、より堅牢にするために、同期を使用してスリープを回避しましょう。同期が不可能な場合は、リトライ手法を検討してください。 + + [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/11-testing/86-sleeping/main_test.go) + +### time API を効率的に処理できていない (#87) + +???+ info "要約" + + time API を使用して関数を処理する方法を理解することで、テストの不安定さを軽減することができます。隠れた依存関係の一部として time を処理したり、クライアントに time を提供するように要求したりするなど、標準的な手段を利用できます。 + + [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/11-testing/87-time-api/) + +### テストに関するユーティリティパッケージ( `httptest` および `iotest` )を使用していない (#88) + +* `httptest` パッケージは、HTTP アプリケーションを扱うのに役立ちます。クライアントとサーバーの両方をテストするための一連のユーティリティを提供します。 + + [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/11-testing/88-utility-package/httptest/main_test.go) + +* `iotest` パッケージは、io.Reader を作成し、アプリケーションのエラー耐性をテストするのに役立ちます。 + + [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/11-testing/88-utility-package/iotest/main_test.go) + +### 不正確なベンチマークの作成 (#89) + +???+ info "要約" + + ベンチマークについて + + * ベンチマークの精度を維持するには、time メソッドを使用しましょう。 + * ベンチタイムを増やすか、benchstat などのツールを使用することで、マイクロベンチマークが扱いやすくなります。 + * アプリケーションを最終的に実行するシステムがマイクロベンチマークを実行するシステムと異なる場合は、マイクロベンチマークの結果に注意してください。 + * コンパイラの最適化によってベンチマークの結果が誤魔化されないよう、テスト対象の関数が副作用を引き起こすようにしてください。 + * オブザーバー効果を防ぐには、CPU に依存する関数が使用するデータをベンチマークが再生成するよう強制してください。 + +セクション全文は[こちら](89-benchmarks.md)。 + + [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/11-testing/89-benchmark/) + +### Go言語のテスト機能をすべて試していない (#90) + +* コードカバレッジ + + コードのどの部分に注意が必要かをすぐに確認するために、`-coverprofile` フラグを指定してコードカバレッジを使用しましょう。 + + * 別のパッケージからのテスト + + 内部ではなく公開された動作に焦点を当てたテストの作成を強制するために、単体テストは別々のパッケージに配置しましょう。 + + [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/11-testing/90-testing-features/different-package/main_test.go) + +* ユーティリティ関数 + + 従来の `if err != nil` の代わりに `*testing.T` 変数を使用してエラーを処理すると、コードが短く、読みやすくなります。 + + [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/11-testing/90-testing-features/utility-function/main_test.go) + +* setup と teardown + + setup および teardown 機能を利用して、統合テストの場合など、複雑な環境を構成できます。 + + [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/11-testing/90-testing-features/setup-teardown/main_test.go) + +### ファジングを使用していない(community mistake) + +???+ info "要約" + + ファジングは、複雑な関数やメソッドへのランダムな、予想外の、または不正な入力を検出し、脆弱性、バグ、さらには潜在的なクラッシュを発見するのに効率的です。 + +[@jeromedoucet](https://github.com/jeromedoucet) さんのご協力に感謝いたします。 + +## 最適化 + +### CPU キャッシュを理解していない (#91) + +* CPU アーキテクチャ + + L1 キャッシュはメインメモリよりも約 50 ~ 100 倍高速であるため、CPU バウンドのアプリケーションを最適化するには、CPU キャッシュの使用方法を理解することが重要です。 + +* キャッシュライン + + キャッシュラインの概念を意識することは、データ集約型アプリケーションでデータを整理する方法を理解するのに重要です。CPU はメモリをワードごとにフェッチしません。代わりに、通常はメモリブロックを 64 バイトのキャッシュラインにコピーします。個々のキャッシュラインを最大限に活用するには、空間的局所性を強制してください。 + + [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/12-optimizations/91-cpu-caches/cache-line/) + +* 構造体のスライスとスライスの構造体 + + + + [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/12-optimizations/91-cpu-caches/slice-structs/) + +* 予測可能性 + + CPU にとって予測可能なコードにすることは、特定の関数を最適化する効率的な方法でもあります。たとえば、ユニットまたは定数ストライドは CPU にとって予測可能ですが、非ユニットストライド(連結リストなど)は予測できません。 + + [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/12-optimizations/91-cpu-caches/predictability/) + +* キャッシュ配置ポリシー + + キャッシュがパーティション化されていることを認識することで、重大なストライドを回避し、キャッシュのごく一部のみを使用するようにすることができます。 + +### 誤った共有を引き起こす並行処理(#92) + +???+ info "要約" + + 下位レベルの CPU キャッシュがすべてのコアで共有されるわけではないことを知っておくと、並行処理におけるの誤った共有などでパフォーマンスを低下させてしまうことを回避できます。メモリの共有はありえないのです。 + + [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/12-optimizations/92-false-sharing/) + +### 命令レベルの並列性を考慮しない (#93) + +???+ info "要約" + + 命令レベルの並列性(ILP)を使用してコードの特定の部分を最適化し、CPU ができるだけ多くの命令を並列実行できるようにしましょう。主な手順の 1 つにデータハザードの特定があります。 + + [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/12-optimizations/93-instruction-level-parallelism/) + +### データの配置を意識していない (#94) + +???+ info "要約" + + Go言語では、基本型は各々のサイズに合わせて配置されることを覚えておくことで、ありがちな間違いを避けることができます。たとえば、構造体のフィールドをサイズで降順に再編成すると、構造体がよりコンパクトになる(メモリ割り当てが少なくなり、空間的局所性が向上する)可能性があることに留意してください。 + + [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/12-optimizations/94-data-alignment/) + +### ヒープとスタックの違いを理解していない (#95) + +???+ info "要約" + + ヒープとスタックの基本的な違いを理解することも、Go アプリケーションを最適化する際には大切です。スタック割り当ては容易なのに対して、ヒープ割り当ては遅く、メモリのクリーンアップに GC を利用します。 + + [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/12-optimizations/95-stack-heap/) + +### 割り当てを減らす方法がわかっていない( API の変更、コンパイラの最適化、および `sync.Pool`) (#96) + +???+ info "要約" + + 割り当てを減らすことも、Go アプリケーションを最適化する上で重要です。これは、共有を防ぐために API を慎重に設計する、一般的な Go コンパイラの最適化を理解する、`sync.Pool` を使用するなど、さまざまな方法で行うことができます。 + + [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/12-optimizations/96-reduce-allocations/) ### インライン展開をしていない (#97) ???+ info "要約" - ファストパスのインライン化手法を使用して、関数の呼び出しにかかる償却時間を効率的に削減します。 + ファストパスのインライン化手法を使用して、関数の呼び出しにかかる償却時間を効率的に削減しましょう。 ### Go言語の診断ツールを利用していない (#98) @@ -1423,4 +2028,4 @@ Go Context の最後の使用例は、キーと値のリストを運ぶことで ???+ info "要約" - Docker と Kubernetes にデプロイする際のCPUスロットリングを回避するには、Go言語がCFS対応ではないことに留意してください。 \ No newline at end of file + Docker と Kubernetes にデプロイする際の CPU スロットリングを回避するには、Go言語が CFS 対応ではないことに留意してください。 \ No newline at end of file From 2e67bfc710fb74a421a0786a8b9a49888b30eb47 Mon Sep 17 00:00:00 2001 From: rustinwelter Date: Thu, 19 Oct 2023 18:45:12 +0900 Subject: [PATCH 2/3] minor fix --- docs/ja.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ja.md b/docs/ja.md index 05217ac5..4c46bdde 100644 --- a/docs/ja.md +++ b/docs/ja.md @@ -1716,11 +1716,11 @@ nil チャネルは状況によっては便利であり、Go 開発者は並行 ## 標準ライブラリ -### 誤った `time.Duration` を指定する (#75) +### 間違った時間を指定する (#75) ???+ info "要約" - `time.Duration` を受け入れる関数には注意を払ってください。整数を渡すことは許可されていますが、混乱を招かないように time API を使用するよう努めてください。 + `time.Duration` を受け入れる関数には注意を払ってください。整数を渡すことは許可されていますが、混乱を招かないように time API を使用するよう努めてください。 標準ライブラリの多くの関数は、`int64` 型のエイリアスである `time.Duration` を受け入れます。ただし、1 単位の `time.Duration` は、他のプログラミング言語で一般的に見られる 1 ミリ秒ではなく、1 ナノ秒を表します。その結果、`time.Duration` API を使用する代わりに数値型を渡すと、予想外の動作が発生する可能性があります。 From e86cdddb3132f50dcf749f8f910cfd5ba72baeaf Mon Sep 17 00:00:00 2001 From: rustinwelter Date: Sun, 29 Oct 2023 22:31:21 +0900 Subject: [PATCH 3/3] minor fix --- docs/ja.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/ja.md b/docs/ja.md index 4c46bdde..9736a8c8 100644 --- a/docs/ja.md +++ b/docs/ja.md @@ -548,7 +548,7 @@ Go言語では、nil と空のスライスは区別されます。nil スライ [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/03-data-types/29-comparing-values/main.go) -## 構造の制御 +## 制御構造 ### 要素が `range` ループ内でコピーされることを知らない (#30) @@ -565,9 +565,9 @@ range ループを使用すると、さまざまなデータ構造に反復処 * マップ * 受信チャネル - 古典的な `for` ループと比較すると、`range` ループはその簡潔な構文のおかげで、これらのデータ構造のすべての要素に反復処理をするのに便利です。 +古典的な `for` ループと比較すると、`range` ループはその簡潔な構文のおかげで、これらのデータ構造のすべての要素に反復処理をするのに便利です。 - ただし、range ループ内の値要素はコピーであることを覚えておく必要があります。したがって、値を変更する必要がある構造体の場合、変更する値またはフィールドがポインタでない限り、要素自体ではなくコピーのみを更新します。range ループまたは従来の for ループを使用してインデックス経由で要素にアクセスすることが推奨されます。 +ただし、range ループ内の値要素はコピーであることを覚えておく必要があります。したがって、値を変更する必要がある構造体の場合、変更する値またはフィールドがポインタでない限り、要素自体ではなくコピーのみを更新します。range ループまたは従来の for ループを使用してインデックス経由で要素にアクセスすることが推奨されます。 [ソースコード :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/04-control-structures/30-range-loop-element-copied/)