GocCon2017感想とか

行ったので書く(VimConfの記録は出遅れたのであとでこっそり)。

パネルディスカッション

VimConfに続きmattnさんを招待しての他セッション。

聞きながら取ったメモを貼り付けておきます。抜け漏れは許容していただけると。

Keynote

Digital Oceanのエンジニアであり、vim-goの作者であるFatih氏の発表。

Go+Microservices at Mercari

https://talks.godoc.org/github.com/tcnksm/talks/2017/11/gocon2017/gocon2017.slide#1

メモ : gRPCを触ってみる。

Story of our own Monitoring Agent in golang

Gocon2017:Goのロギング周りの考察

個人的に、Goのロギングは下みたいなLoggerのinterfaceを用意して 使いたいライブラリを薄くWrap、DIすればなんでも良いかなぁ、と思ってるんですがどうなんですかね?

type Logger interface {
    Fatalf(...interface{})
    Errorf(...interface{})
    Warnf(...interface{})
    Infof(...interface{})
    Debugf(...interface{})
}

How to achieve parallel compilation in Go 1.9

Async, Persistent, Fast, and Sable "Enought" Queue/Worker Using Go and PostgreSQL

Diff algorithm in Go

TODO スライドを追加する。

EBITEN

https://docs.google.com/presentation/d/e/2PACX-1vSSbSxPObBZcJHjvUpAt-HEJVLaux2FQBpJbvbxInJgmEhxSn-lVxTVxUMmUNQwtJtC8w6_HkhuW2hk/pub?start=false&loop=false&delayms=3000&slide=id.p

reviewdog and static analysis for Go

https://docs.google.com/presentation/d/1_BWQXamZvIhL3l9ziL9zb25yP9RjpgXoxkWX-48ECss/edit#slide=id.p

今後

込み入ったGoの部分については掘っていないのであんまり凝った話はできませんが、パッケージ構成とかについてはAdventCalendarとかに書いていこうかなと思います。

golang.tokyo #5 感想

4月27日に開催されたGolang.tokyo#5に行ってきた。 GCPUG Tokyoとの共同開催ということで30分枠はGAEでGo言語を使ったお話。

直前に人数を見たら珍しく定員割れをしてましたそれでも100人超えてるあたりGoの注目度の高さが伺える。

GO *GAE

株式会社カブクさんの方の発表(資料未公開)。 GAEの基本的な部分の説明と、実際に業務で使ったときのお話。 Go言語は業務で使っていますが、GCPは触れたことがない勢なので基本的なところを説明していただいてとてもありがたかった。

以下、気になったところ箇条書きで書く。

  • GAEにはStandardとFlexibleの2種類ある
    • Standard Environment(SE)
      • PaaS
      • スピンアップが速い
      • Goは1.6
    • Flexble Environment(FE)
      • Dockerのコンテナを立ち上げる
      • バイナリを扱える
  • 基本はSEを使う
  • バイナリを扱いたいならFEしかない
  • カブク内で使っているところは3D解析のAPI
  • SEとFEを両方合わせて使用している
  • SEだけだと他の3D解析APIのリクエストに60秒以上かかるためタイムアウトする
  • FEだとTaskQueueが使えない

社内でもGCPを使う話はちょこちょこ聞くけど60秒制限はよく引っかかりそうなのでちゃんと覚えておく。 あと、辛かったことで言っていた事と全く同じことを自分もGoを最初に触った時に思っていたのでわかりがあった。

GAE/GOの勘どころ

株式会社ソウゾウの@osamingoさんの発表。

以下、気になったところ箇条書きで書く。

  • CloudSQLの最大同時接続数が12(Master,Slave合計で)
  • Vendoringは出来るらしい(質疑応答より)
  • gb
  • direnv
  • Deployもよしなに
  • Monitoringも楽
  • GCPは運用が楽
  • GCPは運用が楽(重要)

GCPでの細かいハマりどころとかツール等々。 AWSの運用めんどくさくない?と最近気づき始めたのでGCPの運用が楽という話を聞いて触りたくなった。

Datastore/Go のデータ設計と struct の振る舞いについて

ここからLT枠。 @pospomeさんのDatastoreでの設計のお話。

設計の話は特に好きなのでfmfmと思いながら聞いていたが、@vvakameさんの

というツイートを見てなるほど自動生成系ツールもあるのかという知見を得た。

となると@__timakin__さんもツイートしていたように、サービスロジックで扱う構造体とDatastoreで扱う構造体を分離するのが、 ベターかなぁという感じがした。

Contextアンチパターン

神(Context)を殺した話。

面白かった(小並感)。

自分ではまだContext.Valueを使っていなかったが、使う時には気をつける。

あと、現在自分のチームではechoを使っているが、スライドの内容と同じように殆どRoutingにしか使ってないんだったら少し重い。 なので、最近は、個人でWebのものを書くときとかプロダクトから少し離れたツールを書くときは httprouterを使っている。

ASTライブコーディング

@tenntennさんのASTライブコーディング。

以下のコードから文字列のhoge変数だけを取り出すコードを10分で書いていた(すごい)。

package main

import (
    "fmt"
)

func main() {
    var hoge = "hoge"
    fmt.Println(hoge)

    func() {
        var hoge = 0
        fmt.Println(hoge)
    }()
}

触ろう触ろうと思ってastは全然触っておらず、いい機会でもあったので自分でも書いてみた。

https://gist.github.com/nametake/1fd82014bdfc434a32aba6590c4c66bb

(@tenntennさんがやっていたやり方とは違う気がするけど取り出せてるからセーフ)

astを触ったことがなくてもast.Printの内容を見ていれば何となくどうなっているのかわかって、思っていた以上に楽ちんに書けた。

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の内部は非常にシンプルな実装になっていたので、気になる人は内部の実装をちゃんと見ることをおすすめする。