emahiro/b.log

Drastically Repeat Yourself !!!!

git subtree コマンドを使う

git subtree という便利なコマンドを教えてもらいました。

submodule と異なり、取りこむ外部リポジトリのコミットも取り込み元のリポジトリの commit に入れるので、取り込んだリポジトリの編集を commit に含めることが可能です。

How to use

help コマンド叩けば大体何ができるかわかります。

git subtree -h
usage: git subtree add   --prefix=<prefix> <commit>
   or: git subtree add   --prefix=<prefix> <repository> <ref>
   or: git subtree merge --prefix=<prefix> <commit>
   or: git subtree pull  --prefix=<prefix> <repository> <ref>
   or: git subtree push  --prefix=<prefix> <repository> <ref>
   or: git subtree split --prefix=<prefix> <commit>

    -h, --help            show the help
    -q                    quiet
    -d                    show debug messages
    -P, --prefix ...      the name of the subdir to split out
    -m, --message ...     use the given message as the commit message for the merge commit

options for 'split'
    --annotate ...        add a prefix to commit message of new commits
    -b, --branch ...      create a new branch from the split subtree
    --ignore-joins        ignore prior --rejoin commits
    --onto ...            try connecting new tree to an existing one
    --rejoin              merge the new branch back into HEAD

options for 'add', 'merge', and 'pull'
    --squash              merge subtree changes as a single commit

外部のリポジトリを取りこむときは

git subtree add --prefix=<取り込み先のpath/$directoryName> <取りこむ流リポジトリのURL> <取り込むリポジトリのbranch or commit hashj>

を叩きます。但しこのままだと取り込み先の外部リポジトリの commit がそのまま取り込み元の親リポジトリの commit に含まれるので --squash をつけると1つの commit にまとまってくれます。

// e.g.
git subtree add --prefix=app/vendor/3rdLibSample https://github.com/hoge/3rdLibSample.git master --squash

Usecase

取り込む外部リポジトリの変更を親リポジトリの commit ログに入れることができるので

  • 外部のライブラリにパッチを当てて検証する

などのケースで力を発揮するのかなと思いました。

自分は普段 Go を書いて仕事をしているのですが、外部ライブラリの修正を開発中のサービスで動作検証したいときに vendor の中身をいじっていたのですが、 git subtree コマンドを教えてもらってからは prefix で指定した path に外部のリポジトリをコピってきてそのリポジトリの中身を編集、go module の replace の機能を使ってビルドするときは prefix で指定した修正後のライブラリを使う、というフローになりました。

以下のエントリにも追記しました。

ema-hiro.hatenablog.com

IntelliJ から VSCode に移行してみる

もともと JetBrains に魂を売っていた人間なのですが、最近4Kの外部ディスプレイに繋ぐとよく固まってしまってキーボード入力に遅延が発生して体験が圧倒的に不安定になってきているので、これを機に VSCode に乗りかえました。

普段は Go を書いているので、VSCode で Go を書く環境設定の手順をメモっておきます。 なお、今回 VSCode を使うかなーと思ったのは、VSCode と Go の Language Server の相性が少し前まであまり良くなく定義ジャンプなどがもっさりしていたので、ちょっと使いづらいなと思っていたのですが、最近改善されたのを聞いて、それなら使えるかなーと思ったのも1つあります。

また僕自身は完全に JetBrains にキーバインドに慣れて凝り固まってしまっていたので、キーバインドも全て JetBrains のそれを踏襲しました。

Install VSCode

これは書くまでもないと思いますので、割愛します。

https://code.visualstudio.com からDLします。

IntelliJキーバインドを設定する

https://marketplace.visualstudio.com/items?itemName=k--kato.intellij-idea-keybindings がありましたので、このプラグインをインストールして終了です。

正直これが一番悩みました。VSCodeキーバインドに慣れようとも思ったんですが、今更なかなか手に馴染んだ癖を矯正するのはしんどく、これがなかったら移行は無理だったかもしれません。

Language Server を設定する

setting.json に以下を追加するだけです。

"go.useLanguageServer": true,

Language Server の細かい設定がしたい場合は go.languageServerExperimentalFeatures に細かい設定を記載することができます。

"go.languageServerExperimentalFeatures": {
        "format": true,
        "autoComplete": true,
        "rename": true,
        // 略
}

Language Server については公式の README にも記載されてます。

ref: https://github.com/microsoft/vscode-go#go-language-server

generate json tag を設定する

type User struct {
    ID   int64  `json:"id"`
    Name string `json:"name"`
}

Go の struct に json タグを指定することで json で出力するときの key 名を指定できますが、IntelliJ だとstruct の field と同じ行で json.... って打ち込むだけで自動で生成されてくれて(スネークケースでしたけど)、非常に重宝していました。

これと同等の機能をなんとか実現できないものかと調べたところ、公式のドキュメントの https://github.com/Microsoft/vscode-go/wiki/Go-tools-that-the-Go-extension-depends-ongomodifytags for modifying tags on structs という項目があってどうやらデフォでできそう、ということまではわかりました。

実際にどうやって使うのかについては https://github.com/fatih/gomodifytags の本家を見に行きます。

(ここで知ったんですが、この gomodifytags という昨日は各種エディタに提供してるタグの自動生成ツールなんですね....)

本家の README には vscode-go with commands Go: Add Tags and Go: Remove Tags とあるので、struct を記載たら Cmd + Shift + P でコマンドパレットを開いて、Go: Add Tags を探して実行するだけです。普通に Add Tags... と打ち込めば出てくるはず。 タグを削除したいときも同等に コマンドパレットで Remove Tags を探して実行するだけで問題ありません。

ただ1つ問題があって、この gomodifytags を使ってタグを生成するとき、 omitempty がデフォルトだとついてきてしまうので gomodifytags の挙動を設定で制御します。

具体的には以下のような感じです。

"go.addTags": {
    "tags": "json",
    "options": "json=", // json の何もつけない e.g omitemptyをつけたいときは json=omitempty となる。
    "promptForTags": false,
    "transform": "camelcase" // キャメルケースで key 名を記載する e.g. userHoge
},

ref: https://github.com/Microsoft/vscode-go/issues/433#issuecomment-287566310

gomodifytags を使い始めて Intellij より便利だなと感じたのは tag の書式はスネークケースしか指定できなかったのですが、 gomodifytags を使う場合には transform で書式を決めることです。

https://github.com/fatih/gomodifytagsREADME にも書式の記載方法が記載されてます。

その他

言語ごとの細かい設定をする

各言語ごとにタブのサイズとか細かく決めたいケースなどは setting.json

"[$langName]": { // $langName に特定の言語を指定する e.g. ruby, typescript など

}

// e.g.
"[typescript]": {
    "editor.tabSize": 2,
}

こんな感じです。

追記

また色々調べてみて追記できそうなことがあったら随時付け足していきます。

Language Server の挙動の不安定さについて(20190924)

gopls を最新にしたら治るかと思ったんですが、どうやら gopls をアップデートした local の go のversionと setting.json に記載している go の version が一致していないと挙動が不安定(Goのファイルを開いたときに毎回再起動してる?)という状態になりました。

自分の環境で調べただけなので、僕の環境に限った話かもしれませんが。

go get でインストールするツール群を最新にする

goimports, gopls などの go get で入れたツール群を最新版にアップデートする方法

# ツールごとに最新版にする
go get -u golang.org/x/tools/cmd/goimports

# cmd で使えるツール群を一括で最新版にする
go get -u golang.org/x/tools/cmd/...

# いっそのツール群全て最新版にする
go get -u golang.org/x/tools/...

./... で指定するとそのディレクトリ配下全てを対象としてくれるの便利。

slice 操作や検証のあれこれ

ある slice の操作や検証をするときにスッキリ書く方法を教えてもらったので備忘録。

※ 随時更新(忘れて新しくエントリ書くこともあるかも)

重複する要素を持つ slice を弾きたいとき

func duplicate(arr []string) bool {
    base := map[string]int{}

    for _, k := range arr {
        if i := base[k]; i != 0 {
            return true
        }
        base[k]++
    }
    return false
}

base のような slice において []string{"a", "a", "b"} みたいな重複する要素を持つ配列を弾きたい時に使います。

ref: https://play.golang.org/p/bW5YFckAZmB

base の部分も引数に渡してもいいかもしれませんが、ユースケースごとに作ってもいいと思います。
int を使用したのは重複チェックするなら重複した要素をカウントするのがいいかなと思ったためですね。

ただ、場合によっては int より効率いいもの使いたい!っていうケースもあると思うのでそしたら bool を使うのがいいと思います。
typeごとのデータサイズは https://golang.org/src/go/types/sizes.gobasicSizes を見るとわかります。

func deplicate(arr []string) bool {
    base := map[string]bool{}

    for _, k := range arr {
        if ok := base[k]; ok {
            return true
        }
        base[k] = true
    }
    return false
}

ref: https://play.golang.org/p/m_g0u9TCWTX

今思うとこっちの方がスッキリしてますね。

意図しない要素をもつ slice を弾きたいとき

func hasInvalidElement(arr []string) bool {
    base := map[string]int{
        "a": 0,
        "b": 0,
        "c": 0,
    }

    for _, k := range arr {
        if _, ok := base[k]; !ok {
            return true
        }
    }
    return false
}

base にない要素をもつ slice を弾きます。
ref: https://play.golang.org/p/Rnc6SFbx1il

このとき要素の重複は許可する場合 e.g. []string{"a", "a", "b", "c"} には個数のカウントはしません。

そしてこれももっとスッキリ書くなら bool にすると良さそうですね、ってことで書き直してみます。

func hasInvalidElement(arr []string) bool {
    base := map[string]bool{
        "a": true,
        "b": true,
        "c": true,
    }

    for _, k := range arr {
        if ok := base[k]; !ok {
            return true
        }
    }
    return false
}

ref: https://play.golang.org/p/SRGe4ZqeTrr

さらにメモリ効率を良くしたい!と言ったときは bool ではなくて struct{} を使用してもいいかもしれません。

func hasInvalidElement(arr []string) bool {
    base := map[string]struct{}{
        "a": struct{}{},
        "b": struct{}{},
        "c": struct{}{},
    }

    for _, k := range arr {
        if _, ok := base[k]; !ok {
            return true
        }
    }
    return false
}

ref: https://play.golang.org/p/Wrpuc0JTupn

組み合わせ

これらを組み合わせて例えば 要素はお互いがユニークかつ決まった要素が入ってるsliceを検証する と言ったslice の検証要件があったときに以下のようなバリデーションを考えてみます。

func isValid(arr []string) bool {
    base := map[string]bool{
        "a": false,
        "b": false,
        "c": false,
    }

    for _, k := range arr {
        if got, ok := base[k]; !ok || got {
            return false
        }
        base[k] = true
    }
    return true
}

map[string]bool を使って上記のように書くことが可能です。
ref: https://play.golang.org/p/r8qBfhhhq6m

過去に書いた slice 操作系のエントリ

slice の操作は色々考えてみると結構面白いです。 また何か追加するかもしれません。

過去書いた slice 周りのエントリは以下です。

ema-hiro.hatenablog.com

ema-hiro.hatenablog.com

Go で書いた OSS を公開するまでの備忘録

以下のエントリで触れてますが、GitHubOSSを公開したときの手順を備忘録として記録します。

github.com

手順

  1. GitHubに公開用のリポジトリを作成し、コードをPushする
  2. LICENCE を設定する
  3. ciを連携する
  4. Go Report Card を登録する
  5. Godocを書く

GitHubに公開用のリポジトリを作成し、コードをPushする

Pushするまでは割愛します。

一つハマったことはコード書いてる途中に気づいたんですけど、手元の Go のバージョンが Go1.12 以上の場合、go.mod の設定で go1.12 が付いてきてしまって、Go1.11 でビルドしてるプロジェクトで go get ができなくなりました。go.mod ファイルから go1.12 を消しておくか、今のところは go1.11 で go mod init しておくといいかなと思います。

追記: 僕の手元の環境がおかしいだけだったっぽいです。

LICENCE を設定する

ライセンスを登録します。

ライセンスについては全く意識したことなかったのですが、同僚が OSS 公開したら海外の別のエンジニアにコード丸パクリされたというのを見ていたので、ちゃんと設定しないといけないな!と思って設定方法調べました。
どのライセンスが〜みたいな詳細はここでは触れませんが、今回は MIT を選択しておきました。

GitHub の対象リポジトリから「create new file」 を選択して「license」と入力すると license のテンプレートが出てくるのでライセンスにするか選んで commit します。

f:id:ema_hiro:20190813004734p:plain

f:id:ema_hiro:20190813004745p:plain

ちなみに後から知ったんですが、リポジトリ作成時にライセンス選択できるっぽいです。知りませんでした。

CI を設定する

今回は travis.ci を使いました。

使い方は結構簡単で、travis.ciGitHub を連携させて CI を追加したいリポジトリを選択するだけです。

リポジトリを選択するだけだと CI は回らないので、https://docs.travis-ci.com/user/languages/go を参考に設定ファイルを作ってリポジトリに commit します。
自分は初めてだったので簡単な設定にしました。 ref: https://github.com/emahiro/ae-plain-logger/blob/master/.travis.yml

今回は設定が簡単だったので travis を採用しましたが、ちょうど GitHub Actions で CI/CD がサポートされたタイミングだったのでこっちを使ってもよかったかもしれないなーと思いました。

Go Report Card を登録する

プロジェクト内のコードの品質をオンラインで検査してくれる https://goreportcard.comリポジトリを登録します。

上記のURLにアクセスし、検査したいリポジトリGitHub の URL を入力します。

以下のようにプロジェクト内のコードをオンラインで検査してくれます。

f:id:ema_hiro:20190813004811p:plain

Godocを書く

今回はあまりちゃんとは書いてないですが、公開メソッドや sturct に対してコメントを追加する、という最低限の Godoc の記法に則ったものだけは記載しました。
Google のコードとか読んでるよすごい量のコードコメントが書いてあって圧倒されますが、今回はそこまでしなくていいかなーくらいのノリでした。
少しずつちゃんと書いていきたいなと思ってます。

流石に適当な英語が多すぎ、かつ pacic する可能性があるのにそのことが実装を読まないとわからないのは不親切すぎたので以下の PR で Godoc を拡充しました。

github.com

github.com

何をどう書いたらいいかわからなかったのですでにある godoc を参考にして記載しました。

現在の godoc はこのようになっています。

godoc.org

追加で package document を書きました。

以下のようにパッケージ宣言の上にコメントを記載するとパッケージの説明を godoc が拾ってくれます。

/*
    Package log is ~

    Example:
        ctx := request.Context() // タブで一段下がるとコードレイアウトとして描画してくれます。
*/
package log

画像で示すと以下の部分が package document が反映された部分になります。

f:id:ema_hiro:20190816004835p:plain

f:id:ema_hiro:20190816004848p:plain

ここまで書いて最後に godoc の batch を README.md に追加しました。 ?status.svc をつけて挿入します。

[![GoDoc](https://godoc.org/github.com/emahiro/ae-plain-logger?status.svg)](https://godoc.org/github.com/emahiro/ae-plain-logger)

まとめ

外に出して恥ずかしくないコードを書く、というのは色んな手順を踏む必要がありました。
特に Godoc は普段仕事のコードを書く時にはとりあえず lint を防ぐ目的でしか使ってなかったりするので、ドキュメントを使う人のために書く、というのは慣れずにフィードバックをもらいながら調整していきました。
書きたい文章を Google 翻訳にかけてもイマイチピンとこない英訳だったので、これを機会に普段馴染みのある package の Godoc をひたすら漁ったりしてました。結構いい方法だと思いました。

Godoc を書かないと、Godocの正しい書き方がわからない、ということを学びました。

今回初めて色々やってみたので、次からは迷わずにやっていけそうです。

追記: internal package 化

以下のPRで spancontext package を internal に移動しました。理由は外から触れたくないからです。この辺も internal package は外から触れないことは知ってましたが、意図して internal に囲い込むという手法もライブラリを作ることで学べたことでした。

github.com

追記2: タグの切り方

リリースタグを切るときに vX.X.X のように vをつけないと go.mod でコミットハッシュのバージョン管理をされてしまいます。

vなしでタグを切ったとき

// go.mod
github.com/emahiro/ae-plain-logger v0.0.0-20190815145805-39cea2e23c34

vつきでタグを切ったとき

// go.mod
github.com/emahiro/ae-plain-logger v0.2.0

GAE/Go1.12 において構造化ログを出力する

App Engine 2nd Generation で構造化ログを出力するための ae-plain-logger というライブラリを作成しました。

github.com

aelog とは?

ログエントリを json ペイロードとして標準出力に出力します。この時 traceID と spanID をログエントリに含めることで stackdriver logging のGUIで構造化ログとして出力することができます。

サポートしてること

  • 構造化ログを出力すること
    • プラットフォームリクエストログにアプリケーションログが紐づくこと

サポートしていないこと

  • サポートしてること以外の Proprietary App Engine Log API で提供していた機能
    • プラットフォームリクエストログに紐づいたアプリケーションログの中で一番高い severity をプラットフォームリクエストログの severity として伝播させる機能etc...

使い方

README.md にも記載してる通りピュアな net/http を使っているケースのみで How to use で記載してますが、他にもメジャーで使われてる(と思う)WAFでの使い方を記載してみました。
基本的には http.Handler を引数に指定するだけで実装が可能ですので、以下に示している WAF 以外でも使うことは可能だと思います。

Gin

mux := gin.New()
mux.GET("/hello", func(gc *gin.Context) {
    gc.Status(http.StatusOK)
    gc.Writer.Write([]byte("hello"))
    return
})

h := middleware.AELogger("ginRouter")(mux)
server := http.Server{
    Addr:    fmt.Sprintf(":%d", addr),
    Handler: h,
}

go func() {
    if err := server.ListenAndServe(); err != nil {
        log.Fatalf("server closed with error: %v", err)
    }
}()

go-chi

mux := chi.NewMux()
mux.HandleFunc("/hello", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(http.StatusOK)
    w.Write([]byte("hello"))
}))

h := middleware.AELogger("chiServeMux")(mux)
server := http.Server{
    Addr:    fmt.Sprintf(":%d", addr),
    Handler: h,
}

go func() {
    if err := server.ListenAndServe(); err != nil {
        log.Fatalf("server closed with error: %v", err)
    }
}()

gemux

最近気になってる薄い router の gemux も追加しておきます。

mux := &gemux.ServeMux{}
mux.Handle("/hello", http.MethodGet, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(http.StatusOK)
    w.Write([]byte("hello"))
}))

h := middleware.AELogger("chiServeMux")(mux)
server := http.Server{
    Addr:    fmt.Sprintf(":%d", addr),
    Handler: h,
}

go func() {
    if err := server.ListenAndServe(); err != nil {
        log.Fatalf("server closed with error: %v", err)
    }
}()

参考

先駆者の方々のお知恵を色々拝借したり、Googleのドキュメントを真面目に読み込んだりしてました。

追記

追記1.

ドキュメントを読んでて気づいたいんですけど https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry の Payload type の箇所の protoPayload の項目に "type.googleapis.com/google.appengine.logging.v1.RequestLog" が明記されていました。
これはしばらくはこのフォーマットがサポートされる、的な認識を持って良いでしょうかね?

google.appengine.logging.v1.RequestLog をうまいこと自前で組み立てれば、1st Gen に近しいログ出力になりそう、かつ 1st Gen の ログフォーマットに依存した監視設定をしているケースにおいてはそこを移行せずともよくなるので protoPayload を使えると嬉しみがあります。

追記2

第一弾としては構造化ログの出力のみをサポートしましたが、long-running operation 用の filed も用意されているので、こちらにも対応したいなと思ってます。
ref: https://cloud.google.com/logging/docs/agent/configuration#special-fields

Update

2019/11/11 update package name

パッケージ名を ae-plain-logger から aelog に変更しました。

github.com

勤続2年の経過報告

7月も終わって現所属に異動(転籍)してきてから2年と1ヵ月が経ったので勤続エントリを書いてみます。 (これもいつまで続くかな...笑)

去年のエントリは以下

ema-hiro.hatenablog.com

目次

  • 2年終わって
  • 技術スタック的なあれこれ
  • チャレンジしたこと
  • 次の一年

2年終わってみて

また1年よく生き延びたなと思います。
この1年は前半はフロントエンド周りに手を出しつつ、後半は登壇の機会をもらうなど、幅が広がった1年でした。

技術スタック的なあれこれ

サーバーサイドはGAE/Go、フロントエンドは Vue/Typescript からは基本的には大きくずれてません。

途中少しだけRailsの案件やったりしてましたが、ベースはGoを書いてますし、いつの間にやら2年も Go を書き続けて、なんだかんだキャリアで一番長く接した言語になりました。
(今まではPHPがキャリアの経験では最長でした。)

2年も Go にどっぷり浸かってましたが、一向に上達する気配がありません。

インフラもほぼほぼPaaSしか触れておらず、それもApp Engineのみを触ってきていて、他のインフラやクラウドは全く触ってません。
App Engine だけでも十分奥深く、面白く、そしてやっぱりよくわからんとなってます。

最近は第二世代 App Engine へのマイグレーションをやっています。結構大変ですが、面白いです。
あとこれだけはいっておきたいですが、App Engine は最高のツールです。

チャレンジしたこと

以下の二つが大きかったかなと思います。

  1. モダンなフロントエンドの挑戦
  2. 登壇

モダンなフロントエンドへの挑戦

去年の年末のエントリにも記載しましたが、モダンなフロントエンド(Vue + TypeScript)にチャレンジできたことはよかったです。
プロダクトとしてリリースするところまで持っていけたので一つスキルセットとして進歩した感じあります。

登壇

  • 社内勉強会 × 2
  • Go Conference 2019 Spring

エンジニアのキャリアの中で初めてこういった人前で発表する機会をもらえました。
とても緊張しましたし、まだまだ至らないところがあるなという反省もありつつ、チャンレンジできてよかったです。 そして、会社としてもチャレンジさせて貰えたことに感謝です。

半年で3回も人前で話すことを経験したら、流石に少しだけ度胸がつきました。
また、Feedbackをもらうことが恐くなくなった(Feedbackを歓迎するスタンス) のもいいメンタル面の変化でした。ちなみにFeedbackは怖くなくなりましたが、間違うと恥ずかしいのは変わりません。

ema-hiro.hatenablog.com

ema-hiro.hatenablog.com

次の一年

引き続き今のスキルセットをベースにして、大規模開発に必要な知見を貯めていきたいなー思ったりしてます。
飽きっぽいのでまた来年は全く違うことしてるかもしれませんが。。。

Intellij で自動 import 折り畳みをoffる

Go で import 順を揃えたい(standard -> 3rd -> local の順)のに、intellij でファイルを開くと最初から自動で import が折りたたまれてて、レビューでフィードバックされるまで気づかないことがよくあったのでその設定方法を off にする方法がないか調べました。

intellij-support.jetbrains.com

このFAQに記載されている内容通りにやればうまくいきました。

Preferences > Editor > General > Code Folding を選択して、Import のチェックを外します。

image
image

CircleCIの高速化ログ ~キャッシュ先を変更する~

ema-hiro.hatenablog.com

これの追記です。

ディレクトリ構造を変更した時に save_cache ディレクティブで新しいキャッシュ先を指定しても restore_cache で新しいキャッシュ先ではなく古いキャッシュが利用されてしまうケースがあってハマってました。

結果としてディレクトリ構造の変更を行うなどして別のpathに新しくキャッシュを作成する場合 pathsだけでなくキャッシュのkey名も変更が必要 という初歩的なことを忘れてました。

こんなんで結構時間を消費してしまってマジで「ぐぬぬ...」という感想しかありませんでした。という備忘録。

IntelliJを CLI から起動する ~ Tool Box篇 ~

ema-hiro.hatenablog.com

以前このエントリーを書いたのですが、ここ最近 JetBrains の Tool Box を使い始めたときに従来の方法で Command Line Tool が作れなくなっていたので、再度作成するための手順を記載します。

手順

IntelliJから作成する

f:id:ema_hiro:20190625015305p:plain

Tool > Create Command-Line Launcher... とすると

f:id:ema_hiro:20190625015348p:plain

上記のような通知が出る。どうやら Tool Box 配下で Jetbrains のアプリケーションを管理している場合、Tool Box 経由で Command-Line Launcher を作成しないいけないらしい。

説明は以下の Jet Brains の公式ドキュメントに記載してありました。

blog.jetbrains.com

この手順に沿って Tool Box 自体の設定画面から Generate shell scripts のボタンを ON にします。その次に、作成するコマンドラインの PATH を設定します。PATHは /usr/local/bin でいいと思います。ここはなんでも大丈夫です。
※ このとき、Tool Box で管理する前に作成したスクリプトがある場合は削除しておきます。

f:id:ema_hiro:20190625015811p:plain

PATH を指定したら Tool Box を再起動します。これで新しくコマンドが作成されていれば完了です。

f:id:ema_hiro:20190625015913p:plain

Go Conference 2019 Spring に登壇者として参加してきました

Go Conference 2019 Spring - Gocon に登壇者として参加してきました。

登壇の詳細は以下になります。

タイトル: 「Go1.9 で作られた App Engine のサービスを Go1.11 に移行した話」 登壇資料

speakerdeck.com

こういった大きなカンファレンスで発表するのは、エンジニアキャリアの中で初めてだったので、実際に登壇者として参加してみた学びと反省、感想をまとめてみました。

学びと反省

登壇について

社外の大きなカンファレンスでの初めての登壇でした。
かなり緊張しましたが、実際に登壇をしてみると、聞く側と話す側では全く違いました。
普段から使ってる App Engineについての発表にも関わらず、細かいところは案外曖昧で、理解を一段進める意味でも、発表する機会を頂けたことは良かったです。

ただ登壇については、練習では時間通りに進められていましたが、当日思わぬトラブルがあり、最終的に時間が足りなくなったために、最後の方は駆け足でまとめてしまいました。
流石に今の自分にはリカバれるスキルはなかったので、次回以降の課題として持ち越しかなと思います。

内容にについて

Google App Engineについての発表をしました。
イムリーに話題に上がっている内容で、かつ普段の業務で行なっている内容を取り上げることができました。

ただし、少しApp Engineによりすぎてしまったと思っており、知ってる人にはわかるけど、知らない人は置いてけぼり...みたいな内容にもなってしまったかなと思います。ここはもう少し説明を増やしたり、コンテンツの量を調整するなど、練り込める余地は残っていました。
加えて、Go の話はバージョンをあげること以外であまり盛り込むことができませんでした。

スライド作りについて

視認性の高いテキストサイズ、コードのシンタックスハイライト、細かい文言や単語の表記揺れなどPCでスライドを作っているときは気にならなくても、実際に話してみると、見づらいスライドに当初はなってしまってました。

特にコードのシンタックスハイライトは結構悩んで、最初はダーク系にしていたんですが、ライト系にした方がいいとアドバイスもらったのでそちらを採用しました。
幸い、自分のセッションのときは部屋を暗くしてもらったので、シンタックスハイライトが原因でコードが見づらい、ということはなかったと思います。
ダーク系の場合、部屋が明るかったときに見えづらい可能性もあったので、この辺の塩梅がとても悩ましかったです。

全く知見がないので、スライドで視認性が高く、かつ部屋の大きさや明るさに左右されないシンタックスハイライトのテーマについて教えて欲しいです...。そういうエントリとかないかあ...。

また、スライドを作りながら、テキストは少量で、テキストサイズは大きいに越したことはない、ということを感じました。
作成してる時はPCに向かっているので、特に文字サイズについて気にすることがありませんでしたが、GoCon前に社内で発表練習していたときに、文字サイズのフィードバックを多くもらいました。
自分がちょうどいいと思うサイズは案外小さく見づらく、大き過ぎでは?と思うサイズくらいがちょうどいいのかなと感じました。

なお、こちらは余談ですが、Googleスライド力がまた少し上がりました。 Outputする機会があるごとに新しい機能を知っている気がします。今回も2つくらい新しい機能を知ることができました。
Googleスライドってとても便利ですね(小並)

スピーチについて

これが一番難しかったです。
ストーリーに仕立てることに非常に苦労しました。
スライド間のつなぎの言葉というか、話してるスライドから次のスライドに行くときに、どういう接続をすると綺麗にプレゼンが流れていくのかは試行錯誤しました。

今回に関しては無理矢理繋げていった、という感じは否定できないので、スライドのアウトラインに加えてストーリー構成を考えてからプレゼンを組み立てていくといいのかなと思いました。

ちなみに、自分は普段めちゃくちゃ早口で、今回みたいな緊張したときは、いつも以上にさっさと進んでいくのかと思いきや、なぜか当日登壇してみたらスライドが全く進まず、なんでや!となりました。
(時間計りながら、なんで進まんのや...と自分が一番驚きました。)

感想と今後の意気込み

不安だらけでしたが、なんとかやり切れたのでよかったです。

2月に社内の技術勉強会へ登壇したときですらとても緊張したのに、それから3ヶ月後に、社外で、かつ現在自分が使用してるソフトウェアの大きなカンファレンスに登壇するなんて考えてもみませんでした。来週誕生日なので28才最後の週末にいい経験ができました(笑)

反省も多かったので Autumn 目指してまた登壇できそうなネタを貯めていきたいと思います。
(次回もまた App Engineネタかなぁ....)

最後に、初登壇で不安だらけだった中、スライドのフィードバックや発表練習などで同僚の皆さんに多大な協力をいただきました。感謝です。
自分一人ではどうすることもできませんでしたmm

おしまい。懇親会のベイスターズビールはとても美味しかったです(ステマ

メモリアロケーションなしで slice をフィルタする

令和最初のエントリです。
連休前に教えてもらったことについてまとめました。

このエントリに記載する内容については、githubSliceTricks · golang/go Wiki · GitHub に記載されてる内容になります。

go の sliceについて

go の slice はポインタ型です。また、go で slice を初期化する際には、通常はメモリアロケーションが走ります。

// sample
a := []int{1, 2, 3}
b := make([]string, 0, 0)

go の slice については Go Slices: usage and internals - The Go Blog この辺が詳しいです。

slice をメモリアロケーションなしでフィルタする

ある slice から特定の値を持ってるものだけを抜き出し、別の slice にコピーするようなユースケースを考えてみます。
素直にコードを書くと以下のようになると思います。

func main() {
    arr := []string{"apple", "banana", "orange"}
    arr2 := make([]string, 0, 0)
    for _, v := range arr {
        v := v
        if v == "apple" {
            arr2 = append(arr2, v)
        }

    }

    fmt.Printf("arr:%v, address: %p\n", arr, arr)
    fmt.Printf("arr2:%v, address: %p\n", arr2, arr2)
}

// 出k力結果
// arr:[apple banana orange], address: 0x43e260
// arr2:[apple], address: 0x40c128

通常、こういったある slice の中から特定の値を取り出した slice を作り直したい(slice の値を詰め直す)ときは、別に詰め直す用の slice をインスタンス化しておいて、loopで回して詰め直すのが一般的な方法だと思います。

しかし、この方法だと、ソースとなる slice と詰め直す先の slice で二重にメモリアロケーションが必要になります。
※ 出力結果でもアドレスの値は異なります。

しかし、現実世界でプロダクトを運用しているとメモリが厳しく、slice 一つとっても極力メモリ空間を使いまわして、メモリアロケーションを走らせることなく slice をフィルタリングしたいユースケースってあると思います。ソースの slice で使用されてるメモリをうまいこと再利用して slice を詰め直す方法が SliceTricks · golang/go Wiki · GitHub に書いてありました。

この gowiki に記載されてる Tips を元に実際のサンプルコードを書き直してみます。

func main() {
    arr := []string{"apple", "banana", "orange"}
    arr2 := arr[:0] // ここが異なる。
    for _, v := range arr {
        v := v
        if v == "apple" {
            arr2 = append(arr2, v)
        }

    }

    fmt.Printf("arr:%v, address: %p\n", arr, arr)
    fmt.Printf("arr2:%v, address: %p\n", arr2, arr2)
}

// 出力内容
// arr:[apple banana orange], address: 0x43e260
// arr2:[apple], address: 0x43e260

ref: https://play.golang.org/p/D9ubw_22WTw

出力を確認すると、詰め直した先の slice もソースとなった slice のアドレスと同じなので、うまくメモリを再利用できています。

注意点も gowiki には記載してあります。

This trick uses the fact that a slice shares the same backing array and capacity as the original, so the storage is reused for the filtered slice. Of course, the > original contents are modified.

以下のような slice の各要素の値を変更する場合、同じアドレスで詰め替えを行うので元のソースの slice も書き換えてしまいます。

func main() {
    a := []string{"a", "b", "c"}
    b := a[:0]
    fmt.Printf("a: %[1]p = %+[1]v\n", a)

    for _, aa := range a {
        aa := aa
        b = append(b, fmt.Sprintf("1-%s", aa))
    }

    fmt.Printf("b: %[1]p = %+[1]v\n", b)
    fmt.Printf("a: %[1]p = %+[1]v\n", a)
}


// 出力結果
// a: 0x43e260 = [a b c]
// b: 0x43e260 = [1-a 1-b 1-c]
// a: 0x43e260 = [1-a 1-b 1-c]

ref: https://play.golang.org/p/LxYnXCUNpjP

MicroServices本3兄弟を読んで考えたこと

平成最後のエントリです。(ギリギリ間に合った...)
10連休中の目標であった『進化的アーキテクチャ』を読み切ることができたので備忘録を残します。

※ 僕は別に設計について明るいわけでもマイクロサービスについて明るいわけでもありません。

サマリ

  • 『マクロサービスアーキテクチャ』、『プロダクションレディマイクロサービス』、『進化的アーキテクチャ』のマイクロサービスアーキテクチャ本3兄弟(僕が勝手にそう呼んでるだけです。)を読み終えました。
  • 拡大するフェーズのマイクロサービス化を終えて、運用人数が限られて行く中でのマイクロサービスをどう運用するのかに最近の個人的な関心があります。

雑感

下記三冊を2年がかりくらいでようやく読み終えました。

そこで考えたことをまとめてみます。 マイクロサービスアーキテクチャって昨今、様々な企業で採用されるケースがとても増えてきてると思うんですよ。
特にモノリスで作られたサービスを分割する、という文脈でのマイクロサービスを採用するケースをよく目にするようになってきました。(個人の観測範囲の話です。)

僕も業務でマイクロサービスアーキテクチャで作られたサービスの運用に関わっています。
ただ、運用をする中で最近、成熟したプロダクトでマイクロサービスアーキテクチャを採用してる際にどう運用していくべきなのか、ということを考えてます。

というのも、組織や企業が拡大するフェーズにおいてマイクロサービスを採用するのは、マイクロサービス本に書かれてる通り嬉しいことが多いと思います。
その一方で、サービスとして成熟してしまって、自動化などもありつつ、開発・運用する人数が限られていく(減っていく)にも関わらず、マイクロサービスの数は変わらず、エンジニア1人が見るべきサービスの守備範囲(ドメインの守備範囲と言ってもいいかもしれません)が広がっていって負荷が上昇していく可能性がある場合に、どう安定的にサービスを運用していくのか、ということに個人的に興味があります。
サービスを拡大する中で、マイクロサービス化して、疎結合で、技術スタックも柔軟に、デプロイ頻度も増やして、プロダクトの改善イテレーションを高速に回していく、というのはプロダクトと組織が拡大する前提があってこそでは?と最近考えるようになりました。プロダクトが現状維持、開発メンバーの工数削減傾向というフェーズにある場合はどうするのか、まだその実践的な活きた情報は多くありません。

先を見据えながら、開発サイドがつらくならない程度でマイクロサービスを少しずつ減らしていく(統合していく)方向もあるでしょうし、さらなる自動化もあるでしょう。(あんまり積極的ではないですけど)ドキュメンテーションで解決できることもあるかなと妄想してます。

マイクロサービスは事実として採用されるケースが増えてきてますが、ある時点で、ナノサービスではない粒度で、適切な粒度に切られていたマイクロサービス達は、プロダクトそのものが成熟し、本当の意味での運用フェーズに入ったときにどういう手法をとるのがベターなのか、その実践的なプラクティスをどんどん知りたいなと思っています。

平成最後の雑エントリ終わり。令和でもアウトプットの継続頑張るぞ。

CircleCIの高速化ログ

Overview

circleci でのビルド時間の高速化をやったので、その備忘録です。

方針

circleci の高速化 = ビルド時間の短縮になるんですが、そんなことしたことなかったのでまず何をするかを考えました。
circleci の中でやってることは基本的には、手動で叩いてるコマンド群を設定することで自動でゴリゴリタスクを回してる、というだけです。
そのため、このタスクの実行時間を短縮することがそのまま circleci のビルド時間の短縮に繋がります。

やったこと

cacheを使う

npm install した結果の node_modules や パッケージ管理ツールで取得した中身をキャッシュして、キャッシュが存在する場合は、パッケージのインストールプロセスをスキップするようにしました。

circleci.com

steps:
      - restore_cache:
         keys: cache-yarn-{{ package.json }} // key名は適当
      - run:
          name: install node_modules
          command: |
            if [ ! -e node_modules ]; then
              yarn install // node_modulesがなければ yarn install が走る。cacheを解凍してすでにあれば走らない。
            fi
      - save_cache:
         key: cache-yarn-{{ package.json }}
         path: 
           - ./workingDir/node_modules

なんだかんだこれが一番効果がありました。npm にしろ yarn にしろ1分近くかかっていたのでcacheするだけで丸々1分削除できたのは大きかったです。

unittestを高速化する

普段 gae/go で開発をしているので、GitHub - favclip/testerator: test accelerator for appengine/go を使って appengine を使ったテストを高速化しました。
なぜ高速化できるのかについては以前のエントリでも記載しました。

ema-hiro.hatenablog.com

詳しくは testerator - GoDoc にある通りですが、インスタンスの残機が1つ以上残ってるケースではインスタンスを完全に落とさないので、起動が早くなります。 TestMain をうまく使いながら常に残機が1以上残ってるようにテストを組み立てると恩恵が大きいです。

fmtかけるファイルを減らす

gofmt などの format や lintツールを使ってる場合 vendor ディレクトリを検査対象から外すことで多少速くなります。

まとめ

circleci のビルド時間短縮なんてやったことなかったですが、案外やり始めると楽しかったです。
少しでも他の開発者の人が待ち時間少なくなると嬉しいなと思いました。