Skip to content

Instantly share code, notes, and snippets.

@ar90n
Last active November 26, 2023 15:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ar90n/06b709915a8840eddfb9354d249bcca9 to your computer and use it in GitHub Desktop.
Save ar90n/06b709915a8840eddfb9354d249bcca9 to your computer and use it in GitHub Desktop.
memo_about_go_100_tips
## No.5 インターフェース汚染
不要な抽象化でコードを埋め尽くし理解をしにくくすること
インターフェースが有用なポイント
* 共通の振る舞い
* 具体的な実装との分離
* 振る舞いの制限
インターフェースは抽象化を行う。抽象化は作成するものではなく、発見するもの。
インターフェースで設計するのではなく、インターフェースを発見する。
## No.6 生産者側のインタフェース
インタフェースは実装側で定義せず、利用側で定義する。
これにより、利用側が本当に必要な最低限の機能のみに限定したインタフェースを定義できる。
実装側は利用側で定義されたインタフェースの存在を知る必要はない。(Goでは暗黙的にインタフェースを満たすかの判定が行われる)
したがって、利用側が実装側のモジュールをインポートする。
例外(実装で定義)はそのインタフェースが全てのケースで必要となる場合。
## No.7 インタフェースを返す
関数の戻り値の型としてインタフェースは使用しない。実体を返す。
インタフェースを返すと戻り値を特定のインタフェースとしてしか利用できなくなる。
## No.10 型埋め込みで起こり得る問題を意識していない
型埋め込みは継承ではなくコンポジションのシンタックスシュガー。
レシーバーにおける`this`相当のもがどのような型になるかを考えるとこれらの違いがわかる。
継承では`this`は継承元の型になるが、コンポジションでは埋め込まれた型になる。
型埋め込みは積極的にな使用しない。特にコードの簡略化程度には使用しない。
外部から容易にアクセスできるようにするために使用する。
## No.11 関数オプションパターンを使わない
積極的に関数オプションパターンを使用しよう。
ビルダーパターンでメソッドチェーンを行う場合、`self, error`という型を返せないなので辛い.
## No.15 コードのドキュメントがない
基本的に公開されるものにはコメントを付与する。コメントは要素の名前ではじまる完全な文とします。
```go
// Customerは、顧客を表します。
type CUstomer struct {}
```
## No.26 スライスとメモリリーク
### 容量のリーク
スライスは内部的に配列を参照している。スライスの容量は参照している配列の容量と同じ。
```go
s := make([]int, 0, 1024)
storeSlice(s[:3]) // capが1024のスライスとして保存される
```
### スライスとポインタ
スライスの要素がポインタやポインタフィールドを持つ構造体の場合、その要素はGCで回収されない。
スライス全体をGCで回収するか、個別に要素をnilにする必要がある。
## No.42 どちらのレシーバ型をつかうべきかわかっていない
### レシーバがポインタでなくてはならない
* メソッドがレシーバを変更する場合
* レシーバがコピーできないフィールドを持つ場合
### レシーバがポインタであるべき
* レシーバが大きなオブジェクト
### レシーバが値でなければならない
* メソッドがレシーバを変更しない場合
* レシーバがマップ、関数、チャンネルの時
### レシーバが値であるべき
* レシーバが小さなオブジェクト
* レシーバがプリミティブ型
## No.47 deferの引数やレシーバの評価方法を無視している
deferの引数やレシーバはdeferが実行される時点で評価される。
## No.49 エラーをラップすべきときを無視する
エラーは%wヴァーブでラップできる。エラーラッピングの主なユースケースは
* 文脈情報を追加
* 特定のエラーとしてマーク
エラーのハンドリングパターンは以下の通り
* ラップしない(直接返す)
* 独自のエラー型を定義する
* fmt.Errorfと%w
* fmt.Errorfと%v
## No.50 エラー型を不正確に検査する
エラー型を検査する場合は、エラー型のインタフェースを満たすかを検査する。
エラー型の検査は`switch err.(type)`か`errors.As`を使用する。
## No.51 エラー値を不正確に検査する
エラー値を検査する場合は、エラー値の値を検査する。
エラー値の検査は`errors.Is`を使用する。
## No.54 deferでエラーを処理しない
deferで発生したエラーは名前付き戻り値で返す。
```go
func foo() (err error) {
defer func() {
err := doit();
}()
}
```
## No.58 競合問題を理解していない
データ競合(data race)と競合状態(race condition)とは異なる。
### データ競合
二つ以上のゴルーチンが同時に同じメモリ位置にアクセスし、少なくとも1つが書き込み中である場合に発生。
以下の方法で対応が可能。
* アトミック操作を使用
* ミューテックスでクリティカルセクションを保護
* チャンネルを使用してゴルーチン間でデータをやり取り
### 競合状態
制御できないイベントの順序やタイミングで動作が変わること。
## No.62 ゴルーチンを停止するタイミングを分からずに起動する
ゴルーチンはちゃんとクローズする。リソース解放のため。
## No.64 selectとチャンネルを使って決定的な動作を期待する
selectはランダムに選択される。決定的な動作を期待する場合は、チャンネルを使用せずに条件分岐を行う。
以下のコードではch1がch2に対して優先されることはない。一様乱数でランダムに選択される。
```go
select {
case <-ch1:
// ch1の動作
case <-ch2:
// ch2の動作
}
```
## No.66 nilチャンネルを使っていない
nilチャンネルはメッセージを読む時も送る時も常にブロックする。
チャンネルをマージする場合、一方のチャンネルをクローズすると常に0が読み込まれる.
従って、以下のコードではch1が常に成立する。
```go
for {
select {
case <-ch1:
// ch1の動作
case <-ch2:
// ch2の動作
}
}
```
なので、クローズ後にnilをセットする.
## No.72 sync.Condの存在を忘れている
複数のゴルーチンから同時にチャンネルを読まれた場合、ランダムに選択されたゴルーチンがチャンネルを読み込む。
他のゴルーチンはブロックされる。デフォルトではブロードキャストされない。
## No.91 CPUキャッシュを理解していない
メモリとキャッシュとのやりとりはキャッシュライン単位で行われる。
予測可能性のあるアクセスパターンを作成することで、キャッシュミスを減らすことができる。
ユニットストライドや定数ストライドによるメモリアクセスは予測性が高い。一方、ランダムアクセスは予測性が低い。
## No.92 偽共有につながる平衡コードを書く
独立した変数同士が同じキャッシュラインに存在する場合、偽共有が発生する。
これは、複数のゴルーチンがそれぞれ別の変数を更新するにも関わらす、同一のキャッシュラインを更新するため、当該キャッシュラインはダーティになる。
従って、パフォーマンスが低下する。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment