oinume journal

Scratchpad of what I learned

O'Reilly Online Learningで日本語の本を読む方法

O'ReillyのOnline Learning(旧O'Reilly Safari Books Online)は月額$49でオライリーの本や動画などが見放題になるエンジニア向けのサブスクを提供している。以前は英語の本しか読めなかったが、いつからか日本語の本も読めるようになっていたのでメモ。

www.oreilly.com

Sign Inして、左のメニューのSettingsをクリックするとLanguage Preferencesがあるので、ここでJapaneseにチェックを入れて下のUpdate Preferencesをクリックして保存する。

これでHomeに行き、例えばGraphQLで検索すると検索結果の画面でBooksのタブがあるのでこれを選択する。そうするとLanguageの選択ができるので、ここでJapaneseを選ぶと日本語の本だけに絞ることができる。

目的の本が見つかったらそれをPlaylistに入れるなりすればOK。なお、AndroidのO'Reillyのアプリからだと日本語の本をダウンロードして読むことができたが、iOSはエラーになって読めなかった。

2020年11月時点では合計38冊の日本語の本が読めるらしい。個人的に良さそうだなと思ったものをいくつか書いておく。

なお、裏技としてACMの会員になると年$99でO'Reilly Online Learningにアクセスできるようになるので、4冊ぐらい読めば元は取れることになる。ACMの登録方法についてはここによくまとまっているので参考になると思う。

それでは素敵なO'Reillyライフを!

ドクターズプライムに入社しました

これはなに?

入社エントリー&会社紹介です。表題の通りで、10月をもってメルカリ/メルペイを退職し、11月からドクターズプライムという会社で働いています。

なにやってるの?

Backend Engineerとして、救急車のたらい回しをなくすためのプロダクトを開発しています。救急車のたらい回しが発生する理由としては以下のスライドに書いてある通りなのですが、これを解決するのがDr.'s Primeという医師の採用サービスになります。

自分も子供を持つ親なので何度か病院のお世話になったことはあるし、子供が救急車で運ばれて手術&入院したこともあるので、救急医療に関しては一当事者として良くしていきたいという思いがありました。ドクターズプライムは救急車の搬送を断らないための仕組みを採用サービスとして第三者の立場から提供していて、素直にいいソリューションだなと思っています。

入社の経緯

今年に入ってからスタートアップで働くと言うことを意識し始めて、何社か話を聞かせてもらいました。時系列にするとこんな流れです。退職すること自体は昨年から別の理由で検討していたのですが、今年に入ってからスタートアップ良さそう!と思うようになりました。

  • 1月:スタートアップでDXをやっている会社に興味を持ち始める
  • 6,7月:メルペイを退職する意向を固めて、本格的に転職活動を始める
  • 8月:2社からオファーをもらって悩み始める
  • 10月末:入社を決める

8月は2社からオファーをもらっていて悩んでいた時期で、そのうちの一つがドクターズプライムでした。2社のうちどちらにするかを悩んでいたところ、「両方で働いてみて決めればいいんじゃない?」という提案をしてもらい、ありがたく両方の会社で1ヶ月ずつ働くことにしました。ちなみにもう一つは地方自治体向けのSaaSを提供しているグラファーという会社です。

最終的には、より会社規模が小さいドクターズプライムを選びました。エンジニアが自分を含めて2名しかいないため、必然的にプロダクト開発チームを作るところからスタートになります。1ヶ月お試しで働いてみてこのメンバーならチーム作りを含めて楽しくやれそうだと感じたこと、HRT(Humility, Respect, Trust)をはじめとしてカルチャーを大事にしていて、実際に中で働いてそれを体験できたことが決断の後押しになりました。

ちなみにグラファーもいい会社です。こちらはリモート中心の働き方でしたが、相談事があったらすぐにZoom MTGが開催されたり、障害が発生した場合はポストモーテム(振り返り)をきちんと行い再発防止に努めていたり、プロフェッショナルさを感じることが多かったです。たまにオフィスに集まると和気藹々とランチを一緒に食べたり、スタートアップとしての一体感も強いです。

取り組んでいる分野に違いはあれど、どちらの会社も日本に存在するペインを猛烈なスピードで解決するスタートアップであり、その両方で働く機会をもらったことはとてもいい経験になりました。両社とも楽しく仕事させてもらったので、分身できるのなら両方でフルタイムで働きたかったぐらいですw

ドクターズプライムでやっていきたいこと

創業時のリーンなプロダクト開発の結果として、GAS + Spreadsheet + Google Forms のシステムで売り上げが立つという、ある意味嬉しい誤算の状態になっていました。しかし、このGASベースのシステムもそろそろ綻びが見え始めており、一般的なFrontend + Backendの構成に置き換えていくことが当面やっていくことになります。

これ以外にも

  • 新しい病院向けプロダクトの開発
  • GCPの設定をterraformで管理したい
  • もっとTDDを押し進めたい
  • 社内の情報機器の管理をもっと効率的にやりたい
  • 勤怠システムの打刻がめんどいのでSlackで打刻できるようにしたい

などなど、やりたいことは山のようにあります。しかし、まだBackend engineer2名という体勢なので採用も頑張らなくてはなりません。特に初期設計から実装までまるっとお任せできる強いFrontend Engineerの助けを必要としています。(マジで助けてくれ!)

その他にも多数の職種を募集しているので、興味がある人は以下の採用ページをみてもらえればと。オープンポジションもあるので、事業に興味はあるけど自分がマッチするポジションのイメージがなくても大丈夫です。

採用情報 | 株式会社ドクターズプライム

などと採用の話になってしまいましたが、とりあえず私は生きていて浅草で働いているので近くにいる人はぜひランチでも行きましょう!

f:id:oinume:20201109182914j:plain
オフィス近くから見えるスカイツリー

moq - gomockを使わないMock生成

Goでよく使われるMockの生成ツールとしてgomockがある。個人的にはgomockが生成したコードでモックを書くのが好きではないので、代替としてmoqを使うやり方を取り上げてみようと思う。

題材

GitHubのAPIを使って指定したリポジトリのブランチを出力するServiceを考えてみる。このServiceには以下の引数owner, repoで指定されたリポジトリのブランチをwに出力するというメソッドを持っている。

PrintBranches(ctx context.Context, w io.Writer, owner, repo string) error

service.go

Serviceのコードは以下のようになる。見てもらえばわかるように、githubClientを使ってGitHub APIを呼び出し引数のリポジトリのブランチの一覧を取得している。

github/client.go

次にServiceから参照されているgithub.Client interfaceについて説明する。これは以下のように純粋なinterfaceとして定義している。そして、ユニットテストではGitHub APIへのアクセスをモックにしたいので、このinterfaceが定義しているListBranchesを実装するモックをコメントのgo:generateで生成するようにしている。

package github

//go:generate moq -out=client_test.moq.go . Client
//go:generate mockgen -destination=client_test.gomock.go -package=github . Client

import "context"

type Client interface {
    ListBranches(ctx context.Context, owner, repo string) ([]string, error)
}

gomockを使ったテストコード

それではgomockを使ってService.PrintBranchesのテストコードを書いてみよう。以下のようにgithub/client.go にgo generateでmockgenを呼び出すコードを追加し、go generate ./mock/github を実行すると、./mock/github/client_test.mock.go が生成される。

//go:generate mockgen -destination=client_test.gomock.go -package=github . Client

そして、以下が実際にgomockが生成するMockを使って書いたservice.goのテストコードである(service_test.go)。

ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockGitHubClient := github.NewMockClient(ctrl)
mockGitHubClient.
    EXPECT().
    ListBranches(context.Background(), "oinume", "playground-go").
    Return([]string{"main", "feature/xyz"}, nil)
s := Service{githubClient: mockGitHubClient}
out := new(bytes.Buffer)
if err := s.PrintBranches(context.Background(), out, "oinume", "playground-go"); err != nil {
    t.Fatal(err)
}
fmt.Printf("out = %v\n", out.String())

注目して欲しいのは、以下のEXPECT()以降の部分。

mockGitHubClient.
    EXPECT().
    ListBranches(context.Background(), "oinume", "playground-go").
    Return([]string{"main", "feature/xyz"}, nil)

このEXPECT()が返すのは*MockClientMockRecorder型で、これに定義されているメソッドListBranchesのシグネチャはListBranches(arg0, arg1, arg2 interface{}) *gomock.Callになっている。これは本来のListBranchesのシグネチャとは異なるものになり、引数の型がinterface{}になっているため、オリジナルの引数の型であるstring以外の型も渡せてしまう。テストコードの実行には問題がないかもしれないが、本来であればClient interfaceに定義されている引数の型と同じにしておいた方が変な誤解を生まなくてすむと思う。

また、MockClientMockRecorder.ListBranchesが返す*gomock.Callに対してモックが返す値をReturn([]string{"main", "feature/xyz"}, nil)のようにセットする必要があるが、戻り値の数を間違えてもコンパイル時にはエラーにならず、実行時にエラーになることもイケてない点だ。

また、Mockのために以下のようなboilerplateのコードを毎回書くのも個人的には好きではない。

ctrl := gomock.NewController(t)
defer ctrl.Finish()
...

moqを使ったテストコード

では次に、moqが生成したコードを使った場合のservice_test.goを見てみる。

githubClient := &github.ClientMock{
    ListBranchesFunc: func(ctx context.Context, owner string, repo string) ([]string, error) {
        return []string{"main", "feature/xyz"}, nil
    },
}
s := Service{githubClient: githubClient}
out := new(bytes.Buffer)
if err := s.PrintBranches(context.Background(), out, "oinume", "playground-go"); err != nil {
    t.Fatal(err)
}
fmt.Printf("out = %v\n", out.String())

moqの場合はモックにしたいメソッドをstructの初期化時にセットする。これはただのメソッドなので、中身の実装は好きなように書けばよく、引数の型や戻り値の型も元のClient interfaceのままである。つまり、gomockのように戻り値の数を間違えてもコンパイル時に検出してくれる。個人的にはこのmoqを使ったコードの方が、boilerplateなコードもなく純粋にモックにフォーカスできるのではないかと思う。

まとめ

gomockではなくmoqを使うとモックを使ったテストコードがよりわかりやすく、かつ使い方を間違えた時の実行時のエラーもなくなることが判ったのではないかと思う。個人的にはモック化する対象が少なければmoqを使わずに、moqが生成するような該当メソッドを置き換えるfuncを手で書いてもいいのではないかと思う。というわけで、gomock割といろんなところで使われているけどより良いツールがあるよ、という紹介でした。おしまい。

2020年8月までの振り返り

ずいぶんと振り返りをサボってしまったので、頑張って7ヶ月分を書く

2月

この頃は各国のコロナの感染者数を毎日チェックしていた気がする。

そしてコロナの影響で2月終わりから在宅勤務、英語でカッコよく言うとWFHというヤツに会社が全体的に移行した。仕事的にはGitHub Actionsを使って何かいろいろ自動化しようとしていて、そんなに切羽詰まっている感じではなかった気がする。

3月

新型コロナの影響で3月〜5月に行く予定だったライブやフェスがことごとく延期もしくは中止になり、個人的な趣味を奪われてしまって、仕事で溜まったストレスが発散できなくなった。仕事はプロジェクトが炎上し始めたのと、WFHになったおかげでこの月から労働時間が著しく増えた気がする。

4月〜6月

保育園から「在宅勤務の家庭は登園を自粛して欲しい」という状態になり、子供の面倒を家で見ながら仕事をするというとてもストレスフルな状態になった。そして仕事ではプロジェクトが炎上し始め、さらにSoftware Designの原稿の締め切りがGW明けとバッティングして苦しい状況だった。

仕事では4月にProductivity Teamなるものを立ち上げ、炎上プロジェクトに対して人を増やすと言うアンチパターンにアンチテーゼを投げかけた。自分一人でやっていたものに対してチームで取り組めるようになったので、自分はリーダー業務に徹して実作業はチームメンバーにお願いすることになるのだが、子供の面倒をみなくてはいけないという状況においてはこれはかなり良かった。

というのは、子供が家にいるととにかく集中して作業することができない。奥さんが見てくれる時間もあるとはいえ、となりの部屋で叫び声が聞こえてくるとどうしても気になってしまう。なので、集中力が必要なタスクは深夜または早朝にやるようにして、昼間はSlackでのやりとりやMTGに終始するようにした。ただし、労働時間は平常時の1.25倍ぐらいになっているにもかかわらず、アウトプットはほぼ変わらない OR 下がっているという状態で、この期間が精神的にも肉体的にも一番つらかった。

7月

保育園の登園自粛が解除され、平日昼間も集中できる時間が増えた。また、炎上プロジェクトもなんとかリリースが終わり、自分はもう退職することが決まっていたので引き継ぎを徐々に始めた。

リリースしたとはいえ、リリースがむしろスタートであり、特にリリース直後のオンコールはつらかった。未知の問題のアラートが多発し、過去のSlackのログなどを漁ってなんとか対応するというやり方で逃げ切った感がある。途中からOperation Reportなるものを作るようにしたおかげで、過去にどういう対応をしたのかなどがわかりやすくなり、手間はかかるがこの仕組みはやって良かったと思う。

8月

8/14が最終(物理)出社日だった。オフィスに珍しく10人ぐらい人がいたので集合写真を撮ったりした。会社のアカウント類は本来は最終出社日で無効化されるはずが、なぜか1週間ぐらい無効化されてなかったのでSlackは毎日のように見ていた。

退職エントリーというやつを人生で初めて書いたところ、反響がすごくてこれだけでTwitterのフォロワーが200人ぐらい増えたw メルカリ/メルペイのブランド力というヤツはすごいなーとあらためて思う。

買ったもの

Dell 4Kモニター 27インチ USB Type-C モデル

この半年の中で買ったものの中ではこれが満足度が一番高い。このモニタからMacBookが充電できるし、場所もそこまで取らない。23.8インチと迷ったけど1万円しか違わなかったのでこっちを購入。

Galaxy Buds+

3月〜7月はとにかくオンラインの会議が多くて、AirPodsだと充電が持たなかったので購入。バッテリーは11時間確かにもつし、音もなかなか良いので気に入っている。難点はBluetoothが弱くて満員電車など人が多いところだと混戦して音が飛んでしまうことがある。この点ではAirPodsに軍配が上がるけどそれ以外では重宝している。

Kuman 3.5インチ HDMI 小型モニター 480*320 ラズベリーパイ向け

もともとラズパイは32インチのテレビにつなげていたのだけど、そのテレビをメルカリで処分してしまったので代わりに購入。ターミナルの文字が視力検査ばりに見えないけど、安くてコンパクトだったのでまぁ良いかという気がしている。

個人的な取り組み

今まで通勤中にやっていたこと・習慣がWFHになったため全て消え去ってしまった。特に英会話はオフィスでやっていたため、家でやる時間も習慣も根付かなかった。とりあえずなんとか家でも習慣作りをしていかないと。WFHはあと1年ぐらいは続くと思うので、これは今後の課題。

  • 英語: ほぼやれてない。英会話をする時間がないのでiKnowで単語や文章を発音することだけ毎日やっている
  • アルゴリズム: 全然やれてない

Gatsby + Firebase Hostingでプロフィールサイトを構築する

GatsbyFirebase Hostingを使って自分のプロフィールサイトを構築したのでそのメモ。

前提

  • サイトの中身はGitHubリポジトリで管理
  • https://github.com/santosfrancisco/gatsby-starter-cvがプロフィールサイト向けだったのでこれを使った
  • GitHub Actionsでmasterブランチにpushされたら自動的にFirebase Hostingに反映されるようにした
  • 今回は一つのGitHubリポジトリで複数のサイトをホスティングしたかったので、サブディレクトリを作成した。以下のような構成になっている。
    • lampetty-net-sites (リポジトリルート)
      • www (今回構築するプロフィールサイト)
      • xyz (別のサイト)

準備

  • GitHubリポジトリを作っておく
  • NodeJSをインストールしておく

Quick Start | Gatsbyに沿って以下を実行する。

(on repository root)
npm install -g gatsby-cli
gatsby new www https://github.com/santosfrancisco/gatsby-starter-cv

これで www ディレクトリ配下にサイトの静的ファイルが生成される。次にこれをFirebase Hostingにデプロイする。

Firebase Hostingのセットアップ

(on repository root)
npm install -g firebase-tools
firebase login
firebase init

今回は一つのリポジトリと一つのFirebaseプロジェクトで複数のサイトのファイルをホスティングしたかったので、以下のファイルを作成した。

https://firebase.google.com/docs/hosting/multisites に沿ってFirebaseの設定を行い、以下のようなファイルを作成した。

firebase target:apply hosting www lampetty-net-sites

firebase.json

{
  "hosting": [
    {
      "target": "www",
      "public": "www/public",
      "ignore": [
        "firebase.json",
        "**/.*",
        "**/node_modules/**"
      ]
    }
  ]
}

.firebaserc

{
  "projects": {
    "default": "<PROJECT_NAME>"
  },
  "targets": {
    "<RESOURCE_NAME>": {
      "hosting": {
        "www": [
          "<RESOURCE_NAME>"
        ]
      }
    }
  }
}

最終的にデプロイは以下のように行う。

firebase deploy --only hosting:www

GitHub Actionsの設定

以下のような感じでnode_modules配下をキャッシュすることでビルド時間を短縮している。

まとめ

静的サイトを構築するのは簡単