t.Cleanup()で並列に動かすことのできるテーブルドリブンテストを書く

最近は副業でGoを書いています。

最近よくテーブルドリブンテストを書いています。 テストの数が多くなってくるとテストを動かすだけでも長い時間がかかるので、t.Parallel()を使い、(DB等の同時にアクセスすると不整合が起きたりする場合などを除いては)できるだけ並列にテストを動かしています。

テーブルドリブンテストを並列で行うということについては、テストを変更してエラーが起きた際にテストコード起因なのか、それとも並列に動かしていることによって起因したものなのかがわかりづらいという論点があると思いますが、そこは考えずに今回は進めて行きます。

A, Bという2つのテスト用関数がある時、A, Bを並列にテストするというのはあると思いますが、この中でテーブルドリブンテストを動かす際には、テーブルの情報によって作られたサブテストも並列に動かしたくなると思います。

つまりはテーブルをfor文で回して1回ずつテストするのではなく、あらかじめテーブルの情報からサブテストを作成しておき、並列でサブテストを全て回すということです。

func TestFunctionA(t *testing.T){
    t.Parallel() // TestFunctionA を並列に動かす

    type testCase struct {
        name string
        title string
        expectStatusCode int
    }
    
    testCases := map[string]*testCase{
        "Yurine-sanはアクセス可能": &testCase {
            name: "Yurine",
            title: "DROPKICK ON MY DEVIL",
            expectStatusCode: http.StatusOK,
        },
        "Medusa-sanはアクセス可能": &testCase {
            name: "Medusa",
            title: "DROPKICK ON MY DEVIL",
            expectStatusCode: http.StatusOK,
        },
        "zyashin-changはアクセスできない": &testCase {
            name: "Zyashin",
            title: "DROPKICK ON MY DEVIL",
            expectStatusCode: http.StatusBadRequest,
        },
    }
    
    for testName, tc := range testCases {
        tc := tc
        t.Run(testName, func(t *testing.T){
            t.Parallel()
            t.Log(tc)
        })
    } 
}

func TestFunctionB(t *testing.T){
    t.Parallel() // TestFunctionB を並列に動かす

    type testCase struct {
        name string
        title string
        expectStatusCode int
    }
    
    testCases := map[string]*testCase{
        "Rin-sanはアクセス可能": &testCase {
            name: "Rin",
            title: "Yurucamp",
            expectStatusCode: http.StatusOK,
        },
        "Ena-sanはアクセス可能": &testCase {
            name: "Ena",
            title: "Yurucamp",
            expectStatusCode: http.StatusOK,
        },
        "Nadeshiko-changはアクセスできない": &testCase {
            name: "Nadeshiko",
            title: "Yurucamp",
            expectStatusCode: http.StatusBadRequest,
        },
    }
    
    for testName, tc := range testCases {
        tc := tc
        t.Run(testName, func(t *testing.T){
            t.Parallel()
            t.Log(tc)
        })
    }  
}

このコードについては、特に何も意識せずに動くはずです。

実際に動くコード

動くとこうなるはずですね

=== RUN   TestFunctionA
=== PAUSE TestFunctionA
=== RUN   TestFunctionB
=== PAUSE TestFunctionB
=== CONT  TestFunctionA
=== RUN   TestFunctionA/zyashin-changはアクセスできない
=== PAUSE TestFunctionA/zyashin-changはアクセスできない
=== RUN   TestFunctionA/Yurine-sanはアクセス可能
=== PAUSE TestFunctionA/Yurine-sanはアクセス可能
=== RUN   TestFunctionA/Medusa-sanはアクセス可能
=== PAUSE TestFunctionA/Medusa-sanはアクセス可能
=== CONT  TestFunctionB
=== RUN   TestFunctionB/Rin-sanはアクセス可能
=== PAUSE TestFunctionB/Rin-sanはアクセス可能
=== RUN   TestFunctionB/Ena-sanはアクセス可能
=== PAUSE TestFunctionB/Ena-sanはアクセス可能
=== RUN   TestFunctionB/Nadeshiko-changはアクセスできない
=== PAUSE TestFunctionB/Nadeshiko-changはアクセスできない
=== CONT  TestFunctionB/Rin-sanはアクセス可能
    TestFunctionB/Rin-sanはアクセス可能: prog.go:75: &{Rin Yurucamp 200}
=== CONT  TestFunctionB/Nadeshiko-changはアクセスできない
    TestFunctionB/Nadeshiko-changはアクセスできない: prog.go:75: &{Nadeshiko Yurucamp 400}
=== CONT  TestFunctionA/Medusa-sanはアクセス可能
    TestFunctionA/Medusa-sanはアクセス可能: prog.go:39: &{Medusa DROPKICK ON MY DEVIL 200}
=== CONT  TestFunctionA/Yurine-sanはアクセス可能
    TestFunctionA/Yurine-sanはアクセス可能: prog.go:39: &{Yurine DROPKICK ON MY DEVIL 200}
=== CONT  TestFunctionB/Ena-sanはアクセス可能
    TestFunctionB/Ena-sanはアクセス可能: prog.go:75: &{Ena Yurucamp 200}
--- PASS: TestFunctionB (0.00s)
    --- PASS: TestFunctionB/Rin-sanはアクセス可能 (0.00s)
    --- PASS: TestFunctionB/Nadeshiko-changはアクセスできない (0.00s)
    --- PASS: TestFunctionB/Ena-sanはアクセス可能 (0.00s)
=== CONT  TestFunctionA/zyashin-changはアクセスできない
    TestFunctionA/zyashin-changはアクセスできない: prog.go:39: &{zyashin DROPKICK ON MY DEVIL 400}
--- PASS: TestFunctionA (0.00s)
    --- PASS: TestFunctionA/Medusa-sanはアクセス可能 (0.00s)
    --- PASS: TestFunctionA/Yurine-sanはアクセス可能 (0.00s)
    --- PASS: TestFunctionA/zyashin-changはアクセスできない (0.00s)
PASS

HTTPハンドラのテスト

カンが良い方ならテストケース等から「これはHTTPハンドラ周りのテストだな?」ということが解ると思いますが、実際にこのようなテストを書いてテストを書くとなると、どのような関数になるでしょうか?

特に考えずに書くとするなら、以下のような手順になりそうです。

  • テストをするハンドラを書く
  • テストの中でリクエストを待つようなサーバーを起動して待ち受ける
  • for文の中で起動しておいたサーバーに向かってリクエストを投げつける
  • responseに入っているステータスコードやBodyを使ってassertする

それでは実際に書いていきましょう。なお、今回の論点はサーバー周りの実装に対するものではないため、ある程度簡易的な実装までとします。

テストをするハンドラを書く

別ファイルでもいいですが、今回は実装を簡単にするためにとても簡素なものにします。

type Anime struct {
    Title string `json:"title"`
    Name string `json:"name"`
}

func getTestHandler() http.HandlerFunc {
    return func(w http.ResponseWriter, req *http.Request) {
        if req.Method != http.MethodPost {
            w.WriteHeader(http.StatusNotImplemented)
            return
        }

        b, err := ioutil.ReadAll(req.Body)
        if err != nil {
            w.WriteHeader(http.StatusInternalServerError)
            return
        }

        var anime Anime
        err = json.Unmarshal(b, &anime)
        if err != nil {
            w.WriteHeader(http.StatusInternalServerError)
            return
        }

        if anime.Title == "DROPKICK ON MY DEVIL" {
            if anime.Name == "Zyashin" {
                w.WriteHeader(http.StatusUnauthorized)
            } else {
                w.WriteHeader(http.StatusOK)
                w.Write([]byte(fmt.Sprintf("Hello %v San!", anime.Name)))
            }
        } else {
            w.WriteHeader(http.StatusBadRequest)
        }
        return
    }
}

getTestHandler() を呼べばハンドラが返ってきます。これをテストごとに動かせば良いはずです。いい感じに動かすとすればこうなるでしょう。

func TestFunctionA(t *testing.T) {
    t.Parallel() // TestFunctionA を並列に動かす

    type testCase struct {
        name             string
        title            string
        expectStatusCode int
    }

    testCases := map[string]*testCase{
        "Yurine-sanはアクセス可能": &testCase{
            name:             "Yurine",
            title:            "DROPKICK ON MY DEVIL",
            expectStatusCode: http.StatusOK,
        },
        "Medusa-sanはアクセス可能": &testCase{
            name:             "Medusa",
            title:            "DROPKICK ON MY DEVIL",
            expectStatusCode: http.StatusOK,
        },
        "zyashin-changはアクセスできない": &testCase{
            name:             "Zyashin",
            title:            "DROPKICK ON MY DEVIL",
            expectStatusCode: http.StatusUnauthorized,
        },
    }

    ts := httptest.NewServer(getTestHandler())
    defer ts.Close()

    for testName, tc := range testCases {
        tc := tc
        t.Run(testName, func(t *testing.T) {
            t.Parallel()

            var u bytes.Buffer
            u.WriteString(string(ts.URL))

            anime := &Anime{Title: tc.title, Name: tc.name}
            b, err := json.Marshal(anime)
            if err != nil {
                t.Fatalf("request json create failed: %v", err)
            }

            client := http.DefaultClient
            defer client.CloseIdleConnections()

            req, err := http.NewRequest(http.MethodPost, u.String(), bytes.NewBuffer(b))
            if err != nil {
                t.Fatalf("error occured at create new request : %v", err)
            }

            res, err := client.Do(req)
            if err != nil {
                t.Fatalf("test api request error: %v", err)
            }

            defer res.Body.Close()

            body, err := ioutil.ReadAll(res.Body)
            if err != nil {
                t.Fatalf("response read failed: %v", err)
            }

            if tc.expectStatusCode != res.StatusCode {
                t.Fatalf("unexpected response status code: want %v, but got %v, response body: %v", tc.expectStatusCode, res.StatusCode, string(body))
            }

            var resdata Anime
            err = json.NewDecoder(res.Body).Decode(&resdata)
            if reflect.DeepEqual(anime, &resdata) {
                t.Fatalf("unexpected server response want: %v but got %v, response body: %v", anime, &resdata, string(body))
            }
        })
    }
}

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

しかし、これで動かすと上手く動きません。

=== RUN   TestFunctionA
=== PAUSE TestFunctionA
=== CONT  TestFunctionA
=== RUN   TestFunctionA/Medusa-sanはアクセス可能
=== PAUSE TestFunctionA/Medusa-sanはアクセス可能
=== RUN   TestFunctionA/zyashin-changはアクセスできない
=== PAUSE TestFunctionA/zyashin-changはアクセスできない
=== RUN   TestFunctionA/Yurine-sanはアクセス可能
=== PAUSE TestFunctionA/Yurine-sanはアクセス可能
=== CONT  TestFunctionA/Medusa-sanはアクセス可能
=== CONT  TestFunctionA/Yurine-sanはアクセス可能
=== CONT  TestFunctionA/zyashin-changはアクセスできない
    prog.go:108: test api request error: Post "http://127.0.0.1:30426": dial tcp 127.0.0.1:30426: connect: connection refused
=== CONT  TestFunctionA/Yurine-sanはアクセス可能
    prog.go:108: test api request error: Post "http://127.0.0.1:30426": dial tcp 127.0.0.1:30426: connect: connection refused
=== CONT  TestFunctionA/Medusa-sanはアクセス可能
    prog.go:108: test api request error: Post "http://127.0.0.1:30426": dial tcp 127.0.0.1:30426: connect: connection refused
--- FAIL: TestFunctionA (0.00s)
    --- FAIL: TestFunctionA/zyashin-changはアクセスできない (0.00s)
    --- FAIL: TestFunctionA/Yurine-sanはアクセス可能 (0.00s)
    --- FAIL: TestFunctionA/Medusa-sanはアクセス可能 (0.00s)
FAIL

全部 connection refused エラーになります。つまりクエストが上手く出来ていません。

一度並列に動かすのをやめてみましょう。

テーブルからサブテストを生成する

   for testName, tc := range testCases {
        //tc := tc
        t.Run(testName, func(t *testing.T) {
            //t.Parallel()

この部分をコメントアウトします。

=== RUN   TestFunctionA
=== PAUSE TestFunctionA
=== CONT  TestFunctionA
=== RUN   TestFunctionA/Yurine-sanはアクセス可能
=== RUN   TestFunctionA/Medusa-sanはアクセス可能
=== RUN   TestFunctionA/zyashin-changはアクセスできない
--- PASS: TestFunctionA (0.00s)
    --- PASS: TestFunctionA/Yurine-sanはアクセス可能 (0.00s)
    --- PASS: TestFunctionA/Medusa-sanはアクセス可能 (0.00s)
    --- PASS: TestFunctionA/zyashin-changはアクセスできない (0.00s)
PASS
ok      command-line-arguments  0.004s

コメントアウトする、つまり並列にテストをしなくなるといい感じに動いているようです。

並列にしたいけれど、並列にすると何故か connection refused になるという状況です。

connection refusedの原因として、一番に考えられるのはサーバーが立ち上がっていないという状況です。どうやら

 ts := httptest.NewServer(getTestHandler())
    defer ts.Close()

このあたりで起動したサーバーが死んでいるようです。 どうもdeferあたりが怪しいですね。

どうもサブテストが回る前にdeferが実行され、サーバーが落ちている可能性があります。

というわけで簡単なテストのためのテストを書きました。

package main

import (
    "log"
    "testing"
)

func TestDefer(t *testing.T) {
    tcs := map[string]string{
        "a": "あ",
        "b": "い",
        "c": "う",
    }

    defer log.Printf("えお")

    for tn, tc := range tcs {
        tc := tc
        t.Run(tn, func(t *testing.T) {
            t.Parallel()
            log.Println(tc)
        })
    }
}

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

ここで問題、上のようなparallelな環境下で、deferはどのタイミングで呼ばれるでしょうか?

普通に考えれば出力としては

あ
い
う
えお

となりそうです。これは defer が最後の最後に実行されるということからも自然であると言えます。

しかし、実際の出力はこうでした

=== RUN   TestDefer
=== RUN   TestDefer/a
=== PAUSE TestDefer/a
=== RUN   TestDefer/b
=== PAUSE TestDefer/b
=== RUN   TestDefer/c
=== PAUSE TestDefer/c
2009/11/10 23:00:00 えお
=== CONT  TestDefer/a
2009/11/10 23:00:00 あ
=== CONT  TestDefer/b
2009/11/10 23:00:00 い
=== CONT  TestDefer/c
2009/11/10 23:00:00 う
--- PASS: TestDefer (0.00s)
    --- PASS: TestDefer/a (0.00s)
    --- PASS: TestDefer/b (0.00s)
    --- PASS: TestDefer/c (0.00s)

??????????????????

どうもdeferがサブテストに入る前に実行されているらしい。調べていると、同じようなことが issueに上がっているのが確認できました。

https://github.com/golang/go/issues/17791

作成者は同様なテストを書いていて、deferが先に呼ばれている問題に遭遇していた模様。 その中でなされていた会話の一部で

t.Parallel is making the t.Run itself a parallel subtest of TestBug. There is no parallel subtest of the t.Run subtest. TestBug will not complete until the parallel test completes. But TestBug's body has to finish because that is what triggers running the parallel tests in the first place.

とある。サブテストが起動する前にdeferが呼ばれるのはどうも仕様らしい。

deferも上手く使えないみたいだし、どうしようと思っていたら大学の先輩が書いた記事がヒットした。どうやら t.Cleanup() を使うことで解消することができるようです。

https://syfm.hatenablog.com/entry/2020/05/17/161842

https://godoc.org/testing#T.Cleanup

Cleanup registers a function to be called when the test and all its subtests complete. Cleanup functions will be called in last added, first called order.

t.Cleanup() はサブテストが終わってからLIFOで呼び出されるらしい。この周りはdeferと同じ挙動をしているようです。

書き換えてみました。

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

package main

import (
    "log"
    "testing"
)

func TestDefer(t *testing.T) {
    tcs := map[string]string{
        "a": "あ",
        "b": "い",
        "c": "う",
    }

    t.Cleanup(func() {
        log.Printf("えお")
    })

    for tn, tc := range tcs {
        tc := tc
        t.Run(tn, func(t *testing.T) {
            t.Parallel()
            log.Println(tc)
        })
    }
}
=== RUN   TestDefer
=== RUN   TestDefer/a
=== PAUSE TestDefer/a
=== RUN   TestDefer/b
=== PAUSE TestDefer/b
=== RUN   TestDefer/c
=== PAUSE TestDefer/c
=== CONT  TestDefer/a
=== CONT  TestDefer/b
=== CONT  TestDefer/c
2009/11/10 23:00:00 あ
2009/11/10 23:00:00 い
2009/11/10 23:00:00 う
2009/11/10 23:00:00 えお
--- PASS: TestDefer (0.00s)
    --- PASS: TestDefer/a (0.00s)
    --- PASS: TestDefer/b (0.00s)
    --- PASS: TestDefer/c (0.00s)
PASS

確かにdeferのときとは違い、サブテストが終了してから えお が出力されており、期待していた挙動をしています。

これを元に、上のe2eテストっぽいものを書き換えてみましょう。

https://play.golang.org/p/-buH40moEg4

=== RUN   TestFunctionA
=== PAUSE TestFunctionA
=== CONT  TestFunctionA
=== RUN   TestFunctionA/Yurine-sanはアクセス可能
=== PAUSE TestFunctionA/Yurine-sanはアクセス可能
=== RUN   TestFunctionA/Medusa-sanはアクセス可能
=== PAUSE TestFunctionA/Medusa-sanはアクセス可能
=== RUN   TestFunctionA/zyashin-changはアクセスできない
=== PAUSE TestFunctionA/zyashin-changはアクセスできない
=== CONT  TestFunctionA/Yurine-sanはアクセス可能
=== CONT  TestFunctionA/zyashin-changはアクセスできない
=== CONT  TestFunctionA/Medusa-sanはアクセス可能
--- PASS: TestFunctionA (0.00s)
    --- PASS: TestFunctionA/Yurine-sanはアクセス可能 (0.00s)
    --- PASS: TestFunctionA/Medusa-sanはアクセス可能 (0.00s)
    --- PASS: TestFunctionA/zyashin-changはアクセスできない (0.00s)
PASS

t.Cleanup()は1.14から入った機能で、割りと新しめのものとなっているがかゆいところに手が届く感じでとてもいいと思いました。

抵抗の値をslackで確認したい!!!

どうもこんにちは、最近いろいろバタバタしているはとバスです。

この記事はAizu Advent Calenderの19日目の記事になります

18日目 fatman???? さん

20日目 のあ

当日の朝に担当だったことを知り、ほぼ半日で作った適当なslackのコマンドを紹介します。

カラーコードを確認したい

最近はめっきり機会が減ってしまったんですが、電子工作が好きです。

大学一年のときから始めて、いろいろな物を作ってきました。

hatobus.hatenablog.jp

hatobus.hatenablog.jp

電子部品を使うときのあるあるなんですけど、抵抗の値って、難しくないですか?????

f:id:flying_hato_bus:20191219214131p:plain

12の色と値を暗記しなければいけないし、パッと見ただけでは分からないので調べてから使うという人もいると思います。

そんな時に、slack上で簡単に確認できたら良さそうじゃないですか?

作りました

slackのslashコマンドを作りました。

f:id:flying_hato_bus:20191220095723p:plain

/resistor [抵抗の値]と書くことで、カラーコードの画像が返ってきます。

f:id:flying_hato_bus:20191220100244p:plain

f:id:flying_hato_bus:20191220100322p:plain

実装

使っている技術としては

  • サーバー
    • GCP cloud functions
  • 言語
    • Go

です。cloud functionsをサーバーにして、リクエストに応じてGoで画像を生成、そのままslackに画像を投稿しています。

f:id:flying_hato_bus:20191220101110p:plain

ポイント

github.com

カラーコードを生成する部分です。

抵抗の値を数字だけで表現する場合は入力された文字列をint64にキャストすればいいだけですが、抵抗の値を指定するときには少し特殊なフォーマットの場合があります。4700 Ωを 4k7などに表現するやつです。このようなフォーマットで表現する時に、以下のような表現の仕方があります。

  • 1k
  • 4k7
  • 47k
  • 470k

また、k以外にもMの接頭辞のときもあります。

このように表現されたときの抵抗値も対応しています。

SI接頭辞付きの抵抗値の場合

330Ωなど、接頭辞がついてないときには、 strconv.Atoi()などを使えば、stringをintにキャストできます。まずはこれで当たってみて、接頭辞が付いているときにはエラーが返ってくるため、Splitするセクションに入ります。

上で上手くキャストできなかった場合、SI接頭辞(k, Mなど)が付いているときには、入力された文字をSplitします。Goでは strings.Splitという関数があります。

golang.org

これは文字列を指定文字で切り取ることができるという関数です。

srcstr := "a,b,c"
splitstr := strings.Split(srcstr, ",")

// splitstr -> ["a" "b" "c"]

つまり、これでk, Mの場合でSplitすれば、 4k7などは ["4", "7"] という値が返ります。Splitした値はこのようになります。

  • 1k --> ["1", ""]
  • 4k7 --> ["4", "7"]
  • 47k --> ["47", ""]
  • 470k --> ["470", ""]

補足ですが、Splitには罠が存在して、 47kのように、最後に切り取る文字が来ている場合には最後に空白が入ります。気をつけましょう。

これで上手くSplitできればできたときの文字で乗数も割り出せますね。

  • kでSplitできた --> 乗数 = 3
  • MでSplitできた--> 乗数 = 5

あとはカラーコードを割り出すまでもうちょっとです。

f:id:flying_hato_bus:20191220111435p:plain

抵抗は上2桁の数字をそれぞれの色に対応させ、3本目の線を乗数の色にします。

- 4k7の場合

4700なので 1本目は 黄色 2本目は 紫 乗数は10^2なので 赤

のように求まります。

上にある 1k, 4k7, 47k, 470k の場合は以下のようになります。 (Split済みの配列をcontainsと書きます)

- 1k
contains[0]の文字は1桁、contains[1]の文字は無し、この場合1st barは1になり、2nd barは0になる、乗数は1減るので減らしておく

- 4k7
contains[0]の文字は1桁、contains[1]の文字は1桁、この場合1st barは4になり、2nd barは7になる、乗数に変化はない。

- 47k
contains[0]の文字は2桁、contains[1]の文字は無し、この場合1st barは4になり、2nd barは7になる、乗数は1増やす必要がある。

-470k
contains[0]の文字は3桁、contains[1]の文字は無し、この場合1st barは4になり、2nd barは7になる、乗数は2増やす必要がある。

contain[0]の長さでif-elseしています。

 if len(contain[0]) == 1 {
        fv, _ := strconv.Atoi(contain[0])
        firstband = int64(fv)
        if contain[1] == "" {
            secondband = 0
        } else {
            sv, _ := strconv.Atoi(contain[1])
            secondband = int64(sv)
        }
    } else if len(contain[0]) == 2 {
        fv, err := strconv.Atoi(contain[0])
        if err != nil {
            return nil, err
        }
        firstband = int64(fv / 10)
        secondband = int64(fv % 10)
        mag += 1
    } else {
        resistor_topvalue, err := strconv.Atoi(contain[0])
        if err != nil {
            return nil, err
        }
        for i := 0; ; i++ {
            last1digit := resistor_topvalue % 10
            resistor_topvalue /= 10
            if int64(resistor_topvalue/10) == 0 {
                firstband = int64(resistor_topvalue)
                secondband = int64(last1digit)
                break
            }
            mag += 1
        }
    }

これによって、firstband, secondband, magの3つの数値が求められ、これを元に色を出しています。

色を求めて、いい感じに画像を生成しています。

cloudfunctionsについて

cloud functionsの仕様として、ユーザーが自由にファイルを書き込める場所に制限があります。

cloud.google.com

ファイル システムで書き込み可能な部分は /tmp ディレクトリだけです。このディレクトリは、関数インスタンスの一時ファイルの保存先として使用できます。

とあるので、slackに投稿する画像は /tmp以下に置かなければいけないことがわかります。

大急ぎで書いたのでクソ適当になりましたが、今回のアドベントカレンダーネタは以上になります。

来年からはもっとちゃんと書きたい。

もっと書くべき内容があったと思いますが、内容がバカでかい騒動にまで発展してしまったので書けませんでした。

気をつけよう!!!!!!

野菜を食べる技術

早いものでもう12月ですね

今年もアドベントカレンダーの時期ですね。みなさんはいかがお過ごしでしょうか?

今回のアドベントカレンダー、僕だけ2つの記事を書くみたいになってしまって本当に申し訳ないです(見たときには前半のほうが開いていて、埋まってなかったらなんとも言えない気持ちになってしまうので急いで入れてしまった...)

その貴重な枠をこのようなポエムで汚してしまうことをどうかお許しください...

てなわけでこの記事は Aizu Advent Calendar 2019 2日目の記事になります。

adventar.org

1日目は まさちゃこさん

3日目は インターネットミームの使い手 dennougorilla くんの記事になります。

健康的な生活をしたい

定期的に言っていますが、僕は結構な デブ です。

小学校から高校まではテニス部でそれなりに汗を流していたので、大学入学時には70kgを切っていましたが、大学生活によってブクブク太っていき去年一番太っていた時期は96kgありました。

単純に30kg太ったことになります。

今は85kgあたりをウロウロしています。

切実な目標として、健康的な生活をしたいです。

そもそも健康的な生活とは...? という問題に直面しますが、WHO憲章では、その前文の中で「健康」について、次のように定義しています。

Health is a state of complete physical, mental and social well-being and not merely the absence of disease or infirmity.

公式の訳としては

健康とは、病気でないとか、弱っていないということではなく、肉体的にも、精神的にも、そして社会的にも、すべてが満たされた状態にあることをいいます。

このWHOの健康の定義、めちゃくちゃ厳しくないですか?????

この健康的な生活を目指すのは流石に厳しいので記事の中ではWHOの健康の定義からは離れ、自分が実践していたある程度健康的に暮らす方法として 効率的に野菜を摂る方法 を書こうと思います。

野菜を食べる

野菜を食べるということはビタミンやミネラルを補給でき体のバランスを整えることに繋がります。

健康的な生活をするためには不可欠です。

あと「俺は野菜を意識して食べているぞ、そんじゃそこらの人よりも偉いはず、俺は偉い偉い偉い」と他人にマウントを取れます。精神的な健康も保てるはず。

たまに二郎の上に乗っているもやしを野菜と言っている人もいますが、あれは野菜ではなく油まみれのなにかです。健康的な生活をしたいのであれば二郎を月イチくらいに抑えたほうが絶対にいいです。

効率的に野菜を食べよう

おすすめの食べ方は鍋。一人鍋用スープの素があれば片手鍋でグツグツ野菜を煮込めば放っておくだけで美味しい何かが出来上がり、野菜を摂取できます。

食材を保存する

野菜は水分が多く、すぐに傷んでしまいます。あんまり自炊をしないという人に生野菜をおすすめすることは難しいと思うんですが、冷凍することでこの問題を解決できます。

週の初めや野菜が安い時にキャベツや玉ねぎ、えのきやしめじを買っていい感じの大きさに無心で刻み、100均に売っている安いジップロックみたいなものに詰め込んでそのまま冷凍庫にぶち込んでください。

お肉も一緒に買ってきて、ラップ等で小分けにし一緒に冷凍することもでき、タンパク質も楽に摂れます。

おすすめの野菜

  • キャベツ
    • 味噌や坦々系の鍋におすすめ、よく煮込むと甘みが出てきておいしい
    • 半玉買ってざく切りにするとジップロック特大1袋くらいになる
    • 冬に買うと葉が硬いので注意する
  • 白菜
    • 鍋に使う野菜の王道
    • どんな鍋にも合う
    • 味がしみて美味しい、1/4くらいでジップロック1袋くらいになる
  • 玉ねぎ
    • 味噌汁やラーメンを作るときとかにも使える
    • 常温でも保存が効くので買い置きしてもいい
  • ニンジン
    • 短冊切りにすると鍋で食べやすい
    • ラーメンを作る時に玉ねぎ一緒に入れるだけでもかさ増しになる
  • ネギ
    • 斜めの輪切りにすれば鍋や味噌汁の実として
    • 細切りにすれば薬味としてそのまま乗せられる
    • ゆっくり煮ればトロトロになっておいしい
  • えのき
    • 意味不明なくらい安いときが多い
    • どんな料理にも合うのでハズレが少ない
  • しめじ
    • デフォルトのきのこ
    • どこのスーパーにもあって安く手に入る

お肉はウィンナーをおすすめします。

冷蔵室に置いておいてもそれなりに日持ちがするし、なんなら常温でボリボリ行けるので何か口に入れないととなった時に食った気になれます。

三等分にして冷凍しても鍋にぶちこめばおいしい出汁が出てくれますし、包丁で切るのもめんどくさいときは冷蔵庫から出して手でいい感じの大きさにちぎるだけでもいいです。

鍋を作る

素晴らしいもので、世の中には 一人鍋の素 というものがあります。

絶大な信頼を置いているプチッと鍋シリーズ

ちょっとの水に鍋の素をいれて好きな野菜をぶち込んで10分くらい煮込めば野菜の入った美味しい何かが出来上がります。

一緒に生姜を入れてあげれば手足の先の冷えの対策になります(なってほしい)

鍋の具材を食べ終わった後に冷凍のうどんや餃子を入れればシメも美味しく楽しめます。

???「ガスを使うのめんどくさい、鍋を洗わなくちゃいけないのが嫌」

エビス 電子レンジ調理用品 ブラック 1000ml

エビス 電子レンジ調理用品 ブラック 1000ml

レンジ対応の一人鍋容器でできます。

使い方としては

  1. 容器に水と鍋の素を入れる
  2. 好きな野菜と肉を入れる(肉は最初に入れないと焦げる可能性がある)
  3. 500Wのレンジで10分くらいチンする
  4. おいしい

シメにうどんやご飯を入れたいときも物を入れて5分チンすればおいしいうどんやおじやができます。

洗い物も箸と容器だけで済むので楽ちんです。

鍋という選択

鍋はめちゃくちゃ楽でオススメです。具材をぶち込んで煮るだけで美味しい鍋が出来上がり、野菜も摂れるしいいことずくめです。

ちなみに、友達が家に来たときとかにも楽で、一人鍋の素を人数分に増やせば鍋の量を調整できます。鍋のオートスケーリング(申し訳程度の技術成分)が可能です。

おすすめの鍋の素

おすすめの鍋の素をいくつか貼っておきます。スーパーならこのなかで1つは必ずあるはず。 ちなみに、ドラッグストアのワゴンセールで意味不明なくらい安いときもあるのでそういうときは狙い目です。

  • こなべっち 鶏だし生姜鍋つゆ

最高の鍋の素、生姜の香りと鶏のうまみが感じられる美味しいやつです。シメのうどんも最高にうまいのでオススメ、鍋以外の料理として茹でたそうめんを入れてにゅうめんにしても美味しいですよ。 なかなか店頭に並んでいるのを見ないので、見たら速攻で買いだめするのが吉

オススメの具材
- 白菜
- 鶏肉
- にんじん
- しめじ
- ネギ
  • プチッと鍋 寄せ鍋

寄せ鍋にしてもよし、そのままうどんのつゆにしてもよし、万能でハズレがないおいしい鍋の素 会津だと簡単に手に入る。

オススメの具材
- 白菜
- 肉(なんでも)
- にんじん
- しめじ or えのき
- ネギ
  • プチッと鍋 濃厚みそ鍋

最近ハマっているおいしいやつです、キャベツを煮込んであげると甘くておいしい。シメには業務スーパーで買った安い乾麺を入れてあげるとシメの味噌ラーメンになります。

オススメの具材
- キャベツ
- 肉(なんでも)
- にんじん
- もやし
- しめじ or えのき
- コーン

さいごに

後輩とかを見ていると、ちゃんとした食事を摂って欲しい人が数名います。

↓↓↓↓↓↓↓↓例です↓↓↓↓↓↓↓

食というのは生きる上での基本であって、栄養の偏りや不規則な食事などによる肥満や、それらが原因と考えられる生活習慣病などになっては元も子もありません。

長期的なことを考えればエンジニアも結局は体が資本であり、自身の健康は自分が作っていかなければなりません。

日頃の不摂生がたたったせいでエンジニアとして生きていけなくなるの、嫌じゃないですか?

なんか普段のツイートとは真逆のすごく意識の高い感じがして自分が嫌になったのでここらへんで失礼いたします。

みんな!!!野菜を食べて健康的になろうな!!!!!

平成最後はgRPCなサーバーを書いてハッカソンしていた話

こういうのってあとから書こうとすると時間が経ちすぎてなかなかポストできなくなりますよね。

そういうわけで書きます。

何してきたのよ

サイバーエージェントさんの 平成最後のハッカソンに出場してきました。

www.cyberagent.co.jp

4月の頭くらいからtwitter上で人事の方々がこのハッカソンについて話をしていたので、「ちょっと出てみるかぁ」みたいなノリで考えてました。

出場するまでの流れ

そんなことを考えてると、最近イキりがすごい id:NoahOrberg

id:NoahOrberg < これ、マヤミトくんと出ない?

みたいなことを言ってきました。

「プログラミングができる5歳児」ことのあくんが、インターネットで赤ちゃんをしている id:yt8492 を推していましたし、なによりもマヤミトくんと一緒に出たいなと僕も少し考えていたのでこの3人で出ることにしました。

チームの人員としては

  • プログラミングのできる5歳児 (サーバー&技術担当) のあ id:NoahOrberg
  • SNS広報担当とチームの平均体重を引っ張り上げる(サーバー) 僕 id:flying_hato_bus
  • 精神年齢生後3ヶ月(Android) マヤミト id:yt8492

この3人で蠱毒みたいなチーム作って参加登録しました。

数日後にはみんな参加できることが決定し、やっていきを高めてました。

お題が発表される

今回のハッカソンのお題は 「平成生まれの私たちが、平成を楽しく振り返ることができるサービス/モノ」というものでした。

さっそくのあくんとマヤミトくんと何を作るかを話し合うことにしました。

f:id:flying_hato_bus:20190508170237p:plain
チーム名とスクリーンネームが被ってちょっとテンションの上がるのあ

話した時に出たものはこちら

  • 小学校時代に書いた(こともある)プロフ帳を作る
  • 黒歴史を共有できるものを作る
  • spotifyでおすすめの曲をサジェスト

主に出たのはこの3つでした。

選ばれたのは黒歴史を共有するものを作るというプラン。

他の2つは

  • プログラミングのできる5歳児 (サーバー&技術担当) のあ id:NoahOrberg
  • SNS広報担当とチームの平均体重を引っ張り上げる(サーバー) 僕 id:flying_hato_bus
  • 精神年齢生後3ヶ月(Android) マヤミト id:yt8492

このチームの人員から考えても、そんなおしゃれなものを作るよりはネタに振り切ったほうがいいだろう、という考えでボツにしました。 僕達がそういうプロダクト作るのは似合わないってことですね

f:id:flying_hato_bus:20190508170904p:plain
みんなで話し合ったあとのホワイトボード

黒歴史を共有できるものということで深堀りしていき、出てきたのがこのホワイトボードのようなことです。

黒歴史 x ブロックチェーン

f:id:flying_hato_bus:20190508172051p:plain 黒歴史というものは、オタクならば結構な人たちが作ったことであろう歴史です。

  • イキリ満載のブログエントリ
  • 某動画サイトに実況動画を投稿
  • 肌にペンで傷を描いて学校に登校
  • 霊感があるキャラを装って心霊写真を捏造

こんな感じで「あの頃はこれがかっこいいと思った」「他人とは違う自分を演じたかった」という考えで突っ走ってしまう行為です(全部僕の体験談です)

黒歴史というものはあくまで「歴史」、勝手に改変されたり、なかったことにされるということはもってのほかです。

勝手にネット上で作った黒歴史を消されてはいけません。

つまり、これを恒久的に保存してあげる必要があります。

ここでブロックチェーンです!!

ブロックチェーンは情報の改ざんに強いデータベースです。

つまり、この黒歴史の情報をブロックにしてしまえば、半永久的に黒歴史は保存され、歴史の改ざんも防ぐことができるようになります。

作るにあたって

作るにあたっての作業分担はこんな感じ

サーバーは Go x gPRC、ネイティブアプリは Kotlin x gRPC という感じの構成。

github.com

github.com

それぞれのgithub repositoryです。

のあくんとマヤミトくんがいろいろと頑張ってくれたので実装は早く終わりました。

gRPCを使ってみた感想

今までやってきたようなRESTfulなJSONサーバーより断然書きやすかったです。

gRPCは自動生成されたgoのファイルを見れば何を実装すればいいか一目瞭然ですし、入出力がProtocol Buffersなので、jsonを扱う時のように Marshal / Unmarshal しなくて済むというのがめちゃくちゃ楽でした。

goではstructで扱えるので変数に型があり、どういうデータなのかがすぐ分かりますし、何よりもシリアライズされたデータが行き来するので速さが出ます。

そんなわけで大体のサーバーはハッカソンの3日前には大方の実装が終わり、マヤミトくんに頑張ってもらうだけになってました。

ブロックチェーンについて

ブロックチェーンは †最強†エンジニアののあくん id:NoahOrberg が担当しました。

合意形成アルゴリズム

ブロックチェーンの中心とも言える合意形成のアルゴリズムはのあくんが独自に実装してくれました。

独自合意形成アルゴリズム HEISEI

合意形成アルゴリズムは平成を使いました。いい感じにブロックが生成されるのが "平" "成" の二文字を使った時でした。

これも前日にはのあくんがすべて実装していたのでさすがですね。

ネイティブアプリ

一人で全部実装したマヤミトくんの記事でしっかり書かれているはずです。

yt8492.hatenablog.com

学部2年生ながらここまでやってくれたマヤミトくんは煽り抜きで最強だと思います。

これで彼女がいれば敵なしですね。

ハッカソン当日

ハッカソン当日には黒歴史ブロックチェーンがある程度出来た状態で参加できました。

あとは繋ぎこみをするだけという状態です。

まあ、こういう時は大体苦労するんですよね、苦労しました。

サーバーにバグが結構残っていた

本当に申し訳がなかったです。許して、マヤミトくん、のあくん。

直前まで小さなバグがちょっとずつ出ていてそれを潰す作業をしていました。

前日まで散々イキっていたのにこのザマです。精進します。

発表のフォーマットが特殊すぎた

チームが多すぎて発表の時間が押していた結果、

「スライドは2枚、1枚は実際に動いている動画を貼り、2枚目には説明を書く」

というものになっていました。

見た目にインパクトがあるものが有利になったのかなと思います。

僕らのチームは 見た目のインパクト < 技術 & 発想力 みたいな部分があったので技術力としてはそれなりにあったと思いますが、インパクトが足りませんでした。

優勝者のチームが以前会ったことのあるはととさんだったことにも衝撃でした。

作ったものが。「マリオのゲームで平成を振り返る」というコンセプトで、web上でマリオのステージが進むごとにゲームハードが進化していくという技術力がやばいやつでした。

「Vueのちからってすげーーーーー!!!!」

まさかの発想がかぶる

黒歴史という発想は被りそうだな〜〜〜〜」

とマヤミトくんとのあくんと笑いながら話していましたが、見事に被りました。

それも同じ大学のmtのチームでした。

会津大学生は黒歴史が好きです

思わず笑い合いました。

ちなみに、他のチームとは黒歴史のネタがかぶらなかったです。奇跡かな。

さいごに

懇親会で他の参加者の方とお話をしていたら

黒歴史ブロックチェーン、僕は投票しました!」

という声を結構聞いて嬉しかったです。やっぱり他の人からフィードバックを受けるというのは気持ちがいいです。

ブロックチェーンをフルタイムで働きながらも作ってくれたのあくん

のあくんのひどい彼女イキリに耐えながら完璧なネイティブアプリをつくってくれたマヤミトくん

最後の最後でマヤミトくんがキレながらのあくんに 「は!?!?」 と言っていたのは見ててめちゃくちゃ面白かったです。

二人のおかげでとっても面白いものが作れました。ありがとうございました。

おしらせ

お仕事をください

2019春インターン振り返り ~アカツキさん編~

基本的な情報

  • 参加させていただいたところ
  • インターンの内容
    • ゲーム内仮想通貨管理サービスのサーバー周り
  • 期間
    • 3/11 ~ 3/29 (三週間)
    • 15営業日分

やったこと(細かく)

  • 課金基盤のサーバー、インフラ周りの開発

    • 使った技術
      • Go (サーバー)
      • Terraform (インフラ)
      • GCP (TaskQueue, GAE, Stackdriver)
  • shellscript, yamlファイルのlinterをCircleCIに入れ込む

    • 使った技術
      • CircleCI (CI)
      • shellcheck (linter)
      • yamllint (linter)

はじめに

自己紹介

こんにちは、飛ばすはとバスと申します。この度はアカツキさんで課金基盤のサーバサイドエンジニアとしてインターンをさせていただきました。いつも僕はGoとPythonで色々な物を作っていますが、インターンとして大きなプロダクトの中でGoを書くのはほぼ初めてのインターンになりました。

インターンに参加したきっかけ

僕は会津大学という、東京駅から行くと新幹線とローカル線で3時間ほどかかる山の中の大学で勉強をしています。会津大学は色々と珍しい大学で、公立では珍しいコンピュータ専門の単科大学です。

会津大学では、たまに企業の方々を招いてLT会を開くことがあります。

www.facebook.com

アカツキさんを知ったのは学部2年のときのLT会でした。今回のインターンでメンターをしていただいたsachaosさんがLTをしていて、「面白そうな会社だなぁ」と感じたのが当時の印象です。

Goを書き始めたのは大学三年の夏で、春休みになったしインターンでGo/サーバーの知識を深めたいと思っていたところでした。そのときにちょうどアカツキさんから「サーバーサイド(Go)のインターンに参加しませんか?」という声をかけていただけたので、参加させていただきました。

LT会などで、どのような物を作っているかどのような人たちがいるかというのがある程度わかった状態で参加できるので、安心して業務に携わることができました。

インターンの内容

バックグラウンド

アカツキさんでは、様々なタイトルのゲームを配信しています。その中でも特に大きな役割を持っているのは「課金」の部分です。 お金という大きなものを動かすために、安定性や信頼性が必要になります。 各タイトルで課金の部分を実装してもいいのですが、タイトルごとに課金部分の実装に違いが出てしまうと、いざ障害が出た時などに解決に時間がかかる可能性があります。

課金の機能をマイクロサービスとして切り出すことによってこれを解決しています。今まではアプリごとに作っていた課金周りの機能を基盤としてまとめることで障害の対応や、プラットフォームのストアの仕様のアップデートへの追従などの開発・運用をやりやすくしています。

参加したプロジェクト

マイクロサービス化した課金基盤の開発にジョインさせていただきました。今回の記事では課金基盤の開発についてお話をします。

サービスの裏側

このサービスでは、課金の情報をBigQueryに保存しています。

f:id:flying_hato_bus:20190328135541j:plain

タイトルが大きくなれば、必然的に課金も増え、BigQueryに入ってくる課金のデータも多くなります。

f:id:flying_hato_bus:20190328135630j:plain

BigQueryにデータをinsertする処理はレイテンシが高く、これにリソースを割いてはレスポンスが悪くなります。ここで登場するのがTaskQueueです。

f:id:flying_hato_bus:20190328135745p:plain

タスクキューを使用すると、アプリケーションは、タスクと呼ばれる作業をユーザー リクエストの外で非同期に実行できます。アプリがバックグラウンドで作業を実行する必要がある場合、タスクがタスクキューに追加されます。

cloud.google.com

つまり、処理にコストのかかるinsertをTaskQueueで非同期で処理をすることで、リクエスト内でinsertにかかる時間をなくすことができ、レスポンスの向上ができます。 また、TaskQueueは処理が失敗すると自動でQueueの末尾にTaskを追加し、何回でも再実行されます。これによってデータが闇に葬り去られることもなく、安定してデータを保存することができます。

僕がやったタスク

初日のメンターさんとの1on1で、インターンの中で僕が何をするかを決めました。内容としては、「TaskQueue内のレイテンシとリトライ回数を計測する」というものでした。

f:id:flying_hato_bus:20190328135957j:plain

実現したい理由

理由としては、時間やリトライ回数を計測することでどれくらい処理が重いかということを見たかったため。 TaskQueueに入った時間と、TaskQueueできちんと処理された時間の差分で時間を計測することにしました。

実装

TaskQueueに入っている時間を取得するためには (TaskQueueで処理が成功した時間) - (TaskQueueにそのタスクが入ってきた時間) で取得ができます。

また、リトライ数の取得についてはGAEがタスクをpushする際にヘッダに情報を込めて送ってくるので、これを取得することで可能でした。

ヘッダに込められている情報

  • X-AppEngine-QueueName
    • キューの名前
  • X-AppEngine-TaskName
    • タスクの名前
  • X-AppEngine-TaskRetryCount
    • このタスクが再試行された回数。この試行回数には、インスタンス数不足が原因でタスクが異常終了した試行も含まれる。
  • X-AppEngine-TaskExecutionCount
    • このタスクがこれまでに異常終了した回数。この回数には、インスタンス数不足が原因の失敗は含まれない
  • X-AppEngine-TaskETA
    • タスクの目標実行時間。1970 年 1 月 1 日からの秒数

cloud.google.com

この二つがStackdriver Loggingで見られるようになったので、Terraformを書きました。

TerraformはIaCの一つで、インスタンスなどをコードで管理しようというものです。 これを使って、上で作ったログから数値を拾ってきてログの指標にしたり、値が閾値を超えたらアラートを作成できるようになります。

個人の開発ではなかなか使うものではないですが、最近の流行りとして色々な企業で導入されていたり、色々なサービスがTerraformに対応をし始めたりと注目されていて、僕自身、Terraformに触れてみたいと感じていました。

どちらとも、先日のデプロイで本番環境で走ることになり、とてもいい体験になりました。

インターン中の生活

会津大学生は、夏休みや春休みなど、長期休みになると出稼ぎに東京へ来るのがよくあります。 今回のインターン中に、出稼ぎに来ている同じ大学の人とご飯に行ったり、古くからの友達と飲んだりと楽しい生活を送ることができました。

社員の方にお昼に誘っていただいたり、同じインターン生同士でご飯に行ったりと社内での交流も盛んで、働いていても毎日が楽しいインターンでした。

最後に

普通の大学生では作るor触れられないような大きなサービスの開発を経験して、とても勉強になりました。

3週間という期間の中で多くのことを学ぶことができ、それでいて楽しい毎日を過ごせたような気がします。 インターン期間中にお世話になったメンターのsachaosさん、インターンを調整してくださった人事の方々、同じチームの皆さんなど、多くの方に感謝の気持ちを込め、ここで今回の記事を閉めようと思います。

本当にありがとうございました。

年末の振り返り

どうもみなさまこんばんは、はとバスです。

今私は川崎にある友人宅でRAIZINと無人島生活をローテーションしながら、紅白で椎名林檎ユーミンを見ていたら堀口がベラトールの首を取っていたのを見逃してしまい身悶えしています。

なんかみんな年末の振り返りをしているので、かるーく振り返ります。

インターン関連

春と夏の二回行きました。

春はネオス株式会社さんにお邪魔させて頂き、機械学習系のインターンに参加。

夏は株式会社リクルートさんにお邪魔させていただきました、こちらはGolangによるサーバーサイドのインターンに参加しました。

hatobus.hatenablog.jp

最近年収が一つの単位になった方に「安定の飯インターン」と言われましたが、それ以外にもGolangの大量の知見を得られ、今に至っています。

技術力の低い私をインターンに受け入れていただいた関係者の方々には厚くお礼を申し上げます。

今後の学生生活の中で幅広く使えるような様々な知識を学ぶことができ、最高のインターンでした!

賞を受賞

夏に作った農業とITを繋げるシステム、「SmartAgri」というシステムがあります。

ビニールハウスや農場にESP32で作成したデバイスを設置、温度や湿度をサーバーに送り、AmazonのAlexaを使い情報を取得できるようなシステムです。

  • 農場に置くセンシングデバイスHANIYAMA
  • Golang+MySQLのサーバーサイドアプリ OHJIN
  • アレクサスキルの UKEMOCHI

の3つのOSSによってこのシステムは動いています。

f:id:flying_hato_bus:20181231225928j:plain
SmartAgriのシステム構成図

サーバーとスキルは全てGolangで作成、これも夏インターンで学んだことの復習も兼ねて作成したものでした。

この度、私と農家の方、そして会津にあるIT企業の方の三人で作成したシステムが

というとても栄誉のある2つの賞をいただける運びとなりました。

www.minpo.jp

f:id:flying_hato_bus:20181231230642p:plain
受賞者一覧(福島民報のサイトより引用)

ICTスタートアップ支援センター | 会津産IT技術認定会議 〜 募集案内 | NICT-情報通信研究機構

SmartAgri

一年生の時からずっとtwitterなどに投稿してきた謎の電子工作の花が開いた瞬間かなと思います。

自分の作ったシステムを実際に農家の方に使って頂き「今までのスマホアプリやデバイスにはない利便性」「無限の可能性を感じる」などのお言葉を頂き、とても嬉しかったですし、自分の持てる力をもってして、農家の方々の問題を解決することができるということを知れ、とても有意義なものとなりました。

もう少し細かく知りたい場合は公式サイトがありますのでこちらをどうぞ。

SmartAgri

色々なものを考えるのが増えた

いよいよ大学三年生にもなり、必然的に考え事や思うことが多くなりました。

後輩や自分の進退について考える1年になったかなと思います。

自分の進退

自分の進退としては、学部で就職することを決めました。

自分は進学したいという気持ちがやまやまですが、親の意向 「それ以上勉強して何になるんだというジジババのコメント」 シンプルにお金がない! も相まって就職という形を取らせていただきました。

どうしてももっと勉強したいという気持ちがあるならば、そういうサポートをしていただけるような企業に入り、大学院のほうで勉強しながら働くという道もありますし、自分で勉強ということもできます。

幸いにも、就活の方でいくつかの内定を頂くことができたので最悪の結果は避けられるかなと言う感じです。

個人的には大学で計算機科学を学ぶことができたので、この時代のIT企業のトップクラスのGoogleとかをダメ元で受けてみようかなと思います。

あとGSoCに応募もしたいです。

後輩

後輩のことについては、一年生と一緒に行ったハッカソンが大きいです。

今年の夏の石巻ハッカソンで、今年はマヤミトくんと一ノ瀬くんとで参加しました。

twitter.com

twitter.com

一ノ瀬くんは去年も一緒に参戦した現役高校生です。一方で大学に入ったばかりのマヤミトくんは今回でハッカソン初参戦という感じでした。

blog.github.com

ここでなんとマヤミトくんが所属していたチームが「ヤバイで賞」を受賞、彼自身初めてのハッカソンで、それも技術的に素晴らしいと認められての入賞です。お知り合いのフィッシュさんとの一緒のチームでの受賞でした。

この後のマヤミトくんは外部のLTへの登壇や勉強会への参加というように、怒涛の勢いで成長をしています。

彼のこのような成長や行動を見ていると

「やっぱりハッカソンに連れてきてよかったな」

と心の底から思うことができました。

このような経験や体験により「後輩のためになるような行動をしよう」「後輩に憧れられるような先輩になろう」という気持ちが生まれました。

来年入ってくる学生や、今いる学生の模範となるようなことや、後輩などのサポートをしていけたらなと思います。

改めて 来年の目標

  • 後輩のためになるようなことをする
  • GSoCに応募
  • 日々のアウトプットも忘れない

この3つを念頭に置いて行きていきたいと思います。

最後に

最近Thinkpadを買いました。お金が無いけど頑張って働きます。

それでは、良いお年を。