🦜

Go Tips(2020Q3)

2020/09/23に公開

Go 言語での便利な Tips を紹介していきます。

「GO111MODULE=on」を設定しよう!

最近、いろんなサードパーティパッケージが Go モジュールサポートへの修正が進んでいます。
その中で、GO111MODULE=auto のデフォルト値のままだと go get に失敗するものが目立つようになりました。Go1.16 からは「GO111MODULE=on」がデフォルト値になる予定なので、それまでは各自「GO111MODULE=on」を設定しちゃいましょう!

ドキュメント通り「go get」してもうまくインストールできない場合は「GO111MODULE=on」を指定してリトライしてみてください。

ちなみに OS に関係なく環境変数を書き換える便利なコマンドがあります。

go env -w GO111MODULE=on

err 変数名はひとつで問題ない

エラーハンドリングを一つのスコープ内で何度もする時、

if _, err := hoge(); err != nil {...}

とか

v1, err := hoge1()
if err != nil {...}
v2, err := hoge2()
if err != nil {...}

ていう書き方がおすすめだよ!
この書き方なら「err」変数名のバリエーションは増やさずに済むよ!

過去あった「GOPATH 配下縛り」の制約は無くなった

go mod init 自モジュール名
echo package hoge > ./hoge/hoge.go

とする時、

import "自モジュール名/hoge"

でサブパッケージをインポートできるよ!このプロジェクトはもう GOPATH 配下に無くても大丈夫だよ!

Linux 向けスタティックリンク

「net」と「os/user」の2つのパッケージは libc 依存のコードをデフォルトで出力します。
それぞれ「netgo」「osusergo」のタグを指定することでこれらの実装を Go で書かれた実装に切り替えることができます。つまり、libc または musl 依存の無いバイナリを出力します。

go build -tags osusergo,netgo .

CGO 依存のあるもののクロスビルド

CGO 依存が他の依存を呼び込むようなアプリケーションは互換環境(例えば docker)などでビルドしたものを配布すると良いでしょう。

例えば、RaspberryPi の alpine-linux(aarch64)環境向けのバイナリを提供する場合、docker イメージ「aarch64/alpine」上でビルドしたものを実機に転送することで動かすことができます。
(ちなみに docker は CPU アーキテクチャの異なるイメージも PC 上で動かすことができます。)

一部のパスをバイナリから取り除く

panic のスタックトレースにはビルド時点の環境のソースコード絶対パスが表示されますが、その情報はバイナリに埋め込まれた状態です。

これは例えば「/Users/irieda/...」というように個人の開発者の名前が埋め込まれたままのバイナリを配布することになってしまう。

様々な理由でこういった情報をバイナリに埋めたく無い場合は以下のビルドオプションでパスの先頭部分を除去することができます。

go build -gcflags=-trimpath=$HOME -asmflags=-trimpath=$HOME .

このオプションの意味は$HOME に該当するプレフィックスを持つパスから$HOME 部分を削り取ります。

こうしてビルドされたバイナリの panic 時のスタックトレースには$HOME に相当する部分が取り除かれた表示になっているはずです。

追記

Go1.14以降では以下のオプションだけでGOPATHより上のパスプレフィックスを除去してくれるようです。また、Go-Moduleモードの場合モジュールルートより上のパスプレフィックスの代わりに「モジュール名@バージョン」に置き換えます。

go build -trimpath .

ミニマムな JSON-RPC サーバー

Go 標準の JSON-RPC はコネクション型なので TCP か Websocket が必須だったんですが、
以下の記述なら HTTP-POST でハンドリングできます。

package main

import (
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/rpc"
	"net/rpc/jsonrpc"
)

type Server struct{}

func (s *Server) Method(req string, rep *string) error {
	*rep = req
	return nil
}

func main() {
	s := rpc.NewServer()
	s.Register(&Server{})
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		s.ServeRequest(
			// Codec を ServeRequestにわたすと1ハンドリングだけ処理する
			jsonrpc.NewServerCodec(
				// Codec を作るのに必要な io.ReadWriteCloser を合成する
				&struct {
					io.ReadCloser
					io.Writer
				}{
					ioutil.NopCloser(r.Body),
					w,
				},
			),
		)
	})

	if err := http.ListenAndServe(":8080", nil); err != nil {
		log.Fatal(err)
	}
}

以下のコマンドで JSON-RPC コールが試せます。

$ curl localhost:8080 -d '{"method":"Server.Method", "params":["hello"], "id": 1}'
{"id":1,"result":"hello","error":null}

日時表現フォーマットの覚え方

「Go の日時書式のベースはアメリカ時刻表記順の連番」となっています。
時刻が 2020/09/15T13:50:30+0900 の場合、
01-02-03-04-05-06-07 という指定で 09-15-01-50-30-20+09 になる

  • 1 時を 24h 表記にしたければ 03 を 15 にする
  • 20 年を 2020 表記にしたければ 06 を 2006 にする
  • ゼロサプレスが不要なら2桁目のゼロを取り除けば OK

なのでアメリカ時刻表記とこの Tip の1行目さえ覚えていれば直感的に書式を調整できちゃう!

チャネルのコツ

  • 送り手と受け手の責任をしっかり分離しよう
    • 境界が曖昧な場合はチャネルが不向きだと言える
  • closeの条件を一言で説明できない場合はcloseしない方が良い
    • 絶対に送り手が動かないことが保証されたタイミング以降にcloseする
    • 絶対に送り手が動かないことが保証できないならcloseしない運用にする
  • チャネルのナイーブなハンドリングにsyncパッケージ使うのを避ける
    • 併用しだすとパズルになり、そうなってきたら設計は見直したほうががいい
    • チャネルは意図的に不便に作れられていて、チャネルの機能不足を感じる状況になったら設計見直しの良い機会
    • 自分でパズルを解くより前に実例から適用できそうなものを探そう。

自作Goモジュールが更新できない時

go-getで「sumデータベースに該当バージョンがない」とエラーが出る場合、
GOPRIVATE環境変数に自分リポジトリを設定すると直る場合があります。

go env -w GOPRIVATE="gitlab.com/nobonobo"

Discussion