pkg/erorsを使ったGo言語でのエラーハンドリング
tl;dr
- エラーを返すときには
pkg/errors
のerrors.Wrap
でラップして返すとエラーの原因を返せる - エラーを受け取るときには
pkg/errors
のerrors.Cause
で原因を見れる
前置き
以前の記事の
「エラーをチェックするだけでなく、それらを正常に処理しなさい」の項目の話を知ってから、
業務で書いているコードでも以下のようにバシバシfmt.Errorf
を使っていた。
package main import ( "fmt" ) func foo() error { err := bar() return fmt.Errorf("foo error: %v", err) } func bar() error { err := buz() return fmt.Errorf("bar error: %v", err) } func buz() error { return fmt.Errorf("buz error") } func main() { err := foo() if err != nil { fmt.Println(err) return } }
「俺ちゃんとエラー処理してる」感を感じたのも束の間、以下のコードような課題に直面した。
package main import ( "fmt" ) func foo() error { if err := bar(); err != nil { return fmt.Errorf("foo error by bar: %v", err) } if err := fatal(); err != nil { return fmt.Errorf("foo error by fatal: %v", err) } return nil } func bar() error { return fmt.Errorf("bar error") } func fatal() error { return fmt.Errorf("FATAL") } func main() { err := foo() if err != nil { fmt.Println(err) return } }
一見特に問題のあるコードには見えない。しかし、エラーを受ける側が発生したエラーを判断して処理を 変更したい時に非常に困ることになる(実際困った)。
上記のコードの例だと、mainでfoo関数を実行して、そのエラーの内容を出力している。 もしエラーが発生したときは以下のように出力され、どこでどのエラーが発生したのかを知ることはできる。
foo error by bar: bar error
しかし、「foo関数で発生するエラーはそこまで致命的ではないので、翌日の朝にログとして検出できれば良いが、 fatal関数で発生するエラーは致命的で、即アラートを上げたい」というような状況が起こると少し面倒なことになる。
何が問題か
Goのエラーハンドリングの方法として、以下のように自分でエラーを定義してswitch
で処理を切り替えるという方法がある。
package main import ( "errors" "fmt" ) var ( errBar = errors.New("bar error") errFatal = errors.New("fatal error") ) func foo() error { if err := bar(); err != nil { return errBar } if err := fatal(); err != nil { return errFatal } return nil } func bar() error { return errBar } func fatal() error { return errFatal() } func main() { err := foo() switch err { case errBar: fmt.Printf("log: %v\n", err) case errFatal: fmt.Printf("Aleart! : %v\n", err) default: fmt.Println(err) } }
上記のコードの例だと、foo関数はエラー発生時に自作のエラーを返している。 外で定義されているエラーを返しているため、mainはそれをswitchで比較して動作を切り替えることができている。 しかし、この方法だとbar関数やfatal関数で発生したエラーの内容を切り捨てることになる。 もしfoo関数内で複数のエラーが発生する可能性があるとそれらも無視することになってしまう。
「そこでfmt.Errorf
を使えば」と思うが、実はfmt.Errorf
を使うとswitchで比較ができなくなる。
fmt.Errorf
の内部を見てみると、標準パッケージのerrors.New
のラッパーになっている。
そのerrors.New
の中身はerrorString
構造体にメッセージの内容を格納して
返している(ソース)。
つまり、fmt.Errorf
を経由すると、全てのエラーは新たに定義されたerrorString
に変わってしまうことになり、
自分で定義したエラーとは比較ができなってしまう。
どうやって解決したか
エラーの文字列で判別するのかな?、とも一瞬思ったが、調べたらスマートなやり方があった (参考1)。 標準のerrorsパッケージに則ったライブラリのgithub.com/pkg/errorsを 使うと、発生したエラーをハンドリング元まで返すことができるようになる。
package main import ( "fmt" "github.com/pkg/errors" ) var ( errBar = errors.New("bar error") errFatal = errors.New("fatal error") ) func foo() error { if err := bar(); err != nil { return errors.Wrap(err, "handling bar errror") } if err := fatal(); err != nil { return errors.Wrap(err, "handling fatal error") } return nil } func bar() error { return errBar } func fatal() error { return errFatal } func main() { err := foo() switch errors.Cause(err) { case errBar: fmt.Printf("log: %v\n", err) case errFatal: fmt.Printf("Aleart! : %v\n", err) default: fmt.Println(err) } }
foo関数内でエラーが発生した際、errors.Wrap
に発生したエラーとメッセージを渡して返している。
errors.Wrap
を使用して作成したエラーは、errors.Cause
を使用して、一番最初にerrors.Wrap
されたときの
エラーを取り出すことが出来る。
そのため、foo関数で発生したエラーは、bar関数とfatal関数のどちらが原因なのかをmain関数で取り出すことが出来るようになり、
エラーの内容によって処理を切り替えることができるようになる。
pkg/errorsの内部実装を見てみると、errors.Wrap
を使用してエラーを作成する時に、渡されたエラーを保持する構造体を
返すようになっていた。そのため、errors.Wrap
を使用している限りは最初に発生したエラーは消えること無く、
errors.Cause
で取り出せるようになっている。
pkg/errorsの内部は非常にシンプルな実装になっていたので、気になる人は内部の実装をちゃんと見ることをおすすめする。
Go Proverbsを勉強がてら和訳して少し解説した
Go (その3) Advent Calendar 2016 11日目の記事です。
Go言語の生みの親、Rob Pikeが2015年のGopherfestのセッションで 言っていた、Go Proverbsを自分の勉強がてら和訳してちょっと解説してみる。
後半に行くに連れて集中力が切れたり、いろいろ調査不足のところや、認識違いの部分もあると思うので、
そういう部分のところがあったら是非指摘してください。自分でも気づいたら修正していきます。
ちなみにタイトルの「Go Proverbs」は、動画の冒頭で囲碁の話をしているとおり「碁の格言」の直訳で、 「Goの格言」にかかっていておしゃれ。
メモリを共有して通信したり、通信してメモリを共有してはならない
原文は"Don't communicate by sharing memory, share memory by communicating."
これはGoでのメモリ共有モデルのお話。 (参考:https://blog.golang.org/share-memory-by-communicating)
CやJavaではメモリ空間(変数)を共有することでプロセス間で値をやり取りします。 しかし、正しく排他制御を行わないとデータ破壊が発生する可能性があります。 また、次のプロセスに情報を渡すときには現在行っている処理が終了したことを次のスレッドに伝える仕組みを用意する 必要もあります。(この仕組みを実装するためにJavaではConditionインタフェースが用意さています) これらの実装を間違うと、デッドロックやデータ破壊が発生してプログラムが正常に動作しなくなってしまいます。
Goではプロセス間の通信をchannelという仕組みを用意して、 メモリを共有するのではなく、情報(メッセージ)を送受信することで実現しています。 channelは以下のようにデータをやり取りします。
ch := make(chan int) // channelの作成 ch <- 1 // 書き込み i := <- ch // 読み込み
この時、channelへの書き込みと読み込みはGo本体が自動的に排他制御を行っているため、 プログラムを実装する側が排他制御を実装する必要はありません。 また、以下のように読み込みの時にそのチャンネルに何も書き込まれていないと、その部分でプログラムはブロックします。
package main import ( "fmt" "time" ) func main() { ch := make(chan int) go func() { time.Sleep(3 * time.Second) ch <- 1 // 3秒待ってからchannel書き込み }() i := <-ch // 読み込み時には何も書き込まれていないのでブロックする fmt.Println(i) // 3秒後に実行 }
これにより、次のスレッドに現在行っている処理が終了したことを伝えるには、channelにデータを書き込むだけで よくなります。
このあたりのことを更に詳しく知りたい場合は、WebDBで牧さん(@lestrrat)が非常に詳しく解説している記事が あるので、そちらを読むことおすすめします。
並行と並列は違う
原文は"Concurrency is not parallelism."
「並行処理」と「並列処理」は、どちらも同時に処理を実行するという日本語ですが、意味合いが異なります。 このあたりはRob PikeがHerokuのWaza conference2012 (https://talks.golang.org/2012/waza.slide)で解説をしています。 (あと、先日Go初心者LT会で発表したりしました。リンク)
「並列処理」は同じ処理を行うプロセスを同時に実行している状態を指し、 「並行処理」は別の仕事をするプロセスを同時に実行している状態を指します。
Rob Pikeのスライドの14ページ目の図と 19ページ目の図との対比がわかりやすいと思います。 14ページの図のように、ドキュメントの山や焼却炉を複数のプロセスでアクセスしようとすると、ロックが発生したり プロセス同士を同期させる必要があり、それらがボトルネックになってしまいます。 しかし、19ページの図のように、1つのプロセスが1つのリソースにアクセスするようにプロセスを立ち上げると、 それぞれのプロセスが干渉しなくなり、無駄なく処理を続けさせることができます。
Go言語が持っている並行処理の機能はこれらを簡単に実現できるため、高速化の必要な処理を書くときは覚えておきましょう。
チャンネルは排他的で直列になるように調整する
原文は"Channels orchestrate; mutexes serialize."
Goのチャンネルは特に何もしなくてもで排他的かつ直列にデータを送れるようになっています。 そのため、プログラマがgoroutine間でデータのやり取りをする際、ロックや順番等をあまり気にせずに使用することができます。
大きなinterfaceは抽象度が低い
原文は"The bigger the interface, the weaker the abstraction."
これはタイトル通りで、interfaceの実装はなるべく小さくしましょう(言い聞かせ)。 小さいinterfaceで設計できていると構造体がinterfaceを満たすのが非常に容易です。 実際にGoのデフォルトパッケージにあるinterfaceは非常に小さく設計されています。
具体的にGoが標準で用意しているWiterを実装して、人に喋らせてみます。
package main import ( "fmt" "io" "os" ) type human struct { w io.Writer } func (h *human) Write(p []byte) (int, error) { p = append([]byte("Ω< "), p...) h.w.Write(p) return len(p), nil } func main() { h := &human{w: os.Stdout} fmt.Fprintln(h, "hello world") }
0値を有益にしなさい
原文は"Make the zero value useful."
Goでは値が宣言された段階で初期化されます。 この機能自体は最近の言語だとよくあることだと思うのですが、 重要なのは0値に意味に対してちゃんと意味を持たせておくことだと思います。
Goでは構造体生成時その内部のプライベートな値も全て0値で初期化されます。 しかし、Javaのコンストラクタの様に、使う側に必ず初期値を指定させる方法がありません (Newで始まるメソッドを作成することはありますが)。 そのため、初期化するための関数やNew関数を経由せずに呼び出されること困るようなコードは書くべきではなく、 必ず0値で初期化されたときのことを考慮してコードを書きましょう。
インターフェース型は何も言わない
原文は"interface{} says nothing."
Goではどんな値でも入れることの出来るとしてinterface{}型があります(正確にはメソッド定義のないinterfaceだから どんな値でも入れられる)。 引数の型等に仕様するとどんな型でも受け取れるようになり、汎用的なものになる気がするのですが、 Goの1つの特徴である、静的型付け言語という特性を捨てることになります。 interface{}型が使用されている部分で、正しく型アサーションや例外処理等が行われていれば問題ないのですが、 それが行われていない時にはコンパイル時にバグが発生することを察知できません。 何でも渡したい時には、もしかしたらMap型の方が適切だったり、自ら構造体を定義しておいたほうが余計なバグが 減らせる可能性が高いので、なんでもかんでもinterface{}型で引数を受け取るようにするのはやめましょう。
Gofmtのスタイルは誰かのお気に入りではないが、誰もがお気に入りだ
原文は"Gofmt's style is no one's favorite, yet gofmt is everyone's favorite."
必ず
go fmtを
しましょう
小さいコピーは小さい依存より良い
原文は"A little copying is better than a little dependency."
ある程度Goを書いていると、再利用をするためにライブラリ化をしていくと思います。 しかし、再利用が目的になることは避けないといけません。 再利用するためにライブラリ化してそれを使うということは、そのライブラリに依存するということになります。 Rob Pikeは講演で、表示のためのライブラリが巨大なUnicodeのパッケージに依存してしまう例を上げていました。 その時はそれを避けるために必要な部分をコピーして、Unicodeのパッケージに依存することを避けた、と言っています。
普段開発をしていて、流石にUnicodeレベル大きさのパッケージに依存することはあまりないとは思いますが、 依存することでテストが重くなったりするのであれば、敢えてライブラリをimportせず、ソースコードをコピーすることも検討しないと いけないのかもしれません。
システムコールは常にビルドタグで保護されるべきだ
原文は"Syscall must always be guarded with build tags."
Go言語はクロスコンパイル言語です。そのため、1つのコードが複数の環境で動くように心がけないといけません。 Goだけで書かれたパッケージを使用している場合は特に意識することはないと思います。 しかし、特定のアーキテクチャや環境に依存するシステムコールを利用する場合は必ずビルドタグで保護しましょう。 ビルドタグで保護することで、コンパイル時にその環境では動かないことを示すことができます。
Goの公式のページにも書いてある通り、 以下のようにファイルの行頭にビルドタグをつけることで、そのファイルがビルドできる環境を指定することができます。 他にもファイル名で指定する方法もあるようです。
// +build linux darwin
CGoは常にビルドタグで保護されるべきだ
原文は"Cgo must always be guarded with build tags."
これもシステムコールの理由と同じですね。 Cgoも同様にビルドタグで制御できるので、CでコンパイルされたものをGoで使用するときには必ずビルドタグを保護しましょう。
CgoはGo言語ではない
原文は"Cgo is not Go."
(自分はcgoを扱ったことがないのでここはちょっと荷が重い……) cgoはGo言語からC言語を扱うための機能です。しかしここではcgoを使うのであれば、それは既にGoではないと言っています。 理由としては以下のような物があげられるようです(参考:cgo is not Go)。 * 遅いビルド * 複雑なビルド * クロスコンパイルではなくなる * パフォーマンスの問題 * 呼び出すCが別の何かに依存していることがある * 配布時の展開の複雑さ
なにか必然的な理由(参考先ではグラフィックドライバやウィンドウシステム)がない限り、なるべく使うべきでは ないのかもしれません。
安全ではないパッケージでは保証はない
原文は"With the unsafe package there are no guarantees."
このあたりは自明なことかもしれません。 外部パッケージを使用する時にはそれが本当に安全で使いやすいものかを確認しなければいけません。 もし安全でないものを使っていると、glide upする度にコードが動かなくなることもあります(実体験)。
見やすい実装は巧みな実装より良い
原文は"Clear is better than clever."
これもある程度自明かもしれません。 Go言語の設計思想として、「読みやすく」という物があります。 それを実現するのにより見やすい実装が書けるのなら、それを採用するべきであるし、それがGo言語の哲学にもなっています。
リフレクションは決して綺麗なものではない
原文は"Reflection is never clear."
リフレクションというのは、コードを動的に操作することです。 リフレクションを使うと、プライベートの値を外から直接弄ったりメソッドの実装を置き換えたりと割と闇なことができます。 ライブラリを作る側がある程度汎用的にするために使用することはあっても、普段から使うのは避けるべきでしょう。
エラーは値だ
原文は"Errors are values."
参考:Errors are values Go言語の特徴の1つにエラーを値として扱う、というものがあります。 「エラーハンドリングをifでやるんでしょ?」と思い以下のようなコードを量産しがちですが(自分も最近理解した)、本質は違います。
if err != nil { }
Goのエラーは「値として扱えるから、ロジックで処理ができる」というのが本質になります。
参考サイトからそのままソースコードを借りてきます。 例えば、Goを書いていると以下のようなエラーハンドリングはよく発生します。
_, err = fd.Write(p0[a:b]) if err != nil { return err } _, err = fd.Write(p1[c:d]) if err != nil { return err } _, err = fd.Write(p2[e:f]) if err != nil { return err } // and so on
おそらくGoを書いたことのある人は自分で書いたこともあるし、他人が書いたのを見たこともあると思います。 これでも確かに動作はしますが、明らかに冗長なコードではあります。 そこで、エラーは値であることを利用して簡単なロジックをプログラムを作成し、きれいにハンドリングしてみます。
まず、以下のような構造体とメソッドを用意します。
type errWriter struct { w io.Writer err error } func (ew *errWriter) write(buf []byte) { if ew.err != nil { return } _, ew.err = ew.w.Write(buf) }
errWriterはWriterを実装したものと、errorを持ちます。 writeメソッドは、byteのスライスを受け取りそれをWriter interfaceが持つWriteメソッドに書き込みます。 その時、値として発生するエラーを構造体の中に保持しておき、writeメソッドが使われる度にその保持したエラーを見て 処理するかどうかを決めています。
上記の構造体を使用することで、先程の冗長な処理を以下のように書き換えることができます。
ew := &errWriter{w: fd} ew.write(p0[a:b]) ew.write(p1[c:d]) ew.write(p2[e:f]) // and so on if ew.err != nil { return ew.err }
この手法が絶対的に正しいわけではないですが、ここで重要なのはエラーをプログラムの中で処理できるということです。 エラー処理をするときには脳直でif文を書くのではなく、値として処理できることも覚えておきましょう。
エラーをチェックするだけでなく、それらを正常に処理しなさい
原文は"Don't just check errors, handle them gracefully."
参考:Don’t just check errors, handle them gracefully
同じくGo言語のエラーの話です。
こちらも参考サイトのソースコードを借りてきます。
以下のようなコードがあったとします。
func AuthenticateRequest(r *Request) error { err := authenticate(r.User) if err != nil { return err } return nil }
これ自体は単純で、authenticateメソッドが返すエラーを上のメソッドに返しているだけです。 こちらもありがちなコードだと思うのですが、もしauthenticateでエラーが発生し、 上にメソッドを返した時に、どこでエラーが発生しているのかを判別する手段がありません(参考サイトを見た時に画像で笑った)。
つまり、ただエラーチェックをして、その値をただ上に渡しているだけだと、どこで何が発生したのかわかりにくくなってしまいます。 Goではfmtパッケージで文字列を整形した上でエラーを作成するメソッドが用意されています。 それを利用して、以下のように、どこでエラーが発生したのかを明確にして上に返してあげないといけません。
func AuthenticateRequest(r *Request) error { err := authenticate(r.User) if err != nil { return fmt.Errorf("authenticate failed: %v", err) } return nil }
デザインはアーキテクチャ、名前はコンポーネント、ドキュメントは詳細
原文は"Design the architecture, name the components, document the details."
これもタイトル通りだと思います(ちょっと抽象的だったので理解できていない)。 幼稚園児レベルのヒアリングとGoogleの字幕機能を駆使してなんとか元動画の英語を 理解しようとした結果、
構成要素の詳細を示すアーキテクチャをデザインし、それを構成するコンポーネントに名前をつける。 コンポーネントの名前が適切であれば明確かつ自然にプログラムが書けるが、 説明しなければならない詳細についてドキュメントを書きましょう。
という感じで理解しました。
解釈が間違っていたり、こういう意味だよというのお待ちしています。
ドキュメントは使う人達のために
原文は"Documentation is for users."
よくプログラムにはドキュメントを書け、という話がありますが、それが本当に意味のあるドキュメントかどうかを 意識しましょう、ということです。 その関数が引数に何を取り何を返すのかをただ書くのではなく、その関数がどのような意図で作られて、 どのような意図でその処理をするのかを書くべきだということです。
これは会社でプログラムを書くようになって強く実感するようになりました。 Goに限らず、という話ですかね。
panicさせるな
原文は"Don't panic."
Effective GoのErrosの項目を読みましょう、というお話。 Goにはエラーを発生させる方法として、panicという関数が存在します。 プログラムを継続的に実行することが難しくなった時に発生するエラーです。 エラーを値として扱うものとは大きく違い、その場でプログラムが停止してしまいます。 安全にエラーを返す方法があるのであれば、間違いなくそちらを使うべきです。
Go言語LT大会! 「最近、Go言語始めました」の会でLTしました
Go言語LT大会でLTしてきました。
資料です。
見る人が見ればわかっちゃうんですが、Rob Pikeの発表を丸パクリLT用に焼き直して、デザインパターンとか絡めたものです。
もっと詳しく知りたい方は、Concurrency is not Parallelismとか Concurrency is not parallelism - The Go Blogとかを見ると良いと思います。 初めてみたときは結構感動しました。
LT上では時間の関係上スレッドという単語を使っていましたが、正確にはgoroutineはスレッドとは異なるものです。 その辺の説明はWEB+DB PRESS Vol.95とかにわかりやすい説明が載っているので、 詳しく知りたい方は読んでみると良いかもしれません。
最近Go言語の波が来ている気がするのでその波に乗っていきたい所存。 (そしてやっぱり実年齢を言うと驚かれるので、見た目の若々しさを手に入れたい)
VimConf2016行ってきました
VimConf2016に行ってきました。
発表資料のまとめと軽く所感をば書いてみます。
Introduction to Vim 8.0
2016年9月にリリースされたVim8.0の新機能紹介とVimの歴史についてのスライドでした。
Vimのコントリビュータの上位10人に日本人が5人含まれている、という情報が一番驚きでした。 最近上司に、「どうして最近Vimは流行ってるの?」みたいなことを聞かれましたが、 この事が無関係では無い気がします。
Vim as the MAIN text editor
他のエディタからVimに移行したお話。
Vimを使っていると、よく「教えて」と言われるので、質問であったアンチパターンは 他人に教えるときのアンチパターンにしていこうと思います。
Denite.nvim ~The next generation of unite~
https://gist.github.com/Shougo/7c78b3a1725f70c1435d004ed14f2558
Vim界のアイドル兼暗黒美夢王ことShougoさんの発表。
VimのFold機能を利用して、Markdownをスライド代わりにするという発表方法でした。
一応説明しておくと、マークダウンファイルは下書き用でプレゼンファイルを最初作る予定だったんですが、denite.nvim の開発で力つきたのでマークダウンファイルでそのまま行くことにしました。#vimconf2016
— 暗黒美夢王(deoplete dev) (@ShougoMatsu) 2016年11月5日
denite.nvimの簡単な説明、Unite.vimがなぜ遅いのか、denite.nvimはVim8.0で動くのか、おまけ、という構成でした。
個人的注目ポイントは、おまけでShougoさんがおっしゃっていた、既存のShougoさん作のプラグイン (neocomplete, neosnippet, vimfiler, etc...)をDark poweredで書き直す(かも)と言っていたことですかね。
Go、C、Pythonのためのdeoplete.nvimのソースの紹介と、Neovim専用にpure Goでvim-goをスクラッチした話
http://go-talks.appspot.com/github.com/zchee/talks/vimconf2016.slide
個人的に非常に熱かった発表。
内容としてはdeoplete.nvim用に既存の補完ツールを書き換えた、というお話だったのですが、 deoplete-goでDelveの機能を使えるのが最高すぎました。
これでGoをIntelliJで書いてる派の人たちにデバッグのことで胸を張っていけます。 (ちょっと中身を見てみようかと思ってる)
エディタの壁を越えるGoの開発ツールの文化と作成法
Go界隈で有名なtenntennさんの発表。
Goの文化とエディタのプラグイン作成の親和性がテーマでした。
懇親会でも、Shell ScriptのフォーマッタがGoで書かれていたという話をしたので、 Goでプラグインやツールを作る文化ががっつり来ている気がします。
vim-mode-plus for Atom editor
http://qiita.com/t9md/items/0bc7eaff726d099943eb
vim-mode-plusしゅごい(小並感)。
恥ずかしながらテキストオブジェクトとオペレータの組み合わせ操作を初めて知ったので、 今後はそれらも意識して使って、手癖にしていきたい。
vimの日本語ドキュメント
https://drive.google.com/file/d/0ByQIX4Ls1SHlZ3JtOWxHUUkwaDA/view
Koronさん(kaoriyaさん)の日本語ドキュメント翻訳の話。
https://github.com/vim-jp/vimdoc-ja https://github.com/vim-jp/vimdoc-ja-working
Vim script parser written in Go
https://docs.google.com/presentation/d/1A6_A7XzPoHv_wG5N_R6zbgYKBX2ycii6BCzR-7b-nOw/pub
VimのパーサをGoで書いたお話。 将来的にはフォーマッタのような機能も実装されるそうです。呼び方はvimfmt(ゔぃむふむとぅ)なんですかね?
僕の友達を紹介するよ
https://aiya000.github.io/Maid/my-vim-friends/my-vim-friends#/
気になったのは * vim-alignta * undotree * vim-textobj-indent
aligntaはalignとの比較をしてみて良さそうだったら乗り換えてみようと思います。 vim-textobj-indentは、テキストオブジェクトとセットで覚えていこうかと。
Best practices for building Vim plugins
https://gist.github.com/thinca/785171e327e66c48d2d293690dc2f65a
Vimのプラグインを作る時に気をつけてほしいことのまとめ記事でした。
:help desgin-documented
を熟読しようと思います。
全体所感
後半に行くに連れて集中力が下がっていってるのが伝わってしまう
Vimを使い始めて3年弱ですが、まだまだVim弱者なので、得た情報を糧にしていこうと思います。 あとは、会場でもGoを書いている人が多く、VimだけじゃなくてGoの強さも感じました。
とりあえずdeoplete-goが使いたすぎるのでnvim移行を開始します。 zcheeさんと少しお話をして、中身がほぼGoだということも聞いたので、 使いながら中身も見てみようと思います。
Vimmerの集まりに参加するのは初めてだったのですが、非常に濃い内容で楽しかったです。 発表者の方々と会場の準備をしてくださった方々に感謝です。
ブログ書いたので私のVimConf2016はこれにて終了です。
(直接会って話していても新卒に見られたことが無いので大丈夫だと思います)
vimで折りたたみの状態を保存する方法
vimを使ってそれなりに経つのに、最近になって設定したのでメモ。
vimにはmkview
という、現在の折りたたみの状態を保存するコマンドと、
loadview
という保存した折りたたみ状態を読み込むコマンドがある。
もちろん自分でコマンドを入力するのはめんどくさいので、 ファイルを保存した時と読み込んだ時に勝手にコマンドを実行して欲しい。
ということでautocmd
を使って、勝手に実行してくれるようにするわけだが、
先人が色々なことを考慮した上で mkview
とloadview
を自動呼び出しする設定を
公開してくれている。
細かい説明もリンク先に書いてあるので、自分はリンク先の設定をそのまま以下のようにコピペしただけ。
" Save fold settings. autocmd BufWritePost * if expand('%') != '' && &buftype !~ 'nofile' | mkview | endif autocmd BufRead * if expand('%') != '' && &buftype !~ 'nofile' | silent loadview | endif " Don't save options. set viewoptions-=options
1つ躓いたところとして、Go言語を開いて保存したときには、その時点での折りたたみが保存されずに 全て折りたたまれる、という現象が起きた。
少し探ってみると、vim-goの設定で、保存時に自動でfmtをかける機能がONなのが原因だった。
ということで、vim-goの設定の一部を以下のように変更して、Fmt
コマンドを自動で行ってくれる設定を追加した。
" Not working " let g:go_fmt_autosave = 1 let g:go_fmt_autosave = 0 autocm BufWritePre *.go silent Fmt
これで大規模なコードをvimで見てもストレスなくコードが書けるように!
機械学習の環境をDIGITSとDockerを使って簡単に整える話
機械学習が何をやっているのか全くわかっていなくても 簡単に画像処理の機械学習を行える環境を整えた話。
この記事の目標は、何もわからなくてもとりあえず画像を機械に学習させる、です。 (細かい部分が全くわからないことの言い訳)
今回は、Digitsという、NVIDIAが作っている Caffeという機械学習ライブラリのWebアプリを使ってみる。 また、めんどくさい導入部分を全部Dockerにやってもらう。
事前準備
- docker-machine導入済み
- docker-compose導入済み
docker-machineでdefault
という名前のマシンを作ってある前提で進めます。
docker-machineってなんだよって人は 公式ドキュメントのDocker Machineの項目を 読めばいいと思う。
Digitsが動いているコンテナを立てるまで
以下のdocker-compose.ymlファイルを適当なディレクトリに配置し、
同じ階層でdocker-compose up -d
を実行する。以上。
実行が終わったらdocker-machine env default
コマンドで出てくるIPに
ポート5000でアクセスすればWebアプリが動いているのが確認できるはず。
これだけで画像の学習させる環境ができちゃう。便利。
# docker-compose.yml digits: image: kaixhin/digits ports: - "5000:5000" volumes: - ./images/:/mnt/volumes/images
Docker HubにDigitsのコンテナイメージが あったので、ありがたく使わせて頂いた。
コマンドを実行した時に作られるimages
ディレクトリは、
後で学習する画像ファイルを設置する場所になるのでそのままにしておく。
もし作成されてなかったら、images
ディレクトリを作成してから
もう一回docker-compose up -d
。
学習させてみる
とりあえず、右上の「ログイン」ボタンからログインしておく。 出てきたフォームに適当に名前を入れるだけでOK。
データセットの作成
学習させるためには、データセットが必要なのでまずはそれの作成から。
自動で作成されたimages
の中に、学習させたい画像をディレクトリ毎にわけて設置する。
例としてオクスフォード大学で公開されている
ペット画像のデータセットを使ってみる。
上記リンク先の「Dataset」から、圧縮されたペット画像をダウンロードして展開。
展開すると、images/
ディレクトリとその下に37種類のペットの画像ができるはず。
展開された画像を、以下のようなディレクトリ構造になるように配置する。
ちなみに、全種類の動物を使うと非常に重いので、 お試しの人は、3、4種類ぐらいを配置すればいいと思う。
docker-compose.yml images ├── Abyssinian │ ├── Abyssinian_1.jpg │ ├── Abyssinian_2.jpg │ ├── Abyssinian_3.jpg ~ ~ ~ │ ├── Abyssinian_197.jpg │ ├── Abyssinian_198.jpg │ └── Abyssinian_199.jpg ├── Bengal │ ├── Bengal_1.jpg │ ├── Bengal_2.jpg │ ├── Bengal_3.jpg ~ ~ ~ │ ├── Bengal_197.jpg │ ├── Bengal_198.jpg │ └── Bengal_199.jpg
こうすることで、それぞれのペットの画像がディレクトリ名の
Abyssinian
やBengal
といったラベルごとに分類されることになる。
次に、分類した画像からCaffeに流し込むためのデータセットを作成する。
下の画像のように、「New Dataset」の「Images」から、「Classification」を選択。
「New Image Classification Dataset」というページに飛んだら、 下図の赤丸の部分を同じように編集する。
編集をしたら、「Create」ボタンをクリック。 クリックしたら別ページに飛んでデータセットが作成され始める。
ダウンロードしたペット画像全部を使っていると ちょっと時間がかかるので少しだけ待つ。 飛ばされたページを定期的に更新すれば、今どれぐらい終わったかがわかる。
終わるまで待ったらデータセットの作成は終了。簡単!
モデルの作成
データセットができたら、それを学習したデータモデルを作成する。
データセットを作成した時の横にある「New Model」から、 同じようにClassificationを選択する。
以下の様な画面が出てくるので、赤丸の部分を同じように設定。 設定が終わったら名前をつけて「Create」をクリック。
クリックすると、また別ページに遷移して学習が始まる。 終わったら学習終了。簡単!
学習結果を試す
モデルデータを作成する時に飛ばされるページの下部に、下図のようなフォームがある。
図中の「Test a single image」の「Upload image」から識別させたい画像を選択して、 「Classify One」をクリック。
クリックすると別ページが開き、学習させたモデルから選択した画像が何に近いのかを、 教えてくれる。
下図だと柴犬ちゃんがしっかり「shiba inu」と認識されている。すごい。
DigitsのRest API
なんとDigitsではRest APIが実装されている。
使い方はissueに書いてある通り。
コレを使えば割と簡単に学習結果をアプリケーションに反映させられる。