- 投稿日:2019-12-21T23:13:42+09:00
【爆速】Go入門2
この記事は2019新卒 エンジニア Advent Calendar 2019の21日目の記事です。
今回は、爆速でGoに入門していくシリーズのPart2です(Part1はこちら【爆速】Go入門)。
Methods
Goには、クラスのしくみはありませんが、型にメソッドを定義できます。
メソッドは、レシーバと呼ばれる特別な引数を伴う関数です。
レシーバはfunc キーワードとメソッド名の間に自身の引数リストとして書きます。
この例では、 Abs メソッドは v という名前の Vertex 型のレシーバを持つことを意味しています。
package main import ( "fmt" "math" ) type Vertex struct { X, Y float64 } func (v Vertex) Abs() float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) } func main() { v := Vertex{3, 4} fmt.Println(v.Abs()) }$ go run main.go 5以下の Abs は、先ほどの例から機能を変えずに通常の関数として記述しています。上の例との違いを確認しておきましょう。
package main import ( "fmt" "math" ) type Vertex struct { X, Y float64 } // func (v Vertex) Abs() float64 { // return math.Sqrt(v.X*v.X + v.Y*v.Y) // } func Abs(v Vertex) float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) } func main() { v := Vertex{3, 4} // fmt.Println(v.Abs()) fmt.Println(Abs(v)) }$ go run main.go 5例で挙げたstructの型だけではなく、任意の型(type)にもメソッドを宣言できます。
また、レシーバを伴うメソッドの宣言は、レシーバ型が同じパッケージにある必要があります。 他のパッケージに定義している型に対して、レシーバを伴うメソッドを宣言できません。
package main import ( "fmt" ) type MyFloat float64 func (f MyFloat) Abs() float64 { if f < 0 { return float64(-f) } return float64(f) } func main() { f := MyFloat(-46) fmt.Println(f.Abs()) }$ go run main.go 46ポインタレシーバでメソッドを宣言できます。
例では *Vertex に Scale メソッドが定義されています。
ポインタレシーバを持つメソッド(ここでは Scale )は、レシーバが指す変数を変更できます。 レシーバ自身を更新することが多いため、変数レシーバよりもポインタレシーバの方が一般的です。
変数レシーバでは、 Scale メソッドの操作は元の Vertex 変数のコピーを操作します。 (これは関数の引数としての振るまいと同じです)。 つまり main 関数で宣言した Vertex 変数を変更するためには、Scale メソッドはポインタレシーバにする必要があるのです。
package main import ( "fmt" "math" ) type Vertex struct { X, Y float64 } func (v Vertex) Abs() float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) } func (v *Vertex) Scale(f float64) { v.X = v.X * f v.Y = v.Y * f } func main() { v := Vertex{3, 4} v.Scale(10) fmt.Println(v.Abs()) }$ go run main.go 50ポインタレシーバを変数レシーバに変えて、違いを確認しておきましょう。
package main import ( "fmt" "math" ) type Vertex struct { X, Y float64 } func (v Vertex) Abs() float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) } // func (v *Vertex) Scale(f float64) { // v.X = v.X * f // v.Y = v.Y * f // } func (v Vertex) Scale(f float64) { v.X = v.X * f v.Y = v.Y * f } func main() { v := Vertex{3, 4} v.Scale(10) fmt.Println(v.Abs()) }$ go run main.go 5次に、先ほどのScaleメソッドを関数として書き直し(ScaleFunc)、呼び出し時の違いを見ていきましょう。
package main import "fmt" type Vertex struct { X, Y float64 } func (v *Vertex) Scale(f float64) { v.X = v.X * f v.Y = v.Y * f } func ScaleFunc(v *Vertex, f float64) { v.X = v.X * f v.Y = v.Y * f } func main() { v := Vertex{3, 4} // ScaleFunc(v, 10) エラーになる ScaleFunc(&v, 10) v.Scale(2) // (&v).Scale(2)として解釈される p := &Vertex{4, 3} p.Scale(3) ScaleFunc(p, 8) fmt.Println(v, p) }ポインタを引数に取る ScaleFunc 関数は、ポインタを渡す必要があります。
v := Vertex{3, 4} ScaleFunc(&v, 10) // OK ScaleFunc(v, 10) // ERRORメソッドがポインタレシーバである場合、呼び出し時に、変数、または、ポインタのいずれかのレシーバとして取ることができます。
v := Vertex{3, 4} v.Scale(2) //OK p := &Vertex{4, 3} p.Scale(10) //OKv.Scale(5) のステートメントでは、 v は変数であり、ポインタではありません。 メソッドでポインタレシーバが自動的に呼びだされます。 Scale メソッドはポインタレシーバを持つ場合、Goは利便性のため、 v.Scale(5) のステートメントを (&v).Scale(5) として解釈します。
$ go run main.go {60 80} &{96 72}ポインタレシーバを使う2つの理由があります。
ひとつは、メソッドがレシーバが指す先の変数を変更するためです。
ふたつに、メソッドの呼び出し毎に変数のコピーを避けるためです。 例えば、レシーバが大きな構造体である場合に効率的です。
下の例では、次の Abs メソッドはレシーバ自身を変更する必要はありませんが、 Scale と Abs は両方とも *Vertex 型のレシーバです。
一般的には、変数レシーバ、または、ポインタレシーバのどちらかですべてのメソッドを与え、混在させるべきではありません。
package main import ( "fmt" "math" ) type Vertex struct { X, Y float64 } func (v *Vertex) Abs() float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) } func (v *Vertex) Scale(f float64) { v.X = v.X * f v.Y = v.Y * f } func main() { v := Vertex{3, 4} v.Scale(10) fmt.Println(v.Abs()) }$ go run main.go 50Interfaces
型にメソッドを実装していくことによって、インタフェースを実装(満た)します。
インタフェースを実装することを明示的に宣言する必要はありません( "implements" キーワードは必要ありません)。
package main import ( "fmt" ) type I interface { M() } type T struct { S string } // このメソッドは暗黙的にTがインターフェースIを実装していることを意味する func (t T) M() { fmt.Println(t.S) } func main() { var i I = T{"hello gunsoo"} i.M() }$ go run main.go hello gunsooインターフェースの値は、値と具体的な型のタプルのように考えることができます : (value, type)
インターフェースの値は、特定の基底になる具体的な型の値を保持します。
インターフェースの値のメソッドを呼び出すと、その基底型の同じ名前のメソッドが実行されます。
関数describeを定義して確認してみましょう。
package main import ( "fmt" ) type I interface { M() } type T struct { S string } func (t T) M() { fmt.Println(t.S) } func main() { var i I = T{"hello gunsoo"} describe(i) i.M() } func describe(i I) { fmt.Printf("(%v, %T)\n", i, i) }$ go run main.go ({hello gunsoo}, main.T) hello gunsooインターフェース自体の中にある具体的な値が nil の場合、メソッドは nil をレシーバーとして呼び出されます。
いくつかの言語ではこれは null ポインター例外を引き起こしますが、Go では nil をレシーバーとして呼び出されても適切に処理するメソッドを記述するのが一般的です(この例では M メソッドのように)。
具体的な値として nil を保持するインターフェイスの値それ自体は非 nil であることに注意してください。
package main import ( "fmt" ) type I interface { M() } type T struct { S string } func (t *T) M() { if t == nil { fmt.Println("<nil>") return } fmt.Println(t.S) } func main() { var i I var t *T i = t describe(i) i.M() } func describe(i I) { fmt.Printf("(%v, %T)\n", i, i) }$ go run main.go (<nil>, *main.T) <nil>ゼロ個のメソッドを指定されたインターフェース型は、 空のインターフェース と呼ばれます : interface{}
空のインターフェースは、任意の型の値を保持できます。 (全ての型は、少なくともゼロ個のメソッドを実装しています。)
空のインターフェースは、未知の型の値を扱うコードで使用されます。 例えば、 fmt.Print は interface{} 型の任意の数の引数を受け取ります。
package main import ( "fmt" ) func main() { var i interface{} describe(i) i = 46 describe(i) i = "gunsoo" describe(i) } func describe(i interface{}) { fmt.Printf("(%v, %T)\n", i, i) }$ go run main.go (<nil>, <nil>) (46, int) (gunsoo, string)Type assertions
型アサーション は、インターフェースの値の基になる具体的な値を利用する手段を提供します。
t := i.(T)
この文は、インターフェースの値 i が具体的な型 T を保持し、基になる T の値を変数 t に代入することを主張します。i が T を保持していない場合、この文は panic を引き起こします。
インターフェースの値が特定の型を保持しているかどうかを テスト するために、型アサーションは2つの値(基になる値とアサーションが成功したかどうかを報告するブール値)を返すことができます。
t, ok := i.(T)
i が T を保持していれば、 t は基になる値になり、 ok は真(true)になります。そうでなければ、 ok は偽(false)になり、 t は型 T のゼロ値になり panic は起きません。
package main import "fmt" func main() { var i interface{} = "hello" s := i.(string) fmt.Println(s) s, ok := i.(string) fmt.Println(s, ok) f, ok := i.(float64) fmt.Println(f, ok) // f := i.(float64) // panic // fmt.Println(f) }$ go run main.go hello hello true 0 false型switch はいくつかの型アサーションを直列に使用できる構造です。
package main import "fmt" func do(i interface{}) { switch v := i.(type) { case int: fmt.Printf("Twice %v is %v\n", v, v*2) case string: fmt.Printf("%q is %v bytes long\n", v, len(v)) default: fmt.Printf("I don't know about type %T!\n", v) } } func main() { do(46) do("hello") do(true) }$ go run main.go Twice 46 is 92 "hello" is 5 bytes long I don't know about type bool!Errors
Goのプログラムは、エラーの状態を error 値で表現します。
error 型は fmt.Stringer に似た組み込みのインタフェースです:
type error interface { Error() string }よく、関数は error 変数を返します。そして、呼び出し元はエラーが nil かどうかを確認することでエラーをハンドル(取り扱い)します。
nil の error は成功したことを示し、 nilではない error は失敗したことを示します。
package main import ( "fmt" "strconv" ) func main() { i, err := strconv.Atoi("46") if err != nil { fmt.Printf("couldn't convert number: %v\n", err) return } fmt.Println("Converted integer:", i) }$ go run main.go Converted integer: 46まとめ
主にメソッドとインターフェースを学んだGo入門第2回目でした。
次回へと続きます。
(参考)
https://go-tour-jp.appspot.com/list(Go入門1)
【爆速】Go入門
- 投稿日:2019-12-21T21:52:18+09:00
[golang] graceful shutdownは難しい
メリークリスマス!
概要
非同期にログのようなものをサービスから送信するようなclientを作るとき、「サーバーをどう落とすか」というのはとても難しい問題だと思います。
特にお金がかかわるようなログだと送り漏れがないようにする必要がありますし「もう同期でいいんじゃね」と頭を抱えてしまいます。非同期なのでバッファのような場所にデータがたまっているので、これを掃除してから落とす必要があるのが厄介の源です。
要件
clientは、あるjson型のログを別のRESTのシステムに投げるようなユースケースを想定していますが、ここではそこは端折ってio.Writerに書いています。
clientは、Sendで送られたメッセージをキャッシュし、
- キャッシュがあるサイズに達したら
- 定期的に
- 最後にサーバーをShutdownするときに
の3つの条件でflushします。
解
解1:同期的に送信する
まずシンプルな解として同期的なものを用意しておきます。非同期処理はこれと同じ事を実現する必要があります。
type V1Client struct { w io.Writer } func NewV1Clienet(w io.Writer) *V1Client { return &V1Client{w} } func (c *V1Client) Send(message string) error { _, err := fmt.Fprintln(c.w, message) return err }これでなぜダメだったかといいますと、メッセージを束ねる必要があったためです。
解2:戦いはここから始まった
type V2Client struct { w io.Writer q chan string logs []string max int mu *sync.Mutex itvl time.Duration } func NewV2Client(w io.Writer, bufsize int, itvl time.Duration) *V2Client { return &V2Client{ w: w, logs: make([]string, bufsize)[:0], mu: &sync.Mutex{}, max: bufsize, itvl: itvl, } } func (c *V2Client) Send(ctx context.Context, message string) { c.mu.Lock() defer c.mu.Unlock() c.logs = append(c.logs, message) if len(c.logs) >= c.max { c.flush(ctx) } } func (c *V2Client) Start(ctx context.Context) { go func() { for { time.Sleep(c.itvl) c.mu.Lock() if err := c.flush(ctx); err != nil { fmt.Printf("error: %+v\n", err) } c.mu.Unlock() select { case <-ctx.Done(): return } } }() } func (c *V2Client) flush(ctx context.Context) error { defer func() { c.logs = c.logs[:0] }() for _, m := range c.logs { if _, err := fmt.Fprintln(c.w, m); err != nil { return err } } return nil }非同期バージョンの最初の実装はこんな感じでした。ほかの言語のイメージで実装するとこんな感じになるのではないかと思います。
この実装には様々な問題点があります。
- context cancelすることでプログラムを落とす、その時にlogsの中に残ったログの掃除をしていない
- logsという配列を共有メモリのようにgoroutine間のデータのやり取りに使っている
- logsがオーバーフローしたとき、同期的にログを送信してしまっている
主にこの3点でしょうか。ほかにもいろいろあるとは思いますが。
解3:修正版
type V3Client struct { logs []*string // log buffer q chan string // channel to send log max int // max size of logs itvl time.Duration // log send interval w io.Writer // l2pc backup logger exit chan struct{} once *sync.Once } func NewV3Client(writer io.Writer, buffsize int, interval time.Duration) *V3Client { client := &V3Client{ logs: make([]string, buffsize)[:0], q: make(chan string), max: buffsize, itvl: interval, w: writer, once: &sync.Once{}, } return client } func (c *V3Client) Send(message string) { c.q <- message } func (c *V3Client) Start(ctx context.Context) { if c.exit != nil { fmt.Println("client is already started.") return } c.exit = make(chan struct{}) go func() { t := time.NewTicker(c.itvl) defer t.Stop() for { select { case m := <-c.q: if err := c.tryFlush(ctx, m); err != nil { fmt.Println("log send error: " + err.Error()) } case <-t.C: if err := c.flush(ctx); err != nil { fmt.Println("log send error: " + err.Error()) } case <-c.exit: return } } }() } func (c *V3Client) Close(ctx context.Context) { c.once.Do(func() { c.exit <- struct{}{} if err := c.flushAll(ctx); err != nil { fmt.Println("log send error" + err.Error()) } }) } func (c *V3Client) tryFlush(ctx context.Context, msg string) error { c.logs = append(c.logs, msg) if len(c.logs) >= c.max { return c.flush(ctx) } return nil } func (c *V3Client) flushAll(ctx context.Context) error { close(c.q) all := c.logs for l := range c.q { all = append(all, l) } for { if len(all) < c.max { c.logs = all return c.flush(ctx) } c.logs, all = all[:c.max], all[c.max:] if err := c.flush(ctx); err != nil { return err } } } func (c *V3Client) flush(ctx context.Context) error { defer func() { c.logs = c.logs[:0] }() for _, l := range c.logs { if _, err := fmt.Fprintln(c.w, l); err != nil { return err } } return nil }}各メソッドはそれぞれ
client 説明 client#Start clientの非同期送信を開始する。goroutine部。 client#Send メッセージを送る(入力側)。業務ロジックが呼び出す。 client$Close 終了処理開始および、終了処理がおわるまで待機 client#flush 実送信部 client#tryFlush メッセージを受け取り必要であればflushする client#flushAll 終了処理 です。#2と比較して特に違うのは「ノンブロッキング」だという事だと思います。
ほかの言語だと、こういったプログラムには必ずといっていいほどLockが顔を出します。ですが上にはありません。
- SleepではなくTickerを使っている
- logsをgoroutineで共有せず、goroutine間の受け渡しにqというchanを使っている
ためです。
また、終了処理のために専用のexitというchを導入しました。なのでselect/caseで3つのchannelを待つコードになっています。学んだこと
- goroutine間を渡すデータはlockして共有メモリで渡すのではなくchannelで渡す
- 一定時間置きに起動する処理はSleepを使わずTickerを使う
defer cl.Close()
で終了処理を開始しつつかつ終了処理の終了まで待たせる- select/caseで複数のchannelを待つとき、同時に複数のchannelにデータが入ったときどのchannelが起動するかはrandom
func main(){ ctx := context.Background() cl := NewV3Client(...) cl.Start(ctx) defer cl.Close(ctx) // start server with client }最終的なmainはこのようになりました。
割と普通なインターフェースに収まったように思います。他の言語に慣れていると、「ノンブロッキングに無限forループを回す」というのはなかなか違和感があるのですが、確かにPythonのGILのように、過剰な範囲で足止めをしてしまい、パフォーマンスの足を引っ張る要因になりやすいのはわかる気がします。
一旦ここまでで。
- 投稿日:2019-12-21T17:11:21+09:00
Golandをつかいましょう 2019冬
はじめに
この記事は、アドベントカレンダー Go4 の21日目の記事です。
みなさん、Goのエディタ何を使っていますか?
Vim? Vscode?そうですねGolandを使いましょう。
Golandの利点
- インストールしたらデフォルトでいい感じの環境
- JetBrainsのエディタはUI同じなので複数言語使う人におすすめ
Golandはじめ方
それでは、Golandを始めていきましょう
筆者がMacしか持ってないのでここではMacでやる手順を記載してしまいました。
ただし心配することはありません。JetBrains製品はWindows,Linuxもサポートしています。導入手順も大体同じです。多分。Golandを買いましょう
Golandは有償です。初年度は1万円ですが、2年目以降はディスカウントされます。さらに使用していくうちにだんだんと多幸感に包まれるため、相殺され実質無料になります。
国内代理店のサムライズムさん経由なら若干安く購入することが出来ます。
また、日本語でのサポートも行っていただけるのでそちら経由で購入することをおすすめします。
https://samuraism.com/jetbrains/goland実質無料なのですが、どうしても使ってから購入したい人は30日間のトライアルがあるのでそちらを利用してもいいかもしれません。
ここまできてまだお悩みなら一旦 All Products Pack
を購入された方がいいと思います。こちらも初年度は3万円弱のお値段なのですが、継続3年目で2万円を切る値段にディスカウントされます。
もしあなたが、複数の言語を操るプログラマーなら迷わず購入するべきです。
以降はサムライズムさんのサイトで購入した体で話を進めていきます。
インストールしましょう
購入してアクティベーションされるまで若干のタイムラグがあります。その間にGolandをインストールしておきましょう
ちょっと待ってください。Chromeで
Goland
と検索してダウンロードページに進もうとしませんでしたか?
JetBrains製品をインストールするために直接アプリをインストールするのは愚かです。
ToolBox Appをインストールしましょう
https://www.jetbrains.com/ja-jp/toolbox-app/ToolBox Appを使うことで、ライセンスの自動適用、バージョン自動管理などを簡単に行うことが出来ます。
ToolBox Appを起動すると、なんか右上にメ●カ●のようなアイコンが表示されていませんか?
クリックしてみましょう。そこにGolandがありますね。
Install
ボタンをポチりましょう。そしてインストールされるのを待ちましょう。
....
アクティベーションしましょう
ちょうど、アクティベーションメールが届く頃だと思います。
Gmail
で確認しましょう。
こんな感じのメールにPDFが添付されているかと思います。アクティベーション用URLをクリックして、流れでJetBrainsアカウントを作りましょう。
そしてそのアカウントでToolBox Appにログインしましょう。
これでエディタ個別のアクティベーションが不要になり、以降は期限が切れるまでつかいたい放題です。Goをインストールしましょう
「は?ここでGo?Golandで全部揃うって言ったじゃん?嘘なの?」
と思われるかもしれませんが、Golandと同じくらい便利なツールがあります。
anyenv
です。「は?goenv入れておけばいいやん。なんで?」
と思われるかもしれませんが、anyenvを入れておけば、他言語も使えるからとりあえず入れとこ?な?brewで入れて環境変数をゴニョゴニョするだけです。入れておきましょう。
https://github.com/anyenv/anyenv#homebrew-for-macos-userGOENV_DISABLE_GOPATH=1
悪いこと言わないので
~/.bash_profile
とかに
export GOENV_DISABLE_GOPATH=1
を追加しておいてください。
何も考えずにとりあえず追加しておいてください。
理由を知りたい人はこちらにありがたい記事があるので見ておいてください。
https://qiita.com/gimKondo/items/add08298e24ae400505e
(自分もハマってた)goの最新版をインストールしてください。
goenv install -l # インストール可能なバージョン確認 goenv install 1.13.4 # 最新バージョンインストールしてね goenv global 1.13.4goimportsをインストールしておきましょう
go get golang.org/x/tools/cmd/goimportsXcodeをインストールしましょう
何も考えずにとりあえず入れておきましょう。
インストール超長いんですが、業務時間に入れて構いません。入れたら、下記のコマンドをやってください。
xcode-selectそんで環境変数を追加してください。
これも~/.bash_profile
とかに突っ込んでおけばいいです。export SDKROOT="$(xcrun --sdk macosx --show-sdk-path)"理由はOSXのアップデートのたびに、SDKROOTのパスがコロコロ変わるからなんですが、
ありがたい記事で解説されているので、参照されるといいと思います。
https://qiita.com/imbsky/items/da709a514dded95b8575Golandを立ち上げましょう
あなたは今使っているエディタに不満を持っていて、Golandに乗り換えようとしていると仮定しましょう。
ただし、手元には他エディタで作業しているプロジェクトフォルダがあります。ToolBox AppからGolandを立ち上げてください
そして、あなたのいつも作業しているプロジェクトフォルダを開いてください。
開きましたね。
後少しで開発出来ます。
設定をしましょう
GOROOT
Command + ,
でPreferences(設定画面)が開きます。
Go > GOROOT
で設定しましょう。
Go Module
GolandはGo Moduleに対応しています。
Go > Go Modules(vgo)
でEnable Go Modules(vgo) integration
にチェックしましょう
Proxy
はdirect
にしておいてください。Dep,Go Mdudle Vendoring modeにも対応していますが、絶対にやめておきましょう。
Go Moduleを使っている限り、プロジェクトフォルダにvendor/
が存在することに何の意味もありません。(個人の感想です)コーディングしましょう
おめでとうございます!ここまできたら、Golandでの開発環境は整いました!
あとはザクザクコーディングしていくだけです!Golandの世界へようこそ!プラグインを入れておきましょう
どんなにデフォでいい感じと言いつつも、やれvimバインドしたいだの、github連携させたいだの
個人の趣味に合わせた使い方をしたい場合はプラグインをインストールすることが出来ます。僕は本当はデフォでも大丈夫なんですが、下記のプラグインとか入れています。
- ideaVim
- .ignore
- BashSupport
- Github
- Docker
コーディングサンプル
先ほど、あなたのプロジェクトフォルダを開いてもらったのですが、いったん閉じていただいて、
ここではサンプルを使って、Golandでのコーディング作業を見ていきましょう。こちらのコードを下記のディレクトリ構成にして貼り付けてください
project/ `- sample/ |- mocks/ `- sample.go |- sample.go `- sample.test.gosample/sample.gopackage sample import ( "fmt" ) type HogeClient interface { Do() error } type Sample struct { HogeClient HogeClient } func (s *Sample) Do() error { err := s.HogeClient.Do() if err != nil { return fmt.Errorf("えらーだよ : %w", err) } return nil }sample/sample_test.gopackage sample import ( "fmt" "testing" "github.com/golang/mock/gomock" "github.com/junpayment/sample/sample/mocks" "github.com/stretchr/testify/assert" ) func TestSample_Do(t *testing.T) { t.Run("success", func(t *testing.T) { ctrl := gomock.NewController(t) mock := mocks.NewMockHogeClient(ctrl) mock.EXPECT().Do().Return(nil) s := &Sample{HogeClient: mock} err := s.Do() assert.Nil(t, err) }) t.Run("failed", func(t *testing.T) { ctrl := gomock.NewController(t) mock := mocks.NewMockHogeClient(ctrl) mock.EXPECT().Do().Return(fmt.Errorf("test")) s := &Sample{HogeClient: mock} err := s.Do() assert.Nil(t, err) }) }sample/mocks/sample.go// Code generated by MockGen. DO NOT EDIT. // Source: sample/sample.go // Package mocks is a generated GoMock package. package mocks import ( gomock "github.com/golang/mock/gomock" reflect "reflect" ) // MockHogeClient is a mock of HogeClient interface type MockHogeClient struct { ctrl *gomock.Controller recorder *MockHogeClientMockRecorder } // MockHogeClientMockRecorder is the mock recorder for MockHogeClient type MockHogeClientMockRecorder struct { mock *MockHogeClient } // NewMockHogeClient creates a new mock instance func NewMockHogeClient(ctrl *gomock.Controller) *MockHogeClient { mock := &MockHogeClient{ctrl: ctrl} mock.recorder = &MockHogeClientMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use func (m *MockHogeClient) EXPECT() *MockHogeClientMockRecorder { return m.recorder } // Do mocks base method func (m *MockHogeClient) Do() error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Do") ret0, _ := ret[0].(error) return ret0 } // Do indicates an expected call of Do func (mr *MockHogeClientMockRecorder) Do() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Do", reflect.TypeOf((*MockHogeClient)(nil).Do)) }さらにいろいろ
go get
してくださいgo get github.com/golang/mock go get github.com/stretchr/testifyこの状態で、ここの緑の三角をクリックしましょう。
こんな感じでまじでほとんど何もしなくてもデフォでデバッグ出来ます。
おっとテスト失敗しましたね。
テストを書き直して失敗したテストだけやり直しましょう。sample/sample_test.got.Run("failed", func(t *testing.T) { ctrl := gomock.NewController(t) mock := mocks.NewMockHogeClient(ctrl) mock.EXPECT().Do().Return(fmt.Errorf("test")) s := &Sample{HogeClient: mock} err := s.Do() assert.NotNil(t, err) // ここ })楽ぅ〜
条件付きのブレークポイントも貼りましょう。
便利ぃ〜
最後に
ここまで来たら、もはや今までのエディタをアンインストールし、全てのJetBrains製品をインストールするのみです。
Goland,JetBrains人口は他のエディタに比べて少ないのですが、
[引用]https://job-draft.jp/articles/323
デフォルト状態でいい感じに開発スタートできる(当社比)
,すぐコーディング作業に没頭することのできる(当社比)
という利点は
各プログラミング言語ごとに最適化された製品があるJetBrainsならでは無いでしょうか?だよね?みなさんGolandつかいましょう。
- 投稿日:2019-12-21T16:58:26+09:00
Echoで簡単なwebサーバーを実装してみる
VOYAGE GROUPのサマーインターン「Treasure」のアドベントカレンダー17日目の記事.
遅れてごめんなさい.目的
Goのwebフレームワークの一つであるEchoを使って簡単なwebサーバーをさくっと生やす
なんでEcho?
Goのwebフレームワークはたくさんある. ginとかbeegoとか...
そんな中Echoを選んだ理由は
1. 公式のドキュメントがすごい丁寧でわかりやすかった
2. 軽量で処理が高速 (らしい)
3. 書き方がわかりやすかったという感じ. 他のフレームワークにももちろん良いところはあるのだろうが, とっつきやすさを感じたのでEchoを使うことにした.
実践
サーバーの起動
Echoでwebサーバーを起動させるのは超簡単
main.gopackage main import "github.com/labstack/echo/v4" func main() { e := echo.New() e.Start(":1991") }これでmain.goを実行すると以下のようにwebサーバーが立ち上がる.
APIの実装
それではAPIを作っていく. 今回は
/hello
にGETリクエスト
を送ったらHello World
という文字列がレスポンスとして返ってくるような簡単なAPIを作る.
main.goに数行追加する.main.gopackage main import ( "net/http" "github.com/labstack/echo/v4" ) func main() { e := echo.New() // 追加部分 e.GET("/hello", func(c echo.Context) error { return c.String(http.StatusOK, "Hello World") }) e.Start(":1991") }今回はリクエストに対してDBの操作などをしないので, ルーティングの部分でレスポンスを返しているが, もちろん関数に切り出せる.
それではリクエストを実際に飛ばしてレスポンスが返ってくるか確認しよう. まずはmain.goを実行し, 別のターミナル上で以下のコマンド叩く.curl http//localhost:1991/helloまとめ
書いてから気づいたがechoの公式ドキュメントのQuick Startとやってることが99%一緒だった. 今回は簡単な事しかやらなかったがEchoは色々とカスタマイズができる. ログの表示の仕方とかも自分でいじれたりルーティングのグループ化ができたり... 時間ができたらそういったこともアウトプットしていきたい.
- 投稿日:2019-12-21T16:40:55+09:00
眺めて覚えるGo言語 その3 web server gateway
眺めて覚えるGo言語(Wsgi編)
-Wsgi Webアプリケーションフレームワーク)を接続するための、標準化されたインタフェース定義である
views/layout.tpl<!doctype html> <html lang="ja"> <head> <!-- Required meta tags --> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <!-- Bootstrap CSS --> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous"> <title>{{.}}</title> </head> <body> <main role="main"> <nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark"> <a class="navbar-brand" href="#">Boostrap4</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarsExampleDefault" aria-controls="navbarsExampleDefault" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarsExampleDefault"> <ul class="navbar-nav mr-auto"> <li class="nav-item active"> <a class="nav-link" href="/">Home <span class="sr-only">(current)</span></a> </li> <li class="nav-item"> <a class="nav-link" href="/about">About</a> </li> <li class="nav-item"> <a class="nav-link" href="/contact">Content</a> </li> <li class="nav-item dropdown"> <a class="nav-link dropdown-toggle" href="http://example.com" id="dropdown01" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Dropdown</a> <div class="dropdown-menu" aria-labelledby="dropdown01"> <a class="dropdown-item" href="#">Action</a> <a class="dropdown-item" href="#">Another action</a> <a class="dropdown-item" href="#">Something else here</a> </div> </li> </ul> <form class="form-inline my-2 my-lg-0"> <input class="form-control mr-sm-2" type="text" placeholder="Search" aria-label="Search"> <button class="btn btn-outline-success my-2 my-sm-0" type="submit">Search</button> </form> </div> </nav> {{ template "content" . }} </main> <!-- Optional JavaScript --> <!-- jQuery first, then Popper.js, then Bootstrap JS --> <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script> </body> </html>だだのhtmlです。
- {{ template "content" . }}に注目
- ここに実体を流し込みます。
content.tpl{{ define "content" }} <!-- Main jumbotron for a primary marketing message or call to action --> <div class="jumbotron"> <div class="container"> <h1 class="display-3">Hello, world!</h1> <p>これは、単純なマーケティングWebサイトまたは情報Webサイトのテンプレートです。ジャンボトロンと呼ばれる大きな吹き出しと、3つのサポートコンテンツが含まれています。これを基本として、よりユニークなものを作成してください。</p> <p><a class="btn btn-primary btn-lg" href="https://getbootstrap.com/" role="button">Learn more »</a></p> </div> </div> <div class="container"> <!-- Example row of columns --> <div class="row"> <div class="col-md-4"> <h2>Heading</h2> <p>Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui. </p> <p><a class="btn btn-secondary" href="#" role="button">View details »</a></p> </div> <div class="col-md-4"> <h2>Heading</h2> <p>Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui. </p> <p><a class="btn btn-secondary" href="#" role="button">View details »</a></p> </div> <div class="col-md-4"> <h2>Heading</h2> <p>Donec sed odio dui. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Vestibulum id ligula porta felis euismod semper. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.</p> <p><a class="btn btn-secondary" href="#" role="button">View details »</a></p> </div> </div> <hr> </div> <!-- /container --> {{ end }}コンテンツの中身です。
- {{ define "content" }}.....{{ end }} に注目
- {{ template "content" . }}に流し込ませる
about.tpl{{ define "content" }} <div class="container mt-5"> <h2>{{.title}}</h2> <h3>{{.message }}</h3> <p>この領域を使用して、追加情報を提供します。</p> </div> {{ end }}about用コンテンツ
contact.tpl{{ define "content" }} <div class="container mt-5"> <h2>{{.title}}.</h2> <h3>{{.message}}</h3> <address> 〒100-0001<br/> 東京都千代田区千代田1<br /> <abbr title="Phone">P:03-3213-2050</abbr> </address> <address> <strong>サポート:</strong> <a href="mailto:Support@example.com">Support@example.com</a><br /> <strong>営業:</strong> <a href="mailto:Marketing@example.com">Marketing@example.com</a> </address> </div> <!-- /container --> {{end}}contact用コンテント
main.gopackage main import ( "./mux" "html/template" "log" "net/http" ) func Home(w http.ResponseWriter, r *http.Request) { // テンプレートをパース t := template.Must(template.ParseFiles("views/layout.tpl","views/content.tpl")) str := "Home" // テンプレートを描画 if err := t.ExecuteTemplate(w, "layout.tpl", str); err != nil { log.Fatal(err) } } func About(w http.ResponseWriter, r *http.Request) { // テンプレートをパース t := template.Must(template.ParseFiles("views/layout.tpl","views/about.tpl")) data := map[string]string{ "title": "About", "message": "Your application description page.", } // テンプレートを描画 if err := t.ExecuteTemplate(w, "layout.tpl", data); err != nil { log.Fatal(err) } } func Contact(w http.ResponseWriter, r *http.Request) { // テンプレートをパース t := template.Must(template.ParseFiles("views/layout.tpl","views/contact.tpl")) data := map[string]string{ "title": "Contact", "message": "Your contact page.", } // テンプレートを描画 if err := t.ExecuteTemplate(w, "layout.tpl", data); err != nil { log.Fatal(err) } } func main() { r := mux.NewRouter() r.HandleFunc("/", Home) r.HandleFunc("/about", About) r.HandleFunc("/contact", Contact) http.Handle("/", r) http.ListenAndServe("192.168.1.10:8080", r) }html描画用関数を
- Home
- About
- Contact
テンプレートファイルと関数を用意する。
メイン関数でルータを用意しurlと関数を関係つけする。
- 投稿日:2019-12-21T16:08:43+09:00
ioutil.WriteFile のFileMode の挙動の意味がわからなかったので調べてみた
ファイルを一括で記述したかったので、
ioutil.WriteFile
を使おうと思いました。この第三引数の部分がいまいち理解できていないので調べていました。定義
func WriteFile(filename string, data []byte, perm os.FileMode) errorよくここに
os.ModePrem
とか書くけどあれなによ?というやつです。FileMode
の定義も読んでみます。様々なデバイスファイルやモードが定義されています。単にファイルを書きたいときだと、os.ModePrem
でオッケーで、Unix の0777のパーミッションがファイルに設定されるように見えます。const ( // The single letters are the abbreviations // used by the String method's formatting. ModeDir FileMode = 1 << (32 - 1 - iota) // d: is a directory ModeAppend // a: append-only ModeExclusive // l: exclusive use ModeTemporary // T: temporary file; Plan 9 only ModeSymlink // L: symbolic link ModeDevice // D: device file ModeNamedPipe // p: named pipe (FIFO) ModeSocket // S: Unix domain socket ModeSetuid // u: setuid ModeSetgid // g: setgid ModeCharDevice // c: Unix character device, when ModeDevice is set ModeSticky // t: sticky ModeIrregular // ?: non-regular file; nothing else is known about this file // Mask for the type bits. For regular files, none will be set. ModeType = ModeDir | ModeSymlink | ModeNamedPipe | ModeSocket | ModeDevice | ModeCharDevice | ModeIrregular ModePerm FileMode = 0777 // Unix permission bits )ファイルのパーミッションがうまく動作しない理由
それなら、普通に書きこむファイルのパーミッションを設定できるのでは?と思いしらべてみると、次のように書けます。
package main import ( "fmt" "io/ioutil" "os" "strconv" ) func main() { ioutil.WriteFile("ModePerm.txt", []byte("ModePerm.txt"), os.ModePerm) fmt.Println(os.ModePerm.IsRegular()) perm := "0777" perm32, _ := strconv.ParseUint(perm, 8, 32) ioutil.WriteFile("777.txt", []byte("some"), os.FileMode(perm32)) fmt.Println(perm32) perm = "0644" perm32, _ = strconv.ParseUint(perm, 8, 32) ioutil.WriteFile("644.txt", []byte("some"), os.FileMode(perm32)) fmt.Println(perm32) perm = "0666" perm32, _ = strconv.ParseUint(perm, 8, 32) ioutil.WriteFile("666.txt", []byte("some"), os.FileMode(perm32)) // os.Chmod("./666.txt", os.FileMode(perm32)) }これを実行すると、4つのファイルが生成されて、想定通りのパーミッションが設定されるといいのですが、そうなりません。
ls -l total 4 -rw-r--r-- 1 ushio ushio 4 Dec 21 08:45 644.txt -rw-r--r-- 1 ushio ushio 4 Dec 21 08:45 666.txt -rwxr-xr-x 1 ushio ushio 4 Dec 21 08:45 777.txt -rwxr-xr-x 1 ushio ushio 12 Dec 21 08:45 ModePerm.txt -rw-r--r-- 1 ushio ushio 47 Dec 21 08:11 go.mod -rw-r--r-- 1 ushio ushio 809 Dec 21 08:44 main.goなんでやねん!と思って私のGo師匠であるかえる師匠に聞いてみたのですが、こんなStackOverflowの記事を見つけてくれました。
これを読むと、これは、go の問題ではなく、OS で設定されている
umask
の問題では?との答えがありました。umask
umask() sets the calling process's file mode creation mask (umask) to mask & 0777 (i.e., only the file
permission bits of mask are used),and returns the previous value of the mask.The umask is used by open(2), mkdir(2), and other system calls that create files to modify the permissions placed on newly created files or directories. Specifically, permissions in the umask are turned off from the mode argument to open(2) and mkdir(2).
Umaskは、openやmkdirのシステムコールで使われて、新しいファイルやディレクトリを作るときにパーミッションをモディファイするとあります。ここで、私が今使っているWSLのumaskを見てみましょう。
$ umask 022022 ですね、これをマスクするということは、Unixのファイルパーミッション設定は、4(read) 2(write) 1(execute) なので、022は所有者以外の書き込み権限なので、それでマスクをすると、書き込み権限がなくなることになります。
実際の上の実行例を見てみると、644は正しく設定されているのに、666は、644に変更されています。ModePerm も元が777なのが、755に変更されています。想定どおりのパーミッションを設定したい場合は?
では、想定通りのパーミッションを設定するにはどういう方法がよいでしょう?先ほどのStack Overflow からたどれるWikiで次のような記述があります。
In Unix-like systems, each file has a set of attributes that control who can read, write or execute it. When a program creates a file the file permissions are restricted by the mask. If the mask has a bit set to "1", then the corresponding initial file permission will be disabled. A bit set to "0" in the mask means that the corresponding permission will be determined by the program and the file system. In other words, the mask acts as a last-stage filter that strips away permissions as a file is created; each bit that is set to a "1" strips away its corresponding permission. Permissions may be changed later by users and programs using chmod.
Unixシステムでは、ファイルを作成した時にはマスクで、制限されてるとあります。パーミッションはchmodを使って後で変えられるかもしれないとあります。つまり、想定通りのパーミッション例えば、
0666
を設定したい場合は、os.Chmod("./666.txt", os.FileMode(perm32))を追記すればいいことになります。そうすれば、666に無事設定されていることがわかると思います。
ls -l total 4 -rw-r--r-- 1 ushio ushio 4 Dec 21 08:49 644.txt -rw-rw-rw- 1 ushio ushio 4 Dec 21 08:49 666.txt -rwxr-xr-x 1 ushio ushio 4 Dec 21 08:49 777.txt -rwxr-xr-x 1 ushio ushio 12 Dec 21 08:49 ModePerm.txt -rw-r--r-- 1 ushio ushio 47 Dec 21 08:11 go.mod -rw-r--r-- 1 ushio ushio 651 Dec 21 08:49 main.go
- 投稿日:2019-12-21T15:56:32+09:00
Go言語のGo Modulesを試す。フォルダ配置が柔軟に。
背景
GoにはGOPATHという概念があります。
GOPATHはフォルダ(Unixではディレクトリ)を示しており、自動で設定されます。
https://github.com/golang/go/wiki/SettingGOPATHIf no GOPATH is set, it is assumed to be $HOME/go on Unix systems and %USERPROFILE%\go on Windows.
(USERPROFILEは
C:\Users\Windowsのユーザー名
です)なおGOPATHは変更可能です。
去年までは、Goに関連するファイルはGOPATH以下の置くことになっていました。
つまり、プロジェクトを好きなフォルダに置けませんでした。僕は他の言語も含めて、
C:\Users\Windowsのユーザー名\code
にプロジェクトを置いてますが、ここに置けませんでした。
(GOPATHをこのフォルダに設定すれば置けますが、デフォルトが好きなので変えたくありませんでした)しかしながら、今年リリースされたGo 1.12からGo Modulesというものが導入されました。
(Go 1.11でもオプションで使えます)
これにより、好きなフォルダにファイルを置いていいようになりました。今回はこの機能を試してみます。
クイックスタート
公式を参考にします。
https://github.com/golang/go/wiki/ModulesWindowsで行います。
オプションですがGitHubを使います。まずGitHubにリポジトリを作成します。
https://github.com/hideboh/qiita_go_modulesクローンします。
新しいモジュールを初期化します。
go mod init github.com/hideboh/qiita_go_modul
go.modmodule github.com/hideboh/qiita_go_modules go 1.13が作成されます。
コードを書きます。
hello.gopackage main import ( "fmt" "rsc.io/quote" ) func main() { fmt.Println(quote.Hello()) }ビルドして実行します。
go build -o hello.exe .\hello.exe
こんにちは世界。
が表示されました!
go build
をすると、必要なライブラリを$GOPATH/pkg/mod/
に自動でダウンロードしてくれます。また
go install
をすると、実行ファイルを$GOPATH/bin
にコピーします。
ファイル名は、モジュール名のqiita_go_modules.exe
となりました。
Windowsだとインストール時?に$GOPATH/bin
が環境変数のPath
に設定されるので、コマンドプロンプト等でqiita_go_modules.exe
を打つと実行されます。簡単ですので、これで好きなフォルダで開発ができます。
- 投稿日:2019-12-21T14:17:29+09:00
Goで迷路生成ツールを作る
この記事はtomowarkar ひとりAdvent Calendar 2019の21日目の記事です。
はじめに
以前作成したツールに迷路生成機能を追加してみました。
このように自動迷路を生成する機能と、最短経路を求める機能になります。
迷路生成アルゴリズムは現状棒倒し法と穴掘り法の2種類だけなのでゆるりと拡張していけたらなという感じです。
使い方
ソースコード
go get
go get -u github.com/tomowarkar/biomeコード
package main import ( "github.com/tomowarkar/biome/maze" ) func main() { // 迷路描画フィールドの生成 m := maze.NewMaze(48, 60) var seed int64 // 棒倒し法 m.StickDown(seed) // 迷路を10倍スケールでpng変換 m.ToPng("maze1", 10) // 迷路の最短経路を導出 m.Solve() m.ToPng("route1", 10) // 穴掘り法 m.Digging(seed) m.ToPng("maze2", 10) m.Solve() m.ToPng("route2", 10) }棒倒し法
maze1.png
route1.png
穴掘り法
maze2.png
route2.png
おわりに
明日はGIF画像生成について書いていけたらなと思います。
以上明日も頑張ります!!
tomowarkar ひとりAdvent Calendar Advent Calendar 2019