Quantcast
Channel: クックパッド開発者ブログ
Viewing all 726 articles
Browse latest View live

日々の気づきをクックパッドのサービスとして形にするための取り組み

$
0
0

こんにちは、検索・編成部ディレクターの原田です。検索・編成部では検索サービスをはじめとして、主にクックパッドでレシピを探す方に向けたサービス開発をしています。

さあ、しっかり企画を立てよう!の落とし穴

突然ですが、例えば

  • 思いつきで施策をスタートしてしまって、後でチームのメンバーがバラバラになってしまった…

  • 関連する部署を調整できなかった…

  • またその逆で、誰かの思いつきの施策によって翻弄されて大変だった…

という経験はないでしょうか。

個人の制約を超えて目的を達成できることがチームで仕事をすることの可能性ですが、チームワークの威力は各自が自発的に行動できるようになって発揮されるので、判断を支える明確な目標や指針を含む「企画」が必要になります。

一方で、さあちゃんとした企画を立てるぞ! と意気込んでみても、ちゃんとしたどころか発想すら浮かばない…ということはないでしょうか。毎日の暮らしの中で使っていただくサービスであれば、ユーザーの生活をリサーチする中で、または自分自身が日常の中で感じた「何か」が大きな価値につながる可能性は信じたいものです。

今回はそのような「気づき」に明確な目標と実現までの道筋という力を与え価値を形にするために、クックパッドの検索・編成チームが使っている3つのフレームワークについてご紹介します。

気づきに力を与えるための3つのフレームワーク

1. 気づきを企画に組み立てる「価値仮説テンプレート」

何かの「気づき」を得てもそれを企画にするためには、まず何をどう進めるか・どう考えようかということが頭をよぎります。実際私が今のチームで開発をし始めた頃、何をどう進めるかを決めること自体に時間をかけてしまい、コアな議論を始めるのに時間がかかっていました。

そこで、考えなければいけない要素をテンプレート化し、チームでサービス開発用に活用しているGitHub Enterprise レポジトリのREADME.md上に置いています。なお、GitHub Enterpriseを活用したサービス開発についてはすでにこちらのエントリーに詳しいのでぜひご覧ください。私たちのチームでは主な要素として「ユーザー」「解決する問題(シーン)」「解決策(サービスの特徴)」「成功指標(KPI)」「テストの仕様と対象とユーザー」「TODO & 期限」の5つを設定しています。

これに沿って考えを組み立てることで、何をどう進めるか・どう考えようかということにエネルギーを消費することなく「気づき」をチームのメンバーが共有可能な企画にパワーアップさせることができます。

2. 根源的な欲求を見極める「なぜなぜnode」

ところが「価値仮説テンプレート」に沿って考えられるようになると、ある程度の完成度が担保できるという利点があるゆえに、企画のストーリーを都合の良いように(多くの場合は意図せずに)つじつまを合わせてしまうという新しい問題に直面するようになりました。部署としてKPIに置いている検索成功率から逆算して解決したいことを自分で作り出してしまい「そんな状況って本当にあったっけ?」ということが起き始めたのです。

そこで「価値仮説テンプレート」を検討をするときに、人間として根源的な欲求を定めてから、妨げになる理由をブレイクダウンしながら「価値仮説テンプレート」の各要素を紐付けられるやり方をフレームワーク化しました。

まず、人間の根源的な欲求としてクックパッドが大切にしている「毎日の料理を楽しみに」を出発点に、「毎日の料理が楽しく感じられないところにどんな悲しみや失望があるのか」をひたすら「なぜ、なぜ」とブレイクダウンします。

ポイントは、この時点ではアウトプットのことは一切考えないことです。また、出発点を「楽しく料理をしたい」というようなポジティブな感情にしないことも大事です。生活者の周りにある「悲しみ」や「失望」から出発することで「あったらいいかもね」というレベルの企画になってしまうことを防ぐことができます。

「なぜなぜ」を出し切ったところで、グルーピングして特に注力したい枝をチームのメンバーで決めていきます。

f:id:a_harada:20151203171042p:plain

そこまでできたらようやく「価値仮説テンプレート」の次の項目である「解決する問題(シーン)」と「解決策(サービスの特徴)」を、疑いようのない欲求にどうつながるのかを見極めながら検討します。

ポイントは、この時点ではサービスの特徴を「機能」で考えないことです。そのサービスが実現する「状態」にこだわって言語化することで欲しいのは「ドリル」ではなく「穴」だったという話を防ぐことができます。

f:id:a_harada:20151203171051p:plain

そしてそのサービスが実現する「状態」を数値化したものが「評価指標(KPI)」となるわけなのですが、ここでようやくチームの目標に対してどのくらい影響を与えそうかを他の施策と比較し、優先順位を判断することができるようになります。実際にはKPIから逆算することが悪ではなく、いったりきたりしながら考えるようにしています。

3. ”個人の制約”を乗り越える「価値形にする会」

ここまでツールについて話をしてきましたが、結局のところ一人で考えていては限りがあります。

そこで「価値形にする会」というMTGを週1回開催し、別の施策を担当するディレクターが集まり「なぜなぜnode」の元となる欲求の妨げ要因や、その先にある解決策の具体的な仕様についてのアイデアフラッシュをする場として、フレームワーク自体をサポートしています。

フレームワーク自体の磨き込み

以上、私たち検索・編成チームで使っている3つのフレームワークをご紹介しましたが、フレームワークは固定化されたルールではありません。それ自体が開発に関わる自分たち自身に向けたサービスの一つだと考えています。ゆえにフレームワーク自体もご紹介した3つのフレームワークに則り「自分たち自身の疑いようのない欲求(目的)」と「どのような状態になることを期待しているのか(ゴール)」を定めてPDCAを回しています。

そもそも、3つのフレームワーク自体「さあ作ろう!」と考えられたわけではなく、個々人が実務の中で生み出した素敵な知恵に名前をつけて体系化したものなのです。サービス開発で価値を生み続けるフレームワークであるためには、個々人の良いやり方を見つけて光を当て、不要なものはしっかり捨てることを続けることが大事です。そのためにフレームワークの運用自体をissue化したりもしています。

f:id:a_harada:20151203171010p:plain

まとめ

ユーザーが最も大切にする価値を実現するにはエネルギーが必要です。企画を立てられたところで、そのあとにはあるべき形を目指し失敗と改善の繰り返しが待っています。

フレームワーク自体はそれを使うことで価値を実現できる魔法のツールではありません。フレームワークを持つことで、チームのエネルギーが考え方や進め方から解放されて「ユーザーの毎日の暮らしの中に起きていること」についての具体的な会話が溢れるようになる、そんな世界を目指しています。

検索・編成部では、「毎日の料理について起きていること」から発想して技術の力で笑顔を増やせることに無条件にワクワクしてしまうエンジニアを募集しています。


Smart Lock for Passwordsを利用したAndroidアプリログインフロー改善への取り組み

$
0
0

技術部モバイル基盤グループの児山です。

モバイル基盤グループでは、クックパッドのiOS/Androidアプリの開発だけでなく、アプリのユーザー体験を向上させるために新しい仕組みの調査や実装も行っています。 本稿ではAndroidアプリにおけるログインフロー改善の取り組みについてご紹介したいと思います。

モバイルアプリでIDとパスワードを入力してログインするのは大変です。 端末サイズが大型化してきているとはいえ、キーボードで1文字ずつポチポチメールアドレスを打つのはストレスが溜まりますし、安全な長いパスワードは入力する気が起きません。 特にWebサービスも提供しているモバイルアプリであれば、ChromeやSafariなどに保存しているWebサービスの認証情報をアプリでも使えたら…と思うことがよくあります。

実はSafariに保存した認証情報でのログインはiOSアプリで利用可能になっているのですが、AndroidではiCloud Keychainに相当する機能がなかったため実装できていない状態でした。 そんな中、今年のGoogle I/OにおいてAndroidでもSmart Lock for Passwordsが発表され、AndroidでもChromeに保存した認証情報を利用できるようになりました。 12月1日に公開したクックパッドアプリ v15.11.2.2 ではこのSmart Lock for Passwordsを利用してログイン補助機能を実装しています。

Smart Lock for Passwordsの動作例: Googleアカウントに認証情報が保存されている場合、以下のように自動的に補完入力を行います。

f:id:nein37:20151204142407g:plain

Smart Lock for Passwordsについて

Smart Lock for Passwordsという名前は長いのでついSmart Lockと略したくなりますが、実はSmart Lockというのは複数の機能の集合名です。 Smart LockにはSmart Lock for Androidという機能も含まれていますが、こちらは端末のロック解除を簡単にする機能で本稿の内容には直接関係ありません。 今回利用するChromeやAndroid端末からGoogleアカウントに認証情報を保存する機能はSmart Lock for Passwords、Androidでの実装はSmart Lock for Passwords on Androidという名前になっていますが、さすがに長過ぎるので以下ではSmart Lock for Passwordsとだけ書きます。

Smart Lock for Passwordsの動作はかなりシンプルで、認証情報の読み書き以外の機能はあまり持っていません。 主な機能は以下のとおりです。

  1. Googleアカウントに認証情報を保存する
  2. Googleアカウントに保存された認証情報を取得する
  3. Googleアカウントに保存された認証情報を削除する
  4. 他のアプリやサイトで使っているユーザー名やメールアドレスを認証のためのヒントとして取得する

Googleアカウントに、という表現の通り、Android端末にGoogleアカウントでログインしていない場合はSmart Lock for Passwordsは利用できません。 また、Google Play Servicesの機能なのでGoogle Play開発者サービスが入っていない端末でも利用できません。 Android 6.0 Marshmallowと同じくGoogle I/Oで発表されたため稀に勘違いされていますが、Android 2.3 (API level 9)から利用できます

ここまでは非常に簡単なのですが、実はAndroidではユーザーとGoogleアカウントが完全に1:1ではなく1人のユーザーが複数のGoogleアカウントでログインできるため、これを考慮すると挙動が一気に複雑になります。 しかもSmart Lock for Passwordsは複数の認証情報を保存することができるため、一人のユーザーが複数のGoogleアカウントでログインし、さらにGoogleアカウントごとに複数の認証情報を保存しているという状態もあり得ます。 発表当初はこの挙動についてドキュメントにあまり細かい説明がなくスクリーンショットもなかったため、実装前にSmart Lock for Passwordsが取り得る挙動についてひとつひとつ動作確認をするしかありませんでした。

以下はその調査内容の一例です。

認証情報保存時、ユーザー確認などがあるか

新しいIDを保存する場合は保存確認ダイアログを表示し、同一のIDの場合はユーザーに確認せず上書きを行う。

保存確認ダイアログ

f:id:nein37:20151204142430p:plain

複数のGoogleアカウントでログインしている状態で認証情報を保存しようとした場合どうなるのか

保存先のアカウントを選択するダイアログを表示する。

f:id:nein37:20151204142427p:plain

認証情報取得時、ユーザー確認などがあるか

認証情報が1件しかない場合はユーザーの同意なしにその認証情報を取得できる。 取得時には画面下部に青色の帯が表示される。

f:id:nein37:20151204142423p:plain

認証情報が複数ある場合はどれかひとつを選択するダイアログが表示される。 ユーザーがどれか1つを選ぶとその認証情報を取得できる。 この時は青色の帯は表示されない。 「上記以外」はダイアログのキャンセル動作(最近の更新で追加されたもの)。

f:id:nein37:20151204142419p:plain

アプリ側でUIの変更は可能か

ダイアログ表示のタイミングは制御できるがダイアログの文言やデザインの変更は基本的にアプリ側ではできない。 また、UIはGoogle Play Serviceのアップデートにより変更される可能性がある。

画像はSmart Lock for Passwords公開直後の認証情報取得成功時に出ていた帯。 右側にgマークが入っていた。

f:id:nein37:20151204142416p:plain

Webサイトとアプリでそれぞれ別の認証情報を保存するとどうなるか

Webサイト、アプリのすべての認証情報の中からひとつを選ぶダイアログが表示される。 ダイアログの見た目は通常の複数の認証情報から選ぶものと同じ。

複数のGoogleアカウントがそれぞれ認証情報を保存している場合に認証情報を取得しようとした場合どうなるのか

すべてのGoogleアカウントで保存されている認証情報の中から取得対象を選択するダイアログを表示する。 ダイアログの見た目は通常の複数の認証情報から選ぶものと同じ。

クックパッドアプリと同一のパッケージIDを持つアプリが認証情報を不正に取得することはできるのか

パッケージIDと証明書の組み合わせでアプリを特定しているため、証明書が異なるアプリでは認証情報を取得できない。 管理ページを見るとアプリから保存した認証情報は以下のURLの認証情報として管理されているように見える。

android://証明書か何かのハッシュ?@パッケージ名/

なお、Chromeの設定画面からパスワード管理を見た時はパッケージ名が先に来ている。

android://パッケージ名(途中で省略されて見えず)

本番環境と検証環境で認証情報を別々に保存することはできるのか

パッケージIDを変更すれば可能。 ただし証明書を変更するとChromeの設定ページで区別しづらくなるため、あまりお勧めできない。

上記のような調査はもちろん実装のためでもありますが、ユーザーが実際に機能を利用する時に直面する問題を知るためでもあります。 ユーザーの利便性向上のために入れた機能でユーザーが困ることがないよう仕様を完全に把握した上で導入し、開発者自身によるドッグフーディングによって問題の洗い出しを行っています。

導入への取り組み

Smart Lock for Passwordsを導入するにあたり、挙動を見ながら色々な実装を試した結果、既存のログインフローを大きく変更しないような形で実装することになりました。 当初は「インストール直後に起動すると自動的にログイン済み」というフローも検討していましたが、ログイン失敗時の処理やダイアログ表示などで逆にログインフローが複雑化してしまう懸念があったために現在はログイン画面内でのみ動作するようにしています。

実は7月ごろにはSmart Lock for Passwordsの実装はある程度形になっていたのですが、上記の懸念がリリース前になって持ち上がりました。開発者間で話し合った結果、一旦リリースは見送り、ドッグフーディングで検証・改善を行いつつGoogle社とも協議を行いました。 そのドッグフーディングの結果やGoogle Play ServicesのアップデートによるUIの改善を反映して、今月やっとSmart Lock for Passwordsを実装したアプリをリリースすることができました。

現在のクックパッドアプリが行っていることは以下の2つだけです。

  1. ログイン画面を表示した時にGoogleアカウントに認証情報が保存されていれば入力欄に補完する
  2. ログイン成功時に認証情報の保存を行う

結果的にSmart Lock for Passwordsのほぼ最小限の実装となっていますが、この実装により普段Chromeでクックパッドを利用していた新規アプリユーザーや機種変更をするユーザーの一部はキーボードに触れることなくログインすることができるようになっています。 今後もSmart Lock for Passwordsの利用状況やUIのアップデートにあわせて継続的にログインフローの改善に取り組んでいく予定です。 端末を買い替えたりタブレットを買い足したりしたとき、使っていてよかったと思ってもらえるようなクックパッドアプリを目指して行きたいと思います。

おわりに

本稿ではAndroidアプリのログインフロー改善に関する最近の取り組みについてご紹介しました。

クックパッドではモバイルアプリのより良いユーザー体験のために新しい技術を追い続けるエンジニアを募集しています。

興味がある方はぜひご応募ください。

クックパッドのジョブ管理システム kuroko2 の紹介

$
0
0

こんにちは。技術部 開発基盤グループの大石です。

今回はクックパッドで利用されているRuby製のジョブ管理ツールkuroko2について紹介したいと思います。

f:id:eisuke-oishi:20151207174217p:plain

kuroko2 とは

クックパッドでは2011年頃より、kurokoというジョブ管理ツールがありました。 そして現在、kurokoの後継のジョブ管理ツールとしてkuroko2が2014年にkurokoの開発者でもある当時の技術部長 takaiによって開発され現在に至っています。

商用のジョブ管理ツールは昔からありますし、ここ最近はAirflowやAzkabanなどのOSSのツールが存在しており、わざわざ自社でジョブ管理ツールを作る必要は無いのかもしれません。 しかし、kurokoが開発された当時はそこまでの完成度のOSSのジョブ管理ツールは存在していなかったということと、過去のバッチ資産も問題なく動作させることでき、機能追加など自分たちの望む形に素早く進化させやすいため、自社製のジョブ管理ツールを使っています。

アーキテクチャ

f:id:eisuke-oishi:20151207174236p:plain

Web UI

Web UIの主な機能は以下のようなものになります。

  • ジョブの定義 (後述) と、スケジュールの定義、管理者、エラーの通知方法などの設定
  • ジョブの手動実行
  • 実行中のジョブの確認、実行中のジョブの強制停止などのオペレーション
  • 実行履歴、実行時のログ確認

job-scheduler

cronスタイルで設定されたスケジュールに合わせて実行するべきジョブをworkflow-processorへ伝えます。

workflow-processor

ジョブの定義から実際に実行されるコマンドやワークフローをcommand-executorへ伝えます。 また、実行中のジョブに対するkill signalやエラー時のリトライなどの操作もWeb UIを通じてworkflow-processor経由で行われます。

command-executor

command-executorという実際のコマンド実行を責務としたワーカーが複数台のEC2 インスタンス上で動作し、実行可能なジョブを逐次実行します。 command-executorは同時に、実行状態を確認するcommand-monitorと呼ばれるワーカーと、Web UI経由で送信される kill Signal を受け取り実行中のバッチを停止させるcommand-killerと呼ばれるワーカーも起動させます。

また、1つのマシン上で複数のcommand-executorが動作してるので、大量のメモリやCPUを消費するようなバッチの場合は、それに合わせた性能の専用のインスタンスを立て、それに向けてジョブを実行することが可能です。

機能

ジョブの定義方法

メンテナンスや障害時にはインフラチームが確認することもあるため、他の部署が見たときのために、どのようなジョブなのか、どのような影響があるのか、障害時にどのような対応をするべきかを把握できることが重要です。 kuroko2では、設定時に入力するジョブの内容説明のテキストフィールドに、プレースホルダーとしてテンプレートが用意されており、Markdown記法を採用することで、これらの情報を記述、管理しやすいように工夫がされています。

f:id:eisuke-oishi:20151207174251p:plain

kuroko2 script

具体的なジョブの定義は kuroko2 script と呼ばれるDSLを記述していきます。

最も基本的な記述例は下記のようになります。

execute: echo "Hello, world"

execute:の後に書かれたものは、command-executorからシェルにそのまま渡される単なるシェルコマンドになります。

クックパッドでは、多くのアプリケーションでRailsを採用しています。 Railsアプリケーションのバッチについては app/batches以下に管理し、 rails runnerコマンドで実行できるようにしています。

kuroko2ではそのようなRailsアプリケーション用に、RubyやRailsに関する環境変数を自動で設定する executeを拡張したDSLを用意して、それを経由してバッチを実行しています。

もちろん、Rails以外にも対応しています。 具体的な例として、aamineのSQLバッチフレームワークである bricolageも kuroko2 から実行されています。 詳細な bricolage についての説明は、「巨大なバッチを分割して構成する 〜SQLバッチフレームワークBricolage〜」を参照ください。

ワークフロー管理

上記のジョブ定義DSLは、1つのバッチ実行を定義するだけでなく、依存関係にあるような複数のバッチを1つのジョブとして定義することができます。

execute: command1
execute: command2

上記はcommand1、command2の順番で逐次実行をするだけですが、さらに

execute: command1
fork:
  execute: command2-a
  execute: command2-b
execute: command3

のように、command2-aとcommand2-bは並列実行され、2つの実行が終了したところで、command3が実行されるようなワークフローも定義することができます。 また、例えばcommand2-bがエラーになった場合、そこでワークフローは一時停止し、実行をリトライあるいはスキップさせることもできます。

これらはほんの一例ですが、その他にも、

  • batch_runner - railsアプリケーション用のバッチ実行コマンド
  • env - バッチに渡す環境変数を設定できる
  • sub_process - 他のジョブを実行する
  • timeout - 設定された時間を過ぎた場合はジョブを途中で終了させる
  • expected_time - 想定実行時間を超えたらジョブの管理者にアラートを送る

上記のようなDSLがあります。 さらにDSLの追加が簡単に行えるようになっており、今後の環境変化などに柔軟に対応できるように設計されています。

ジョブ実行の管理

エラーや状態の管理について

ジョブ実行中に何らかの原因でエラーが起きたときのkuroko2の挙動について説明します。

f:id:eisuke-oishi:20151207174313p:plain

kuroko2 はジョブに、WORKING, ERROR, SUCCESS, CANCEL ステータスを持っており上記のような流れになります。

終端ステータスは、SUCCESSとCANCELになり、ERRORの場合はジョブ内のエラーが起きたバッチをリトライやスキップ、あるいはジョブ自体をキャンセルします。

ERRORが終端ステータスではない理由は、ERRORになったということは途中までしか実行できず完了していないという意味でWORKINGと同じ扱い、という理由です。さらにERRORの状態からリトライが行えるような機能となっています。

また、エラーが発生したときは、メールやチャットで管理者に通知し、エラーのまま放置されている場合にもエラーの状態が解決されるまでアラートメールを送信します。 これらの機能によって、エラーが発生した場合には管理者は素早く気づいて対応が行うことができ、ジョブがエラーのまま放置されるような事態も防げるようになっています。

多重実行について

バッチ実行の際に気をつけるべきものとして、同じバッチが多重に実行されてしまうことです。 バッチの実行が想定外に時間がかかりスケジューリングされた次の実行の時間になってしまったり、意図せず手動で多重に実行してまい事故に繋がることもあります。

多重実行を避けるコードを、すべてのバッチに個別に実装していくのは骨が折れるため、kuroko2はジョブの定義として、多重実行の可否を定義できるようになっています。

エラーが起きたときにスケジュールされたジョブの実行を止める

例えば、1日1回必ず実行されることが前提で、1日でも歯抜けになってしまうとデータに齟齬が生じてしまうため、日付の順番通りに実行しなければいけないバッチがあったとします。

そういったバッチがエラーになった場合、その日のうちに担当者がリカバリできれば問題ないですが、それを絶対に行えるという保証はありません。

kuroko2ではジョブがエラーになり上記のERRORステータスのままの履歴が存在している場合、それ以降のジョブのスケジュールを自動でキャンセルする機能があります。すべてリカバリを行い、エラーが解決されたところでスケジュール通りに実行するように再開されます。

これは特に休日や連休などすぐに対応できない場合に、最悪の事態が起きないような防御策として有効です。

ジョブの一覧管理

kuroko2では大量にジョブを管理しているため、ジョブを素早く探せることや、管理しているジョブの状態を素早く知れることは非常に重要です。 kuroko2のWeb UIには検索機能やダッシュボードがあり以下のような機能が提供されています。

  • 自分が管理しているジョブやお気に入りに登録したジョブが一覧で確認できる
  • タグで絞り込みができる
  • 次回実行予定時間が確認でき、その時間でソートすることでこれからの実行予定がわかる
  • 検索機能
  • 実行中のジョブの一覧

f:id:eisuke-oishi:20151207174329p:plain

Dockerコンテナの活用

kuroko2の欠点として、新規のプロジェクトで新たにアプリケーションがバッチを実行したいときには、そのアプリケーションが動作できるように環境構築の作業が発生してしまうことです。

社内では、eagletmtによるDocker環境の整備が進められており、バッチ実行に関してもDockerの恩恵が受けられるようになっています。 具体的には、Dockerイメージをジョブ実行の際にpull、コンテナを立ち上げ、Dockerコンテナ内でコマンドを実行できるようにしたものです。

Dockerを利用することで、アプリケーションサーバーにも使えるDockerイメージを1つ作るだけでジョブ管理システム上での環境構築を行う必要なく、ジョブ管理システム上でバッチが実行できるようになりました。

最後に

クックパッドで利用されているジョブ管理システムとその周辺について簡単に紹介しました。

残念ながらkuroko2はオープンソース化の予定はありませんが、今回紹介した運用方法や機能などの知見は過去の経験や失敗を元にしたものが多く、ジョブ管理やバッチ実行の管理を行うエンジニアにとっては、同じような悩みや問題を抱えていたりするのではないかと思います。 そういった方々にとって、今回の記事が参考になれば幸いです。

gdbを使ったrubyのデバッグ

$
0
0

技術部の国分 (@k0kubun) です。

先日byebugの高速化を行っていた最中、変更を加えたbyebugを使っていると一定の確率でrubyがSEGVするバグを発見しました。 私はC言語のコードのデバッグの経験はなかったのですが、デバッガの使い方を調べながらSEGVの原因調査を行いパッチを送ったところ無事取り込まれ、最新の高速なbyebugが安全に使えるようになりました。

その際、ruby自体をデバッグするために必要な情報が分散していて大変だったので、まだrubyのデバッグをしたことがないけれどやってみたいという人を対象に、gdbというデバッガを使ったrubyのデバッグの方法を紹介します。

デバッグ用にrubyをビルドする

デバッグ時に変数名やソースコードなどの情報を見るためには、最適化オプションをオフにしてデバッグ用にrubyをビルドしておく必要があります。

rubyのデバッグ用ビルド手順

GitHubにあるrubyのリポジトリをcloneしてデバッグ用にビルドする手順を紹介します。 デバッグに必要なgccのコンパイルオプションに関しての詳細はこちらのドキュメントをご覧ください。

$ git clone https://github.com/ruby/ruby
$ cd ruby
# ./configureを(再)生成
$ autoreconf
# -O0で最適化を無効にし、-g3でデバッグ情報を付ける
$ ./configure optflags="-O0"debugflags="-g3"
$ make
$ make install

これにより生成される./rubyを使ってデバッグしていきます。

ビルドしたrubyをrbenvから使うには

もしビルドしたrubyをrbenvで管理したい場合は~/.rbenv/versions/以下にインストールする必要があります。以下のような手順でtrunkとしてrbenvから使えるようにできます。

# ~/.rbenv/versions/trunk にインストールされるようにする
$ ./configure optflags="-O0"debugflags="-g3"--prefix="${HOME}/.rbenv/versions/trunk"
$ make
$ make install

デバッグに使う時は、./rubyの代わりに$(RBENV_VERSION=trunk rbenv which ruby)を指定してください。

gdbでrubyの動作を追ってみる

では、以下のコードがrubyにどのように処理されるのかgdbを使って実際に追ってみましょう。

str = 'world'
str.prepend('hello ')
puts str

rubyのメソッドが実装されている関数名を調べる

str.prepend('hello ')の前後で変数strがどう変わるかを見てみます。 String#prependが実行される直前でブレークするためには、まずそれがCのソースのどこで実装されているか知る必要があります。

ruby-docのString#prependのページで「click to toggle source」というリンクをクリックすると、以下のようにString#prependrb_str_prependで実装されていることがかります。

f:id:k0kubun:20151209162906p:plain

調べた関数でブレークする

どの関数でブレークするかがわかったら、以下のように目的の関数にbreakし、runでスクリプトを走らせます。

$ echo"str = 'hello'\nstr << ' world'.freeze\nputs str"> ./hello.rb
$ gdb --args ./ruby ./hello.rb
(gdb)break rb_str_prepend
Breakpoint 1 at 0xdc16b: file string.c, line 2689.
(gdb) run
Starting program: /home/vagrant/ruby/ruby ./hello.rb
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/i386-linux-gnu/libthread_db.so.1".
[New Thread 0xb7ae5b40 (LWP 2272)]

Breakpoint 1, rb_str_prepend (str=2151591520, str2=2151591700) at string.c:26892689        StringValue(str2);

rb_str_prependで実行を止められました。

状況を確認する

上記の出力だけだと今どこにいるのかわかりにくいですが、デバッグ用にrubyをビルドしているおかげで、listを叩くと以下のように周辺のソースコードを表示することができます。

(gdb) list
2684*/26852686static VALUE
2687    rb_str_prepend(VALUE str, VALUE str2)
2688    {
2689        StringValue(str2);
2690        StringValue(str);
2691        rb_str_update(str, 0L, 0L, str2);
2692return str;
2693    }

strstr2は何か確認してみましょう。VALUEはポインタなので、pで値をそのまま表示するとオブジェクトのアドレスが表示されます。

(gdb) p str
$1 = 2151591520
(gdb) p str2
$2 = 2151591700

このオブジェクトの値を確認するにはどうすればいいでしょうか。ruby内にはデバッグ用にrb_pという関数が定義されており、これをcallで呼び出すと値を確認できます。

(gdb) call rb_p(str)
" world"

もしgdbをrubyのリポジトリ内で実行していれば、rubyのリポジトリにある.gdbinitでrb_pcall rb_pにするコマンドが定義されているため、rb_pだけで確認できます。

(gdb) rb_p(str2)
"hello"

他にも、rb_backtraceを使うとrubyレベルでのバックトレースが見れます。

(gdb) rb_backtrace
        from ./hello.rb:2:in `<main>'
        from ./hello.rb:2:in `prepend'

これはgdbのコマンドですが、backtraceを叩くとCレベルでのバックトレースが見れます。

(gdb) backtrace
#0  rb_str_prepend (str=2151591520, str2=2151591700) at string.c:2691
#10x801381b1 in call_cfunc_1 (func=0x800dc165<rb_str_prepend>, recv=2151591520, argc=1, argv=0xb7ae6020)
    at vm_insnhelper.c:1542
#20x80138b52 in vm_call_cfunc_with_frame (th=0x802a3c18, reg_cfp=0xb7b65fc8, calling=0xbfffede8, ci=0x80380ab8,
    cc=0x803d8058) at vm_insnhelper.c:1709
#30x80138c57 in vm_call_cfunc (th=0x802a3c18, reg_cfp=0xb7b65fc8, calling=0xbfffede8, ci=0x80380ab8,
    cc=0x803d8058) at vm_insnhelper.c:1804
#40x80139816 in vm_call_method_each_type (th=0x802a3c18, cfp=0xb7b65fc8, calling=0xbfffede8, ci=0x80380ab8,
    cc=0x803d8058) at vm_insnhelper.c:2091
#50x80139e81 in vm_call_method (th=0x802a3c18, cfp=0xb7b65fc8, calling=0xbfffede8, ci=0x80380ab8, cc=0x803d8058)
    at vm_insnhelper.c:2215
#60x8013a06c in vm_call_general (th=0x802a3c18, reg_cfp=0xb7b65fc8, calling=0xbfffede8, ci=0x80380ab8,
    cc=0x803d8058) at vm_insnhelper.c:2258
#70x8013cccd in vm_exec_core (th=0x802a3c18, initial=0) at insns.def:995
#80x8014c33c in vm_exec (th=0x802a3c18) at vm.c:1636
#90x8014cbc6 in rb_iseq_eval_main (iseq=0x803eafa0) at vm.c:1879
#100x80018fd5 in ruby_exec_internal (n=0x803eafa0) at eval.c:244
#110x800190dd in ruby_exec_node (n=0x803eafa0) at eval.c:309
#120x800190ab in ruby_run_node (n=0x803eafa0) at eval.c:301
#130x80017224 in main (argc=2, argv=0xbffff764) at main.c:36

ステップ実行する

変数の推移を確認しながらステップ実行をしてみましょう。nextを叩くと1行ずつ先に進むことができます。

2689        StringValue(str2);
(gdb) next
2690        StringValue(str);
(gdb) next
2691        rb_str_update(str, 0L, 0L, str2);

rb_str_updateという何か更新が走りそうな関数の手前に止まりました。この行ではstrstr2の値はどう変わるでしょうか。

(gdb) next
2692return str;
(gdb) rb_p(str)
"hello world"
(gdb) rb_p(str2)
"hello"

#prependのレシーバになっていた" world"のみ値が変わっていることが確認できました。

デバッグ中に任意の式を実行する

rb_eval_string_protect関数を使うと、デバッグの途中で任意の式を実行することができます。

(gdb) call rb_eval_string_protect("str << '!'", 0)
$3 = 2151591520
(gdb) rb_p str
"hello world!"

strの末尾に"!"を追加できました。continueで最後まで走らせるとどうなるでしょうか。

(gdb) continue
Continuing.
hello world!

このように、putsに渡る引数をデバッグ中に変更できていることが確認できました。 チュートリアルは以上です。最後に今回使用したコマンドをまとめておきます。

コマンド 操作内容
break f 関数fにブレークポイントを貼る
run プログラムを実行する
list 現在いる位置のソースを確認する
p Cレベルでの値を確認する
backtrace Cレベルでのバックトレースを表示する
rb_p rubyレベルでの値を確認する
rb_backtrace rubyレベルでのバックトレースを表示する
next 次の行に移動する
continue 最後まで実行する
call rb_eval_string_protect("...", 0) 任意の式を実行する

rubyがSEGVした時のデバッグ方法

UNIXプロセスが異常終了すると、プロセスのメモリの状態をそのまま保存したコアファイルというものが生成されます。 rubyがSEGVしたときもそのコアファイルをgdbから読み込むことで、後からSEGVした瞬間の状況を調査することができます。

ただし環境によってはデフォルトでコアダンプが無効になっていることがあるので、以下のようにコアダンプを有効にしてからrubyをSEGVさせます。

$ ulimit-c# core file sizeを確認。0ならコアダンプされない
$ ulimit-c unlimited # コアファイルのサイズを最大にする

その後以下のようにするとSEGV時の状況を調査することができます。

# 例えばOSXでは /cores/core.XXXXX に作られる
$ gdb -c /path/to/core ./ruby

それ以外のデバッグ操作はrubyの動作を追いかけた時と同じです。

まとめ

rubyのデバッグを始めるのに必要な知識を一通り紹介しましたがいかがだったでしょうか。 普段rubyを使う側の立場だとC言語のデバッグに必要な知識はなかなか身につかないですが、やってみるとrubyの内部の挙動がわかって面白いので是非お試しください。

もう失敗しない!プロジェクト書きなおして、最高の開発環境を手に入れる

$
0
0

ちくしょう、プロジェクトまるごと書き直したい

自分で作り始めたプロジェクトであっても、途中から相乗りしたプロジェクトであっても、誰もが一度は体験する気持ちではないでしょうか。

私が携わっている「おいしい健康」も例外ではありません。

プロジェクトを全て書き直したいと思う一方で、全てのコードを書き換えようとするアプローチは、これまで作ってきた見た目や機能、ビジネスロジックを再現しきれずに潰えてしまう。という話しも良く聞きます。

実は全て書き直したいのではなく、その大きな目的としては以下のようなモノがあるのではないでしょうか。

  • シンプルな作りに変えたい
  • 不要なコードが重なり、動作が遅くなっているところを解消したい
  • 新しいライブラリ、新しい技術を取り込めるようにしたい

今回は、このような目的を達成しつつ、プロジェクトの書き換えを行った私達の話をしたいと思います。

cookpad本体からのプロジェクト分離

cookpad と おいしい健康

「おいしい健康」はクックパッドの新規事業の1つですが、cookpad のコードべースの中に名前空間を分け、コードとしては本体事業と同居して開発を進めていました。

cookpad は他の記事でも紹介されている通り、非常に大きなRailsのプロジェクトです。

参考) The Recipe for the World's Largest Rails Monolith

このコードベースに乗って開発をすることで、社内にある便利な仕組みを手軽に使ったり、デプロイやエラーの監視などを含め、まるっと cookpad のプロダクトに背中を預けることができます。

プロダクトをゼロから作り始める時、事前準備をさほどせずとも作り始められるところがメリットでもあります。

cookpad のコードベースに同居する不都合

作り始めはそれでよかったのですが、だんだんとプロダクトが成長していくとともに、cookpad のコードベースに乗っていることによるデメリットが目立ち始めました。

  • コードをマージしてから、テスト&デプロイまでに時間がかかる
    • 変更したコードは少しでも、CIでは2万以上ものテストが実行される
  • デプロイできる時間が決まっている
    • 新規事業的にはユーザーに影響が起きなければ、どんな時でもすぐに価値を届けたい
  • Rails の起動やspec を動かすのも時間が時間がかかる
    • gem 読み込みやモンキーパッチなどが多数ある

コードベースを分けることだけにフォーカスする

先ほどのデメリットを全て解決するには、新しく書き直し。。と判断してしまいそうですが、あまりに時間がかかりすぎてしまいます。

事業スピードを止めず、かつ、2つ目までの課題をクリアするために、私達は1年半ほど前にコードベースの分離を行いました。

この分離作業では、cookpad のコードベースをforkし、Model/View/Controller, assets, routing に関して「おいしい健康」関連のコードのみを残す。ということを実施しています。

f:id:tanukiti1987:20151209170557p:plain

コードベースを分けただけの作業がメインではありますが、テストやデプロイのフローが、cookpadとは別になったことにより、「おいしい健康」だけのテスト実行およびデプロイが行えるようになりました。

プロジェクト分離での学び

一部課題は残してしまうものの、一番の課題であるデプロイ周りの課題が解決されました。

また、この作業自体は1人のエンジニアが1ヶ月程度で行えたものでした。期間的にも短く、「おいしい健康」に与えた変更も最小限ですみ、大きな混乱もなく、リリースすることができました。

レールに乗ったRailsアプリケーションへ

先ほどまでの話しで、デプロイ周りの課題は解決できたものの、整理しきれなかった多数のライブラリや、モンキーパッチにより、Railsを起動する時間、テストを実行する時間は長いままでした。

cookpad のコードベースを引き継いできたので、巨大なRailsアプリケーションを動かすために、レールから外れているところが目立ってきます。

  • Railsのバージョンをあげるとき、ライブラリやモンキーパッチの依存関係に激しく悩まされる
    • これまでは開発基盤の人たちの作業に乗れていたが、今はそうではない
  • Railsの起動、RSpec の実行が(普通のRailsプロジェクトと比べると)遅い
    • 消しきれなかった使用していないライブラリが多数存在しいてる
    • 高速化の対策もあったが、cookpad本体に当てられるものが(当然)ほとんど
  • 新しいイケてるライブラリを見つけても導入が難しい

これらを解決することによる事業的なメリットは、短期的には見出しにくいものではありますが、長期的に見ると開発効率は格段に向上し、確実にプラスになります。

第一弾の分離作業により時間が経ち、「おいしい健康」のプロダクトをより成長させようとするほど、いよいよレールに乗ったRailsへと書き換える機運が高まってきます。

着手が遅くなればなるほど、書き換えも難しくなり、レガシーを引きずったまま開発することになることは目に見えているからです。

フルスクラッチでは書き直さない

この段階で成し遂げたいことは以下の3つでした。

  • Rail に乗ったRailsプロジェクトにすること
  • 使用していないライブラリの削除
  • モンキーパッチを出来る限り無くすこと

この3つだけを念頭に、作業を進めていきます。

ロジックは基本的に全て移動するだけ

Rails のプロジェクトを作るときに叩く rails newのコマンドを叩き、愚直にModel からロジックを移していき、既存のテストが通るように修正していきます。

f:id:tanukiti1987:20151209170645p:plain

途中、クラスを分割したくなったり、リファクタリングをしたくなったりしても、まずはグッと堪え、ロジックの移動&テストを通過させることに集中します。 あまりにもコードの最適化に脱線してしまうと、最後まで書き換えを完遂せずにプロジェクトが頓挫してしまうことを防ぐためです。

もちろん、時折の息抜きは必要です。 気分転換がてら rspec にて machinist を使っていた所を全て factroy_girl に書き換えたりもしました :-)

新しく生まれ変わるプロジェクトを愛でるキッカケにもなるので、ほんのりと脱線することはオススメでもあります。

使用していないライブラリの削除、モンキーパッチを出来る限り無くすこと

どうしても、保守的に残してしまいがちなモンキーパッチやライブラリですが、まず一度全て消してしまいます。

specを動かしたり、実際にviewを renderさせたときにエラーがでるようであれば、ライブラリを追加していきます。

合わせて、最新のライブラリでも動作確認を行い、極力新しいものを使うよう心掛けます。

モンキーパッチは、積極的に無くす方向に動き、難しければライブラリを探し、それでもダメなら、当てるという方向が良いかと思います。 実際、我々のプロジェクトに対して当てられていたモンキーパッチは古いものが多く、現在では他のライブラリを用いることで代用可能だったのもがほとんどでした。

書き直したプロジェクトを検証する

全てのロジックの移植が終わり、テストもAll Green となれば、いよいよ検証作業に入ります。

いかんせん、ほぼ書き直したプロジェクトなので、テストがGreenでもきちんと動くかは不安です。

今回、私達は kageを使用し、本番に流れてくるリクエストを裏で書き換えたRailsの動いているサーバーにも流すようにしました。

ユーザーには影響を与えないようにし、実際に頻繁にリクエストの来るところを中心に問題がないかを検証していきました。

検証期間としては、1週間程度を設けましたが、ほとんど大きなエラーも出ず、リリースへの決心がつきました。

安全にリリースする

いざ、リリースするときには、問題があったらすぐに巻き戻せるよう、稼働中のサーバーに加え、新しいプロジェクトのサーバーを用意してもらい、ELBで一度にアクセスの向き先を切り替えるようにしました。

実際の作業は事業部担当のエンジニアではなく、インフラ担当のエンジニアの方にやっていただきましたが、スムーズに切り替えが完了しました。

今回は特に問題なく、巻き戻しも行いませんでしたが、今後同じようなことがあれば、この方法をとるのが安心かなと思っています。

小規模にコードベースを書き直しするメリット

冒頭にも述べた通り、ヘルスケアチームでは2度に渡り、コードベースの書き直しを行ってきました。

どれも一度に全てを変えるものではなく、小規模に着実にリリースしていくような手法を取ってきました。

このような方法は事業部側ともメリット/デメリットが握りやすく、期間も短くて済みます。

それは、事業部としてもエンジニアとしてもモチベーションを保ちやすく、また効果も感じやすいものです。

他にも幾つかのメリットが今回の取り組みでわかってきました。

自分のプロジェクトの奥深い闇を理解できる

自分たちが普段触っているプロジェクトがどのように動いているのか、深く理解することができます。

余談ではありますが、 empty?メソッドと blank?メソッドがモンキーパッチにより同じ挙動をとるようになっていたのは、個人的にはすごく驚いたことの1つです。

また、普段は触らないミドルウェアについても触ることができ、今後の開発スピードを一段とアップさせる要因になったかと思っています。

モダンな開発手法に追従する気力がわいてくる

合わせて、プロジェクトの構造を知れれば、ライブラリは何を使うべきか。バージョンアップしてよいものか。も、ある程度察しがついてくるようになります。

Railsの新しいバージョンを試してみよう。 capybara-webkit ではなく poltergeist を使ってみよう。

などなど、フットワーク軽く、様々な技術的トライが行えるようになります。

これらは、チームに活力を与え、メンバー全体の技術力の向上にも役立つものかと思っています。

次回の書き直しも安心!

小さなものでも成功体験が得られれば、次回どういう風に進めればよいか、イメージは湧きやすくなります。

一度、プロジェクトをキレイにしたら、終わりではありません。おそらく1年後にはまた、イケてないポイントを見つけ、プロジェクトを書き直したくなることでしょう。

その時にも、前回、今回のトライは確実に生きてきます。

そうなってくれば、次回以降もコードベースの書き直しにトライすること、確実に成功させることにも価値が見いだせています。

終わりに

今後も定期的に、このような取り組みをやっていくつもりです。

本件の内容にかかわらず、クックパッドやおいしい健康の開発について、気になることなどあれば、今週末開催の RubyKaigiにも出席しておりますので、関口までぜひお声がけください!

今回の記事が皆様のお役に立てることを願っています。

現代のエンジニアのための強力なメモ帳 Jupyter notebookのすゝめ

$
0
0

会員事業部の有賀(id:chezou)です。 今年一年、社内では勝手に"Jupyterの伝道師"を標榜してJupyter notebookの普及活動を展開してきました。 先日、社内でハンズオンも行ったおかげもあり、かなり社内のマシンにPython環境が構築されてきました :)

Jupyter notebookとは?

ひとことで言うとブラウザで動くすごい便利なREPL*1です。 百聞は一見にしかず、見てみましょう。

f:id:chezou:20151211081910g:plain

このように、Rubyの対話環境であるpryを触っているようにインタラクティブにコードを書くことができます。 以降で説明をしますが、Jupyter notebookは記録・共有・再現がとても得意です。特に図表があるときにその効果を発揮します。

Jupyter notebookの良い所

過去のコードを改変、再実行できる

f:id:chezou:20151211111845g:plain

セルと呼ばれる入力部分にはMarkdownやコードが記述できます。ここのコードはShift+Enterで実行可能なのですが、何度も修正して再実行することができます。 パラメータを少しずつ変えて再実行したりすることがとても容易です。 保存をしたければそれをCtrl+Sで保存すれば良いので*2、たんなるconsoleよりもコードと実行結果を後に残すことが容易です。

私も自分の主催する勉強会(kawasaki.rb)のパーフェクトRuby読書会で1年以上使っていますが、いちいちhistoryを記録に残したりする面倒がないのでとても重宝しています。

画面を切り替えずにコードを書きながらグラフの描画もできる

Jupyter notebookでは書いたコードの描画結果を埋め込むことができます。棒グラフ、折れ線グラフ、箱ひげ図など大抵のグラフが描けます。 画像はbase64 encodeされて保存されるので、notebookの中に保存することができます。

グラフ付きのnotebookを簡単に共有できる

保存したnotebookは簡単に共有することができます。 notebook自体はjson形式で保存されるのですが、Githubのレポジトリやgistに置けばグラフなどの画像とともにそのままレンダーされます。 Github Enterpriseをお使いの場合もnbviewerを使えばURLを使ったnotebookの共有ができます。

いろんな言語が実行環境としてある

Jupyter notebookはもともとPython向けのツールIPython notebookとしてスタートしたのですが、version 3.0でカーネルを分離し名前も変わりました。 これにより、各言語のカーネルを導入することでJupyter上でRuby, Julia, R, Sparkなど様々な言語が動きます。*3

SQLのメモ帳としてのJupyter notebook

サービスの改善や新機能をリリースした時には、ダッシュボードをつくる前に、TreasureDataやRedshift、BigQueryに蓄積されたログに対してSQLでアドホックに分析しますよね。

クックパッドの場合、TDとRedshiftを利用しているのですが、以前は以下の様な手順でアドホック分析をしていました。

  1. console/SQL clientでTD/Redshiftにクエリを実行
  2. 取得結果をcsvで保存
  3. Google spreadsheetに貼り付けてグラフ化する
  4. ダメだったら1に戻る
  5. 良いグラフが得られたら共有する

なんどもなんども2と3を往復するのが結構面倒です。

Jupyter notebookを使うと

  1. Jupyter notebookでクエリを実行しグラフを描き試行錯誤する
  2. 良い結果が得られたらnotebookを共有する

というように1ストップでできるようになります。

これは、pandasというライブラリの恩恵がとても大きいです。 pandasは、表形式のデータ構造DataFrameとグラフ描画をシームレスに扱えるライブラリです。*4 R言語でDataFrameが生まれましたが、pandasでより便利に進化しています。

pandas-tdredshift-sqlalchemyを使うと、TDやRedshiftなどの接続も簡単にできます。実験的にBigQueryもサポートされているようです*5

さきほどのアニメーションgifでもお見せした、Redshiftのデータを扱ったものがこちらです。 データの例としてUCI Machine Learning RepositoryからBankデータを利用しています。 結婚しているかどうかという属性ごとの預金を描画した箱ひげ図や、学歴毎による年齢と預金の散布図などが描画されています。

gist.github.com

Jupyter Tips

環境構築

Pythonに慣れていない方は、Minicondaを使って環境構築をするのが簡単なのでおすすめです。*6私はpyenvとminicondaで環境を作るのを好んでいます。 Treasure Data社のブログが導入方法としてわかりやすいです。

慣れている方はお好きな方法で環境構築していただければと思いますが、社内で聞くとPythonに強い人はpyenvとpyenv-virtualenvwrapperを組み合わせている人が多いようです。

Redshift/TDは以下のパッケージを追加すると便利に使えます。BigQueryはpandas自身が実験的にサポートしています。

  • Redshift
    • redshift_sqlalchemy
    • ipython-sql
  • TD
    • pandas-td

なお、pandas-tdはクエリを実行したらWeb consoleのURLと実行状況が出てきてとても便利です。

f:id:chezou:20151211112918p:plain

また、feature requestを送ったら1時間チョットで対応してくれたなど、TreasureData社が手厚くサポートしてくれています。 このリクエストのおかげで、jobの実行結果を後から取ることができるようになりました。

パスワード周り

DBに接続するためのパスワードなどは、環境変数に指定するなどして、notebookに直接埋め込まないようにしましょう。 これは、notebookを共有した時にうっかりパスワードも共有してしまうのを防ぐためです

弊社では、環境変数で管理するためにenvchainを使っています。

notebook用のディレクトリをgitで管理する

個人的なオススメの使い方としては、~/notebooksというディレクトリを作成し、そこでJupyter notebookを起動しgitで管理することです。

こうすることで、自分用のメモを貯めては定期的にrepositoryをpushし、いい結果が見つかればそれを簡単に共有できます。

終わりに

今日はRubyKaigi 1日目ですが、Jupyter notebookはPythonistaの間だけで使われているのはもったいない!と思って紹介しました。 特にpandasはあまりPythonっぽい記法ではなく、Rubyistな方々も是非一度試してみていただければと思います。

Rubyではnyaplotやdaruなどを使えばnotebookでグラフ描画もできますが、pandasに比べるとまだまだ改善の余地は大きそうです。 こうしたグラフ描画周りや行列計算が充実していき、Rubyでも科学技術計算が盛んになることを期待しています。

*1:対話型の実行環境

*2:autosave機能もあるので突然のカーネルパニックにも安心

*3:なお、Jupyterの"Ju"はJuliaのJuです

*4:@lchin曰く「RubyのキラーアプリがRailsならPythonのキラーアプリがpandasだ」

*5:残念ながら、BigQueryとpandasは筆者は使ったことがありません

*6:numpy, scipyなどの導入はハマると結構大変だが、minicondaは一通り面倒を見てくれるので初心者には良い

ディレクションの役割を持つスタッフの活躍を広げる取り組みについて

$
0
0

クックパッド検索・編成部の五十嵐啓人です。本業はレシピなどの料理検索を中心とした、主に「さがすユーザー」のサービス責任と、ユーザー数の拡大に責任を負っています。本日は部門を超えて取り組んでいる、ディレクションの役割を持つスタッフの活躍を広げるための取り組みについて紹介します。

ディレクションの役割を取り巻く当社の状況

日本のインターネットサービス界隈で「プロダクトマネージャ」の話題が盛り上がりを見せつつありますが、当社でもプロダクト開発を牽引・補佐する役割を担当しているスタッフを(名前の議論はありますが)慣習的に「ディレクター」と分類しています。

当社では、以前からエンジニアリングで活躍するスタッフについては、エンジニアマニフェストやエンジニア専用の評価制度作りなどに注力し、組織として期待するエンジニア像の言語化による職種の価値向上、およびキャリア支援を充実させてきました。しかし、エンジニアリング以外の役割については、個人それぞれ様々な活躍はありますが、成長は個人に依存し、組織として期待する像の言語化や、キャリアづくりの支援に踏み出せなかったのが実情です。

「上司のもっとも重要な仕事は、部下の履歴書を豊かにすること」という言葉は、当社代表の穐田が常々口にする言葉です。これを実現するためにも、今年度はじめからクックパッドという組織に必要とされる、ディレクションの役割を担うスタッフについて、社内の成功の類型化や言語化といったことへのチャレンジを始めています。これにより、当該スタッフがキャリアイメージをより具体化できるようにする他、当社のスタッフや会社にとって、何をやっている役割なのかを理解してもらい、今以上に必要な役割として認知されるようになりたいと考えています。

役割の言語化と要件作り

まず着手したのは、「ディレクションを担当するスタッフの会社での役割の言語化」と、「当社で活躍できるディレクターの要件づくり」です。「まず」とはいっても非常に難航したのですが、役割と要件について、社内の活躍事例を参考にしながら、大まかには下記のような項目を抽出しました。

ディレクション担当の会社での役割

サービスを通じユーザーに提供する価値と体験に責任を持ち、企画・進行・ものづくり・宣伝、必要に応じ収益化も含めあらゆる方法でサービスの価値を向上させることに責任を持つ

当社で活躍できるディレクターの主要な要件

今後も状況に応じ常にアップデートされるべきものですが、今期においては仮に以下のように要件を言語化をしました。

(ユーザー理解)サービス提供対象と、プレイヤーの理解

  1. ユーザー理解
  2. サービスを提供するユーザーの姿やイメージが具体的に描けており、だれよりもユーザーの課題に詳しくなっている 2.情報感度
  3. 担当プロダクトの領域について、類似のサービスや業界動向・経済動向の最新情報を把握し、業務に活かすとともに、チームメンバーに展開できている 3.つながり
  4. 同じ領域にチャレンジしている社外人材のネットワークを持ち、公開されている以上の情報を獲得し業務に反映できている

(協調)ビジョンの伝達とチームプレー

  1. 周辺領域の知識
  2. 一緒にプロダクトを作る仲間の業務知識をある程度越境して把握しており、お互いスムーズに業務が進められる
  3. ゴールの言語化と具体化
  4. プロダクトのゴールを言語化・図示化して、チームや社内メンバーに正しく理解し、巻き込むことができる。また現状解決すべき具体的な課題に落とし込める

(技術力)成功に導くためのテクニック

  1. エンジニアリング以外の技術力
  2. プロダクトを成功させるために必要な様々な高い技術力(プロジェクト管理、IA、ユーザー分析、アクセス分析、マーケティング、オペレーション等)を持ち、業務に適用できている

(推進力)プロダクトのハンドリング力

  1. ハンドリング力
  2. ハンドリングできるプロダクトの範囲が広がり、プロダクトそのものの価値変革や、改善が行える
  3. 進行能力
  4. スケジュールを切り、だらだらと考え続けるのではなく動くもので仮説検証をすすめ、プロダクト開発を前へすすめることができている

(共有) 経験のアウトプットと展開

  1. 情報発信力とノウハウ化
  2. 社内外でのMTGや講演やブログ等により、自社および自分の試したチャレンジに対する情報を発信し、周囲の行動に影響を及ぼしている。また、自らの成功事例を他人が実践可能なメソッドに落とし込めている

各項目については当社の事情や背景など様々な思惑があり、もっと丁寧にお話したいのですが、特に個人的にこだわったものとして、「技術の獲得」と「アウトプット」があります。

「技術」というとエンジニアが注目されがちですが、サービスを成功させユーザーの生活を変えるには、エンジニアリング以外にも分析、進行、ユーザーの欲求抽出など様々な高度な技術を磨き、適用することが不可欠です。この「技術」を磨くには、勉強によるインプットを継続的に行い、業務を通じ技術を利用し、自分の技術の改善をすることが必要になりますが、さらには特定の案件や業務でしか通用しないローカルナレッジだけでなく、他のスタッフが参照したくなる何がしかの「専門家」というポジションを獲得することで、さらに高い経験が積める高度な仕事を呼び寄せることが非常に重要だと考えています。

エンジニアリングでも、今でこそ当社は社外から一定の評価をいただいていますが、当社が小さい頃は、不得意なことでも突っ込んでいって、効率は悪いが時間を掛けてでもやりたいことを実現せざるをえませんでした(デザイナーが課金システムを作ったりといったことも、その最たる例でしょう)。ですが、現在当社のエンジニアリングでは、各領域の様々なプロフェッショナルが集った結果、何事を進めるにも、だいたい社内に専門家がおり、ものごとが非常にスピーディーにかつ、レベルの高い状態で進みます。さらに、その専門性ゆえ社外でも、自分の可能性を広げる様々な機会を獲得できています。

今回のディレクター指標の検討にあたっても、そのような横にあるエンジニアの事例を強く意識しながら、言語化をすすめました。

運用にあたって

実際の運用にあたっては、各「要件項目」に対し、点数評価を「自己診断」として行い、それを基に「少し先のキャリアについて上司と話す面談の材料」という形で、テスト導入を一部部門で行いました。人事評価と結合する方法も考えられましたが、特に経験の少ない若いスタッフが、キャリアパスをイメージできるようにして成長イメージを描けるようにすることが一番の目的ですので、「まずは小さくテストで評価をしてみよう」というプロダクト開発のようなノリです。

また、一般的にディレクターは職種として、企画職などといった立場で、独立した職種として扱われることも多いと思います。しかし当社では、伝統的にディレクションは特定の職種固有の専権ではないという考え方がありますので、今回は「職種」ではなく、「役割」という概念を取り入れて、例えば「エンジニアリング」や、「編集」などと「ディレクション」の役割を兼任し活躍するスタッフも、ひとつの理想像としてスタッフの多様な才能を評価できるようにしたいと考えています。

今後は、今回ご紹介した要件のブラッシュアップに加え、各要件項目として掲げたことを伸ばす機会づくりを拡大したいと考えています。いくつか小さな取り組みは動き始めていますが、そのひとつとして、今年の秋より、こちらの技術ブログをお借りしてディレクション系の役割を担う有志メンバーのブログポストを始めさせていただきました。これら様々な方法を通じ、各スタッフが社内外に向け自分の強みを発信し、専門性の確立に繋げたいと考えています。

※下記の項目について、ぜひ、取り組まれている同じ分野で気になる内容があれば、情報交換など弊社オフィスに遊びに来てください

ディレクション系の役割を中心に担うスタッフによる最近の当ブログ投稿

また、その他来年度からも様々な取り組みを増やしたいと考えていますので、機会があれば、こちらでご報告できれば良いなと思っています。

こんなディレクター達と一緒に働いてくれるエンジニアの方をお待ちしています。

本題から脱線しますが、当検索・編成部では、日本ひいては世界最高の料理のための検索エンジンを作り、それを生かしたプロダクト作りを進めています。ちょっとした改善が日本の家庭で食べられるものを豊かにできるとてもやりがいがあり、とても楽しい仕事です。

こんなディレクターの方々と一緒に、ユーザー価値について議論しながら、働いてくれる一エンジニアの方を絶賛募集中です。ぜひ、気軽にお話にいらしてください。

モダンJavaScript開発環境 on Rails

$
0
0

投稿推進部の外村(@hokaccha)です。

クックパッドブログの開発でRails上にECMAScript6などのモダンなJavaScript開発環境を導入した経験を元にノウハウを紹介したいと思います。

RailsはSprocketsというgemでJavaScriptやCSSをコンパイルする仕組みが提供されています。Sprocketsによるasset管理の仕組みは非常によくできており、AltJSのトランスパイルやファイルの結合、minifyなど、assetのコンパイルに必要な機能を一通り備えています。

しかし、JavaScriptにおけるモジュールの依存関係の解決や、ライブラリの管理などについてはモダンなJavaScript開発と乖離してきているのが現状です。そこで、Railsでも以下のようなことを実現できることを目標に環境を作りました。

  • ECMAScript6のシンタックスを使う
  • モジュールの依存解決にCommonJS Modules(もしくはES6 Modules)を使う
  • フロントエンドのライブラリ管理にnpmを使う

これらを実現するためにフロントエンドの開発ではスタンダードになってきているbrowserifyBabelといったツールを使うことにしました。これらのツールをRails上でどのように利用するかというのが今回の話です。

gulpを使う

まずひとつめの方法として考えられるのは、Railsのassetのコンパイルの仕組みを完全に捨て、フロントエンドの開発でよく使われているgulpなどのタスクランナーを利用し、その中でbrowserifyやその他のプラグインを使ってassetのビルドをおこなう方法です。

この方法はRailsのレールから完全に外れるため自由度が高いという利点がある一方で、環境構築にかかるコストがそれなりに高いというのが欠点です。

RailsのアプリケーションはAPIだけを提供し、サーバーサイドとクライアントサイドを完全に分けて開発できるケースや、フロントエンドに詳しいエンジニアがいて常に最新の環境に追従できる場合などはこの手法を取るメリットがあると思います。

一方で、ある程度Railsのassetのビルドの仕組みを利用しつつJavaScriptのビルドだけbrowserifyを利用したいという場合には少々オーバースペックです。CSS(Sass)のコンパイルや、デプロイ時のminify、キャッシュ対策のdigest値の付与など、Railsの機能でまかなえる部分はRailsに乗ってしまったほうが楽です。

また、そこまで多くありませんが、JavaScriptのライブラリがgemとしてしか提供されていないもの*1もあるため、そのようなライブラリを読み込む際に既存のSprocketsの//= requireを併用したいというケースもあります。

gulpでコンパイルしたものをRailsから読み込むためのgulp-rev-rails-manifestや、gulpでSprocketsと同様の機能を提供するgulp-sprocketsなどを利用してRailsと併用するという手段もありますが、今回はできるだけRailsに寄せて環境を作りたかったため、こういった手法の採用は見送りました。

browserifyを使う

そこで今回はまず、browserifyを直接利用し、それ以外はRailsに任せるという方法を採用しました。まず、次のようにbrowserifyで対象のファイルをビルドし、成果物をapp/assets/javascripts以下に出力します。

$ browserify -t babelify app/assets/javascripts/src/main.js -o app/assets/javascripts/bundle.js

babelifyというのはbrowserifyのビルド過程でBabelによる変換を行ってくれるbrowserifyのプラグイン*2です。

このとき、元のソースファイル(ここではmain.js)はapp/assets/javascripts以下でなくてもどこにあっても構いません。重要なのはbundle.jsをSprocketsのload path以下に出力することです。そうすることでapplication.js等から以下のように生成したファイルを呼び出せます。

//= require bundle.js

こうすることで、ECMAScript6へのトランスパイルやモジュールの依存関係の解決などはbrowserifyに任せつつ、Sprocketsとの併用ができますし、デプロイ時もassets:precompileの前にbrowserifyのビルドコマンドを実行するだけで済みます。

実際の開発時にはwatchifyという対象のJavaScriptファイルの変更を監視して、変更があったときにbrowserifyの差分ビルドを行ってくれるツールを利用していました。環境やコード量にもよりますが、browserifyは単体で実行すると10秒近くビルドに時間がかかる場合もありますが、watchifyの差分ビルドだと1秒弱ぐらいまで高速化されます。

この方法はある程度うまくいっていたのですが、JavaScriptのファイルを変更してすぐにブラウザをリロードしたときに、ビルド途中の中途半端な状態でSprocketsのほうにキャッシュにされ、再度JavaScriptのファイルを何かしら更新してビルドし直さないとバグったままになってしまうという現象に悩まされました。そんなに頻繁に発生するわけではないのですが、一日開発していたら数回は遭遇するのでけっこうなストレスです*3

また、ビルドされたファイルはバージョン管理システムの管理には含めないので、JavaScriptの開発を行わないエンジニアやデザイナがアプリケーションの開発を行うとき、手元でビルドプロセスを走らせる必要があります。そこまで大きな問題ではないですが、できればこれまで通りrails serverを立ち上げるだけで開発できるようにしたほうがよいと考え、次で説明するbrowserify-railsを導入することにしました。

browserify-railsを使う

browserify-railsはその名の通り、Railsでbrowserifyを使うためのgemです。現状はひとまずこの方法に落ち着いています。

https://github.com/browserify-rails/browserify-rails

Sprocketsのプラグインになっており、Sprocketsのビルドプロセスの中でbrowserifyが実行されます(正確にはSprocketsのPost Proceccerとして動作します)。

つまり、ブラウザのリロードをしたタイミングでbrowserifyのビルドが走るためrails server以外に別プロセスを起動するといったことが不要になりますし、ビルドのタイムラグでタイミングによってはJavaScriptが更新されないという問題もなくなります。また、当然Sprocketsと併用でき、Railsが提供しているassetの仕組みをそのまま使うことができます。

browserify-railsの最大の問題点はビルドの速度です。browserify-incrementalというツールを使うため、browserifyをそのまま使うよりは多少速いですが、watchifyほどの速度はでません。JavaScriptを更新してブラウザをリロードすると数秒待ち時間が発生します。

browserify-railsのドキュメントにあるように、browserifyのMultiple bundlesを使ってサイズが大きいライブラリ(React.jsなど)を別ファイルにするという方法もありますが、それでもこれまで通りのレスポンス速度で開発できるほどは速くなりません。(JavaScriptに変更がない場合はキャッシュが効いて即レスポンスが返るのでJavaScriptを変更しない場合の開発の速度には関係ありません)

逆に、速度以外で困ることはほとんどなく、Railsのレールをできるだけ外れずにモダンなJavaScript開発環境が作れるので、browserify-railsのような仕組みを高速化していくというのは一つの方向性としてはよいのではないかと個人的には思っています。

以下に最小限の構成を作ったので興味がある方は試してみてください。

https://github.com/hokaccha/browserify-rails-example

まとめ

RailsでECMAScript6やnpmなどのモダンなJavaScript環境を整えるためのノウハウについて書きました。

フロントエンドの開発環境まわりはまだまだ過渡期なので、これという決定的な方法が確立されているわけではありません。今回紹介したような方法をはじめ、いくつかの選択肢があるのでプロジェクトの規模や好みに合った方法を探してみてください。

*1:Turbolinksなど

*2:正確にはtransform

*3:livereloadのような仕組みをいれることによって解決できる可能性もあるかもしれません


ウィンドウ関数で作るデータプロダクト

$
0
0

検索編成部の兼山です。

クックパッドではウィンドウ関数をデータプロダクトの開発に活用しています。 ウィンドウ関数を利用すると、行動ログを利用したプロダクトの開発や評価が簡単に始められます。 今回は「関連キーワード」を例にとりウィンドウ関数がどのように活用できるのかを紹介します。

関連キーワードとは

朝ごはんのレシピ 52750品 [クックパッド] 簡単おいしいみんなのレシピが224万品

検索画面に検索を補助するための関連キーワードが提示されています。

「朝ごはん」の検索画面の関連キーワード:

f:id:code46:20151218150853p:plain

レシピを検索していると、自分の欲しいレシピが簡単に見つからない時があります。

理由は様々ですが一例として以下の様な状況があると思います。

  • 「入力するのがめんどくさい」
  • 「さがしたいものがあまりイメージできていない」
  • 「それをさがす適切な検索語が思いつかない」

関連キーワードはこうした状況で、以下の情報をユーザーに提供します。

「朝ごはん」と検索した人は、
「朝ごはん 和食」「ホットケーキ」「フレンチトースト」も検索しています

ユーザーはみんなの朝食アイデアの共通解を知ることで、 入力の手間が省けたり、新しいアイデアを得ることができるのではないでしょうか。

ウィンドウ関数で実装してみよう

早速実装してみましょう。ウィンドウ関数を使うとシンプルに書けます。

必要なもの:

  • 以下のフィールドを持つアクセスログ
    • time(アクセス時刻)
    • unique_id(アクセスしたユーザーを一意に識別するID)
    • keyword(検索語)
    • controller, action(アクセスログから検索ログに絞り込むために利用。railsのcontroller, actionを想定しています)
  • Presto(Redshift, PostgreSQL、Hiveなども可)

実装

with query_transitions as (
select
  time,
  unique_id,
  keyword as origin_query,
  lead(keyword) over (partition by unique_id orderby time) as next_query -- ここがウィンドウ関数です!from
  access_log
where
  time >= 'yyyy-mm-dd'and time < 'yyyy-mm-dd'and controller = 'search'and action = 'show'orderby
  unique_id, time
)
select
  origin_query,
  next_query,
  count(distinct unique_id) as cnt
from
  query_transitions
where
  origin_query != ''and next_query != ''and origin_query != next_query
groupby
  origin_query, next_query
orderby
  cnt desc

実装完了です!

これを実行すると以下の様な結果が得られます。

f:id:code46:20151218150926p:plain

クックパッドではもっぱらTreasureDataかRedshiftからログを参照するため、 データのスケールに悩まされることも稀です。

ウィンドウ関数で遷移を整理

このSQLは何をしているのでしょうか? ウィンドウ関数が仕事をしているのは最初のwithで定義されているquery_transitionsの定義の部分です。 以下の部分に注目してください。

select
  time,
  unique_id,
  keyword as origin_query,
  lead(keyword) over (partition by unique_id orderby time) as next_query -- 次の行のkeywordが入る

lead()はウィンドウ関数のひとつです。5.13. Window Functions — Presto 0.130 Documentation

unique_idごとにグループ分けした上で、現在参照している行の「次の行」から任意のフィールドの値を取ってくることができます。

time unique_id keyword lead(keyword)
time id1 朝ごはん 朝ごはん トースト
time id1 朝ごはん トースト フレンチトースト
time id1 フレンチトースト N/A
time id2 デビルドエッグ デビルドエッグ 簡単
time id2 デビルドエッグ 簡単 N/A

参照している行に対して、lead()のカッコの中だけ時間が進む(リードする)と考えると分かりやすいかと思います。 ウィンドウ関数で「検索語」と「次の検索語」が1行に集められたならあとは簡単なSQLで集計するだけです。 注意点としては、次の行が見つからない時はブランクになるのでそういった行は今回は無視しています。

より詳細な解説は以下が参考になります。 - Sessionization in SQL, Hive, Pig and Python - Dataiku - PostgreSQLとHiveにおけるウィンドウ関数、セッション化について解説しています。 - 10年戦えるデータ分析入門 - 青木峰郎 著 - 8,10章が該当します - Window関数 カテゴリーの記事一覧 - トレジャーデータ(Treasure Data)公式ブログ

データプロダクトを改善する

シンプルに始めるという観点からすると上々です。 *1しかし実際には改善の余地があります。例えば、

関連のないキーワードを提案してしまう(例:「夜食→カレー」)

  • 【仮説1】 「夜食」を検索した翌日、「カレー」と検索されたログを拾っている

    • 対策1: セッション化して30分でタイムアウトしてみましょう。別々の検索セッションとして扱うことができます。
  • 【仮説2】 右往左往している人のセッションを拾っている

    • 対策2: 有用なセッションのみを利用して実行してみましょう。「セッションの選別」もウィンドウ関数を利用してSQLだけで行える場合があります。

これらはSQLとウィンドウ関数だけで実現できます。 SQLにこだわることはないですが、データを一箇所に集め、 SQLで試行錯誤できることを増やしておくとプロトタイピングが容易になります。

この記事では詳しくは触れませんが、 セッション化はユーザーの行動ログを「ひとつの目的をもったまとまり」として分割できます。 さらに、セッションの開始と終端を解釈することでセッションの選別も可能になります。 関連キーワードでは多用される手法で、論文も多数ありますので、興味のある方は探して読んでみてください。 ユーザーに公開するレベルにしようと思うとセッション化は効果的です。

まとめ

今回はウィンドウ関数を利用して関連キーワードを実装してみました。 「関連キーワード」「関連商品」「関連ユーザー」など ユーザーの遷移を整理することで実現できるものは多くあります。

「ウィンドウ関数・データプロダクト面白いなぁ」と思った方、「そんなの常識!もっとこうしたほうがいい!」と思った方、こちらまでよろしくお願いします。

クックパッドにはあなたを待っている課題とデータがいっぱいです!


*1:- Talk Summary: Building Great Data Products · Coding VC (日本語訳)

どんなことをやるにしてもこの記事に示されている教訓を体験することになると思います。毎朝読み返したいです。

サービスの改善を、最も小さく、最も高速に行うために

$
0
0

買物情報事業部の根岸です。寒いですか。僕は今名前がわからない簡易暖房みたいなものの前にいるのであったかいです。今日は、僕がサービス開発エンジニアとして行っているサービス改善プロセスの一部についてお話させて下さい。

サービスの価値を高めるための改善は、より少ない人数で、より速く行うことが重要になります。これは、意思決定を最小限必要な人数以上で行うとコミュニケーションコストが高くなること、また、意思決定は一定の確率で失敗するものなので、イテレーションの速度を早くすると結果的に全体の価値を高めることなどが理由です。

それでは、最小の改善とは、最速の改善とは一体どのようなものなのでしょうか。

最小の改善が行われる単位

最小の改善が行われる単位は、サービスの開発を行っている個人です。個人が、改善の対象となっているサービスの課題を発見して解決へと導くことが最小の改善になります。

リリースしたサービスでメトリクスを取得することでもなく、限定リリースを行ってユーザの動向を細かくチェックすることでもなく、社外のユーザーを呼んだユーザビリティテストでもなく、社内にいるモデルユーザーへのヒアリングでもなく、チームでコミュニケーションを取って改善策を決めることでもありません。

改善が行われるプロセスは実装でも、サービス設計でも起こりえます。クックパッドのふつうのサービス開発エンジニアがサービスの改善をしようとしているとき、改善の対象は実装だったりペーパーモックだったり価値仮説だったりします。サービス開発エンジニアは、仕様に沿った実装だけを行う職種ではありません。

最小の改善単位に対する最速の改善

それでは、最小の改善単位を最速で回すために何が必要なのでしょうか。書き下すと、下記のような内容だと考えています。

  • サービスのユーザーを理解する
  • サービスのユーザーなど、価値モデルを自分の機能的人格の一部とする
  • 価値モデルとしてのユーザーのために、コミュニケーションコストを最小化する

順に説明していきます。

サービスのユーザーを理解する

サービスのユーザーにとって価値が高い状態を作るためには、サービスのユーザーが何者なのかを深く理解する必要があります。この観点ではすでにこのブログでもいくつかエントリーが書かれています。

買物情報事業部が提供している特売情報はB2B2Cなプラットフォームサービスなので、ユーザーには買い物をするお客さんと、特売情報を入稿するスーパーの店員さんがいます。サービス開発エンジニアは買い物客になることはできますが、サービス提供者としては本質的にスーパーの店員さんにはなれないので、普通に考えるとスーパーの店員さんに対してはインタビューだけでユーザー理解を進めなければなりません。一方で買物情報事業部では、所属しているメンバーは全員、スーパーの業務を丸一日お手伝いしつつ、投稿作業を行うということを少なくとも一回はやっています。

そこら辺歩きまわっていらっしゃいませいらっしゃいませと言っていると、今日の焼き芋は本当に大きいわねとお客さんが世間話を仰ります。本当にそうですね、おいしそうな匂いですね〜。と返答して温かい気分になります。しかしながら商品の撮影のために手に持っているスマホを、みんなが見ている気がします。確かに店員さんが携帯片手にフラフラしてたらなんだこいつと思いますよね。冷や汗を書きながら写真を撮影したところで、あ、こないだのインタビューで投稿する店員さんが"特売情報投稿中"っていう腕章をつけている店舗があってすごいな〜と思ったけど、あれの役割のひとつはお客さんに対するエクスキューズなのかもなと唐突に思い当たります。発明の価値の大きさに感無量です。バックオフィスでIE8に向かい合って、撮影した写真を入稿しようとすると、ホコリ取りカバーが付いているディスプレイでは、淡く調整された投稿画面のインターフェース要素のコントラストが低すぎて視認しにくいことに気づきます。とにかくつらいです。入稿を終えてアルコール飲料の品出しに回ると、僕のことをクックパッドの社員だと知っている店員さんが話しかけてくれます。「クックパッドに投稿始めてからベビーカーを持った若いお客様が増えて、通路広げるの検討しようって言ってます」 えっ、本当ですか、すごいなあ、嬉しいなあ。確かに、午前中なのに若い人が多いですね。

とにかく自分がユーザーの立場になると、サービスを利用している情景が自分の中にある状態となります。インタビューが不要なわけではなく、両者が補完しあって精緻なユーザー理解に繋がると思います。

サービスのユーザーなど、価値モデルを自分の機能的人格の一部とする

この、精緻な理解に基づいたユーザーが、自分の人格の一部となることがとにかく重要になります。出来上がった人格を個人の中で価値モデルとして取り扱い、壁当て、サービス価値の向上に使えるからです。 実装者・サービス提供者である自分を一旦忘れて、何らドメイン知識も持たない状態でインターフェースを操作して、何が起きるか理解できるか、期待した動作をしているか、価値を感じるか、結果として仮説が正しいかを検証していきます。

さて、ユーザーのことばかり話していましたが、組織に所属するサービス開発エンジニアとしては、サービスを改善する上で様々な観点があると思います。

  • 1) この仕事に従事していて良いか
  • 2) サービスのユーザーにとって価値が高いか
  • 3) 組織の目的に沿っているか
  • 4) 実装可能か
  • 5) 新しく配属されたメンバーの教育に沿っているか
  • 6) SEOの戦略に沿っているか

みたいな感じです。(1)が個人的意思決定で、残りが組織的意思決定の観点になると思います。 (2)のユーザーにとっての価値モデルを先ほど機能的人格の一部として分離したことで、他の観点も全て人格として分離することが可能となります。

  • 1) 労働者としての自分自身
  • 2) サービスのユーザー
  • 3) 組織のリーダー
  • 4) エンジニア(デバッグ用のクマ)
  • 5) メンター
  • 6) SEO推進者

これらの人格が頭の中でワイワイ合議するということが、僕が捉える最小かつ最速のサービス改善の実体です。

価値モデルとしてのユーザーのために、コミュニケーションコストを最小化する

しかしながら、現実の世界と同じく、払うことができる検討のコミュニケーションコストと言うのは有限なので、6人でフルパワーで合議を行うとかならず混乱が起きます(価値仮説から想定されるユーザの検索クエリとmeta descriptionの内容が乖離してるクマ〜)。

ここで、チームメンバー間でコミュニケーションをする上で、要点をまとめるためのフォーマットの存在が重要であるように、ある程度定型化された意思決定プロセスに落としこむことが省力化のために重要になります。たとえば僕は、SEO推進者のためのチェックリストを持っています。

こういった省力化によって、最も重要な人格であるサービスのユーザーの人格が、ニコニコいかにこのサービスが理解不能かを話しているのを、みんなが黙って聞くという状況を作ることができます。

そして、結果として改善のスピードが最速になります。

おわりに

最小の単位で最速の改善を実行して、サービスの価値に確信を持ったら、チームメンバーに見せに行きましょう。リリースしましょう。そして、自分の確信が間違っていたことをすぐに知るでしょう。これは大抵の場合で、自分が作り上げたサービスのユーザーとしての人格、価値モデルが間違っているからです。しかしそれでも、価値に確信を持っていないものをリリースしてもよいサービスは生まれないでしょう。検証を経て、価値モデルを修正し、またさらに改善をし続けることが重要なのです。

徹底して最小、最速の改善が行えるようにしましょう。そのために、個人で良い意思決定ができるようになりましょう。最良の価値は、最も速く改善を繰り返したサービスから産まれるからです。

モバイルアプリのスレッドプールサイズの最適化(画像読み込み編)

$
0
0

クックパッドの海外向けのAndroidアプリを開発している @rejasupotaroです。海外チームでは英語圏だけでなく、スペイン語圏やアラビア語圏や、その他いろいろな地域・ユーザーの環境に合わせてサービスをローカライズしながら展開しています。

東南アジアや南米では日本に比べるとネットワークは不安定で遅く、現地に行って自分たちのサービスを使うと読み込みの遅さに愕然とすることがあります。レシピサービスにとって画像の読み込みの速度は重要なので、これまでもレイテンシ、フォーマット、圧縮率、キャッシュ、画像サイズ、リクエストの優先度、プリロードなどさまざまな最適化を試みてきました。今回はスレッドプールのサイズについて考察しました。

非同期処理とスレッドプール

Androidには、UIを操作することができる唯一のメインスレッドと、APIや画像のリクエスト、DBの読み書きなどの時間のかかる処理でメインスレッドをブロックしないためのワーカースレッド(ユーザーから見えない裏側で実行されることからバッググラウンドスレッドとも呼ばれる)の、2種類のスレッドがあります。

Androidではメインスレッドとワーカースレッドの処理のやり取りする方法はいろいろありますが、その一つにExecutorフレームワークがあります。Executorは内部にキューを持ち、Runnableを実装したタスクをexecuteメソッドで実行します。図にすると下のような感じです。

メインスレッドでリクエストを生成して、ワーカースレッドでタスクを処理するようなデザインパターンを、プロデューサー・コンシューマーパターンと呼びます。このコンシューマーすなわちワーカースレッドはタスクが要求されるたびに生成することもできますし、スレッドプールを使って再利用することもできます。待機させておくスレッド数や、最大のスレッド数などは実装に依存します。

Picassoのスレッドプール

私のプロジェクトでは画像の読み込みに Picassoを使っています。Picassoは内部にFixedThreadPoolを持っていますが、ネットワークの接続状況に応じて自動的にプールサイズを調整するようになっており、以下のように変動します。

通信規格 スレッドプールサイズ
WiFi, WIMAX, Ethernet 4
LTE, HSPAP, EHRPD 3
UMTS, CDMA, EVDO 2
GPRS, EDGE 1
該当なし 3

しかし、いくつかの理由からこの値は最適でないのではないかと思っていました。

通信規格と速度は必ずしも一致しない

Facebook Launches 3 New Open-Source Tools For Android Developers | TechCrunch

通信規格というのは実際の通信速度を示すものではありません。日本ではLTEよりWiFiの方が速いですが、ブラジルでは逆にWiFiの方が遅かったりします。

I/Oの待ち時間が長いほどスレッド数を増やした方が効率が良さそう

"画像読み込み"という処理を分解すると、

  • 画像のリクエストをタスクにしてキューに送る
  • ワーカーがキューからタスクを取り出して画像のリクエストをサーバー送る
  • ストリームをデコードしてBitmapに変換する
  • メモリとファイルにキャッシュとして書き込む
  • ワーカーからメインスレッドにBitmapを渡して描画する

このように多くの時間がI/O待ちで占められています。なので、ネットワークが遅いほどスレッドの数を増やした方が良さそうに思います。

検証環境

今回は東南アジアや南米を想定して下り500kbps前後の環境に最適化します。Wikipediaによると EDGEの最大スループットは473.6kbpsらしく、ちょうどユーザーの環境に近いのでエミュレーターのネットワークスピードとレイテンシをEDGEに設定して検証しています。

画像のフォーマットはWEBPで品質は70、実際の表示サイズをパラメーターに与えてリクエストしていて、大きくても100KB以下になっています。

ワーカースレッド数と待ち時間

画像のリクエストの到着がポアソン分布に従い、処理時間が指数分布に従い、ワーカースレッドがc個からなるM/M/cキューと見なした場合、待ち行列理論が適用できるので、平均到着率・平均サービス率を調べることでワーカーの利用率、システムの内のタスクの数、システムの平均待ち時間を算出することができます。

平均到着率はアプリの使い方によって変わるので正確に求めることは難しいのですが、ワーカースレッド数を1に固定した状態で、何も考えずに適当に検索したりしながら測定したら、以下のようになりました。

  • 平均到着時間: 0.24 sec =>到着率: 4.166
  • 平均サービス時間: 0.75 sec =>サービス率: 1.333

0.24秒ごとにリクエストが来ていたので、逆数をとると1秒間に4.16回のリクエストが来ていることになります。タスクの処理時間は0.75秒だったので、1秒間に1.3個のタスクをさばくことができることが分かります。到着率とサービス率から、ワーカーの利用率は以下の式で求めることができます。

ρ (利用率) = λ (到着率) / cμ (ワーカー数 * サービス率)

利用率は3.125になり、1を超えているということはリクエストを消化できずに、キューにタスクがどんどんたまり続けている = ユーザーが画像の表示を待つために操作が中断される状態になるということが分かりました。ワーカー数を増やした場合に、ワーカーの利用率やキューの長さやキューの滞在時間がどうなるかを表にしました。

c (Number of workers) ρ (Worker utilization) L (Average tasks in system) W (Average time spent in system)
4 0.7814 5.1401 1.2336
5 0.6251 3.5734 0.8576
6 0.521 3.2521 0.7805
7 0.4465 3.1625 0.759
8 0.3907 3.1362 0.7527
9 0.3473 3.1285 0.7509
10 0.3126 3.1264 0.7504

M/M/cキューの計算式はちょっとゴツいので Queueing theory models calculatorで計算しました。

ワーカー数が3以下だと利用率が1を超えてしまうためキューの長さや待ち時間を計算することができません。またこの表からスレッド数を7より増やしてもほとんど効果がないと言うことが分かります。 待ち行列理論は、コンピューターに限らず一般的な事象に対しての式なので、今回のケースでそのまま適用可能かというのを少し考えみましょう。

待ち行列とスレッドプール

ワーカーの利用率ρを計算しましたが、これはあくまでもワーカーがタスクを持っているというだけで、CPUの利用率とはまた別になります。コンピューターはI/Oなどでコンテキストスイッチが発生するため、ワーカーがタスクを持っていてもCPUを使っているとは限りません。

待ち行列理論はよくレジとレジに並ぶ客に例えられますが、今回のケースではレストランのウェイターと客の関係に近く、注文を取ったあとウェイターは料理が出てくるまでの間に厨房の前でぼーっとしているのではなく他の客の注文が取れそう、ということです。

調べてみたところ、スレッドプールサイズの最適化にはいくつかの式が提案されていることが分かりました。 たとえば Calculate the Optimum Number of Threadsよると、I/Oバウンドな処理は以下の式に当てはめます。

threads = number of cores * (1 + wait time / service time)

この式の wait time / service timeは、タスクに対するCPUが遊んでいる時間と計算している時間の比率になります。ExecutorとDownloaderにフックして測定したところ、平均待ち時間は756msで平均応答時間は1082msなりました。

threads = number of cores * (1 + wait time / service time)
        = number of cores * (1 + 756 / (1082 - 756))
        = number of cores * 3.32

上記の式から係数は3.32となり、CPUコア数にI/O待ちが長くなるほど大きくなる係数がかかっているので感覚的には良さそうに見えます。私たちのサービスのユーザーに多い2コアの端末では6.64スレッド、4コアの端末では13.28スレッドという値になりました。待ち行列理論の式と照らし合わせてみても係数3は妥当な値に見えます。

13スレッド…多すぎない?

画像読み込みではPicassoの他には Glideというライブラリがあり、人気を二分していますが、GlideのスレッドプールサイズはCPUのコア数に等しくなるように設定されています。 ということでPicassoもGlideもだいたい2〜4スレッドになっていることが大半ということで、上記の計算結果の13スレッドはもしかしたら多いのかもしれません。

スレッドプールサイズが大きすぎる場合、同期のためのロックでのリソース消費であったり、スレッドはいるだけで数K〜Mのメモリを消費してしまうので、あまり大きな値を設定するのは避けたいです。

また、アプリでのスレッドプールサイズの最適化はリソースを使い切ることが目的ではなく、ユーザーが快適にアプリを使えるようにするのが目的なので、リソースをかつかつに使ってしまうとその他の動作に影響を及ぼす可能性があります。そのため、もう少し詳しくこの問題を見ることにしました。

ユースケースを考えてみる

話は変わりますが、出張の際には現地のユーザーさんに声を掛けてユーザーテストをさせてもらうことがあります。ユーザーテストは「今日のメニューを決めてください」のようなシナリオをやってもらいますが、ユーザーは画像の読み込みを待ってから次のページを見る、というような使い方をしていたことを思い出して、実際自分もそうだなと思いました。

こんな感じに計測してたけど実際のユーザーは検索していきなり全力で下までスクロールしたりしない!

つまり、画面内に表示される画像の数よりワーカースレッド数を多くしても無駄になってしまう可能性が高いのではないかと思いました。私たちのアプリだと一画面に表示される画像の数は6〜8個(解像度に依存)くらいなので、ワーカー数の最大値は8が適当でしょう。

まとめ

以上のことから私たちのアプリでは CPUコア数 * 38の小さい方を取ると最適になりそうという結果になりました。

Math.min(Runtime.getRuntime().availableProcessors() * 3, MAX_NUMBER_OF_IMAGES_IN_SCREEN)

たとえば、2コアの端末で3G回線のユーザーのデフォルトのワーカースレッド数は3だったのが、この式では6スレッドになるので、厳密にどれくらい改善するかは数字にしづらい(スレッド数によってユーザーの行動が変わり、到着率が変わり、計算結果が変わる)のですが、もしキッチリ画像の表示を待った場合に比べると、画像の待ち時間が半分になり、レシピが2倍早く決まる!(可能性がある)ということになります。

今回の検証ではこのような結果になりましたが、どういうサービスでどこに最適化するかによって結果は変わってくると思います。

たとえば、つぶやきサービスで「ラーメンの様子です」というつぶやきと共に貼られたラーメンの画像は重要度はそこまで高くないので、貴重なリソースは画像読み込みのワーカースレッドより他に回した方がいいと思いますし、レストラン検索サービスでリソースをケチって1スレッドにすると、デートの店選びに使いたいのに、忘年会の幹事で早く店を決めないといけないのに画像の表示が遅くてイライラするなど、そういうこともありそうですね。

ESDoc - The Good Documentation For JavaScript

$
0
0

こんにちは。会員事業部の丸山@h13i32maruです*1

ソフトウェアのドキュメント(マニュアル)を書くには色々なツールや方法があります。 JavaScriptの場合はJSDocというドキュメンテーションツールがデファクトスタンダードです。 ですが、JavaScriptの最新仕様であるECMAScript2015(ES2015)がリリースされたことにより、 JSDocの競合として新しいツールが登場したり、JSDoc自体もバージョンアップしたりしています。 本記事ではそうした新しいツールの一つであるESDocを紹介します。

ESDocとは?

ESDocとは私が2015年4月から開発を行っている、JavaScript(ES2015)向けの良いドキュメントを作るためのドキュメンテーションツールです。

最近ではReactiveX/RxJSLinkedIn/hopscotchLonlyplanet/rizzo-nextなどのプロダクトに採用され始めています。もちろんESDoc自身もESDocでドキュメントが書かれてています。

f:id:h13i32maru:20151218113731p:plainESDocで作成されたESDocのドキュメント

良いドキュメントとは?

本来なら、良いドキュメントとはどういうものなのかを解説したいのですが、そのためには「ドキュメントとはそもそもどういうものなのか?」「ドキュメントはなぜ書くのか?」「ドキュメントはどういう構造をしているのか?」「ドキュメントの評価はどうすればよいのか?」などについて解説する必要があります。ですが、このブログはそれを書くには狭すぎるため*2、今回は割愛します。

このようなドキュメントに対する根本的な考えをThe Web Explorerにて紹介しています。The Web Explorerは多数の著者による共著になっており、ESDoc以外にもWebの最先端の話が収録された書籍になりますので、興味のある方は是非御覧ください。

f:id:h13i32maru:20151218113818p:plain
The Web Explorerの表紙

どうやって良いドキュメントを作るのか?

ESDocの基本的な動作はJSDocやJavadocと同じくソースコード中に特定の記法で書かれたコメント(ドキュメントコメント)を元にドキュメントを作成するツールです。

/** * sums two values. * @param {number} a - a first value. * @param {number} b - a second value. * @return {number} result value. */function sum(a, b) {return a + b;
}

しかし、JSDocやJavadocはソフトウェアのAPIリファレンスを作成することを目的としていますが、ESDocの目的はそれだけではありません。 ESDocの目的はソフトウェアを使用するために必要な情報全般を含んだドキュメントを作成することです。 ここでいうドキュメントとは一般的にマニュアルと呼ばれ、インストール方法、チュートリアル、サンプル、変更履歴などの情報をまとめたものを指します。

そして、ESDocは良いドキュメントを作成するために次のような特徴的な機能を提供しています。

  • マニュアルの統合
  • カバレッジ
  • Lint
  • 静的解析
  • テストコードの統合
  • 組み込み検索
  • ホスティング

以降ではこれらの機能を簡単に紹介していきます。

マニュアルの統合

マニュアルの統合機能はMarkdownで書かれた複数のファイルをドキュメントに組み込むという機能です。 これによって、インストール方法、チュートリアル、サンプル、変更履歴など、ドキュメント(マニュアル)に必要な内容を包括的に作成できます。

f:id:h13i32maru:20151218113844p:plain
マニュアルとして組み込まれたチュートリアル

カバレッジ

カバレッジ機能とはテストカバレッジと同じように、ドキュメントが書かれるべきところ(クラスやメソッド)にどれだけ書かれているかを確認する機能です。 ドキュメントのカバレッジを可視化することで継続的にドキュメントを保守するモチベーションに繋がると考えています。 このカバレッジ機能についてひとつ注意してほしいことは、かならずしもカバレッジ100%を目指す必要はないということです。 あくまでもドキュメントの継続性を改善するためとして、どの程度のカバレッジが必要かは各開発者の判断にお任せします。

f:id:h13i32maru:20151218120903p:plain
ドキュメントを書くべき箇所(350)と実際に書かれている箇所(323)

f:id:h13i32maru:20151218113901p:plain
全体のカバレッジと各ファイルごとのカバレッジ

Lint

Lint機能とはドキュメントがコードに対して間違っていないかを検証するための機能です。 あくまでもドキュメントとコードの不一致を検証するのであって、ドキュメント自体が正しいかどうかを検証できるものではありません。 今のところ、このLintが対象にするのはメソッドのシグネチャとドキュメントが一致しているかどうかだけです。

f:id:h13i32maru:20151218113918p:plain
仮引数はpだが、ドキュメントにはxと記載されているため警告を表示

静的解析

静的解析機能とはドキュメントコメントが書かれていないコードからもそのコードから分かる情報をつかってなるべくドキュメントを生成するというものです。例えば次のようにドキュメントが一切書いていないソースコードの場合でも「Class構文によるクラスと継承」「デフォルト引数による仮引数の型」「return文による戻り値の存在」「テンプレートリテラルによる文字列型」を解析することで、ある程度ドキュメントを作成することができます。

exportdefaultclass Foo extends Bar {
  baz(p = 'Alice') {return `hello ${p}`;
  }}

テストコードの統合

テストコードの統合機能とはテストコード自体も有益なドキュメントと考え、テストコードとテスト対象を関連付けてドキュメントを生成するというものです。テストコードとテスト対象を次のように@testを使って関連付けると、生成されたFooクラスのドキュメントにはテストコードへのリンクが、テストコードの一覧にはテスト対象へのリンクが自動的に追加されます。

/** @test {Foo} */
describe('Foo is useful class', ()=>{/** @test {Foo#bar} */
  it('is useful method', ()=>{const foo = new Foo();
    assert(typeof foo.bar, 'function');
  });
});

f:id:h13i32maru:20151218113931p:plain
クラスをテストしているテストコードへのリンクを表示

f:id:h13i32maru:20151218113933p:plain
テストコードが対象としているクラスやメソッドへのリンクを表示

組み込み検索

組み込み検索機能とはその名前のとおり、ドキュメント内を検索できる機能です。 この組み込み検索機能はJavaScriptだけで実装されているため、サーバ側の実装は一切不要です。 静的ファイルとしてWebサーバで配信する、もしくはローカルのファイルをブラウザで直接開いて検索できます。

f:id:h13i32maru:20151218113950p:plain
ドキュメント内を任意のキーワードで検索

ホスティング

ドキュメントを作成したあと、どこかで公開して誰でも閲覧できるようにする必要があります。 そこで手軽にドキュメントを公開できる仕組みとして、ESDoc専用のホスティングサービスを提供しています。 このホスティングサービスはESDocを使用しているGitHubのリポジトリURLを登録するだけで利用できます。

f:id:h13i32maru:20151218114003p:plain
ホスティングサービスに登録されているドキュメント

f:id:h13i32maru:20151218114006p:plain
ホスティングサービスへの登録方法

おわりに

ソフトウェアはコードとテスト、そしてドキュメントから成り立つものです。コードとテストはたくさんの解説書や議論がありますが、ドキュメントはそうではありません。そのためドキュメンテーションツールもあまり進化していません。そのような状況に何かしら一石を投じたいと思い、ESDocを開発しています。もしこれを期にドキュメントについて興味を持たれた方がいらっしゃれば是非、ドキュメントについても考えてみてください。より良いドキュメントはより良いソフトウェアを作ります。

*1:CodeLunch.fmもよろしくお願いします!

*2:フェルマーの言葉より

エンジニアが人事部に入ってやったこと

$
0
0

人事部 エンジニア人事企画リーダー*1の小川(@conceal_rs)です。

前回はサービス開発に関するお話をしたのですが、今回はエンジニアが人事に入ってやったことについてお話したいと思います。

人事部との兼務のきっかけ

そもそも去年から中途エンジニア採用には関わってきていました。主に書類選考や面接官をしてきたのですが、去年の終わりごろからはさらに、新卒を含めたエンジニア採用全般に関わるようになっていました。また同じような時期から社内のエンジニア評価に関しても関わりが強くなってきていて、業務のほとんどが「人事系の仕事では?」と思う時があるほど比重は大きくなりました。

そんななか、当然のように人事の方と一緒に仕事をすることが増えたのですが、なぜか一歩引いた状態で会話されているなと感じることが増えてきました。言葉では説明しづらいのですが、「手伝っていただいて恐縮です」という雰囲気で接してこられているなと思うことが何度もあったのです。その一方で、どこの会社も同じだとは思いますが、事業部側からのエンジニアを採用してくれという圧力は高く、採用フローのスピード感が求められています。そんな状況で、なんだか人事がボトルネックになっているのでは?という場面がちらほら見受けられるようなりました。

ただ実際にいろいろ見たり話を聞いてみると、実際にはボトルネックになっているわけではなく、人事にエンジニアがいないため、どうしたらよいかわからない点が多いことで、スピードがでにくいのだとわかりました。かと言って事業部のエンジニアにパッと質問したりできるのかというと、若干質問しづらい環境になっている様子でした。さらに調べてみるとお互いがお見合いしているような状態で、このまま非効率な状態にしておくのは良くないなと強く思い始めました。以前からエンジニアの採用はエンジニアがやるべきであると思っていたので、このような状況であるなら、私が人事側でエンジニア採用や評価を担当しようと思い立ち、人事部を兼務させてくれと直談判をすることにしたのです。その結果、人事部にエンジニア人事企画リーダーという役職でエンジニアの評価や採用全般をみることを決断しました。

人事部に入ってみて

さて実際に人事部に入ったとはいえ、まだエンジニアであるというだけでまだ一歩引かれているのを感じたので、ひとまずはエンジニア採用に関わるところにはすべて首を突っ込むようにしてみました。例えば事業部で要求しているエンジニアはどのようなスキルセット、マインドセットが必要かといったヒアリングから、面接官のアサイン、書類選考まで幅広く関わるようにしました。特に新卒エンジニアの採用については、人事担当者が若いということもあり、エンジニア観点だけでなく年長者としてもサポートをし続けました。そのおかげか徐々に仲間であると認識してもらえてきたかな?と、いまでは思っています。

また初期の段階からエンジニア評価について私なりの考えがあったので、それを人事の方と共有することも心がけました。その分、エンジニアに関わる部分はすべて責任をもって担当することにしたので、この時期からほぼコードは書けなくなってしまいました。そんな状況でやっていけるのか?と自分でも不安ではあったんですが、毎日がわりと忙しくてあまり意識することはなかったのが現状です。

人事のお仕事

また人事で仕事をやってみて思ったのは、エンジニアで培ってきたことが結構使えるなということでした。例えばクックパッドのサービス開発では、価値仮説や価値検証といったことに重きをおいています。人事の仕事をしてみると、採用についても同じように適用できる状況であることがわかりました。例えばインターンでは学生のみなさんにどのようになってもらいたいのか、どのような気持ちを持って帰ってもらいたいのかを強く意識することを重要視していました。サービス開発でユーザの体験を考えるように、採用についてもそのユーザの体験をちゃんと設計していなければいけません。もちろん人事の方々がこの点を意識していなかったわけではないですが、カスタマージャーニーマップなどを使って言語化し定式化していくことで、より精度の高い施策や企画を実行できるなど、いいこともたくさんありました。

またちょっと傲慢かもしれませんが、エンジニアのことはエンジニアが一番理解できるという思いがあったので、エンジニアに関係する人事的な仕事には何にでも関わるようにしていました。例えば福利厚生や人事施策についても、エンジニア視点を入れることで幅や深みを若干でももたせられたのではとは思っています。

みんなの協力が必要と周知

そんなわけで、まずは一人でいろいろやり始めたのですが、やはり限界がありました。例えば面接であったり新卒採用イベントへの参加などは、一人でやるには体力的にも限界があります。また開発現場の雰囲気や求められるスキルなどは現場が一番理解していると思っているので、採用に関しては特に事業部のエンジニアの力を借りるようにしました。現場のエンジニアの力を借りるなんて当たり前では?と思うかもしれませんが、事業部としての業務やスケジュールもあるためそう安易に借りるのはよくありません。組織として採用に協力するのは当たり前だったとしても、ちゃんと事業部長と話をして理解を得なければ、お互い納得して採用業務を遂行できない状況になってしまいます。

そこでまずは各事業部に、エンジニアの採用はエンジニアがやるべきであるということを説明して、そうした考えを浸透させることにしました。そのおかげか、現場のエンジニアの協力を仰げるようになってきています。もちろんそれぞれのイベントなどの前にちゃんと話をしますが、みなさん協力をしていただけて人事としては非常に助かっています。

より良い開発環境にするために

というわけで、人事に入って8ヶ月たったエンジニアの今の仕事をつらつらと書いてみました。人事の仕事は大変じゃないですか?とたまに言われたりしますが、クックパッドの全エンジニアに影響を与える仕事をしているという点で、非常にやりがいのある楽しい仕事だと思って取り組んでいます。エンジニアが人事にいることで、現場に近い人事組織になっていけると信じて、今後も頑張っていきたいです。

クックパッドではこのような仕事を手伝ってくれる方や、サービス開発、基盤開発に携わりたいエンジニアを広く募集しています。キャリアについてなどのご相談もお受けできるので、お気軽に小川までお問い合わせください。

*1:技術部部長でもあります

サービス分割時の複雑性に対処する: テスト戦略の話

$
0
0

技術部の taiki45です。

現在のクックパッドでは、cookpad.com 内のデータを利用するようなプロダクトでも、cookpad.com を提供しているアプリケーション(本体アプリケーション)とは別に新規のアプリケーションとして設計・実装しています。また、すでに本体アプリケーションの一部として実装されているプロダクトについても、トレードオフを考慮しながら場合によっては、本体アプリケーションから独立した別のアプリケーションとして設計・実装することが増えてきています。これらの本体アプリケーションや、新規にあるいは本体アプリケーションから独立させて設計・実装したアプリケーションのことを「サービス」と呼んでいます。また、この本体アプリケーションから独立させることを「サービス分割」と呼んでいます。

制御できないほどの巨大な複雑なまとまりを制御するために、その巨大なまとまりと単純なまとまりに独立・切り出して分散系へと切り出す方法があります。サービス分割をすることはこのやり方に沿っていて、小さなアプリケーションとして開発できるメリットや、独立したチームが独立したプロダクトのライフサイクルを持てるといったメリットを享受できます。一方で、分散系ではそれらを構成する要素間の関係をいかにして担保するかという従来とは異なった側面の複雑性が問題になってきます。その関係が正しく動作しないと、系全体として正しく動作しなくなるためです。例えば、サービスディスカバリの問題が発生することや、サービス間の連携が不安定になり複雑になるといったデメリットなどがあります。サービス分割のメリットを享受し、開発速度を保つためには、このデメリットを解消していく必要があります。

今回は、分散環境化で生じる問題の1つとしてサービス間のインテグレーションテストにおける問題、その解決へのアプローチとして Consumer-Driven Contract testing パターン、そしてクックパッドでの取り組みを紹介します。

問題

クックパッドでは各サービス間の連携には、Protocol Buffers や Thrift を用いた RPC を利用するのではなく、JSON over HTTP を利用する方針にしています。この理由としては、現状そこまでパフォーマンスがボトルネックになっていないことがあり、また、デバッギングの容易性や Rails の RESTful な思想との親和性や既存のミドルウェアの再利用といった開発効率のほうを優先しているからです。

クックパッドでは、「機能やデータを提供する側」(Provider)と「そのデータを利用する側」(Consumer)の結合部分において Provider に拡張の余地を残しています。したがって、Provider はレスポンスを変更することがあります。例えば、「レシピ名」、「作者」、「レシピの説明」のようなレシピデータを1件返すような架空の API のレスポンスを考えてみます。

{
  "name": "丸ごと野菜とステーキ アボカドソース添え",
  "author": {
    "id": 8510522,
    "name": "taiki45"
  },
  "description": "ステーキと丸ごと野菜だけでもおいしいのですが、少しさっぱりとしたアボカドマスタードソースが絡むことでとてもおいしいです。"
}

この時、トップレベルに新たに idキーを追加することも、あるいは authorオブジェクトに新たに created_atキーを追加することもどちらも Consumer にとって非破壊的変更になるように、Consumer が利用するクライアントライブラリは未知のキーを無視するように実装されています。

今まではこのようなサービス間の結合部分を、主に WebMockあるいは vcrが提供するテストダブルを利用して、ほとんど固定されたレスポンスを使用してテストしていました。ここでの問題は、Provider がレスポンスを変更する際に誤って非互換な変更を含めてしまうことに対する対処です。非互換な変更の例としては、「Consumer が利用しているキーを削除してしまう」や「値の型を変更する」などです。

固定されたレスポンスを使う方法では、Provider が間違って破壊的変更を追加した際に、テストを実行した段階では破壊的変更を検知できません。そのため、Provider がその変更をリリースして初めてサービス間連携が壊れた事に気がつく事態になってしまいます。そのような事故を減らすために、今は大きな変更の前には Provider と Consumer が調整しています。

しかし、組織とサービスの独立性を高め、お互いに非同期かつ高い効率で、サービス開発するには、サービス間連携が壊れないことをテスト実行時に検証する仕組みを導入し、Provider と Consumer の調整コストを減らすことが必要です。

破壊的変更を自動的に検知する容易な方法として、Consumer 側でのインテグレーションテストにおいて実際の Provider サーバーを起動してテストする方法があります。まず、この方法ではテスト実行時間が増加します。さらに、Provider 側で変更を加える時、Consumer 側の時間がかかるインテグレーションテストを全て通さなければならなくなります。そのため、サービス間の依存が複雑になるにつれ、テストのコストが上がっていき、クックパッドが実現したい継続的デリバリーの形に支障をきたしてしまいます。

クックパッドでの取り組み

このような問題に対して、クックパッドではまず VCR を拡張して、Provider が CI を回す毎にレスポンスデータを生成して Consumer に渡し、Consumer はそのレスポンスデータを元にインテグレーションテストを実行する方法を開発しました。詳しい内容は「マイクロサービス時代を乗り越えるために、Rack::VCRでらくらくアプリケーション間テスト」から参照できます。この方法では常に最新の Provider のレスポンスデータで Consumer はテストを実行できます。

この方法はある程度うまくいったのですが、いくつか問題がありました。1つ目に、Provider に変更があるとその Provider に依存する全ての Consumer の検証をパスしないと安全に Provider の変更をリリースできないことです。これはサービス間の依存グラフが複雑な状況ではテスト時間が問題になってしまいます。2つ目は、Consumer が要求するリクエスト・レスポンスの組を Provider の側で記述する必要がある点です。サービス開発効率という面では、Provider と Consumer 間の調整コストはもっと減らしたい。

上記のような問題を解決するために調査をしてみると、この形は実は Integration Contract Testing戦略と呼ばれているものに近いことが分かりました。この戦略は Provider が提供する機能や Consumer が必要とする機能を Contract としてくくりだし、コンポーネント単体でそれらの Contract をテストする手法です。

Contract は入力・出力のスキーマ、呼び出しが起こす副作用、パフォーマンスなどの特徴から成り立ちます。Contract は大別すると3種類にわけられます:

  • Provider Contract: Provider が提供する機能全体をカバーする Contract。
  • Consumer Contract: 個々の Consumer が Provider に要求する機能をカバーする Contract。
  • Consumer-Driven Contract: ある Provider への Consumer Contract 全ての集合。

この中でも Consumer-Driven Contract に着目します。この Contract を利用して以下のようなプロセスを実施することで、Provider-Consumer 間の結合が破壊されていないことを検証できます。

  • Consumer がある特定のリクエストに対応する期待するレスポンスを定義する。
  • Provider と Consumer はその Contract について合意する。
  • Provider は自身が継続的にその Contract を守れているか検証する。

このようなテスト手法は Consumer-Driven Contract testing パターンと呼ばれています。このパターンは他のインテグレーションテストの手法に比較して、次の点でメリットがあります:

  • インテグレーションテストがコンポーネント単体で完結できる。
    • 2つ以上のサービス間の結合のテストのための調整コストが低くなる。
  • Provider は Consumer-Driven Contract を守っている限り、安心して変更を行える。
    • どの Consumer も要求しないような Provider Contract に対して Provider は責任を持つ必要がない。
  • Consumer の開発者が自身が依存する Contract を作成・メンテナンスできる。
    • Contract が守られなかった場合、主に影響を受けるのは Consumer 側なので、Provider よりも Consumer のほうが Contract に対して関心が高い。したがって Consumer が Contract の作成・メンテナンスに責任を持つほうがより適切である。

実際に Consumer-Driven Contract testing パターンを実現するには次のような仕組みが必要です:

  • Consumer が Contract を定義して、それを Provider に届ける仕組み。
  • Provider が Contract を取得して、それを検証する仕組み。
  • (Provider が状態を持ったり、他のサービスに依存している場合) Consumer が特定の Contract での Provider の状態を記述し、Provider がそれを再現できる仕組み。

このような仕組みを実装してるツールとして代表的なものに Pactがあります。

クックパッドでは、現在はこの Pact を検証すると同時に、Consumer が Contract を書く手間を自動生成ツールで代替できないか等を検討しています。

エンジニア募集中!!

クックパッドではこのような挑戦的な問題解決に取り組める環境があり、問題解決に取り組む仲間を募集しています。興味のある方はぜひ一度遊びに来てみてください!!

テストエンジニア | クックパッド株式会社 採用情報

Cookpad TechConf 2016 開催報告

$
0
0

こんにちは、Cookpad TechConf 2016 実行委員長*1の小川です。

先週の土曜日の2016/01/23、クックパッド初となる技術系カンファレンスCookpad TechConf 2016を開催しました。staffblogの方でも報告させていただきましたが、開発者ブログの方では発表内容の紹介をしたいと思います。

基調講演: ユーザーのために、技術をどう活かすか by 舘野 祐一

f:id:conceal-rs:20160123132104j:plain

まず最初は基調講演で、弊社CTO舘野さんによる発表です。ユーザファーストのためにクックパッドがどのように技術を活かしてきたのか。例えば仮説検証や効果測定を手軽にできるようにするためにどうしてきたのか。またエンジニア組織としてユーザファーストを支えるにはどうあるべきか、どうやっているのかについての話もありました。

おでかけスポット検索のむずかしさ - Holiday を支える検索技術 by 内藤 雄介

f:id:conceal-rs:20160123135301j:plain

次はホリデー株式会社の内藤さんが、位置情報の検索についてHolidayではどのように改善しているのか、Elasticsearchでの実例を交えながらの発表でした。例えば中目黒という地域について、単に語句で検索しただけでは地名がヒットするが、人が認識している中目黒の範囲と異なるなど、いろいろな問題を解決するためにやってきたことなど苦労が伺えます。

Railsアプリ開発環境の高速化 by 国分 崇志

f:id:conceal-rs:20160123141106j:plain

次は国分さんによる、開発環境の高速化の話です。御存知の通り、クックパッドはRailsアプリケーションですが、世界的に見ても大規模なものになっています。これだけ大規模にもなるといろいろと調整をしないと開発環境もとてつもなく遅くなってしまい、開発効率が大きく下がってしまいます。この困難に対して挑戦し続けてきた内容がまとめられていました。

R&D at Foodtech company by 有賀 康顕

f:id:conceal-rs:20160123145544j:plain

休憩を挟んで始まったのが、有賀さんによるクックパッドでの研究開発についての発表です。NIIでのデータ公開や研究開発を進めるために結成されたチームの紹介など、クックパッドが学術分野で活動していることが紹介されました。特にクックパッドならではという点では、食文化研究やヘルスケア領域など食の領域への学術貢献をしているところが特徴的だと思っています。

技術力を事業の強みするために必要なこと by 大野 晋一

f:id:conceal-rs:20160123145815j:plain

次は若干毛色が変わって技術を事業にしっかり活かすためにはどうすればいいかについて、大野さんの発表です。事業を進めるためにエンジニアができること、やるべきことについて論理的にかつ構造的に説明されました。「わかっているつもり」になっていることが大野さんなりの言葉で明確に示されました。いまあるビジネスをどのように成長させるのか、そこにエンジニアとして技術をどのように活かすのかについてがわかりやすく示されていました。

開発した新技術から、新しい価値を作るためのクックパッド検索チームのプロダクト開発手法 by 五十嵐 啓人

f:id:conceal-rs:20160123151713j:plain

続いては五十嵐さんによる技術をどうプロダクト開発に活かすべきか、そのクックパッドでの手法についてのお話です。技術を製品にし、それをユーザに価値として届けることはよく考えられることですが、それだけではユーザさんに価値を届けられない可能性があることを、実例を上げて紹介されています。ユーザさんの欲求から製品を考え、そこに技術を適用するという、クックパッドのやり方が紹介されていました。

「今日なに作ろう?」を支えるデザイン by 木村 真理

f:id:conceal-rs:20160123154815j:plain

休憩を挟んで続いたのは、デザイナーの木村さんによるデザイン面からサービス開発を支えるお話です。デザイナーとしてサービス開発に対してどのように関わっていくのか、プロトタイピングとデザインフレームワーク、デザインレビューについて、実例を交えながら紹介しています。クックパッドではデザイナーのみならず、 ディレクターやサポートの方もGitHubのissueを使ってやりとりをすることが多いのですが、もちろんプロトタイピングやデザインレビューにも利用されています。 あとデザインフレームワークを用意して、基本的なデザインであればすぐに実装できるようになっていることも開発者としては非常に助かっています。

確かめながら作るユーザー体験 by 出口 貴也

f:id:conceal-rs:20160123160615j:plain

続いては出口さんによるサービス開発に関する発表です。しょっぱなからサービス開発は難しいという発言がある通り、開発を続ける上でいろいろな困難が立ちはだかります。そんななか、クックパッドがどのようにサービス開発を進めるのか、どの段階でどのように意思決定するのかを紹介しています。

モバイルアプリのインタラクションプロトタイピング by 多田 圭佑

f:id:conceal-rs:20160123162043j:plain

そして次は多田さんによるモバイルアプリ開発のプロトタイピングのお話です。ユーザさんにいかに早く、価値あるものを届けるか。早く何度も仮説検証を繰り返すためにクックパッドではどのような手法を用いているのかについて、Holidayでの実例を交えながらの発表でした。必要に応じてプロトタイピング手法自体を変更することで、検証したいことを明確にできることが示されていたと思います。

モバイルアプリ開発の"標準"を探る by 藤 吾郎

f:id:conceal-rs:20160123165051j:plain

最後の休憩を挟んで、藤さんによる発表です。クックパッドは多くのユーザさんに使って利用していただいていますが、最近はアプリからの利用が増えてきています。そんな状況で安定して開発・運用することが求められており、それに対してクックパッドのモバイルアプリエンジニアがどのように取り組んできたかを紹介しています。そこでクックパッドが考える標準的なモバイルアプリ開発について、実例を上げながらいろいろな面からのお話でした。

DWHに必要なこと by 青木 峰郎

f:id:conceal-rs:20160123171613j:plain

次は青木さんによるデータウェアハウス(DWH)についての発表です。クックパッドも規模の大きなサービスですので、PVやユーザさんの行動記録など様々なデータが蓄積されています。これらのデータを使って新しい価値を生み出すべく分析・解析をするのですが、そのためにもDWHの構築は不可欠です。構築する上で気をつけること、考え方などが発表されていました。

基調講演: クックパッドの継続的な成長のために開発と運用が何をしてきたのか、その失敗と成功について by 成田 一生

f:id:conceal-rs:20160123180645j:plain

最後の発表はクックパッドインフラストラクチャー部の部長である成田さんです。クックパッドの成長のために、我々がどのように開発・運用してきたのか、失敗談も上げつつ紹介しています。例えば「全部書き直したい!」と思うときがあるのが世の常ですが、ではそのときにどう進めるべきか。サービス分割をするときに成功した事例を元に、どのように進めていったのかなども紹介されていました。事業も組織も変化し、開発も継続するなかで、どのように品質を保ち続けるのか、何を考えて行動すべきかが発表の中で示されていました。

まとめ

このようにエンジニア組織のあり方から、技術的なトピック、研究開発や事業への取り組み方、またプロダクト開発に関することまで、幅広い領域の発表を取り揃えられたと思っています。当日来場された方はお楽しみいただけたでしょうか。また惜しくも落選した方は、こちらで雰囲気を感じ取っていただければ幸いです。

実行委員長としては、このようなイベントは定期的に開催したいと思っています。SNS経由でも結構ですので、ご意見・ご要望などいただければ幸いです。

*1:技術部部長でもあります


品質アップの時間を確保するデザインの進め方について

$
0
0

こんにちは。ユーザーファースト推進室、デザイナーの坂本です。

私は、クックパッドのレシピを主に生活習慣病の方に向けて管理栄養士が健康的にアレンジした「おいしい健康」というサービスのデザインを担当しています。

「おいしい健康」は2月22日に、アカウント(会員)機能やレシピのリコメンド機能、献立の保存機能などの新機能をリリースしました。このリリースに関わったメンバーは、ディレクター1名、エンジニア3名(+アルバイトの方2名)、デザイナー1名です。

私はデザイナーとして、UI設計からビジュアルデザインまで関わったのですが、パターンの考慮漏れや使い勝手の悪さなどで、手戻りを増やしてしまいました。

リリースした今、改めて振り返ってみると、もう少しうまくやれば手戻りを少なくし、品質アップの時間をもっと確保できたのでは?と思いました。一体何が悪かったのか? どうすれば良かったのか? もし神様が願いを叶えてくれて、今の記憶を持ったままリニューアル作業が始まる前に戻れたらどうするか、というラノベ的なことを考えつつ、こうしたら良かったのでは、という3点についてまとめてみました。

1. ページごとではなく目的ごとにデザインする

今回はページごとにデザインをつくったのですが、そうではなく目的ごとに作った方が良かったと思いました。

ページごとにつくると、パッと見は良くても、ユーザーがきちんと目的を達成できるか、という視点が抜けがちになります。

今回のリリースでいくつかの機能が追加されたのですが、それによって改修となるページも数ページ存在しました。利用者数が多いから、という理由でトップページやレシピの検索結果ページからデザインをしたのですが、そうすると一連の体験の中で、つまづくところが出たり、違和感を覚えたり、何か引っかかるところが出てきてしまいました。

例えばPCのトップページに各病態(糖尿病や高血圧など)へのナビゲーションがあるのですが、最初は以下のようなデザインでした。

f:id:kanako-sakamoto:20160222153004p:plain

これは、トップページや検索などで食べたいレシピが見つからなかったとき、別の切り口で探すために設けたのですが、そうすると、この切り口だけでは不十分で、もう少し他の切り口があった方が探しやすいのでは、ということで以下のようにナビゲーション全体を変更しました。

f:id:kanako-sakamoto:20160222153021p:plain

ページごとのデザインでは、ページ全体の完成度(配置のバランスなど)にばかり注目してしまいました。そうではなく、目的ごとにデザインをした方がスムーズにゴールへたどり着けるかわかりますし、手戻りも少なくなるのではと考えます。

2. どのユーザーストーリーが重要か、という順位をつける

今回はページごとにデザインを作り、かつ重要なストーリーを優先してつくる、ということをしなかったため、その検証が遅くなる、ということが起こりました。

例えば、重要なストーリーが「生活習慣病の夫が安心して食べられるような、今日の夕飯の献立をたてたい」というものだったとします。

それに関わるものの一例として、以下のページのコンテンツが考えられます。これらを優先的につくっていれば、この重要なストーリーを早い段階で検証し、品質を高める時間をもっととれたのではと思います。

f:id:kanako-sakamoto:20160222153032p:plain

3. デザインの複数案を用意する

自分で良いか悪いかの判断ができなくなったときは複数案を用意しましたが、それ以外はほぼ1つだけの案でした。

例えば、PCの献立帳と呼ばれる機能の栄養価のエリアで、実装後、改めて見てみると違和感があり、再検討する、ということになりました。

ただその次のデザインレビューで、複数案をつくっていくとすんなりと決まる、ということがありました。

f:id:kanako-sakamoto:20160222153042p:plain

最初の案では、情報が多く煩雑なので削った方がいいのでは、いやいややはり基本の8栄養価は必要だ、などで意見が飛び交ったのですが、実際に様々なパターンをみてもらうと、やはり栄養価は8つあった方がよいが、エネルギーや塩分に比べると重要度は低いので小さく見せる「②」のデザインが良い、ということになりました。

なぜ1案と複数案でこのような違いが出たのか不思議だったのですが、その理由が 「人間中心設計推進機構 第4回「見えるシステム、分かりやすいシステム」の「5-1-2. 3案比較の勧め」にわかりやすく載っておりました。

ユーザインタフェースは比較することによりはじめてその優劣が判断できるようになる。今後、研究が進みUIの優劣を定める客観的な指標が発明されるまでは、必ず複数案のUIを考案し適切なユーザビリティ評価を実施すればよい。その際に3つの案を比較することをお勧めする。2つでは違いは判っても、関係性は見つけにくい。3つを比較するとそれぞれの特徴やUI要素の関係が見え、そこから優良な方向が見えてくる。

複数案があると、その妥当性を判断しやすくなるのだと思います。最初にいくつかの案をつくる時間はかかりますが、より良い体験を届ける確度が上がり、手戻りも少なくなるので、特に重要な体験に関わる場面では複数案をつくった方が良いと感じました。

まとめ

以上、今回の手戻りの原因と、今後どのような改善をしていけば品質アップの時間を確保できるのかをまとめてみました。

今回は開発期間が長く、そうなると、最初に「これが大事だよね!」と共通認識を持ったことも忘れがちになるのだと感じました。それを思い出させるものがユーザーストーリであり、それが形になったものがUIやビジュアルデザインだと思います。

しかしそれはデザイナーが意識しないと、形に現れませんし、手戻りも多くなってしまうのだと感じました。今回は本当にその点を反省しました。

また、Webサービスは作って終わりではなく、改善をしていくことが重要です。PDCAをどんどんまわして、より良いユーザー体験を届けていきたいと考えております。


クックパッドでは、より良いユーザー体験を届けていきたい!というデザイナーやエンジニアを募集しています。

クックパッド株式会社 採用情報

成熟期にあるWebサービスの新たな価値を求めて - サービス開発はじめの一歩

$
0
0

会員事業部の小椋(@littlestarling)です。

会員事業部では会員向けサービス全般、中でもプレミアムサービスの価値向上を継続すべく、日々サービス開発を行っています。 クックパッドのプレミアムサービスの柱のひとつは人気順検索機能です。

今年、私はその人気順検索でカバーしきれない課題を見つけ、その課題を解決する新たな価値を生み出すことをミッションとして与えられました。

当社ではサービス開発はディレクターとエンジニアのチームによって進められています。

ディレクターの方に既に明確なビジョンがある場合はその壁当て相手として質問を繰り返して煮詰める作業に徹することが出来ますが、今回はお互いゼロからのスタートとなったため、一緒に"What"と"Why"を探っていくことになりました。 本稿では、このようにエンジニアがディレクターと企画を一緒に考えていったことについて書きたいと思います。

検討のために行ったこと

以下のような流れで検討を行いました。

進め方を決める

まず企画とは何かを最初に定義したいと思います。

企画とはある課題に対応した解決策といえます。ある企画自体が別の大きな課題の解決策になったりという構造になっています。

この定義に沿って考えると、クックパッドには「毎日の料理を楽しみにすることで、心からの笑顔を増やす」という理念がありますので、提供したい大きな価値として「毎日の料理を楽しみにする」ということが挙げられます。 人気順検索機能によってこの価値を提供できているところもあるでしょう。そして、「人気順検索でカバーしきれない課題」、という表現では課題がなんなのかがはっきりしません。 課題とその解決によって得られる価値を定めるために、チーム内でのブレインストーミングと、社内ユーザーのインタビューによって課題を探すことにしました。

ブレインストーミング

チーム内でモデルユーザーを想定しながらユーザーと日々の料理に関わる悩みとその理由を書き出していきました。 自チーム版のなぜなぜnodeのようなものです。 ここでは料理経験およびクックパッド利用経験に基づいたモデルユーザーを何種類か考え、抱えていた悩みが経年で変化するかといったことも踏まえて案を出していきました。

社内ユーザーインタビュー(1)

まずは 人気順検索を使っていて不満なところはどこか?という質問をいきなりぶつけてみました。 不満を聞いているので当然ですが、 「今の自分の気持ちに合わない結果が出た」という意見が多かったです。

この「今の自分の気持ち」とは?という点が深掘り対象になりそうという印象がありました。

内容整理とここまでのふりかえり(1)

ヒアリング内容を整理してみると、前述の通り既存検索の結果にミスマッチが起こるケースがあるということが浮かび上がってきます。 ブレインストーミングの内容と付きあわせてみても、何らかの理由で期待された検索結果が出てこない、という悩みの種が出ていました。

ここで一つ改善施策を考えついたのですが、この段階で具体的な施策に落とすのは時期尚早ということで、一旦引っ込めることにしました。 実現可能であればすぐに試してみたいという気持ちはありましたが、課題が固まりきっていない状態で施策を実施したとしても価値の実証につながるかは説明しづらいです。

そのため、検索に限定せずに、問題を探す領域を広げて課題探しを続けることになりました。 具体的にはどういったユーザー体験が求められているのかをより深掘りすべく、改めてインタビューをし直すことにしました。 このとき、問題を探す領域を広げるために検索の不満点という聞き方ではなく、普段どのように使っているのか、より個々人のライフスタイルとの関わりを聞くようにしようという質問の改善点が出ました。

社内ユーザーインタビュー(2)

前回インタビューの整理を踏まえて、改めて社内ユーザーで属性を広げた形でインタビューを行いました。 例えば女性に偏っていたインタビュイーを男性にも広げたり、子どもがいるなど、作る料理に制約が出てきそうな要素がある人を追加しました。 今度は料理の頻度、買物方法、レシピの決め方など、日々の食にまつわる生活サイクルについて広く聞くようにし、あとでチーム内で問題点を探す方法を採りました。 日々の生活でのクックパッドの使いどころや、レシピの選び方についての知見を得られたように思います。

内容整理(2)

再度実施したインタビューと問題点の洗い出しを行いました。 ヒアリング内容から隠れている不満点・問題点は何か?を洗い出し、インタビュイーのペルソナ抽出をして整理を進めました。 ここでも当初のブレインストーミングの内容も引き直して確認をしたりしました。

課題と価値の関係を表すキーフレーズを見出す

ここまでの整理内容から、出したい価値は何か、を象徴的に表す表現を探しました。 ディレクターさんがいい言葉を見つけてくれました。 最終的なプロダクトオーナーに企画の説明をした時の感触で、これが自分たちのやろうとする価値を表現する言葉になると感じました。

やってきたことを振り返って

このようにして、今後の施策に向けての価値の軸を定めました。この軸に基づいた施策を繰り返し試すことによって軸自体の検証も行われることになりますが、これまで行ってきたことを簡単にふりかえってみます。

不安を共有する

普通の開発プロジェクトと同様、KPTも行いました。課題を見つけて解決できるのかはもちろん、そもそも課題がちゃんと見つかるのか、提供したい価値はなんなのか、そのストーリーを作りきれるのかという不安がありました。 また、2人構成のチームなので議論が熱くなると人 vs 人の構図に陥る可能性もありました。不安感情を先に共有してしまうことで、お互いの弱みも認識され、「問題 vs 私たち」の構図を作ることが出来たと思います。

誘導質問をしない

1度目のインタビューの時の質問の仕方は検索という機能に絞った形になったために、答えを誘導してしまう可能性がありました。 「今の人気順検索の不満点はどこですか?」という内容は不満があることを前提にしています。 本人的に満足しているが、無意識に解消している問題の発見にはつながらなかったり、無理矢理些細な不満を大きな声として言ってしまうこともありえます。 なるべく開かれた視点で話を聞けるよう、インタビュイーに共感的傾聴を持って話を進めるようにした結果、無意識に現状に折り合いをつけていそうな行動を見出すことが出来ました。

ヒアリング内容をそのまま書き出したら一旦寝かせる

共感している間は自分が同じマインドで言葉を受け取れてしまい、暗黙の合意を持って話を進めてしまうことがあります。 特定のユーザーからのヒアリングで得た課題が特定ユーザー層のみに向かれたものなのか、その先に普遍的な価値を持って応えられる 課題なのかを見極めるためにも一度時間をあけて見直すことを意識しました。 実際にはインタビューそのもので疲れてしまうということもありますが、ある程度時間をあけてから話をまとめ直すというのは意図して行っていた作業です。 一歩引いて俯瞰しながら整理するという視点に立ちやすい効果が得られたと感じています。

コードに落とそうとしない

企画のアイディアを発散させようとする時にコードに落とし込んで考えることは発想の幅をせばめてしまいがちです。 どこかで実装可能な施策案に落としこむことは当然必要なのですが、実装方法によらない課題と価値を定義する段階では一度思い切ってコードのことを忘れてしまった方がよいです。 様々な施策検証に耐えうる課題と価値を設定できたら、それを実現するための具体的なアイディアを考える番です。 得意の言語、利用しているフレームワーク等、実現可能性を思う存分考えましょう。 この点は以前にも言及されていますね。

いろいろなフレームワークを試してみる

企画開発フレームワークとしては当社ではEOGSがよく使われていますが、例えば今考えていることをAISASに当てはめられるか?リーンキャンバスで書いてみるとどうか?構造化シナリオを使えるか?といった具合に異なるフレームワークを壁当て相手として使ってみることも自分の考えの整理につながります。 自分たちでまとめた表は最終的になんと呼べばいいのかわからない感がありますが、あえて言うならリーンキャンバスにおける課題、顧客セグメント、独自の価値提案を詰めたということになるでしょう。 求めるユーザー体験のサイクルがどうあるべきなのかはあれこれ壁当てをした結果辿り着いたように思います。

まとめ

サービス開発のはじめの一歩の部分で行ったことをご紹介しました。企画立案の話になりますが、この領域もサービス開発エンジニアに必要な要素だと考えています。 残念ながら諸事情で本プロジェクトは現在ペンディングになってしまっておりますが、再始動をする時に動きやすい軸を定めるところまでは辿りつけたのではないかと考えています。 私たちは既存サービスの世界を広げるための新しい価値を提供するためにチャレンジしたいエンジニアやディレクターを募集しています。ご興味あればぜひお気軽にお話しましょう。

アプリの動作確認をする時に心がけていること

$
0
0

こんにちは。投稿推進部ディレクターの新里です。

私はディレクターとして、動作確認に積極的に関わるようにしています。その理由は、担当したサービスをより多くのユーザーに快適にご利用いただきたいという思いからです。

せっかくサービスを使おうと思っていただいたユーザーがいるのに、不具合をきっかけに不便だなと思ったり、使わなくなったりするのは、私だけでなく開発しているメンバー全員にとっても、すごく残念なことだと思います。

そんな状況を少しでも減らすために、私が普段の業務の中で動作確認を行う時に心がけていることをご紹介したいと思います。

心がけていること6つ

  1. なるべく実機で確認する
  2. 条件を組み合わせる
  3. タイミングを狙う
  4. あえて余計なことをやる
  5. エンジニアに聞く
  6. きちんと管理する

1.なるべく実機で確認する

シミュレーターで動作確認することも可能ですが、実際にユーザーが使うのに近い状態で画面を見て、違和感がないか、使いにくくないかを確認するようにしています。

例えば、手元の端末で見ると思ったよりも文字が小さいとか、クラウド上の画像をカメラロールから取得しようとしたら落ちるといった、実機じゃないと発見しづらい不具合が見つかることもありました。 もしも会社に検証端末があるのであれば、基本的には実機を使います。

2.条件を組み合わせる

テストの際は、以下の様な条件を組み合わせながら確認します。

  • 機種、端末
  • OSのバージョン
  • ユーザーステータス(ゲストor会員など)
  • 操作
    • 画面上の操作(タップ、スワイプ、長押しなど)
    • 端末操作(電源を切る、他のアプリに切り替える、機内モードにするなど)
  • 通信環境(Wi-Fi、3G、4G、低速)

動作が重くなりそうな、古い機種と新しいOSで組み合わせたり、通信環境を低速〜高速までつなぎ変えながら確認したりします。(弊社ではテスト用に低速のWi-Fi環境が整備されています) それ以外にも、画面操作途中で機内モードに変更したり、1つのアカウントで複数端末からログインするとどうなるか、といったテストも行います。

過去の事例としては、ユーザーステータスを変えて画面を開いた時に、ログインユーザーであれば、アイコンとユーザー名が表示されるが、ゲストユーザーはアイコンが表示されないという不具合がありました。(本来はデフォルトのアイコン画像が表示されるはずでした)

f:id:Teriyakky:20160225145425p:plain

3.タイミングを狙う

前述の「条件を組み合わせる」に近いですが、タイミングを狙って、画面をタップしたり、通信エラーを起こしたりします。 普通になんとなく使っているだけでは不具合は見つかりにくいので、「ここだ」というタイミングを狙って、条件を組み合わせると効率よく不具合が見つかります。

不具合を見つける時に狙いたいタイミング

  • 画面切り替え時(画面遷移時、縦横回転、ボタン押下時)
  • ページ読み込み中、通信中
  • 下限/上限値に達する瞬間
  • レアな画面の時
    • n回に1回表示される画面
    • 一定の条件を満たした時だけに表示される画面

4.あえて余計なことをやる

画面の指示通りに操作していては不具合は見つかりません。あえて指示とは違うことや、アプリに負荷がかかるようなことをやって、不具合を見つけます。

  • 文字数オーバーしてるのに決定ボタン・保存ボタンを押す
  • フライトモードにして画面操作する
  • 1回しか押せないはずのボタンを連打する
  • どんどんスクロールして、画像の読み込みをし続ける

具体的には、文字数制限が20文字のところで、文字数オーバーするとアラートが表示されるのですが、そのアラートの[OK]ボタンがキーボードの下に表示されてしまい、ボタンが押せずに画面でハマってしまうという不具合がありました。

f:id:Teriyakky:20160225145433p:plain

5.エンジニアに聞く

例えば、Aという不具合を直したら、Bという新しい不具合が出るようになったというのはよくある話だと思います。

不具合を修正したら、その不具合をどうやって修正したかを確認し、それに伴って影響を受けそうな機能がないかどうか、自分なりに一度考えてみます。そこで競合する機能や、こういうパターンの時はどうなるんだろう?と気づいたら、エンジニアに再度確認します。

また、特に自分では思いつかなくても「その修正で影響が出そうなことって何か考えられます?」の一言をエンジニアに投げかけて、確認をお願いしたりしています。

6.きちんと管理する

動作確認時に不具合を見つけたら、まず以下の2つのことをすぐやります。

  • スクリーンショットを撮る
  • 状況(機種、OSのバージョン、発生状況)をメモする

見つけた不具合は、GitHub上に記載し、issueにあげるようにしています。(ただし、新規アプリの開発時などは、全ての画面とリンクを抜け漏れなく確認する必要があるので、検証項目リストを作成する場合もあります。)

そして、GitHubで不具合をissueとしてあげる際は、ラベルを使い、対応の優先度や案件の種別を分かりやすく可視化するようにしています。

f:id:Teriyakky:20160225145444p:plain

上記が実際に運用しているラベルのリストです。 テストで発見した案件は「検証FB」のラベルを貼ります。 テストは不具合を見つけることも目的ですが、ユーザーにとって分かりやすく、操作しやすいかという観点でチェックすることも非常に大事です。そのため、機能的な「不具合」とは別に「既存機能の改善/改良」の項目を設けています。

ただし、ここで気をつけなければいけないのが、ディレクターが「もっとこうしたい」を言い続けていては、どんどんスケジュールが押してしまうということです。スケジュールの中で、どの機能は対応して、どの機能は対応しないのか、もしくはスケジュールを変更してでも対応すべき事象かを、エンジニアと相談しながら、調整するようにしています。

そして、対応が終わったら不具合を発見した本人が、修正を確認した上でissueをクローズします。ここまでやってリリース前のチェック完了です。

まとめ

私が普段の業務の中で動作確認を行う時に心がけていることをまとめてみました。

動作確認のタイミングは、スケジュールとの兼ね合いで、開発側は一番バタバタしてしまう時期かと思います。しかし、ユーザーにサービスを利用してもらう前の最終チェックの場でもあります。この記事が、より良いサービスを作り上げるためのきっかけの1つになれば幸いです。

たべみるを研究者に向けて公開します

$
0
0

こんにちは、研究開発チームの伊尾木です。

クックパッドには「たべみる」というレシピの検索データを分析できるサービスがあります。 例えば、「唐揚げと豚の角煮はどっちが検索されるの?(ちなみに唐揚げです)」とか「冷やし中華と鍋焼きうどんはどっちが検索されるの?(ちなみに冷やし中華です)」ということがわかります*。 日々膨大な検索データが蓄積されており、これらのデータを解析することによって日本の内食の一面がみえてきます。

たべみるは通常は有料サービスとして運営していますが、研究者に向けて無償で公開いたします。 詳細な申し込みなどは以下のURLをご参照ください。 https://cookpad.com/terms/tabemiru/academy

なぜ公開するの?

たべみる公開によって、食文化などの研究が進むことを期待しています。

食に関係する問題はレシピを探す問題だけではありません。 例えば、偏食や孤食、伝統の断絶など様々な問題が存在します。あるいは食が文化的な摩擦の起因になることもあります。 このような様々な食文化や、健康などの問題に対して、何らかの貢献を行いたいと考えています。

一方、食文化などの研究には、データが十分に揃わないという課題があります。 食の現状を調査するのに一般的に用いられている手法はアンケートですが、アンケートでは、どうしても収集できるデータ量は限られてしまいます。 このためどうしても研究が限定的になってしまったり、十分に研究を進めることが難しいという問題が起きてしまいます。

今回のたべみる公開は、このような研究課題への解決の一歩となればと思っています。 そして、研究が進むことで様々な食の問題が良い方向に向かうことを期待しています。

最後に

クックパッドの理念は「毎日の料理を楽しみにすることで、心からの笑顔を増やす」です。 食の研究が進むことで、より多くの笑顔を増やすことができればと、心から願っています。

本件に関するお問い合わせなどありましたら、research-tabemiru [at] cookpad.com までご連絡ください。

テストを使いサービス開発を駆動していくために取り組んでいること

$
0
0

技術部の松尾(@Kazu_cocoa)です。

最近、 @moroや私を中心に、テストから開発を駆動するという方向で、とある活動を始めました。その活動の中では、 @t_wadaさん技術顧問として巻き込んで活動を進めています。そんな取り組みを少しここにまとめます。

f:id:kazucocoa:20160301195056j:plain:w300

取り組みの前段階

先日、私はテストエンジニアというロールに焦点を当ててテストという言葉に対する2種類の話をいたしました。TDDのようにテストによって開発を駆動していく側面の話と、人の認知・感じ方に寄った仕様自体含めてテストしていく側面の話です。

その際、会の傍でt_wadaさんらと私たちが開発するWebアプリケーションのテストコードに関して少し話をしていました。

現在、クックパッドのWebアプリケーションには2万を超えるRSpecのテストケース(example)が存在します。すべてのテストを単純に実行すると、完了までに相当に長い時間を必要とします。そのため、『分散テスト実行システムRRRSpecをリリースしました』などを始めとした取り組みを行い、現実的な時間でテストコードが完了する、という状態を維持してきました。ここでいう現実的な時間とは、過去、JaSST'14 Tohokuにて高井がお話ししたCIにかかる時間は10分以内、多くとも20分以内にしていることをさします。(スライドはこちら)

これにより、テストコードとその開発プロセスによって、巨大なクックパッドのWebアプリケーションが壊れにくいように開発を続けることをある程度担保してきていました。一方で、その実行時間や依存関係、テストコードの巨大さは日々のサービス開発に伴って増加の一途をたどっています。ある機能を確認するための重複したテストコードや時間などに依存するコードのような依存性の高いコードなど、保守を困難にさせるコードも散見されます。これにより、テストが不安定になって開発に影響を与えることもありました。

別な取り組みとして、分割が進むサービスに対する依存性問題に対して『サービス分割時の複雑性に対処する: テスト戦略の話』や『マイクロサービス時代を乗り越えるために、Rack::VCRでらくらくアプリケーション間テスト』といったものも進めています。これは、クックパッド社内で取り組んでいるサービスの分割を進めると直面していくであろう課題に対して、私たちの環境ではどのような解決策が現実的なのか、という試行錯誤になります。

本来、私たちはサービス開発を続け、価値あるものを創造し続けたいという想いを持っています。そのために、テスト(コード)をよりうまく使うことに対して力を使い、サービス開発に力を注いでいきたいと考えていました。そのためにはやはりテスト(コード)は開発の中で必要な手段であり、さらにはその手段の価値を引き出すために テストが開発を駆動するという側面にもちゃんと取り組む必要性があるのではと考え、t_wadaさんをその方面の技術顧問として巻き込み、アドバイスを頂きながら変化する道を歩み始めました。

テストが開発を駆動する世界

テストが開発を駆動する世界の1面として、例えばテストしやすい設計にするというものがあります。これは、奇怪なことをしなくとも単純な形で対象となる機能の動作を確認しやすくする、というようなものです。多くの場合、そうすることで開発を継続していくなかでも動作を確認しやすいコードを保てる可能性が高くなります。

このなかで、社内でとある時間に依存するコードに対してあるやりとりがありました。その世界の実現に向けて実際にどのようなやりとりが行われているのか想像できる例の一つです。

時間の依存性を除く

Rubyだと、timecopといった時間を操作するgemがあります。一般に、リファクタリング系の書籍やテストに関する書籍などにおいて、時間に対する依存性はテストを困難にする要因の1つであると広く知られています。私たちの間でも、過去に時間に対する依存性に関して議論が起こりましたが、当時の現実解としてtimecopを使うようになりました。

その依存性に対して、最近またある議論が行なわれました。依存性を除いたテストを構築するにあたり、現在のクックパッドの開発に沿うように導入するにはどうすれば良いか、というような議論です。

f:id:kazucocoa:20160301195018j:plain

f:id:kazucocoa:20160301195045j:plain

このようなテストしやすい形からシステムを考え、それを実現していくというのはテストが開発を駆動する、という一例です。この取り組みの結果、後になっても時間が関係する機能の組み合わせに対する動作確認を容易にできるようになると、サービス開発を行う場合も極端に臆病にならずに様々な取り組み、確認、リリース、検証というサイクルを、障害を予防したうえで回すことができるようになってきます。

JSのテストを書くための、実例を使った社内勉強会

t_wadaさんに技術顧問をやっていただいている中で、テストに関する社内勉強会をやる機会がありました。先日行った、ある社内勉強会のことです。

最近、私たちのサービスの1つである『おいしい健康』がリニューアルされました。このサービスは、以前に『もう失敗しない!プロジェクト書きなおして、最高の開発環境を手に入れる』というタイトルでtechlifeにも書かれたサービスです。このサービスの実コードを題材に、JavaScriptのテストを書くための勉強会を行いました。

f:id:kazucocoa:20160301195101j:plain

健康チームはサービスとして1つのRailsアプリに分離しましたが、そのテストコード、特にJavaScriptに対するテストの書き方に悩んでいました。愚直にSeleniumなどを使いE2Eのようなテストを実施した場合、そのテスト実行時間や安定性、ロジックに対する網羅性が損なわれるのは明らかです。JavaScript界隈自体動きが早いので、ロジックに近いところのテストを十分に満たすには、その動きにも十分に追従する必要がありました。そのような動きの早いJavaScript界隈の話に追従しつつテストの形を考え、さらにはサービス開発を同時に進めるということは負荷のかかりすぎるものでした。

そこで、JavaScriptに関しては知見も豊富なt_wadaさんに協力してもらい、主にはJavaScriptを対象としながらもテストコードに対する勉強会を開きました。結果的には、約20名のエンジニアに参加してもらい、約2時間にわたり学び、議論し、理解を深めることができました。

JavaScript界隈におけるテストツールの分類であったり、そもそものテストコードの区分やその独立性などの基礎的なところといった、比較的幅広い話をしていただきました。例えば、以下のサイボウズの佐藤鉄平さんの33枚目の資料を参考にしながら、JavaScript エコシステム、テストツールの種類や機能、守備範囲をお話いただきました。

また、それに付随して以下の通りいくつか関連するURLを補足、共有いただいたりしました。

f:id:kazucocoa:20160301195051j:plain

サービスを分割した直後はテストコードも小さく、比較的高速に開発を回すことができます。一方で、何も考えずにテストコードを書いていくと防ぎたい不具合も防げなかったり、時間がかかりすぎるものになったりします。そんな課題に対して、このような勉強会という形で目指すべき姿やその取り組みの具体例を共有し、開発を支える、といった取り組みも行っています。

健康チームは足がかりを得ることができたらしく、手を出すことができるところから少しずつ学んだことを生かそうとしています。

テストのパターンを学ぶ

テストコードを書く人に取って著名な書籍にxUnit Test Patternsがあります。社内勉強会で、こちらの書籍を学びながら、JavaScriptだけではなく、Android/iOS/Rubyなど問わず、どういうパターンがあるのかを学ぼうと考えています。

http://xunitpatterns.com/Cover-Small.gif

通常の設計に対するデザインパターンのように、テストコードにも同様のパターンが存在します。それらを活用することで、よりサービス開発に注力できるだけの基礎力がついてくると思っています。

これから

今後、このような活動や実際にテストコードを書くなどして、今あるテスト(コード)の課題を解決していきながらも、同じ失敗を予防できるような形を作り上げたいと思っています。これにより、先のエンジニアトークナイトで話したことがあるように、副次的にでもサービス開発を支えたり、サービスの品質を向上するためのより良い手段としていきたいと考えています。

私たちは、このような取り組みをより深化させるために仲間を募集しています。特に、ソフトウェアエンジニアとしてテストによって開発を駆動させたい人や、より品質を高めるための組織を築き上げることに興味のある人、いかがでしょうか。

Viewing all 726 articles
Browse latest View live