その手の平は尻もつかめるさ

ギジュツ的な事をメーンで書く予定です

gowrtr - goコード生成支援ライブラリ

gowrtr (go writerと発音します) というgoのコード生成支援ライブラリ (ジェネレータ群) を書きました.

github.com

Synopsisに書いたように,

package main

import (
	"fmt"

	"github.com/moznion/gowrtr/generator"
)

func main() {
	generator := generator.NewRoot(
		generator.NewComment(" THIS CODE WAS AUTO GENERATED"),
		generator.NewPackage("main"),
		generator.NewNewline(),
	).AddStatements(
		generator.NewFunc(
			nil,
			generator.NewFuncSignature("main"),
		).AddStatements(
			generator.NewRawStatement(`fmt.Println("hello, world!")`),
		),
	).
		EnableGofmt("-s").
		EnableGoimports()

	generated, err := generator.Generate(0)
	if err != nil {
		panic(err)
	}
	fmt.Println(generated)
}

のようなコード (ジェネレータ) を書いてやると

// THIS CODE WAS AUTO GENERATED
package main

import "fmt"

func main() {
        fmt.Println("hello, world!")
}

というようなコードが生成されるというようなライブラリです.
この例だと生成結果があまりにシンプルなので,逆に記述量が増えてアレな感じになっていますが,ある程度生成結果が大きくなるようなものだと便利に使えるはず……です (後述のImmutabilityの話題もご覧ください).詳細につきましてはGoDocをご覧ください (Exampleも示してあります).


特徴としては

  • 生成したコードにコードフォーマッタ (gofmt, goimports) を適用することができる
  • ライブラリの各メソッドはimmutableに振る舞う

というものが挙げられます.

前者は生成結果にgofmtをかけることによって「生成コードのフォーマットが統一される」というメリットと「Syntaxチェックができる」というメリットを享受できることに加え,goimportsを適用することによって「コード生成のためのジェネレータを書いているときに『何をimportするか』を考えなくても (記述しなくても) 良くなる」というメリットを得ることができます.

後者については各ジェネレータの各メソッドが暗黙的に内部状態を変更しないので,利用者はジェネレータのスナップショットを任意のタイミングで取得することができます.これによりジェネレータの再利用が可能になると同時に,そこからジェネレータを派生させていくようなコードが書きやすくなるというメリットがあります.




さて,Javaにはsquare/javapoetというライブラリがあって,これはJavaコードの生成支援を行うライブラリなのですが,gowrtrはこのライブラリに着想を得て作られました.
まだできたてのライブラリなのですが,一部の自前環境に適用したところうまくワークしているのである程度動くのではないでしょうかというステータスです.が,もしかしたら足りない機能や記法のサポートがあるかもしれません.ご意見・ご要望をお待ちしております.

ぜひご利用くださいませ.

go-errgen書いた

goのstructにエラー定義を書いておけば良い感じで「エラーを返却する関数」をコード生成するツールであるgo-errgenを書きました.

github.com

Synopsisに書いてあるとおり,

package mypkg

//go:generate errgen -type=myErrors
type myErrors struct {
	FooErr error `errmsg:"this is FOO error"`
	BarErr error `errmsg:"this is BAR error [%d, %s]" vars:"hoge int, fuga string"`
}

みたいな感じでstructにエラー定義を書いて,go:generate を設定してから go generate を実行すると

package mypkg

import "errors"
import "fmt"

func FooErr() error {
	return errors.New("[ERR-1] this is FOO error")
}

func BarErr(hoge int, fuga string) error {
	return fmt.Errorf("[ERR-2] this is BAR error [%d, %s]", hoge, fuga)
}

func MyErrorsList() []string {
	return []string{
		`[ERR-1] this is FOO error`,
		`[ERR-2] this is BAR error [%d, %s]`,
	}
}

という感じのコードが my_errors_errmsg_gen.go として生成されるというツールです.

これはエラーメッセージに「通し番号が付いたprefix」を付与したerrorを返却する関数をコード生成します.もし errmsg に加えて vars パラメータが定義されている場合はその値が関数の引数パラメータとして利用され,かつ fmt.Errorf() によってsprintf互換のプレースホルダにbindされます.


主なモチベーションとしては,

  • 集権的にエラーを定義して管理したい (エラー定義が散在するとつらい)
  • エラーの特定を行う際にエラーコードを利用したい

というのがあり,特に後者はいろいろなチームから多く利用されるコンポーネントであれば必須に近い機能でしょう.で,集権的にエラーを管理するにしても,手でエラーコードを記述するようにしたところでうっかりミスってしまう可能性もありますし (例えばエラーコードを重複させてしまうとか),そこんところは機械的にやりたいな〜という気持ちからerrgenを作ったという感じです.

errgen -type=myErrors -prefix=My-Prefix のように -prefix を付与すると [My-Prefix-1] のようにprefixを自由に設定することもできます.


この手の仕組みはプロジェクトごとに自作しがちだったんですが,毎度毎度書くというのも面倒だったのでこの度汎用的に使えるようツール化したという次第です.

ご利用くださいませ.

golangのstruct custom tagをうまいことparseして値を引っこ抜きたいってとき

type Foo struct {
	Bar string `buz:"qux" iyan:"bakan"`
}

における `buz:"qux" iyan:"bakan"` を良い感じで buz => "qux", iyan => "bakan" のように取得したいというケースでは以下のようにするとよい:

tagKeyValue := reflect.StructTag(`buz:"qux" iyan:"bakan"`)
buz := tagKeyValue.Get("buz") // => "qux"
iyan := tagKeyValue.Get("iyan") // => "bakan"

という具合でreflect.StructTagを使うと良い感じでできます.よかったよかった.

……クソ,なんのためにこんなものを書いてしまったんだ!!!! 良いお年を!!!!

github.com

superuserに対する「ふつーのuser」にどういう名前を付けるべきか

TL;DR

  • unprivileged users
  • ordinary user
  • standard user

などが良いようだ.ordinaryとかstandardはuserに限らず他の種別でも使えそう.

会話の様子

shinpei0213 [14:11]
特権ユーザー root user 一般ユーザー xxxx user
xxxx なんて言えばいいの

moznion [14:12]
prisoner
単にuserだと問題が出るケースってことですよねえ

shinpei0213 [14:12]
そうそう
なるホディウスでした

moznion [14:13]
prisonerは完全に問題があるw

shinpei0213 [14:13]
だめかな
だめか

moznion [14:13]
「囚人」

shinpei0213 [14:13]
normal userだとへん?
でもadd user するときに prisoner とか渡さないっけ
わたさないな
なにかと勘違いしている

moznion [14:13]
jailingの文脈で思いついたw
normal,僕は変だと思わないっすけど,ネイチブの人とかはどう思うのかな
limitedにするとか?

shinpei0213 [14:14]
ah

moznion [14:14]
restrictedとか

shinpei0213 [14:14]
とりあえずnormalにする。azs
restricted いいね!!!!
それにします。天才かよ

moznion [14:15]

sunglasses

ichigotake [14:19]
Unprivileged users と表現しているところもあるようですね
>Unprivileged users can use the su and sudo programs for controlled privilege escalation.
https://wiki.archlinux.org/index.php/users_and_groups

moznion [14:19]
unprivillegedよさそう

ichigotake [14:19]
Unprivileged users でググってみると結構ヒットする

shinpei0213 [14:20]
第二の天才現る

moznion [14:20]
(タイプしにくいことを除くと
↑の僕のpostもタイポしてるしw
unprivilegedのほうが文脈としては正しそうな雰囲気ある

ichigotake [14:28]
英語版wikipedia を見るとこういう表現もあるようだ
standard user
ordinary user
https://en.wikipedia.org/wiki/Superuser

aws-lambda-perl5-layer 書いた

[2018-12-05 追記]
ビルド済みのLayerを公開しましたので,そちらを使うと便利です.
See also: http://moznion.hatenablog.jp/entry/2018/12/05/211523
[追記ここまで]

先日のre:InventでCustom AWS Lambda Runtimesが発表され*1,これはつまり任意の言語でAWS Lambdaを実行することを可能とする機能なんですが,ということはPerl5が動くということでして,すなわち動くと嬉しいはず,したがって動かすためのLayerを書いたという話です.

github.com

この場合のLayerというのはPerl5を動かすための基盤だと思ってください.基本的にはbootstrapが実際にぐるぐる回ってperlの関数を実行するという感じになっております.
どのようにして動いているかは公式のTutorialが詳しいので参考されたい: https://docs.aws.amazon.com/lambda/latest/dg/runtimes-walkthrough.html


さて実際やってみた感想としてはbootstrapを書くのは非常に簡単で,ざっくり挙動を説明するとOver HTTPでLambdaのイベントを取ってきてその内容に従って関数を実行して,結果をPOST backするという非常に素朴な感じです.どんな言語でも比較的容易に書けるでしょう.

問題は実行基盤を整備するところで,これは scripts/build.sh に様々集約されているわけですが,エッセンスとしては

  • デフォルトだとPATHは /usr/local/bin:/usr/bin/:/bin:/opt/bin 以下に通っているのでそこにランタイムを突っ込む必要がある
  • Layerのzip archiveのルートは /opt に アタッチされるっぽい
    • なので bin/ やら lib/ やらをzipのルートに入れておけば良い感じになる
    • そこにperlのランタイムを突っ込むという作戦
    • bootstrapが必要とするライブラリ群もあらかじめここに入れておく
  • Layer zipのルートに bootstrap を置いておけばそのファイルがbootstrapとして認識される (つまり実行される)
    • 実行可能なpermissionが当たってないと動かない
  • ランタイムやツールのインストール (つまりzipping) する際には実環境と揃える必要がある
    • 適切なDockerコンテナを使って処理する必要がある
    • lambciの提供しているものを使った

という感じです.結構難しくてハマってしまいました……なお,実際に利用可能な環境変数群については以下を参考にされたい: https://docs.aws.amazon.com/lambda/latest/dg/current-supported-versions.html


まだドキュメントが十分に揃っていなく,また謎挙動もいろいろあるのですが *2 非常に面白い機能だと思いました.夢が広がりますね!!!

設計Night2018で話してきました

connpass.com

スライドは以下です.

id:nkgt_chkonkさんに「パフォーマンス視点でのソフトウェアの設計というテーマでなんか話せない?」と持ちかけられ,当時色々考えていたこともあり「やってみるか!」という感じの経緯でできたのがこのトークです.


言いたかったことはスライド終盤に書いたように

  • 一発で良い設計にたどり着ければ最高だけどなかなかそうもいかんよね〜
  • 良い設計とは「変化に耐えうること」「変更にあたってコストが低いこと」ではないか
  • 設計には段階がある
  • 第一段階としての良い設計は「最適化の余地がある設計」ではないか

ということです.「早すぎる最適化」はもちろん良くないことですが,しかし「最適化の余地を残しておくことは重要」というような認識です.


スライド中に出てくる「美しいものは空気抵抗が少ない」というのは新幹線を開発した三木忠直さんという方の言葉で,つまり「美しいものは速い」という意味なんですが *1 ,かねがね「いい言葉だな〜」と思っていたので引用させてもらいました.困ったときはそっちの方に倒す,という感じで日々を暮らしています.


なお,この発表はズルをしていて,キュー(バッファ)に使うミドルウェアのコストに全く言及していませんでした.しかし前時代と比較してキューのようなミドルウェアはオンデマンドで利用しやすくなった,そして運用コストが下がって使いやすくなったというのはあると思います.Amazon KinesisやSQSやGoogle Cloud Pub/Subなどといったクラウドサービスがキュー(あるいはpub/sub)サービスを提供しているのが大きいと思います.以前は自分でマシンを確保して「ActiveMQを入れるぞ〜〜〜」みたいな感じだったわけですから……


ところで発表の冒頭でも言ったのですが,僕は割と野生の勘でソフトウェア設計をしがち (もちろん理論は基礎として持ちつつ) で,こういう感じで体系的に言語化するのが得意な方ではないのですが、一丁頑張ってやってみましたところ自分の中でも理解が整理されたり深まったりしたので良かったですねという感じです.


設計Night2018,いいイベントでした.ありがとうございました.

*1:必ずしも一般化できる話ではないことは承知していますが

stringsを使ってどのgoのバージョンでコンパイルしたバイナリかをサクッと取得する

How to find out which Go version built your binary | Dave Cheney

stringsを使わないちゃんとしたやり方はこちら.


雑に取るならstringsコマンドを使うとそれっぽいのが取れます.

$ strings main | grep go1[.]
stack=[cgocheckdebugcpugo1.11.1runnableruntime.scavengeunknown( (forced) -> node= blocked= defersc= in use)

なるほど,これはgo1.11.1でコンパイルされたバイナリ.非常にスッキリしています.

$ strings main | grep go1[.]
, not 390625<-chanArabicAugustBrahmiCarianChakmaCommonCopticFridayGOROOTGothicHangulHatranHebrewHyphenKaithiKhojkiLepchaLycianLydianMondayRejangSCHED SundaySyriacTai_LeTangutTeluguThaana[]bytechan<-efenceerrno go1.11objectpopcntselectstringstructsweep uint16uint32uint64 (scan  (scan) MB in  Value> dying= locks= m->g0= nmsys= s=nil
/tmp/test/bin/go1.11
...

これは1.11でコンパイルされている様子.1.11.1と比べてごちゃごちゃ度が増していて,かつパス情報が下にずらずらと列挙されていますね.

$ strings main | grep go1[.]
stack=[cgocheckgo1.10.4runnableruntime.scavengeunknown( (forced) -> node= blocked= defersc= in use)
/tmp/test/bin/go1.10.4
...
Go cmd/compile go1.10.4

1.10.4です.1.11と比較してスッキリとした風味.パス情報が列挙されており,かつその下にはGo cmd/compile go1.10.4という非常にわかりやすい文字列が入っています.親切!

$ strings main | grep go1[.]
value=cs     float32float64fs     gctracego1.9.7gs     invalidpanic: r10    r11    r12    r13    r14    r15    r8     r9     rax    rbp    rbx    rcx    rdi    rdx    rflags rip    rsi    rsp    runningsyscalluintptrunknownwaiting goal
/Users/moznion/tmp/test/bin/go1.9.7/src/fmt/scan.go
...
Go cmd/compile go1.9.7

1.9.7も1.10.4と似ていますね.Go cmd/compileが付いていて良い時代.

$ strings main | grep go1[.]
value=cs     float32float64fs     gctracego1.8.7gs     invalidpanic: r10    r11    r12    r13    r14    r15    r8     r9     rax    rbp    rbx    rcx    rdi    rdx    reflectrflags rip    rsi    rsp    runningruntimesyscalluintptrunknownwaiting goal
/Users/moznion/tmp/test/bin/go1.8.7/src/fmt/doc.go
...

1.8.7のようです.Go cmd/compileが無いですね.つまりこのマーカーは1.9から1.10.xまでの短い命だったのですね……


というわけでサクッと取れて便利でした.