emahiro/b.log

Drastically Repeat Yourself !!!!

「Vueの学習を始めたぞ」の巻

大したこと書いてません。 Vue を覚えないとそろそろいけないので触りからはじめました。

手順

環境

go + vue.js
※ 静的なファイルをサーブできればなんでもいいのでgoじゃなくてもいいと思います。

環境用意

goの環境用意

mkdir todo; and mkdir todo/src; and mkdir todo/src/todo
cd todo; and touch .envrc

emacs .envrc
export GOPATH=$(pwd)

direnv allow
direnv: loading .envrc
direnv: export ~GOPATH

cd todo/src/todo
dep init
touch main.go

とりあえずざっくりこんな感じでgoの動く環境を用意します。

ディレクトリ構成
todos
  /src
    /todo
        main.gp
        handler/main.gp
  /tmpl
    /layout
      index.tmpl 

静的ファイルを表示させる

Vueを書いていくHTMLファイルを用意します。

index.tmpl

<!DOCTYPE HTML>
<html>
<body>
<div id="app">
    <p>This is sample Vue app</p>
</div>
<script src="https://unpkg.com/vue"></script>
</body>
</html>

とりあえず簡単なvueの動作確認だけしたいので、CDNからDLするようにします。(ビルド周りとか、*.vueファイル使うのはまた次回)

Vueでコードを書く

イベントを作成する

イベントを作成します。 カウントアップするだけのコードを書きます。

new Vue({
    // describe options
    el: '#app',
    data: {
        count:0,
    },
    methods: {
        countUp: function () {
            this.count++
        }
    }
})

イベントリスナーを登録する

methods に記載した countUp をclickイベントに登録します。登録の仕方は以下の2つの記法があります。

<button v-on:click="countUp">countUp</button>
<button @click="countUp">countUp</button>

@ が省略記法らしい。この辺どちらを使うのがVueっぽいのだろうか...

双方向データバインディングを書く

<div id="app">
  <input type="text" v-model="text">
  <p v-html="text"></p>
</div>
new Vue({
    // describe options
    el: '#app',
    data: {
        text:""
    },
    methods: {
        // 何も書かなくていい
    }
})

(これだけで最小限の双方向データバインディング書けるの結構感動しました。ロジックに何も書いてない...)

textareaを使用した場合は改行た適切に反映されないので出力側のスタイルを調整します。 cf. フォーム入力バインディング — Vue.js - 複数行テキスト

その他、フォーム入力バインディング — Vue.js - 基本的な使い方 に各種要素の使い方が記載せれてますが、 methods にロジックを追加せずとも双方向データバインディングできるのはマジで便利。

コンポーネントを作る

Vueでは機能ごとのコンポーネントを作成してそれを組み合わせることによって画面を組みたてることができるので、そのパーツを作ってみました。

以下のように Vueインスタンスの内部でコンポーネントを追加します。

new Vue({
    // describe options
    el: '#app',
},
// componentを作成
Vue.component('item', {
    template: '<div><span v-html="content"></span> world</div>',
    props: ['content']
}))

※ #appにVueがマウントされている必要があります。

props

テンプレに値を渡すときには props を使います。 props にproperty名を指定しておくと、それをテンプレートにbindしてくれます。

API経由で取得した値なんかを props を使ってbindしておくとかに使えそうだなっと思いました。

まとめ

関わっているプロジェクトでも使っているのでVueの触りを学習し直してみましたが、ドキュメントの豊富と記法の容易さというのは非常に良い点だと思いました。
正直触りしかやってませんし、コンポーネントの考え方とvuexまで追いつかないといけないのでこれからですが、jqueryを脱CDN経由で入れれば実行環境問わないし、単純なプロジェクトで考えればこれでいいのではとも思ってます。
ライフサイクルも単純なのでマウント以前にAPI通信をするなど、各Vueのライフサイクルでやることをはっきりさせることができそうだなという印象も持っています。

もう少し色々触ってみたいと思います。

コードはこちら

github.com

【Go】WebサーバーでLogを表示する

やりたいこと

Goで立てた簡易webサーバーにアクセスした時に標準出力でLogを表示させるようにします。

webサーバーを立てる

/src/todo
  main.gp
  handler/main.gp

main.go

package main

import (
    "fmt"
    "net/http"

    "todos/handler"
)

var addr =  ":3000"

func main() {
    router := http.NewServeMux()
    router.HandleFunc("/", handler.Index)
    fmt.Printf("[START] server. port: %s\n", addr)
    if err := http.ListenAndServe(addr, router); err != nil {
        panic(fmt.Errorf("[FAILED] start sever. err: %v", err))
    }
}

handler/main.go

package handler

import "net/http"

func Index(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("hello"))
}

ただし、このWeb Serverを起動(go run main.go)して、 http://localhost:3000 にアクセスしてもターミナル上で標準出力でありがちなリクエストログはされません。

go run main.go
[START] server. port: :3000
# ここに標準出力でリクエストをログを表示させたい。

ログを表示させる

ログを表示させるには、Log用のhandlerを用意して、Listenする時にそのログを出力するhandlerを通るように登録します。。 cf. Google グループ - Http Server and logging?

handler/log.go

package handler

import (
    "fmt"
    "net/http"
)

func Log(h http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        rAddr := r.RemoteAddr
        method := r.Method
        path := r.URL.Path
        fmt.Printf("Remote: %s [%s] %s", rAddr, method, path)
        h.ServeHTTP(w, r)
    })
}

main.goを以下の用に修正します。

if err := http.ListenAndServe(addr, handler.Log(router)); err != nil {
  panic(fmt.Errorf("[FAILED] start sever. err: %v", err))
}

ポイントはLogのhandlerの登録の仕方です。 Log出力用のhandlerを登録する場合は http.ListenAndServe(addr, hanlder) のhandlerの引数にrouterを単にrouterを追加すればいいのですが、以下のように middleware的な handlerの登録の記法が使えます。

func SomeMethod(h http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
}

return で返す http.HandlerFunc method内部でリクエスト毎に行いことをつらつら書いていけばなんでもできます。

まとめ

FWとか使っていると、この辺いつもFWが吸収してくれててブラックボックス化されてたんだなと痛感しました。
そういったところ、FW側でどう動いているのかわからずに使っていることが多かったので、どんな言語でも簡易的でいいので一旦web server作ってみるというのは学習としてはいい「車輪の再発明」だと思いました。

Vue.js書くのに役に立ちそうなリンクまとめ

vue書くのに役に立ちそうなリンクを備忘録でまとめます。

Links

少しずつここを更新してく(予定)

『エンジニアリング組織論への招待』を読んだ

話題の『エンジニアリング組織論への招待~不確実性に向き合う思考と組織のリファクタリング』をGWに読みましたので感想をまとめてみます。

感想

話題になっている書籍は読んでみるタイプでざっと通読したあと気になるところを二度読みました。
個人的な感想としては、かなり「確かに」とか「わかるわかる」と何度も頷きながら読み進めてましたし、普段の仕事をしている中で感じていた課題感をうまく解説してくれている書籍で、読んでてすっと内容が頭に入ってくる良い書籍でした。

章立て

内容は実際に書籍を読んでみて欲しいのですが、章立てが以下のようになっています。

  1. 思考のリファクタリング
  2. メンタリングの技術
  3. アジャイルなチームの原理
  4. 学習するチームと不確実性のマネジメント
  5. 技術組織の力学とアーキテクチャ

この中で個人的に印象に残っていたのは1章の内容です。1章でも本当に最初の方で本書を読むに当たっての頭の整理の内容が自分の中では強く頭に残っています。

印象的だった内容

第一章から「リファクタリング」という言葉が使われているとおり

頭の中で発生してしまう無駄なプロセスを削除して、考えるときの指針をもつことで、問題解決に向かって明確に行動出来るように促すもの

と章の頭で説明しています。

この書籍を読んだからと行って、「問題解決能力が爆発的に上がる」とか「不確実性に対する有効なソリューションを得た」ということはありませんが、NEXT ACTIONを明確にできる、というは確かにあると思います。

そもそも、この書籍を読むまで普段の業務中で「あれ?なんかうまく進まないな?」っという漠然とした感覚が湧いてくるし、そういうのが蓄積されると、テンパって思考が停止してしまうことが自分はよくありました。ただ、それがどういう原因なのか、考えてもうまく言語化出来ないことがあったのですが、それが 不確実なものを潰してない、そもそも何が不確実なものかがわかってない し、その 不確実なものと向き合ってない ことって結構多かったなと気づきました。

1章で最初の方で語られている内容は以下の三点が中心です。

  • 論理的思考の盲点
  • 経験主義と仮説思考
  • システム思考

この中で 論理的思考の盲点 については個人的にも共感するところが大きかったです。
何より納得感が大きかったのが、論理的思考って基本的にもてはやされる類のものであるんですが、本書では

他人が介在する問題について、私たちは感情的にならざる得ない生き物

というように、人間が他社とコミュニケーションを取る際にベースが論理的でないということを先に言ってしまっています。その上で

  • 共同作業を行う上で無意識的に感情的になってしまうこと
  • 複数人で問題解決の仕事を行う上で、コミュニケーションの失敗が生まれること
  • ここでの失敗とは
    • 「私はあなたではない」という単純なことを忘れる
    • 自分の事情が全て相手に伝わっているのだという勘違い

を指摘しています。

論理的思考は重要な能力ですが、コミュニケーションの失敗によって、制限されたり、自分の正解は相手にとっての正解でないという前提を忘れてしまうと、途端に論理的なコミュニケーションは破綻する、というそもそものスタート地点を理解しておくって他人と仕事上のコミュニケーションをする上で大事だと思います。

もう一つ、1章で印象に残ったことが 「アンガーマネジメント」 です。

僕は他人に対してアンガーを抱いたことはほぼありません(怒られたことは数知れず...(笑))
そもそも怒るのが苦手、というもありますし、沸点が上がりにくいという性格が起因しているような気もしています。

ただ、もし自分が他者の所為に対して、何か指摘しないといけない、場合によっては叱った上で、うまくコミュニケーションを取って、改善につなげてほしいというときが来た時にどう伝えるのがいいのかわからないなと思っていたんですが、本書に1つの手法としてあった以下の内容は「確かに!」と思わされました。

「怒り」を「悲しみ」として伝える

これはかなり目からウロコでした。自分も怒りをぶつけられるとどうしても自己防衛本能が働いてしまい、相手の意見を素直に受け入れることができないことがたまにありますが、「〇〇をわかってくれなくて(自分が大事にしている価値観がぞんざいに扱われて)悲しい」と、相手の価値観と怒った(悲しみの)原因もセットでコンテキストとして伝えられると、相手を理解できなかった自分に否があると自責で考えることが出来るようになると思います。
(あくまで個人の主観です。)

怒りと悲しみって対極にある感情だと思うんですが、真逆であるということで、相手にとっては怒られると思っていたのに、何故か悲しみ(悔しさ、無念さもあると更に有効?)を伝えられると、そもそもガードが緩んで、何故怒っているのか(悲しんでいるのか)の原因がすっと届くのでは、という仮説は確かに確度が高いように感じますし、自分事で考えてみてもそうだと思います。

当分今の自分のおかれている状況で、他人を怒るなんてことはないと思いますが、1つの手法として覚えておこうと思えるくらいいい内容でした。

まとめ

感想にも記載しましたが、エンジニアとして働いている中で日々疑問に感じていたり、うまくいかないなーって思っていたことをうまく言い得ていて、とても納得感をもって読み進められる書籍でした。
エンジニアだけでなく、エンジニアと業務で関わる方はきっと面白く読めると思います。
また、これから働くというより、ある程度業務をこなしてからの方が頭に入ってきやすいように感じました。
やらないとわからないことはある と考えるタイプなので、エンジニアってこういう生き物、コミュニケーションにはこういう齟齬が生じる、様々な不確実性と日々戦わなければならない、というのはある程度経験(少なくとも1年くらい)してみないと実感として湧いてこないものだと思っています。

プロマネの友人にも薦めましたし、非エンジニアの方である程度業務をこなしたことがある方であれば3->4->1->2->5という風に読み進めるのがいいのでは?と思います。
理由は具体的な手法やユースケースを理解した上で、その大本の議論を見ると良いと思ったからです。
5章を最後に置いたのは、エンジニア的な内容が多かったので5は非エンジニアの方が読むと難しいのでは?と思ったからですが、普通に読めるとも思います。

久しぶりに頭の中が整理されたと実感できるいい本に出会いました。

『教養としてのテクノロジー』 を読んだ

『教養としてのテクノロジーーAI、仮想通貨、ブロックチェーン』を読みました。

GWに帰省するときの下りの新幹線での時間潰しに読んだんですが、去年辺りから話題になりつつあるワードの大枠を理解するには良い書籍だと思いました。

章立てとしては前半のテクノロジーの概略を俯瞰しつつ、後半の「教育」「人間」「日本人」についての議論を展開していて、個人的には後半の方が楽しめました。

トレンドワードを使ったビジネスモデルやサービスを目にするたびに、それらは 手段であって目的じゃない ということをいつも感じます。

ブロックチェーンにしろAIにしろ、それらは「何かの目的を達成するために使うもの」でしかないと思っているので、そもそも使ったことでどう変わるんだっけ?ということや、自分たちは何がしたくて、それらのツールを使用するのか?といったことを大本におかないと行けないなーと。

ちなみに、目次だけですが、両親が目を通していて、親世代もニュースや新聞でよく目にするワードだから興味はあるんだなーということが結構新鮮に感じました。

ポインタ型のvarでの初期化でハマった話

ハマったところ

CLIで特定の画像をbinary化してから画像にしてPostで送信する、というコードを書こうと思った時にポインタ型の初期化についてハマったのでメモ。

ざっくり書きたかったコードは以下(※ コードは適当)

func UploadImgFile() error {
  // 画像を開く
  f, _ := os.Open("imagePath")
  
  // form-dataを入れるバイナリを箱を用意
  buf := new(bytes.Buffer)
  
  // 箱は multipart型にする
  w := multipart.NewWriter(buf)
  
  // form-dataを作成する
  ff, _ := w.CreateFormFile("filedName", "test.png")
  
  // io.Copyでform-dataにOpenで取得した画像を書き込む
  if _ , err := io.Copy(ff, f); err != nil {
    panic(err)
  }
  w.Close()
  
  // upload処理hogehoge
  client := http.Client{}
  req, _ := http.NewRequest("POST", "Upload先のURL", buf)
  resp, _ := client.Do(req)
}

cf. multipart - The Go Programming Language

こういった処理を書こうと思った時の以下の箇所

// form-dataを入れるバイナリを箱を用意
buf := new(bytes.Buffer)

// 箱は multipart型にする
w := multipart.NewWriter(buf)

ここでのbytes.Bufferの初期化方法についてハマりました。

multipart型でWriterを初期化可能なパターン > The Go Playground

Writer初期化時点でpanicが起きるパターン > The Go Playground

panicになってしまうかどうかはぬるぽを踏んでしまうかどうか、という1点に尽きるのですが、panicになるケースにおいては ポインタ型の変数領域だけ宣言しても、初期値のままなんでゼロ値でポインタ型なのでnilになる

panicにならないケースについては newで領域確保してそのポインタを返しているのでnilでないのでうまくいく

multipart - The Go Programming Language

を見ると引数に指定するのは io.Writer 。しかし、io.Writer が実装されているのはポインタ型に対してなので、引数に渡すべき butes.Buffer はポインタ型でなければなりません。
cf. src/bytes/buffer.go - The Go Programming Language

そのため、varで単に初期化した場合でもアドレス型にして渡してあげればコンパイルエラーは起きず、処理を続けることが可能です。> The Go Playground

まとめ

golangのvarによる初期化についてはたまに思いっきりはまってしまうことが多いのでdocument読み返しながら進めないと思わぬ所で時間を食ってしまうなー。

GCSに保存してあるデータを取得する

※ 当たり前のことでドキュメント通読すればいい話ですが、ちょっとハマったので備忘録として書きました。
Google Cloud Storage(GCS)に保存してあるデータを取得するという話です。

GCSから取得する時

storageパッケージを使います。

godoc.org

データを取得するだけであれば以下のコードを書いて終了です。

func GetDataFromGCS(w http.ResponseWriter, r *http.Request) http.Handler{
  
  ctx := appengine.NewContext(r) // Contextの作成
  clinet, err := storage.NewClient(ctx)
  if err != nil {
    panic(err)
  }
  
  reader, err := client.Bucket("BUCKET_NAME").Object("OBJECT_PATH").NewReader(ctx)
  if err := nil {
    panic(err)
  }
  
  b, err := ioutil.ReadAll(reader)
  if err != nil {
    panic(err)
  }
  
  // これでGCSから取得したデータをbinaryに変換したので、いかようにも料理可能になります。
}

僕がハマったのは以下の部分です。

reader, err := client.Bucket("BUCKET_NAME").Object("OBJECT_PATH").NewReader(ctx)

ここでBucket内の任意のpathにあるデータをstreamで取得することが出来るわけですが、このpathの指定の仕方でハマりました。

例えば https://bucketName/pathToImage/test.png という画像を取得する場合に最初

reader, err := client.Bucket("bucketName").Object("/pathToImage/test.png").NewReader(ctx)

と書いていたのですが、この Object(path string) に渡すpathの書き方が間違っていて正しくは / を付けずに

reader, err := client.Bucket("bucketName").Object("pathToImage/test.png").NewReader(ctx)

のように指定しなければ行けません。
こんなことでメッチャハマってしまったわけです...

Objectを取得するときの流れ

ただ、ハマってしまっても仕方ないので、Object(path string) を取得するまでの流れを追っかけにライブラリの中まで見てみると cloud.google.com/go/storage/bucket.go にObjectメソッドの実装があります。

// Object returns an ObjectHandle, which provides operations on the named object.
// This call does not perform any network operations.
//
// name must consist entirely of valid UTF-8-encoded runes. The full specification
// for valid object names can be found at:
//   https://cloud.google.com/storage/docs/bucket-naming
func (b *BucketHandle) Object(name string) *ObjectHandle {
    return &ObjectHandle{
        c:      b.c,
        bucket: b.name,
        object: name,
        acl: ACLHandle{
            c:           b.c,
            bucket:      b.name,
            object:      name,
            userProject: b.userProject,
        },
        gen:         -1,
        userProject: b.userProject,
    }
}

この中の ObjectHandle を更に追っかけてみると clodud.google.com/go/storage/storage.go

// NewReader creates a new Reader to read the contents of the
// object.
// ErrObjectNotExist will be returned if the object is not found.
//
// The caller must call Close on the returned Reader when done reading.
func (o *ObjectHandle) NewReader(ctx context.Context) (*Reader, error) {
    return o.NewRangeReader(ctx, 0, -1)
}

というどうやらreadをしている場所があって、この (o *ObjectHandler) NewReader (ctx context.Context) の中の NewRangeReader を見てみると

// NewRangeReader reads part of an object, reading at most length bytes
// starting at the given offset. If length is negative, the object is read
// until the end.
func (o *ObjectHandle) NewRangeReader(ctx context.Context, offset, length int64) (*Reader, error) {
        // 略
    u := &url.URL{
        Scheme:   "https",
        Host:     "storage.googleapis.com",
        Path:     fmt.Sprintf("/%s/%s", o.bucket, o.object),
        RawQuery: conditionsQuery(o.gen, o.conds),
    }

}

読み込む対象のpathを

Path:     fmt.Sprintf("/%s/%s", o.bucket, o.object)

という箇所がありました。ここがどうやらbucketのpathをしている箇所みたいです。

当初ハマっていたときのような /pathToImage/test.png のようなpathを取ってしまうと

fmt.Sprintf("/%s/%s", o.bucket, o.object) -> // bucketName//pathToImage/test.png

のようなpathになってしまうわけで、この結果指定されたpathでは object doesn't exist が出てしまっていたわけです。

まとめ

なんかソースコード読めばすぐにわかったことで半日くらい悩んでしまってました。。。

空のstructにはomitemptyが効かない

以下のようなコードを書いたときに

https://play.golang.org/p/JVvctX-ir-w

{} を返ってくることを期待しているのに、 {"field":{}} が返ってくる、ということにずっとハマっていました。

これは encoding/jsonomitempty の振る舞いとして空のstructにomitemptyのtagを指定しても、空structとして扱ってしまって(値が入っているものとして扱ってしまって)key除外されないというものに起因していました。

The "omitempty" option specifies that the field should be omitted from the encoding if the field has an empty value, defined as false, 0, a nil pointer, a nil interface value, and any empty array, slice, map, or string. json - The Go Programming Language

pointer型のstructにした場合には、key除外されます。

https://play.golang.org/p/YjD9D_Ot-Ot

そもそものomitemptyの挙動を理解してませんでした。。。。

omitempty むずかしす。。。。

2018年始の目標の4半期報告

前回更新から少し間が撒いていまいましたが、新年度が始まったので年始に決めたことの経過と4半期振り返りをしようかなと思います。

ema-hiro.hatenablog.com

エンジニアとしてコミットしたといえるプロダクトを作る。

業務の話なので詳しいことは書けませんが、1~3月で1つ大きな案件を消化したので、こちらは少しは達成できてるかなと思います。
今現在も別の面白そうなプロジェクトに関わってて、しんどいこと大半だし、それのお陰で最近ブログ書く時間すら取れていなかったのですが、キャリアの中では1つのモノ作りに集中できてる期間だと今のところは言えそうです。

これは引き続き継続していきたいですね。

月2冊以上の技術書を読む

1月は出来たけど2、3月は出来てないでいました。 新しい知識を詰め込むというより、温故知新じゃないですけど、基礎的なところ、HTTPの仕様だったり設計のinputだったり、業務で必要になるために技術書じゃない仕様書やドキュメントを読む機会が多かったです。
新しい知識を得るため、よりも今まで読んでた本を読み直したり、改めて復習したりと言ったことが多かったです。

少し仕事も落ち着いたので、また新しく色んな技術書を漁りたいと思っています。

githubへの1commit/weekをしていくこと

全く出来ませんでした。進捗0です。
特に言語を書くというよりも書く一歩手前の知識のinputが必要でその知識を使って仕事をこなしている期間がメインで、新しく何か作ったり、何かを試そうというモチベーションがあまり今現在湧いてこなかったのが原因です。

こちらはまだ、やりきれないですし、その前時点の知識のinputをしないと行けないので、これからの夏くらいまでは一旦こちらは目標から外したものとして扱おうと思います。

新しいサービスは積極的に使っていく

これは継続的に出来ている気がします。とは言え、新しく使い始めたもので未だに使っているものはほとんどなく、むしろiPhone内のアプリを断舎離しています。

そんな中でも年度初めから使うようになった Voicy というサービスがあるのですが、これはすごく良いなと実感します。

voicy.jp

今まで耳から情報を得るということをしてこなかったのですが、音楽聞かない場合、耳って自分自身の感覚の中で常に暇している場所だなと気づいて、朝の通勤中とかにベンチャーニュースとか経済ニュースをずっと聞いて耳からながらで情報を収集しています。

耳からなので、習慣化するのもすごく楽で今では1習慣に3~4回くらいは通勤で必ずvoicyを使っています。

新しい言語を一つ仕事レベルで習得すること

まだ出来てないですね。目標はnodeですがまだ着手できていないです。
ただ、これは少し進めたいので夏までは頑張って時間を捻出して頑張ります。

まとめ

案外進捗出てないなと思いました。 自分でまとめてるのにw

もう少し、決めた目標にコミット出来るように仕事を効率的に完了させて、自分の時間を取れるようにしたいなと改めて思いました。

Lerning about Cache part1

仕事で真剣にCache戦略を考える機会があったので、これまでなんとなくしか理解してこなかったブラウザキャッシュについて調べてみました。
今回はpart1です。

参考

Real World HTTP
HTTP キャッシュ - HTTP | MDN
HTTP キャッシュ  |  Web  |  Google Developers

summary

  • キャッシュとは?
  • 更新日時でCacheする
  • etagでキャッシュする

Cacheとは?

本エントリではブラウザCacheに絞って調べました。

そもそもCacheとはウェブサイトのリッチ化の流れの中で

すでにダウンロード済みで、内容に変化がなければダウンロードを抑制し、それによってパフォーマンスを上げるメカニズム (Real World HTTP P.48)

とされています。
コンテンツの差分がなければそもそもサーバーにアクセスせずとも、手元にキープしておいたコンテンツを使うメカニズムのことで、サーバーアクセスを減らし、応答速度を高速化させてるように見せることができます。

EndUserにとっては見るコンテンツが変わらなければ、裏でサーバー通信をしていようが、手元にためてたコンテンツを表示させようが大差はありません。

通常

f:id:ema_hiro:20180326022046p:plain

Cacheあり

f:id:ema_hiro:20180326022054p:plain

ざっくり簡単に書くとこんな感じです。

リソースが最新かどうかを確認する

Cacheする以前に、リソースの更新の有無を確認する方法をまずは調べます。
というのも、あるリソースを取得するときに、200OKでレスポンスを受け取った場合、bodyに取得したリソース(jsonなどの形式)が入ってきます。
リソースも情報を含んでいるので、データのサイズに応じて通信はかかるし、取得するまで時間がかかってしまいます。このリソースが変わらないのに、毎回通信が発生したり、そもそもリソースの情報を全て取得する必要はなく、「新しい状態かどうか」だけ返してくれればbodyにリソースの情報を含めずに、手元にあるデータを再利用していいというお墨付きをサーバーから受け取ることが出来ます。

そのお墨付きを与えるパターンが以下の2つです。

  • 更新日時を利用する: Last-Modified/If-Modified-Since
  • Etagを利用する: If-None-Match

上記2つのパターンについてはCacheの有効性を確認するためにリクエストヘッダーに更新確認用の値を付与してリクエストをします。

更新日時を利用して最新状態を確認する

リソースの新旧のみを比較します。その比較のための目印として、更新日時 を利用します。
これは一番わかり易いと思いました。理由は、リソースが更新された場合は、リソース内部の UpdateAt が更新されているはずで、ここだけ見れば、そのリソースが新しい状態かどうかを確認できます。

この確認をするためには リクエストヘッダーにIf-Modified-Sinceをつけて、手元のリソースの最終更新時刻を値として付与してリクエス します。

If-Modifewd-Since: (手元のリソースのUpdateAt GMTのタイムゾーンを使用する)

変更がある(最終更新時刻が変更されている) 場合は、サーバーはそのまま通常通り200 OKを返してbodyにリソースの情報をいれてクライアントに返します。
変更がない(差一周更新時刻が変更されていない)場合は、サーバーは 304 Not Modified を返し、bodyはレスポンスに含まれません。この場合は、クライアントは手元にあるリソースを再利用可能なお墨付きを貰っている状態です。

Etagを利用して最新状態を確認する

更新日時を利用した最新状態の確認では、リソースのUpdateAtを見ることでリソースが最新状態にあるのかどうかを確認しましたが、Etagを使う方法もあります。 ※ Etag ... Entity Tag の略

この場合、リソースのデータ構造の設計時にEtagが入っていることが必要になります。

Etagをリクエストヘッダーに付与するときは If-None-Match ヘッダーを利用します。

If-None-Match: (Etag)

クライアントからリクエストする時にEtagを If-None-Match ヘッダーに付与することで、 - Etagが更新されてなければリソースに変化なしとしてリソースを取りに行かずに304 Not Modifiedを返し、bodyはレスポンスに含めません。 - Etagが更新されていれば、そのまま200OKを返して、bodyをレスポンスに含めます。

Expiresについて

文字通り 有効期限 です。
更新日時にしろ、Etagにしろ、かならずリソースの状態を確認するために、サーバーにアクセスしないといけません。リソースの状態によってレスポンスにbodyを含めるか、304のみを返すかの違いがあり、通信そのものを減らすことが出来る場合はありますが、結果として Cacheの有効性、リソースの状態を確認するための通信が発生する ことに変わりはありません。

この通信自体をなくしてしまうために、有効期限をもたせることが出来ます。
有効期限を持たせるには Expiresヘッダーを付与します。

ExpiresヘッダーにはCacheが有効な(期限切れになる) 日時 が格納されます。 ※ あとで記述するmax-ageは今からの相対秒数が格納されるのと混同しやすい

Expires: (期限切れになる日時 GMTのタイムゾーンを利用する)

ここで設定された日時はあくまでも、サーバーアクセスをするかどうかの判定にしか使われません。
(Real World HTTP)

このため、有効期限以前にアクセスした場合は、Cacheを自動的に使います。サーバーにはアクセスしません。X秒後の時間をしていても、X秒たった時点で自動的にサーバーに取り行くようなことはせず、古いコンテンツが使われ続けます。
X秒たったあとにリロードした場合にExpiresが切れているのでサーバーアクセスを行います。

動的に常に変更する要素には余り使うべきでなく、CSSやHTML内の滅多に更新されない要素に対して使うのが望ましいヘッダーです。

まとめ

とりあえずpart1としてざっとまとめてみました。Cacheについて実は挙動だけでなく、何がCacheなのか、リクエストヘッダーも含めたところから考える機会がなかったので一度おさらいしてみてよかったです。part2ではCacheControl周りを調べます。

gin✕GAEの環境でhandlerのunittestを書く

前提

goのhandlerレイヤー、MVCのApplicationで言うところのcontrollerのレイヤーのテストを今まで書いてきたことは少なかったのですが、意図しないinputに対して、正常なレスポンスを返さない(400とか401とかを返す)ようなEPを想定した時にhandlerとは言え、ちゃんとテストを書いておいたほうが良いなと思ったので、今回は普段使っているgin✕GAEの環境でのhandlerのunittestを書く方法を調べました。

summary

  • GAE上で動かしているginのApplicationにおいてhandlerのunittestを書きます。
  • 使った手順は以下の2つ
    • CreateTestContextを使って書く
    • testeratorを使って愚直に書く

CreateTestContextを使う

これはテスト用のcontextとginのrouting(engine)を提供してくれるginのテスト用のメソッドです。

refs:

godoc.org

github.com

当初はこの CreateTestContext を使って

// handler.go

func Method(c *gin.Context){
  // hogehoge
}


func TestMethod(t *testing.T){
  gin.SetMode(gin.TestMode)
 
  w := httptest.NewRecorder()
  _, r := gin.CreateTestContext(w)
  
  r.GET("/", Method)
  req, _ := http.NewRequest("GET", "/", nil)
  r.ServeHTTP(w, req)
  
  assert.Equeal(t, 200, w.Code())
}

というコードを書くことを想定してました。

しかし、1つ罠があって、手元で動かしているginのversionが古すぎて CreateTestContext が使えないことがわかりました。

ある程度のversionのginであれば CreateTestContext を使うパターンが最も効率がいいですが、これが使えないので、handlerのテスト毎にそれぞれRoutingを定義してテストを書くという方式を試しました。

testeratorを使って愚直に書く

CreateTestContext が使えないginのversionにおいては愚直にhandlerごとのテストでroutingを定義してEPに対するテストを書きます。
ただし、ginのcontextをappengineのcontextから生成するのに一工夫必要で今回は testerator というappengineのテスト高速化のライブラリの力を借りました。

これはappengineのプロセス(instance)を高速に立ち上げてくれるテスト用のライブラリです。

refs

github.com

godoc.org

qiita.com

testeratorを使うときの注意点は SpinUp した後テストが完了したら SpinDown してプロセスを明示的に完了させる必要があります。
終了させないと延々appengineのプロセスが立ち上がり続けて、ciが落ちるかもしれません。

// handler.go

func Method (c *gin.Context){
  // hogehoge
}

func TestMethod(t *testing.T){
  gin.SetMode(gin.TestMode)

  instance, _, _ := testerator.SpinUp()
  defer testerator.SpinDown()
  
  r := gin.New()
  r.GET("/", Method)
  req, _ := instance.NewRequest("GET", "/", nil)
  w := httptest.NewRecorder()
  
  r.ServeHTTP(w, req)
  
  assert.Equal(t, 200, w.Code())  
}  

これでテストをやってみると

$ goapp run -v ./ -run TestMethod
appengine: not running under devappserver2; using some default configuration
=== RUN   TestMethod
# 略
--- PASS: TestMethod 
Pass
ok ./handler

このようにテストが通ります。

まとめ

普段requestやhandlerのテストを書くことは少ないですが、inputとそれに対して返ってくるレスポンスが期待したレスポンスになっているのかはテストとして残しておいたほうが良いと思いました。

YAPC::Okinawa 2018に参加してきた話

少し日が空いてしまいましたが、先週の3/2~3/4に恩納村で開催されたYAPC::Okinawa 2018 ONNASON に参加してきました。

yapcjapan.org

YAPC::Okinawaに参加するまで

普段の業務はGAE/Goを使って開発していて、perlに触れる機会はほぼないので、perlの経験はほぼ皆無でしたが、とりあえず参加してみての感想まとめてみます。
ちなみに自分自身は渋谷にある、日本で最大規模のperlで書かれたサービスを作っている会社に所属していて、しかもperlで書かれたプラットフォームの事業部署にいながらperlに触れたことはありませんでした。(普段書いてないから当たり前なんですが。)

なら、なんでYAPCに参加してきたのかってことなんですが、単純に興味があったことと、perlが使われる理由を知ることで、仕事で使うきっかけにしたいと考えていたからです。
というのも、あまりperlをこれから学習することに対してポジティブな印象は持っていなかったので、今まで大規模なサービスを支えてきたperlについて知ることができれば少しでも自分の中で変化があるのではないかということを期待して参加してきました。

参加してみた感想

  • OISTすごい
    • 施設としてすごい
  • コミュニティが成熟している
    • コミュニティそのものに歴史を感じる
    • コミュニティに所属するエンジニアのレベルが高い(年齢も高い...)
  • perlが選ばれる理由は存在する
    • 企業の中で使う場合ツールの1つとしては有用である。
      • 他のLL言語より高速であること
      • system call friendlyであること and so on...
    • でも、perlはやはりメインストリームではなくなってきている(仕方ない側面もありあそう。)

個人的なセッションのまとめ

セッションはすでに講演者の方がスライドを後悔しているものありますし、同時刻別会場で行われていたセッションは見ることができなかったので、僕の聞いた中で印象に残っていたものをpickup してまとめてみます。

GraphQL をプロダクション導入した結果

GraphQLは個人的に興味があったものの、あまり利用例を聞くことがなかったので、プロダクションで使っている事例を聞くことができてよかったです。

普段仕事でjson-schemaを使っているので、差分だったり実運用のツライことなども聞けたのですが、個人的な感想としては 別にGraphQLでなくもていい というものでした。実運用できるのか、考えるキッカケにしたいと思っていたのですが、そこまではいけず。
でも価値ある内容を聞けたと思います。

RESTでのしんどいところや、クライアントサイドから柔軟にqueryを書いてデータを取得できるようにすることは確かに開発する上ではメリットが大きそうだし、そういう知見をこれからも知りたいなと思いましたし、機会があれば触れて、実運用まで持っていってみたいと感じました。

全部Perlが教えてくれたこと

技術的な話ではないですが、OSSに関わるきっかけがperlだったという話でした。

話自体はエモい内容だったのですが、ふと自分ごとに置き換えるとあまりコミュニティに貢献してきたことってなかったなーと聞いてて感じました。

別に無理してまでする必要もないんでしょうけど、普段恩恵受けているツールやコミュニティに対して、出来る範囲で少しでも今後は貢献していったりしたいなーと思いました。
(何をどうするかは全く考えてません。)

Perl in Mercari YAPC::Okinawa 2018 ONNASON

メルカリでのperlの採用事例の話だったのですが、このセッションが なぜperl? という問いかけに対して、最も納得の行く解を提示していたと感じました。

特にperlがsystem call friendlyでシステムプログラミングへの関心を醸成するためのツールとして使われていて、メルカリのSREは研修で必ずperlに触れているというところは、意図明確で納得感がありました。

インフラの構築をクライドがしてくれて、アプリケーションレイヤーではweb系のLL言語しか触れて来なかった自分のようなタイプは確かに、システムプログラミングなどのいわゆる低レイヤーの知見はほとんどないまま、エンジニアを続けていたこともあり、そういうperlの使い方もあるし、1つのツールとしてしっかりサービスを支えていました。

低レイヤーのことを知った上で改めてアプリケーションのコードを書くとコードの書き方にいい影響があると、低レイヤーに足踏み入れた友人からも聞いているので、知見がある状態で低レイヤーに馴染む機会があるのはすごく良いと感じました。

まとめ

総じてperlに触れた経験はほぼ皆無の状態で参加しましたが、参加して良かったです。
コミュニティの成熟度合いを知ることができましたし、セッションの内容もperlの技術的な内容に囚われず、多岐に渡っていてperl知らなくても楽しむことが出来ました。(とはいえ、知ってたらもっと楽しめたんだろうなとも思います...)

歴史のある言語だからこその知見、僕も高校生~大学生の頃にお世話になったコミュニティサービスや、未だにお世話になっているブログサービスなどの一斉を風靡した大規模サービスを支えてきた知見が詰まってるツールなんだなーと思うとperlの見方が変わりましたし、その知見は知りたいなーと興味が湧くきっかけになりました。
(実際に仕事で使うかは現時点では未定です。)

あと、仕事扱いでしたが、久しぶりに沖縄行けたので良かったです(笑)
当日大雨だったことと蒸し暑いことを除けば、花粉が飛んでないし、温かいしで最高に快適でした。

f:id:ema_hiro:20180303173916j:plain

100記事継続できた振り返りと所感とこれから

この記事がちょうど100記事目です。

一年弱くらいのんびり書いてたらいつの間にか100記事に到達しました(祝)
特に目標を決めていたわけではないですが、とりあえず気づいたら100記事書いていたので、飽き性の自分がここまで書き続けられた理由を備忘録としてまとめてみます。

継続させたモチベーション

  • wiki力を上げること
  • 無理しないこと

この2つが大きな要因だと思います。

wiki力を上げること

去年の7月からプラットフォーム開発に関わる部署に移動して、仕事をしていく中で、コードを書くこと以上にドキュメントを書くことが多くなって、仕様や設計をちゃんとwikiに残すという作業が格段に増えてました。
そうした業務をこなす中で自分の書いたwikiそのものが、自分で書いたくせに自分で読みづらくてどうにかしたいなーと思ったのがキッカケでずっと休んでいたブログ再開しようかなと思い立ちました。
(今までも幾つかのプラットフォーム渡り歩いてきましたが、とりあえずエンジニアだし、ブログ書くならはてブじゃね?みたいな軽いノリで何年も前に作成したアカウントで再開しました(笑))

自分のwiki力のなさの大本は、人に読んでもらうための文章 をキャリアを通して仕事として書いてこなかったことに起因していたので、まずは読みやすい文章を書くために

  • 下書き
  • 章立て
  • トンマナ合わせ
  • まとめを書く

という三点を意識して常に書くようにしてました。とは言え未だに h2にするか h3 するかとか見出しで悩むことはたくさんあります。

自分みたいな中小のベンチャー出身でドキュメントよりコードみたいな環境で社会人スタートすると、ドキュメント書くことを疎かにしがちだなーと改めて痛感しましたし、結局ドキュメント残しておかないと後々自分が苦労することがわかってきたので、ドキュメント文化の中で育ってきた方々は本当にすごいと感じます。
とてもじゃないけど、 コードがドキュメントだ なんてもう言えなくなりました。

少なくとも書き始めた頃よりは、少しばかりはまともな文章を書けるようになったという実感はあります。
まだまだtypoや誤字脱字は多いので、ちゃんと下書き、草稿段階で修正できるようにしないと思ってますが。

ちなみに下書き用には色々ツールを試した結果 boostnote を愛用しています。

(追記) 2020年現在すでに boostnote は使用していません。

無理しない事

深く掘りすぎない。
ボリュームにもよりますが30分程度で書き上げることが出来るくらいのコンテンツと量を意識することで隙間時間を使って継続できたんだと思います。

内容としてはちょっとしたツールの使い方や、言語や仕様のドキュメント読んだ備忘録程度のものでもいいと割り切っていたので結果続けることができたんだと思います。

地味にIDEとかの使い方ってメモっておくと価値あるなーと実感したり、案外やりたいこと同じだけど、ネット上に転がってなかったりするコンテンツってあるなーって実感したりしました。
こういうのはあとになって自分でも見返しすことがよくあります。

とは言え無理しないとは言っても、少しは意味のある記事の比率を増やしていきたいなーとは思っています。

まとめ

結果としてwiki力を上げるために少しでも文章を書く訓練として続けることが出来ました。
副次的な効果として、ブログを書くようになってからタイピング速度が更に上がり、業務でもwikiの作成速度が向上しました(笑)

また無理しないことを決め、ある程度、意味あることを残すというより「続ける」こと自体を目的化したことで、習慣が作れるようになってきました。
今となっては書いてない期間が続くと何か書かなきゃ!と思うことも増えてきました。

この習慣がついてからというもの、日々の仕事でちょっと調べたことだったり、わからなかったこと躓いたことをメモしておくクセが付きました。
あとで(業務情報に関わる部分はもちろん除外して)ブログに書こうと思い始めると、仕事上で色々気づこうと意識する回数が増えましたし、何より、ブログにまとめるには手順を覚えておく必要があるので、背景と手順まで含めてメモっておくクセがついたことは良かったなと思いました。

ただinputするよりも、output前提のinputの方がinputとしての質が高い というのは本当なんですね。

雑感とこれから

今思うと、とりあえず続けてみるっていうのもすごく良いモチベーションになっていたなと感じます。

当分はこのスタンスで行こうかなと思っていますが、それでもそろそろちゃんと意味のあるエントリーも書いたり出来るようにしていきたいなと思っていて、続けることを目的としつつもそうした目標も持ちながらゆるゆると続けていきたいなーと思います。

gae上のアプリに対してService Workerを登録する

やりたいこと

gae上で動いているアプリに対してService Workerを登録します。

やること

  • service worker用のjsファイル sw.js を登録すること。
  • sw.js ファイルのレスポンスヘッダーに Service-Worker-Allowed:/ を登録すること。

手順

1. sw.jsを読み込むには、app.yaml にてstaticファイル以下に配置した sw.js を読み込むようにします。

    - url: /sw.js
      static_files: static/js/pathTo/sw.js
      upload: static/js/pathTo/sw.js
      expiration: 0m

参考:

stackoverflow.com

2. Service-Worker-Allowed: /sw.js のレスポンスヘッダーに付与します。

// r *http.Request
r.Request.Header.Set("Service-Worker-Allowed", "/")

これを static/ 配下のroutingにセットします。

ちなみにFWのginなら以下のように書けます。

// main.go
r := gin.New()
g := r.Group("/static")
g.Use(func(c *gin.Context) {
    c.Header("Service-Worker-Allowed", "/")
    c.Next()
})
g.Static("", "./static")

これでgae上でservice workerを使うための sw.js を登録することが出来ます。

追記

20180307 GAE上で動かしているアプリ上にservice-workerを登録するためのjsを仕込むには、service-worker登録用のjsをどうサーブするのかを考えないと行けないのですが、以下の場合に置いてGAE上のデプロイで sw.js が404になってしまうという事象に遭遇しました。

  1. app.yamlファイルでsw.jsをサーブするpathを指定する。上記例では/sw.js としているところ。
  2. routingでstaticでファイルをサーブする箇所を指定して Service-Worker-Allowed ヘッダーを登録する。

skip_files.yamlやignore設定してない状態で sw.js が上がるものかと思っていましたが、サーブ元が同じファイルをyamlとroutingの2箇所で同じ指定をしているとそのファイルにアクセスできませんでした。(多分上がっていない?)

これを解決するために

  • routingでのstaticファイルのサーブ先の指定されている場合は、yamlファイルでのstaticファイルのサーブ先の指定しない。(yamlの設定はいらない)

ということがわかりました。正常に sw.js がアップロードされました。

参考にしたstackoverflowのエントリーはroutingでstaticファイルをサーブするなどを考慮していない純粋なGAEでのstatic file のサーブ方法だったので問題なかったですが、プロジェクトによってはstaticファイルのサーブをroutingで管理している場合もあると思うので、プロジェクトの状況に応じてsw.jsのサーブ方法を修正したほうが良いかもしれません。

サンプルコードには上げてしまったのですが、gin上で g.Static("", "./static") でroutingにおいてstaticファイルのサーブ先を指定している場合は、yamlファイルでの設定は特にしなくても良く、ヘッダー情報だけ追加してService-Workerが動くpathを制限すればよかったということがわかりました。

routingでstaticファイルのサーブをしている場合と、yamlで指定する場合において、何かしら競合が起きると正常に動作しないということがわかりました。

おまけ

ginでのstaticファイルの指定方法について深掘りしてみました。

ginのStaicファイルの登録メソッドの定義は以下。

// Static serves files from the given file system root.
// Internally a http.FileServer is used, therefore http.NotFound is used instead
// of the Router's NotFound handler.
// To use the operating system's file system implementation,
// use :
//     router.Static("/static", "/var/www")
func (group *RouterGroup) Static(relativePath, root string) IRoutes {
    return group.StaticFS(relativePath, Dir(root, false))
}
  • relativePath: 相対パス上記コードではGroupで /static を指定してしまっているので特に意識しなくていい
  • Dir(root, false): ProjectRootのディレクトリから対象ディレクトリまでの絶対パスを指定する。trueにすると http.Dir() と同じ動作をします。

以下のコードを見るとわかるけど true のときは File, error を返すようになります。 falseのときは File のみ返します。

// @gin/fs

type (
    onlyfilesFS struct {
        fs http.FileSystem
    }
    neuteredReaddirFile struct {
        http.File
    }
)

// Dir returns a http.Filesystem that can be used by http.FileServer(). It is used interally
// in router.Static().
// if listDirectory == true, then it works the same as http.Dir() otherwise it returns
// a filesystem that prevents http.FileServer() to list the directory files.
func Dir(root string, listDirectory bool) http.FileSystem {
    fs := http.Dir(root)
    if listDirectory {
        return fs
    }
    return &onlyfilesFS{fs}
}
// @http/fs

// A Dir implements FileSystem using the native file system restricted to a
// specific directory tree.
//
// While the FileSystem.Open method takes '/'-separated paths, a Dir's string
// value is a filename on the native file system, not a URL, so it is separated
// by filepath.Separator, which isn't necessarily '/'.
//
// An empty Dir is treated as ".".
type Dir string

func (d Dir) Open(name string) (File, error) {
    if filepath.Separator != '/' && strings.IndexRune(name, filepath.Separator) >= 0 ||
        strings.Contains(name, "\x00") {
        return nil, errors.New("http: invalid character in file path")
    }
    dir := string(d)
    if dir == "" {
        dir = "."
    }
    f, err := os.Open(filepath.Join(dir, filepath.FromSlash(path.Clean("/"+name))))
    if err != nil {
        return nil, err
    }
    return f, nil
}

refs:

http - The Go Programming Language

環境を変えた時に先にしておくといいことをまとめてみた

最初に

※ これは個人的な経験をもとにして記載しました。

Summary

転職したり、異動したりで新しい環境に所属することはエンジニアに取って珍しい話ではありませんが、新しい環境に行った時に先んじて理解しておいた方が良いと思われることをまとめて見ました。

環境編

  1. チュートリアルを読む
  2. トイレの位置を把握する
  3. 勤怠ルールを確認する
  4. New Commer 専用のチャット部屋を作る

エンジニアリング編

  • デプロイルールを覚える
  • デプロイをする

環境編

チュートリアルを読む

ある職場はいい職場だと思います。
異動直後、入社直後のチュートリアルがあると、教えてもらう側も教える側もコスト少なく、必要な情報を伝えることが出来ますし、例えば入っておくべきメーリスや、最低限のslackのチャンネルなどNewCommerが自律的にセットアップできるような仕組みはあるとすごくよいと思いました。

もしない場合は、すぐに作ったほうが良いと思います。

  • エンジニア向け
  • ビジネス向け

など色々種類はあると思いますが、業種分あるのがおそらく理想なのだと思います。

自分もそうですが、NewCommerのころの最初の1ヶ月位は結構緊張して、生産性が上がってこないので、極力、気を使うことを減らしてあげて

トイレの位置を把握する

重要です。できれば個室の個数も数えておくと良いと思います。
入りたてのころはこういう些細なところで体力消耗するので当日に調べておく、できればピークタイムを最初の一週間くらいで覚えておくと良いと思います。

喫煙者の方は喫煙所のある階、及び喫煙ルールとかも調べておくと良いと思います。近頃は禁煙の流れなので、電子タバコしか吸えなかったりとか細かいルールがあると思うので調べておくと良いかもしれません。

勤怠ルールを確認する

重要です。いくら自由な職場だったとしても最低限のルールはあるものだと思うので、そのルールは予め確認しておくといいと思います。
勤怠は同じ会社でもチームによって違ったり、職種によって異なったり、勤怠連絡の仕方とかもメールなのかチャットなのか色々ルールがあると思うので、当たり前ですが入社当日に知っておくべきだと思います。

※ NewCommer向けチュートリアルに書いて有るべきですね。

New Commer 専用のチャット部屋を作る

※ これは実際に運用されているのを見てすごく良い仕組みだと実感しています。こういうことに気づける人材になりたいです。

初日からチャットであれこれ話せる人は(いるかもしれませんが、)まれな人材だと思います。
New Commerである以上、最初の数日は同僚の顔と名前も一致しないし、ましてチャットで話されている内容なんて、まず事業も環境も知らないのにわかるわけがないと思っています。

しかも、雑談チャットとかでもいきなり自分から声をかけるのは難しいと思います。周りは気軽に聞いてねー!みたいなことを言っても、僕もそうですが、初心者がいきなり気軽に話しかけに行けるわけがありません。
結構ストレスです。

なので、予め歓迎用チャンネルとか作って、部署の人、業務で関わる人は先に入っておく。そして、New Commerはそこで色んなことをつぶやきベースで聞いてみて、部署の人はそのつぶやきを拾ってあげる。そういう流れにすると、馴染みやすくなるのではないかなと思います。自己紹介なんかもそこですると良いかなと思いました。

チャットは例え部署のprivateな雑談チャットだとしても、公共の場です。公共の場でいきなり自分の発言をするのは負荷がかかります。そのため、ある程度NewCommerのpersonalな環境を用意してあげて、ゆるふわに色んなことを話せる場を作る、そうするとチャットでの発言がしやすくなって、ひいては仕事の話もしやすくなるのではないかなと思います。

ちなみに、チャンネル名は welcome-XXXX とか tutrial-XXXX とかが良いのではないかなーと思います。対外的にもわかりやすいですし。
※ これは入社時点で予め作ってあるとすごく良いですね。

コミュニケーションは仕事の基本なので、そのコミュニケーションの最初の一歩をどれだけ後押ししてあげられるかがその人が馴染める初速につながっていくと思います。
これによりコミュニケーションが円滑に進み、仕事のスピードに繋がり、ひいては事業スピードに繋がっていくと思うので、この仕組みは本当に良い仕組みだなと思っています。

エンジニア編

エンジニアとしては

  • 開発環境構築
  • システムの理解

など最初にやるべきことはある程度決まっています。

その中で個人的に、ある程度慣れないとやらないことだけど、先にしておくと実はコスパが良いのではないかと思ったのがデプロイ です。

デプロイを先に理解する意図

開発者たるもの、作った機能のコードを書くことだけが仕事ではありません。
リリースして事業に貢献する ことが仕事だと思っています。

そのためリリースをするためのデプロイ権限をエンジニアは持たないといけないと思います。
しかしJOIN当初は当たり前ですが、デプロイ権限はなく、権限を持つ人に依頼することになるのが一般的だと思います。

デプロイ権限を持たないデメリット

リリースが自分以外の誰かに依存してしまうことで幾つか弊害があると感じます。 - リリースするだけなのに、その「誰か」のスケジュールを確認しなければ行けない - 何かバグがあった場合にも切り戻しも依頼しないといけない - つまり、自分以外の誰かに依頼する以上、案件を全て自分でハンドリング出来ない。

デプロイ権限があることのメリット

誰かに依存することがありません。

  • スピード感持ってリリースできる
  • 案件への責任感が生まれる
  • もし仮に問題があっても、実装者である自身が気付けるので、切り戻しが早く行える。

そして何より、自分から進んで案件を拾いにいけると思います。
書いたコードが実際に自分の手で世の中に届けることができる権限を先に渡しておくことで、ずっとスピード感持って開発をしていけるような気がしています。

デプロイを標準化する

デプロイ手順を簡略化、べき等化しておくことは、チームの責任だと思います。
ちょっとしたデプロイでさえ秘伝のタレ化していたり、細かな手順を踏まないと行けなかったりというだけで、開発のモチベーションが削がれます。
誰がしても同じようにデプロイ出来る仕組みを作っておくことは、受け入れチームの責務だと思います。
ある程度揃えておきましょう。

また、権限を取るためのチェック項目や、デプロイ手順もドキュメントにまとめておくと良いと思います。  

デプロイて権限管理等が悉皆されていると後回しになりがちなことだと思います。
ただ、これを早い段階で誰かに依存せずにNewCommer1人で出来るようにしておけば開発がより早く進むと考えています。

デプロイするにあたって

デプロイルールを覚える

Documentや権限付与のための簡単なチェックシートがあるといいですね。
読み終えたら自動的に権限付与、くらい裁量があるとことがスムーズに運ぶと思います。

デプロイする

簡単なチケット等の実装をして、見守られつつも早い段階でデプロイまでしておくと良いかと思います。
最初のデプロイは、冷や汗もんですよね。

僕は異動3ヶ月目くらいでようやくもらってその前後で生産性や仕事へ意識が変わった実感があったので、これは先に経験しておくとよかったなぁと後悔したところだったので記載しました。

まとめ

僕は特にそうですが、新しい環境ってめちゃめちゃ緊張します。
一緒に働く人の顔と名前も定かでない状態でいきなり100%のパフォーマンスは出せません。
NewCommerにできるだけ早く100%の力を出させてあげる、そのための小さな障害やストレスになりそうなものは予め軽減しておくことは会社、受け入れるチームの責任であると思っています。

めんどくさがらずにチュートリアルも歓迎チャンネルも用意しておくときっとNewCommerが馴染むまでの時間を軽減できます。
そしてそれがそのまま事業スピードにつながると思うので、こういうちょっとした気遣いやオーバーヘッドかもしれないことをこなしておくといいです。
最後にトータルで見たときに結果を実感するようなものなので、最初は腰が重いかもですが、ある・なしで本当に異なるなーという実感があります。

個人でもwelcomeチャンネルで即レスしたり、新しく入った方が早く環境に馴染める努力はしていきたい所存です。