Effective Goのまとめ
Effective Go読んだのでまとめ。 C言語 + オブジェクト指向言語の経験者向け。
- Formatting
- Commentary
- Names
- Semicolons
- Control structures
- Functions
- Data
- Printing
- Initialization
- Methods
- Interfaces and other types
- The blank identifier
- Embedding
- Concurrency
- Errors
- A web server
- 感想
Formatting
- go fmt使え
- indentはスペースでなくタブ
Commentary
- 文法としては、C++と同じ
- トップレベルコメントはJavadoc的な立ち位置。go docで出力する。普通のテキスト扱い。HTML使ったり自前の整形などはしないで、普通のテキストとして記述するのが良い。
- すべてのパッケージはパッケージコメント、つまりパッケージ節の前にあるブロックコメントを持つべき
- 関数宣言などにつけるコメントの最初の文は、宣言されている名前から始まる1文の要約であること
- 変数宣言のグループ化とコメントを組み合わせると、個々の変数でなくまとめてコメントを付けれる
Names
- パッケージ名
- Getters/Setter
- 先頭に"Get"を付けない
- Setterは"Set"付ける
- Interface names
- 1メソッドしかないのものは"-er"にする。Reader/Writerなど。
- メソッド名が、Write, Close, Flush, Stringなどの標準的なシグネチャと機能に一致しているなら、それらの名前を付ける。そうでないなら、標準的な名前は避ける。
- Snakecaseでなく、MixedCapsまたはmixedCaps
Semicolons
- つけなくてOK。自動挿入されるので。
Control structures
- for/if/switchがある
- 特筆すべきはswitch
- switch
- caseには、定数以外も指定できる。ifで書くような条件文も書ける。変数の型による分岐もできる
- 特に指定しなければfallthroughはなし
- Labelを付けて、break
Functions
- 多値返しできる。 C言語のようにポインタを引数に渡さなくても良い。
- 戻り値に名前を付けて、変数として値を設定したり取得できる
- "defer func(args)"で、deferの呼び出し元の関数の終了時にその関数を実行できる
- 複数deferを記述した場合の関数実行順は、LIFO。
- 引数は即評価される。deferの呼び出し元の関数の終了時ではない。
Data
newによるallocate
- 戻り値は*T
- 中身はゼロ値
Constructors and composite literals
- オブジェクト作成時にゼロ値だと困るときはコンストラクタ作る。名前は"New<型名>"。
- オブジェクト作成をリテラル形式で書くと実装が簡潔になる
- new(File)と&File{}は等価
- C言語と違って、ローカル変数の参照を返せる
makeによるallocate
- slice/map/channel用
- これらをnewで作成しないのは、ゼロ値だと困るから。sliceでいえば、array、len、capも初期化したいから。
Arrays
- C言語と違って値型。 関数呼び出し時、呼び出し元と変更内容を共有したい、値のコピーを避けたいときは、ポインタ型を使う。
- 要素の型は同じでもサイズが違うなら、別の型扱い
Slices
- 関数呼び出し時、Arraysと違い、呼び出し元と要素の変更は共有される。内部的には配列のポインタを保持しているため。
- 関数呼び出し時、呼び出した関数の中でサイズを変更する場合は、結果をSliceで返すなどする。内部的には長さは値型として保持されているため。
Maps
- 等価演算子が実装されている型ならkeyに使える
- 存在しないkeyを参照するとゼロ値が返る
- 存在チェックはをするなら"v, ok = map[key]"。okがtrueなら存在するkey。
- 要素削除はdelete(map, key)。要素は存在しなくてもOK
Printing
正直あまり覚える必要ないと思う。
- Printfなどで使う%vは重要。
- Printlnは%vで値を表示してくれるっぽい。
- %vは、値のデフォルトの文字列を返す
- %+vは、構造体のフィールド名も出力
- %#vは、go syntaxとして値を出力
- %xは、文字列や配列にも使える
Append
- sliceに要素追加:append(slice, a, b, c)
- sliceにsliceを追加:append(slice, ...slice2)。"..."はリストを展開する意味。可変長引数にarrayやsliceを渡すとき使う。
Initialization
Constants
Variables
- 初期化のタイミングは実行時
Init Functions
- 変数宣言の初期化子を評価された後に実行
- 使い所
- 変数宣言では設定しきれない値の初期化
- 状態の正しさを保証する
Methods
- 値メソッドはポインタと値に対して呼び出すことができる
- ポインタメソッドはポインタに対してのみ呼び出すことができる
Interfaces and other types
- 型は複数のインタフェースを実装することができる
- 型変換の方法
- あるinterfaceを実装している型について、そのinterfaceとして使わないなら、コンストラクタの戻り値の型は、そのinterface型にする
- ほとんど全てのものがメソッドを持つことができる
- 構造体、組み込み型、関数など
The blank identifier
"_"の使いどころ。
- 多値返しの関数の結果を受けるときに、値を捨てる
- 開発段階で、パッケージの未使用importによるエラーを避ける
- "var _ = fmt.Printf"のように書くと、fmt未使用のエラーを回避できる
- この記述は、imoprt文の直後に置くのが慣習
- パッケージのinit関数を実行させるため(そのinit関数による副作用が必要なとき)
- ある構造体が特定のインターフェースを実装しているかチェックする
Embedding
type ReadWriter struct { *Reader // *bufio.Reader *Writer // *bufio.Writer }
このように書くと、ReadWriterは、ReaderWriterからReadやWriteを直接呼べる。
良い点
- 内側の構造体のメンバへのアクセスが簡潔にできる
- 内側の構造体が実装しているinterfaceを引き継げる
- 例でいえば、ReadWriterはReaderとWriterの両方のinterfaceを実装している扱い
メンバ名が被った時のルール
- 外側のメンバで上書き
- 同じ階層のメンバ名が被るとエラー。ただし、そのメンバに参照しなければエラーにならない。
Concurrency
Share by communicating
基本:メモリ上での値の共有は避け、通信によって値を共有する
(Mutex避けるということ)
Goroutines
- 複数のOSスレッドに多重化されているので、I/Oを待っている間などに1つのスレッドがブロックされても、他のスレッドは実行され続ける
- "go func()"のように書く。func()の実行完了は待たないで次に進む。
- 関数リテラルを使うと便利
Channels
- makeで作る
- 使い方はいろいろ。たとえば、サーバのリクエストの処理数の制限。
例
var sem = make(chan int, MaxOutstanding) func Serve(queue chan *Request) { for req := range queue { req := req // Create new instance of req for the goroutine. sem <- 1 go func() { process(req) <-sem }() } }
req := reqは重要。これを忘れると、全てのgoroutineから見えるreqは、最新のreqの値になってしまう。ループ変数reqのスコープは、for文の1イテレーションではなくfor文終わるまでだから。
Channels of channels
- channelにchannelをもたせることもできる
Parallelization
Channelを使うと並列処理できる。
const numCPU = runtime.NumCPU() func (v Vector) DoAll(u Vector) { c := make(chan int, numCPU) // Buffering optional but sensible. for i := 0; i < numCPU; i++ { go v.DoSome(i*len(v)/numCPU, (i+1)*len(v)/numCPU, u, c) } // Drain the channel. for i := 0; i < numCPU; i++ { <-c // wait for one task to complete } // All done. }
runtime.NumCPU()の代わりにruntime.GOMAXPROCSというのも使える。これは、 実装者が指定したプログラムが同時に実行できるコア数。デフォルト値はruntime.NumCPU()。
A leaky buffer
Channelを使うと並列処理と関係ないリソース管理にも使えるよ、という話。
Errors
基本
- 関数の多値返しを利用してエラーを返す + チェックする
- エラー型には、組み込みinterfaceのerrorを実装させる
- 可能であれば、エラー文字列は、エラーを発生させた操作やパッケージを示す接頭辞を持つなどして、その発生源が分かるようにする
panic
recover
- panicから復旧させるときに使う。これをしないとプログラムはクラッシュする。
- deferとセットで使う
- 用途
- 複数gorouttine を実行していて、問題が起きたgoroutineだけシャットダウンする
- エラー処理を簡潔に書く
- recover後の再度panicさせると、元のpanic要因と再panic要因の両方がクラッシュレポートに含まれる
A web server
"html/template"や"net/http"を使うと、Webサーバ作ったり、HTMLテンプレートが簡単に使える、というだけの話。
感想
他のEffective Xと違って、「Go言語入門」の一部。A Tour of Goの次に読んだ方が良い。