TECH PLAY

BASE株式会社

BASE株式会社 の技術ブログ

577

はじめに これは「BASE Advent Calendar 2018」15日目の記事です。 devblog.thebase.in こんにちは、Backend Engineer Groupの 菊地陽介 です。今回は、BASEの育休エバンジェリストとして記事を書きたいと思います。今年の8月8日に第一子(綾瀬はるか似の娘)が生まれ、その後BASEの男性社員としては初めてとなる育児休業(以下、育休)を1.5ヶ月ほど取得しました。 育休取得直後に娘の心臓に先天性の疾患があることが分かり、一般的な赤ちゃんの倍くらい世話が必要だったので私が育休を取って妻と2人で育児をすることができたことは大変ありがたかったです(その後手術を行い現在は回復の方向に向かっています)。 なにより育休期間中の1.5ヶ月は娘の成長を間近で見ることができ、私の人生において生涯忘れないであろうかけがえのない日々となりました。そこで私が今回育休を取得して気付いたこと、思ったことを日本の全パパにお伝えしたい。 日本の男性社員が育休を取りにくい理由の一つとして、前例がまだ少なく情報があまりないということが挙げられると思います。今回育休に関する様々な知見を得ることができましたので紹介します。本記事をきっかけに育休に対する理解を深められたり、これまでより柔軟に考えてもらう機会になれたら幸いです。 (注) - 制度の内容は育休を取りやすい方向に変わっているので本記事で述べているものと現在の制度が異なっている可能性があります。 - 本記事は育休について大まかに知ってもらうことが目的なので詳細については言及していません。 日本のパパの育休について 私が育休を取得しようか検討する際に知りたかった情報を紹介します。 一ヶ月以上の育休取得率は1%以下 パパの育休取得率は年々上がってきてはいるもののそれでも平成27年度時点で2.65%程度しかありません。さらに5日未満しか育休を取得しない人が56%を占め、一ヶ月以上の育休を取っている割合は20%以下です。つまり一ヶ月以上の育休を取っているパパは日本には100人に1人以下しかいないというのが現状です。 (厚生労働省「 資料 「平成 27 年度雇用均等基本調査」の結果概要 」より) 休業前の手取り賃金の8割がもらえる 育休を取得しない理由の一つとして、育休期間中の収入が0になると思っている人が多いのではないでしょうか。その心配はいりません。雇用保険の育児休業給付金制度があります。(※支給要件があります)手取り賃金で比べると約8割が支給されます。下記資料が分かりやすかったです。ちなみに メルカリ さんのように育休期間中の給与を100%保障する会社もあるようです。 (厚生労働省「 育児休業給付金の資料 」より) ※その他にも健康保険から支給される「出産育児一時金」や「出産手当金」等があります。(※こちらも支給要件があります) 産後一年の間で取得可能、また延長も可 育休は子供が産まれてすぐに取得しなければいけないものではありません。出産のタイミングでは自分が関わっているプロジェクトが忙しかったり、業務の引き継ぎがたくさん必要で育休を取りにくいという場合があるかもしれません。しかし「子供が産まれてから1年の間であれば取得可能」ということであればその間で育休を取れるように調整することは難しくないのではないでしょうか。 また、産後一年以内であれば育休を延長することもできます。私の場合当初1ヶ月の予定でしたが、育休取得後に娘の心臓に疾患が見つかり検査や入院などが必要となったため半月ほど延長しました。 私の育休 育休を取得するまで 上司に伝える 出産予定日の半年ほど前に上司に「1ヶ月くらい育休とりたいんですけど」と週に1度行われている1on1ミーティングの際に相談してみました。 このとき嫌な顔されるといった心配はまったくしておらず、「お、いいね!ていうか一ヶ月だけでいいの?もっと取った方がいいよ!」と言われるほどでした。 出産予定日前後は自分がいなくても業務に差し支えがないように配慮してもらいました。感謝です。 チームメンバーに伝える チームメンバーには育休を取る一ヶ月前くらいに報告しました。 育休を取得しない理由の大きな理由の一つとして、自分が休むと会社が回らなくなってしまうと心配してしまう人が多いのではないでしょうか。もしそう思うのであれば本当に心配しなくてはいけないのは、自分がいないと会社が回らないほどに属人化してしまっている状況です。私の場合は属人化している業務がほぼなかったため、引き継ぎは隣の席の @tenkoma に15分ほど説明しただけでした。 赤ちゃんはいきなり産まれてくるわけではありません。自分も父になるんだという気持ちが湧いてから産まれるまでに半年以上の猶予があると思います。その間に自分の業務をチームのメンバーができるように引き継ぎを行い、気持ちよく育休を取得できるようにしたいですよね。 (ちなみに自分が休んでいても何一つ問題なく会社が回っていると寂しい気持ちになります) 申請の書類を提出する 育休の申請については、育休を取得する前に会社に簡単な申請書を提出し、あとの手続きは会社でやってもらえたのでとても簡単なものでした。 育休中 育休はバケーションじゃない 育休前は少なからず下記のような気持ちがありました。 知性を感じさせるパパを目指して自己啓発本をたくさん読もう カッコいいパパを目指して毎日筋トレして体を鍛えよう 子供に尊敬されるパパを目指して国旗とか覚えよう etc 結果的に何一つとしてできませんでした。 育休期間中に育児以外のことをする時間はないと思って間違いないと思います。おそらく日本では育休をバケーションのように考えている人が少なからずいらっしゃるようで、育休を取得することに対して後ろめたさを感じ、取得に踏み込めない人が多いのかなと感じます。 育休≠バケーションです。本音を言うと私にとって育休は仕事をしてるよりしんどかったです。 ママの期待値を調整する 産後クライシスという言葉があるように、出産を機に夫婦の関係性が悪い方向に大きく変わることも多いようです。 出産直後はホルモンバランスが崩れネガティヴ思考になったり、育児に対して過剰な責任を感じてしまう母親が多く、期待してるような行動を父親が取ってくれないと苛立ちやすくなってしまうようです。特段穏やかな私の妻に限ってそれはないと思っていたのですが甘かったです。 育児中の母親は父親に求めるハードル(期待値)が高くなりやすい一方で、世の多くの父親はそれをなかなか超えてこないので苛立ちが生じるのだと思います。ですので、ハードルを下げましょう。 具体的には「オムツ交換とミルクを作るのはパパ、お皿洗いと料理の準備はママ」といったように家事や育児の役割分担をできる限り明確にすることが重要だと思います。例えば「お皿洗いは気が付いた方がやるようにしよう」としておくと、「私は寝不足で疲れてるんだからパパが洗ってよ」とパパがお皿を洗うことを期待されることがあります。しかし事前にお皿洗いはママの役割と明確にしておけばそのような期待は起きないでしょう。逆にお皿洗いはママの役割としているのにパパがお皿を洗ったら感謝されるものです。 結果的にやってることは一緒でも期待値の調整次第でママからの評価は大きく変わるものです。 仕事と一緒ですね。 考え方が変わった 育児をすることで色々考え方が変わったなと感じます。私はたくさんある育児の中でオムツ交換が一番好きです。下記写真のように赤ちゃんのなされるがままにお股を広げている無力な様がたまらなく可愛いく思うのです。 あの人怖いな、苦手だな、堅物だなとこれまでは避けていたような人に対しても、「昔はオムツ交換してもらってたんだな」と思うと微笑ましく感じられるようになり、以前よりも余計な感情なくニュートラルに人と接することができるようになったと感じています。 また、育児がこれほどまでに大変なんだと実感することで自分を育ててくれた両親に対しての感謝の気持ちが強くなりましたし、世の中にいるパパママはみんなすごいなと尊敬の気持ちを抱くようになりました。 育休が終わってから 復帰初日は転職初日のような緊張感がありました。1.5ヶ月間の育休でしたが個人的には1年くらい休んでいた感覚でした。私が育休を取っている間に次のような変化がありこれぞベンチャー企業だなと思いました。 渋谷の2フロアに分かれていたオフィスから、六本木の高層ビルの37階に引っ越ししていた 新入社員がたくさん増えてた BASEの男性社員として初となる私の育休をきっかけに多くの男性社員から「次に子供が生まれる際には自分も育休を取ろうと思う」というふうに声をかけられました。 今後も育休エバンジェリストとして育休を取得することの意義を布教していきたいと思います。 出産&育児で得た知見 本記事の育休を勧めるという目的からは話が逸れるのですが、出産&育児の際にいくつか知見を得たので共有します。 日用品にかかるお金はけっこう節約できる 先輩パパママから「子供ができるとオムツ代とかミルク代がバカにならないよ!」とよく聞かされていました。 実際はそんなことありません。 そうです、ふるさと納税です。ふるさと納税を有効に使えばオムツ(メリーズ、ムーニーマン、グーン等なんでもある)、おしりふき、ミルクetcほとんどなんでも揃えることができます。ぜひ活用してみてはいかがでしょうか。 おすすめのアプリ 様々なアプリをたくさん比較した上で厳選したというわけではないですし、一番メジャーなものを使っていただけなのですが下記で紹介するアプリは大変素晴らしく感動したので個人的におすすめさせてください。 トツキトオカ 「 トツキトオカ 」は、夫婦で共有できる妊娠記録・日記アプリです。 日々アプリの中の赤ちゃんが成長していき、また話す言葉もグッとくるものなので父親になるんだなという実感が徐々に湧いてきました。 また妻が毎日このアプリで日記を更新していたのですが、口には出しづらい心配ごとなどを書いていたりして妻の心情を理解するのに役立ちました。 ( トツキトオカ 公式HP より) みてね 「 みてね 」は、子供の写真を家族で共有することができるアプリです。 スマホはLINEくらいしか使いこなせていなかった私の父と母もこのアプリなら使いこなせるくらい、UIUXが優れていて使いやすいです。 また、フォトブックやDVDをすごく簡単に作成することができます。しかもとても安価に作成できるので運営会社が今後も継続していけるのか心配になるほどです。 ( みてね 公式HP より) まとめ 本記事を読んで育休を取ってみようかなという気持ちになっていただけたなら幸いです。 私が育休をとるにあたって業務の調整を行ってくれた上司やフォローをしてくれたチームメンバー、復帰後も温かく迎えてくれたBASEの社員みんなにこの場を借りてお礼を伝えたいと思います。 本記事を読んで少しでも伝わったなら嬉しいのですが、BASEはみんないい人でとても働きやすい良い会社です。 BASEってどんな会社なの?会社に遊びに行ってみたい!育休の話もっと詳しく聞きたい!財テクの話が聞きたかった!と思っていただける方がいらっしゃればぜひ 私 までメッセをいただければと思います! 明日はデザイナーの吉岡さんがデザイン表現を広げるアレコレについて書くようです、ぜひご覧ください!
アバター
この記事は、「BASE Advent Calendar 2018」の14日目の記事です。 devblog.thebase.in こんにちは!Design Groupに所属している森( @ mrkzk )です! リードエンジニアのお兄さんに「森さん、寿司食いたくないですか?」と聞かれ「もちろん、食べたいです。」と答えたらアドベントカレンダーブログを書くことが決定していました!お寿司は隠語だったのでしょうか?びっくりです! では早速、タイトル通り デザイナーの私が初めてプロダクトのコードをコミットしてみたお話 をします。 どうしてコードを書くことになったの? 私の普段の業務はアプリのUIデザインなのですが、ときどきWebのクリエイティブ作成にも関わることがあり、いつもクリエイティブを作ったのちに「お願いします!」とこのようにエンジニアの方にお渡ししています。 今回は、既存バナーのUIやショップ数が古かったので更新するために差し替えが必要だったのですが、「お願いします!」と言いながら、画像の差し替えくらい自分でできたらな、、と思っていたところこのような流れが・・・ 「こんな綺麗な流れってあるんだ・・・」と感心しつつ、探り探りでやっていくことに・・・ まず、作成したクリエイティブをステージングへ、そして本番へアップロードしなければいけません。 そこで噂には聞いていた画像アップローダーの出番です! 画像アップローダーの実装に関しては、先日エンジニアリングマネージャーの加賀谷が記事に書いています! devblog.thebase.in 画像アップローダーくんの使い方 Slackの該当チャンネルに作成したクリエイティブを投稿する Slackで @image-uploader hey と話しかける   画像アップローダーくんからお返事がきたので快く yes を選択 すると・・・ ステージングにアップロードできちゃいました!こんな簡単に・・?と疑っていると、今度はタコとネコのハーフからお返事が来たので快く yes を選択。 すると・・・ できました!差し換え用のクリエイティブを本番にアップロードすることができました。すごい! と、ここまでは画像アップローダーくんに助けられていましたが、ここからは一人の戦いです。 OSの出し分け 今回私が作成したクリエイティブは、アプリへのダウンロード導線バナーです。 OS別にバナーを作成しているので、iOSユーザーには左のiPhone端末バナー、そしてAndroidユーザーには右のAndroidの端末バナーを出したいのです。 ところが、コードの書き方が全くわかりません。 とりあえず OS 出し分け で検索してみると、「JavaScriptで簡単にUA判定する方法」に関する記事が出てきました。どうやらUA判定というものがあって、これを使えばiOSかどうかがわかるらしいので、UAを利用してiOSを判定するコードを探しました。 こちらです。 var ua = navigator.userAgent var isIOS = ua.indexOf( "iPhone" ) >= 0 || ua.indexOf( "iPad" ) >= 0 || navigator.userAgent.indexOf( "iPod" ) >= 0 こちらを利用して、実際に画像を出し分けるコードを書いてみます。 imgタグはわかっていたので、このソースを切り替える方法を調べていくと「getElementByIdを使ってソースを書き換えると良い」とあったのでとにかく書いてみました。 < script > var ua = navigator.userAgent var isIOS = ua.indexOf ( "iPhone" ) >= 0 || ua.indexOf ( "iPad" ) >= 0 || navigator.userAgent.indexOf ( "iPod" ) >= 0 ; var img = document .getElementById ( 'appBannerImg' ) if ( isIOS ) { img.src = "//static.thebase.in/img/u/U1MK212HY/FER5CBE2D.png" } else { img.src = "//static.thebase.in/img/u/U1MK212HY/FERP402M8.png" } </ script > これでpushしてみると・・・ちゃんと動いてます!出し分け成功! なんとかこれをプルリクエストまで持っていくことができました!なんでしょうか、この達成感は・・!! プルリクエストー!!と舞い上がっているところに間髪入れずリードエンジニア兄さんからのつぶやき↑が聞こえてきました。コードを少し書くだけでも一苦労だったので正直勘弁してほしいなと思ってしまいましたが、どうせなら!と querySelector に書き換えることに。 - var img = document.getElementById('appBannerImg') + var img = document.querySelector('#appBannerImg') getElementById の部分を querySelector に変更し、imgタグの前に # をつけるだけ! これがイマドキなんですね(getElementByIdの方が響きはイカしてると思いますが) こうして無事リードエンジニアの兄貴からOKが出ました!リリースです🎉 おしまい ここまでの流れを簡単に書いてきましたが、実際はコマンド一つを実行するにも調べながら、質問しながら、とかなり苦労しました。 それでもターミナルがガガガーっと動くのは楽しいし、自分の制作物をエンジニアさんにお任せするのではなく、最後まで責任持って本番環境に届けることに気持ちの良さと達成感を覚えました! 私のようにこれからは一通り自分でやってみるぞ!!というデザイナーが増えると嬉しいです。 ちなみに Twitterトップ でLINEスタンプ販売中なので見てみてください! 明日の担当はBASEのイクメン・菊地さんです!
アバター
BASE Advent Calendar 2018 (13日目) この記事は、「BASE Advent Calendar 2018」の13日目の記事です。 devblog.thebase.in Backend Engineerの沖中 ( @okinaka ) です。 読者のみなさんはテストを書いてますか?テストが面倒くさいとか思ってませんか?(私はたまにあります) そんな時、モチベーションをあげるには、どのような工夫をしていますか? 私の場合、カバレッジ計測の結果のレポートがとても役に立っています。 レポートを眺めると、視覚的に 「どの行が実行された」とか 「カバレッジ率が◯%」だとか 「ここ複雑だけどテストがないクラスがあるよ」など、 いろいろ気づきがあります。 「ここマズイな、テスト書いて確認しなくちゃ!」という気持ちになるのでとても重宝しています。 とてもいいツールだと思うのですが、残念ながら BASE社内でもまだ十分に活用されていないのが現状です。ですので、この記事をきっかけに、広めていければいいのではないかと期待しています! 現状ではカバレッジレポート出力が時間がかかる問題があるのですが、今後、自動生成する仕組みを用意できればと考えています。 カバレッジレポートについて BASEでは、主にCakePHPでサービスが構築されており、ユニットテストも、若干手を加えていますが基本的にはCakePHPに用意された仕組みを利用しています。カバレッジレポートは、その機能を利用して生成しています。 カバレッジについての簡単な説明は、 CakePHP クックブック をご覧ください。 (内部的には PHPUnit を利用しています。詳細は こちら ) カバレッジレポートには出力形式がいくつかあるのですが、今回はモチベーションアップが目的なので、人が見てわかりやすいHTML出力を紹介します。 出力されるHTMLページは大きく分けて3つに分類されます。 各ディレクトリ内のファイル一覧 (index.html) ファイル毎の大まかなカバレッジ状況を一覧で確認できるページ *1 ファイル一覧(index.html) 各ファイルのカバレッジ詳細 (PHPファイル名.html) どの関数・行がテストされたかなど詳細を確認できるページ 次にどんなテストが必要かを考える際に役立ちます カバレッジ詳細(PHPファイル名.html) ダッシュボード (dashboard.html) 以下のカバレッジ状況を俯瞰的に確認できるページ クラス・メソッドのカバレッジ率 (Coverage) コードの複雑度 (Complexity) の分布図 カバレッジ率の低い順のクラス・メソッド一覧 リスク (CRAP値) の高い順のクラス・メソッド一覧 全体の状況を把握したり、次にどのファイルをテストすべきかをざっと見るのに便利! ダッシュボード(dashboard.html) テスト作成とカバレッジレポート出力 では、実際にレポートを出力して内容を見てみましょう。 まず最初に、とあるメソッドに対してテストを1つ書いてみました。 <?php public function testView () { // テスト対象を実行 $ this -> testAction ( ' /posts/view/1 ', [ ' method ' => ' get ', ' return ' => ' contents ' ]) ; // とりあえず、ステータスコードの確認 $ this -> assertSame ( 200 , $ this -> controller -> response -> statusCode ()) ; // その他、なんらかの結果の確認... } この時点で、カバレッジレポートを出力してみます。レポートを見ると、どうやら一部実行されていない行があるようです。 (注: 緑の部分 がテストされた行で、 赤い部分 が未テストな行です。 赤い 部分をなんとか消したくて、ウズウズしてきますね!) カバレッジ詳細(テスト1回目) 未テストだった行を確認してみると、 $id が未指定 ( null ) だった場合と、 データーベースに未登録な $id だった場合の処理がテストされてないようです。 このレポートを受けてテストケースを追加してみます。 <?php /** * @expectedException NotFoundException * @expectedExceptionMessage Invalid post */ public function testViewで、IDが未指定だった場合は例外発生 () { $ this -> testAction ( ' /posts/view/ ', [ ' method ' => ' get ', ' return ' => ' contents ' ]) ; } /** * @expectedException NotFoundException * @expectedExceptionMessage Invalid post */ public function testViewで、未登録なIDを指定した場合は例外発生 () { $ this -> testAction ( ' /posts/view/999 ', [ ' method ' => ' get ', ' return ' => ' contents ' ]) ; } 結果がこれ。 カバレッジ詳細(テスト2回目) やりました! 赤かった ところが 全部緑色 に!これで安心です。 ここで、注意点があります。 全部緑色 になって喜んでいましたが、 緑色の行 を増やすよりもむしろ テストの内容 の方が重要です。 たとえテストが実行されたとしても、ちゃんと動作を確認していなければ意味がありません。あくまで、モチベーションを高め、テスト状況を確認するためのツールと捉えていただければと思います。 明日はデザイナーの森さんです!お楽しみに! *1 : 注: CakePHP 公式のクックブックの チュートリアル を元に、独自のテストを実行した結果を表示しています。
アバター
この記事は、「BASE Advent Calendar 2018」の12日目の記事です。 devblog.thebase.in こんにちは、BASE BANKインターンの かわごえ( @heart_breakers2 )です。 今回は少し趣向を変えて、「鶴岡さん観察日記」と題して、BASE 代表取締役CEOおよびBASE BANK 代表取締役CEOである鶴岡の普段の様子をイラストを交えてカジュアルに伝えていきたいと思います。 (みなさんにもカジュアルな雰囲気で読んでいただきたいので、記事内ではあえて「鶴岡さん」と表記しています。) 私がインターンとしてBASE BANKにジョインし、鶴岡さんのななめ前の席に配属されてから半年が経ちました。 みなさんがきっと知らない鶴岡さんの素顔や、BASEの好きなところをまとめてみました。 この記事を読んでいただき鶴岡さんやBASEに興味を持っていただけると嬉しいです。 はじめに みなさんは、BASE代表の鶴岡さんをご存知ですか? BASEが提供するネットショップ作成サービス「BASE」は、「お母さんも使える」をコンセプトとしたネットショップを作成できるサービスで、現在は60万人を超える オーナーズ に利用されています。 鶴岡さんはBASEの創業者。 Forbes JAPANが発表した「 日本の起業家ランキング2019 」で3位に選出された、日本で最も注目されている起業家の一人です。 「60万ショップも使っているサービスを作っているんだ!すごい!」 「Forbesでも注目されているんだ!すごい!」 と思った方もいるのではないでしょうか。 私から見た鶴岡さんは、傾聴力と本質を見抜く力が圧倒的に高く、どんな人でも一緒にいて心地よい存在で社内外にファンが多いようです。 しかし、鶴岡さんが遠い存在と思われてしまっているのか、↓のようなツイートにだれもツッコめない状況になっているような気がします。 くだもの食べながら炭酸水飲んだら、ファンタになる — 鶴岡裕太 (@0Q7) 2018年11月10日 この記事では、社内での鶴岡さんはどのような様子なのか?何を大切にして事業を進めているのか? BASEの素敵な制度などもご紹介しつつ、鶴岡さんにまだお会いしたことがない方でも身近に感じていただけるよう、イラストと写真で紹介していきます💫 鶴岡さん観察日記 AM 11:00  Be Hopefulキャンディー 鶴岡さんの朝は、たいてい午前11時のミーティングで始まります。 鶴岡さんはミーティング中どんな感じなの?と気になる方も多いのではないでしょうか。 社内のミーティングの様子を覗いてみましょう。 鶴岡さんとの社内ミーティングは社長室で行われることが多く、鶴岡さんが座る席の近くにはBe Hopefulキャンディーが入ったガラス瓶が置かれています。 ミーティングではキャンディーを手に取り頬張っている鶴岡さんですが、発言はいつも本質的で、 「オーナーズのためになるどうか」 「オーナーズにとってわかりやすいかどうか」 をいつも大切にしているので、私たちもどのような判断をすべきかがクリアになります。 ちなみに、BASEの 行動指針 は Be Hopeful Move Fast Speak Openly の3つで、どれも欠かせない重要な指針となっています。 実際のBe Hopefulキャンディー。 行動指針の浸透のために作られたもので、キャンディーの中に"Be Hopeful"と書かれていてかわいいです。 キャンディーの色は、「BASE」のサービスロゴであるティピのマルチカラーを使用していて、社内のいたるところに置いてあります。 PM 0:30  みんなの食堂 先日からBASEでは、いわゆる社員食堂のサービスが毎週水曜日に提供されています。 鶴岡さんも毎週この社食を利用していて、メンバーと楽しく会話しながらランチをしています! 社食うまい pic.twitter.com/Qf3p0RwL7V — 鶴岡裕太 (@0Q7) 2018年11月15日 (鶴岡さんの写真は撮り忘れちゃいましたが、みなさんが食堂を楽しんでいる様子です💕) 現在は30食限定でいつもすぐに売り切れてしまうのですが、500円で定食を食べることができます!! (BASEは37階にあるので、ランチにいくにも時間がかかってしまうのです) ココロータス 別の日の様子です。 鶴岡さんはお昼頃になると、定期的に、大好きな神宮司さん(執行役員/Product Manager)にこう語りかけます。 ココロータス とは、アジア料理を中心に、多国籍のランチを提供するキッチンカーです。またココロータスは、お支払いアプリ「PAY ID」の加盟店で、スマホでQRコード決済が可能なのでお財布を持たなくてもランチを買うことができます。 メニューが提供されるのを今か今かと待ちわびているようです。 ココロータスのメニューはどれもボリューミーかつ1000円以内で注文できます。企業の社長はいつも豪華な食事をしているというイメージの方もいらっしゃるかもしれませんが、鶴岡さんのランチはいたって庶民的なんです。 ココロータスのキッチンカーは現在、江戸川橋と六本木三丁目で販売を行っています。六本木周辺に勤務されている方はぜひチェックしてみてくださいね! PM 3:00  カップスープ ある日出社すると、隣の机に大量のカップスープが置かれていました。 置いたのはもちろん鶴岡さん。 とのことだったので、3時のおやつにたまごスープをいただきました。 この時は特に何かのお祝いやイベント事があったわけではないですが、特に理由がなくともさらっとメンバーに差し入れをする様子を見て鶴岡さんの優しさや心遣いを感じました。まだ食べきれていないほど、大量に購入されていました! ちなみに、鶴岡さんが好きなのはコーンポタージュ。 PM 4:00  「順調?」 夕方になると、近くにいる人を中心に「順調?」と話しかけるのが鶴岡さんの癖です。 「え!?社長に進捗確認されるとかプレッシャー!!」と感じる方もいるのではないでしょうか。 でもBASEでは違います。 私から見ると、鶴岡さんの「順調?」という発言はメンバーにプレッシャーをかけたいという意図ではなく、「困ったことがあったらいつでも聞いて大丈夫だよ」という心配りなのではないかと思います。 なので、鶴岡さんに「順調?」と聞かれて正直に答えないメリットはありません。順調ならそう答えればいいし、何か詰まっている点があるなら打ち明けた方がよいです。 私自身も実際にこの言葉をかけられて、「ユーザーからの反応があまりなくて、次の施策をどうするか悩んでます!」と相談したことがあります。 なぜなら先ほども書きましたが、鶴岡さんは傾聴力が優れていて、本質的なことを見抜く力が高く、私たちの想像を超えるようなフィードバックをしてくれるからです。 BASEの行動指針に「Speak Openly」とあるように、たとえ物事が順調に進んでいない場合でも、正直に話すことで鶴岡さんから的確なフィードバックを得られるため本質を見失わずにプロダクトを作り続けることができます。 お気に入りのゴジラをつかみながら喋る鶴岡さん PM 5:00  ジャック・ドーシー氏 鶴岡さんが12/3に日経BP社によって行われたイベント「 スモールビジネス新潮流 「ポスト2020」を見据えて 」に登壇が決まった時の話です。 テーマは「スモールビジネスが秘める可能性と未来」について。Twitter、Squareの創業者であるジャックドーシー氏も参加するセッションです。 中小企業や個人で活動しているオーナーズをどのような方法で応援できるかについて、鶴岡さんの考えやBASEの取り組みを話したそうです。当日の様子が気になる方はぜひ、Twitterで「#ポスト2020」と調べてみてください! ちなみに鶴岡さんはかねてからジャックドーシー氏の大ファンで、このイベントでの登壇が決まったときは本当に嬉しそうでした。 It was great meeting you, @jack ! pic.twitter.com/EWtlZUYJ9x — 鶴岡裕太 (@0Q7) 2018年12月3日 (鶴岡さんの目がキラキラしてます!!) おわりに いかがでしたか。 鶴岡さんの普段の様子が伝わるように意識して書き上げたので、鶴岡さんの温かい人柄、飾らない素直な性格、そしてBASEの良さが伝わっていると嬉しいです。 明日は、沖中さんがカバレッジレポートについて書きます!
アバター
先日の締め会の様子 これは「BASE Advent Calendar」11日目の記事です。 devblog.thebase.in こんにちは。BASEで代表をしている鶴岡( @0Q7 )です。 11日目の記事を担当しています。 ある日、Slackでアドベントカレンダーを書く人を募集していたので、調子に乗りそれとなく書く予定にしていたら、書く内容が全く思い付かず悩みすぎたので今はすごく後悔しています。 本日12/11はBASEの設立記念日で、6年が経ちました。早いような遅いような。 ついこの前までは「今日のGMVは100万円は行きたい、そしていつかは500万くらいはいけるようなサービスにしたい。」なんて思いながら毎日帰宅していた気がしますが、その目標から桁が2つくらいは増えたので少しは成長できてるのかなと思います。 ただ何かを成し遂げたわけではないので昔話をしたいわけでもなく、とはいえエモくビジョンの話をするのもなんなので、この6年で実際に感じた学びを書こうと思います。 内容は個人的な主観ですが、誰かの役に立てれば嬉しいです。 殆どの戦いは負けても良い 社会人になって一番の学びかもしれないですが、殆どの戦いには負けても大丈夫なんだなと思いました。 戦いとは、議論や意思決定のことを指していますが、最終的に成し遂げたい目標があったときに、その途中で訪れる論点を全て自分の思い通りにする必要もないんだなと。全ての勝負に勝とうとするにはあまりにも今の人生では短いので、全部の駒を取る事が勝利なのではなく、王将を取る事が勝利なのだとあらゆる場面でゲームをしっかり理解する能力を磨きたいものです。そして王将だけは必ず取りましょう。 感情とは上手く付き合いたい 特に怒りという感情とは上手く付き合った方がいいかなと思いました。 怒りという感情を露わにする事が、物事を最短で進めるための手段になる事は殆どないです。自分の思う通りにしたければまず感情をコントロールできる人間になりたいです。 プロダクトが全て 自分たちが作っているプロダクトが全てを癒してくれます。学歴も地位も立場も関係ないです。最高のプロダクトを作ったチームが一番かっこいいです。「BASE」を使ってくれている60万のショップをみてもそう思います。最高のプロダクトを作ることが出来れば市場が評価してくれます。世の中にあった方がいいプロダクトは必ず成功すると思いますが、タイミングだけが変数です。諦めずチームで最高のプロダクトを作りましょう。 インプットは難しい 自分に合っていない情報のインプットがあると無駄な時間を使ってしまう事があります。情報がこれだけ溢れている昨今において、自分に適している情報はすごく少ないです。Twitterで流れてくる情報は自分のフェーズには早かったりしてほぼ役に立たないなと感じます。世の中で起きている事実だけを把握し、自ら考え自分が思うままに行動するのが一番かなと思います。 評論家にはなりたくない 評論するよりされる側の方がかっこいい!というのも勿論ありますが、これは評論家を批判してるわけではなく、自分のメンタルを保つためにあまりオススメしません。自分を追い込むことになる言動はなるべく慎みたいなと思います。 テクノロジーには超楽観的に テクノロジーがあらゆるものを便利にしていきます。プラットフォームがあらゆるリスクを巻き取っていきます。今不便だと感じる事は全て解決されるんだろうなと感じます。そうやって訪れる未来を幸せと呼ぶのだと信じています。 徳を積む そもそも徳が何かも分かっていないのと、どうやったら徳を積めるのか僕もよくまだ分かっていないですが、お金では買えないものである事は確かです。徳が高い人は凄いなと思います。日々の言動や所作を周りの方々に見ていただいて、自ずと積まれていくものだと思うので、常日頃から意識して生きていきたいです。必ずその徳に助けられる日が訪れると思います。 パッと思いつくところだと、こんな感じでしょうか。 こういうのを書くタイプでは無いのですごく疲れました。。。また黙々とプロダクト作りに励もうと思います。 普段は オーナーズ の為にEC・金融・決済領域でプロダクトを作っています。 もっと話聞いてみたい!BASEってどんな会社なの!会社に遊びに行ってみたい!と思っていただける方がいらっしゃればぜひ メッセ をいただければと思います。 明日はBASE BANKインターンのマリーちゃんです。 ではまた。
アバター
これは「BASE Advent Calendar」10日目の記事です。 devblog.thebase.in こんにちは。BASEで採用広報を担当している米田( @AiYoneda )です。普段はL'Arc-en-CielのHYDEさんの追っかけをしていて、来週にはきっと東京ドームでその御姿を拝んでいることでしょう・・・ さて、HYDEさんの話は置いておいて、私はテックブログこと「BASE開発チームブログ」の編集部として運営に関わっています。今回は、約8ヵ月続けてきたこの「BASE開発チームブログ」の運営についてのノウハウを余すことなく書いていこうと思います。 テックブログはエンジニア・デザイナー組織のプレゼンス向上や採用に効果があるなどと聞いているけど、なかなかうまく運営できていないという方もいらっしゃるかと思います。そういった方にこちらを読んでいただいて少しでも参考になれば幸いです。 テックブログって本当に効果あるの?約8ヵ月間、編集部として運営を続けてきた効果、成果 BASEでは2018年4月にテックブログ編集部を作り、以降テックブログの運営を担っています。下記は編集部が立ち上がって以降の効果や成果について記載したものになります。 公開記事数がめちゃくちゃ増えて30本以上公開できた 2017年:8本 ↓ 2018年4月以降:31本! (このAdvent Calendarで今年あと15本公開予定です!) 2017年は半年くらい更新していない期間もあり、かなりさみしい感じでしたが、2018年4月以降は月2本以上の更新ペースを維持できています。またこの数字はむやみに増やしたわけではなく、ひとつひとつの記事の内容やクオリティは一定に保った上での成果です(実際に記事を読んでいただければわかると思います)。 執筆に関わったメンバーの数ですと、このアドベントカレンダーを含めると約30名のメンバーに執筆してもらうことができました。またブログの読者数(はてなブログの機能で購読していただいている読者の数)は約3倍になりました。 イベント、勉強会での発表機会ができた 大規模Webサービスの開発にせまる「BASE Geek Night #1」 base.connpass.com レガシーコード改革!UT/CIでWebサービスの技術的負債を解消する取り組み base.connpass.com テックブログで書いてもらったネタをプレゼン形式にして勉強会やmeetupで発表してもらうことができました。上記は実際に開催したイベントです。中にはこのイベントで「人生で初めてLTをやった」というメンバーの声もあり、外部での発表機会の創出につながりました。 採用面談、面接で「テックブログを見てます!」との声が増えた 正確に数値を計測してはいないんですが、採用候補者とのカジュアル面談や面接を担当するメンバーより、候補者の方から「テックブログ見てます」と言っていただけることが明らかに増えたとのことです。 何より、このAdvent Calendarを実施できた! 日々の運営の積み重ねが実ったのか、2018年はBASEで初のアドベントカレンダーを開催することができました。 11月末に編集長がSlackに下記のように投稿したところ、 これから数時間後… 次々とメンバーが手を挙げてくれて、あっという間に25枠がすべて埋まりました…この怒涛の投稿の様子には正直かなり感動しました。 しかもアドベントカレンダーの開始は12/1、この投稿は11/28とかなりタイトな依頼だったのにメンバーが次々と手を挙げてくれて、BASEの行動指針の一つである「 Move Fast 」を体現していてすばらしいです。 テックブログの運営で挫折しないためのTips ここからは実際の運営のTipsを書いていきます。 テックブログの運営でネックなのは、運営担当者自身が人事など現場とは別部署に在籍していたり、入社したばかりという方や年上の方が多い方の場合、周りのメンバーをうまく巻き込めるか、途中で挫折しないか不安だという部分ではないでしょうか。私自身も入社してすぐの、まだメンバーの顔も覚えきれていないような状態で編集部として運営に携わるようになり、かつ前職とは業界や職種も違う状況で初挑戦なことばかりでした。 そんな私でも実行できたTipsですので、特に人事や広報、若手社員の方に試していただけると嬉しいです。 テックブログ運営の成功体験をもつメンバーを運営メンバーに入れる このメリットは下記の2つだと考えています。 運営ノウハウを転用できる エバンジェリストとして運用のモチベーションを保ってくれる いきなりテックブログの運営経験がない私が運営を始めてもスベるのは目に見えています。なのでそういう時には素直に成功体験を持つメンバーから運営ノウハウを教えてもらうのが得策だと思います。 また、テックブログ運営の成功体験のある人物とともに運用していくことで、自らのモチベーションを保つことができ、運用中の挫折を回避することができます。自分自身に成功体験がないと運営していく中でどうしても「これは本当に効果があるのだろうか?」、「現場メンバーには迷惑ではないか」と疑心暗鬼になってしまうこともあるのですが、成功体験のあるメンバーはそのような不安を解消してくれる心強い存在で、「 Be Hopeful 」に運営を続けることができます。 サポート体制を作る BASEのテックブログでは、現在編集部が中心となり運営をしています。 編集部が行っているのは下記です。 月1の編集会議でのネタ出しと、予備も含めてその月に公開したい記事を決める 該当のメンバーに執筆依頼 記事チェック(編集やタイトル案の相談など) 画像の作成・撮影のサポート 執筆マニュアルのメンテナンス 記事案の管理はスプレッドシートで行っています。「常設」がいわゆる予備記事です。 他の企業では、テックブログは書きたい人が書きたいときに自由に記事を公開するという運営方法を取っている場合もあるかと思います。もちろんBASEの場合も「このイベントで登壇したからその内容をブログ記事にしたい」、「こんな取り組みをやってみたんだけど、ブログネタとしてどう?」といった流れで記事を書いてもらうことはあります。 運営方法は様々だと思いますが、編集部が主導する体制を作ることのメリットは下記だと考えています。 投稿ペースを維持できる 社内のいろんな人に書いてもらいやすい 記事のテイストを一定に保てるため読者が読みやすい まず投稿ペースに関してです。現在は月に2本は必ず公開するという方針の下、編集部で予備も含めて公開したい記事を決めています。自由投稿だと投稿の頻度にバラつきが出てしまう可能性もあり、そうなるとブログの見栄えとしてはあまり好ましくありません。編集部がペースメーカーとなって更新頻度を維持することでブログの活気を保つことができると考えています。 「社内のいろんな人に書いてもらいやすい」についてですが、自由投稿の場合ですと、社内で素晴らしい取り組みを行っていたり、目覚ましい成果を上げているけどもブログはあまり書かないという人もいて、機会損失が発生する可能性もあります。編集部体制では、そういった取り組みを行っている人に注目して執筆依頼をしています。先述のように2018年4月以降は約30名のメンバーに執筆してもらうことができ、記事内容もPHPでのWebサービス開発からデザイン思想、データベースや機械学習などバラエティに富んだものになっていると思います。 もちろん、理想はどのメンバーからも「これブログで書きたいんだけど」と次々に提案をもらえ、さまざまな記事が投稿される状態だと思いますし、今回のアドベントカレンダーはまさに理想の形で開催することができ、とても嬉しかったです。 そして記事テイストの統一に関してですが、こちらは編集部でテックブログの執筆マニュアルを用意していたり、公開前に編集部が内容を確認することで実現できているかと思います。とはいえこれは最低限のチェックを行うのみで、執筆者の個性を最大限に活かすよう意識しています。 お祭り感を出す 前述のように、現在は編集部から記事の執筆を依頼する場合があり、依頼の仕方によっては執筆者が義務やタスクのようにネガティブに捉えてしまう可能性もあります。 そこで、記事の公開に関する要所要所でお祭り感を出すことで、執筆を依頼された人もまだ執筆したことのない人も、記事の執筆という行為をポジティブに捉えてもらうことができると考えています。 まず、当たり前ですが記事を公開したらメンバー全員がいるSlackチャンネルでお知らせします。記事を公開したら、Slackの #general で執筆した人に「こんなの書いたよ」と投稿してもらいます。そうすると代表の鶴岡やCTOの藤川も含め各メンバーがSNSでシェアしてくれるのでちょっとしたお祭り感が出ます。 またはてブのホットエントリ入りした時や、媒体で取り上げられたりしたときにはメンバーが随時共有してくれるので、執筆した本人も嬉しいですよね。 こんな感じでシェアしてくれます。 おわりに こうやって積極的なテックブログの運営ができるのも、現場のメンバーが日々のプロダクトづくりで様々なことにチャレンジしているからこそです。そして私がこうやって記事がかけるのも編集部のメンバーのサポートのおかげです。この場を借りて記事を執筆してくれるメンバー、編集部のメンバーにはお礼を伝えたいと思います。 今後ともみなさまにお楽しみいただけるようなテックブログの運営をコツコツと続けていきます。 BASEのメンバーは素敵な人たちばかりです。このテックブログを読んでBASEにご興味をもってくださった方は、ぜひ下記をご覧ください!一緒にBASEではたらきましょう! binc.jp 明日はBASE株式会社が創業して6周年ということで、代表の鶴岡が記事を書きます!乞うご期待!
アバター
この記事は、「BASE Advent Calendar 2018」の9日目の記事です。 devblog.thebase.in 前日は id:match_1 でした。こんにちは、10日ほど前に データベース移行についての記事 を書かせていただいた植木です。 今回はインデックスとBtreeのパフォーマンスチューニング系のお話をしたいと思います。 概要 Btreeの構造などを説明している資料は他にもあるし、SQLのアンチパターンもある程度あるけど、何故アンチパターンになるのかなど構造を理解しながらの説明が無いように思っていました。今回はBtreeの構造を説明しながら、どのように動いてパフォーマンスに影響しているのかを説明しようかと思います。SQLのパフォーマンスチューニングはBtreeの構造を理解しているかどうかで全く違うものになります。構造を意識しつつ、誤解しやすいところや何故この処理でインデックスが効くのかなど考えてみましょう。 Btreeの仕組み Btreeとは 情報処理試験などで出ますから知ってるよって人も多いですよね。ソート処理と木構造によって検索の高速化をするアルゴリズムとなります。このBtree構造がDBのインデックスの内部構造となります。弊社だとMySQL系ですので若干カスタマイズしたB+treeとなります。 下の図を見てほしいのですが、データを格納する単位であるノード内にはインデックス対象のキー値とそれ以外にリーフには実データへのポインタ情報(MySQLだとPK)が入っています。木構造を使った分岐で対象のリーフノードを選択したらポインタ経由で実テーブルにアクセスしてデータを取得します。 B+treeがBtreeと違う点はリーフノードにもブランチノードと同じ情報が入る事とリーフノードに次のリーフのポインタが入る事になります。これらの対応をする事で範囲検索やorder by、group by等の対応がしやすくなります。今回はこのB+treeを使っての説明をいたします。以降、Btreeと書いているものはB+treeです。 今回はわかりやすくしたいのでノード内に1データしか入らないと仮定してキー値に1−10のデータを入れた場合を想定します。実際にはノードに複数レコードが入りますが、ノードの先頭データと末尾データを使って同じような構造と比較をします。 また、参考に普通のBtreeだとこうなります。 何故この構造にデータを並べると早いの? 例えば、今回のように1〜10まであるデータの中で6を探したいと思った場合、通常なら10回6という数字と格納データとを比較する必要があります。ユニーク制約をつける事ができたとしても6回です。それに対して、木構造にしておけば、まずルートノードの5と比較して大きいので右のブランチへ行き、7と比較して小さいので左に行けばもう目当てのデータに辿りつきます。 インデックスをつけない状態なら検索はデータ量に比例した処理時間が必要ですが、木構造にする事で必要な時間はデータ量の対数に比例します。 実際のSQLでよくある問題などなど 範囲検索 下記のような検索をした場合に誤解している方がいます。 where id between 6 and 8 この時、Btree上で6と8の位置を検索してその間のデータを全て取るなんて器用な事はできません。今回の上図データだとおそらくDBが6を選んで検索し、6以降の全データを取得して一つずつ8より小さくないかを比較します。また、その際に6と8どちらかの数字を選択するかを判断するには統計情報を使います。 likeでインデックスが効く場合 ここで少しBtree構造がわかった事だと思うので、前方一致ならlike検索でもインデックスが効く事を説明してみます。例えば、yamada、tanaka、suzuki、yamakawaさんがいた場合にBtreeの構造にしたらsuzuki、tanaka、yamada、yamakawaの順番になりますね。この時に下記のような条件で検索すればyamadaさんの直前の位置まで木構造の動きで辿りつく事ができ、それ以降のデータを取得する動きができるという事になります。「%yama%」のような中間一致や後方一致では、Btreeのソート順が活かせないためBtree検索はできません。 where name like 'yama%' 検索キーに関数や演算を使うとインデックスが効かない Btreeはデータをソートしている事が重要な訳ですが、例えば、下記のように関数を使った場合、ソート順が変わる可能性があります。 where date(update_time)='2018-01-01' もっとわかりやすくreverse関数を使えばソート順は真逆になります。一つ一つの関数や演算の特徴までDBは把握しきれないためインデックスを利用できません。 複合インデックスの構造 続いて複合インデックスの場合のBtree構造です。購入などの情報をイメージしてもらえると良いかと思います。今回はユーザーと購入日をイメージしてみます。(user_id,update_dt)にインデックスをつけてuser_idが0001〜0004の人が1/1から1/3まで何らかの操作をした場合、インデックス内部のBtreeはこのような並びになります。 ここで下記のように検索すれば木構造の動きで(0003,2018-01-02)まで到達します(黄色塗り)。 where user_id='0003' and update_dt>='2018-01-02' 後はそれ以降の第一キーが0003のリーフを取得すればいいわけです。(本当はINSERT順考えたらこの木構造にはならないけど、今回は気にしない) 複合インデックスの順番誤り 日付とキーの順番を逆にしている場合がよくあります。(update_dt,userid)でインデックスをつけているわけです。そうなるとこういった構造になります。 この状態で先ほどと同じく「user_id='0003' and update_dt>='2018-01-02'」を検索しても、第一キーであるupdate_dtが'2018-01-02'の先頭(黄色塗り)を検索して、それ以降の8つのデータを全て取得し、user_idが0003と同じかを比較するという処理になります。このようにキーの順番を間違えると非効率な処理となります。 キー飛ばし ソート順が重要なので、基本的に第一キーを飛ばして第二キーのみを指定したクエリを実行してもインデックスは機能しません。第三キーまであるインデックスを作ってwhere句で第二キーを指定しない場合は第一キーまでのソート順が利用できるため第一キーだけのインデックスと同じ動きとなります。 最適な複合インデックスの順番づけ そんなものはありません。まず、=で検索するキーを上位に置きます。続いて上で書いたような範囲検索で使うキー(よくあるのが日付)やinで取得するようなキーを指定します。必須であればあるほど上位のキーにする事が重要です。そして範囲検索やINでの検索はそれによってどれだけ絞り込みができるかが重要なのでアプリケーション仕様にまで踏み込まないとなかなか判断は難しいです。 アプリケーション仕様によって、キーが指定される場合とされない場合などがありますので、そういう場合は(key1,key2,key3)のインデックス以外に(key1,key3)のインデックスも作るなどアプリケーションが指定するキーと実行頻度を考えて最適なインデックス構成を考える必要があります。 最後に Btreeの構造を理解すればわかるSQLパフォーマンスに関わる事をいくつかあげてみました。パフォーマンスチューニングにはジョインアルゴリズムの理解なども大きく響くのですが、まずはBtreeの理解をするのが一番だと思います。むしろ、ここを理解していないと確信を持ってのチューニングはできません。内部構造を理解して説明できる方が増えればSQL、DBのパフォーマンスもよくなりますので、是非ご理解いただけると良いかと思います。 BASEのSREチームではお客様に安心して買い物をしていただくために、サービスの安定性に責任を持ち、インフラ・アーキテクチャの改善に日々取り組み続けております。 ご興味を持たれた方いらっしゃいましたら是非ご連絡いただければ幸いです。 binc.jp
アバター
この記事は、「BASE Advent Calendar 2018」の8日目の記事です。 devblog.thebase.in こんにちは、BASE Design Groupマネージャーの早川( @match0129 )です。 今日は、ネットショップ作成サービス「BASE」の管理画面のリニューアルプロジェクトである「次世代管理画面」の話を少ししてみたいと思います。 次世代管理画面とは? ネットショップ作成サービス「BASE」は、現在「次世代管理画面プロジェクト」として大規模なリニューアルの真っ最中です。 プロジェクトが最初にはじまったのは今年の1月で、今は最初の大きな区切りのリリースに向けて開発が佳境を迎えているところです。 キックオフから早1年経とうとしているこのプロジェクトについて、やってきたことを少し振り返ってみたいと思います。 プロジェクトの方向性とコンセプトの定義 そもそもなぜ今リニューアルか?というのは以前 「BASE U」の記事 になっているので詳細はそちらをご覧いただきたいのですが、いくつかキーワードをピックアップして紹介します。 これまでの5年と、これからの5年 「BASE」はサービス開始から昨年11月で5周年を迎えました。5年間、機能をより早くリリースすることが優先されてきた中で、山積してきたUI/UX上の課題。 これからの5年もショップオーナーに使い続けてもらう管理画面とするためには、個々の課題をつぎはぎ的に修正していくのでは限界があり、全体のUXの見直しから入る必要があるということになりました。 「お母さんも使える」から「難しいことを簡単に」 「BASE」は、もともと代表の鶴岡が「ネットショップをやりたい」と言った自分のお母さんのために作ったサービスということもあり、「お母さんも使える」というコンセプトの元、シンプルなUIで誰でも簡単にネットショップが作れるサービスとして開発されてきました。 5年たった今、60万店舗の多種多様なニーズがある中で、「BASE」本来の「シンプル」さを担保しつつ、どのように機能を提供していくか。例えばインスタ連携など、これまでは「お母さんも使えるか」の判断から実装しなかったような機能も、「どうしたら簡単に提供できるか」という切り口で考えていく必要があります。 機能からパートナーへ 次世代管理画面の方向性として、以下のコンセプトを定義しました。 【ものづくりを行う全て人にとって無くてはならないパートナー】 「BASE」の管理画面は、「機能」から「パートナー」へ。 0から1を生み出せるのはオンリーワンな能力。そんな方々がそこだけに集中するために、 その能力でしっかり生きていってもらい豊かな世の中を創造してもらうために。 本質以外を全て巻きとるパートナー。 課題の抽出 最初の課題の整理だけでも、かなり時間を使いました。これまでに上がってきたユーザーからの声やredmineチケットの整理と分類をしつつ、デザイナー、エンジニア、ディレクター、マーケティングなど部署をまたいだチームでワークショップを実施し、中心的に使われる機能についてのカスタマージャーニーマップを作成。 自分としては初めての取り組みで進行上の反省点も多かったですが、チームの中にはガチでショップを運営しているメンバーもいたりして、ショップを運営してみないとわからない目からウロコのUXの問題も多数発覚し、大きな収穫となりました。 まだ全機能でやりきれてないので、またタイミングみて実施したいと思っています。 BASEらしさの定義とUIコンポーネントの作成 デザイン上で大きな課題だったのが、 昨日の北村の記事 にもあったように、「BASE」全体を通して統一されていないボタンやフォームのコンポーネントのデザイン。次世代管理画面に向けて、UIコンポーネントのデザインを再定義することに。 デザインに入る前に、Design Groupで1日社内合宿をし、「BASE」らしさとは?についてディスカッションをしました。そのときに作成したのが、 以前の記事 でも紹介したこの図です。「誠実」が中央にあるのがポイント! ちなみに、この社内合宿当時にはまだなかったのですが、10月に60万店舗を達成したときに「 We are Owners 」というLPをリリースしました。 このWe are Ownersのメッセージ自体は代表の鶴岡の発案ですが、デザインチームで話し合った「BASEらしさ」とスムーズにシンクロした感があり、個人的にはかなり腹落ちしました。 UIコンポーネントはAtomic Designの考え方をベースに。Sketchライブラリとして作成することで、その後のデザイン作成の効率が大幅にアップしました。このあたりの詳細は後日デザイナー小山が記事にしてくれる予定です。 デザインとプロトタイピング デザイン作業自体の効率はかなり上がったとはいえ、抽出された課題をいかにデザインに落とし込むかはやはり骨の折れる作業です。Sketchのプロトタイピング機能を使い、実際に画面遷移や動きをみながらのプロトタイピングを繰り返しながら進めています。 デザインをする中でこのコンポーネント必要だね、ということが出てくる場合も多々あり。コンポーネント化するかどうかの線引きは、現状「3回以上それが出現するかどうか」を基準としています。 フロントエンドチームの誕生、開発のスタート これまで「BASE」のフロントエンドは、デザイナーがjQueryを頑張って書いて担保するという状態でしたが、同じような重複コードの頻発、CSSのクラス・id名との闇依存、jQuery複数バージョンの共存など、サービス開始から5年分の負債が地獄絵図の様相を呈していました。この辺の負債をどうするかというのも次世代の課題の一つでした。 今回のリニューアルにあたってはフロントエンドチームが新たに発足し、フロントエンドエンジニアのメンバーが中心となって過去の負債の返済と、Vue.js + TypeSrcript の新しいアーキテクチャの構築を同時に進めてくれています。 フロントエンドだけでなく、バックエンド側もAPI化などかなりの大掛かりな開発作業が進行中です。 新しいUIコンポーネントは、Vue.jsのコンポーネントとして実装。スタイルガイドにはStorybookを導入しています。 自分もこれまでは「 漢は黙ってjQuery 」!的なデザイナーでしたが、今回ようやくVue.jsに着手しました。 jQuery脳からの脱却にしばらく時間がかかってしまいましたが、ようやく慣れてきたと思える今日この頃です。 プロジェクトのこれから 今後5年に向けて、デザインだけでなくフロントエンド・バックエンドも含めたフルリニューアルとなっており、この規模のリニューアルに関わるのは自分も初めてで、この1年を振り返ると試行錯誤の連続であっという間でした。 リリースは段階的にしていきますが、「BASE」の管理画面全体でいうとまだまだ入り口です。 一つめの大きな機能の実装である程度パターンが見えてきたところで、来年以降はデザイン・開発ともに関わるメンバーも増やして複数機能を同時に進められるようにして開発効率を上げていければと考えています。 デザインチームとしても、今後はページの表示を少し直すだけでもVue.jsのコンポーネントを触らざるを得なくなるので、その開発まで全員ができる必要はないものの、ある程度Vue.jsに慣れていってもらいたいなと思っています。 ということで、来週はデザイナー向け「Vue bootcamp」がフロントエンド 三佐和教官 !によって実施される予定。 こちらの様子もアドベントカレンダーの記事になる予定なので、お楽しみに☆ 明日はデータベースエンジニアの植木が書きます。こちらもお楽しみに!!
アバター
この記事は、「BASE Advent Calendar 2018」の7日目の記事です。 devblog.thebase.in こんにちは、BASEのデザインチームに所属している北村( @naomi_kun )です。 BASEのデザイン管理ツールに「 Abstract 」を導入してちょうど1年が経ったので、導入する前を振り返りながら、良かったところの所感をまとめたいと思います。 はじめに 突然ですが、デザインデータの管理ってみなさんどうしていますか? 共有するのが大変だったり、バージョン管理のルールを厳格にしておかないとメンテがしづらかったりと、デザインデータの管理って非常に面倒ですよね。 BASEもGoogleDriveやDropboxを用いてデータ共有していたものの、ファイルの置き場が煩雑になったり、新しいデータがどれなのか毎回他のデザイナーに確認しなければならないなど、多くの課題を抱えていました。 デザインデータもGit管理したかった! デザインデータもGit管理したい、と当時同じプロジェクトを進めていたエンジニアさんに相談したところ「 Abstractってツールが良いらしいよ! 」と教えていただいたので、まずはそのプロジェクト内で1ヶ月試験導入してみました。 Abstract導入前の課題と問題点 データの管理が全くされていない😱 誰がどのデータを持っているのかすら分からない😱 複数人でデータを共有できる体制になっていない😱 UIコンポーネントも管理されていなかったので、人によってデザインが違う😱 この他にも、デザインレビューの際に毎回GitHubにissueを建てなければならず、アプリなど短期間で細かな改修を行うプロジェクトではデザイナーの負担が大きかったので、デザイン画面上にレビュー出来るシステムが欲しかったという背景もあります。 導入して1年、現在の状況 Abstractに登録されているプロジェクト一覧 現在はネットショップ作成サービス「BASE」、ショッピングアプリ「BASE」含む全てのプロジェクトをAbstract上で管理しています。 無料アカウントでもコメント機能が使えるので、ディレクターさん、エンジニアさんも含めると既に約40人規模での運用になっています。 Abstract導入後の改善点 データのバージョン管理ができる🤗 複数人で同一データ編集・共有できる体制になった🤗 誰がどのデータを触ったのか分かるようになった🤗 デザインシステムのおかげで統一されたUIでデザインされるようになった🤗 導入してみて変化したところ 実際の開発フロー 新規開発タスクが発生次第、MasterからFeastureブランチを切る ブランチ名は機能ベースの名前にする こまめにコミット Abstract上でデザインレビューする デザインがFixしたら完成形をZeplinへアップする デザイン作業終了後、Masterへマージ diffの確認がとても楽 AbstractでDiffを確認 Gitのように前回のコミットとの差分が分かるので、作業内容の確認がとても楽になりました。 エンジニアさんにお願いするときの共有方法も楽ちんです。 レビューがAbstract上で出来るようになった 今までのデザインレビューはGitHubのissueに画面に直接コメントできるので、 コミュニケーションコストも、作業効率も劇的に改善されました。 実際のレビュー画面 エンジニアさんもコメントしてくれます。 できる! Abstract使っていてもツライことがある Abstractでの管理体制が整ってきているものの、Abstractだけでの運用ではまだまだかゆいところに手が届かない状況です。 複数人のデザイナーが同時に作業するので頻繁にコンクリフトしたり、マージするとアートボードが重なってしまったり…。 先日のAdobeMAX Japanで、XDもバージョン管理機能が出来るようになると発表があったので、今後もウォッチしたいです。 まとめ Abstractを導入して1年が経ちましたが、デザインチーム内での意識改善はもちろん、エンジニアも巻き込んでのAbstract運用フローが根付きつつあると思います。 チーム内の作業効率も格段に上がったので、Abstractを導入していないチームは是非検討してみてはいかがでしょうか。 明日は id:match_1 さんです。お楽しみに! devblog.thebase.in
アバター
これは、「BASE Advent Calendar 2018」の6日目の記事です。 DataStrategyの齋藤( @pigooosuke )が担当します。 devblog.thebase.in はじめに 機械学習エンジニアの人は、分類や回帰などの課題に取り組むにあたって、偉い人や導入先の部門から「その予測どれぐらい外れるの?」「学習モデルの予測に対してどうリスク評価をすればいいの?」と尋ねられることはありませんか? そのような場面で活躍するかもしれないQuantile Regression(分位点回帰)のお話をします。 回帰モデルの評価 カテゴリーを予測するような分類問題では、各クラスでの精度を確認することはできます。しかし、売上や何かしらの値を予測する回帰問題では、そのモデルにおける特定地点での単一の予測値しか出力することが出来ず、その分布を把握することは単純ではありません。 # scikit-learnの線形回帰サンプル >>> import numpy as np >>> from sklearn.linear_model import LinearRegression >>> X = np.array([[ 1 , 1 ], [ 1 , 2 ], [ 2 , 2 ], [ 2 , 3 ]]) # 入力値のセット >>> y = np.dot(X, np.array([ 1 , 2 ])) + 3 # 正解データ >>> reg = LinearRegression().fit(X, y) >>> reg.predict(np.array([[ 3 , 5 ]])) # 入力[3, 5]に対して、予測値は1つ array([ 16. ]) ここで説明にあたって予測の確度という言葉を使うと定義が甘いのでもう少し分解します。回帰予測の範囲を把握する上でよく出てくるのは、この2種類ではないでしょうか。混同しないように事前に再確認しましょう。 信頼区間 Confidence interval ex. 95%信頼区間。こちらは、同じ条件で100回測定したら、計算した回帰曲線がその信頼区間内に収まった測定が95回ありましたという意味になります。 予測区間 Prediction interval ex. 95%予測区間。こちらは、今後観測しうるデータが100回出現したら、予測区間内に収まる観測が95回ありましたという意味になります。 下図は、標本に対して回帰曲線を計算したものです。 回帰曲線は信頼区間に収まり、標本は予測区間に収まります。 基本的には予測区間のほうが信頼区間の外側に位置します。 使い分けとしては、 モデルそのものの精度を測りたいのであれば 信頼区間 モデルにおける特定地点での予測値のばらつきを調べたいのであれば 予測区間 が目安になると思います。 今回お話をするQuantile Regressionは、予測区間を説明するために利用します。 Quantile Regression ~ 分位点回帰 ~ Quantileとは、日本語で四分位のことです。データをソートして区切った場合、それぞれのデータが上位何%に位置するのかを表現するときに使います。 2 quantileは、中央値と一致します。 0 quantile = 0 %ile (percentile: パーセンタイル) 1 quantile = 25 %ile 2 quantile = 50 %ile = median(中央値) 3 quantile = 75 %ile 4 quantile = 100 %ile Quantile Regressionは、線形回帰の損失関数を拡張したもので、通常のように二乗誤差を求めて平均値を最適化するのではなく、予め設定したquantile(percentile) での損失関数を最適化していきます。年収など偏りがある分布を平均値ではなく、中央値で確認したい場合に利用されます。用途としても、単純に各分位点での予測値の開きをみるだけでなく、例えば、年収or家賃の大小によって格差を図る指標がどれぐらい変化するのかを比較することによって変数の理解に繋がることもあり、社会学や経済学の分野で活用される事例が多くあります。 ちなみに、Quantile Regressionの推定には、check functionと呼ばれる常に絶対値を取るインジケーター関数を利用して、 以下の損失関数を最適化していきます。 手短に説明すると、正解値と予測値の差が正のときに 、 負のとき(反転するので正確には正)に が重み付けされた和をLossとしています。 導出を詳しく知りたい人はこちらのリンクが詳しいです。 原著(pdf) QUANTILE REGRESSION : ROGER KOENKER wikipedia, Quantile Regression Statistics/Numerical Methods/Quantile Regression 理論的な部分の解説 リンク先を見ても細かな部分の省略が多かったので、勉強ついでに自分でも導出をやってみました。興味がなければ読み飛ばし推奨です。 を確率変数、 を平均値、 をquantile(percentile)、 を分布の中心として定義した場合、平均は誤差の平方和の最小値で表せるように、中央値は絶対偏差和の最小値で表すことができます。 が 以下のときの を以下のように累積分布関数 を用いて定義します。 これを逆関数で表すとこうなります。 上で中央値を定義しましたが、これを一般化すると、 ] ここでの が最初に出てきた非負となるインジケーター関数です。 このインジケーター関数を一行で表現するとこうなります。 確率変数 のときにおける、確率密度関数を 、累積分布関数を とします。そして、離散値として で、連続値として で表現すると、それぞれ以下の通りになります。 上記に対し、 で微分し、左辺を0と置いたとき、 となり、 と分位点 が等しくなりました。 Pythonで試してみる Python系ライブラリのいくつかでは、このQuantile Regressionの損失関数がサポートされており、サンプルとして少し紹介してみます。 みんな大好きscikit-learn import numpy as np import matplotlib.pyplot as plt from sklearn.ensemble import GradientBoostingRegressor #### sin波にノイズを加えたデータセットでやってみます。 n = 300 # 標本数 noise = 0.2 # noiseの強さ np.random.seed( 1 ) x = np.linspace( 0 , 2 *np.pi, n) y = np.sin(x) + noise * np.random.randn(n) x = x.reshape(- 1 , 1 ) # 95%予測区間を求めるため、上限・下限ともに5%を2で等分した0.025, 0.975の位置で予測する alpha = 0.975 # model 定義 clf = GradientBoostingRegressor(loss= 'quantile' , alpha=alpha, n_estimators= 250 , max_depth= 3 , learning_rate= .1 , min_samples_leaf= 9 , min_samples_split= 9 ) # 上限の予測 clf.fit(x, y) y_upper = clf.predict(x) # alphaを反転して下限の予測 clf.set_params(alpha= 1.0 - alpha) clf.fit(x, y) y_lower = clf.predict(x) # 損失関数を最小2乗法に設定して予測 clf.set_params(loss= 'ls' ) clf.fit(x, y) y_pred = clf.predict(x) # vizualization fig = plt.figure(figsize=( 8 , 4 )) plt.plot(x, y, 'b.' , markersize= 10 , label= "標本" ) plt.plot(x, y_upper, 'k-' ) plt.plot(x, y_lower, 'k-' ) plt.plot(x, y_pred, 'r-' , label= '予測値' ) plt.fill(np.concatenate([x, x[::- 1 ]]), np.concatenate([y_upper, y_lower[::- 1 ]]), alpha= .5 , fc= 'b' , ec= 'None' , label= '95%予測区間' ) plt.legend(loc= 'upper right' ) plt.show() 現在は、アンサンブルの GradientBoostingRegressor でしか、この 損失関数 は設定出来ません。 ちなみに、以前から線形モデルに対してQuantile Regressionを追加しようという動きはあるようです。続報が待たれますね。 https://github.com/scikit-learn/scikit-learn/issues/3148 勾配ブースティングのLightGBM import lightgbm as lgb # 95%予測区間を求めるため、上限・下限ともに5%を2で等分した0.025, 0.975の位置で予測する alpha = 0.975 # model 定義 clf = lgb.LGBMRegressor(objective= 'quantile' , alpha=alpha, n_estimators= 250 , max_depth= 3 , learning_rate= .1 , min_samples_leaf= 9 , min_samples_split= 9 ) # 上限の予測 clf.fit(x, y) y_upper = clf.predict(x) # alphaを反転して下限の予測 clf.set_params(alpha= 1.0 - alpha) clf.fit(x, y) y_lower = clf.predict(x) # 損失関数を最小2乗法に設定して予測 clf.set_params(objective= 'regression' ) clf.fit(x, y) y_pred = clf.predict(x) # vizualization fig = plt.figure(figsize=( 8 , 4 )) plt.plot(x, y, 'b.' , markersize= 10 , label= "標本" ) plt.plot(x, y_upper, 'k-' ) plt.plot(x, y_lower, 'k-' ) plt.plot(x, y_pred, 'r-' , label= '予測値' ) plt.fill(np.concatenate([x, x[::- 1 ]]), np.concatenate([y_upper, y_lower[::- 1 ]]), alpha= .5 , fc= 'b' , ec= 'None' , label= '95%予測区間' ) plt.legend(loc= 'upper right' ) plt.show() 実際、scikit-learnに実装されている損失関数自体も中身は pred = pred.ravel() diff = y - pred alpha = self.alpha mask = y > pred if sample_weight is None : loss = (alpha * diff[mask]. sum () - ( 1.0 - alpha) * diff[~mask]. sum ()) / y.shape[ 0 ] と、非常にシンプルな構造になっています。 ちなみに、LightGBMの実装は以下のようになっていました。 class QuantileMetric : public RegressionMetric<QuantileMetric> { public : explicit QuantileMetric( const Config& config) :RegressionMetric<QuantileMetric>(config) { } inline static double LossOnPoint(label_t label, double score, const Config& config) { double delta = label - score; if (delta < 0 ) { return (config.alpha - 1.0f ) * delta; } else { return config.alpha * delta; } } inline static const char * Name() { return "quantile" ; } }; それぞれのleaf_valueでの正解と予測の差に対して重みを付けているだけなので、根本は同じですね。 ただし、もう一つの勾配ブースティング代表格のXgboostでは標準実装されておらず、自分で損失関数を設定する必要がありそうです。 興味がある人は自作してみると面白いかもしれませんね。 今回は、回帰モデルの出力を説明するという点でQuantile Regressionを紹介しました。 回帰モデルの出力に関して説明を求められたときの一つの手法として覚えておくと良さそうですね。 明日は、 id:lllitchi さんがデザインについて語ってくれます。お楽しみに!
アバター
この記事は、「BASE Advent Calendar 2018」の5日目の記事です。 devblog.thebase.in Backend Engineer の田中( @tenkoma )です。 「BASE」の裏側で動いているアプリケーションは CakePHP 2 を使っています。 そのCakePHP 2にPHP7.3対応のプルリクエストを送ろうとしたけど先を越されてしまった話をします。 CakePHPのPHP7.3対応状況 PHPはPHP7.0以降、大きな機能追加のあるバージョンが年1回リリース *1 されるようになりました。 リリースサイクルは公開されており( PHP: todo:php73 )、毎年9月中までにはRC版が公開されているようです。活発なPHP製OSSプロジェクトなら、ビルドスクリプトにPHPの新しいバージョンを追加して、互換性が確認できているか確かめられるようになっているかもしれません。 CakePHP 3はテストスイートがすべてパスしたブランチを2018年10月2日に master マージしていたようです。 参考: Pull Request #12488 Add php7.3 to build matrix in allow failure mode. ではCakePHP 2の状況は? PHP 7.2でだいたい動作することは自分で確かめていたので、PHP 7.3 RC3が出た頃に状況を確認しました。 すると、PHP 7.3互換性対応のプルリクエストがマージされたことがあるが、全テストケースがパスしているかはわからない状況みたいでした。 PHP 7.3 互換性対応: Pull Request #12487 Fixes: a few issues found when running PHP 7.3 メンテナーがローカルでテスト実行して問題ないことを確認したようです。 PHP 7.3の勉強にもなるし、やってみるか〜ということで作業することにしました。 ほぼ同じ修正をコミットしてたのに…悔 まずは、テストケースが失敗するとしたら、どれが失敗するか把握する必要があるので、PHP 7.3でテストを実行するためのプルリクエストをつくり、マージしてもらいました。 Pull Request #12707 [2.x]PHP7.3 added to the build setting. マージ後のビルドログ を見ると、270 Errorsと表示されましたが、ほぼ同種のエラーがいろいろなテストに発生しているだけで、修正はたいしたことありません。 自分のアカウントにフォークしたリポジトリもTravisの設定を追加していたので、一つ一つ修正してはプッシュしてエラーを確認していったところ、残り1箇所まで減らすことができました。 エラーが残り1箇所に減ったビルドログ: Job #90.13 - tenkoma/cakephp - Travis CI 残る一つは mb_strtoupper() のPolyfillのテストでした。 CakePHP 2には、mbstring拡張が利用できない場合でも mb_strtoupper() , mb_strtolower() が使えるよう、関数が用意されています。(実際のコードは Multibyte クラスで実装されています。) mb_strtoupper() のテストも用意されていますが、Travis CIでmbstring拡張が有効になっており、組み込み関数が呼び出されて、そこでテストが失敗していました。 There was 1 failure: 1) MultibyteTest::testUsingMbStrtoupper Failed asserting that two strings are equal. --- Expected +++ Actual @@ @@ -'ԱԲԳԴԵԶԷԸԹԺԻԼԽԾԿՀՁՂՃՄՅՆՇՈՉՊՋՌՍՎՏՐՑՒՓՔՕՖև' +'ԱԲԳԴԵԶԷԸԹԺԻԼԽԾԿՀՁՂՃՄՅՆՇՈՉՊՋՌՍՎՏՐՑՒՓՔՕՖԵՒ' PHP 7.3 RC版の不具合かとも思いましたが、これは文字ケース変換の機能拡張のようです。 https://t.co/TbDfTo9ycq 仕様っぽいな…7.2.9 と 7.3.0RC5 で挙動が変わる — Koji Tanaka (@tenkoma) 2018年11月11日 PHP 7.3で仕様が変わることはわかりましたが、どう直すか悩みました。いまさら Multibyte クラスの仕様をPHPにあわせて拡張するのもどうかと思いましたし(実装難しそう…)互換性を諦めて、テストを削除してしまえばいいか、とも考えました。 それから何もせず1日経ったところで次のプルリクエストを見つけました。 Pull Request #12718: Fix some PHP 7.3 errors コミットの内容はほぼ一緒で、 mb_strtoupper() のテスト失敗はそのままでしたが、見事に先を越されてしまいました。 うお〜先を越された。ともあれPHP7.3もサポート出来そうですね https://t.co/dA56Rm9zDw — Koji Tanaka (@tenkoma) 2018年11月12日 先にプルリクエストを出して、スレッドで相談しながら決めれば良かったんですよね。 行動指針「Move Fast」にしたがって、早く出せば良かったな、と思いました。 補足: PHP 7.3対応で修正されたCakePHP 2の互換性 実装コードの修正を2点紹介します。 compact() で未定義の変数を配列にしようとするとNotice Errorになる 7.2 まで、 compact() では未定義変数名を含めることができました。(戻り値にキーは無い) $defined = 'defined'; $arr = compact ('defined', 'undefined'); var_dump($arr); // output: // array(1) { // 'defined' = > // string(7) "defined" // } 7.3からは以下のようなNotice Errorになります。 PHP Notice: compact(): Undefined variable: path in /home/travis/build/tenkoma/cakephp/lib/Cake/Utility/Debugger.php on line 255 switch文の break; の代わりに continue; を使うとWarning Errorになる 7.2 まで switch文ではbreak文の代わりにcontinue文を使うことができましたが、switch文の外側のループを対象にした命令のつもりでも、switchへのbreak文になる問題がありました。 foreach($items as $item) { switch($item['type']) { case 'A': continue; // foreachループの先頭に戻らず、switch文を抜けるだけ } } PHP 7.3ではWarning Errorとなります。PHP 8では構文エラーになるそうです。 参考: PHP 7.3 曖昧なcontinue文の禁止 – yohgaki's blog まとめ プルリクエストはある程度できたら出しちゃったほうがいいこともあるよ! 明日は id:pigooosuke です。お楽しみに! 「BASE Advent Calendar」の記事一覧はこちら。 devblog.thebase.in *1 : 毎年12月初旬頃
アバター
これは、「BASE Advent Calendar 2018」4日目の記事です。 devblog.thebase.in BASEでサーバーサイドエンジニアをやっている、東口( @hgsgtk )です。 BASE BANK というBASEの子会社にて 金融事業の立ち上げ を行っています。 以前投稿した、 Goを運用アプリケーションに導入する際のレイヤ構造模索の旅路 | Go Conference 2018 Autumn 発表レポート という記事の中で、レイヤ構造を成長させていくためのユニットテストについて言及させていただいていました。こちらのエントリーにて進めていたコードベースでは、全体で約85%程度のテストカバレッジとなっています。本日は、そんなGoのユニットテストについての内容です。 ユニットテストの知見 現在、Goのユニットテストについての知見は数多く見られ、 テーブル駆動テスト ・ サブテスト などといった基本的なところから、 非公開(unexported)な機能を使ったテスト や GoのAPIのテストにおける共通処理 で紹介されているようなインテグレーションテストの知見など、充実した情報量となっていると思います。 ただ、資料を漁っていて「どのようなテストヘルパーを作っているか」・「テーブル駆動テストをどのように活用しているか」といった、現場のノウハウはまだ探しづらいなと感じています。 そのため、今回の記事では、筆者がAPI開発のために使っていたテストTipsや共通化のためのテストヘルパーについて紹介してみます。 具体的には、「 http.Handlerのテスト 」・「 実行ごとに結果が異なる処理のテスト 」・「 外部ライブラリの活用 」の3点についてそれぞれ紹介していきます。 http.Handlerのテスト http.Handlerのユニットテストでは、 httptest パッケージを活用して、ハンドラーに対してリクエストを送り、レスポンスを検証するといったテストを書きます。その際、期待値定義をどのようにするか分かれるところかと思いますが、筆者は対象ハンドラーに対する リクエスト・レスポンスの期待値をJSONで定義する ようにしています。 実際に次のように、ユーザーのリソースの取得を行うようなHandlerコードをのテスト例を紹介します。 . ├── interfaces │   └── controller │ ├── testdata │   │ └── user_controller │   │ ├── request.json │   │ └── response.golden │   ├── user_controller.go │   └── user_controller_test.go └── testutil   └── handler.go 筆者のプロジェクトでは、現在、テストヘルパー群を testutil というサブパッケージにまとめており、各テストケースからは、 testutil パッケージが提供する関数を使用する構造となっています。 実際に、ユーザーのリソース取得を行うHandlerを例に見てみます。 func TestUserController_Handler(t *testing.T) { // prepare http.Request and http.ResponseWriter w := httptest.NewRecorder() r := httptest.NewRequest( "GET" , "/v1/users" , nil ) // execute the target method c := controller.UserController{} c.Handler(w, r) res := w.Result() defer res.Body.Close() testutil.AssertResponse(t, res, http.StatusOK, "./testdata/user_controller/response.golden" ) } testutil.AssertResponse というResponse Assertion用のテストヘルパーを提供しています。 // AssertResponse assert response header and body. func AssertResponse(t *testing.T, res *http.Response, code int , path string ) { t.Helper() AssertResponseHeader(t, res, code) AssertResponseBodyWithFile(t, res, path) } testutil パッケージで定義した AssertResponse は、レスポンスヘッダー・ボディを検証する機能を持っています。 下記がヘッダー検証関数 AssertResponseHeader の中身です。 // AssertResponseHeader assert response header. func AssertResponseHeader(t *testing.T, res *http.Response, code int ) { t.Helper() // ステータスコードのチェック if code != res.StatusCode { t.Errorf( "expected status code is '%d', \n but actual given code is '%d'" , code, res.StatusCode) } // Content-Typeのチェック if expected := "application/json; charset=utf-8" ; res.Header.Get( "Content-Type" ) != expected { t.Errorf( "unexpected response Content-Type, \n expected: %#v, \n but given #%v" , expected, res.Header.Get( "Content-Type" )) } } ステータスコード・Content-Typeをチェックしています。レスポンス検証の際、おまじないのようにこちらのアサーションは書くことになるので、共通化しておくと新規のhandler実装も楽になるかと思います。 次にBody検証関数 AssertResponseBodyWithFile です。 // AssertResponseBodyWithFile assert response body with test file. func AssertResponseBodyWithFile(t *testing.T, res *http.Response, path string ) { t.Helper() rs := GetStringFromTestFile(t, path) body, err := ioutil.ReadAll(res.Body) if err != nil { t.Fatalf( "unexpected error by ioutil.ReadAll() '%#v'" , err) } var actual bytes.Buffer err = json.Indent(&actual, body, "" , " " ) if err != nil { t.Fatalf( "unexpected error by json.Indent '%#v'" , err) } assert.JSONEq(t, rs, actual.String()) } 筆者のプロジェクトでは、 別定義したJSON文字列との比較 によってレスポンスボディを検証しています。使い方の流れは下記になります。 testdata 以下に期待値となるJSONを .golden 拡張子で配置 テストケースにファイルパスを指定。(今回の場合、 "./testdata/user_controller/response.golden" となります。) AssertResponseBodyWithFile にて対象ファイルと実レスポンスを検証 .golden 拡張子の使用方法については、 Testing with golden files in Go を参考にさせていただきました。テストのOutputの期待値を testdata 内に別ファイルとして定義する際に、標準ライブラリ内でよく使用されている方法のようです。 { " user ": { " id ": 1 } } この際、JSON同士の比較の可視性のために、部分的に stretchr/testify の assert.JSONEq を利用しています。 そして、ファイルから文字列を取得する実装 GetStringFromTestFile では、 ioutil.ReadFile を利用することで実現しています。 // GetStringFromTestFile get string from test file. func GetStringFromTestFile(t *testing.T, path string ) string { t.Helper() bt, err := ioutil.ReadFile(path) if err != nil { t.Fatalf( "unexpected error while opening file '%#v'" , err) } return string (bt) } これまで紹介していたレスポンスボディを別ファイル定義する手法は、リクエストボディが必要なテストケースでも同様に使用しています。リクエストで必要なjsonファイルは、Outputの期待値として定義していないため、 .json 拡張子としています。 func TestUserController_Handler(t *testing.T) { // prepare http.Request and http.ResponseWriter w := httptest.NewRecorder() r := httptest.NewRequest( "POST" , "/v1/users" , strings.NewReader(testutil.GetStringFromTestFile(t, "./testdata/user_controller/request.json" )) // execute the target method c := controller.UserController{} c.Handler(w, r) res := w.Result() defer res.Body.Close() testutil.AssertResponse(t, res, http.StatusOK, "./testdata/user_controller/response.golden" ) } 実行ごとに結果が異なる処理のテスト UUIDや時刻を扱うコードなど実行ごとに結果が異なる処理の場合は、実装自体を「 テスト可能な実装 」にする必要があります。 時刻を扱うテスト 本章冒頭でも述べたとおり、時刻を使う処理がある場合はテストごとに結果が変わってしまいます。そのため、筆者は、サブパッケージとして clock パッケージを作成し、テストケース内で実行時刻を固定できるようにしています。 clock パッケージの中身はこれだけです。 package clock import "time" // Now is current time. // テスト時に差し替え可能にするため、グローバル変数として定義している var Now = time.Now テスト時に差し替え可能とするため、グローバル変数にtime.Nowを設定しています。テスト時には、こちらの変数に任意の time.Time を設定することで時間を固定します。 なお、この実装の方法については、 外部環境への依存をテストする という資料を参考にさせていただきました。そして、時間を期待値に固定するテストヘルパーを提供しています。 package testutil // SetFakeTime set fake time to clock package. func SetFakeTime(t time.Time) { clock.Now = func () time.Time { return t } } テストケース内では、 testutil.SetFakeTime を使用することでテストケース内で任意の時間に設定できるようにしています。 UUID UUIDに関してもテスト実行ごとに値が変わってしまいます。筆者はUUID生成に、 github.com/satori/go.uuid を利用していますが、本ケースでは、go.uuidから使用するメソッドのみを必要としたInterfaceを作成しています。 package uuidgen import ( "github.com/satori/go.uuid" ) // UUIDGenerator is interface to generate uuid. type UUIDGenerator interface { V4() string } // UUID has generating method. type UUID struct { } // V4 wrap satori.uuid.NewV4() func (*UUID) V4() string { return uuid.NewV4().String() } そして、UUID生成する実装では、 UUIDGenerator interfaceを受け入れる型として定義することでモック差し替え可能にしています。 例えば、次のように引数に UUIDGenerator interfaceを指定した場合は、 func GetSampleUUID(g uuidgen.UUIDGenerator) { return g.V4() } 次のようにモック実装を作成して差し替えます。 type mockUUID struct {} func (*mockUUID) V4() string { return "sample-uuid-string" } func TestGetSampleUUID(t *testing.T) { actual := GetSampleUUID(&mockUUID{}) if diff := cmp.Diff( "sample-uuid-string" , actual); diff != "" { t.Errorf( "differs: (-want +got) \n %s" , diff) } } 外部ライブラリの活用 Goのユニットテストを書いていくにあたり、標準の testing パッケージでどれだけやるのか、テストフレームワークを利用するのかは序盤で検討するかと思います。筆者は、基本的に標準ライブラリを使用していますが、補助的に次のライブラリを利用しました。それぞれ使用感も含めて紹介いたします。 google/go-cmp github.com/google/go-cmp はGoogle非公式の値比較のライブラリです。構造体などの大きめな値比較をする際に使っている方が多いかと思います。 実際の使い方は 公式のexample が参考になりますが、例えば、構造体のアサーションをする場合には、次のように書くことができます。 func TestFuncHoge(t *testing.T) { expected := Hoge{ Moji: "hogehoge" , AnotherStruct: Huga{ Moji: "hugahuga" , }, Num: 1 , Flag: false , } res := FuncHoge() if diff := cmp.Diff(res, expected); diff != "" { t.Errorf( "Hogefunc differs: (-got +want) \n %s" , diff) } } テスト結果が FAIL の場合は次のような表示になります。 === RUN TestFuncHoge --- FAIL: TestFuncHoge (0.00s) main.go:56: Hogefunc differs: (-got +want) {Hoge}.AnotherStruct.Moji: -: "hugahuga" +: "hugahuga_diff" FAIL 構造体をそのまま比較できるので便利です。特に使って不便になることはないので、迷っていらっしゃる方がいれば使っていくのが良いかと思います。 DATA-DOG/go-sqlmock.v1 gopkg.in/DATA-DOG/go-sqlmock.v1 は、DBを使うコードをテストする際にsql.Driverをモックするものです。 実際にSQLを実行してデータを取得するような技術的実装を行う関数のテストにおいて、こちらのライブラリを使っています。 go-sqlmockは、各人が愚直に使うと少々辛いところがあったので、こちらのライブラリを不便少なく使うためにヘルパーを作成しています。 実際には、SQL自体をGoファイル内で定義していくのは見通しが悪く感じたので、次のようにSQLファイルを testdata ディレクトリに配置してテストで利用しています。 下記より、user情報を保存するコードに対するテストコードを例に紹介します。 . ├── infrastructure │   └── datastore │ ├── testdata │   │ └── user │   │ └── save.sql │   ├── user.go │   └── user_test.go └── testutil ├── error.go   └── sqlmock.go まず、テストヘルパーを利用したテストコードは次のようになります。 package datastore_test func TestUserStore_Save(t *testing.T) { // create database mock db, mock := testutil.NewMockSQLDB(t) // Helper 1 defer db.Close() // set expectation of mock query := testutil.GetSQLFromFile(t, "./testdata/user/save.sql" ) // Helper 2 mock.ExpectPrepare(query). ExpectExec(). WithArgs( 1 , 1 , testutil.GetTestTime(t), testutil.GetTestTime(t)). WillReturnResult( 1 ). WillReturnError( nil ) // execute the target method s := datastore.UserStore{} result, err := s.Save(db, 1 , 1 ) // assertion testutil.AssertMockExpectation(t, mock) // Helper 3 testutil.AssertError(t, err, tt.expectedErr) if tt.expected != result { t.Errorf( "expected: %#v, \n given: %#v" , tt.expected, result) } } 上記のテストコードに対して、関連するもので3つHelperを提供しています。 package testutil // Helper 1 // NewMockSQLDB create a new instance sql.DB for test mocking func NewMockSQLDB(t *testing.T) (*sql.DB, sqlmock.Sqlmock) { t.Helper() db, mock, err := sqlmock.New() if err != nil { t.Fatalf( "failed to create sqlmock '%#v'" , err) } return db, mock } 1つ目が、sqlmockのinstanceを生成するヘルパーです。ライブラリ系では、どうやって生成するか毎回思い出すのも面倒なのと、テスト用インスタンスの生成のエラーハンドリングにてテストコードが冗長になりがちなため、テストヘルパーとして提供しました。 次に、期待値となるSQL文を準備するためのヘルパーです。 package testutil // GetSQLFromFile get sql string which is quoted meta to use in sql-mock. func GetSQLFromFile(t *testing.T, path string ) string { t.Helper() mq := GetStringFromTestFile(t, path) return regexp.QuoteMeta(mq) } こちらでは、別ファイルのパスを受け取り、string型の文字列を返すヘルパーを作成しています。 sqlmockは、テスト時のSQLを正規表現で期待値との比較するのですが、「正規表現での比較」という点をテストコード実装者に意識してほしくないので、このようなヘルパーを用意しています。 テストコード実装者は、次のように testdata ディレクトリ以下に期待値となるSQLをSQLファイルとして設置すればOKです。 INSERT INTO users (last_name,first_name,created,modified) VALUES (?,?,?,?) 最後に、sqlmockで設定した期待値と一致しているかを確認するテストヘルパーを提供しています。 // AssertMockExpectation assert mock expectation func AssertMockExpectation(t *testing.T, mock sqlmock.Sqlmock) { if err := mock.ExpectationsWereMet(); err != nil { t.Fatalf( "there were unfulfilled expectations: %s" , err) } } これは、sqlmockの sqlmock.Sqlmock の持つ関数をwrapしています。ライブラリ系の特殊なAssertionも、書き方を忘れがちなのと、設定するエラーメッセージは共通ですので、テストヘルパーとして仕様をWrapすると、各人でばらつきも少なくなるかと思います。 sqlmockの使用感ですが、昨今実データベースでテストするべきという言説が増えている通り、sqlmockで保護できるのが「期待したSQLが発行されているか」のみとなり、「そのSQLが正しいかどうか」についてはテスト保護対象外となる点が難点です。 筆者は、「SQLの妥当性」をチェックしやすいように、SQLのみ別ファイル定義する方法で、ミスを見逃すリスクの軽減に努めましたが、時間が許すのであればFixture機構を用意して実データベースでのテストを行うのがより安全かと思います。 実データベースを用いたテストについては、 Golang API Testing the HARD way という資料にて紹介していただいています。 golang/mock github.com/golang/mock は、テスト時のモックを生成する選択肢として使われることが多いかと思います。 基本的な使い方として、下記のようなコマンドを実行することでモックを自動生成してくれます。 mockgen -source hoge.go -destination mock_hoge.go 自動生成したモックは、本ライブラリが提供するモックコントローラによって期待値を設定することができます。 ctrl := gomock.NewController(t) mockHoge := mock_hoge.NewMockHoge(ctrl) mockHoge.EXPECT(). Fuga( "hoge" , "huga" ). Times( 1 ). Return( "hogehuga" , nil ) 筆者は、テーブル駆動テストでgomockを利用しているのですが、それぞれのテストケースで設定したい期待値が異なるため、期待値設定用の構造体を定義して動的に期待値を変えています。 type mockExpectedHogeFuga struct { callTime int arg string result string err error } func TestHoge_Fuga(t *testing.T) { tests := [] struct { name string mockExpected mockExpectedHogeFuga expected bool }{ { name: "success_to_print_hogehuga" , mockExpected: mockExpectedHogeFuga{ callTime: 1 , arg: "hoge" , result: "hogefuga" , err: nil , }, expected: true , }, } for _, tt := range tests { t.Run(tt.name, func (t *testing.T) { ctrl := gomock.NewController(t) mockHoge := mock_service.NewMockHoge(ctrl) mockHoge.EXPECT(). Fuga(tt.mockExpected.arg). Times(tt.mockExpected.callTime). Return(tt.mockExpected.result, tt.mockExpected.err) // テスト対象関数へモック注入し実行... }) } } テストケースごとに期待値を変えることによって、テーブル駆動テストでも使いやすいようにしています。この手法を使うことでモックをテストケース内で柔軟に使うことができるため重宝しています。 gomock自体の使用感ですが、モック量が多くなってきたタイミングでは、gomockに任せてしまえばよいので、テスト効率は上がりました。ただし、Interfaceに対するモック実装に慣れていない状態では使わないほうが良いかと筆者は思います。モック実装を自前で作れる状態になった上で 効率化のためのモック自動生成手段 として導入するプロセスが、Goらしいコードを学ぶ上で有益だったと実感しています。 まとめ 今回は、Goのユニットテストの実践しているTipsについて書きました。テストヘルパーは、ライブラリ使用状況・コード構成によって、提供の仕方はそれぞれ微妙に異なるかもしれませんが、一つアイデアとして参考になれば幸いです。 明日は、 id:tenkoma です!お楽しみに!
アバター
この記事は、「BASE Advent Calendar 2018」の3日目の記事です。 devblog.thebase.in 前の日 は id:yaakaito でした。Suggested change、便利ですよね。プルリクエストのレビューであまり重要ではないけれど気になる些細なコメントをするかどうか迷うというときに、悩まずにコメント代わりにパッチをサクッと送れるので、レビューに関わる人たちみんなが幸せになれるすごく良い機能だと思いました。ガンガン使っていきたいです! 3日目の今日は、エンジニアリングマネージャを担当している加賀谷が、「新任エンジニアリングマネージャ(私自身)を支えてくれた本と言葉」と題して、いくつか紹介していきたいと思います。 駆け出しマネジャーの成長論 - 7つの挑戦課題を「科学」する https://www.amazon.co.jp/dp/4121504933/ 駆け出しマネジャーの成長論 - 7つの挑戦課題を「科学」する (中公新書ラクレ) 作者: 中原 淳 発売日: 2014/05/09 メディア: 新書 私は2018年、エンジニアのグループマネージャとして活動してきました。それまではサーバサイドエンジニアとして、アプリのバックエンドを作ってきました。なので、まずはマネージャとは何をすればいいのか?というところからのスタートでした。そんなときに救われたのが人事をしている同僚の言葉と、その時にオススメされたこの本でした。 「例えるなら、サッカー選手から管理栄養士へのジョブチェンジです。加賀谷さんは今までサッカー選手としてメンバーとともに試合に出て、時に自分で点を取り試合に勝ってきました。これからは管理栄養士としてチームを勝たせて下さい。」 選手ではないことはまだ受け止められたのですが、チームの管理栄養士です。まだ、監督だったら戦術をどうしようとか、直接ボールを蹴らなくてもまだ試合を動かす術があります。それもできません。試合(開発現場)からかなり遠いイメージを持ちました。 結果的に「サッカー選手から管理栄養士へのジョブチェンジ」という比喩が、すごくしっくりきました。本の中で語られていた、マネージャの仕事 = Getting things done through others(他者を通じて何かを成し遂げる仕組みをつくること)とリンクしたからです。 手を動かさなくていいのか。自分でコードを書いた方がはやいのでは。その葛藤、分かるよ。というところから始まり、マネジメントとはなんぞや、全部並べるとこういう仕事がある、ただいきなり全部やらなくていいんだぞ(むしろやれないし、今まで周りにそんなマネージャいなかったでしょ)、これからマネージャになる君とその周りにはこういうことが起きるぞ、だからこうやって乗り越えていこうー。 少しチクっとするけどこれから現れる症状を和らげてくれる、ワクチン接種のような優しい本です。 エンジニアが「明日からマネジメントして」と言われたら – FiNC Engineering Blog https://medium.com/finc-engineering/12908151a41e BASEのプロダクトマネージャーは思想を持って戦略を立て、何を作り何を作らないのかをずっと意志決定し続けています。ディレクターはプロジェクトチームが最速で目的達成するために、やりたいことを具体的な戦術として言語化とタスク化し、チームの今とこれからの課題を明確にして優先度を決め進捗を管理しています。 そんな中、具体的な数値目標をOKRとして設定して日々追っていく事業系グループのマネージャと、私が担当しているエンジニアのグループのマネージャでは少し違うような気がしていました。グループ単体で達成できるようなOKRの設定が難しく、具体的に何をマネージメントするのかがまだピンとはきていませんでした。 Getting things done through others、だとしたらエンジニアのグループのマネージャとして何をすればいいのか。迷っていたときに、こちらの記事と図を見てすごくしっくりきました。 私がやるべきことは、この図の中のピープルマネジメントでした。各メンバーが活躍できる場所を整え、フロー状態に持って行くのです。そのためにまず、プロダクトマネージャーと密にコミュニケーションを取り、立ち上げるべきプロジェクトの検討とアサインを考える必要がありました。そして、我々が向かう目的地と次の電柱の場所が見えるように目標を咀嚼して伝え、メンバーにかける期待を上司や役員とすりあわせた上で伝えること、プロジェクト内のメンバーが健全な対話と共同作業ができているか見続けて、お互いの期待のズレがないかすりあわせることが仕事のうちの1つと考えました。 OKR(オーケーアール) シリコンバレー式で大胆な目標を達成する方法 https://www.amazon.co.jp/dp/4822255646 OKR(オーケーアール) シリコンバレー式で大胆な目標を達成する方法 作者: クリスティーナ・ウォドキー 発売日: 2018/03/15 メディア: 単行本 BASEでは目標設定のフレームワークとしてOKRを導入しています。各マネージャが全社OKRを因数分解するようにOKRを設定します。OKRとはなんなのか、良いOKRとはを知るためにはこの本が読みやすくて良かったです。茶葉を販売する架空のスタートアップに様々な困難が起き、エンジェル投資家のすすめでOKRを導入したがうまくいかず、そこからだんだんと変わっていくストーリーにも親近感が持てました。 Team Geek ―Googleのギークたちはいかにしてチームを作るのか https://www.amazon.co.jp/dp/4873116309 Team Geek ―Googleのギークたちはいかにしてチームを作るのか 作者: Brian W. Fitzpatrick , Ben Collins-Sussman 発売日: 2013/07/20 メディア: 単行本(ソフトカバー) 優れたソフトウェアはチームで作る、だとしたら優れたチームはどうすれば作れるのか。ソフトウェア開発はチームスポーツであり、技術と同じように人を考えることが大事。謙虚(Humility)、尊敬(Respect)、信頼(Trust)の3本柱を身につけよう、という本です。マネージャになって読み返したのですが、自分がいちエンジニアの時に読んだのとは見え方が全く異なっていました。1章に書いてあるうちの、優れたプロダクトを作る上で必要なプロセスの一部である、建設的な批判の仕方と受け入れ方、失敗からの学び方、集団のエゴを考える大切さについては特にです。 採用基準 https://www.amazon.co.jp/dp/B00B42SX70/ 採用基準 作者: 伊賀 泰代 発売日: 2012/11/09 メディア: 単行本(ソフトカバー) リーダーとマネージャの境が分からなくなってきたときに気づきを与えてくれました。 リーダーは引っ張る人でも何でもなく、チームの使命を達成するために必要なことをやる人のことで、そのリーダーが持つ問題解決リーダーシップは、メンバー全員が持つべき。その方が高い成果を生み出せるチームになる。なぜなら、お互いに背中を任せた上で何をすべきか自発的に動けるからです。 Team Geekにも書いてありましたが、時にマネージャはリーダーであることも期待されますが、リーダーは必ずしもマネージャである必要はないです。マネージャはメンバーのリーダーシップを育んでいくことで、よいチームになりプロダクトにより大きく寄与できるのではないかと思います。 エラスティックリーダーシップ ―自己組織化チームの育て方 https://www.amazon.co.jp/dp/4873118026 エラスティックリーダーシップ ―自己組織化チームの育て方 作者: Roy Osherove 発売日: 2017/05/13 メディア: 単行本(ソフトカバー) 同時に複数のエンジニアグループのマネージャをさせていただいたのですが、時にグループごとに課題がちがい、同じ振る舞いではうまくいかないことを学びました。この本ではチームの状態を、サバイバルフェーズ、学習フェーズ、自己組織化フェーズ3つで定義し、それぞれのフェーズでとるべきリーダーシップの方法を紹介しています。 ヤフーの1on1―――部下を成長させるコミュニケーションの技法 https://www.amazon.co.jp/dp/4478069786 ヤフーの1on1―――部下を成長させるコミュニケーションの技法 作者: 本間 浩輔 発売日: 2017/03/25 メディア: 単行本(ソフトカバー) メンバーと定期的に1on1をすることになり、1on1ってなんぞ?と読み始めた本です。マネージャになって最もお世話になった本かもしれません。 そもそも何を目的としてどうやったらいいかを良く理解していなかったので、この本のまんま通りにやってみて場数をこなすところから始めました。90度に近い配置で座り「最近話したいこと話せてますか」と話をしてみるところからでした。 最初は全くうまくいきませんでした。何を話していいのか分からず、場は沈黙ばかりでしたし(最初の頃、その沈黙が経験学習を促すために必要と勘違いもしました)、メンバーには1on1の必要性すら届きませんでした。 1on1は1回あたり30分、頻度は多いときは1週間に1度行っています。話している内容をMarkdownでメモでとりながら話し、最後に必ずマネージャにできることがないかリクエストの確認をします。1on1の最後にはメモをSlackのDMで本人だけに送り、気づきの確認と、次のアクションの確認をします。もらったリクエストはその日のうちに行動して、どうなったかを本人に連絡しています。 1on1のメモは MacのメモツールのDayOne でとっていたのですが、そのメモを数えたら311回分ありました。1on1をしていく上で大切だと思った準備や質問がいくつかできてきたので、それはまた別の機会に紹介したいと思います。 Getting things done through others、自分が直接携わらないサッカーの試合を勝利に導く上で、1on1はマネージャの強力なツールの1つだと思います。いいプロダクトを作りショップオーナーの支援につながる事が上のサッカーの試合の勝利だとすると、そのためにはチームメンバーがそれぞれ活躍していて、高く評価されていることが必要です。 エンジニアがやりたいことやできることの円と、プロダクトづくりでやりたいことの円があったとき、両方の円が重なったところが組織と個人の課題や目的が一致したところで、メンバーが活躍できることと考えることができます。なので、メンバーが活躍するためには、この両方の円を近づけていくように整えていく事がエンジニアリングマネージャの仕事の1つかなと、今は思います。また、両方の円を大きくすることでも重なる面が増えるので、こちらも仕事の1つと考えています。 これがエンジニアリングマネージャとしてのレバレッジの効かせ方のひとつ、自身のアウトプットの与える影響範囲を広範囲に及ばせる、ということなのではないでしょうか。 全然別の話ですが、日々1on1をしていくなかで金八先生やルーキーズの川藤先生を思い出しました。赴任してきていきなりみんなの前でああしようこうしようと理想を語ったところで、次の日皆がそれぞれの居場所を見つけて充実した顔をしている卒業式のようにはならないわけです。ドラマで1話ごとに1人と向き合い、強みと課題を見つけて成果を出せるように行動変容を促すようにサポートし続ける姿勢はマネージャとして見習わなければならないと思いました。 今年1年、気付けば1on1を通じてたくさんの経験学習をさせてもらっていたのは自分のほうでした。メンバーには感謝の気持ちでいっぱいです。ありがとうございました。 明日は id:khigashigashi です。お楽しみに! 「BASE Advent Calendar」の記事一覧はこちら。 devblog.thebase.in
アバター
これは「BASE Advent Calendar 2018」の2日目の記事です。 devblog.thebase.in Native Application Group の右京です。 ネットショップ作成サービス「BASE」では日々機能追加や改善の為、無数の Pull Request が作成され、レビューされています。今日はそんな中で知っているととっても便利な機能を紹介します。 レビューをしていたら typo をみつけてしまった... ありませんか?前後に真面目なレビューがあるとなんとなく指摘するのを躊躇してしまいますね...。シレッと修正するにも編集するなりブランチを切るなりして...はコンフリクトの可能性などもあり、コストが高く面倒ですよね。 そんなときはこれ、「Suggested change」です。 Incorporating feedback in your pull request - User Documentation これは最近 GitHub に追加された機能(public beta)なのですが、僕はこれを知ってからというものおすすめの機能の一つになりました。では、実際にこれを使って見つけてしまった typo をそっと修正してみます。 修正を送る側はとっても簡単 レビューでコメントをつけるときと同じ要領で行を選択して、ここクリックすると... 変更点に対してこのようなコメントが挿入されます ```suggestion function niec() { ``` この状態ではまだ typo したままなので、正しい内容に修正してプレビューしてみると... このように「Suggested Change」が作られます。そしてこれをコメントかレビューとして送信すると... こうなります!これで、この変更をパッチとして送っている状態になりました、取り込まれるのを待ちましょう。 取り込む側もすごく簡単 パッチをもらった側は、変更が妥当ならそれを取り込むことができます。「Commit Suggestion」から「Commit changes」で変更をコミットできます。 変更が取り込まれて、コミットが作られました。今回は自作自演したので僕だけがコミットしたことになっていますが、実際はこんな風に提案した側、取り込んだ側の両方がコミットに記録されます。 お互いに手間も無駄な気遣いも少なく、コードを少し良くすることができました。小さなことからコツコツと! Move Fast 今回は typo を例に出しましたが、文章の推敲、設定ファイルに対する小さな修正など、BASE のコードレビューシーンではすでにいろんなところで「Suggested changes」が活用されています。「提案を即座に取り込んでいける」という面が本当便利で、行動指針である「Move Fast」を体現した小回りの効く機能だと感じています。パッチをもらった側は uniposで #MoveFast すれば完璧 ですね! 以上、「Suggested changes」の紹介でした、この機能であなたも BASE にパッチを送りませんか? 明日は id:yuhei_kagaya です!楽しみですね! 「BASE Advent Calendar」の記事一覧はこちら。 devblog.thebase.in
アバター
これは BASE アドベントカレンダー 1日目の記事です。 devblog.thebase.in CTOの藤川です。ネットではえふしんと名乗っていて、会社でもえふしんさんと呼ぶ人が大多数です。 今年はテックリードの働きかけをきっかけとして、BASE社でもアドベントカレンダーを書いてみよう!という話になりました。皆様よろしくお願いいたします。 最近、エンジニアリングマネージャという役割が急速に普及し始めているので、今回はマネジメントの話について書いてみたいと思います。 IT業界にはエンジニア35歳定年説という都市伝説がまかり通っています。開発技術が進化してコードを書く労力が劇的に下がったにも関わらず、このことに恐怖してる人は少なくないようですし、何よりエンジニアがコードを書かなくなるような状態は望ましい状態ではないとされています。 一方で、成長するビジネスにおいては組織を作っていかないといけないのですが、エンジニアは、エンジニア出身のリーダーに評価してもらいたいという部分と、コードを書かないとエンジニアとして死ぬというイメージに矛盾があって、35歳定年説の不安と相まって、エンジニアのマネジメントのキャリアパスというのはどこか淀んでいる印象すらあります。 要は、マネージャの存在を絶対的に必要としているのに、自分がやるのは不安だという矛盾をはらんでいます。 これまでの転職志望者でも、現職で管理職になってしまったが、まだ技術の最先端に携わりたいと言う理由で面接にお越しいただく方もいて、転職後にマッチングに失敗するかもしれないリスクを取ってでも、転職せざるを得ない状況に追い込まれるというのは、エンジニアである僕らのキャリアにとっては辛い状況とも言えなくもないです。 この状況をポジティブに解決するのがエンジニアリングマネージャという役割だなと思い始めています。 エンジニアリングマネージャとは? エンジニアリングマネージャとは、ピープルマネジメントの一形態です。エンジニアリングマネージャの仕事を一言で言うと、 事業、プロダクトに貢献しながら、チームのエンジニアの活躍にコミットすることで、メンバーの評価を上げる仕事 だと考えています。チームのメンバーに活躍してもらって、高い評価を得られることが、マネージャ自身も評価され、それができなかったらエンジニアリングマネージャとしては評価が下がる仕事として考えられます。 チームを持ってる以上、事業の推進やプロダクトの開発責任も担うわけですが、自分がコードを書いて問題を解決してもエンジニアリングマネージャとしては評価されないわけです。 会社によっては、そもそもエンジニアリングマネージャは組織図上の上長ではない立ち位置であったりもするようです。メンターみたいな立ち位置でしょうか。 当社においては、組織図上の上司にあたるピープルマネージャがその任を担います。いずれにせよ、ネット系企業におけるピープルマネージャの役割は、サーバント・リーダーシップにのっとりメンバーを下支えすることで、メンバーの活躍をメイクできなければ、すぐにでも他社に転職されてしまう昨今の状況において、チームを支える、メンバーの活躍を導くという役割を担っていることを自覚しているかどうかこそが重要です。 メンバーの給料は成果に応じて昇給する会社がほとんどだと思います。その前提において、エンジニアリングマネージャはメンバーの状況を常にトラッキングし、1on1などを通じてメンタリングしながら、活躍をメイクし、どういう成果が評価可能なのか?を明確にし、今後はどういう期待をかけていくべきか?を共有し、メンバー自身がそれを実現していきます。 昇給というものは、結果そのものに支払われるのではなく、結果によってもたらされる次なる期待に対し人的投資として支払われるものですので、そのお膳立てをする役割だと考えます。評価権限を持っていることで、メンバーと踏み込んだ話をすることができるし、それだけの責任を負うことになります。 エンジニアリングマネージャは常に挙動をメンバーから評価されている エンジニアリングマネージャがメンバーの評価権限を持つ以上は、メンバーからすると、評価する人に値するか否かというのは常に評価されることになります。 その基準は、 評価する人としての人間性は期待できるか 自分のスキルを判断できる技術力、技術判断力を持っているか 日常の活躍をしっかり観察してくれている人か しっかり自分を高めてくれる指導をしてくれるか など、緩すぎても厳しすぎても成立しませんし、日常の言葉一つ一つにおいて常にチェックされていると言っても過言ではありません。 コードを書くような知的労働においては、ポジティブに脳内物質の分泌が行われるか否かが生産性の大多数を支配します。目的意識を持っていないとか、楽しくないという状況で良いアウトプットができるわけはありません。それが一文字直すだけの簡単な仕事でも、アーキテクチャから作り変えるような難しい仕事でも、やる意義を引っ張ったり、勇気を振り絞って困難に立ち向かうように問題をほぐしていくのはエンジニアリングマネージャの重要な仕事です。 このマネージャの下にいると自分は評価されないと思った時に取りうる行動は、他部署に異動するか、会社を辞めるかのどちらかであることが多く、マネージャと戦い上司を教育してまで状況をよくしようとしてくれる人は稀であることを考えると、マネージャは常に緊張感を持ってメンバーから信頼に値する評価を得られているのか?について考えていなければいけません。 よく言われる、マネージャになるとコードを書く書かない問題は、チームメンバーの成長にコミットする役割において、メンバーや会社から自分が評価されるために、目の前のコードを自分が書くことのトレードオフを考えて、状況状況でマネージャが最適な手立てを判断してもらえば良いと思います。どうしても危機的状況や問題がある時、または背中を見せる時に率先してコードを書くシーンもあるでしょうから、采配として手段を使い分けるのはエンジニアのリーダーだからこそできることです。 また、技術はどんどん変わってもいくので、メンバーから評価されるためのキャッチアップが必要と考えると、むしろメンバーのプルリクエストを通じて最新技術やエンジニアリングを学ぶ姿勢が必要であるとも考えます。そのためにもプロダクトや事業と連動した技術マネジメントを行う必要も出てくるハズで、基本的にエンジニアとして前向きな役割でしかないと僕は考えますが、皆さんはいかがでしょうか? エンジニアリングマネージャは「偉い」立場ではない ここまで書けば、エンジニアリングマネージャは、シニアなエンジニアみたいな役割に近く、旧来型の組織である「上司・部下」「偉い人」というイメージではないことに気がついてもらえるのではないでしょうか? むしろプロジェクトマネージャやディレクターに近い役割で、とても現場感が求められる仕事で、エンジニアリングにより踏み込んだ形でエンジニアの成長にコミットする役割であると考えます。 この役割になれるスキルはエンジニアとしてのスキルや事業に対する姿勢が信頼されていなければ、エンジニアであるメンバーから評価されることはないわけで、エンジニアのキャリアパスの一つであると考えられます。 そう書くと、この役割にはエンジニアのスキルがものすごく必要なのでは?と思われがちですが、ミニCTOとして技術で引っ張るテックリードとは違います。しかし、テックリードは卓越した技術力でチームへの影響を与え、如何に高いアウトプットを出すか?と言う部分にコミットする役割であると考えると、人を導くという点においてはエンジニアリングマネージャと向いているベクトルには大差はなく、何を「とくいわざ」とするかだけの話で、それぞれがチームで働くエンジニアに求められる職能として考えることが可能です。 そして、ここからが大事なことですが、必ずしもエンジニアリングマネージャは永遠にその立場でいる必要もなく、一定期間で別の人に入れ替わっても良いと考えています。期待するのは大人としてチームの活躍を支えるチームディレクションとしての役割です。ディレクションかプレーヤーかというのは、その都度、役割を入れ替えることはあってもよいと思います。 なによりそうすることで、たくさんのエンジニアが人の成長を支える仕事の難しさを知ることができるし、一度、エンジニアリングマネージャを経験した人は、より広い視野での仕事を期待できるわけなので、改めて現場でコードを書く役割にコミットしながら、開発プロジェクトマネジメントやメンバーの育成においてもエンジニアリングマネジメント力を発揮することが期待できます。 そうこうしてるうちに新しい事業アイディアが出てきた時には、そういう人が率先してエンジニアリングマネージャとしてチームを作れるようになることで組織のスケーラビリティは向上します。 このようにエンジニア組織全体としては、立場を入れ替え可能であることと、マネージャという役割をエンジニアとしてのキャリアの幅や柔軟性を高める役割として定義することで、プログラマ35歳定年説に代表されるような、コードを書かなくなって、エンジニアとしての死を迎えるみたいな不安な世界を終わらせることができるのではないかと考えています。 なお、これは、今自分が携わっているCTOにおいても全く同じです。あくまでも役割分担の一つとして柔軟に人を入れ替え、抜擢可能なチャレンジ文化を作ることは組織の柔軟性とスケーラビリティに影響が出るし、新たな役割への期待を常に作り続けることで、ビジネスが成長することを大前提とした組織運営の考え方として捉えています。 以上、えふしんでした! 明日は id:yaakaito です!お楽しみに! 「BASE Advent Calendar」の記事一覧はこちら。 devblog.thebase.in
アバター
こんにちは、BASEでサーバーサイドエンジニアをやっている、東口( @hgsgtk )です。つい最近、 Goを運用アプリケーションに導入する際のレイヤ構造模索の旅路 | Go Conference 2018 Autumn 発表レポート - BASE開発チームブログ というGoに関するエントリを書きましたが、PHPもしっかり書いてます。 今回は、特に弊社のメインプロダクトであるネットショップ作成サービス「BASE」のフレームワークの大部分を占めるCakePHPの国際カンファレンス CakeFest の協賛についてです。 さて、この度、BASE株式会社は、 CakeFest 2019 に、シルバースポンサー・ランヤードスポンサーとして協賛いたします。 cakefest.org CakeFest 2019 とは、PHP製フレームワークの一つであるCakePHPの国際カンファレンスです。年に一回開催されており、今回は初めて日本での開催です。 冒頭でも述べましたが、弊社はCakePHPをメインプロダクトのフレームワークとして採用し、日々CakePHPをベースとしたプロダクト開発を行っています。そのため、CakePHPコミュニティに貢献したいという想いから、初の日本開催となるCakeFest 2019に協賛することを決定しました。 また、CakeFestについて、日本で開催されると決定される前から、弊社の @tenkoma が、PHPカンファレンス関西2018にてCakeFestを紹介しておりました。 私も日本開催を待望しており、今回協賛という形でCakeFestに参加できることをとても嬉しく思います。 speakerdeck.com CakeFestに参加しましょう! 肝心な開催時期ですが、 @CakeDC の発表では、2019年11月の開催を予定しているとのことです。 We will be sponsoring #CakeFest2019 (November in Japan!) - and we hope that you will join us. Sponsorship opportunities are available! Get details here: https://t.co/TrndH05dtD #cakephp #cakefest #cakedc #php #phpconference pic.twitter.com/3BYksH59j6 — CakeDC (@CakeDC) 2018年9月19日 国際カンファレンスが日本開催される機会はそこまで多くないと思いますので、普段CakePHPを触っていない方もこれを機会に参加してみると新たな発見があるかと思います。 トーク応募始まってます! また、現在トーク応募が開始されています。 Have you submitted your talk for #CakeFest2019 ? Call For Speakers is now open!! If you are keen to be apart of the speaker line up for CakeFest next year, then NOW is the time to apply! CFP's close Jan 7th #php #phpc #Japan #CakePHP https://t.co/ghvbY0hho8 pic.twitter.com/3CPn12Wp2Y — CakePHP (@cakephp) 2018年11月27日 普段足が運びづらい国際カンファレンスが日本で開催されるということで、海外で英語で発表したいといった目標のある方はとても良い機会になるのではないでしょうか!
アバター
こんにちは。BASEで採用広報を担当している米田( @aiyoneda )です。 今年も残り一か月となりました。今年は、BASEとして初のAdvent Calendarを実施する運びとなりました。こちらの「BASE開発チームブログ」にて、12/1(土)から12/25(火)まで毎日記事を公開していく予定です! 記事一覧はこちらです。随時更新していきます。 devblog.thebase.in 初となる「BASE Advent Calendar」では、技術的な内容だけにとどまらず、BASEのプロダクトや開発方針、開発組織の運営に関わる内容を中心にカジュアルな内容も含めて幅広くお届けしようと思っています。 これをきっかけにBASEという会社やメンバーの雰囲気、またBASEの開発部門での取り組みについて知っていただき、興味を持っていただければ幸いです。 記念すべき初日は、取締役CTOの藤川( @fshin2000 )に筆を執ってもらいます!お楽しみに!
アバター
こんにちは、BASEランニング部で10kmマラソンなどに参加し、3kgほど体重が落ちたSRE Groupに所属しているデータベースエンジニアの植木です。おかげで甘いものが美味しいです。ちなみに次はハーフマラソンに挑戦です! 今回は会社のブログなどを書いてみます。弊社では、ネットショップ作成サービス「BASE」およびショッピングアプリ「BASE」を運営していますが、11月にメインDBをRDS for MySQL5.6からAurora(MySQL5.6コンパチブル)に変更しましたので、そちらの話を書かせていただきます。 何故Aurora? まず、弊社でAWSをメインに使っていたという背景があります。入社した際にはRDS for MySQLを使用しており、CTOの藤川も「AWSを使うならAuroraにしたい」という要望を持っていました。私自身、AWSをメインに使い続けるつもりであればAuroraは良い選択だと思っていました。 MySQLを使う場合、オンプレミスやMySQL on EC2の方がスピードや機能面でメリットがありますが一定以上のスキルのエンジニアを複数人確保し続ける必要があり、運用にそれなりの稼働が取られます。RDSを選択して運用負荷を下げる方針であるならば、いっそRDSだからこそ使えるAuroraのメリットを受ける方が良いと考えています。下記2点がOKならAuroraの選択を候補に入れて良いかと思います。 ・AWSにベンダーロックインしても大丈夫か ・MySQLの最新バージョンの機能よりもRDBの基本処理のスループットを重視するか 弊社運用を考えて、特にメリットと思った点 レプリカラグが発生しにくい ストレージの使い方が大きく変わっていますのでレプリケーションラグが発生しにくいです。 クラスターエンドポイントとカスタムエンドポイント HAproxyを使っての複数台レプリカのロードバランスを検討していたため、カスタムエンドポイントが移行直前にリリースされた事は助かりました。リーダーエンドポイントは弊社のように分析用レプリカを作っている場合にはそちらも参照先に入ってしまいます。書き込みはクラスタエンドポイントを指定して、読み込みはサービス用インスタンスだけを指定したカスタムエンドポイントを作成して使用しましょう。 クエリキャッシュ クエリキャッシュの作りがMySQLとは全く違うためかなり有用になっています。 無停止パッチ 稀にAWS側要件のパッチが発生するので助かります。 スループットの向上 MySQLの5倍程度との事。そこまでのスループットはまだ必要ではないですが、後述のログや監視機能を有効にするためにスループットが向上されている事は良いです。 Performance Insights MySQL Performance Schema設定を有効にする必要があるので、スループットに15%程度の影響が出るとの事ですが、専門じゃなくても負荷原因特定などが簡単にできるようになっていてとても便利です。MySQLでも使えるのですがスループットへの影響があるので躊躇っていたものをAurora移行を機に利用開始しました。 監査ログ ECというサービスを考えると監査は必要です。スループットに影響するのですがMySQLの監査設定と比べるとAuroraでは格段に影響が少なくなっているため利用する決断がしやすくなりました。ただし、ユーザー単位での記録くらいしかログ対象の選択設定ができないためCloudWatch Logが増える事は認識しておきましょう。 費用面 一台ずつの料金はMySQLの方が安いですが、AuroraのマルチAZは参照可能になっています。MySQL利用時にはサービス用のマスターレプリカ以外に分析・集計用DBを用意していましたが、AuroraではマルチAZ用インスタンスをそちらに割り振ることができます。そのため4台分必要だった費用が3台で良くなり費用削減ができます。 高速クローン 本番相当のDBをQA用に用意する事を進めているため、そちらで高速クローンが利用できると考えています。 Aurora注意点 続いて、Auroraの注意点です。基本的にMySQL互換ではありますが、少しAuroraで利用できない操作や違いがあります。 テーブルの圧縮機能が無い 明確に圧縮非サポートと書いてあるドキュメントは無かったのですが、非サポートです。移行時のスナップショットからのリストア時に非サポートと書いてあるドキュメントはあります。 圧縮コマンドは通るのですが、実際には圧縮されず気づきにくいのでご注意ください。 Parameter Groupが複数存在する インスタンス用とクラスタ用とに分かれます。Auroraはインスタンスとストレージが分かれているのでクラスタ用=ストレージ用と考えるとわかりやすいです。 Aurora Serverlessはどうなのか? テスト環境としてAurora Serverlessを利用できないか考えましたが、仕組み的にクラスタ用パラメータグループしか存在せずインスタンス用パラメータグループにあるinnodb_file_formatでbarracuda指定ができないのが痛いです。おそらく、アクセス時にdefaultパラメータグループを利用してインスタンスが立ち上がってくるためAWS側でdefaultの設定を変えてくれるか、barracudaがデフォルトになっている5.7互換のAuroraでServerlessを出してくれるかに今後期待です。 Aurora MySQL5.7互換はどうなのか? Aurora最新は5.7互換ですがPerformance Insightsなど新しい機能リリースが5.7にはまだされていません。また、移行パスがスナップショットからのリストアしかないため後述のレプリケーションを利用した移行ができず、移行時間が長くかかります。そのような状況ですので、今回は5.6互換のAuroraが最適だと判断しました。 事前作業 作業は大きく事前作業と当日作業とに分けられます。比率としては事前作業が8、9割で移行当日はそれほど作業がありません。重要なのは事前作業です。 テストに関して テストに関しては、開発環境・Staging環境を事前に移行してそこを使用して開発をやってもらう事で網羅テストとしました。開発環境を移行後に最低限の確認を各開発チームでしてもらい、その後二ヶ月ほど開発環境として使ってもらいました。 また、開発・Staging環境については後述のような移行構成は作らずに空いている時間にデータベースの変更処理でアップグレードするのが簡単で良いです。 本番環境事前構成の作成 オンプレミスでMySQLをある程度扱っていた方ならわかると思いますが、レプリケーションを利用した移行の事前構成です。下記のような構成を作ります。RDS for MySQLでは子レプリカまでしか作成できませんが、Auroraでは孫以下のレプリカ作成が可能になっています。そのためこのような構成を作る事でサービスに利用しているMySQLには影響を与えずに事前にAuroraの構成を作っておく事が可能です。 この際にほんのちょっと工夫するところとして、テンポラリの書き込み可能MySQLレプリカを間に入れる事をお勧めします。サービスに影響しない書き込み可能レプリカを入れることで、「mysql.rds_stop_replication」と「mysql.rds_start_replication」コマンドにてレプリケーションを操作し、レプリケーションを止めている間にテンポラリに対して巨大テーブルへのALTER文等を実施して事前にAuroraのテーブル定義をカスタマイズできます。弊社で言えば大きめのテーブルへのインデックス追加はこのやり方で実施しておきました。row_formatをDynamicに書き換えておきたい場合などはこの方法で実施すれば夜間メンテなどで長い時間サービスを停止する必要なく実施できます。 当日作業 当日の流れ 他社事例を見ると、ノンストップでの移行なども見受けられましたが、ネットショップ作成サービス「BASE」は60万店舗のショップに使っていただいているサービスのため、一時的に参照だけ可能になる事などが許される状況ではありません。そのため各ショップに連絡し、夜間メンテナンスタイムを入れさせてもらいました。ざっと下記のようなスケジュールです。 2:00:メンテイン作業開始 2:30:移行処理およびその他の追加作業開始 4:00:メンテ明け作業開始 4:30:社内限定アクセステスト 5:00:メンテ明け&最終テスト 5:15:終了 当日の移行作業 AuroraのMasterインスタンスを昇格します。その後、CNAME内のエンドポイント情報を全てAurora側に向ければ完了です。また、旧MySQLインスタンスは万一の場合にすぐに戻しができる必要があるため当日の削除はしません。どこからもアクセスできないSecurity Groupを作って入れ替えるかインスタンス名自体を変更してエンドポイント情報が変わるようにしてあげると良いです。弊社では後者を実施しました。インスタンス名変更は短時間でできますし、AWSコンソール上から見て名前が変わっている事が一目でわかるところが良いです。こちらで無事5時過ぎには作業完了しました。 使用感 マスターはCPU利用率が高くなります。CPUを使い切る最近の流れですので、そこはあまり気にせず。レプリカのCPUはそんなに変わりませんが、タイムラグは本当に減りました。レプリケーションの仕組みが以前と違うのでわかっていたとは言え、ここはとても嬉しいところです。マスターインスタンスのCPU利用率が高めになったため、監視システムのワーニング値はレプリカと分けるなど見直しをした方がいいですが、全体に急激なコネクション増加なども減り、良い結果に繋がっております。 下記が移行前後のCPU利用率の変化です。 緑:旧マスター 赤:旧レプリカ 青:新マスター 黄:新レプリカ また、下記がレプリカのラグです。単位がミリセコンドなので、非常に安定しています。 最後に 入社後、二ヶ月ほどでRDS for MySQL5.5からRDS for MySQL5.6への移行を実施し、それから1年経ってAuroraにたどり着きました。ようやく、一つ重めの荷物を下ろす事ができるような気がしています。まあ、そう言いながら来年はAurora MySQL 5.7互換やってたりしそうな気もしますが・・・。データベースとの付き合いはコードとの付き合いより長いと言います。今後もうまく回していきたいと思います。 BASEのSREチームではお客様に安心して買い物をしていただくために、サービスの安定性に責任を持ち、インフラ・アーキテクチャの改善に日々取り組み続けております。 ご興味を持たれた方いらっしゃいましたら是非ご連絡いただければ幸いです。 jobs.binc.jp
アバター
お久しぶりです、BASEでサーバーサイドエンジニアをやっている、東口( @hgsgtk )です。 BASE BANK というBASEの子会社にて 金融事業の立ち上げ を行っています。今回は、BASE BANKで行っているGo言語でのチーム開発について書こうと思います。 なお、このエントリーの内容について、2018年11月25日に開催された Go Conference 2018 Autumn にてLT登壇してきました。 発表資料については次のURLで公開しています。 https://go-talks.appspot.com/github.com/higasgt/gocon-lt/architecture.slide#1 今回、LTをCFPで2つ提出して、偶然2つ採択いただいたので、併せてDocker開発環境の整備についても次のスライドで発表させていただきました。 https://go-talks.appspot.com/github.com/higasgt/gocon-lt/realize.slide#1 Go製のApplicationをDocker上で開発する際に環境整備が喫緊の課題としてある方がいらっしゃればこちらの資料も併せてご覧いただくと参考にしていただけるかもしれません。 社内のGo言語の使用状況 今年の5月23日、「Go言語勉強会を始めたよ」といったエントリーを投稿していました。 devblog.thebase.in その際には、基本的にBASEのプロダクトはほとんどがPHPで構成されており、各開発者が一時的に使用するスクリプトをGoで書くケースがあるよといった使用状況でした。 現在は、新規機能をGo製APIとして実装・運用するプロダクト機能が出てきており、実際に運用開始しているものもあります。今回紹介する事例についても運用開始に向けて進めているものになります。 レイヤ構造模索の旅路 Go言語で運用を前提としたサービスを実装する場合に最初に頭を悩ませる、レイヤ構造実現のためのプロセスについてです。世の中には、Clean Architectureなどレイヤ構造についての解説記事が多数ありますが、結局は自分たちの要件にあったものを実現していく必要に迫られます。それをどう探っていったかという少し泥臭い話をしようかと思います。 なお、注記しておきますが、今回紹介する構成が「本当に正しいLayered Architectureである」などといった概念を正確に実現できているかという点に関しては保証できませんので予めご了承ください。 なぜレイヤ構造が必要だったか 様々必要・不要意見があるかと思いますが、筆者の環境では次のような理由にて必要だと感じておりました。 チームの開発成果物に統一的な構造をもたせて、どこで何が書いてあるかわかりやすくしたい 運用後のプロダクト改善を見越して、ビジネスロジックを入出力から分離した変更容易な設計としたい 運用を前提としない単純なスクリプトなどであれば、コードベースにレイヤ構造をもたせるといった検討は不要かもしれません。 しかし、今回は実際に運用しながらプロダクトとしてブラッシュアップしていく必要があるものであったため、上記の理由からレイヤ構造を考えていきました。 どのように進めたか 構造の概念図を仮定義 まず、最初に概念的な構成を図として定義するところからはじめました。実際に次のような簡易的な概念図を作成しています。 Layered Architecture を参考にしつつ、単一な依存方向となる構成とし、HTTPなどアプリケーションの外側の処理とビジネスロジックを分離する当初の目論見を形にできそうな構成を作成しています。 作っては壊す 上記の図を書いたからと言って実際に実装に落とし込み、かつ機能をそこに乗せてみないと本当に自分たちにとって必要なものだったのかわかりません。そのため、概念図をチームに説明した上で、作っては壊していくことを宣言していました。 スキルを上げていく チームが扱えるレベルを上げていかないことには、変更容易な設計となる成果物は難しいと考えます。そのため、その時点のチームで、ちょっとずつ背伸びしていく方針としました。そのため、後述しますが最初はシンプルな構成から仮定義した地点に近づけていく作業をしています。 実装を進めるにあたって ユニットテストに対する意味付け 今回、進めるにあたってユニットテストに対して次の意味付けをしていました。 設計に対するフィードバック材料 ユニットテストを書くことによって、「疎結合な実装になっているか」といったコード設計に対するフィードバックがある程度得られると考えます。そのため、「実装の肌感を増やすためのユニットテスト」という意味合いをもたせました。 大胆なリファクタリング 「作っては壊す」と上で書いたとおり、都度都度大胆なリファクタリングが発生しうります。大胆にコードベースをいじるためにユニットテストを書いています。(しかし、これは同時に筆者に大きく反省を残す瞬間が後ほど現れます。) パッケージの使用 Go言語を実際に扱うに当たり、標準パッケージでどれだけやるのかという議論はよく耳にするかと思います。筆者のケースでは、Go言語自体を覚えれば書ける状態にしたかったため、なるべく標準パッケージを用いる方針にしています。 後続のエンジニアに対する配慮という意味合いもありますが、同時に自分たちが、インターフェースの使い方など「Goらしく」実装するために必要な技法をプロジェクトを通じてある程度体得している状態としたかったためです。 そのため、最初は愚直に書いていき、理解度に合わせて順次便利になるライブラリ・機構を導入していっています。 実際のレイヤ構造 初版:Simple Model-Controller 一番最初の構造です。非常にシンプルなcontrollerとmodelしかない構造です。 ├── model └── controller この際は、GoのAPI開発にまずチーム全体で慣れるというところから始めたい意図があり、最小限の構成となっています。 model内には、データベースに対する技術的実装なども含まれており、インターフェースの使い方はまだこれからです。 第二版:Model-Controller + DIP 次に移動した構造です。repositoryとdatastoreという2つのパッケージが増えています。 ├── domain │ ├── model │ └── repository // + ├── infrastructure │ └── datastore // + └── controller ここで、model内にべた書きされていた、データベースに技術的実装をdatastoreパッケージとよんでいる箇所に移動し、repositoryという抽象レイヤを挟む構成となりました。実装イメージとしては次のようになります。 package repository type UserRepository interface { Save(userID int ) error } package datastore type UserStore struct { DB *sql.DB } func (s *UserStore) Save(userID int ) error { // 処理 } ここにて、インターフェースの扱い方について習熟するため、ユニットテストでも自前でモック実装を作成してもらい実装の流れを掴むということをしていました。 第三版:Layered Architecture + DIP ├── config ├── domain │ ├── model │ └── repository ├── infrastructure │ ├── datastore │ └── router ├── interface │ ├── controller │ └── middleware └── service // + layered architectureのapplication layerに該当 ここで、最初の概念図の構成要素がほとんど用意された状態になります。単一な依存方向となるようなLayered Architectureの構成とし、レイヤ間をより疎結合にするためドメインレイヤにインフラストラクチャが依存する抽象を設置しています。 また、処理の複雑化に伴いアプリケーションレイヤに相当する(※1)serviceパッケージを作成しています。これはトランザクションを扱うことが喫緊の課題として取り組みましたが後に記述する反省点の一つとなりました。 ※1 MVC + Serviceと捉えた場合はServiceレイヤと同等とも言えるかも知れません。 現在:Layered Architecture + DIP 2018年11月26日現在の構成です。 ├── common // + 全レイヤー共通で利用する機能群 │ ├── uuidgen │ └── validation ├── config ├── domain │ ├── model │ └── repository ├── infrastructure │ ├── clock │ ├── datastore │ ├── logger │ └── router ├── interface │ ├── controller │ └── middleware ├── service └── testutil // + テストヘルパー群 全レイヤ共通で利用するUUID生成などをcommonディレクトリとして集約し、テストヘルパー群をtestutil packageに集約しています。 実践した上での反省点 トランザクションの扱い トランザクションをどう扱うかを最初から考えておいたほうが良かったと反省しております。トランザクションをどう扱うによって大きくインターフェースが変わってくるかと思います。筆者の例の場合は次のような扱い方にしています。 package service type SimpleServiceImpl struct { DB repository.DBConnector User repository.UserRepository } func (s *SimpleServiceImpl) Run(userID int ) error { tx, err := s.DB.Begin() if err != nil { return errors.Wrap(err, "failed to begin transaction" ) } if err = s.User.Save(tx, userID); err != nil { // 引数に*Txを渡す wrapRollback(tx) return errors.Wrap(err, "failed to save user" ) } if err = wrapCommit(tx); err != nil { wrapRollback(tx) return errors.Wrap(err, "failed to commit transaction" ) } return nil } トランザクションはservice packageで扱います。その際、service package内で生成された*sql.Tx構造体をそれぞれのrepositoryの引数に渡す構造としています。 package repository type DBHandler interface { Execer Querier Preparer } type UserRepository interface { Save(db DBHandler, userID int ) error } その上で、repositoryでは、*sql.DB/Txをwrapした抽象型を作成し利用しています。なお、この構成については、 @codehex さんの もう一度テストパターンを整理しよう(WebApp編) - Speaker Deck という資料を大変参考にさせていただいています。 そして、この実装構成にするために、本当に大胆なリファクタリングが必要になり、チーム全員でそれぞれ自身の実装した機能をリファクタリングするという工数が発生してしまいました。 世の中で公開されているレイヤ構造のサンプルコードでなかなかトランザクションを扱っているものは少ない気がしていますが、実際にやるのであれば、最初にトランザクションを扱うコードをサンプルとして作ってみることはおすすめしておきます。 追記:Contextの扱いについて 頂いたリアクションの中で、トランザクションに合わせてcontextの扱いについての話が出ておりましたので追記しておきます。 こちらもトランザクションと同様にメソッドの第一引数に context.Context を渡す実装にしたほうがよいかと思います。 筆者が実際に context パッケージを用いた実装を行う際には、 @deeeet さんの次の記事を非常に参考にさせていただきました。 Go1.7のcontextパッケージ | Taichi Nakashima Golangのcontext.Valueの使い方 | Taichi Nakashima テストヘルパーを先回りして用意しておく ユニットテストを愚直に書いていくという方針は当初理解度を深める上ではよかったのですが、進んでいく中で共通で毎回やるアサーションなどがコピペ作業になっている状態が発生し、「テストを書くのが億劫になる」といった状態が生まれていました。 先回りして共通的なものをテストヘルパーとして提供して開発体験を損ねないということが重要だったなと思います。 実際例として、ResponseHeaderのアサーションなどのヘルパーなどを随時用意していっています。 // Example assertion response header func AssertResponseHeader(t *testing.T, res *http.Response, code int ) { t.Helper() if code != res.StatusCode { t.Errorf( "expected status is '%#v', \n but actual is '%#v'" , code, res.StatusCode) } if expected := "application/json; charset=utf-8" ; res.Header.Get( "Content-Type" ) != expected { t.Errorf( "expected Content-Type is '%#v', \n but given '%#v'" , expected, res.Header.Get( "Content-Type" )) } } また、テストに関するTipsは今回のGo Conference 2018 Autumnにて、 @ timakin さんが発表されていた、 Golang API Testing the HARD way - Speaker Deck という資料が大変勉強になるかと思います。 まとめ 今回のエントリでは、最初に構想を決め、実践しながら拡張して構想に近づけていく事例を紹介させていただきました。 すでにGo言語をバリバリ使用している現場の方であれば過程の振り返りに、これから使用を検討されている方にとっては一歩踏み出す参考となれば幸いです。
アバター
どうもお久しぶりです。BASEビール部部長の氏原です。最近急に涼しくなりましたね。ハイアルなベルギービールでも飲んで温まるといい季節ですよ。 さて、今回もビールの話はとりあえず置いておいて現在Data Storategy Groupで取り組んでいる内容として、今年に出たらしい論文「 Adversarially Learned One-Class Classifier for Novelty Detection 」を実装して商品画像フィルタにならないか試してみたことについてお話しようと思います。 One-Class Classifierとはあるクラスに属するか否かの判別器です。例えばある画像に写っているものがQRコードか否かとか、水着か否かとかです。 今回の話は一行で言えば、ショッピングアプリ「BASE」の商品検索などから意図と異なる画像をフィルタリングするためにAdversarially Learned One-Class Classifier(ALOCC)が使えないか試したという内容です。 背景 皆さんショッピングアプリ「BASE」を使ったことはおありでしょうか?数多くの商品が並んでいて、私は米とか肉とか買ってます。そんな中こんな商品たちを見かけたことはないでしょうか? またはおすすめショップのところのこういうのとか こちらは、お知らせをひとつの商品として登録するとこのように表示されます。本来的な商品画像の使用法とは異なりますが、登録自体は可能です。 これらの商品ではない商品がショップのページに出てくるのは特に問題ないのですが、もし検索とかで出てきたら探している商品と違うなぁと思ってしまうのではないでしょうか? この問題を解決するために商品ではない商品を自動で発見して検索等には出ないようにする機能を開発しています。そしてQRコード画像については現在検索等からの除外を開始しています。 今回お話するのはお知らせ等の文字画像の検出について今取り組んでいることの紹介です。 Adversarially Learned One-Class Classifier(ALOCC) for Novelty Detectionについて 単純に文字画像か否かの判別器を作ることを考えると以下のような問題があります。 文字画像ではない画像、という教師データをどう集める? 文字画像以外の画像では幅が広すぎる 文字画像とはなにか?がはっきりとわからない。人によって違う。 文字中心のポスターや、文字っぽいロゴや、サイズ表はどう扱うべきか そもそも文字画像のサンプルが少ない 圧倒的に教師データが不足している これらの問題が Adversarially Learned One-Class Classifier for Novelty Detection では軽減できそうだったので、使えそうかどうか試してみました。 実験結果に行く前にこの手法を簡単に解説しておきます。 ネットワーク構造 論文に乗ってた画像そのまま上げます。 ( M Sabokrou, "Adversarially Learned One-Class Classifier for Novelty Detection", arXiv.org, 2018 ) 構造としてはGANにインスパイアされたものになります。 D がDiscriminatorなのはGANと同じで、GANでGeneratorだった部分がReinforcerとなっています。ReInforcerは画像(元の画像にノイズを追加したもの)を与えられて、何かしらの画像を出力します。 DiscriminatorはReinforcerが出力した画像と元の画像を見分けるように学習させ、ReinforcerはDiscriminatorを騙すように学習します。 学習の目的 さて、では上記ネットワーク構造は何を意図しているのでしょうか?これは元画像との違いを検出する検出器を作ろうとしているのです。 学習に使う画像は検出したいターゲットのクラスの画像だけ、今回の私の用途で言えば文字画像だけいいのです。文字画像以外の画像というものを教師として揃える必要がありません。これはありがたいです。 教師を作るためにこれは文字画像、これは文字画像ではない…と延々とやってるとこれはどっちだろうかという画像が出てきてだんだん混乱してきます。なのでこれは文字画像にしたいなってものを集めるだけでいいのはとても楽です。 R はターゲットに似た画像を生成するようになります。でも特定のターゲットクラスの画像しか学習してないので、ターゲットのクラスではない画像を渡すとぐちゃぐちゃな画像が生成される、ということを期待しています。今回は文字画像を学習させるので文字画像っぽいものは綺麗に再構築されるけど、それ以外はぐちゃっとなってほしいというわけです。 D は再構築された画像、つまりターゲットから少しでも違う画像を見分けようとするため、違いに敏感になるように学習されます。この結果 D はターゲットとの違い、つまり新規性の検出器となります。 文字画像を1として学習させれば、 D が返す結果が1に近いほど新規性がない、つまり学習した文字画像に近い画像ということになり、0に近いほど新規性の高い見たことがない画像ということになります。 ( M Sabokrou, "Adversarially Learned One-Class Classifier for Novelty Detection", arXiv.org, 2018 ) 論文ではペンギン画像を学習させています。 画像を R に通した結果、ペンギン画像はペンギン画像として再構築されていますが、ペンギンでない画像はなにか汚い画像になっています。この再構築した画像を D に渡すと元の画像を渡すよりも綺麗にペンギンとそれ以外を判別できるようになると主張されています。 実装 さて、一応論文の 著者の方のgithub はあったのですが、tensorflowがゴリゴリでわかりづらかったのでpytorchを使って実装してみました。 Reinforcer Reinforcerの中身は受け取った画像をConv2dで畳み込むencoderと、Deconvするdecorderです。GANだとパラメータ受け取ってDeconvしていくだけなので、ここがちょっと違います。 import torch.nn as nn class Reinforcer (nn.Module): def __init__ (self): super (Reinforcer, self).__init__() self.encoder = nn.Sequential( nn.Conv2d( 3 , 64 , 3 , stride= 1 ), nn.ReLU(), nn.BatchNorm2d( 64 , 0.8 ), nn.Conv2d( 64 , 128 , 3 , stride= 1 ), nn.ReLU(), nn.BatchNorm2d( 128 , 0.8 ), nn.Conv2d( 128 , 256 , 3 , stride= 1 ), nn.ReLU(), nn.BatchNorm2d( 256 , 0.8 ), nn.Conv2d( 256 , 512 , 3 , stride= 1 ), nn.ReLU(), nn.BatchNorm2d( 512 , 0.8 ), ) self.decoder = nn.Sequential( nn.ConvTranspose2d( 512 , 256 , 3 , stride= 1 ), nn.ReLU(), nn.BatchNorm2d( 256 , 0.8 ), nn.ConvTranspose2d( 256 , 128 , 3 , stride= 1 ), nn.ReLU(), nn.BatchNorm2d( 128 , 0.8 ), nn.ConvTranspose2d( 128 , 64 , 3 , stride= 1 ), nn.ReLU(), nn.BatchNorm2d( 64 , 0.8 ), nn.ConvTranspose2d( 64 , 3 , 3 , stride= 1 ), nn.Tanh(), ) def forward (self, x): x = self.encoder(x) x = self.decoder(x) return x Discriminator DiscriminatorはGANと何も変わりません。受け取った画像を畳み込んで全結合層に渡すだけですね。 import torch.nn as nn class Discriminator (nn.Module): def __init__ (self): super (Discriminator, self).__init__() self.model = nn.Sequential( nn.Conv2d( 3 , 64 , 3 , stride= 2 ), nn.BatchNorm2d( 64 , 0.8 ), nn.LeakyReLU( 0.2 , inplace= True ), nn.Dropout2d( 0.25 ), nn.Conv2d( 64 , 128 , 3 , stride= 2 ), nn.BatchNorm2d( 128 , 0.8 ), nn.LeakyReLU( 0.2 , inplace= True ), nn.Dropout2d( 0.25 ), nn.Conv2d( 128 , 256 , 3 , stride= 2 ), nn.BatchNorm2d( 256 , 0.8 ), nn.LeakyReLU( 0.2 , inplace= True ), nn.Dropout2d( 0.25 ), nn.Conv2d( 256 , 512 , 3 , stride= 2 ), nn.BatchNorm2d( 512 , 0.8 ), nn.LeakyReLU( 0.2 , inplace= True ), nn.Dropout2d( 0.25 ), ) self.adv_layer = nn.Sequential( nn.Linear( 512 * 9 , 1 ), nn.Sigmoid(), ) def forward (self, img): out = self.model(img) out = out.view(out.shape[ 0 ], - 1 ) validity = self.adv_layer(out) return validity 学習 学習の仕方もGANとほぼ変わりません。 import torch import torch.nn as nn import torch.optim as optim import torchvision.datasets as dset ... device = torch.device( "cuda:0" ) netR = Reinforcer().to(device) netD = Discriminator().to(device) dataset = dset.ImageFolder(....) dataloader = torch.utils.data.DataLoader(dataset) criterion = nn.BCELoss() optimizerR = optim.Adam(netR.parameters(), lr=..., betas=...) optimizerD = optim.Adam(netD.parameters(), lr=..., betas=...) ... for epoch in range (n_epochs): for data in dataloader: images = data[ 0 ] # dset.ImageFolderのtransformでtorchvision.transforms.Lambdaつかって # 元画像とノイズ画像両方とれるようにごにょごにょしてる real = images[ "real" ].to(device) # 元画像 noised = images[ "noisy" ].to(device) # ノイズかけた画像 batch_size = real.size( 0 ) ############################ # Update D network ########################### netD.zero_grad() # 本物 label = torch.full((batch_size, 1 ), 1 , device=device) output = netD(real) errD_real = criterion(output, label) errD_real.backward() # 偽物 fake = netR(noised) label.fill_( 0 ) output = netD(fake.detach()) errD_fake = criterion(output, label) errD_fake.backward() optimizerD.step() ############################ # Update R network ########################### netR.zero_grad() # 偽物でDを騙す label.fill_( 1 ) output = netD(fake) errR = criterion(output, label) errR.backward() optimizerR.step() 結果 文字画像を学習させてみた結果、文字画像はそこそこ綺麗に再構築され、それ以外は結構ぐちゃっとなるようになったようです。 accuracy false positive false negative D ( X ) 0.92 0.06 0.02 D ( R ( X )) 0.87 0.03 0.10 むしろ判別性能は R をかました方が落ちてます。ただ、false positive、つまり本来文字画像じゃないのに文字画像と認識してしまった率は多少改善されました。文字画像は出したくないのですが、本来出したい商品画像が落とされてしまうのは避けたいのでfalse positiveはできる限り抑えたいところです。そういう意味ではそこそこ意味はあるのかなと思います。 感想 文字画像はそこまで綺麗にいかない、というか論文の結果の画像が綺麗にできすぎ。何か書いてない(もしくは私がちゃんと読めてない)工夫があるのかも。 Reinforcerは論文にはない工夫を追加した(まだ実験中なので内容は内緒)。そうしないとただのGANにしかならない。ターゲット画像以外の画像渡しても普通にそこそこ綺麗な画像が再構築される。 Batch Normarizationは偉大。 まとめ 文字画像を正確に判別できるようになるまではまだまだ道のりが長そうです。 false positive 3%といっても画像は1日に何万枚と上げられてきますので、間違いの絶対数はそこそこ多くなります。理想的には桁をもう一つ下げたいところです。 でも今回の判別器では、単純に文字画像判別器を作ったときに弾かれがちだった文字Tシャツはかなり高精度で文字画像ではなく商品であると認識できるようになりました。 こういうやつ ( RACCOONS ONLINE STORE より) 引き続き良きユーザー体験を提供できるように頑張っていきます。
アバター