Edited at

golangちょいテク

More than 3 years have passed since last update.

golangやっていて見つけたちょっとしたテクニック的なものをまとめてみます。


factory切り替え

golangにおいて関数は第一級オブジェクトなので、例えばあるインターフェイスのfactory methodを状況に応じて入れ替えるみたいなこともできます。


user.go

// ユーザー情報を表す構造体

type User struct {
Name string
Age int
}

// Userに関係するインターフェイス
type UserRepo interface {
FindByName(string) (*User, error)
}



repo_mem.go

// メモリ上にユーザー情報を持っておく

type userRepoMem struct {
mem []*User
}
func (r userRepoMem) FindByName(name string) (*User, error) {
for _, u := range r.mem {
if u.Name == name {
return u, nil
}
}
return nil, errors.New("not found")
}

func newUserRepoByMem() Repo {
return userRepoMem{}
}



repo_sqlite.go

// SQLite上にユーザー情報を持っておく

type userRepoSQLite struct {
db *sql.DB
}
func (r userRepoSQLite) FindByName(name string) (*User, error){
rows := r.db.QueryRow("select * from user where name = ?", name)

user := new(User)
err := rows.Scan(&user.Name, &user.Age)

return user, err
}

func newUserRepoBySQLite() Repo {
db, err := sql.Open("sqlite3", ":memory:")
panic(err)

return userRepoSQLite{ db }
}



repo.go

var (

// デフォルトではメモリ実装を使う
NewUserRepo func() UserRepo = newUserRepoByMem
)

// 外部からどちらを使うか決められる
func UseMem() {
NewUserRepo = newUserRepoByMem
}
func UseSQLite() {
NewUserRepo = newUserRepoBySQLite
}


この例では外部から設定してますが、ユースケース的には設定からどちらを使うか確認して内部で切り替え、みたいなのがあるんじゃないかなと思います。

参考:

https://github.com/GoogleCloudPlatform/go-endpoints


struct{}

何もフィールドを持たない構造体はサイズが0なので「具体的な値はいらないダミー変数」に使えます。

例えば、golangにはsetがないのでmapで代用してsetみたいなことすると思いますがその時とかに使えます。

// 記事を表す構造体

type Post struct {
OwnerID int
Title string
Body string
}

// 記事群から筆者IDを重複なしで抜き出す
posts := GetAllPost()
owners := func() []int {
set := make(map[int]struct{})
for _, post := range posts {
set[post.OwnerID] = struct{}{}
}

res := make([]int, 0, len(set))
for id := range set {
res = append(res, id)
}

return res
}()

参考:

http://qiita.com/najeira/items/47539ab346fa0c00dc62#tips-%E7%A9%BAstruct%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6


x/net/context

googleが公開している非標準パッケージにx/net/contextがあります。

これは並行処理のために使われることが想定されているパッケージっぽいんですが、一般的なコンテキストとしての使われ方もされているみたいです。

NewContext/FromContextを定義して既存のコンテキストに巻き込み/引き剥がすみたいです。

package database

import (
"database/sql"
"golang.org/x/net/context"
)

const (
reqKey = "database"
)

func NewContext(ctx context.Context, db *sql.DB) context.Context {
return context.WithValue(ctx, reqKey, db)
}
func FromContext(ctx context.Context) *sql.DB {
if db, ok := ctx.Value(reqKey).(*sql.DB); ok {
return db
}
// いちおうnilを返しているけど型アサーションの確認はしないで返している例もありました
return nil
}

// ctxにはdb以外にもいろいろと情報が巻き込まれている
func GetUserByName(ctx context.Context, name string) (*User, error) {
// 確実にctxにdbが巻き込まれていることを想定する
db := FromContext(ctx)

// ... 以下dbを使って処理する
}

参考:

https://github.com/drone/drone/tree/master/server

https://github.com/guregu/kami

http://qiita.com/nyarla/items/c89ee1ae8703cc459aec


内部ユニークキー

x/net/contextは既存のコンテキストに巻き込むためにcontext.WithValueという関数を公開しています。

この関数を呼び出すには「既存のコンテキスト」「巻き込む値」そして「取り出すときに使うキー(型はinterface{})」が必要になります。

このキーは重複させないようにする必要があるのですが、簡単にユニークなキーを作る方法があります。それがプライベートな型のプライベートな変数です。

先ほどの例だと

// struct{}のままだとどこでも使えるので、別名をつけてプライベートな型にする

type private struct{}

var (
reqKey private
)

func NewContext(ctx context.Context, db *sql.DB) context.Context {
return context.WithValue(ctx, reqKey, db)
}
func FromContext(ctx context.Context) *sql.DB {
if db, ok := ctx.Value(reqKey).(*sql.DB); ok {
return db
}
return nil
}

こんな感じです。golangではポインタ演算はできず、大文字で始まらないものは外部からは見えないので、普通にやっていれば重複することはないはずです。

(reflectやunsafeあたりでごにょごにょすればreqKeyと同じものを作れるかもしれませんが)

ちなみにこのreqKeyをstruct{}にすると普通に衝突してしまいます。プライベートな型であることが重要です。

同一パッケージ内で複数のNewContext/FromContextを作りたい場合はプライベートなint変数のポインタとかを取るといいかもしれません。(struct{}のポインタを取ると衝突します)

参考:

https://github.com/goji/context/blob/master/bridge.go

http://ikawaha.hateblo.jp/entry/2015/03/13/013521

http://qiita.com/nyarla/items/202d1ded0f576f1bb36b


グローバル変数

func echo(msg string) int {

fmt.Println(msg)
return 0
}

var n = echo("global var")

func init(){
_ = echo("init")
}

func main(){
_ = echo("main")
}

このようなgolangファイルを実行してみるとわかるのですが、グローバル変数初期化での関数呼び出しはinit関数やmain関数より早いです。

この性質を使ったパッケージに flag があります。


server/app.go

package server

import (
"flag"
"net/http"
"fmt"
)

var port = flag.Int("port", 8080, "listen post")

func Serve(){
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request){
fmt.Fprint(w, "Hello World")
})
http.ListenAndServe(fmt.Sprintf(":%d", *port), nil)
}



main.go

package main

import (
"flag"

// go buildする場合は下のimportパスを適宜書き換えてください
"./server"
)

func main(){
flag.Parse()

server.Serve()
}


これをgo buildして ./server -port=9000 みたいに呼び出すと http://localhost:9000 でHello Worldが表示されます。

このように複数パッケージを横断して設定をばらまけるのは非常に便利です。

参考:

https://github.com/drone/config (flag.FlagSetのラッパーパッケージ。使いやすそうなんだけどライセンスがよくわからない)


推奨されないであろうテク

おそらく推奨されないテクニックです。ネタとしてご覧ください。


_code.go

golangではファイル名が _ から始まるファイルはビルド対象外になります。これを使うとソース単位で実装を切り替えるみたいなことができます。


datastore/mem.go

var mem []*User

func GetUserByName(name string) (*User, error) {
for _, u := range mem {
if u.Name == name {
return u, nil
}
}
return nil, errors.New("not found")
}



datastore/sqlite.go

var db *sql.DB

func GetUserByName(name string) (*User, error){
rows := db.QueryRow("select * from user where name = ?", name)

user := new(User)
err := rows.Scan(&user.Name, &user.Age)

return user, err
}


こんな感じでソース単位で実装を用意しておいて使わない方を _~~~.go の様にすれば切り替えが行えます。

普通にinterface作ったほうがいいですね・・・