20200217のGoに関する記事は6件です。

JavaとGoの値渡し/参照渡しを整理する

自分はこれまでにJava, Goを勉強してきた。この二言語における値渡し/参照渡しをここで整理してしまう。

そもそも定義

  • 値渡し(call by value): メソッドの引数に、新たに値のコピーを作って渡す方法のこと。
  • 参照渡し(call by reference): メソッドの引数に、変数そのものの参照を渡す方法のこと。

Javaの場合

  • メソッドへの引数は100%値渡しで渡される。
  • が、Javaのオブジェクトが変数に収納されている時、そこに収納されているのは「そのオブジェクトが収納された場所を示す参照値(reference value)」である
  • よって、オブジェクトを引数として渡すときにはその参照値(reference value)のコピーが渡されている。
  • この結果、「引数に渡したオブジェクトが参照渡しで書き換えられている」かのように勘違いしそうになる。

Goの場合

  • ポインタを引数として渡すことで参照渡しを実現する。
  • Golangの構造体/Arrayは(Javaのオブジェクトとは違って)値型。すなわちその全てのフィールド/要素を収納する場所を確保し、それを構造体を表す変数に収納しているイメージ。
  • よって構造体を値渡しすると、そのフィールドの全てがコピーされる。これを書き換えても元の構造体は書き変わらない。
  • 一方で、map/sliceは(Javaのオブジェクトと同様)参照型。すなわち変数に入っているのは要素のポインタが入った容器であり、各要素にアクセスするにはポインタを辿っているイメージ(参考)。
  • このため、これらを値渡ししたとしても、元のmap/sliceが書き換わってしまう。よって、普通これらをポインタ渡しする意味はなく、普通そうしない(参考)。

補足

  • 参照渡し ≒ ポインタ渡しであるかのように書いたが、そこの細かいところはこちらに詳しい。
  • 値型/参照型の説明は正確ではないかもしれないのであくまでイメージとして捉えてほしい。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Go 1.13~] errors.Is と errors.As の違いについてお気持ちを理解する

TL;DR

これまで Go 1.13 から登場した errors.As の使い所があまり良くわからなかったのですが,
全ての答えは公式 Doc にありました.

In the simplest case, the errors.Is function behaves like a comparison to a sentinel error, and the errors.As function behaves like a type assertion.

  • errors.Is: 特定のエラーとの比較
  • errors.As: エラーに対する型アサーション

ここでは実例をもとに理解を深めていきたいと思います.

実例 error.Is

errors.Isはあるエラーが特定のエラーを Wrap したものかを判別するためのものです.

import (
    "fmt"
    "errors"
)

ErrFoo := errors.New("foo error")

func main() {
    wrapped := fmt.Errorf("wrapped woo: %w", ErrFoo)
    if errors.Is(wrapped, ErrFoo) {
        fmt.Println("this error is caused by %v", ErrFoo)
    }
}

こんな感じで特定のエラーの起源をたどって,例外処理ができます.
比較的単純で「なるほど,便利.」という感じです.

実例 errors.As

errros.As は冒頭でも書いたとおり,エラーに対する型アサーションです.
言い換えると自前でエラー interface を書いたときに,ビルトインのエラーなのか自前で用意したエラーなのかを判別するときに使えます.
また,その付加効果として(こっちがメインかもしれないが),動的に自前のエラー独自のメソッドを利用することができます.

少し長いですがここでは go-playground/validator を用いた例について紹介します.
コード自体は基本的に main 部に注目してくれればよいです.

import (
    "fmt"
    "errors"

    "github.com/go-playground/validator/v10"
)

type Foo struct {
    NaturalNumber int `validate:"gt=0"`
}

func validateFoo(f *Foo) error {
    v := validator.New()
    return v.Struct(f)
}

func buildFoo(n int) (*Foo, error) {
    foo := &Foo{NaturalNumber: n}
    if err := validateFoo(foo); err != nil {
        return nil, fmt.Errorf("cannot build Foo: %w", err)
    }
    return foo, nil
}

func main() {
    foo, err := buildFoo(-1)
    if err != nil {
        var verrs validator.ValidationErrors
        if errors.As(err, &verrs) {
            for _, verr := range verrs {
                fmt.Println(verr)
            }
            return
        }
        fmt.Printf("unknown error is occurred: %v", err)
        return
    }
    fmt.Printf("this is %v", foo)
}

go-playground/validator はいわゆる validation エラーを扱うためのパッケージで, ValidationErrors という独自のエラーを内部で持っています.

ここで buildFooFoo インスタンスを作成するための関数で,validation エラーが起きた場合はそれを Wrap して返すものとします.
よって,返ってくるエラーは特定のエラーインスタンスを Wrap したものではなく, ValidationErrors という interface を満たしたものを Wrap していることに注意してください.つまり,エラーインスタンスに対する比較である errors.Isはここで用いることはできません.

そこで型アサーションの役割を果たす errors.As を使います.
errors.As では err を UnWrap していくと verr (ValidationErrors) として扱うことができるかということをチェックします.
もし可能なのであれば, errors.Astrue を返しつつ,verr に Wrap して出てきた ValidationErrors を代入します.このため if 文の中では verrserr から抽出された実態を持たせて扱うことができ,動的に ValidationErrors の機能を用いることができます.
実際に上記コードの if 文内の for 文は ValidationErrors が持っている機能です.

まとめ

実例を見てみると,

  • errors.Is: 特定のエラーとの比較
  • errors.As: エラーに対する型アサーション

というのはかなりしっくりきます.
加えて errors.As は動的に interface 独自の処理を扱えるようになるので良いですね.

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Golang】テンプレートを簡単に使える外部ライブラリを見つけた (echo)

前提

  • 言語は、Golang。フレームワークは echo を使用しております。
  • templateをより簡単に使えるパッケージを紹介しています。

詳細

https://github.com/foolin/echo-template

Deprecated!!!
Please consider trying to migrate to Goview

https://github.com/foolin/goview
はじめ、前者の echo-template を発見しましたが、後者の goview に統合されたらしく、後者を使用しました。一応、 前者も使えます。
echo, Gin, Go-chi, go.rice 等にサポートしているらしいです。

インストール

go get github.com/foolin/echo-template

go get github.com/foolin/goview/supports/echoview-v4

diff

どれだけ簡略化されるのか。diff形式で。

diff(main.go)
 package main

 import (
-   "html/template"
-
    "github.com/labstack/echo"
+   "github.com/foolin/goview/supports/echoview"
    "net/http"
 )


-type Template struct {
-   templates *template.Template
-}
-
-func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
-   return t.templates.ExecuteTemplate(w, name, data)
-}
-
 func main() {
    // Echo instance
    e := echo.New()


-   t := &Template{
-       templates: template.Must(template.ParseGlob("views/*.html")),
-   }
-   e.Renderer = t
+   e.Renderer = echoview.Default()

    e.GET("/page", func(c echo.Context) error {
        return c.Render(http.StatusOK, "page.html", echo.Map{"title": "Page file title!!"})

diff元のソースコードはに貼っておくので、気になったら見てください。

結果

コードを省略できる。


備考

呼び出しについて

.html.tpl等、拡張子無しでRenderする場合は、/views/layouts/master.htmlが呼び出される。

ソースコード

通常

main.go
package main

import (
    "html/template"

    "github.com/labstack/echo"
    "net/http"
)

type Template struct {
    templates *template.Template
}

func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
    return t.templates.ExecuteTemplate(w, name, data)
}

func main() {
    // Echo instance
    e := echo.New()

    t := &Template{
        templates: template.Must(template.ParseGlob("views/*.html")),
    }
    e.Renderer = t

    e.GET("/page", func(c echo.Context) error {
        return c.Render(http.StatusOK, "page.html", echo.Map{"title": "Page file title!!"})
    })

    // Start server
    e.Logger.Fatal(e.Start(":9090"))
}

echoviewを用いた場合

main.go(echoview)
package main

import (
    "github.com/labstack/echo"
    "github.com/foolin/goview/supports/echoview"
    "net/http"
)

func main() {
    // Echo instance
    e := echo.New()

    e.Renderer = echoview.Default()

    e.GET("/page", func(c echo.Context) error {
        return c.Render(http.StatusOK, "page.html", echo.Map{"title": "Page file title!!"})
    })

    // Start server
    e.Logger.Fatal(e.Start(":9090"))
}

その他

マークダウンで ```diff ってやると、差分にちゃんと色が着くってことに驚いている。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

handy-spannerを使ってspannerのテストをしよう

はじめに

こんにちわ、すえけん(@sueken5)です。この記事ではspannerのエミュレータであるhandy-spannerを使ってspannerのコードをテストするやり方を紹介します。

handy-spanner

handy-spannerはgcpugで管理されているプロジェクトで非公式ではありますがspannerをエミュレートするツールです(リンク)。

できることとできないことがREADME.mdに書かれているのでチェックしておきましょう。

built in server

handy-spannerはgolangで書かれていてテストプロセスの中でエミュレータを起動して使うことができます。なのでDockerなどを用意する必要がないためとても簡単にテストできます。

//https://github.com/gcpug/handy-spanner/blob/master/fake/example_test.goより抜粋

func ExampleSpannerClient() {
    ctx := context.Background()
    dbName := "projects/fake/instances/fake/databases/fake"

    // Run fake server
    srv, conn, err := fake.Run()
    if err != nil {
        log.Fatal(err)
    }
    defer srv.Stop()
    defer conn.Close()

    // Prepare spanner client
    client, err := spanner.NewClient(ctx, dbName, option.WithGRPCConn(conn))
    if err != nil {
        log.Fatalf("failed to connect fake spanner server: %v", err)
    }

    // Use the client
    _, _ = client.Apply(ctx, []*spanner.Mutation{
        spanner.Insert("Test",
            []string{"ColA", "ColB"},
            []interface{}{"foo", 100},
        ),
    })

    // output:
}

実際のテストコード

//init_test.go
package spanner_test

import (
    "fmt"
    "log"
    "os"
    "testing"

    "github.com/gcpug/handy-spanner/fake"
    "google.golang.org/grpc"
)

var (
    testServer     *fake.Server
    testConnection *grpc.ClientConn
)

func TestMain(m *testing.M) {
    fmt.Println("test main")

    // Run fake server
    server, conn, err := fake.Run()
    if err != nil {
        log.Fatal(err)
    }

    defer server.Stop()
    defer conn.Close()

    testServer = server
    testConnection = conn

    ret := m.Run()
    os.Exit(ret)
}

TestMainでhandy-spannerサーバーを起動しておきます。

func TestGet_OK(t *testing.T) {
    databaseID := "get-ok"
    dbName := fmt.Sprintf(dbFormat, projectID, instanceID, databaseID)

    ctx := context.Background()
    testCase := struct {
        input  []byte
        key    string
        expect []byte
    }{
        input:  []byte("OK"),
        key:    "test",
        expect: []byte("OK"),
    }

    if err := createDatabase(context.Background(), dbName); err != nil {
        panic(err)
    }

    //fakeサーバー起動時に渡されたコネクションを使ってクライアント生成
    client, err := spanner.NewClient(ctx, dbName, option.WithGRPCConn(testConnection))
    require.Nil(t, err)

    s := &spanner.Store{
        PKey:  testCase.key,
        Value: testCase.input,
    }

    mut := s.Insert(ctx)
    _, err = client.Apply(ctx, []*libspanner.Mutation{mut})
    require.Nil(t, err)

    actual, err := Get(ctx, testCase.key)
    require.Nil(t, err)
    assert.Equal(t, testCase.expect, actual)
}

TestMainでサーバーを起動した時に生成されたコネクションを使ってspannerのクライアントを生成します。あとはいつも通りテストするだけです。

まとめ

簡単にテストできたのでとてもよかったです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Golangのネットワーク関連ライブラリを雑多にまとめる

Golangのネットワーク関連のライブラリを自分用にざっとメモ。

HTTPリクエストを飛ばす

簡単な方法

// リクエスト
resp, _ := http.Get("http://example.com")

// そのリスポンスをprint
defer resp.Body.close()
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(endpoint)

難しい方法

// request structの作成
req, _ := http.NewRequest(
    "Post",
    "https://test",
    bytes.NewBuffer([]byte("request body"),
)
// そのheaderを指定
req.Header.Add("Content-Type", "application/json")

// クライアントを初期化
var client *http.Client = &http.Client{}

// そこからリクエストを飛ばす
resp, _ := client.Do(req)

// そのリスポンスをprint
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))

エンドポイントURLをいじる

base, _ := url.Parse("https://test/mypage/")
reference, _ := url.Parse("myPicture?a=1&b=2")
endpoint := base.ResolveReference(reference).String() // URLを結合する

// クエリパラメタの確認
req, _ := http.NewRequest("GET", endpoint)
fmt.Println(q:= req.URL.Query()) // map
fmt.Println(q.Encode()) //クエリ形式の文字列

MarshalとUnmarshal

  • Marshalは構造体→JSON文字列のバイト配列
  • UnmarshalはJSON文字列のバイト配列→構造体
type Person struct {
    Name string `json:"name"`  // Marshal時のkey名をnameに
    Age int `json:"age,string"` // Marshal時valueがstringに
    Friends int `json:omitempty` // Marshal時valueがempty(0)だと無視される
    password string `json:-`   // Marshal時無視される
}

func main() {
    b := []byte{`{"name":"John", "age":25, "nickname": "Johnny"}`}
    var p Person

    // byte文字列をPerson構造体へMarshalする (属性名はCaseInsensitive)
    if err := json.Unmarshal(b, &p); err!= nil {
        fmt.Println(err)
    }

    // Person構造体をbyte文字列へUnmarshalする
    v, err := json.Marshal(p)
    fmt.Println(string(v))
}
  • Person構造体のカスタム(アン)マーシャルを定義。Person構造体に以下のメソッドが存在すると、json.Marshal(Person p)json.Unmarshal(b, &p)が呼び出された際、以下が使われる。
// カスタムマーシャル
func (p Persion) MarshalJSON() ([]byte, error) {
    // nameだけをマーシャル
    v, err := json.Marshal(&struct{
        Name string
    }{
        Name: "Mr." + p.Name,
    }
    return v, err
}


// カスタムアンマーシャル
func (p Person) UnMarshalJSON(b []byte) error {
    // まずPerson2という構造体を定義し、Person2でアンマーシャル
    type Person2 struct {
         Name string
   }
    var p2 Person2
    err := json.Unmarshal(b, &p2)

    // それを使いpに放り込む
    p.Name = "Mr. " + p2.Name
    return err
}

HMAC(Hash-based Message Authentication Code)

HMACの仕組み

  1. 事前にAPIKeySecretKey(秘密鍵=publicKeyではない!!!)を発行し、その両方をクライアントサイドに保存する

  2. ブラウザからサーバへ以下を送信する

    • APIKey
    • バイト配列のデータ
    • HMAC = SecretKeyを用いハッシュ関数を利用してデータを暗号化したもの。HeaderのSignatureに、使用したハッシュ関数の種類とともに収納する。
    • 用いたハッシュ関数の種類(SHA-1、MD5など)
  3. サーバサイドでは以下の手順でデータが改竄されていないことを確認する

    1. APIKeyに対応するSecretKeyを取り出す
    2. MACをその秘密鍵でdecryptし、それが「データ」と一致するか確かめる

Golangで実装

// クライアントサイド
func main() {
    const apiKey = "api_key"
    const apiSecret = "api_secret"

    data = []byte("data")
    h := hmac.New(sha.New, []byte(apiSecret))
    h.Write(data)
    sign := hex.EncodeToString(h.Sum(nil))

    // これをハッシュ関数サーバへ送る
}

// サーバサイド
func Server() {
    // 受け取ったapiKeyとHMACで...
    apiSecret := DB[apiKey]
    h := hmac.New(sha256.New, []byte(apiSecret))
    h.Write(data)
    expectedMAC = hex.EncodeToString(h.Sum(nil))

    // これが受信したデータと一致するか確認
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

goenvでGo1.13.6をインストールメモ

背景

homebrewでインストールしたgoenvをupgradeしたがインストールできるバージョンの最大が1.12だったので使いたかった1.13.6が使えなかった。

$ goenv install -l
Available versions:
  [省略]
  1.10.7
  1.11.0
  1.11beta2
  1.11beta3
  1.11rc1
  1.11rc2
  1.11.1
  1.11.2
  1.11.3
  1.11.4
  1.12beta1

作業メモ

goenvのディレクトリとbrewのgoenvを一旦削除

$ rm -rf `goenv root`
$ brew uninstall goenv

goenv/INSTALL.mdの手順通りにコマンド実行

$ git clone https://github.com/syndbg/goenv.git ~/.goenv
$ echo 'export GOENV_ROOT="$HOME/.goenv"' >> ~/.bash_profile
$ echo 'export PATH="$GOENV_ROOT/bin:$PATH"' >> ~/.bash_profile
$ echo 'eval "$(goenv init -)"' >> ~/.bash_profile
$ echo 'export PATH="$GOROOT/bin:$PATH"' >> ~/.bash_profile
$ echo 'export PATH="$PATH:$GOPATH/bin"' >> ~/.bash_profile
$ eval "$(goenv init -)"
$ brew install goenv

インストールされている事を確認

$ goenv install -l
Available versions:
  [省略]
  1.11.12
  1.11.13
  1.12.0
  1.12beta1
  1.12beta2
  1.12rc1
  1.12.1
  1.12.2
  1.12.3
  1.12.4
  1.12.5
  1.12.6
  1.12.7
  1.12.8
  1.12.9
  1.12.10
  1.12.11
  1.12.12
  1.12.13
  1.12.14
  1.12.15
  1.12.16
  1.12.17
  1.13.0
  1.13beta1
  1.13rc1
  1.13rc2
  1.13.1
  1.13.2
  1.13.3
  1.13.4
  1.13.5
  1.13.6
  1.13.7
  1.13.8
  1.14beta1
  1.14rc1

Go1.13.6をインストール

$ goenv install 1.13.6
Downloading go1.13.6.darwin-amd64.tar.gz...
-> https://dl.google.com/go/go1.13.6.darwin-amd64.tar.gz
Installing Go Darwin 64bit 1.13.6...
Installed Go Darwin 64bit 1.13.6 to /Users/xxxxxxxxx/.goenv/versions/1.13.6

1.13.6が使えるようにする

$ goenv global 1.13.6
$ goenv rehash
$ goenv versions
  system
* 1.13.6 

アップグレードする場合

$ cd ~/.goenv
$ git pull

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む