覚えたら書く

IT関係のデベロッパとして日々覚えたことを書き残したいです。twitter: @yyoshikaw

golang.tokyo #6 に行ってきたよ #golangtokyo

golang.tokyo #6 に参加してきました。

以下自分用のメモです。


概要

  • 開催日: 2017/6/1
  • 場所: 株式会社ディー・エヌ・エー


Gopher Fest 2017 参加レポート

上田拓也 氏 (メルカリ/ソウゾウ)

  • Gopher Festとは?
    • Google I/O に合わせて開催されるイベント
    • 5/15にサンフランシスコで開催
  • The State of Go
    • Goの開発状況
      • Go1.8は2/16にリリース済
      • Go1.9は5月1日にコードフリーズ済
    • 言語仕様の変更
      • 型のエイリアスを定義できるようになる
        • 完全に同じ型
        • キャスト不要
        • エイリアスの方でメソッドは定義できない
    • 標準ライブラリの変更
      • math/bits
        • ビット演算に便利なライブラリ
      • sync.Map
        • スレッドセーフなマップ
      • html/templateがより安全に
      • os.Exec
        • 重複する環境変数を削除するように、一番後ろのものを優先するようになった(直感的になった)
    • ツール類の変更
      • コンパイラのエラーメッセージの改善(少し優しくなった笑)
    • コンパイラの速度改善
    • go testの改善
      • 複数パッケージの場合にvendorを無視
    • go doc
      • フィールドにリンクが貼れるようになった


初めてGolangで大規模Microservicesを作り得た教訓

Yuichi MURATA (DeNA)

  • Golangで大規模なMicroservices構成のAndAppを作ったときに得た教訓
  • AndAppの基本構成/アーキテクチャ
    • Google App Engine / Go 1.6
    • Gin / Echo
  • 教訓1:フレームワークにこだわらない
    • Ginで始めた開発
      • シンプルで使いやすかった
      • App Engineとのインテグレーションもサンプルにあった
      • Framework Context vs App Engine Context 問題
        • gin,ContextからApp Engine Contextを派生させ回避  * Echo
      • ワイルドカードパスを共有したルーティングが可能
      • Webアプリ系のコンポーネント開発が捗る
      • 設計がガリガリ書き換わる
      • Version3でコンテキストの派生が不可能に
    • そもそもGoでフレームワークにこだわらない
      • 洗練されたnet/httpパッケージがある
      • Golangは言語仕様がシンプルかつgofmtがある
  • 教訓2: interfaceを尊重する
    • 独自のエラー型を定義して利用することに
    • Nilには型があるの罠
      • errorインターフェースと独自エラー型を混在した時に発生
    • 素直に他の関数にならってerrorインt-フェースに統一すればよかった
    • interfaceの定義されたものを積極的に使った方が標準ライブラリとの連携がシンプルになる
  • 教訓3: regex compile / reflection は遅い
    • 複数スキーマを読み込むためにgojsonchemaを選択
    • パフォーマンステストで問題が発覚
      • バリデーションの有無でパフォーマンスに顕著な差が出ることがわかった
      • パースの度にRegexコンパイルしていた
      • 動的にスキーマを解析する部分でrefletionを多用していた
    • そもそもgo-jsvalで頑張ればさらに段違いに良い結果になった・・
    • regex / reflection などのコスト高の操作を意識する
  • 教訓4: 非対称暗号は遅い
    • Microservicesにおいて肝となる認証・認可
      • JSON Web Token / Json Web Signatureを活用している
    • 非対称鍵の署名が重い
      • opensslと比べgoのcrypto/rsaが貧弱
      • 対象会議の署名:0.37msec, 非対称鍵の署名:486msec
    • cgoを使ったopensslのバインディングも存在する
  • まとめ
    • 困ったときにGoの哲学に帰りシンプルなアプローチをとる
    • Goを過信せずにパフォーマンスに気を配る


ゲーム開発には欠かせない?!あれをシュッと見る

Ryosuke Yabuki (カヤック)

  • CSV
    • CSVは視認性が悪い
    • カラムとデータの関係性が見づらい
  • csviewer
    • CSVをいい感じに表示するコマンドラインツール
    • CSVをテーブル形式で表示してくれる
    • 絞り込みなどもできる
  • 実装で使った便利だったライブラリ
    • sliceflag
      • 複数の値を肝がんに受け取ることができる
      • flagの普段の使い方と大きく変わらない
    • tablewriter]
      • CSVを表示するだけなら相当少ないコード量で済む


Go Code Review Comment を翻訳した話

鎌田健史 氏 (KLab)

  • Go Code Review Commentsとは
    • コードレビューする際にます見るべき箇所をまとめたもの
    • Effective Goを簡単にしたもの
  • 幾つかのカテゴリに分かれている
    • コードの見た目を改善
      • golintめっちゃ優秀
    • コメントや文章の体裁
    • tips系
    • 設計の指針になるようなもの
      • レシーバをポインタにするかどうか迷ったら読もう


ScalaからGo

James (エウレカ)

  • Scala
    • 経験者は関数型っぽく書く
  • 関数型開発はGoでできますか?

  • No!
  • 関数型開発のコンセプトはGoで使える?
    • Yes!
  • 関数型開発とは?
    • 副作用がない開発
  • コードレベル
    • 部分適用はGoでもできる
  • ScalaとGoどっちが好き?
    • Scalaが好き
    • 企業としてはGoがいいかも。Goの方が初心者でも綺麗なコードが書ける


Crypto in Go

Kengo Suzuki (マネーフォワード)

  • Goにおける暗号アルゴリズムを利用する
    • ECBモード
    • パディングとHMACの実装面倒くさい
    • ASE + GCM
      • とてもシンプル



関連エントリ・リンク

Go言語 - 日時のフォーマット処理

本エントリでは、Go言語で日時を特定の書式にフォーマットする処理を実行してみます。

基本的にはtimeパッケージのTime.Format関数を利用することになります。


例えばJavaなら、LocalDateTime#format やら SimpleDateFormat#format 等を利用してフォーマットを行いますが、
その時に与える書式は例えばyyyy/MM/dd HH:mm:ssという文字列になります。

で、これと同じことをGoでやろうとした場合、指定する書式は

2006/01/02 15:04:05

です。具体的な上記の日時を書式として与える必要があります。

慣れの問題なんでしょうけど、具体的な値を指定するというのは私には違和感がありました。

一応典型的な書式はtimeパッケージでconstとして提供されています。(が、日本人好みのスラッシュが入った書式は無いです・・)

const (
        ANSIC       = "Mon Jan _2 15:04:05 2006"
        UnixDate    = "Mon Jan _2 15:04:05 MST 2006"
        RubyDate    = "Mon Jan 02 15:04:05 -0700 2006"
        RFC822      = "02 Jan 06 15:04 MST"
        RFC822Z     = "02 Jan 06 15:04 -0700" // RFC822 with numeric zone
        RFC850      = "Monday, 02-Jan-06 15:04:05 MST"
        RFC1123     = "Mon, 02 Jan 2006 15:04:05 MST"
        RFC1123Z    = "Mon, 02 Jan 2006 15:04:05 -0700" // RFC1123 with numeric zone
        RFC3339     = "2006-01-02T15:04:05Z07:00"
        RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00"
        Kitchen     = "3:04PM"
        // Handy time stamps.
        Stamp      = "Jan _2 15:04:05"
        StampMilli = "Jan _2 15:04:05.000"
        StampMicro = "Jan _2 15:04:05.000000"
        StampNano  = "Jan _2 15:04:05.000000000"
)

ちなみに、与えられる日時は上記の値(2006年1月2日 15:04:05)に固定されていて、例えば年部分を 2017 にしたり、月部分を 12 にしたり とかはできません。(やると結果がおかしなことになります)

ただし、12時間表記にするために時間部分の 15 を 03 にしたり、 ゼロ埋めしないように 秒部分の 05 を 5 にしたり等はOKです。


以下で現在日時を色々な書式でフォーマットして出力してみました。

■サンプルコード

package main

import (
    "fmt"
    "time"
)

func main() {
    nowTime := time.Now()

    const format1 = "2006/01/02 15:04:05" // 24h表現、0埋めあり
    fmt.Printf("now -> %s\n", nowTime.Format(format1))

    const format2 = "2006/1/2 3:4:5" // 12h表現、0埋め無し
    fmt.Printf("now -> %s\n", nowTime.Format(format2))

    const DateFormat = "2006/01/02"
    const TimeFormat = "15:04:05"
    const MilliFormat = "2006/01/02 15:04:05.000"
    const MicroFormat = "2006/01/02 15:04:05.000000"
    const NanoFormat = "2006/01/02 15:04:05.000000000"

    fmt.Printf("yyyy/MM/dd -> %s\n", nowTime.Format(DateFormat))
    fmt.Printf("HH:mm:ss   -> %s\n", nowTime.Format(TimeFormat))

    // ミリ秒まで出力
    fmt.Printf("Milli -> %s\n", nowTime.Format(MilliFormat))

    // マイクロ秒まで出力
    fmt.Printf("Micro -> %s\n", nowTime.Format(MicroFormat))

    // ナノ秒まで出力
    fmt.Printf("Nano  -> %s\n", nowTime.Format(NanoFormat))

    // Unixtimeに変換
    unixTime := nowTime.Unix()
    fmt.Printf("unixTime -> %d\n", unixTime)
}


■実行結果

now -> 2017/05/24 19:21:04
now -> 2017/5/24 7:21:4
yyyy/MM/dd -> 2017/05/24
HH:mm:ss   -> 19:21:04
Milli -> 2017/05/24 19:21:04.135
Micro -> 2017/05/24 19:21:04.135562
Nano  -> 2017/05/24 19:21:04.135562200
unixTime -> 1495621264


というわけで無事に日時のフォーマット処理ができました。(2006/01/02 15:04:05を頭に入れとこう。)



関連記事

Go言語 - melodyを利用してWebSocketサーバを立てる

melodyGinを使ってWebSocketサーバを立てて、簡易的なチャット画面を作ってみました。

実際には、melody Exampleを、ほぼそのまま流用しているだけです。


準備

以下を実行してmelodyを利用可能な状態にしておきます

go get gopkg.in/olahol/melody.v1


プログラムとhtml

WebSocketサーバと動作確認用チャット画面のindex.htmlとして以下を用意しました。

■WebSocketサーバ(Goプログラム)

package main

import (
    "fmt"
    "log"
    "net/http"

    "github.com/gin-gonic/gin"
    "gopkg.in/olahol/melody.v1"
)

func main() {
    log.Println("Websocket App start.")

    router := gin.Default()
    m := melody.New()

    rg := router.Group("/sampleapp")
    rg.GET("/", func(ctx *gin.Context) {
        http.ServeFile(ctx.Writer, ctx.Request, "index.html")
    })

    rg.GET("/ws", func(ctx *gin.Context) {
        m.HandleRequest(ctx.Writer, ctx.Request)
    })

    m.HandleMessage(func(s *melody.Session, msg []byte) {
        m.Broadcast(msg)
    })

    m.HandleConnect(func(s *melody.Session) {
        log.Printf("websocket connection open. [session: %#v]\n", s)
    })

    m.HandleDisconnect(func(s *melody.Session) {
        log.Printf("websocket connection close. [session: %#v]\n", s)
    })

    // Listen and server on 0.0.0.0:8989
    router.Run(":8989")

    fmt.Println("Websocket App End.")
}


■index.html

<html>
  <head>
    <title>Chat powered by Melody</title>
  </head>

  <style>
    #chat {
      text-align: left;
      color:#ffffff;
      background: #113131;
      width: 400px;
      min-height: 300px;
      padding: 10px;
      font-family: 'Lucida Grande', 'Hiragino Kaku Gothic ProN', 'ヒラギノ角ゴ ProN W3', 'Meiryo', 'メイリオ', sans-serif;
      font-size: small;
    }
  </style>

  <body>

    <center>
      <h3>Sample Chat</h3>
      <pre id="chat"></pre>
      <label id="title"></label>
      <input placeholder="say something" id="text" type="text">
    </center>

    <script>
      var url = "ws://" + window.location.host + "/sampleapp/ws";
      var ws = new WebSocket(url);
      var name = "Guest-" + Math.floor(Math.random() * 1000);
      var chat = document.getElementById("chat");
      document.getElementById("title").innerText = name + ": ";
      
      var text = document.getElementById("text");
      var now = function () {
        return new Date().toLocaleString();
      };

      ws.onmessage = function (msg) {
        var line =  now() + " : " + msg.data + "\n";
        chat.innerText += line;
      };

      text.onkeydown = function (e) {
        if (e.keyCode === 13 && text.value !== "") {
          ws.send("[" + name + "] > " + text.value);
          text.value = "";
        }
      };
    </script>

  </body>
</html>


実行してみた

対象のアプリを実行します。実行すると以下のようなログがコンソールに出力されます

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /sampleapp/               --> main.main.func1 (3 handlers)
[GIN-debug] GET    /sampleapp/ws             --> main.main.func2 (3 handlers)
[GIN-debug] Listening and serving HTTP on :8989


http://localhost:8989/sampleapp/にアクセスすることでチャット画面が利用できました


f:id:nini_y:20170523160815g:plain


というわけで、お遊び程度ですがかなりコード量少なくWebSocketのサーバサイドを実装することができました。



関連エントリ

Go言語 - Goの特徴

Go言語(Golang)の特徴(メリット)として以下のようなものが一般的に挙げられています


高パフォーマンス

一般的に軽量言語(LL)ではメモリ管理のコストなどに注意してコードを構成しなければパフォーマンスが極端に悪くなることがあります。
それに対して、Goは細部を気にせずにコードを書いても比較的パフォーマンスを引き出しやすい傾向があります。


メモリ管理からの解放

C/C++等はメモリ管理を手動で行う必要がありますが、Goではメモリ管理をGoランライムに任せることができます


コンパイル速度の速さ

C/C++等に比べてGoのコンパイル速度は極端に早いです。
コンパイル速度が速いので、コードを修正してビルド・実行という開発の流れを気軽に何度も実行することができます


スタイルやコード整形

プログラムをチーム開発している際に、どの書式スタイルに統一するかという議論がなされることがあります。
Goではスタイルの規定が最初からしっかりしていて、かつ、そのスタイルをサポートするためのコード整形ツールも揃っています。
このことにより、Goではコードのスタイルに関して質を簡単に保つことができます。


シンプルな言語仕様

Goは機能の割にとてもシンプルな仕様で作られています。
人によっては1日程度で言語仕様を理解することもできます。


シングルバイナリ

Goで書かれたプログラムは基本的には単体で実行可能なシングルバイナリとして生成されます。
一旦コンパイルしてしまえばLL系言語で必要なランタイムや依存関係の管理は必要なくなります。
アプリのインストール・デプロイといった作業も非常に簡単になります。



関連エントリ

Go言語 - Ginを使ってHello World

Go言語ではnet/httpを利用することでHTTPサーバを立てることができまが、GinといったWebアプリ用のフレームワークも存在しています。


事前準備

以下を実行してGinのパッケージ取得を行います

go get github.com/gin-gonic/gin


とりあえずHello World

以下 Hello World の文字列を返す例です。

Engine.GETにより、ハンドリング対象のパスとハンドラを登録します。


■サンプルコード

package main

import (
    "log"

    "github.com/gin-gonic/gin"
)

func main() {
    router := gin.Default()

    router.GET("/hello", func(ctx *gin.Context) {
        ctx.String(200, "Hello World")
    })

    if err := router.Run(":8686"); err != nil {
        log.Fatal("Server Run Failed.: ", err)
    }
}


■実行結果

http://localhost:8686/helloにアクセスすると以下の結果が出力されます

Hello World


静的ページを返す

URLへのアクセスに対して静的ページを返す場合は、router.StaticFS 等を利用します。

以下サンプルでは、staticディレクトリにindex.htmlとuser.htmlというファイルを用意しているものとします

f:id:nini_y:20170519185348p:plain


■サンプルコード

package main

import (
    "log"
    "net/http"

    "github.com/gin-gonic/gin"
)

func main() {
    router := gin.Default()

    router.StaticFS("/web", http.Dir("static"))

    if err := router.Run(":8686"); err != nil {
        log.Fatal("Server Run Failed.: ", err)
    }
}

■実行結果

  • http://localhost:8686/web/ に アクセスすると staticディレクトリのindex.html の内容が返される
  • http://localhost:8686/web/user.html に アクセスすると staticディレクトリのuser.html の内容が返される



関連エントリ