Golang に関する投稿を表示しています

grpc-go の interceptor を理解した

元実装はここ
GitHub - grpc/grpc-go: The Go language implementation of gRPC. HTTP/2 based RPC

Interceptor の例としてはここ
GitHub - grpc-ecosystem/go-grpc-middleware: Golang gRPC Middlewares: interceptor chaining auth logging retries and more.

要はなにかというと Laravel をやっていたときに使っていた Middleware と自分では理解した。
Middleware - Laravel - The PHP Framework For Web Artisans

リクエストを処理する前後に、任意の処理を差し込めるもの。

リクエストを中断したり、ウェイトしたり、前処理したり。
あるいはリクエスト処理の後に、かかった時間やステータスについてのメトリクスを取ったり、といったことができる。

これはサーバだけでなく、クライアント側にも差し込めるので、クライアントリクエストする前に何処に対してリクエストしようとしているか、とか、なんか色々できそうではある。

自分でも書いてみる。

func AccessLogUnaryServerInterceptor() grpc.UnaryServerInterceptor {
	return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
		clientIP := "unknown"
		if p, ok := peer.FromContext(ctx); ok {
			clientIP = p.Addr.String()
		}

		ua := ""
		if md, ok := metadata.FromIncomingContext(ctx); ok {
			if u, ok := md["user-agent"]; ok {
				ua = strings.Join(u, ",")
			}
		}

		ctxzap.AddFields(
			ctx,
			zap.String("access.clientip", clientIP),
			zap.String("access.useragent", ua),
		)

		return handler(ctx, req)
	}
}

これは UnaryServerInterceptor で、つまりは単一リクエスト用のもの。
peer を使ってリクエスト元 IP と Metadata に入っている UserAgent を取得し、それを ctxzap を使って追加フィールドとして設定しておくもの。

なので go-grpc-middleware の grpc_zap.UnaryServerInterceptor が設定されていないと使えない。(そこで ctxzap が設定されるので)
go-grpc-middleware/logging/zap at master · grpc-ecosystem/go-grpc-middleware · GitHub

peer はここ。リクエスト元についての情報が簡単に入っている。
grpc-go/peer at master · grpc/grpc-go · GitHub

Metadata についてはここ。ざっくりとは HTTP Header みたいなやつ、と理解したけど大体あってそう。
grpc-go/grpc-metadata.md at master · grpc/grpc-go · GitHub

ctxzap は先に出した go-grpc-middleware で定義されるもの。
go-grpc-middleware/logging/zap/ctxzap at master · grpc-ecosystem/go-grpc-middleware · GitHub

gRPC リクエストの context に zap.Logger を出し入れできるもので、リクエストごとに Logger が設定されているような状態にできる。
ので、例えばリクエストの認証情報を詰めたりとかできて便利ちゃん。

実際に組み込もうとするとこんな感じ。

func NewGRPCServer() *grpc.Server {
	return grpc.NewServer(
		grpc_middleware.WithUnaryServerChain(
			grpc_zap.UnaryServerInterceptor(zap.L()),
			interceptor.AccessLogUnaryServerInterceptor(),
		),
	)
}

で、この interceptor を使った gRPC サーバに対してリクエストを送ってみると、こんな感じにログがでる。よさそう

{
  "level": "info",
  "ts": 1562940752.853247,
  "caller": "zap/server_interceptors.go:40",
  "msg": "finished unary call with code OK",
  "grpc.start_time": "2019-07-12T23:12:32+09:00",
  "system": "grpc",
  "span.kind": "server",
  "grpc.service": "helloworld.Greeter",
  "grpc.method": "SayHello",
  "access.clientip": "127.0.0.1:65516",
  "access.useragent": "my-user-agent/1.0.0 grpc-go/1.19.1",
  "grpc.code": "OK",
  "grpc.time_ms": 0.3019999861717224
}

ちなみにクライアントから UserAgent をどうやって設定するかはこうする。

client, err := grpc.DialContext(ctx, address, grpc.WithUserAgent("my-user-agent/1.0.0"), ...)

後ろに grpc-go/1.19.1 的な文言がついてしまうのはそうなんだけど、まあ、いいんじゃないかな。


あと余談なのだけど grpc-go と go-grpc-middleware とで grpc と go が入れ替わってていつもどっちだ!?!?となる


ワンソースでドッと試したのはこんなかんじ。
example にある helloworld な proto をそのまま利用するとお手軽簡単に gRPC 周辺の様子みれて便利。

package main

import (
	"context"
	"net"
	"strings"
	"time"

	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
	grpc_zap "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap"
	"github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap"
	"go.uber.org/zap"
	"google.golang.org/grpc"
	pb "google.golang.org/grpc/examples/helloworld/helloworld"
	"google.golang.org/grpc/metadata"
	"google.golang.org/grpc/peer"
)

// server is used to implement helloworld.GreeterServer.
type server struct{}

// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
	ctxzap.Extract(ctx).Info("Received", zap.Any("in.name", in.Name))
	return &pb.HelloReply{Message: "Hello " + in.Name}, nil
}

func main() {
	port := "127.0.0.1:13000"

	l, _ := zap.NewDevelopment()
	zap.ReplaceGlobals(l)

	// server
	go func() {
		logger := zap.L().Named("Server")

		listener, err := net.Listen("tcp", port)
		if err != nil {
			logger.Error("failed to listen: %v", zap.Error(err))
		}
		defer listener.Close()

		s := grpc.NewServer(
			grpc_middleware.WithUnaryServerChain(
				grpc_zap.UnaryServerInterceptor(logger),
				accessLogUnaryServerInterceptor(),
			),
		)
		defer s.Stop()

		pb.RegisterGreeterServer(s, &server{})

		if err := s.Serve(listener); err != nil {
			logger.Error("failed to serve: %v", zap.Error(err))
		}
	}()

	// client
	go func() {
		time.Sleep(2 * time.Second)
		ctx := context.Background()
		logger := zap.L().Named("Client")

		client, err := grpc.DialContext(
			ctx,
			port,
			grpc.WithUserAgent("my-user-agent/1.0.0"),
			grpc.WithInsecure(),
		)
		defer client.Close()

		if err != nil {
			logger.Error("failed create client", zap.Error(err))
		}

		greeterClient := pb.NewGreeterClient(client)

		logger.Info("Do request")

		response, err := greeterClient.SayHello(ctx, &pb.HelloRequest{Name: "sters"})

		logger.Info("", zap.Any("response.Message", response.Message), zap.Error(err))
	}()

	time.Sleep(5 * time.Second)
	zap.L().Info("Shutdown")
}

func accessLogUnaryServerInterceptor() grpc.UnaryServerInterceptor {
	return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
		clientIP := "unknown"
		if p, ok := peer.FromContext(ctx); ok {
			clientIP = p.Addr.String()
		}

		useragent := ""
		if md, ok := metadata.FromIncomingContext(ctx); ok {
			if ua, ok := md["user-agent"]; ok {
				useragent = strings.Join(ua, ",")
			}
		}

		ctxzap.AddFields(
			ctx,
			zap.String("access.clientip", clientIP),
			zap.String("access.useragent", useragent),
		)

		return handler(ctx, req)
	}
}

最近パッケージ読み込み出来るようになったって聞いたので The Go Playground で実行できる。
ココ にある

Confuluence を Markdown で書く

Confuluence の世界に Markdown で書いてたものを持っていくとか Markdown でサクッと書きたいなあ、などというのが微妙に不便しているので、誰かベストソリューションを教えてほしい。

Markdown for Confluence | Atlassian Marketplace

これは Confuluence のページに Markdown なコンテンツを埋め込める、というもの。
Markdown で Confuluence のページを書けるといえばそう。けど、これだと Markdown の世界と Confuluence の世界がつながらないので、ちょっと微妙…、かも?

Confluence Wiki マークアップ - アトラシアン製品ドキュメント

マークアップ機能はでっかい Markdown や、やや処理にクセがあるっぽく、うまく出来ないときの修正の仕方がいまいちわからない。
ただ Confuluence Wiki 記法はうまく処理されるっぽい。
というわけで Markdown から Confuluence Wiki 記法にするそれっぽいのを書いてみて、手元だけはすこし楽できるようにしてみた。

GitHub - sters/md2cw: Markdown syntax to Confluence Wiki syntax

converter を見てもらうとわかるけれど Markdowm のパーサーやレンダラーとして動いてくれる blackfriday というのがある。

GitHub - russross/blackfriday: Blackfriday: a Markdown processor for Go

ぼくはそれぞれの要素が Confuluence でいうどれにあたるかを書いたくらいしかしていないくらいに、世の中は便利。


git(というよりは svn が近いか)のそれみたいに、プル、プッシュ、マージが出来るようなクライアントとかあってもいいかもなーとか思った。
そうなるともはや Confuluence である必要はそんなにないかもしれないけれども。。


それはそうとして、このリポジトリ、go getでうまくいれられないくない?(日本語崩壊)
go modulesとgo getのお気持ちがちょっとまだわからない…

grpc のリクエストをガシガシ送るツールghz

gRPC なエンドポイントのパフォーマンスをみたり、負荷試験をしたいなあという話をしたら ghz というツールがあるよと教えてもらったので、これを使ってみる。

ghz · Simple gRPC benchmarking and load testing tool

公式にはリリースページからインストールしてくれやと書かれている。
が go get でとれるのでこれでいいのでは… latest を使うことになるのでリリースされてるものと微妙に差異があるかも。

go get github.com/bojand/ghz/cmd/ghz/

ab ライクに使えるので、ヘルプを見たらまあ問題なく使えるはず。
Usage · ghz

Kubernetes 上で動くアプリケーションに対して実施するとこんな感じ。
ポートフォワードしてから、

kubectl port-forward svc/foo 5000

※Service に向けてポートフォワードしてますが kubectl コマンド実行時に宛先 Pod を解決してしまうので、ロードバランスしないです。

別ターミナルで、こういう具合 proto の指定がないときは Server Reflection して勝手にやってくれる、超便利。
そっちの話題はここにある。 grpc/server-reflection.md at master · grpc/grpc · GitHub

$ ghz \
    --call foo.bar.Service.RPC \
    -d "$(jo foo='bar')" \
    -m "{\"Authorization\":\"BEARER $TOKEN\"}" \
    -q 4 \
    -c 2 \
    -n 10 \
    --insecure \
    localhost:5000

Summary:
  Count:    10
  Total:    1.37 s
  Slowest:    243.03 ms
  Fastest:    111.49 ms
  Average:    149.98 ms
  Requests/sec:    7.29

Response time histogram:
  111.491 [1]    |∎∎∎∎∎∎∎∎
  124.645 [5]    |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
  137.799 [0]    |
  150.953 [2]    |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
  164.107 [0]    |
  177.261 [0]    |
  190.415 [0]    |
  203.569 [0]    |
  216.723 [0]    |
  229.877 [0]    |
  243.031 [2]    |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎

Latency distribution:
  10% in 118.75 ms
  25% in 119.66 ms
  50% in 123.21 ms
  75% in 243.01 ms
  90% in 243.03 ms
  0% in 0 ns
  0% in 0 ns

Status code distribution:
  [OK]   10 responses

リクエストパラメータつくるのに jo を使っている。
GitHub - jpmens/jo: JSON output from a shell

上記の例だと 最大 4 req/sec になるように、 2 並列でリクエストし、 10 リクエスト送ったら終わる、というオプションになっている。
なので、レスポンスタイム次第ではあるが、サクッとレスポンスがあれば 1 秒ちょっとで完了する。

レスポンスタイムの分布であったり、パーセンタイルもでたり、サクッと負荷かけて様子を見るのに便利そう。

もちろん、オプションの指定が必要だけど 1 リクエストだけ送ることもできるので、普段使いにもいいんじゃなかろうか。

また Go のパッケージとしても使えるっぽい。
Package · ghz
ちょっと用途が思い浮かんでないけど、例えば、複数の RPC があって、順番にテストしたいんだよね〜〜〜みたいなときとか CI 的に負荷をかけたいんだよね〜〜とか、(シェルスクリプトでさくっと ghz ラッパーでもいいのでは感)

Go で 定期的に Ping する

疎通確認であったり、コネクションのkeepalive的な用途として、定期的に "ping" をしたいんだよなあ、という話があり
ぐるぐる回しっぱなしでよきに使えそうな感じのコードを書いてみた。

GitHub - sters/pinger: Ping somethings for use warmup and standby or others.

いまは土台と、HTTPしかないけど、MySQLとかなんかまあ他にもできそう

使い方はここを見てもらうのがわかりやすいかも
pinger/http_test.go at master · sters/pinger · GitHub

実践的にはこういう感じになるのかな。

ctx := context.Background()

fooPinger := pinger.NewWorker(...)
barPinger := pinger.NewWorker(...)

eg, ctx := errgroup.WithContext(ctx)
eg.Go(func() error { return fooPinger.Run(ctx) })
eg.Go(func() error { return barPinger.Run(ctx) })

if err := eg.Wait(); err != nil {
    ...
}

すくなくとも、いま主に触っているソレだとこういう感じで書くことになると思う。

余談だけど...

  • type Hoge interface{}を書いて使ったりしていると、これがダックタイピングかとどっしり理解したので、やっばりやってみないとわからんなという気持ち
  • sync パッケージ、使うものの並列にアクセスされる可能性あるやろなあ、ロックするか、くらいの感覚しかまだわかってないので要修行
  • goroutine はちょっとわかってきた感じがある。おしごとでもぼちぼち書いたりしてる
  • これまで簡単なツールをサクッとつくるの Node.js か PHP 使ってたことが多いけどもう Go 言語でよいのでは、と思えるくらいには書けるようになってきた感じ。
  • よく使うようなライブラリとかツール群はまだイマイチなので都度 :innocent:

Withなんちゃらなオプション( Functional Option Pattern )

Go 文化なのかわからないけど New とか構造体作れる関数や初期化する関数へのオプションを設定するためのコールバック関数を設定するキメ文句をよく見るような気がする。

func WithInsecure() func(*Config) {
    return func(cfg *Config) {
        cfg.insecure = true
    }
}

func WithUserAgent(ua String) func(*Config) {
    return func(cfg *Config) {
        cfg.userAgent = ua
    }
}

func main() {
    req := NewRequest(
        WithInsecure(),
        WithUserAgent(),
    )
    req.Send()
}

こういう感じのやつ。

オプションを外のパッケージに対して隠せるっていうメリットがあるのかな。
流れに乗っておくと、雰囲気で利用できて便利そうだなーって思った。


追記:2019年3月4日
これは Functional Option Pattern だ、という話をまったく違う流れで社の Slack で知った。

Functional Option Pattern

Config 構造体でがんばって持つのもいいけど、文量増えてきたり、外に公開するような API であった場合にちょっと不便かなあと思っていたので、このデザインはいいなと思った。

envconfig の

kelseyhightower/envconfig: Golang library for managing configuration data from environment variables

ざっくり説明

環境変数を読み込んで構造体におとせるやつ。
構造体のタグに環境変数名をいれると、任意の環境変数をパースしたときに展開してくれる。
書かなくてもよしなに動く(すごい)

構造体は入れ子にできる

タグに必須有無やデフォルト値をかける。

使い方

リードミーに書いてあるので読むべし。
envconfig/README.md at master · kelseyhightower/envconfig

よしなにしてくれるが、毎回タグを書くようにしたほうが事故防止や分かりやすさからもいいと思う。

type (
    // Config of the app.
    Config struct {
        // Service config
        Environment string `envconfig:"ENVIRONMENT" required:"true"`
        LogLevel    string `envconfig:"LOG_LEVEL" default:"ERROR"`
        Version     string `envconfig:"VERSION"`
    }
)

なぜ使うか

ツール類なら flag パッケージで十分だと思う。
アプリケーションとしてだと環境変数に値をいれておいて設定を変えたい場合があるのでは。
DB の接続先とか。

Laravel のときにお世話になってた dotenv もそういう感じよね。
Configuration - Laravel - The PHP Framework For Web Artisans

Go でも dotenv が出来るやつある。
joho/godotenv: A Go port of Ruby's dotenv library (Loads environment variables from .env.)

これは構造体にシュッとならないので map 経由してよしなにやる必要ありそう。
envconfig は構造体にシュッとなって便利そうではある。

どうやって実装されているのか

これは完全に興味で羽を伸ばしてみる。

このソースだけで完結できる。意外と短い?
envconfig/envconfig.go at master · kelseyhightower/envconfig

Process がエントリポイントで、すぐに gatherInfo を呼ぶ。
この中では、引数のバリデーションと、渡された構造体を reflect パッケージを使って、タグや形、キーたる名前をパースしてる。要は構造体の情報から必要なものだけ集約したもの。それが varInfo のスライスになる。
このとき、構造体のタグに split_words がついていると、ベストエフォートでキャメルケースを、スネークケースに変換してキーを設定する。

構造体を持っている場合、それがデコード出来ない形式なら再帰的にパースを試みる。
がてことは割と雑多に構造体を放り込んでもよしなに動いてくれそうではある。

このようにして、読み込んだデータを格納する構造体についての分析が終わったら、いよいよ環境変数から値を読み込む。

すでに確認されているキー名で lookupEnv(os.getenv にあたる)をして、環境変数から読み込み、なければデフォルト値を試みて required の判定をする。
読み込んだ値は文字列でしかないので、構造体の形情報に合わせて変換していく。それを processField でやっている。

このとき、対象が Decoder か Setter を実装していると、それを使って構造体のフィールドを戻そうとする。あるいは TextUnmarshar,BinaryUnmarshaler でも。

サンプルだと IP アドレスな文字列から net.Ip 構造体に落としている。
たとえばだけど暗号化した値を渡して復号化することもできし json を入れて構造体に戻すこともできるし DB 接続の情報をいれて sql.DB のクライアントを作ったり API の接続情報から http.Client を作ることもできる。
もはや設定値から作るようなものなら何でも作れる。
やりすぎるとコード読む側が苦になりそうだけど…。

話をコードに戻すと、あとはエラーの場合がある程度で、以上。

Process がエントリポイントと最初に書いたけど MustProcess というのもある。
こっちはエラーになったら panic する。

regexp.MustCompile なんかと同じ。

おわり。

dd-trace-go を使って Datadog の Span をモリモリ作る

import "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"

func hoge(ctx context.Context) (err error) {
    span, ctx := tracer.StartSpanFromContext(
        ctx,
        "do hoge",
    )
    defer func() { span.Finish(tracer.WithError(err)) }()

    err = somethings1(ctx)
    if err != nil {
        return
    }

    err = somethings2(ctx)
    if err != nil {
        return
    }

    return
}

こういう具合に tracer.StartSpanFromContext してあげるとよい。
StartSpanFormContext を使うと context に実行時の span を保持してくれて、親子関係を構築してくれる。

で、これをサードパーティなパッケージが絡んだところや、その他いろいろにモリモリ適用しようとすると大変そうだなあと思ったので、クロージャでシュッと StartSpanFormContext を書いてみた。

sters/wrapspan: for https://github.com/DataDog/dd-trace-go/

別に何も難しいことはやってない。


dd-trace-go に contrib パッケージがあるように、対象のソレをラップした構造体なり関数なりを作るほうが、そういうパッケージとして切り出せるしオプションの勝手もよいだろうしコードの見通しもよくなりそうだ、と、この記事を書いているときに思った。

Readme に書いた例だとこういう感じ。

func Xsomething1(ctx context.Context) (err error) {
    span, ctx := tracer.StartSpanFromContext(
        ctx,
        "span-1",
    )
    defer func() { span.Finish(tracer.WithError(err)) }()

    return X.something1(ctx)
}

func Xsomething2(ctx context.Context) (err error) {
    span, ctx := tracer.StartSpanFromContext(
        ctx,
        "span-2",
    )
    defer func() { span.Finish(tracer.WithError(err)) }()

    return X.something2(ctx)
}

func Xsomethings(ctx) (err error) {
    span, ctx := tracer.StartSpanFromContext(
        ctx,
        "span-somethings",
    )
    defer func() { span.Finish(tracer.WithError(err)) }()

    err = X.something1(ctx)
    if err != nil {
        return
    }

    err = X.something2(ctx)
    if err != nil {
        return
    }
}

よさそう。

Windows で dep ensure したときに File name too long が出るのを解決した

どうやら Windows はパスの文字数が 260 文字という制限があるらしい。

が、それをレジストリで設定変更できる。ここを 1 にしたらよいとのこと。

\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem

LongPathsEnabled = 1

Pro やら Enterprise やらだとグループポリシーの設定が必要そう。

Local Computer Policy > Computer Configuration > Administrative Templates > System > Filesystem

Enable Win32 Long Paths

see: Long File Names?


そしてさらに git の設定が必要そう。

git config --system core.longpaths true

see: Sorce Tree で"Filename too long"のエラーが出た時の対処 - Qiita

PHP を Go にするという話を mercari.go というイベントで喋りました

PHP を Go にするという話を mercari.go というイベントで喋りました(今更)

mercari.go #4 を開催しました - Mercari Engineering Blog

スライドに書いてある話がおおむねすべてです。

リポジトリはここ: sters/phptogo: Transpile PHP code to Go like something code.

今のメルカリの状況については、各所の web メディアにかかれている Mercari Tech Conf の話がいちばんまとまっていると思います。

この記事では後付けというか、いくつか補足できればと思いますー。

コードすべて変換して手直しはそう大変でもない

初期のもの(スライド中の v1)では、丸っと全コード変換はしていません。

Go でざっくりと書いたテンプレートファイルを作っておいて、そこに struct を書いていて、元の PHP コードから function 単位で、該当するテンプレートファイル上の関数へと埋めていく、といったことをやっていました。

Github で公開したものはその手続きを含んでいないので、大変そうに見えると思います。
(実際これでやれって話になったらチョットつらい)

加えて、各種 Go ヘルプするツール群を利用すれば
自動である程度整ってくれるので、マジつれーわーうわーみたいなことにはならないとおもっています。

型情報もう少しなんとかしたい

PHPDoc あるいは PHP の型宣言の情報を利用すれば、もっと埋めることができます。
そのとき、わからないやつは一旦全部 interface{}でゆるっと扱う、みたいなことになると思います。

関数においての PHPDoc からの補完はすでに書いています。
add function parameter type completion form phpdoc · sters/phptogo@b8e3d96

テストどうやる問題

確かにテストは本当に正しいのかどうかわからないというのもわかります、実際わからない。

一つのやり方として、生成物が Go のような何かのコードではなく、完全に Go の AST を作ってしまう、という手が考えられます。
テストとしては AST→AST ができることを確認すればよいので、今よりは信頼できる(?)テストになるんじゃないでしょうか。
当然そのまま持っていくことがムリな情報は落とさざるを得ないので、落とすか、それっぽい値で補完するかのどちらかになるのは今と変わらないです。

Githubで公開したものに色々追加したいねー

Github で公開したものは、セミコロンないよとか変数に$はつかないよとか、ごくごく基本的な構文の変換くらいしかやっていません。

もっと足を伸ばして PHP クラスを Go の構造体に落とすぞ!!!みたいなものはちょっとやりすぎで、そのとき、うまく出来るように一定のフォーマットであるとか、フレームワークのようなものを利用者に強いることになってくると思うのでそのフレームワークのようなものを理解しなければいけないのは微妙かなと。

ということで、必要なもの、なんとかしたいもの、はそれぞれで実装してくれ!ただ、それだと勝手が悪いところも多いと思うので、サンプルの文量を増やしていくかなあとはおもっています。
たとえば初期実装ではやっていたようなテンプレートファイルに落とし込むようなやつとか。

そもそもPHPをGoに変換する必要があるのか

いや、そんなにないんじゃないですかね……

複雑すぎないクラスやバッチがたくさんあって、何かしらの理由で Go に持っていきたい、という場合に需要がでてくると思います。
ただ PHP で今動いているものを Go で動かすことに対して、手間ひまがかかることは当然なので、効果というか色々と天秤にかけたうえでやったらいいんじゃないですかね~~~。

欲する人のそれぞれの状況によると思うので一概に答えはないと思います。
僕の状況ではだんだんつらくなってきて欲しくなったので作っていった、というだけです。

Go言語のスイッチが一瞬わからなくなったのメモ

一瞬わからなくなったのでメモ。
https://play.golang.org/p/AmpjLMDFPf8

そもそも型スイッチは interface{} は値を任意の型になっているか確認して、その型にキャストされた状態にしてくれるすごいやつ。
A Tour of Go に出てくるので知っている人も多いハズ。
Type switches

Go なりの型の持ち方、ようはダックタイピングをイマイチちゃんと使ってなかったのもあって理解ができてなかったが
"値がこういうものを持っていたらこの型だよね" という話なだけと、とりあえずのところは理解した

ある値が String() な関数を持っている interface{} ならそれは fmt.Stringer() を満たしているのでキャストができるし
他にも String() だけを要求する interface があれば、それを満たしているともいえるので、その型キャストが出来る。

 

あと混乱をきたすので深掘りせずにイイ感じに応対したいんだけど、そこにポインタが絡んでくると謎っぽい。

https://play.golang.org/p/MHIX7i1Royk

まあ、特に理由がなければ struct の扱いはポインタでよいんじゃないだろうか。
CodeReviewComments にも怪しかったらポインタレシーバを使うのだって書いてあった。
CodeReviewComments · golang/go Wiki · GitHub

 

話がそれてきたのでおわり。

1 2