読者です 読者をやめる 読者になる 読者になる

fish-shellのpecoプラグインをforkした

最近fishに乗り換えた。

pluginはfishermanで管理するようにしているのだが、そうなるとoh-my-fish/plugin-pecoの以下の点が気に食わなかった。

  • fish_user_key_bindingsに自分でbind設定を書く必要がある
  • pecoのlayoutがbottom-up

fishermanを使っているのであればfish_user_key_bindingsになるべく自分設定を書き込みたくなかったので、forkしてnametake/plugin-pecoを作った。

Go言語でのDispatcher-Workerパターン実装メモ

最近Dispatcher-Workerパターンをよく書くので、パターンとして書き残しメモ。

gist510b34bb8963615e88da847b833c05f7

JAWS DAYS 2017行ってきた

OSCも捨てがたかったけど最近業務でAWSを触っていたのもあって、JAWS DYASに参加してきた。

完全に遅刻気味だけどとりあえず自分が見たセッションについてスライドと内容をまとめてみる。

続きを読む

社内でGo言語勉強会をやった

社内で別チームがGo言語を採用するかも、とのことで、自分たちのチームがGoの知見0から開発をして、先に知っておけばよかったこととか、別言語経験者がハマりそうなところとかをスライドにして話しました。

間違っているところとか、これから始める人にこの表現は不適切だ、みたいなところとかがあればどんどん指摘してください。

「ごーげんごちほーへようこそ!」というクソコラが入ってたんですが色々まずいかと思って外しました

pkg/erorsを使ったGo言語でのエラーハンドリング

tl;dr

  • エラーを返すときにはpkg/errorserrors.Wrapでラップして返すとエラーの原因を返せる
  • エラーを受け取るときにはpkg/errorserrors.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言語の波が来ている気がするのでその波に乗っていきたい所存。 (そしてやっぱり実年齢を言うと驚かれるので、見た目の若々しさを手に入れたい)