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

Safariで入力したアカウント情報をiOSアプリで使う

$
0
0

 こんにちは。ユーザーファースト室の中村(@_nkmrh)です。 先日リリースしたクックパッドアプリ v7.6.0 には iCloud の Keychain に保存されているクックパッドアカウントを、アプリから利用する機能を追加しています。具体的には次のような機能です。

1. Mac の Safari から cookpad にログインします

Login to Safari

2. アカウント情報を iCloud Keychain に保存します

Save account and password

3. iPhoneのcookpadアプリを立ち上げ、ログインボタンをタップすると、Safari でログインしたアカウントが選択できるようになっています

select_account

 このように、Mac 又は iPhone の Safari からクックパッドを利用していた人が、アプリにログインする際、面倒な入力をせずにログイン出来るようになりました。ぜひ試してみて下さい。

 ※この機能を使用するには、事前に下記の設定が必要です。また、iOS 8がインストールされたiPhone 5以降、iPad 第4世代、iPad Air、iPad mini、iPad mini Retinaディスプレイモデル、iPod touch 第5世代でご利用いただけます。

  • Mac > System Preferences > iCloud > Keychain > ON
  • iPhone >設定 > iCloud >キーチェーン > ON
  • iPhone >設定 > Safari >パスワードと自動入力 >ユーザー名とパスワード > ON

実装

 以降、この機能の実装方法を紹介します。

  1. Xcodeプロジェクトの Associated Domains に webcredentials の設定を追加します
  2. apple-app-site-association ファイルをWebサイトのルートに配置します
  3. アプリから SecRequestSharedWebCredential 関数を呼び、アカウント・パスワードを取得します

1. Associated Domains

 Associated Domains にWebサイトのドメインを追加します。 - Xcode > Targets > Capabilities > Associated Domains

`webcredentials:example.com`

2. apple-app-site-association

 apple-app-site-association ファイルを作成します。このファイルは、Webサイトのルートに配置しておくもので、連携するアプリのApp Idを記述したファイルをAppleが認可する証明書で署名したものです。

  • webcredentials.jsonファイルを作成し、以下の内容を記述します。

    {"webcredentials":{"apps":["XXXXXXXXXX.com.example.myapp"]}} 

    ※(XXXXXXXXXXの部分はApp Id Prefixを指定します)

  • .p12ファイルを Keychain Access.app から書き出します。SSL証明書を右クリックで選択し書き出しを選択します。

    Certificates > SSL証明書 > Certificates.p12

    Handoffを実装する際は iPhone Developer の証明書が使えたのですが、今回私が試した範囲では iPhone Developer の証明書ではうまくいきませんでした。うまくいかない場合は、Webサイトで使用しているSSL証明書を使用して下さい。

  • webcredentials.json ファイルを Certificates.p12 ファイルで署名します。下記のシェルスクリプトを実行して下さい。

#!/bin/sh
openssl pkcs12 -in Certificates.p12 -clcerts-nokeys-out output_crt.pem
openssl pkcs12 -in Certificates.p12 -nocerts-nodes-out output_key.pem
openssl pkcs12 -in Certificates.p12 -cacerts-nokeys-out sample.ca-bundle
cat webcredentials.json | openssl smime -sign-inkey output_key.pem -signer output_crt.pem -certfile sample.ca-bundle -noattr-nodetach-outform DER > apple-app-site-association
  • 作成した apple-app-site-association をWebサイトのルートに配置します。

3. アプリの実装

 iCloud Keychain のアカウント情報を取得するには SecRequestSharedWebCredential関数を使用します。

void SecRequestSharedWebCredential ( CFStringRef fqdn, CFStringRef account, void (^completionHandler)( CFArrayRef credentials, CFErrorRef error) );
- 第一引数の `fqdn` は取得したいWebサイトのドメイン名を指定します。`NULL` を指定すると、Associated Domains の設定に追加したドメインが使われます。

- 第二引数の `account` はアカウント名を指定します。 `NULL` を指定すると利用可能なすべてのアカウントが返ります。

- 第3引数の `completionHandler` の第一引数 `credentials` に見つかったアカウント・パスワードが格納されます。
SecRequestSharedWebCredential(NULL, NULL, ^(CFArrayRef credentials, CFErrorRef error) {
        if (!error && CFArrayGetCount(credentials) > 0) {
            CFDictionaryRef credential = CFArrayGetValueAtIndex(credentials, 0);

            // credential から アカウントとパスワードが取得できます
            NSString *email = (__bridge NSString *)(CFDictionaryGetValue(credential, kSecAttrAccount));
            NSString *pass = (__bridge NSString *)(CFDictionaryGetValue(credential, kSecSharedPassword));
            dispatch_async(dispatch_get_main_queue(), ^{
                // 取得した メールアドレスとパスワードでログインする
            });
        } else {
            NSError *_error = (__bridge NSError*)error;
            // _error ? @"アカウントは見つかりませんでした。" : @"キャンセルボタンがタップされました。";
        }
    });

 また、アカウント情報の取得の他、追加・削除(SecAddSharedWebCredential関数)・作成( SecCreateSharedWebCredentialPassword関数)が用意されています。

おわりに

いかがでしょうか。以上の手順で iOS アプリの面倒なアカウント入力を無くすことができます。ぜひ試してみてください。また、Keychain周りのライブラリ UICKeyChainStoreが Shared Web Credentials に対応しているのでこちらを利用するのも良いと思います。


参考URL

iOS Developer Library > Shared Web Credentials Reference

MacからiPhoneに遷移させよう


より良い組織を作るために

$
0
0

はじめに

f:id:ryokatsuma:20150410194248j:plain

こんにちは、投稿推進部部長の勝間です。

突然ですが、皆さんは「組織における課題」について考えたこと、意識したことはあるでしょうか。 「組織における課題」なんて言葉を使うと、たとえば

  • 事業戦略の方向性
  • 人事評価制度
  • マネジメント層の育成

など、少し高いレイヤーの話が思いつくでしょうか。 ともすれば自分とは無関係な話のように思えるものかもしれません。

一方で、このようなものはどうでしょうか。

  • なんとなく、最近社内の空気変わった気がする
  • なんとなく、隣の部署が何やってるかよくわからない

このような、もやっとした感覚、は普段働いている中で感じたことがある人も少なくないかもしれません。 こういった「具体的な何か」というより「抽象的な違和感」を私たちが抱くことも組織における課題といってもいいかと思います。

このような組織における課題、違和感を認識したとき、私たちはどのように向かい合うべきでしょうか。

  • 経営陣やマネージャに相談する
  • まぁ、会社はそういうものだよなと、思い込む
  • 違和感が溜まって転職を考える(?)

これ以外にもいろいろな手段が考えられるでしょう。 本エントリーではこのような組織における課題に対して、 個々人で解決に向けて動き、より良い組織をつくろうとする動きを紹介したいと思います。

どういう会社にしていくか

話は2014年秋に戻ります。

当時、私を含めて技術領域4部門のマネージャは「経営、組織のレイヤーで考えてどういう会社にしたいか」をお互い考えてみるお題を経営陣から受けました。

業務の合間にマネージャ同士でディスカッションを交わし「エンジニア文化」「海外展開」「営業利益」... などいろいろな切り口で案を出すものの、 いまいちしっくりくる考えにたどり着けません。そこで、私たちは課題解決型のアプローチで考えることにしました。 つまり、組織において「何が課題か」を考えて、それを解決してより良い組織にするアプローチです。

「開発速度を速くし続ける」「人事制度」「社内ルールの改定フロー」「サービスへの愛着をもっと増やす」... などさまざまな課題が挙げられました。 そんな中、私たちの結論としては、「組織における多くの問題はコミュニケーションの課題に帰着できるものが多い」というものでした。

たとえば

  • 誰が何をやっているのかよく分からない
  • 他部署でやっていることに興味を持てていない
  • 熱を持って仕事に取り組めない

などが具体的な例(冒頭で例に挙げた話ですね)で挙がりましたが、 これらも結局身近な人たちとちゃんと話し、分かり合う場がそもそも圧倒的に少ないことに帰着するのでは?という仮説がありました。 この課題に対する解としては、「自分たちで社員同士で交流できる場を設け、運営する」と置きました。 私たちのオフィスにはキッチンもあり、交流しやすい環境が幸運にも用意されているので、それを自主的にもっと活用していくという考えです。 お酒や食べ物などを用意して社員同士で交流できる場、いわゆる「TGIF」と言うとシンプルかもしれませんね。

当初の考えからかなり現実的なラインに落ちていますが、これは言い換えれば 「マネージャとは言え、経営層ではない、あくまで一社員の立場で立ち向かい、自分たちで解決できそうな課題、およびその解決方法はなにか」を考えた結果とも言えます。ターゲットとその問題をシンプルに考えるようにした、というわけですね。

また、このような自分たちの考えを経営層に伝えたところ、「確かに現実的ないい落とし所だね」というフィードバックをもらったので、この路線で進めることにしました。

コンセプト

その後、マネージャたち議論を重ね、コンセプトを固めていきました。ポイントは2点です。

定期開催

実際、毎週自分たちで運営するのはかなり大変なのは目に見えていましたが、キッチンが他の勉強会やイベントで利用されていない限り、 可能なかぎり毎週行うことを目標にしました。 これは、予定が入っている人も「毎週やってるのなら今日くらい顔出してみるかな」と思ってもらえる状態に、 可能なかぎり多くの人が交流できる状態にしたい、という狙いです。 *1

会社に任せず、自分たちで場をつくる

こちらの方が重要に考えているのですが、「運営を会社に任せることはしない」というスタンスを崩さないようにしています。 たとえば今回の課題感も人事部と相談して予算をとってもらい、会社としてオフィシャルの定期イベントとして開催することも可能です。 ただ、その場合は「会社にやってもらってる」感が残ってしまい、「自分たちで場をつくり、環境を変え、より良い組織にしていく」という意味合いはかなり薄くなってしまいます。

とはいえ、最初は予算も全くない状態からのスタートになります。そこで

  • マネージャ4人が自分たちで5000円づつ出し合い、2万円の予算から開始
  • 会はカンパ制にし、予算が尽きるまで行う(残金は常に開示)

という方針を決定しました。

また、会の名前は覚えやすく理解しやすくするために 「Cookpad Lounge」という名前に、TGIFらしく金曜の夜に開催することとしました。

Cookpad Lounge

準備

方針が決まったので、具体的な準備をすすめていくことにします。 幸運にもビールサーバがオフィスに保管されていたので、見栄えが良さそうな(?)樽ビールをカクヤスで電話注文し、 塊肉などウリになるような食材を選定します。TODOリストは、Github Enterpriseでプロジェクトを作成し、Issueで管理を行いました。

f:id:ryokatsuma:20150607235806p:plain:w360

図: 当時のTODOリスト

また、意識を高めるために公式サイトもMiddlemanで作ることにしました。 ここでの参加ボタンがどれだけ押されたかをGoogleAnalyticsでイベントトラッキングし、何人くらいが参加するかを予測し食材量の調整をすることにします。このへんの準備はエンジニアならではのところで、速いスピードで進んでいきました。

f:id:ryokatsuma:20150607162950p:plain:w360

図: 現在の公式サイト

共用カメラ

準備が整ってくるとCTOも協力してくれ、Eyefiとデジカメを利用して、撮影したら即社内ポータルに公開される共用カメラシステムを空き時間にサクッと構築してくれました。 Cookpad Loungeなどラウンジやキッチンで撮影した内容がリアルタイムにイントラに共有されることで、コミュニケーションのきっかけや集客にも効果は見込めそうです。

社内告知

準備がある程度整った段階で、社内ポータルで告知を行いました。

f:id:ryokatsuma:20150608002200p:plain:w360

ここでは、90名のスタッフが反応してくれました。全員参加してくれるわけではないことは理解しつつ、約1/3にあたるスタッフが反応してくれたこと、 また個別に他の経営陣からも期待の声を聞けたことは良かったです。

初回の開催

f:id:ryokatsuma:20141107194634j:plainf:id:ryokatsuma:20141107195812j:plain

当日はかなりバタバタしましたが、結果として40~45人のスタッフが(中には同じオフィスのZaimの方も)参加してくれ、

  • カンパ: 71,357円
  • 支出: 26,137円
  • 収支: 45,220円

という収支結果になり、無事2回目以降も開催することができることになりました。 中には準備や調理も手伝ってくれるスタッフも数人いて、第一回目としては成功といっても良さそうなものになったと思います。

振り返り

定期開催を行うには、常に開催ごとに振り返りを行い、問題点を改善していく必要があります。 私たちは毎回週明けの月曜に定例で30分KPTの時間を取ることにしました。例えば、第一回のKPTではこのような内容が出ました。

# Keep
- 目標人数20人以上
- ビールサーバ

# Problem
- 肉の責任範囲が曖昧で当日の調理が遅れた
- 飲み物がビールしか無いと辛い

## Try
- メインの調理担当はIssueで管理する
- ソフトドリンクを準備

リーンスタートアップではないですが、Cookpad LoungeもこのようにPDCAを細かく回しながら改善を続けていっています。

課題とその解決

さて、改善を行う中でも、すぐには解決できない、根深い問題も出てきます。

特定の部署の人が参加しない

毎回Cookpad Loungeの告知は社内ポータルで行うものの、バックオフィスや営業系の人たちは必ずしも日常的に社内ポータルに依存しない人たちが多く、 見逃されている、気づかれていないことが多くありました。「アレ、いつ開催されてるの?」「Lounge今日なんですか!?」なんて声もよく上がります。

参加者の層が偏っている

上で書いた問題と近い話ですが、発起人が技術領域のマネージャということもあるのか、参加者の7割前後は男性エンジニアになっています。 当然、特定の層に偏っていると他の職種の人たちや女性は参加しづらい雰囲気になってしまいます。

このような問題は、当初の「社員同士で交流できる場」の目的に反するものなので、今日現在もいろいろ手を打っています。

告知チャンネルを最適化

最初は社内ポータルに加え、全社メールを配信、件名の工夫も行い告知をおこしたこともありますが、 全社宛のMLは得てして「自分向けの情報」という実感を得ることができず見逃されていることが多いのも事実です。 そこで、オフィス内にいれば自然と情報が視界に入るようにビラを刷ってドアに貼るようにしました。 結果的に「ビラ見たから来たよ」と言ってもらえる事例も出てきて、一定の効果は出てきはじめています。

f:id:ryokatsuma:20150607225430p:plain:w240

図: 実際に利用された町内会のお祭り風の告知ビラ

新しく入社した人に積極的に声掛け

クックパッドでは新しく入社した人に、他部署の先輩社員がメンターとして定期的に相談に乗る制度があります。 私自身も数人のメンティーがいるのですが、彼らは入社直後は頑張っても自分たちの部署の人たちとコミュニケーションを取ることでいっぱいであるはずなので、Cookpad Loungeのように他部署の人と話せる場は積極的に提示するようにしています。

まとめ

Cookpad Loungeの現状

ちょうどこのエントリが公開される週の金曜日に第12回目が開催される予定になっています。

最初の発起人4人のうち、1人はスペインへ出向、1人は子会社へ出向となりオリジナルメンバは私を含め2人になってしまいましたが、 「私も手伝います!」と言ってくれるメンバーも出てきて、運営を続けることができています。

まだまだ当初描いていた「組織における課題」を解決するな価値を出するところまで至っていませんが、それでも毎回新しい人が顔を出すようになってくれ、 少しづつ存在価値を出せてきたかなと思っています。

会社側からの参加

回を重ねるうちに「人事イベントを合同でやらせてくれませんか?」と打診されることもでてきました。 たとえば前回は学生たちの懇親会と合同で開催され、50人以上のスタッフが参加して非常に盛り上がりました。 また経営陣も定期的に参加してくれ、社員たちとざっくばらんに会話ができる機会も増えてきました。

このように、最初は個々人で始めたCookpad Loungeも、最近は会社の方からの動きも出てき始めました。

人が人を呼ぶ

少しづつ規模が大きくなることで、人が人を呼ぶ流れもでき始めています。 たとえば、最近入社したデザイナは「もっといろんな人と話したい」ということで運営側に回ってビラのデザインも行ってくれています。 また、最近入社された松浦弥太郎さんが パンケーキを振る舞ってくださり、それを目当てに今まで参加していなかった社員も参加する、のような流れも出てきました。

f:id:ryokatsuma:20150206193109j:plain

図: 牡蠣が大量にカンパされ、群がる人々

組織は生きもの

「組織」と言うと、自分とは無関係な、なんだか良く分からない、会社の片隅でこっそり出来上がる無機質なようなものという印象があるのではないでしょうか。 でも、実際は普段社内で見かける人たちのいろんな考えや狙い、想いを元に出来上がっているものです。 つまり、無機質なものではなく、生きもののように常にその形を変えて成長していくのが当然です。

クックパッドは2015年6月現在、300人前後のスタッフがいます。 これだけの数のスタッフが集まると、多かれ少なかれ「あれ?」と思うことも出てくることはやはり事実です。 とはいえ、そんな中で指を咥えてじっと見ているだけではなく、自分たちができる範囲で少しづつ動いていくことも可能です。 自分たちはCookpad Loungeという名のTGIFという形を取りましたが、他にもいろんな向き合い方はあるでしょう。

この読者の皆さんも「今の組織をもっと良くしたい」「特に問題意識はないけど、少なくとも今の状態を保ちたい」などと思う人も少なくないかと思います。 本エントリが、そのような人たちが「何か自分もやってみようかな」と思えるきっかけになれば幸いです。

f:id:ryokatsuma:20150327190207j:plain

*1:実際は、2015年6月現在、隔週開催を基本運営方針にしています

夏の技術職インターンシップのお知らせ

$
0
0

技術部長の小川です*1

クックパッドでは春の技術インターンシップに引き続き、夏の技術職インターンシップを開催します。

エンジニア向けサマーインターンシップ 2015

この技術職インターンシップは17日間にわたって開催されます。大きく分けて5日間の座学と10日間の実践開発で構成されています。5日間の座学ではクックパッドエンジニアがクックパッドで利用している技術を紹介します。内容は大きく分けて、Railsによるアプリケーション開発フロー、iOSアプリ開発、Androidアプリ開発、サービス開発論、プログラミング論、機械学習の6つの分野にわたります。それぞれ半日から1日かけて行なわれ、座学での講義と実習を組み合わせてみなさんにクックパッドの技術を体験していただきます。

後半の10日間ではメンターとなるエンジニアと一緒に開発をしていただきます。インターンというと、運営側が出したお題に対して開発をするケースもありますが、今回のインターンでは開発現場に実際に入っていただき、クックパッドが直面する課題や問題を一緒に改善・解決していただきます。そのため前半の座学に合格した人だけとはなりますが、クックパッドの開発がどのように行われているのかを体験する機会だと捉えていただければと考えています。

クックパッドの技術や開発手法に興味がある学生のみなさん、ぜひ夏休みを利用してクックパッドの夏の技術職インターンシップに参加してみてはいかがでしょうか。クックパッドの技術を知り、さらに実際の開発を体験できる機会を活用するために、みなさんからのご応募をお待ちしています。

また同時に総合職インターンシップの募集も開始されました。こちらもクックパッドがどのようにサービス開発に取り組んでいるかを体験していただく機会だと考えていますので、興味がありそうな友人や知人へ紹介していただければ幸いです。

総合職向けサマーインターンシップ 2015

*1:会員事業部 副部長でもあり、人事部 エンジニア人事企画リーダーでもあります。

データドリブンでユーザー体験を改善する試み

$
0
0

こんにちは。サービス開発エンジニアの出口 (@dex1t) です。
私は4月までユーザーファースト推進室にて、ユーザー体験の数値化や、その下地作りに取り組んでいました。まだ模索段階ではありますが、本エントリにてこの試みの現状をご紹介します。

点だけでなく線も検証する

リーンスタートアップのBMLループに代表されるように、サービス開発において、検証のフェーズは非常に重要です。 クックパッドでも、開発と検証はセットで考えられており、施策の良し悪しを指標から判断することは日常的に行われています。

ただ同時に、施策単体の検証だけでなく、施策を実施した結果、サービスを通してユーザーが得る体験がどうなったかを検証することも重要です。 クックパッドで言えば、「レシピを探す」機能 (点) だけを改善するのはもちろん、その前後関係を含め「レシピを探し、レシピを決め、作ってよかった!と思えた」という一連の体験 (線) の達成度合いも検証する必要があると言えます。

このような、サービスを通してユーザーが得た体験を、数値化し検証することは難しいものです。クックパッドではこれまで、ご意見ボックスを設置するなどして、定性的なデータを元に判断してきました。 しかしモバイルアプリを提供する機会が増えるにつれ、サービスを機能として捉えるのではなく体験として捉え、エンジニアもデザイナーも数値を武器にしつつ、機能やUIをデザインし開発をしていくことが、これまで以上に重要になってきました。

ユーザー体験をストーリーとして定義する

ユーザー体験を検証するにあたって、まずはサービス開発者が、どのような体験をユーザーに提供したいのかを定義する必要があります。これをユーザーストーリーと呼びます。

クックパッドでは、新しくサービスを企画する際に、アプリケーション定義ステートメントシートと呼ばれる企画フレームワークをよく使います。このシートのなかに、ユーザーストーリーも項目として含まれています。これにより、サービスを企画する際には、ストーリーについて考える機会が生まれます。アプリケーション定義ステートメントシートの詳細については、こちらのエントリで触れられていますので、ぜひご覧ください。

ここでは、以前私が企画・開発を担当していた、iOS版みんなのカフェのユーザーストーリーをご紹介します。 みんなのカフェは、食や暮らしの話題を中心とした掲示板です。その中で、「のんびり時間をつぶしたいユーザー」のストーリーとして以下を定義しました。

  1. 家事の合間にほっと一息。面白そうなスレッドを読んで時間を潰そうと思う
  2. 特に目的は持たずに、スレッド一覧から気になる話題を探してみる
  3. 気になったスレッドの内容、レスを見る
  4. わかる!確かに!と共感する
  5. まだ時間があるので他のスレッドも探してみる

f:id:dex1t:20150610163604p:plain

ユーザーストーリーの達成状況を測る

ここからは先ほど定義したユーザーストーリーの達成状況をもって、ユーザー体験の検証を行っていきます。

ユーザーの行動を測るためのツール

クックパッドでは、ユーザー行動を計測し分析するためのツールとして、Mixpanelを新規アプリを中心に導入し始めています。 この手のツールとしてはGoogle Analytics (以下, GA)が一般的ですが、GAは多くのユーザーの行動を俯瞰的に捉えることを得意とする一方で、ある特定のユーザーがどんな行動を取っているのかを捉えることは不得意です。 Mixpanelは、このGAのデメリットを補完する役割として併用しています。 またMixpanelの利点として、イベントトラッキングをファネル分析やリテンション分析と連携することが容易な点が挙げられます。

なお、ここからはMixpanelについての言及が多くなりますが、Localyticsなど他のツールも同等の機能を備えており、Mixpanelに限った内容ではありません。

Mixpanelでファネル分析

ユーザーストーリーは、ユーザーがサービス内で行う複数の行動から構成されるものです。この行動 (イベント) の発生を、Mixpanelでトラッキングしていきます。

例えば、先ほどのユーザーストーリーであれば、以下のようにストーリーをアプリ内の行動に近似します。

  1. 家事の合間にほっと一息。面白そうなスレッドを読んで時間を潰そうと思う
    • [行動] アプリを起動する (Open App)
  2. 特に目的は持たずに、スレッド一覧から気になる話題を探してみる
    • [行動] スレッド一覧を一定量スクロールする (Find Topic)
  3. 気になったスレッドの内容、レスを見る
    • [行動] スレッド詳細画面に遷移する (View Topic)
  4. わかる!確かに!と共感する
    • [行動] Likeボタンを押す / レスを投稿する (React)
  5. まだ時間があるので他のスレッドも探してみる
    • [行動] スレッド一覧画面に戻り、一定量スクロールする (Find Topic)

そして、これら一つ一つの行動をファネルと捉え、その達成状況をファネル分析することで、ユーザーストーリーの達成状況が計測できます。例えば、このストーリーをファネル化したものが以下です。

f:id:dex1t:20150610163826p:plain注: 図中の値は、実際の値とは異なります

この例だと、CVR 42.0%という数字が、このストーリーの達成状況となります。

最重要指標を決める

Mixpanelのようなツールを導入すると、ついついあらゆるボタンのクリック数を測りたくなってしまいます。 クックパッドでは、GAとの使い分けとして、Mixpanelはユーザーストーリーの達成状況を計測することに限って使うことにしています。

また1つのサービスには、複数の登場人物が存在することが多々あります。例えばみんなのカフェで言えば、暇つぶしに色々なスレッドを閲覧したいユーザーのほか、食に関する疑問があり質問を投稿しようと思っているユーザーなど、モチベーションの異なる複数のユーザーがアプリ内に混在します。そして、そのユーザーそれぞれに利用ストーリーが考えられます。

全ての登場人物のストーリーを追うのは難しく、それを目指すと数値に溺れるような感覚になりがちです。そのため、サービスが成長する上で、現段階で最重要となるストーリーに絞って、達成状況を測ることが重要になります。

みんなのカフェであれば、新規に立ち上げたコミュニティであるため、まずはスレッドを投稿してもらうことが最重要だと考えていました。そのため、まずはPVや回遊率ではなく、スレッドの投稿数を重要指標としていました。したがって、先程の例で言えば、「食に関する疑問があり、質問を投稿しようと思っているユーザー」に関するストーリーの達成状況を最重要視していました。

このような考え方は、Lean AnalyticsのなかでOMTM (One Metric That Matters)と呼ばれています。また同様の考え方がMixpanelの設計思想にも現れており、Mixpanelのヘルプページでも重要指標のみに集中せよとあります。そのため、このような考え方のもとで使うツールとして相性が良いと考えています。

iOS版みんなのカフェでの改善例

以上のような考え方に沿って、実際に数値ドリブンでユーザー体験の改善を行った、iOS版みんなのカフェの事例をご紹介します。

先にも述べましたが、iOS版みんなのカフェでは、コミュニティというサービス特性上、アプリ起動からスレッド投稿にいたるまでのユーザーストーリーの達成状況を、最重要視していました。 実際にスレッド投稿を行うためには、大きく分けてユーザーに以下のような行動をとってもらう必要があります。

  1. スレッド投稿ボタンを押す
  2. カテゴリーを選択する
  3. スレッド内容を入力する
  4. スレッドを投稿する

f:id:dex1t:20150610163918p:plain

ここでMixpanel上で、投稿ボタンを押してから投稿が完了するまでのファネルを作ってみると、以下のようになります。

f:id:dex1t:20150610163937p:plain

ここで気になるのは、最初のステップです。投稿ボタンを押してから、カテゴリーの選択を完了したユーザは、全体の58%しかおらず、約42%のユーザーがここで離脱してしまっています。 カテゴリーを選択するだけのシンプルな画面にもかかわらず、離脱率が高いことから、改善の余地がありそうです。

そこで、改善するための仮説として以下を考えました。

  • 投稿する気持ちが高まっているユーザーに対しては、カテゴリー選択画面よりも入力画面を最初に出したほうが、ユーザーの期待に沿っているのではないか
  • 文章を入力する前から、これから入力する文章のカテゴリーを選ぶのはそもそも難しいのではないか

この仮説のもと、カテゴリー選択画面と、スレッド内容入力画面を入れ替えてみることにしました。これにより、以下のようなフローになりました。

f:id:dex1t:20150610164000p:plain

そして、再びファネル分析をおこなった結果が以下です。仮説通り、入力画面からカテゴリー選択画面への遷移での離脱は少なく、全体のCVR (投稿ストーリーの達成率) も向上しました。また、念のため検定にかけてみると、有意な差がみられました。

f:id:dex1t:20150610164016p:plain

ここでご紹介した事例は、かなりシンプルなものですが、Mixpanelでファネル分析を行いつつ、ユーザーストーリーの達成状況を改善していく雰囲気を感じていただければと思います。

特に、今回のような分析は、あえて専門のツールを使うまでも無いかもしれません。ツールを使ったからといって、これまで分からなかった何かが分かるわけではありませんし、仮説を立てるところこそ、サービス開発者の頭の使い所です。 Mixpanelのようなツールを使うことで、分析が手軽にわかりやすく可視化され、仮説を立てやすくなり、その結果サービス開発のスピードがあがることに価値があると思っています。

まとめ

本エントリでは、ユーザー体験をストーリーとして定義し、その達成状況を計測し改善するための試みについてご紹介しました。実際には本エントリのようなアプローチだけで、ユーザー体験の検証が十分かと言えばそうではなく、インタビューやユーザーテスト、開発者自身によるドッグフーディングなど、他の手法と組み合わせることが重要だと考えています。

UXというとデザイナーの仕事のように思われがちですが、UXはデザインだけでなく、エンジニアリングも含めて成り立つものです。個人の感覚だけに頼るのではなく、チームの共通認識として重要指標を決めることで、エンジニア, デザイナー, ディレクターが一丸となってユーザー体験の向上に取り組むことができるのではないかと思っています。このようなサービス開発にご興味がある方は、ぜひエンジニア・デザイナー採用にご応募ください!

Android のライブラリづくりとライセンスについて

$
0
0

こんにちは。ヘルスケア事業部の関口(@tanukiti1987)です。

普段はRailsを使いつつ、おいしい健康というサービスの開発をしているのですが、ご縁があって3ヶ月ほど前からAndroidアプリ開発に携わっています。

今回はそんなAndroid用のライブラリを誰でもお手軽に公開して、Android開発をグイグイ高速化していこう!という観点からお話をしたいと思います。

忙しい人向けの3行まとめ

  • Android向けライブラリを公開するのは、わずか5ステップのみ
  • 公開するライブラリにはきちんとライセンスを書こう
  • ライセンスに特にこだわりがなければ、Apache2.0, MIT あたりがよい

はじめに

iOSとAndroidの開発上の違い

私自身、前職で2年ほどiOSをメインで開発していたということもあり、Androidは機種差分、バージョンの断片化こそあれ、ネイティブアプリ作りの根幹は変わらないだろうと思っていました。

しかしながら、AndroidではiOSとは違い、カメラ周りの制御が相当苦労することに始まり、iOSで用意されているような、例えば UIImageViewを角丸にするような処理、 ページングを便利に表現してくれる UIPageControlのUIパーツが無いなど、大きいところから細かいところまで、違いがあるものでした。 (当たり前といえば、当たり前なのですが。。)

痒い所に手が届くライブラリが少ない

ライブラリを探してみるも、自分のプロダクトには不要なコードを含んだ大きなライブラリ。 はたまた、軽い処理のものもちらほら見かけるも、ライセンスが書いてなくて使えない。なんていう経験がしばらく続きました。

なければ作ればいい!

きっと誰かが悩み苦しんだ道だろうけど、その道を通るにはまた同じように悩んだりしないといけないのは、少しモヤモヤするものです。 ぜひこの記事をキッカケにAndroidの開発効率を高めるべく、小さくても十分に使う余地のあるコードをライブラリ化して、公開していきましょう!

ライブラリ化によるメリットは大きい

自分が現在作っているプロダクトの一部のロジックやUIパーツをライブラリ化することで、いくつかのメリットが出てきます。

  • ビジネスロジックを追い出すことで実装はシンプルにわかりやすくなる
  • クラスの責務も明確に
  • 結果、テストも書きやすくなる

誰にでも公開するにあたり、ビジネスロジックは当然含ませることができません。

一つ一つのクラスがビジネスロジックと疎結合になることにより、より実装がシンプルになることが期待できます。

すると、クラスごとの責務がキレイにわけられたり、結果テストもしやすくなる!コードの保守性も良くなる!など、よりよい開発サイクルを回すことができるようになります。

では、そんな期待を込めつつ早速ライブラリを作ってみましょう。

GitHubにライブラリを公開するまでの5ステップ

ここからは 実際にライブラリを作って、GitHubに公開するまでの方法をお伝えしていきます。 今回は試しに、ListViewGridViewScrollViewの上に展開した上で設置するパーツを提供するライブラリを公開してみます。

またIDEは、AndroidStudioを使用しての説明になります。

今回、試しに作ったものは https://github.com/tanukiti1987/ExpandedViewsに公開しておりますので、ソースファイルの置き場などに困ったときにはご参考ください。

では、早速とりかかってみましょう!

STEP 1. 新しいプロジェクトとモジュールの準備

まずは、おなじみ。プロジェクトを作ります。

f:id:tanukiti1987:20150615163326p:plain

名前は、viewを展開した上で表示するので、ExpandedViewsと名づけてみます。 今回はActivityなどは含みませんので、 AddNoActivityで進んで頂いて大丈夫です。

そして、新しいモジュールを追加します。 ここでは、 AndroidLibraryという項を選択し、パッケージ名等を決めていきます。

f:id:tanukiti1987:20150615163459p:plain

f:id:tanukiti1987:20150615163624p:plain

GitHubに公開することを意識しつつ、的確な名前をつけてあげましょう。

STEP 2. モジュール化したいコードを書き進める

めでたく箱が用意出来たので、出来上がったパッケージの下にモジュール化したいコードを書き進めていきます。

今回のViewであれば、こんな感じ。

f:id:tanukiti1987:20150615163628p:plain

通常のプロジェクトで開発しているときと同様、作られたパッケージの下にコードを追加していくイメージです。

STEP 3. AndroidArchive(AAR) をビルドする

ここまで来たら、もうリリース一歩手前。

AARといういわゆるAndroidライブラリの固まりのようなものをビルドします。

AAR についてはこちらに詳しく載っています。

プロジェクトディレクトリ直下で、以下の様なコマンドを叩き、AARを作成します。

$ ./gradlew assembleRelease

しばらく待った後に BUILD SUCCESSFULが出ればおそらく無事にAARが作成できているはずです。

$ find . -name '*.aar'

などのコマンドを使い、AARができているかを確認してみましょう。

STEP 4. Maven リポジトリを配置する

今回はGitHubをMavenリポジトリとして、ライブラリを使用できるようにしたいため、その形式に合わせたディレクトリ構成でAAR等を配置するスクリプトを 追加したモジュール以下の build.gradleに追記します。

今回であれば、expandedviews/build.gradleになります。

そこで、以下の様なスクリプトを追加します。

## リポジトリのルートディレクトリ名を指定## 使用するときのURLに影響しますdefrepo = new File(rootDir, "repository")

apply plugin: "maven"

uploadArchives {
    repositories {
        mavenDeployer {
            repository url: "file://${repo.absolutePath}"
            pom.version = '1.0.0'
            pom.groupId = 'com.tanukiti1987.expandedviews'
            pom.artifactId = 'expandedviews'
        }
    }
}

それぞれの設定項目が示すところはMavenのPOMの生成を読んでいただくとして、これらの設定は、実際にライブラリを取り込む側のプロジェクトで記述するライブラリの指定方法に影響してきます。

ここで1つ気をつけなければいけないのは、groupId はライブラリのプロジェクト名にすることです。

1つのライブラリプロジェクトには、複数のartifact が含まれるものがあります。

facebook が出している stethoであれば、ライブラリプロジェクトに stetho-okhttpstetho-timberなど複数の artcifact が含まれるものもあるため、 groupId は開発者の namespace とイコールではないことに注意してください。

このスクリプトを書き終えたら、プロジェクト直下で以下のコマンドを実行します。

$ ./gradlew uploadArchives

これを行うことで、プロジェクト直下に repositoryというディレクトリが出来上がり、AARもそれに合わせて配置されます。

STEP 5. GitHubに公開する & 実際に使ってみる

あとは、GitHubにリポジトリを作り、pushするだけです。

pushが完了した時点で、このライブラリを使用したいプロジェクトは、各 gradle.buildファイルにおなじみの以下の記述を追加するだけで、使えるようになります。

repositories {
    maven { url 'http://raw.github.com/tanukiti1987/ExpandedViews/master/repository/' }
}

dependencies {
    compile 'com.tanukiti1987.expandedviews:expandedviews:1.0.0'
}

この dependencies以下に追記する記述が、先ほどの mavenDeployerでの設定に関係してきますので、ここをイメージしながら、groupIdやartifactIdを決めるのが良いかも知れません。

この記述をした後に、プロジェクトでビルドを行えば、めでたく自分のアプリケーション内で使えるようになります。

f:id:tanukiti1987:20150615163635p:plain

ここまででひとまずライブラリを公開する手順でした。文章で表現すると長かったものの、実際に作業するとそれほど時間がかからないことにお気づきになるかと思います。

忘れちゃいけないライセンス表記

ライブラリ公開の記事もこれでおしまい!と思ってしまいそうですが、公開して誰もが使えるようにするにはもうひとつ大事な作業があります。

そうです、ライセンスの表記です!

ライセンスを書かないとデフォルトのライセンスが適用される

もし、ライセンスを書かずにGitHub等で公開したコードは、公開者自身が全ての著作権を持つ、All rights reservedなライセンスが適用されます。

これは、私的利用については問わないが、ソフトウェアの複製、改変、再頒布は行えないことを意味します。

iOSやAndroidに関しては、ライブラリのロジックをアプリに載せた上で、クライアントにアプリを配布します。

この場合、ソフトウェアの再頒布にあたる可能性が出てきてしまい、アプリでそのライブラリを使用するのが難しくなってくることが考えられます。(利用許可を作者に直接取れば、別の話ですが。)

こうなってくると、気軽な気持ちでそのライブラリは使えず、せっかくライブラリを公開している意義を失ってしまいます。

Android では Apache2.0 ライセンスがよく使われる

Androidで著名なライブラリは、だいたいがApache2.0のライセンスが使われています。

これは、Googleの AndroidOpenSourceProjectがそれを選んでいるところが大きいと思われます。

Android is about freedom and choice. The purpose of Android is promote 
openness in the mobile world, and we don't believe it's possible 
to predict or dictate all the uses to which people will want to put our software.

http://source.android.com/source/licenses.html

このページにもあるように、Androidはモバイル環境でのオープンさを推進させ、全ての人々がAndroidのソフトウェアを使いたくなる世界が来ることを期待している。(意訳)とあります。

しかし、なぜMTIやBSDライセンスなども、Apache2.0 と親しいライセンスではありますが、親しいだけに1つに統一するという意を込めて、このページではApache2.0を選んでいるものと思われます。

MITライセンスで、ゆるく公開してみる

とは言え、個人でライブラリを公開するときには Apache2.0にも近く、更にゆるいMITライセンスを選択するのも手でしょう。

MITライセンスでは、作者の名前を記すことと、作者に一切の責任を求めないことを明記しています。

Apache2.0とMITが大きく異るのは、作者から利用者への特許権の許可を明確にしているところです。

このことから、どちらを選択しても大きく内容は異ならないことから、個人での小さなライブラリについてはMITでもよいと思っています。

ライセンスをお手軽に追加する方法

そんな背景を理解しつつ、自分のライブラリにもライセンスを追加していきます。

この記事では、2つの方法のいずれかでライセンス表記をすることをおすすめします。

1. Choose a licenseを使用してみる

先ほどのAndroidOpenSourceProjectの思想に沿うのであれば、Apache2.0 を使えば良いのですが、少しこだわってライセンスを選びたい。どんな種類のライセンスがあるか知りたい。といった時におすすめです。

このサイトでは、どういったマインドでライブラリを公開したいかを選べば、それ相当のライセンスが表示されます。 表示されたライセンス中にある 著作者を書き換えるだけで立派にライセンスとして成立します。

2. GitHubのリポジトリ作成時にライセンスを選んでしまう

私も最近知ったのですが、リポジトリを作るときにライセンスを選べば自動でそのライセンスのREADME.mdをつくってくれる機能がGitHubにあります。

f:id:tanukiti1987:20150615163632p:plain

その機能を使い、ライセンスを追加してしまうのもひとつの手です。

合わせて覚えておきたい

お世話になったライブラリを列挙する

私が今作っているアプリや他のアプリでもそうですが、ネイティブアプリにはライセンス画面(ページ)が存在しているものが多いです。

アプリの制作の途中で、ライセンスページを作っていると、様々なライブラリにお世話になった実感が湧いてくるものです。

それがありがたいという思いと共に、様々な人達に助けられて出来上がったアプリなのだと感慨深い気持ちになったりします。

ぜひ、皆さんが作る商用アプリはもちろん、個人アプリでも、ライセンス表記の義務のあるライブラリを使用した際には、ライセンス表記のページを作ってみてください。

自分(たち)のプロダクトが色々なライブラリに支えられてできたことを知ると、よりプロダクトに対して愛着が湧いたりしてくるのでオススメです。

自分のライブラリを使ってもらう喜びを感じてみる

また、自分が作ったライブラリがどこかのアプリに使われていることを知ったら、きっと嬉しくなることでしょう。

たとえ小さくてもライブラリをバシバシ公開してき、Android開発者同士がより高速に開発できる日を目指していきましょう!

おわりに

今回は、手軽にライブラリを公開できる方法をご紹介しました。

この方法でライブラリの公開を行うと、作者は簡単に公開できるものの、利用者では build.gradlerepositorydependenciesの二箇所にライブラリ使用のための追記を行わなくては行けません。

もし、たくさん使われるようなライブラリに成長した場合、または最初から利用者にも手軽に使えることを目指す場合には jcenterに登録する方法があります。

jcenterでの公開になると、アカウントの登録であったり公開までのハードルが今回ご紹介した方法よりはハードルがあるため、割愛させていただきました。

まずは、気軽に!というところでまとめとさせていただきます。

Cognitoを使ったらAndroidアプリプッシュ通知実装にサーバサイドプログラミングが不要になった話

$
0
0

こんにちは、id:hogelog(会員事業部 小室)です。

現在自分が開発しているAndroidアプリのプッシュ通知の実装に Amazon Cognito, Amazon SNS, Amazon DynamoDBを使ったらアプリコード(と、AWSの設定)だけで機能が実現できてしまい、予定していたサーバサイド実装がまったく不要となったのでその知見を共有します。

アプリプッシュ通知の要件

今回実装したプッシュ通知の要件は以下です。

  • プッシュ通知を許可したユーザ全員に共通した内容を一斉通知
  • 通知はバッチプログラムから週に数回程度
  • 年内には一万ユーザぐらいに利用されること目標
  • GCMトークンはデータストアに記録しておく
    • 将来的にはA/Bテストなどをおこなうことも可能なように

当初はこれらの機能を実現するため、適当なRailsアプリでGCMトークンを受け取ってうまいことあれこれするAPIを実装しようと考えていました。

Cognitoを使った構成

Cognitoを使うとAmazon Mobile SDKを用いてAWSの機能に直接アクセスすることが可能となります。 今回は以下の機能を利用しました。

  1. Cognitoによる匿名ユーザ認証
  2. SNSにGCMトークンを登録
  3. SNSのTopicにSubscribe
    • アプリユーザに一括配信するため
  4. DynamoDBに直接データを登録
    • アプリからの扱いやすさ、一括読み込みなどのしやすさから

1. 匿名ユーザ認証

  • Cognito ConsoleでIdentity poolを新規に作成
    • Cognitoはまだ東京リージョンに来ていないので北米リージョンを利用(レスポンスを待つ画面が無いため遠いリージョンでもさほど問題なし)
    • Enable access to unauthenticated identitiesにチェックを入れて匿名ユーザ認証機能を有効とする
  • Cognito SDKの依存関係を追加
compile 'com.amazonaws:aws-android-sdk-cognito:2.2.2'
  • ユーザ認証処理をアプリ内に実装
CognitoCachingCredentialsProvider credentialsProvider = new CognitoCachingCredentialsProvider(
        context,
        "us-east-1:aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", // 作成したCognito identity pool ID ARN
        Regions.US_EAST_1
);

これでアプリ初回起動時に匿名ユーザが自動的に作成され、次回以降自動的に同一ユーザとして認証されます。

2. GCMトークンをSNSに登録

compile 'com.amazonaws:aws-android-sdk-sns:2.2.2'
  • 前述したCognito認証情報を用いてGCMトークンをSNSアプリケーションに登録
AmazonSNSClient snsClient = new AmazonSNSClient(credentialsProvider);
snsClient.setRegion(Region.getRegion(Regions.AP_NORTHEAST_1));

CreatePlatformEndpointRequest createRequest = new CreatePlatformEndpointRequest();
createRequest.setPlatformApplicationArn("arn:aws:sns:ap-northeast-1:123456789012:app/GCM/AndroidPushApp"); // 作成したプラットフォームアプリケーションARN
createRequest.setToken("xxxxxxxxxxxxxxxx"); // GCMサーバから受け取ったGCMトークン
CreatePlatformEndpointResult platformEndpoint = snsClient.createPlatformEndpoint(createRequest);

3. SNSのTopicにSubscribe

  • SNS Consoleからトピックを新規作成
  • SNSアプリケーションへの登録のレスポンスに含まれるSNSエンドポイントを用いてSNS Topicを購読
String endpointArn = platformEndpoint.getEndpointArn();
snsClient.subscribe("arn:aws:sns:ap-northeast-1:123456789012:campaign-development", "application", endpointArn); // 作成したトピックARN

4. DynamoDBへのデータ保存

  • DynamoDB Consoleから新規テーブルを作成
    • プライマリキーの属性はハッシュ、ハッシュ属性は文字列とする
  • テーブルに対応するPOJOクラスを作成
@DynamoDBTable(tableName = AwsConstant.DDB_TABLE_NAME)
publicclass PushToken {
    @DynamoDBHashKey(attributeName = "CognitoIdentityId")
    privatefinal String cognitoIdentityId;

    @DynamoDBAttribute(attributeName = "GcmToken")
    privatefinal String gcmToken;

    @DynamoDBAttribute(attributeName = "SnsEndpoint")
    privatefinal String snsEndpoint;

    public PushToken(String cognitoIdentityId, String gcmToken, String snsEndpoint) {
        this.cognitoIdentityId = cognitoIdentityId;
        this.gcmToken = gcmToken;
        this.snsEndpoint = snsEndpoint;
    }

    public String getCognitoIdentityId() {
        return cognitoIdentityId;
    }

    public String getGcmToken() {
        return gcmToken;
    }

    public String getSnsEndpoint() {
        return snsEndpoint;
    }
}
  • 取得したGCMトークン、SNSエンドポイントをDynamoDBに保存
AmazonDynamoDB ddbClient = new AmazonDynamoDBClient(credentialsProvider);
ddbClient.setRegion(Region.getRegion(Regions.AP_NORTHEAST_1));
DynamoDBMapper ddbMapper = new DynamoDBMapper(ddbClient);
PushToken pushToken = new PushToken(credentialsProvider.getCachedIdentityId(), gcmToken, snsEndpoint);
ddbMapper.save(pushToken);

実際のコードはエラーハンドリングなどもあるためもうちょっと複雑になりますが、 基本的には以上に示したように非常に簡単な記述のみでSNS、DynamoDBにアクセスできます。

プッシュ通知の配信処理

上述したモバイルアプリの処理でSNS Topicに各Androidアプリに紐付いたトークンが登録されるため、プッシュ通知の配信処理は バッチ処理(or 管理ツール等)からSNS Publishエンドポイントを呼び出しを呼び出すだけで完了します。

sns_client = Aws::SNS::Client.new(region: "ap-northeast-1")
sns_client.publish(
  topic_arn: "arn:aws:sns:ap-northeast-1:123456789012:campaign-development", # 作成したSNS Topic ARNmessage: message_json, # 送りたいメッセージのJSONmessage_structure: "json",
)

(弊社の標準的なプログラム言語であるRubyを例としましたが、特に言語は問いません)

2015年6月15日現在、Topicあたり(デフォルトで)1000万までの購読がサポートされているので相当な規模のプッシュ配信とならない限りこの構成で問題なさそうです。

SNS は、デフォルトでトピックあたり 1,000 万のサブスクリプション、アカウントあたり 3,000 のトピックを提供しています。制限の引き上げをリクエストするには、http://aws.amazon.com/supportからお問い合わせください。

http://aws.amazon.com/jp/sns/faqs/#limits-restrictions

Cognitoユーザへの権限の付与

弊社ではAWS IAMの権限管理にmiamを利用しているため、 以下のようなDSLでCognito認証ユーザにSNS、DynamoDBへのアクセス権を付与しました。

role "CognitoUnauth", :path=>"/"do
  assume_role_policy_document do
    {"Version"=>"2012-10-17",
     "Statement"=>
      [{"Sid"=>"",
        "Effect"=>"Allow",
        "Principal"=>{"Federated"=>"cognito-identity.amazonaws.com"},
        "Action"=>"sts:AssumeRoleWithWebIdentity",
        "Condition"=>
         {"StringEquals"=>
           {"cognito-identity.amazonaws.com:aud"=>
             "us-east-1:aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"},
          "ForAnyValue:StringLike"=>
           {"cognito-identity.amazonaws.com:amr"=>"unauthenticated"}}}]}
  end

  policy "SNSAccess"do
    {"Version"=>"2012-10-17",
     "Statement"=>
      [{"Effect"=>"Allow",
        # アプリから利用するActionのみ許可"Action"=>[
          "sns:CreatePlatformEndpoint",
          "sns:SetEndpointAttributes",
          "sns:Subscribe",
          "sns:Unsubscribe"
        ],
        # アクセス可能リソースを作成したアプリケーション、Topicに限定"Resource"=>[
          "arn:aws:sns:ap-northeast-1:123456789012:app/GCM/AndroidPushApp",
          "arn:aws:sns:ap-northeast-1:123456789012:campaign-development"
        ]}]
     }
  end

  policy "DynamoDBAccess"do
    {"Version"=>"2012-10-17",
     "Statement"=>
      [{"Effect"=>"Allow",
        # アプリから利用するActionのみ許可"Action"=>[
          "dynamodb:PutItem",
          "dynamodb:UpdateItem"
        ],
        # アクセス可能リソースを作成したアプリケーション、Topicに限定"Resource"=>[
          "arn:aws:dynamodb:ap-northeast-1:123456789012:table/push_tokens"
        ],
        # アクセス可能レコードを限定"Condition"=>
         {"ForAllValues:StringEquals"=>{
          "dynamodb:LeadingKeys" => ["${cognito-identity.amazonaws.com:sub}"]}}}] # ハッシュキーをCognitoユーザIDに限定
     }
  endend
  • SNS、DynamoDBの権限付与は実際に扱う操作のみに限定
  • DynamoDBへのアクセスではCognitoユーザIDがハッシュキーとなるレコードにしかアクセスできないように制限
  • (余談) ちなみにIAM権限追加などもアプリエンジニアがPull Request形式で送り、レビュー&マージ&適用される弊社のインフラのdev-ops環境、非常に面白いです。

例示したAndroidアプリコード例については https://github.com/hogelog/aws-mobile-sdk-exampleにまとめましたので、詳細についてはこちらをご覧ください。

まとめ

以上のようにモバイルアプリからAWSの機能を直接扱うことのできるCognitoにより、サーバサイドの実装なしで 今回のAndroidアプリへのプッシュ通知要件を満たすことができました。

おかげで30営業日ぐらいの見積もりをしていたスケジュールが10営業日ぐらいのスケジュールに圧縮されそうな勢いで大喜びです。(作業量見積もりという意味では失敗しているのですが……)

「モバイルアプリ開発者」「サーバサイド開発者」というくくりで自分の立ち位置を限定させていると、 ふと気づけばあっという間に仕事がなくなっているということもありえそうです。

クックパッドでモバイルアプリ・Rails・AWSなど垣根を越えて幅広い領域を駆使してユーザに価値を届けたいエンジニアの方は http://recruit.cookpad.com/からのご応募、お待ちしております。

雑な雑談からのCognito

ちなみに本案件でのCognitoの採用はGCMトークンを受け取るAPIサーバの実装をめんどうに思った私が弊社インフラ部の星 (@kani_b)さんに 「実装めんどくさいんですけど、なんかこうグッと楽になんとかなりませんか?」と相談したところ「それCognitoで良いのでは?」という返答を得たところから始まりました。 雑なコミュニケーションはやっぱり大事だなあと実感した事例となりました。

ちなみにCognitoは2015年の夏には東京リージョンにもやってくるらしいので今後国内のサービスでももっと利用しやすくなりそうです。各位検討してみてはいかがでしょうか?

シンプルで移行しやすいデータベースシャーディング

$
0
0

技術部の小野(taiki45)です。クックパッドではこれまで様々なデータベースの負荷対策を行ってきましたが、シャーディングは行われていませんでした。しかし先日クックパッドの認可サーバーが利用している MySQL サーバーの負荷分散のためにクックパッドで初めてのシャーディングを行ったので、Rails アプリケーションでのシャーディングの事例のひとつとしてその際の手法をご紹介したいとおもいます。


構成

Before

データベースは1マスター、1ホットスタンバイ、バッチ用の1リードレプリカで構成されています。Read オペレーションのほとんどはキャッシュ層に逃しています。

f:id:aladhi:20150622102704p:plain

After

データベースの各ロールにつきそれぞれ1台ずつマシンが増えています。

f:id:aladhi:20150622102711p:plain

シャーディングが必要になった背景

認可サーバーのアクセストークンの作成・削除時の Write オペレーションが急増し、レコード数自体も急増していて、データベースサーバーサーバーの IO 性能が限界に近くなったので Write オペレーションの負荷分散をする必要がありました。

負荷の増加の仕方が急だったので、素早く負荷分散をする必要がありました。そのため、変更が少なく検証量が少なくすむシャーディングを選択することにしました。

考慮したこと

変更を少なくする

検証やテストの量を減らし素早く移行するために、なるべく既存の構成を変えないこととアプリケーションの変更が少ないことを目指しました。

シンプルに保つ

対象アプリケーションは開発が活発で、単純なクエリが多いという特徴があったため、メンテナンス性をなるべく維持できるシンプルなシャーディングの導入が適切だと考えました。

オンラインで移行できる

対象が認可サーバーなのでダウンタイムがあると広範囲のサービスに影響します。なので、オンラインで移行できることを優先度を高く設定しました。また、レコード数が増加し続ける可能性が高く、物理ノードの追加がある程度頻繁に行われることを想定していたので、ノード追加時もダウンタイムが発生しない方法を検討しました。

ロールバックできる

クックパッドのサービスの性質上リードが大半を占めていてキャッシュ層を追加することで解決したり、また適材適所のデータベース選択によって今まではシャーディングの必要がありませんでした。今回は初めて行うシャーディングだったので未知数なことが多く、移行自体もロールバックできる方法を検討しました。

シャーディング手法

分散モデル

キー分散モデルを採用します。具体的には、値域が正の整数であるようなハッシュ関数を利用して hash(v) mod Nの結果でノードを決定します。単純に N = ノードの数とした場合、ノード追加時に計算結果が大きく変わってしまい大規模なデータの移動が必要となるので、仮想ノードとしてハッシュスロットを導入します。ハッシュスロットの総数が変わってしまうと大規模なデータ移動が必要となるので、ハッシュスロットの総数は十分大きい数に設定します。ノード追加時は旧ノードから新ノードへとハッシュスロットの割り当ての一部を移すことで必要なデータ移動を最小限にします。

ハッシュ関数はパフォーマンスが良くて(今回のようなデータで良くバラけて生成コストが低い)、標準ライブラリに入ってる CRC32 を利用しました。

ノード管理

ノード数を2の冪に固定することでレプリケーションを利用してデータ移動を事前に準備しておくことができます。(101) -> (101,201)という1ノードから2ノードの移行時には 101のデータを201にレプリケーションしておくことで移行期のデータの移動が不要になります。(101,201) -> (101,201,301,401)という2ノードから4ノードへの移行時には101のデータを301に、201のデータを401にレプリケーションしておきます。

f:id:aladhi:20150622104653p:plain

非一貫性とロールバック可能性

この2つの問題を解決するために、マルチマスターレプリケーションを利用します。

データの非一貫性の問題とは、物理ノードを追加してハッシュスロットの割り当て設定を更新する際に発生する問題です。 ハッシュスロットの割り当て設定をすべてのアプリケーションサーバーで同時に更新することは難しく、あるアプリケーションサーバー A では設定が更新され、別のアプリケーションサーバー B では更新されていない状況が発生します。 このとき、A が新規に追加された物理ノードでデータを変更しても、B ではその物理サーバーをまだ参照していないので、A から行われたデータ変更の一部が B からは見えません。事前にマルチマスターレプリケーションを動作させておくことで、レプリケーション遅延によるデータの非一貫性は依然として存在しますが、この問題をある程度軽減できます。

f:id:aladhi:20150622130232p:plain

物理ノードを追加した段階でも マルチマスターレプリケーションをしている間は旧ノードにも新ノードに入るデータが同期されるので、問題発生時などにロールバックできます。

Rails way に乗っている場合は auto increment な idカラムがあると思うのですが、ALTER TABLEできないほど巨大なテーブルに育ってしまった場合は idカラムを残したままシャーディングを実施する必要があります。その場合は、マルチマスターレプリケーション時に idが衝突してしまいますので、衝突しないように auto_increment_increment, auto_increment_offsetを事前に変更します。

移行オペレーション

  1. レプリケーションを利用して、元のノード(101)の完全コピーとなる新ノード(201)を作成します。
  2. 新旧ノードで idがぶつからないように auto_increment_increment, auto_increment_offsetを設定します。その上で、マルチマスターレプリケーションを開始します。
    • note: MySQL のバージョンによっては auto_increment_incrementの設定が異なるコネクションが並行に INSERT を行うと duplicate key エラーになることがありました。対策として、プライマリーキーの duplicate key エラーが発生すると AR のコネクションをリセットしてリトライすることをしました。似たような現象のバグが報告されていました
  3. アプリケーション側でシャーディングを有効にし、新ノード(201)にもクエリーするようにします。
  4. 様子を見て 101, 201 のマルチマスターレプリケーションを解除します。

f:id:aladhi:20150622105447p:plain

実装

上述のような仕組みをサポートする gem は見つからなかったので、新しく実装することにしました。ActiveRecord の接続周りで各シャードを参照するような機能は octopusにあったのですが、今回はハッシュスロットの管理や各シャードへのルーティングが必要な機能で、コードベースが大きい octopus を利用するメリットが少ないと判断しました。なので、新しく mixed_gaugeという gem を作りました。やってることに見合うようにコードベースを小さく保ち、ActiveRecord の変更に追従しやすいようモンキーパッチを極力しない方針で作成しました。

結果

特に問題なく移行し運用できています。

今回のシャーディング手法は求めていたものにハマりうまくいきましたが、逆にどういう時には導入すべきではないかも簡単にですが書いておこうと思います。

今回は MySQL を利用したシンプルなものなので、Expiration やキャッシュ層が必要な場合は自前で用意する必要があります。導入に時間的余裕があり、それらの仕組みをあわせて構築する必要がある場合は、最初からそういった機構を備えている分散データベースを利用するほうが良いかもしれません。

複雑な関連があるアプリケーションは今回のようなシャーディング手法をそのまま適用するのが難しいと思います。その場合は他のシャーディング手法を採用したり、データを再設計し分散データベースを利用してしまうのが良いと思います。

まとめ

今回はシンプルで移行がしやすいデータベースシャーディングの手法についてご紹介しました。

キー分散モデルとハッシュスロットを採用し、レプリケーションを活用できるノード管理の規約を導入しました。このような手法をサポートする mixed_gaugeという gem を作成しました。

この記事がご参考になりましたら幸いです。

クーポンコードの打ち間違えを防ぐために工夫した話

$
0
0

こんにちは。会員事業部ビジネス開発グループの高田です。

クックパッドは今年、株主優待制度として、プレミアムサービス一年間無料クーポンを贈呈しました。本エントリではクーポンコードを打ち間違えて、意図せず他の人のクーポンコードを使用するのを防ぐために工夫した話をご紹介します。

はじめに

クーポンコードは入力のしやすさを優先して数字だけの文字列にしました。はじめは rand 関数を使って生成しようとしていたのですが、数字の打ち間違えや順序間違いで、意図せず誤使用してしまうのを防ぐためにチェックサムを加えるのがいい、と同僚から助言をもらいました。

いくつか調べて見たところ、Luhn アルゴリズムが上記を満たしていたので利用することにしました。

Luhn アルゴリズムの利用

Luhn アルゴリズムとは、誤り検出のためのチェックサム符号で、1 桁の間違いや隣接する数字の順序間違いを検出できるという特徴があります。検証の手順は次の通りです(Wikipediaより引用)。

  1. 右端のチェックディジットを1番目として、偶数番目の桁を2倍にする。
  2. 2倍にしていない桁も含め、各数字の総和を求める(2倍にした桁が2桁になった場合は、それぞれを別々の数字として加える)。
  3. この総和の下1桁が0なら(つまり、10で割り切れる場合)、この番号はLuhnアルゴリズムでは正しく、そうでない場合は正しくない。

71894 を例に検証すると、

  1. 7 2 8 18 4
  2. 7 + 2 + 8 + 1 + 8 + 4 = 30
  3. 30 は 10 で割り切れる

なので正しい、となります。

Ruby には luhn-ruby という gem が存在するので、こちらの実装を読んだ方が分かりやすいかも知れません(luhn-ruby の実装)。なお、このアルゴリズムはクレジットカード番号にも使われています。

実際に使用した Ruby のコードは以下のようになります。

require'luhn'defgenerate_coupon_code(length)
  number = Array.new(length - 1) { rand(10) }.join
  number + Luhn.checksum(number).to_s
end

このようにすることで、隣接する数字の順序が入れ替わった数字文字列(例えば 71984)は生成されにくくなりました。また、1 桁の数字を変更することでクーポンコードを予測することもできなくなりました。

おわりに

本エントリでは、Luhn アルゴリズムを使ってチェックサムを加えることで、クーポンコードの打ち間違えを防いだ話をご紹介しました。私自身は、チェックサムを加えることで比較的安全に数字文字列を生成できるという知識がなかったので、勉強になった出来事でした。


iOS 開発で storyboard と xib をうまく使い分けるプラクティス

$
0
0

Web エンジニアだったはずがひょんなことから iOS アプリを書き始めてはや3ヶ月。ヘルスケア事業部の濱田です。

iOS アプリで画面遷移を実現するためには様々な方法があります。

  • コードのみを使う方法
  • xib を使う方法
  • storyboardを使う方法
  • etc.

初めはかなり混乱しましたが、最終的には storyboard と xib の合わせ技に落ち着きました。 今回はこの方法についてご紹介します。

storyboard を使うか、xib を使うか、それが問題だ

アプリの UI 部品の配置は結構たいへんな作業です。とくに Autolayout の制約の設定などは、コードのみで設定するのは困難でしょう。Interface Builder の支援をなるべく活用したいところです。 そこで、storyboard もしくは xib ファイルを利用して ViewController(以下 VC) を 書いていくわけですが、どちらにも利点と欠点があり、場合によって使い分ける必要があります。

よくある定義の仕方としては、以下の2つがあるようです。

storyboard に複数の VC の定義と遷移を一緒に書く

Pros

  • アプリの遷移を集約して書くことができ、処理の流れが理解しやすい

Cons

  • storyboard がどんどん大きくなって編集がコンフリクトする
  • 同じ VC を複数個置きたいと思っても、ビューの定義を再利用できない

xib ファイルごとに VC を定義して、遷移の定義はコードで行う

Pros

  • 画面を再利用できる

Cons

  • コードでつないでいると、VCのつながりが把握しにくい

前者の方法だとコードは少なくなりますが、VC の再利用を行うことはできません。 後者の方法を使うと、VC を複数の箇所で使いまわせるのですが、画面の流れを把握するのは難しくなります。

そこでちょっとだけ工夫をして、画面の流れは storyboard で定義しつつ、VC のビュー定義は xib ファイルにおいておく折衷方式にしました。

xib と storyboard を組み合わせて使う

折衷方式には、以下のようなメリット/デメリットがあります。

Pros
  • VC のクラス定義、ビュー定義がそれぞれ1ファイルに対応するので取り回しやすい
  • VCを複数のstoryboardで再利用できる(DRY!)
Cons
  • storyboard の画面上では UI パーツの配置が確認できない

今回取り組んだアプリでは同じ VC が異なる storyboard の遷移の中に現れることがしばしばあり、上記の折衷方式で効率よく実装できました。

作業手順

例として、FirstViewController, SecondViewController という2つの画面を持つアプリを考えます。

FirstViewController あるボタンを押すと、SecondViewController がモーダル表示されるという簡単なアプリです。

f:id:manemone:20150623204145g:plain

xib ファイルにビューのみを定義する

まず、FirstViewController、SecondViewController それぞれのビュー定義を行う xib ファイルを作成します。 xib の中には、VC で表示するための UIView を一つおき、その中に画面部品を配置していきます。(VC ではなく、View のみを定義するのがミソ)。

File's owner で xib と VC を関連付ける

次に、xib の File's owner に SecondViewController クラスを設定します。これによって、xib と SecondViewController の紐付けが行われ、ビュー要素へのアウトレットを持つことができるようになります。

f:id:manemone:20150623203041p:plain

f:id:manemone:20150623203043p:plain

VC の loadView() をオーバライド

さらに、VC の loadView()を以下のようにオーバーライドします。

// Yes, we're on Swift :-)class SecondViewController: UIViewController {
  ...overridefunc loadView() {
      iflet view = UINib(nibName: "SecondView", bundle: nil).instantiateWithOwner(self, options: nil).first as? UIView {
        self.view = view
      }
    }
  ...
}

nibname:にはさきほどの xib ファイルの名前を書きます。 loadView()はその名の通り、VC が管理するビューを読み込むためのメソッドです。通常の動作では VC が格納された storyboard や xib ファイルからビューを読み込むようになっています。ここでは、必ず先ほどの xib のビューを読み込むように設定しておきます。

storyboard でフローを定義する

ストーリーボードでビューの流れを定義してみましょう。FirstViewController のボタンを押すと SecondViewController がモーダルとして表示されるようにします。

VC を一つ追加して、中の View を削除します。 Indentity Inspector の Custom Class で、先ほど作ったクラスを指定し、scene の中に指定したクラス名が表示されれば OK です。この状態でシミュレータで走らせてみると、xib ファイルに記述したビューが読み込まれていることがわかるはずです。

f:id:manemone:20150623203045p:plain

f:id:manemone:20150623203047p:plain

segue で接続

あとは、通常の storyboard 作成と同じように、追加した VC をマニュアル segue で接続し、適切な identifier を付けておきます。ここではそれぞれ "buttonA"、"buttonB"としました。 VC の方では、遷移を促すアクションが発生したとき、設定した identifier で segue を実行すれば OK です。

f:id:manemone:20150623203044p:plain

プロパティを IB 経由で与えて調整する

画面の背景色等、UIに与える初期値を storyboard 側から調整したいときは、Interface Builder の User Defined Runtime Attributes を活用するとよいです。例えば、2つめの SecondViewController に表示されるラベルの色を変更したいときは以下のようにします。

SecondViewController で以下のように設定しておきます

class SecondViewController: UIViewController {
  ...
    @IBOutlet weakvar label: UILabel!

    // この変数に IB から色をセットvar labelBackgroundColor = UIColor(red: 240.0/255, green: 118.0/255, blue: 101.0/255, alpha: 1.0)

    overridefunc viewDidLoad() {
      super.viewDidLoad()

        // 変数 labelBackgroundColor に設定された色をラベルの背景色にするself.label.backgroundColor = labelBackgroundColor
    }
  ...
}

Interface Builder の storyboard で SecondViewController を選択し、User Defined Runtime Attributes に色を設定します。

f:id:manemone:20150623204227p:plain

以上で、冒頭の GIF と同じ挙動の遷移が実現できました。

載せきれなかったコードは以下のリポジトリに置いてあります: ReusableVCDemo

いかがでしょうか。関わった仕事では、基本的に上述のやりかたで VC のビューと画面遷移を記述し、storyboard をまたぐ遷移だけコードで記述するようにしています。 画面遷移を細かい storyboard に分割して定義する場合、同じ VC が複数の storyboard に現れるのはよくあることです。そんなときでも VC を DRY に記述しつつ、かつ storyboard の良さであるビジュアルベースの遷移定義ができて便利ですので、よろしければ参考にしてみてください。

Android版クックパッドアプリの採用している技術の現状確認 2015年版

$
0
0

目次

  • はじめに
  • 技術選択の基本的な方針
  • 技術選択の各論
    • HTTP Client
    • Dependency Injection
    • View Injection
    • Asynchronous Control Flow
    • Object Relation Mapper
    • Logging
    • Fragment

はじめに

技術部の id:gfxです。

Android版クックパッドアプリのリニューアル*1から約1年たちました。現在はリリースごとに5人程度がコミットし、2週間に1度リリースを行う開発体制となっています。プログラミング言語はJavaで、コメントも含めたアプリのソースコードの行数は約15万行です。

本エントリでは、Android版クックパッドアプリで使っている技術、具体的にはライブラリやフレームワークについて紹介します。また、そのための技術選択のアプローチについても概説します。

技術選択の基本的な方針

まず技術選択の方針についてですが、これはサービスの性質に依存すると考えています。クックパッドはすでに15年以上の歴史をもつサービスで、その歴史において ColdFusionからRailsへの置き換えを行ったり Rails 3.2から4.1へのアップグレードを行ったりしており、最新の技術を使って開発できるようにしています。

クックパッドのウェブサービス同様に、モバイルアプリの開発もこの先10年以上続くでしょう。したがって技術選択の基本的な指針においては、この先10年間継続してメンテナンスおよび更新していくことができるかどうかが重要です。Androidに関して言えば、まずGoogleの推奨する標準的な開発環境とサポートライブラリをベースにし、その上で適切な技術を選択していくということも必要です。

技術選択の各論

さて、前置きはこのくらいにして、実際にアプリで選択しているライブラリやフレームワークについて、選択の理由や所感などを紹介していきます。

HTTP Client

Cookpad APIの通信を担うHTTP clientは、Apache HTTP client をバックエンドにしたVolleyを使っています。Volleyは単一のリクエストキューを持ったHTTP clientの実装であり、いくつかの認証を組み合わせて使っているCookpad APIとの通信に適していると考えられたからです。リクエストキューがあると、認証が完了するまで他のリクエストを停止するということが簡単にできます。

しかし、Volleyの採用は間違いでした。VolleyはGoogle I/O 2013で大々的に紹介されたにも関わらず、Gradleから利用しやすいアーティファクトの形でリリースはされず、リポジトリの公開のみにとどまっています。また依存しているApache HTTP clientはAndroid API level 22でdeprecatedになりました。Volleyはバックエンドとなる通信用HTTP clientをカスタマイズできるのですが、その通信部にHttpURLConnectionを採用したHurlStackもApache HTTP clientに依存しています。この状況を踏まえると、Volleyの未来は明るくなさそうです。

また、Volleyはカスタマイズ性や安定性にも難があります。Volleyを利用して実装したアプリ用Cookpad API clientではVolleyを大きく拡張しているのですが、結果的にVolleyのインターフェイスにしたがっているだけでVolley自体のコードはあまり使っていません。これは、Volley自体のロジックではアプリの要求を満たせなかったり、エラーハンドリングに問題があったためです。

なお、最近はOkHttpも補助的に導入しはじめており、Cookpad API clientもいずれOkHttpで置きかえるでしょう。Android 4.4からはHttpURLConnectionのバックエンドがOkHttpになっていますが、これはこれでキャッシュまわりにクラッシュするバグがあるので、最新のOkHttpを直接使うほうがいいでしょう。

Volleyについての反省点は、新しい技術に飛びついて痛い目を見たということです。Volleyの実装で参考にできる点は多々ありますが、今となっては使用はおすすめできません。

Dependency Injection

DIはRoboGuiceを採用しています。導入時には RoboGuice / Square Dagger (Dagger1) / Proton / Transfuseが比較検討され、RoboGuiuceが採用されました。RoboGuiceを採用した理由は次の通りです。

一方でRoboGuiceは、重厚でバグを追いにくいこと、動作が遅いことなどの欠点もあります。

現在はよりパフォーマンスのよい Google Dagger (Dagger2)への移行が議論されていますが、依存関係の記述方法が異なるため移行コストが高く、あまり進捗はありません。

DIは概念が複雑で使うのが難しいフレームワークなので、そもそもDIを使うべきか否かという議論も行っています。いまのところは、DIでコードを劇的にシンプルにできるという利点は大きいと判断し、DIを使おうというところに落ち着いています。

View Injection

Viewの各要素をJava objectにバインドする仕組み(View Injection)は、開発初期はRoboGuiceの @InjectViewのみを使っていました。しかし今年に入ってから ButterKnifeを導入して、こちらも併用しています。ButterKnifeの導入が遅かったのは、RoboGuiceの @InjectViewと ButterKnife の @InjectViewが混ざると混乱するのではないかという危惧のためでしたが、やはり ButterKnife がないと ViewHolderパターンの記述が煩雑であるということで導入しました。

ところで、 Google I/O 2015 で Data Bindingという新しいライブラリが発表されました。これはいままでの View Injection ライブラリを置き換える強力なものだったため、さっそく導入して使い始めています。

Data Binding導入にあたっての利点には次のようなものがあります。

  • ButterKnife+ViewHolderパターンの記述量を大きく減らせる
  • 導入にあたっての学習コストが低い
  • 最悪使わない事になったとしても、元に戻す作業が複雑でない

なお現在リリースされている dataBinding:1.0-rc0ではいくつかバグがあるので、一般的には次の安定版を待つのが無難でしょう。

Asynchronous Control Flow

非同期リクエストのフロー制御用ライブラリとしては、RxJavaを採用しました。それ以外のライブラリではFacebook Boltsなどを検討したのですが、使い方が難しいわりに効果があまり見込めないということで採用はしませんでした。

RxJava以前はコールバックベースのインターフェイスとAsyncTaskをCountDownLatchを駆使して制御していました。まだRxJavaを使っていなコードもかなりありますが、徐々に書き換えは進んでいます。

RxJavaの導入については以下のエントリで詳しく紹介しています。

RxJavaの用途としては連続したHTTPリクエストのネストを重ねることなく記述できるプロミス的な使い方が典型的です。またそれだけでなく、非同期リクエストの待ち合わせも rx.Observable.combineLatest()で行っており、RxJavaを使わないコードと比べるとシンプルで読みやすくなりました。

一方でRxJavaはAndroidコンポーネントのライフサイクルに従った unsubscribe()が難しく*2、何度もバグを出しています。このあたりをカバーするのが RxAndroidなはずですが、RxAndroidの方向性について若干モメているようで、まだ枯れているとは言いがたい技術です。

また、RxJava自体の欠点もあります。たとえば、無限リストと単一のリクエストを同じ rx.Observableで表現するのですが、これらは使い方が異なるため、使う際は注意が必要です。様々なデータのストリームをすべて rx.Observableという一つの型で表現するというRxJavaの特徴が、かえって使いにくくしているという結果になっているのです。

このようにRxJavaの導入は利点も欠点もありますが、RxJavaの根底にあるReactiveX自体の歴史は長く十分に枯れており*3、RxJavaの開発も活発なので、総合的に見て導入のメリットはあると考えています。

Object Relation Mapper

ORMについてはActiveAndroidを採用しており、以下のエントリで詳しく述べています。

クックパッドアプリはその性質上マスターデータがサーバーサイドにあり、アプリのデータはそのほとんどキャッシュです。そこで簡単に使えるということを重視してActiveAndroidを使っています。しかしActiveAndroidの開発は停滞しており、issueやpull-requestは放置されていますから、ActiveAndroidの未来は明るくなさそうです。

将来的にローカルのデータベースを使った機能を拡充したいことを考えると、ORMについてはそろそろ刷新する必要があると感じています。

Logging

クリックイベントなどのメトリクスのログ収集のためのロギングライブラリは、自社で開発したものを使っています。

Pureeは継続して開発しており、現在のv3ではAPIはよりシンプルになり、パフォーマンスも改善しています。必要に応じて自社でライブラリを開発し、メンテナンスしていくという選択が有効なこともあります。

Fragment

FragmentはAndroid組み込みのandroid.app.Fragmentではなくサポートライブラリのandroid.support.v4.app.Fragmentを使っています。これは、サポートライブラリのFragmentは最新の機能をすべて使えるのに対して、組み込みのFragmentで使える機能はアプリのminSdkVersionに依存するからです。

また、安定性という意味でもサポートライブラリのFragmentを選択する理由があります。組み込みのFragmentとサポートライブラリのFragmentは共に継続して開発されていますが、組み込みのFragmentのリビジョンはAndroid OSのリビジョンに依存します。つまり、組み込みのFragmentを使うかぎり、Fragmentの実装のリビジョンを指定できません。それならば、バージョンを指定して使えるサポートライブラリのFragmentのほうが確実に挙動を予測できます。

まとめ

本エントリでは、Android版クックパッドで採用している技術について解説しました。個々の技術は成功したり失敗したりしていますが、たとえ失敗したとしても反省をいかしつつ次の10年を気持ちよく開発できるような技術選択を心がけています。

クックパッドでは、次の10年の技術を見極めたいエンジニアを募集しています!

巨大なバッチを分割して構成する 〜SQLバッチフレームワークBricolage〜

$
0
0

トレンド調査ラボの青木峰郎(id:mineroaoki)です。 好きなRubyのメソッドは10年前からString#slice(re, nth)ですが、 最近はRubyよりCoffeeScriptとSQLのほうが書く量が多くて悩んでいます。

今日はわたしが開発している「たべみる」の背後で働いている 巨大バッチの構成について話したいと思います。

たべみるのバッチは約3000行のSQLで構成されており、 処理時間が1日で4時間程度かかる、そこそこの規模のプログラムです。 このバッチ処理プログラムをBricolage(ブリコラージュ)というフレームワークで構造化する手法について説明します。

「たべみる」とは

まず最初に、「たべみる」がどういうものなのかごく簡単にお話ししておきましょう。

「たべみる」は企業のみに提供しているB2Bの分析サービスで、 クックパッドのレシピ検索の分析をすることができます。 具体的には、特定の語の検索頻度や、どんな語と一緒に検索されているか、 それから急激に検索が増えている語などを知ることができます。

例えば次は「バレンタイン」という語の検索頻度のグラフです。

▼「バレンタイン」の検索頻度グラフ(2014年〜2015年) f:id:mineroaoki:20150626162112p:plain

このグラフを見ると、「バレンタイン」という語の検索頻度は12月26日から上昇を始めていることが見てとれます。 つまりクリスマスが終わったとみるや次のイベント(バレンタイン)に向けて準備を始めているのですね。 恐ろしいことです。

「たべみる」バッチの構成

たべみるは上記の画面を500ミリ秒未満で表示することができます。 これは一般的なRailsアプリに比べれば遅いほうですが、 Amazon Redshiftに直接アクセスして6年分のデータ(10億件を余裕で越えます) に対して分析を行っていることを考えると、実は非常に高速と言える速度なのです。

このような分析を高速に実行できるようにするために、 背後では事前に集計を済ませた、いわゆる「サマリーテーブル」を大量に作成しています。 このサマリーテーブルを作るのが、たべみるバッチの主な仕事です。

たべみるバッチは日次(1日に1回の頻度)で動き、 次のような仕組みでサマリーテーブルを更新します。

▼たべみるバッチ概要フロー f:id:mineroaoki:20150626162328p:plain

まず元データをcookpad.comのメインデータベースであるMySQLと、 Treasure Data(Hadoop)から取得してRedshiftに入れます。 そのあとのキーワードの名寄せや集計はすべてRedshift上で行っています。

たべみるバッチではほとんどのデータ加工処理をSQLで記述しているので、 Redshiftの並列処理の恩恵を十分に受けることができます。

またSQLでの処理に更新(update)や削除(delete)はほぼ存在せず、 90%以上の処理は次のようなinsert selectで行います。 これは処理を羃等にし、vacuumを不要にするための工夫です。

insertinto keyword_combination_recipe_sets
select
    l.item_id as item_id
    , r.item_id as pairing_item_id
    , l.recipe_id
from
    keyword_recipe_sets l
    inner join keyword_recipe_sets r
    on
        l.recipe_id = r.recipe_id
        and l.item_id <> r.item_id
where:
            略
            :
;

巨大なバッチをジョブに分割する

たべみるバッチのSQLが3000行とは言っても、もちろん1つのSQLが3000行ではありません。 上記のようなinsert select文を使った20〜30行くらいのSQLをたくさん組み合わせることで バッチは構成されています。

さて、この大量のSQLをどうやって実行していくべきでしょうか。 とにかく実行するだけでいいのであれば簡単ですね。 例えばRubyなら次のようにpgライブラリを使えばSQLを実行できるので、 このコードでひたすら実行していけばいいわけです。

require'pg'

conn = PG::Connection.open(host: 'localhost', port: 5444, dbname: 'production', user: 'tabemirudev', password: '')
begin
  conn.query(<<-SQL)
      insert into base_search_counts      select…略…      ;   SQL

  conn.query(<<-SQL)
      insert into item_search_counts      select…略…      ;SQL

  conn.query(<<-SQL)
      insert into daily_si      select…略…      ;SQL

  conn.query(<<-SQL)
      insert into weekly_si      select…略…      ;SQL# 必要なだけたくさんSQLを書くensure
  conn.close
end

これではまずいのでしょうか?

当然、まずいのです。 まずいと言っても、メソッドに分けるべきとかそういうレベルの問題ではありません。 例えば次のような疑問・要望が湧いてきます。

  • 処理が失敗したとき、どうやって原因を追うのか?
  • 途中で失敗したらどうなるか? 失敗したところから再起動できるか?
  • SQLの単体テストはどうすればいいのか?

「運用しやすいバッチ」とは、「失敗したときに簡単に対処できるバッチ」のことです。 どこで、何が原因で失敗したのかすぐ特定でき、 問題を解決したら失敗したところから実行を再開できるのがよいバッチです。 しかもそのうえで開発しやすく、テストしやすい仕組みならベストでしょう。

このような要望を満たすために、 大きなバッチは小さな処理ごとに複数の「ジョブ」(プログラム)へ分割して、 バッチジョブの組み合わせで全体を構成するのが一般的です。 例えば1つのジョブは1つのSQL(insert select文など)を実行するだけのプログラムとし、 そのようなジョブをたくさん作ることで全体を組み立てるわけです。

SQLバッチフレームワークBricolageによるジョブ化

このようなバッチジョブを作るために、 たべみるでは独自開発したBricolageというフレームワークを使っています。 「SQL」バッチフレームワークとは言っていますが、現在は主にRedshiftを想定した実装になっています。 Bricolageは先日GitHubでOSSとして公開しました(下記URL)ので、誰でも自由に使うことができます。

Bricolageを使ってジョブを作ると決まった場所へ自動的にログが取られますし、 後述するような様々な機能を活用することで容易に開発や運用ができるようになっています。

たべみるの日次バッチは現在200程度のBricolageジョブで構成されています。 その200くらいのジョブの依存関係を図で表すと次のようになります。

▼ジョブフロー図 f:id:mineroaoki:20150626152818p:plain

Bricolageは1つ1つのジョブを作るためのフレームワークなので、 複数のジョブを連動させて上記のようなジョブフローとして動かす仕組みはあまり作り込んでいません。 いまのところ、ジョブフローをCUIで非並列で動作させる簡単な仕組み(bricolage-jobnetコマンド)だけを用意しています。

ジョブフローを実行する仕組みはジョブ管理システムに任せるのがベストでしょう。 代表的なジョブ管理システムとしては、 日立のJP1/AJS3NRIの千手IBMのTivoli Workload Schedulerなどが挙げられます。

またOSSのジョブ管理システムとしては、 Hinemos(NTTデータ)、 RundeckAzkaban(LinkedIn)、 Airflow(AirBnB)などがあります。

機能だけで言えば個人的にはJP1/AJS3が使いたいのですが、ちょっと高いのと、運用が大変なので導入していません。 しかし最近、バッチの時間がのびてジョブフロー全体の最適化が緊急の課題になりつつあるので、 そろそろ何かしらのジョブ管理システムを導入する予定です。 ジョブフローのGUIは絶対にほしいので、まずはHinemosから試そうと思っています。

Bricolageの特長

Bricolageには次のような特長があります。

  1. SQLにパラメーターを埋め込める
  2. SQLの定型パターンを自動生成できる
  3. dry-runとexplainが可能

利点1. SQLにパラメーターを埋め込める

Bricolageでは「$変数名」のような記法でSQLに任意のテキストを埋め込めるようにしています。 例えば最初にinsert selectの例として見せたSQLは実は変数の展開後のコードで、 ソースファイルには次のように書かれています。

insertinto $dest_table
select
    l.item_id as item_id
    , r.item_id as pairing_item_id
    , l.recipe_id
from
    $keyword_recipe_sets l
    inner join $keyword_recipe_sets r
    on
        l.recipe_id = r.recipe_id
        and l.item_id <> r.item_id
where:
            略
            :
;

あえてprepared statementを無視して純粋に文字列ベースでパラメーター展開(置換)をしているので、 式やテーブル名、カラム名もパラメーターにすることができます。 上記のSQLでもテーブル名を変数にしています($dest_tableと$keyword_recipe_sets)。

また、バッチ全体、サブシステム、ジョブのそれぞれのスコープで変数を定義でき、 実行時に上書きすることも可能です。 そのため、本番で一時的にターゲットテーブル名を差し替えてコピーを作る、など柔軟に運用することができます。

さらに、eRubyのタグを使ってRubyの式を埋め込むこともできます。 例えば本番用のサマリーテーブルを複数まとめて切り替えるためのジョブでは次のようなコードを使っています。

begin transaction;
<% publish_tables.each do |table| %>
  altertable $schema.<%= table %> renameto<%= table %>_org;
  altertable $schema.<%= table %>${target_suffix} renameto<%= table %>;
<% end %>
commit;

この切り替えの処理対象となるテーブルは20以上あり、しかも頻繁に増減するので、 手でテーブルリストをメンテナンスするのは事故のもとです。 この方式ならばそのような変化するテーブル群をまとめて扱うことができます。

ちなみに対象となるテーブルは次のようにテーブル定義にBricolage独特の記法で 「publish」属性を付けることで宣言できるようになっています。 「--attributes」で始まっている行が属性の宣言です。

--dest-table: ANYly_stats--attributes: [publish, replicate]createtable $dest_table
( data_date date encode delta
, item_id int encode lzo

, si real encode raw
, sv real encode raw
, ysi real encode raw

, ci real encode raw
, csv real encode raw
, yci real encode raw
)
distkey (item_id)
sortkey (data_date)
;

利点2. SQLの定型パターンを自動生成できる

2つめの利点は、よく使うSQLの定型パターンをオプションだけで自動生成できることです。

例えばテーブルを洗い替えする場合、ターゲットテーブルと同じ定義のテーブルを別途作成し、 insert selectを完了してからrename(alter table)ですりかえるという手法をわたしは好んで使います。

そのような場合、メイン処理となるinsert select文の他に、 テンポラリーテーブルのdrop table文、create table文や、renameのためのalter table文なども必要になります。 またRedshiftの場合は統計をとるanalyze文やソート順を改善するvacuum sort only、 それに権限を与えるgrantも合わせて実行したいところです。 つまり全体では次のようなSQLになるわけですね。

droptable $dest_table;

createtable $dest_table
( ……
, ……
);

insertinto $dest_table
select……
;

vacuum sort only $dest_table;

analyze $dest_table;

grantselecton $dest_table to $tabemiru_reader_group;

たべみるの場合、余裕で100以上のテーブルがあるので、 これらについていちいちdrop、create、rename……と書きたくはありません。 そこでBricolageでは「ジョブクラス」という仕組みを使ってこれらを生成します。

ジョブクラスは、ジョブで実行されるSQL文のテンプレートです。 例えば「rebuild-rename」というジョブクラスを指定すると、 さきほど述べたdrop、create、renameはBricolageが生成してくれます。 開発者が書かなければいけないのはメインのinsert select文だけです。

また、ほとんどのジョブクラスにはanalyzeオプションやvacuum-sortオプションが 設定されており、これをtrueにするだけで処理後にanalyze、vacuum sort onlyが実行されます。

ジョブクラスとそのオプションはジョブごとに「xxx.job」という名前のYAMLファイルで 指定するようになっています。例えば次のような感じです。

class: rebuild-drop
dest-table: $app_schema.weekly_stats${target_suffix}
src-tables:weekly_si: $work_schema.weekly_si
    yearly_si: $work_schema.yearly_si
    latest_yearly_si: $work_schema.latest_yearly_si
    moving_si: $work_schema.moving_si

    weekly_ci: $work_schema.weekly_ci
    yearly_ci: $work_schema.yearly_ci
    latest_yearly_ci: $work_schema.latest_yearly_ci
    moving_ci: $work_schema.moving_ci
table-def: weekly_stats.ct
vacuum-sort:trueanalyze:truegrant:privilege: select
    to:"$tabemiru_reader"

もちろんこの他に、MySQLからRedshiftへのテーブルコピーのように利用頻度が高いパターンも 「ジョブクラス」として用意してあります。

利点3. dry-runとexplainが可能

Bricolageで作成したジョブはbricolageコマンドで実行できますが、 そのとき、次のように--dry-runオプションを付けることで、SQLを実行せずに表示だけさせることができます。 パラメーターとeRubyタグも展開されて表示されます。

% bricolage --dry-run--job recipe_sets/keyword_combination_recipe_sets-rebuild.sql.job
                                                                            -- 最初のほうはオプションで自動生成されている
\timing on

\set ON_ERROR_STOP false
drop table tabemirudev.keyword_combination_recipe_sets cascade;
\set ON_ERROR_STOP true

-- recipe_sets/keyword_combination_recipe_sets.ct          ここはテーブル定義ファイルから読み込まれた
create table tabemirudev.keyword_combination_recipe_sets
( item_id int encode delta, pairing_item_id int encode delta, recipe_id int encode lzo)
distkey (recipe_id)
sortkey (item_id)
;

-- recipe_sets/keyword_combination_recipe_sets-rebuild.sql.job     以下がSQLファイルに書いた部分
insert into tabemirudev.keyword_combination_recipe_sets
select
    l.item_id as item_id
    , r.item_id as pairing_item_id
    , l.recipe_id
from
    tabemirudev.keyword_recipe_sets l
    inner join tabemirudev.keyword_recipe_sets r
    on
        l.recipe_id = r.recipe_id
        and l.item_id <> r.item_id
where
        以下略

バッチでdry-runができることがどれだけありがたいかは、 バッチを運用したことのある人ならよくわかるでしょう。 実行されるSQLが事前にわかるのは精神衛生上たいへんいい効果があります。

また、--explainオプションを付けて実行すると、 メインとなるinsert selectの部分だけexplainを付けて実行することもできます。 こちらは実際にSQLがサーバーに流れるので、 文法と意味解析チェック(型チェックとか)の代わりとしても使うことが可能です。

Bricolageのその他の機能

ここで説明したBricolageの機能は一部にすぎません。 この他にBricolageには複数データソースを管理して切り替えられる機能や、 ストリーミングロードの仕組みなどが用意されています。

本番投入優先で実装しているのでドキュメントがだいぶ雑なのですが、 気になったかたはぜひGitHubのWikiを眺めてみてください。

Bricolageの今後の開発予定

今後、Bricolageでは次の2つの機能の導入を予定しています。

  1. ジョブ管理システム
  2. ジョブ全体のユニットテスト機能

ジョブ管理システムはHinemosから試すつもり、と書きましたが、 それはそれとして選ぶのも面倒になってきたので、自分で書いてしまおうかなとも思っています。 ジョブ管理システムはいろいろ面倒なことが多いので自分で書くのは避けてきたのですが、 いつまでたっても手頃なものが出てこないので痺れを切らしました。

それから、他にぜひ導入したいのがジョブのユニットテスト機能です。 入力データと正解データを書いておいたら自動的に検証してくれるような機能を予定しています。

『10年戦えるデータ分析入門』6月30日発売!

最後に個人的な宣伝です。

10年戦えるデータ分析入門 SQLを武器にデータ活用時代を生き抜く (Informatics &IDEA)

10年戦えるデータ分析入門 SQLを武器にデータ活用時代を生き抜く (Informatics &IDEA)

わたしの、たぶん10冊目の著書 『10年戦えるデータ分析入門 SQLを武器にデータ活用時代を生き抜く』 が、来週6月30日に発売されます。 池袋ジュンク堂など一部の書店ではすでに先行販売を実施中です。

本の発売タイミングがあまりにもブログ当番のタイミングと合いすぎていて 作為的なものすら感じますが、なんとまったくの偶然です。 わたし自身もびっくりしました。

RedshiftやHadoop(Hive)、Presto、Sparkのような並列分析データベースがメジャーになるにつれ、 SQLによるデータ分析は適用範囲を増しています。 これらのシステムの適用範囲は現在のところその場で考えながらクエリーを投げる探索型の分析がメインですが、 その次には分析クエリーをバッチとして定型化・固定化してダッシュボードで監視したり、 システム連携するパターンも増えていくでしょう。 今回紹介した仕組みはそのような場面で役立つはずです。 ぜひ手元のツールボックスの1つとして分析バッチの仕組みを加えておいてください。

検索ログから「じわじわ検索頻度が上昇しているキーワード」を見つける

$
0
0

こんにちは。トレンド調査ラボの井上寛之(@inohiro)です。

普段は法人向けサービス「たべみる」の開発を担当しています。 たべみるはクックパッドの検索ログを基にしたサービスで、任意のキーワードの検索頻度、キーワード同士の組み合わせ検索頻度、 およびそれらを地域や年代・性別で絞り込んで分析することができます。

トレンド調査ラボでは「たべみる」の開発のほか、 クックパッド上のトレンドを見つけるために日々調査を行っています。 ここでのトレンドとは、「流行っている」もしくは「流行りそう」といったものを指します。 消費者が気になっているキーワードが何かを知ることで、消費者が求めている情報を適切に提供できると考えています。

今回は、膨大な検索ログの中から「じわじわ検索頻度が上昇しているキーワード」を見つけるために 行ったことについて紹介したいと思います。

じわじわ検出

「じわじわ検索頻度が上昇している流行りそうなキーワードを、機械的に、いち早く見つける」ことを 「じわじわ検出」と呼ぶことにします(「流行り」については後述します)。

例えば最近流行したキーワードとしては「塩レモン」や「おからパウダー」などがあります。 下図は、「おからパウダー」「ガパオライス」「塩レモン」の、2009年3月から2015年2月までの週間検索頻度の推移です。

f:id:InoHiro:20150629154133p:plain

このようなキーワードを、青い矢印で指すような流行りはじめのうちに見つけられると嬉しいわけです。

そもそも「流行っている」とは?

そもそも「流行っている」とはどういうことなのでしょうか。 じわじわ検出で検出対象となるようなキーワードの検索頻度推移から、共通する点を見つけ出し、 式のように定義できると検出ができそうです。つまり、「流行っている」の定義がこの分析の肝と言えます。 試行錯誤を繰り返し、現在は「流行っている、流行りつつある」を以下のように定義しています。

  • 「1年前の検索頻度を上回っている週数 / 比較対象の週数(N)>= M」であるとき、流行っている、流行りつつある
    • N:比較対象の週数(52週、40週、27週など)
    • M:検索頻度が1年前の同一週よりも上回っている週のNに対する割合(70%、80%、90%など)

文章として書いてみると、「N週の間で、週の検索頻度が一年前の同一週の検索頻度と比べて上回っている週の割合が、M%を超えている」とき、 そのキーワードが流行っている、流行りつつあるということです。

例えば以下の図は、N=10、M=90%で「2015年流行っている」と判定されるような例です。 10週(N)のうち、2015年の90%の週が2014年を上回っており、M >= 90% の条件を満たしています。

f:id:InoHiro:20150629154155p:plain

一方以下の図は、「流行っている」と判断されない例です。 2014年を上回る週は60%であるため、M >= 90% の条件を満たしていません。

f:id:InoHiro:20150629154214p:plain

今回はNに57から27のような大きめの値を設定しましたが、このように設定した理由には次に挙げるような背景があります。

  • 季節要因を排除したい

    • 例えばクリスマスやバレンタインデー、お正月などの季節要因による影響を抑えたい
  • バースト(一過的な爆発的な数値の変化)を排除したい

    • 主にテレビで取り上げられたことによるバーストを排除したい

Nが小さいと、調査対象の期間によっては季節的な要因によって「流行っている」ように見えてしまう可能性があります。 同様に、短い期間のうちにバーストした場合、非常に大きな影響を受ける可能性があります。 そのため、Nは大きめの期間(52~27)を使うのが良いと考えました。

下図は「ガパオライス」の検索頻度のうち、2014年第2-26週と2015年2-26週を比べたグラフです。 ほぼ全ての週で、2015年の検索頻度が2014年の検索頻度を上回っているのが分かります。 絶対値はどうであれ、検索されている(≒多くの消費者が注目している)キーワードであると言えます。 また、ある程度の期間で比較しないと、季節や、テレビなどによるバーストの影響を受けてしまう可能性がある ことが分かると思います。

f:id:InoHiro:20150629154234p:plain

一方、季節要因やバーストの影響を受けない代償として、新語(これまで存在しなかったワード)を見落とす可能性があると言えます。 新語の場合、前52週のうちの多くが検索頻度0である可能性が高く、急に検索され始めたワードは今回の定義では見落とす可能性が高いです。 ただし新語の発見は「じわじわ検出」の目的では無いので無視します。

SQLによる分析

SQLを使って、時系列データから流行っている、流行りつつあるキーワードを探してみました。 SQLを使う理由は、分析対象となるキーワードごとの週間検索頻度が、 Amazon Redshiftに蓄積されているためです。 分析の流れは以下のようにしました。

  1. 年ごとに週番号を付ける
  2. 1年前の同一週とくらべて検索頻度が上回っている週を見つけ出し、その割合を計算する
  3. 対象週数Nと、上回っている収集の割合Mを指定して、最終的なキーワード集合を得る

前提

前提として、分析対象のデータは以下の様なスキーマのテーブルに入っているとします。

createtable weekly_si
( data_date date-- 週の開始日
  , keyword_id int -- キーワードID
  , si float-- 検索頻度
)
;

1. 年ごとに週番号を付ける

row_number()(ウインドウ関数)で、年ごとの週番号をつけたリレーションを作っておきます。 なおwith句を使えば、いちいち表を作らずに2と一緒に実行することができます。

createtable numbered_weekly_si
( week_num int     -- 週番号
  , data_date date-- 週の開始日
  , keyword_id int -- キーワードID
  , si float-- 検索頻度
)
;

insertinto numbered_weekly_si
select
    row_number() over(partition by keyword_id, extract(year from data_date) orderby data_date) as week_num
    , data_date
    , keyword_id
    , si
from
    weekly_si
;

2. 1年前の同一週とくらべて、上回っている週にフラグを立てつつ、N週のうちの割り合いを計算する

1で作った週番号付きの検索頻度を、年をずらしてジョインし、1年前の同一週と比較を行います。 1年前を上回っている場合フラグ(si_growth)を 1に、そうでなければ 0としておきます。 この結果に対して、ウインドウ関数を使って比較対象の週数Nにおける、上回っている週数の割合を求めます。 以下のクエリでは、52週だけでなく、40週、27週についても集計しています。

createtable weekly_si_growth
(
  week_num int             -- 週番号
  , current_data_date date-- 週の開始日
  , si_growth int          -- 検索頻度が成長しているか (0,1)
  , keyword_id int         -- キーワードID
  , ratio52 float-- 前52週での検索頻度成長割合
  , ratio40 float-- 前40週での検索頻度成長割合
  , ratio27 float-- 前27週での検索頻度成長割合
)
;

insertinto weekly_si_growth
select
    *
from (
    select
        week_num
        , current_data_date
        , si_growth
        , keyword_id
        -- 週数N {52, 40, 27} 毎に、1年前同一週の検索頻度を上回っている週の割合を計算-- 分母は定数を直接書くこともできるが、必ずしも {52, 40, 27} 週前まであるとは限らないので、-- 同様に数え上げる
        , sum(si_growth) over(partition by keyword_id orderby current_data_date rows52 preceding) * 1.0
                / sum(1) over(partition by keyword_id orderby current_data_date rows52 preceding)
              as ratio52
        , sum(si_growth) over(partition by keyword_id orderby current_data_date rows40 preceding) * 1.0
                / sum(1) over(partition by keyword_id orderby current_data_date rows40 preceding)
                as ratio40
        , sum(si_growth) over(partition by1orderby current_data_date rows27 preceding) * 1.0
                / sum(1) over(partition by1orderby current_data_date rows27 preceding)
                as ratio27
    from (
        selectcurrent.week_num
            , current.data_date as current_data_date
            -- 1年前の検索頻度を上回っていたら 1 とする
            , case when (current.si - last_year.si) > 0then1else0endas si_growth
            , current.keyword_id
        from
            numbered_weekly_si current-- 1年前の同一週とジョイン
            inner join numbered_weekly_si last_year
            oncurrent.week_num = last_year.week_num
                andcurrent.keyword_id = last_year.keyword_id
                and extract(year fromcurrent.data_date) = extract(year from last_year.data_date) + 1
    )
    groupby1, 2, 3, 4
)
;

3. パラメータ(対象週数N、週数の割合M)を指定して、条件を満たすワードを得る(検出)

2で、全てのキーワードについて、週数N(52週、40週、27週)での検索頻度が上昇している割合を計算したので、 期間と検出に用いる週数N、閾値となる割合Mを指定して、その条件を満たすワードを出力します。 ここでは下記のパラメータで問合せるクエリを示します。

  • 期間:2014-01-01 から 2014-12-31
  • N: 52週
  • M: 90%
    • つまり、2014-01-01から2014-12-31のそれぞれの週から52週前までの期間において、1年前の同一週の検索頻度を上回っている週の割合が 90%以上であるようなキーワード

appeared_sumには期間内(上の条件の場合52週)のうち、 前の52週(N)の中で1年前の同一週の検索頻度を上回っている週が90%である週の数が入ります。 つまり、appeared_sumは期間中の出現頻度で、多ければ多いほど、 流行っている、流行りつつあるキーワードと言えます。

select
    candidate.*
    , keyword.name
    , appeared_sum
from (
    select
        keyword_id
        , sum(appeared) as appeared_sum
    from (
        select
            keyword_id
            , count(*) as appeared
        from
            weekly_si_growth
        where
            ratio52 >= 0.9-- 前52週において、上回っている週が 90% 以上and current_data_date betweendate'2014-01-01'anddate'2014-12-31'groupby
            keyword_id
    )
    groupby
        keyword_id
) candidate

-- キーワード名を得るためのジョイン
inner join keywords keyword on candidate.keyword_id = keyword.id
;

比較する週数(N)を調整することで、季節やテレビなどの影響を平均化したり、その逆を行うことができます。 今回は52週のときのみ取り扱いますが、40週、27週の値を使うと、より敏感にトレンドを掴むことができます(言うまでもなく、その分ノイズも増えます)。

検証

見つけ出したかったキーワードを、上記の方法で本当に見つけられるのか検証を行いました。

検証方法

2014年および2015年上旬に流行った、流行りつつあったキーワードのうち、 少なくともこれだけは見つけ出したかったというキーワードを正解ワードセットとし、それらを見つけ出せるか調べました。 正解ワードセット(25ワード)は以下になります。

豚ロース薄切り, おからパウダー, サンドイッチ, ジャージャー麺, ジャーマンポテト,
トースト, ミネストローネ, ラタトゥイユ, ポップオーバー, マッシュポテト,
スクランブルエッグ, 水菜サラダ, バタービール, フラックスオイル, 白湯(パイタン)鍋,
マシュマロ, 安納芋, ガパオライス, なす煮びたし, 温泉卵の作り方,
塩レモン, マッサマンカレー, 亜麻仁油, 台湾まぜそば, 寝かせ玄米

また、前年同一週を上回っている週数の割合(M)は、小さくすればそれだけ検出する語が増えるので、 90% → 80% → 70%と変化させてみました。

検証結果

前年同一週を上回っている週数の割合が90%のとき

  • 検出した語: 3133 ワード
  • 正解数: 15 ワード(以下)
豚ロース薄切り, おからパウダー, ジャーマンポテト, ミネストローネ, ラタトゥイユ,
マッシュポテト, スクランブルエッグ, 水菜サラダ, マシュマロ, 安納芋,
ガパオライス, なす煮びたし, 温泉卵の作り方, 塩レモン

前年同一週を上回っている週数の割合が80%のとき

  • 検出した語: 5988 ワード
  • 正解数: 21 ワード(90%の時に見つけたワードと、以下)
サンドイッチ, ポップオーバー, バタービール, マッサマンカレー, 台湾まぜそば, 寝かせ玄米

前年同一週を上回っている週数の割合が70%のとき

  • 検出した語: 10089 キーワード
  • 正解数: 22 ワード(80%の時に見つけたワードと、以下)
ジャージャー麺

結果について

適合率(Precision)を上げすぎて今後流行る可能性のあるキーワードを落としてしまうよりも、 検出される語が多くなってしまっても、正解ワードが多く含まれる結果(高い再現率(Recall))を目指しました。 結果として再現率は、M=90%で0.6、M=70%で0.88となりました。

正解データのうち、発見できなかったキーワード

正解キーワードのうち、発見できなかったキーワードはなぜ発見できなかったのでしょうか。 それぞれのキーワードについて調べてみると、以下の様な理由であることがわかりました。

  • トースト、フラックスオイル

    • 検索頻度が1年前同一週とくらべて増加していない
  • 白湯(パイタン)鍋

    • 検索頻度が上昇し始めたのは2014年11月ごろから
    • 52週のうち90%で検索頻度が上昇しているとは言えない

そもそも「トースト」「フラックスオイル」の2ワードについては、 組み合わせて検索される頻度が上昇したことによって 「見つけ出したかったキーワード」(正解ワード)とされていたため、 今回の方法では見つけ出すことができませんでした。

2015年流行りそうなキーワードは?

この方法を2015年上旬の検索ログに適用し、流行語の候補を探してみました。 期間を2015年1月1日から6月20日までとして、N=52週、M=90%として実行したところ、1490ワードを得ました。 その中から、特にこれは流行るかも!?というような候補をいくつか挙げておきます。 ウェブやテレビ、お店などで見つけたら、このエントリのことを思い出して下さい。

その他試したアイデア

実際に今回試したアイデアに至るまでは、多くの試行錯誤を繰り返しました。 試したアイデアをいくつかご紹介しようと思います。

  • あるキーワードの検索頻度が、初めて閾値Nを超えたなら、そのキーワードは流行りはじめているのではないか?
  • 前の週の検索頻度とくらべた差が一定期間、M%以上の上昇を続けていたら、そのキーワードは流行っているのではないか?
  • 検索頻度の移動平均がL%ずつ上昇を続けていたら、そのキーワードは流行っているのではないか?
  • 組み合わせて検索されているキーワードの数が爆発的に増えているキーワードは、流行っているのではないか?
    • 実際には「組み合わせた検索が増える」というのは、探しているキーワードの認知度が高くある必要があるので、見つけることができても既に流行ってしまったキーワードであった(目的である流行りはじめは検知できない)
  • 検索頻度の変化率X%以上の立ち上がり検知し、その後も数週間にわたって検索頻度を維持し続けているとき、流行った状態にあるのでは?
    • テレビ等によるバーストを、流行ったと誤検知したくない
    • こちらも流行ったことはわかるが、流行る前に見つけ出したい

まとめ

本稿では、検索ログから「じわじわ検索頻度が上昇しているキーワード」を機械的にいち早く見つけ出すために 検討し、実際に行った方法、および得られた結果についてまとめました。

今後も定期的に「じわじわ検索頻度が上昇しているキーワード」を見つけるため、方法の改善を行っていこうと考えています。 また、人間の手による温かみのあるパラメータ調整ではなく、機械学習などを用いてより低コストに導き出せないか、 挑戦していこうと考えています。

RubyMineの便利な機能

$
0
0

会員事業部のツヤです。

f:id:tsucook:20150701094138p:plain

クックパッドにもRubyMine愛好家はいますので、社内で共有されたTipsも含め RubyMineの便利な機能を紹介したいと思います。

※ 掲載している画像は実際のサービス・プロジェクトとは一切関係ありません。(blog用です)

Remote Debugging

このために使っていると言っても過言ではないのが Remote Debugging機能です。

どんなサービスでも複雑な要件で構成されたページは存在するもので、クックパッドも同様です。 歴史を重ねたcontrollerのbefore_actionや入り組んだpartialなどのデバッグは大変です。 binding.pryloggerで頑張っていたこともありますがremote debugは優秀です。

1. Gemのインストール

Gemfileに書いてもいいのですが、IDEでDebugしない人には必要ないので別途インストールしています。

gem install ruby-debug-ide
gem install debase

2. RubyMineの設定

  • Run -> Edit Configurations -> [+] -> Ruby Remote debug

f:id:tsucook:20150701094139p:plain

事前に起動させたいサービスは別に登録しておくと、Before launch: Another Configurationに設定できます。 クックパッドでも事前に起動させたいサービスはいつかあるのでBash等で登録してあります。 RubyMineを使用しない場合はforemanで起動させています。

3. デバッグ

デバッグサーバーを起動

f:id:tsucook:20150701094140p:plain

Terminalから実行してもいいですが、Tools -> External Tools に登録しておくと便利です。

デバッグするページにアクセス

表示に関連するデバッグ以外は、Tools -> Test RESTful Web Service を利用すると便利です。

REST Client はヘッダーやパラメーター、cookieを容易に変更できるのでAPIテストに利用していますが、 ブラウザと違いページ以外のリソースにアクセスしないのでデバッグでも活躍します。

f:id:tsucook:20150701094141p:plain

ブレイクポイント

f:id:tsucook:20150701094142p:plain

コールスタック、スコープ内の変数、変数の監視などが表示されます。

コールスタックからは、プロジェクトのファイルでもGem内のファイルでも参照できます。

ブレイクポイントはControllerやModel、Viewに限らず、routes.rb などのconfigやgem内のファイルにも設定できます。

f:id:tsucook:20150701094153p:plain

便利な機能

便利な機能はたくさんありますが、ほんの一部だけ紹介したいと思います。

紹介する機能には一部プラグインが提供しているものもあります。

Search Everywhere

f:id:tsucook:20150701094143p:plain

プロジェクトが大きくなればファイルを探すのも一苦労ですが、Search Everywhere は強力に空気を読んで探してくれます。 頻繁に参照するフォルダやファイルは Favorites に登録しておくといいかもしれません。

デフォルトではダイレクトなショートカットキーは割り当てられていませんが shift 2回でダイアログが表れます。

no new line at end of file

f:id:tsucook:20150701094144p:plain

たまにこんな残念なcommitをしてしまうことがあります。 Ensure line feed at file end on Saveをチェックしておくと保存時ファイル末尾に改行を追加してくれます。

f:id:tsucook:20150701094145p:plain

Ruby Style Guid

f:id:tsucook:20150701094146p:plain

ruby初心者にもありがたいRoboCopの inspection機能です。IDEは学ぶ機会も与えてくれます。

Ruby Style Guid でコーディングスタイル統一することは可読性を高め、保守性を向上することにつながります。 以前はcommit hookに設定してたりしましたが、コードを書いている最中に分かる方が効率的だと思います。

keymap

f:id:tsucook:20150701094147p:plain

自分は"郷に従え"派なので変更しませんが、vim, emacs など他エディタからの移行や、使い慣れたキーバインドで!と言う方には各キーマップがプリセットされています。

RubyMineは隠れ機能(隠しているのではなく探せないだけ)が豊富です。このキーマップを探ると便利機能が見つかることがあります。ぜひとも探訪して頂きたい設定です。

Tasks & Contexts

  • Tools -> Tasks & Contexts -> Configure Servers

f:id:tsucook:20150701094148p:plain

f:id:tsucook:20150701094149p:plain

チーム開発では何かしらのタスク管理ツールが使われると思いますが、メジャーなToolとは連携ができます。

タスクからブランチを作成したりもできます。 タスクごとにワークスペース(開いているファイルやウインドウ分割など)が保持されるのでタスクをうまく切り換えて運用すると便利です。

Terminal

f:id:tsucook:20150701094150p:plain

iTermでもいいのですが、IDE内で完結することができます。若干のクセ(日本語表示)があるので注意が必要です。 ログの参照やconsoleに使用しています。

PlantUML

f:id:tsucook:20150701094151p:plain

ちょっとしたシーケンス図、クラス図を書きたい。そんときは PlantUMLが便利です。

シンプルな文法なので、テキスト編集でさらっと書けてしまうのが特徴です。png, svg形式で保存も出来ます。 社内の共有ツールでもPlantUMLが使用できるので、そのまま共有資料として使用することもあります。

Gfm

f:id:tsucook:20150701094152p:plain

マークダウンをGitHubのフォーマットでプレビューしてくれるので仕上がりがローカルでわかります。 分割してライブプレビューできるのはありがたいです。 Pull RequestやIssueの下書きで利用すると便利です。

普段からメモ帳としてもIDEを使ってしまっています。(ミーティングもIDE)

まとめ

長年JetBrains製品で開発していますが、未だに未開の機能がたくさんあります。

IDEに限った話ではありませんが、道具は上手に使ってこそ生きるのだと思います。これからも上手にIDEとつきあっていきたいと思います。

エンジニアのキャリアの方向性

$
0
0

CTO の舘野 (id:secondlife) です。丁度1年半ほど前に、クックパッドの CTO になり、自分が20代の時に憧れていたいわゆるハッカーとは違う道を歩んだという事もあり、ソフトウェアエンジニア*1のキャリアってどんな物があるんだろうと改めて考えた時期がありました。

しかしながら一人悶々と考えても、答えが見つかる物でも無かったので、私の先を行く方々の話を聞きたいんですよね、みたいな事を md2inaoで有名な WEB+DB PRESS 編集長の稲尾さんとしていたところ、じゃあそれ連載記事でどうですか、とお話を貰ったので記事として連載させて頂きました*2

その時、連絡させていただいたメールにはこんなことを書いていました。

背景としては、今やエンジニアは、サーバサイドは AWS/heroku 等 IaaS/PaaS の台頭、github を中心とした OSS フレームワーク・ライブラリのエコシステムの発展、等々により学習の高速道路が整備され、サービスを作るために必要な技術を学ぶコストが、昔より低くなってきたと感じてます。

こうなると、若いうちは良いのですが、30を超え始めると、特に最新の流行の技術の部分では技術が大好きな若者に勝てなくなってきてしまいます。

プログラマ35歳定年説じゃありませんが、自分のポジションを取り、何の技術を選択して、どのスキルを磨くのか、しっかりと考えて行かないとある一定の年齢を超えると徐々に価値が下がってしまうのも事実だと思ってます。

そのため、この先エンジニアが生き残るにはどうすればいいのか、実際にキャリアを築いて活躍するエンジニアの方々にインタビューさせていただき、若者にキャリアプランを示したり、こんな生き方もあるんだよ、という話しを記事にしたいと考えております。

私が今考えるエンジニアのキャリアプランは大きく分類するとこの3つがあるのかなぁ、と思ってまして

1.経営者(CTOなど)・技術マネージャ

様々な技術の知識やマネジメント力があるからこそ、組織にあった技術戦略を考え、会社や社員を導き育てる人

2. 技術スペシャリスト

歳を重ねても技術を吸収し磨き続け深い知識があるからこそ、活躍できるエンジニア

3. サービススペシャリスト

ユーザに向けてアプリやサービスなどを提供し続けるエンジニア。技術もできるが、その技術をエンドユーザの価値に直接結びつけられる人。

これらの方々にお話を伺いたいと思ってます。

そんなこんなでキャリアの方向性を考えだしてから、6名の方々にインタビューさせていただきました。

みなさん 1~3 のどれか・もしくは複数のキャリアに属しており、各々が強みとする技術を生かしつつキャリアを築いています。中途半端な技術ではなく、しっかりと強みとなる技術を持っていることで、マネジメントだったり、さらに広く知識を深めていったり、ユーザに対して大きな価値を生めるのでしょう。

私が社内のエンジニアと面談するときは、年齢や時期にもよるんですが、若手のエンジニアには強みの分野を持てるように、自分が興味がある技術*3に打ち込んで伸ばす、30前後になってきたら、その強みを生かしつつもどんなキャリアを歩んでいきたいのか、具体的に方向性を示しつつも考えてもらっています。技術をさらに深めプロフェッショナルなエンジニアになっていくのか、ユーザー価値を考えプロダクトに反映できるエンジニアになりたいのか。それともマネジメントで組織を引っ張っていくエンジニアになりたいのか。

またプライベートを含めた自分の状況に照らし合わせて考えてみることも大切で、家庭をしっかりもちつつも活躍しているひげぽん(蓑輪太郎)さんが 限られた時間をどう使うかで話されている内容に大きなヒントがありました。

というわけで、皆さんも時々はどんな道を歩んでいたいのか、時々は立ち止まって方向性を考えてみてはいかがでしょうか。 業界的に、明確なキャリアパスがなく、ゴールが描きにくいのですが、それは私たちが普段行ってる開発と同じですね。不明瞭なゴールに対して、どう明確化していくのか、イテレーションをまわしつつ KPT で定期的に振り返ったりしつつ、進む方向性を考え続ける。キャリアについても何となく進んでしまわずに、定期的に考えることで、自分がどんな技術を学ぶべきなのか、立ち振る舞いはどうすべきかとったことが見えてくるはずです。

*1:なおこの文脈のエンジニアは、クックパッドのように主にユーザにサービスを提供するような企業におけるソフトウェアエンジニアです

*2:連載してると締め切りドリブンで必ず前に進むのでいいですね!忙しい時期は辛いけど…

*3:好奇心があり、モチベーションがあって学び続けられることは重要

7/23(木) Cookpad × Fablic のデザイナーイベント「Think User First」を開催します!

$
0
0

こんにちは。Holidayの多田です。

このたび、フリマアプリ「フリル」でおなじみの Fablicさんと共同で、デザイナー向けのイベントを7/23(木)にクックパッドオフィスで開催することになりました!

“ユーザーファースト”を掲げる Cookpad と Fablic の2社で、開発現場での実際の取り組みについて紹介するイベントです。前半は、デザイナーの手がけている仕事の紹介とサービス開発現場での事例紹介を現場のデザイナー陣が行い、後半はパネルディスカッションを実施する予定です。

Think User First - Cookpad × Fablic

イベント詳細

スピーチ・パネルディスカッション

  • 倉光 美和(くらみつ みわ) Cookpad / デザイナー
    • 「クックパッドデザイナーが実践するユーザーファースト」
  • 多田 圭佑(ただ けいすけ) Holiday / デザイナー
    • 「Holiday のデザインと開発 - ユーザーに価値を届けるためのプロトタイピングから実装まで」
  • 竹渓 潤(たけたに じゅん)Fablic / 取締役・デザイナー
    • 「ユーザーファーストな会社の誕生と実践」
  • 塚由 恵介(つかよし けいすけ) Fablic / デザイナー
    • 「UXマップを活用したサービス改善」
  • 池田 拓司(いけだ たくじ) Cookpad / 執行役・ユーザーファースト推進室長

※…パネルディスカッションのみ参加

タイムスケジュール

7/23(木) 19:00 START

  • 18:30 - 19:00 受付開始
  • 19:00 - 19:10 オープニング
  • 19:10 - 20:40 プレゼンテーション
  • 20:40 - 21:10 パネルディスカッション
  • 21:10 - 22:10 懇親会

アクセス

クックパッド株式会社東京本社オフィス キッチンラウンジ

アクセス:https://info.cookpad.com/location/

持ち物

  • お名刺1枚
    • 懇親会で交流の時間がございますので、お名刺を余分にお持ちいただければと思います

懇親会

終了後、同会場(クックパッド社内のキッチンラウンジ)にて懇親会を実施いたします。 ぜひご参加ください!

参加方法

こちらよりご応募ください(外部サイトに移動します)

締め切り

07/15(水) 15:00 まで

※ 応募者多数の場合抽選とさせていただきます。

最後に

「ユーザーファースト」について深く考えたい、議論したい、そんなデザイナーのみなさまのご応募、お待ちしております!

Think User First - Cookpad × Fablic (2015/07/23 19:00〜)connpass.com


ユーザーの献立決定を助ける導線の改善をするときに考えたこと

$
0
0

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

私が担当をしている「クックパッドおいしい健康」には、クックパッド本体の様々な場所から導線(誘導のリンク)が張られています。その中の1つに、特定キーワードで検索した時の導線があります。

↓こちらは、スマートフォンサイトの特定キーワード検索時に表示される「おいしい健康」への導線です。例えば「糖尿病」や「高血圧」など特定の病名が入ったキーワードでレシピ検索をしたとき、この導線が表示されます。

この導線は検索ボリュームに対する遷移数の割合が著しく低かったため、改善のABテストをしました。小規模な改善だったのですが、その時に考えたことと、結果が興味深かったので、ご紹介できればと思います。

変更前の問題点

変更前の問題点として、以下のようなものがありました。

  1. ユーザーへのメリットが不明瞭
  2. 別サービスに遷移することがわかりづらい
  3. キーワード検索のノイズになっている

↓こちらが変更前のデザインです。

上記問題点の詳細と、それを解消するために考えたことを、以下記述していきます。

問題点の解消

『1. ユーザーへのメリットが不明瞭』について

変更前はレシピの説明が『体にやさしい』など汎用的になっており、逆にそれが、どんなメリットがあるのかが伝わりづらいものとなってました。

そこで、ユーザーへのメリットを明確にするために、糖尿病や高血圧などの病名に合わせてどんな特長があるのかを記載するようにしました。

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

『2. 別サービスに遷移することがわかりづらい』について

変更前は、同サービスの別コンテンツに遷移するのか、別のサービスに遷移するのか不明瞭でした。

なぜこの点を明確にするのかというと、別のサービスへの遷移は新たに認知・学習のコストがかかり、ユーザーに負担をかけてしまいます。そのため、別サービスであることを事前にわかってもらう必要があります。

これは、サービスのアイコンを表示したり、「もっと見る」のテキストを外したりすることでわかりやすくなると考えました。

『3. キーワード検索のノイズになっている』について

変更前は、常に特定のレシピが表示されて検索キーワードに合っておらず、検索のノイズになっていました。

ユーザーはレシピを探しており、また、他サービスへは遷移せずそのまま探したいというユーザーもいるので、その体験は邪魔をしないように以下のことを考えました。

  • レシピは1つの決まったレシピを提案するのではなく、キーワードに合ったレシピにする
  • 単なるサービス紹介では迅速にユーザーの課題を解消できないので、レシピ提案にする

ABテスト案の作成

以上のことを踏まえて、2つのデザインを作成しました。2つ作った理由は、「別サービスへ遷移することを明確にする」、「キーワード検索の体験は邪魔しない」のどちらの比重が高い方が遷移してもらやすいのかを試したかったからです。 (この導線は、スマートフォン、PCの両方にあったのですが、当時スマートフォンのユーザー数がPCのユーザー数を追い抜こうとしていたため、スマートフォンの変更から行うことにしました。)

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

A案…「他サービスへ遷移することを明確にする」の比重が高い B案…「キーワード検索の体験は邪魔しない」の比重が高い

結果

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

AとBのCTRはほぼ同じでした。しかし、遷移したあとのサービスの回遊率や滞在時間は、Aの方が高かったです。(Aが上、Bが下です)

その理由として、以下のように考えました。

  • Aの方が、Bに比べて情報が多く、どんなサービスに遷移するのかがわかる。そのため、「このサービスには他ページにも自分の知りたい情報があるのではないか」と予想し、回遊してもらいやすくなるのでは
  • Bでは、遷移先がユーザーのニーズに合わない場合、どんなサービスかの事前情報が少ないため、そこで行き止まりのように感じるのでは

また、変更前の導線と比べて、CTRは約1.8倍でした。(キーワード連動になったので、単純な比較はできませんが…)

最後に

今回の導線改善では以下2点が上記のような結果に結びついたと考え、今後「おいしい健康」の導線改善をするときは、これらのことを盛り込んだ導線にしていく予定です。

  • 別サービスであることの明示
  • ユーザーのニーズ(今回はキーワード検索の内容)に合った情報の表示

今回はキーワード連動型でしたが、ユーザーのニーズが何かを考え、それに合わせた情報を伝えることが重要なのだと改めて感じました。

クックパッドでは、このような改善をガンガン行っていきたいというデザイナーを募集しています!

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

Cookpad × Fablic のデザイナーイベント「Think User First」を開催しました。

$
0
0

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

クックパッドでは去る7月23日(木)、フリマアプリ「フリル」を運営するFablicさんと合同でデザイナー向けにイベントを行いました。

f:id:hrtk441:20150727103853p:plain

イベントのタイトルにもある通り、"ユーザーファースト"をテーマに掲げ、開発現場での実践事例を交えながら両社の文化やデザイナーの働き方についてご紹介しました。

お集まりいただいた70名の皆さまの熱量も高く、イベントスタート時からパネルディスカッション、懇親会に至るまで、とても充実した時間を過ごさせていただきました。 ご来場いただいた皆さま、本当にありがとうございました。

また今回は多数応募をいただいたため残念ながら抽選に漏れてしまった皆さま、たいへん申し訳ありません。 こういったイベントは今後も開催していきたいと思いますので、またのご参加を心よりお待ちしております!

最後に、各発表者の当日のスライドを以下に公開いたします。ぜひご覧ください!

クックパッドデザイナーが実践するユーザーファースト

  • 倉光美和(Cookpad/デザイナー)

f:id:hrtk441:20150727112830p:plain

Holiday のデザインと開発 - ユーザーに価値を届けるためのプロトタイピングから実装まで

  • 多田圭佑 (Holiday/デザイナー)

f:id:hrtk441:20150727102726j:plain

ユーザーファーストな会社の誕生と実践

  • 竹渓潤さん (Fablic/取締役・デザイナー)

f:id:hrtk441:20150727102737j:plain

UXマップを活用したサービス改善

  • 塚由恵介さん (Fablic/デザイナー)

f:id:hrtk441:20150727102744j:plain

クックパッドでは、"ユーザーファーストなものづくり"を本気で実践できるデザイナーを募集中です!

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

プログラマの健康を考えるイベント「ヘルシープログラマ!」を開催!

$
0
0

新刊『ヘルシープログラマ』(http://www.oreilly.co.jp/books/9784873117287/) の出版を記念し、クックパッドと共同イベントを開催します。  オライリー・ジャパンの7月の新刊『ヘルシープログラマ』は、プログラマのために書かれた健康の本です。長時間座ったまま仕事を続けるプログラマには、腰痛、手首の痛み、目の痛みや頭痛がつきものですが、食事を意識したり、少し体を動かすだけで健康状態は改善します。本書では、プログラマが直面する様々な健康問題を回避するためのイテレーティブかつインクリメンタルなテクニックとTipsを紹介します。  本書の発売を記念して、さまざまなアイデアを持ち、実践してきたエンジニアのみなさんと一緒に、健康とプログラミングについて考えてみようというのが今回イベントです。当日は、スピーカーのみなさんの健康に関する発表と懇親会を楽しむことができます。書籍の販売も行います。

発表

  • 「ヘルシープログラマ・翻訳、そして実践」玉川竜司 — @tamagawa_ryuji 『ヘルシープログラマ』翻訳者。本業はソフト開発。新しい技術を日本の技術者に紹介することに情熱を傾けており、その手段として翻訳に取り組んでいる。
  • 「走って良かった10のこと」井原正博 — @ihara2525 株式会社ビットジャーニー代表取締役。ヤフー株式会社にて開発部長を務めたのち、クックパッド株式会社の技術部長として技術力の向上やエンジニアの採用に従事、今日にいたる基礎をつくりあげる。2015年1月、株式会社ビットジャーニーを設立し、新規インターネットサービスを開発中。

ライトニングトーク

  • 「昇降式デスクの全面導入事例」小野和俊(株式会社アプレッソ代表取締役社長 & 株式会社セゾン情報システムズ 取締役 CTO) — @lalha2
  • 「体の瞑想を求めて:アレキサンダーテクニークからSSSまで」森正弥(楽天株式会社 執行役員 兼 楽天技術研究所代表) — @emasha
  • 「糖質とプログラミングと私」末並晃(株式会社ペロリ) — @a_suenami
  • 「東京から仙台まで歩く方法」川原翔吾(旅人) — @ooharabucyou
  • 「You're what you eat ~ 食事を自分に最適化する ~」関口隆介(クックパッド株式会社 ヘルスケア事業部 健康エンジニア) — @tanukiti1987

概要

会場:クックパッド株式会社(東京都渋谷区恵比寿4-20-3 恵比寿ガーデンプレイスタワー12F

https://info.cookpad.com/corporate/location

日時:2015年8月25日(火):19:00〜21:30(開場18:30、懇親会あり)

参加費:無料

定員:50名

主催:クックパッド株式会社、株式会社オライリー・ジャパン

参加申し込み方法

応募フォームはこちらです。

備考

Cookpad Android TV Appのデザインで考えたこと

$
0
0

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

クックパッドでは、AndroidアプリをGoogleのTV向けプラットフォームであるAndroid TVに対応するようアップデートを行いました(アプリページ)。この記事では、本アプリのデザインで検討した点について、簡単にご紹介したいと思います。

f:id:vistawalk:20150803195725p:plain

■ Android TVとは

Android TVはGoogleのTV向けプラットフォームで、TVにつなぐとアプリ・映画・ゲームがリモコンで楽しめるデバイスです。国内ではGoogleのNexus Playerで利用できるほか、ソニー社がAndroid TVの機能を組み込んだテレビを販売しています。

TVアプリでできること

このアプリでできることは主に以下の3つです(今回は1,2についてお話しします)

  1. おすすめレシピの料理動画が流れてきて、"ながら見"でも楽しめる
  2. 料理の材料/手順や関連レシピを見られる
  3. 200万品のレシピから検索できる(音声入力も可能)

1.料理動画が自動で流れるから気軽に"ながら見"しながらでも楽しめる

当初は利用シーンとして「時間に余裕がある時に、今日の晩ご飯どうしようかなと思った時に、テレビのチャンネルを切り替えて、料理雑誌を見ていくようにパラパラと話題のレシピなどを見ながら、感覚的に良さそうなレシピを見つける」ことを想定していました。

そこで以下のように、話題のレシピや人気検索キーワードのレシピを並べ、興味のありそうなレシピがあれば詳細を確認してみるといったUI/フローを考えてみました。

しかし、チームメンバーにデザインのレビューをしてもらったところ、「人気の検索キーワード10個にそれぞれレシピがいくつか表示されているが、ソファでくつろぎながら使うには情報量=選択肢が多すぎて圧倒されてしまう」とフィードバックをもらいました。ユーザの行動の想定として、能動的にレシピを見に行くことを前提にしてしまっていて、受動的に使われるテレビ上で使うUIとしては難しくなっていたのだと思います。

基本的にテレビは受け身で、あまり操作をせず、他の作業をしながら"ながら見","流し見"されることも多いデバイスだと思います。レビューの中でなので、特に目的がなくてもアプリに切り替えて、流れている動画を何かの作業中に"ながら見"して、気になるものがあればちょっと詳細を見てみる(そのうちに今晩のご飯の手がかりが得られる)くらいの気軽さが丁度良さそうだと考えました。

f:id:vistawalk:20150803195550g:plain

そこで話題のレシピなどは並べず、自動再生の動画を中心とした構成に変更してみました(動画は一定時間で他のレシピ動画に切り替わる)。実際に使ってみても、ただ流しておくだけでよい気軽さがあって、テレビを見ている人が触りやすいアプリになっていると思います。

能動的に探す場合はおそらくユーザの手元にあるスマートフォンにおまかせして、TVとしては受け身で気楽に使えて、見ていくうちに今晩何にしようか手がかりが得られる、というところにフォーカスしてデザインを進めることにしました。

2. レシピの発想が広がる関連レシピ機能

また、今晩のご飯の手がかりを得るという点から、レシピ詳細ページに関連レシピを掲載しています。関連レシピとは、例えば元となるレシピと同じ食材を使うけれど別の調理方法で料理するなど、似たレシピを提案する機能です。

例えば 7/29 時点の動画レシピ ズッキーニを使ったチーズハーブピカタには、フリットオープンオムレツといった、同じズッキーニを違うアプローチで料理したレシピが掲載されています。

f:id:vistawalk:20150803195545p:plain

もともとアプリに掲載される動画レシピは十数個と特別多くはなかったため、よりインスピレーションを得る糸口を広げられないかと考えたのがこの機能のきっかけでした。関連レシピから「この料理方法もいいね」「この味付け美味しそうだし、冷蔵庫にある○○をこれで料理してもいいかな」という風に発想を刺激する材料になればと思っています。

さらに関連レシピからさらにその関連レシピへと、ネットサーフィン的に見ていくこともできるので、テレビを見ている時の受け身な状態でも感覚的にレシピの着想を得られそうです。

おわりに

テレビというメディアを考えて、なるべく受け身の状態でも使いやすいように、動画の自動再生や関連レシピを採用したことをご紹介しました。

クックパッドが利用できるデバイスは当初はPCだけでしたが、現在はスマートフォン(ウェブ/アプリ)、タブレット、Watch、TV、と様々なデバイスで利用できるようになりました。

ただ、各々のデバイスには異なる役割や強み/弱みがあります。WatchAppの開発ストーリーでも触れられていましたが、デザインするデバイスの役割や特性を理解し、プロダクトのフォーカスを明確にすることは良いプロダクト作りには、当然の話ではありますが不可欠だと感じました。

クックパッドのサーバプロビジョニング事情

$
0
0

インフラ部の荒井(@ryot_a_rai)です。この記事ではクックパッドで利用しているプロビジョニングツール "Itamae"の紹介と細々した Tips を紹介します。

式年遷宮とプロビジョニングツール

現在、弊社ではインフラの式年遷宮*1を進めています。式年遷宮以前、弊社では Puppet を利用してサーバをセットアップしていましたが、式年遷宮に際して既存のプロビジョニングに関するコードは捨てることになるため、プロビジョニングツールの再検討を行うことになりました。

Puppet, Chef, Ansible, SaltStack を検討した結果、

  • 言語特性の観点では、Ruby DSL な Chef が良い
  • アーキテクチャ・エコシステムの観点では、シンプルな Ansible が良い

といった点から、どれも決め手に欠ける状況で、Ruby DSL で記述できるシンプルなプロビジョニングツールが必要とされていました。 そこで、以前から筆者が細々と開発していたItamae(当時は Lightchef と呼んでいました)が採用され、開発が進められました。

Itamae とは

Itamaeは一言で言うとかなりシンプルな Chef で、以下のような特徴があります。

  • Chefにおける cookbook, role, environment などの概念はなく、レシピのみを管理します
    • 低い学習コストで使うことができます
  • プロビジョニング対象のサーバに Itamae が入っている必要がない
    • SSH 経由で他サーバをプロビジョンすることが可能です
  • Gem でプラグインを作ることができる
    • Bundlerのみで依存関係を記述することができる
    • Chef における Berkshelf の代替

入門 Itamae

Itamae の使い方について軽く触れてみます。

まず、Ruby と Bundlerをインストールしておいてください。作業用ディレクトリを作ります。

$ mkdir itamae-getting-started
$ cd itamae-getting-started

Itamae をインストールするために Gemfile を置きます。直接gem installでインストールすることも可能ですが、レシピが意図せず動かなくならないように Bundler で Itamae のバージョンを固定することをおすすめします。プラグインを使う場合には、この Gemfile にプラグインを追加するだけで利用できます。

# Gemfile
source 'https://rubygems.org'
gem 'itamae'

Itamae をインストールします。

$ bundle install

レシピを書いてサーバの状態を記述することができます。ここでは sl コマンドをインストールしてみます。

# sl.rb
package "sl"do
  action :install# デフォルト値なので省略可能end
$ bundle exec itamae local sl.rb
 INFO : Starting Itamae...
 INFO : Recipe: /home/ryotarai/itamae-getting-started/sl.rb
 INFO :   package[sl] installed will change from 'false' to 'true'

なお、SSH 越しに実行する場合は以下のように実行します

$ bundle exec itamae ssh --host host001.example.jp sl.rb

上記のレシピに書かれているpackageはリソースと呼ばれ、サーバ上の何かしらのリソース(パッケージやファイルなど)の状態を記述します。Itamae には他にも様々なリソースが用意されていますが、代表的なものをいくつか紹介します。

# package リソース
package "nginx"do
  action :install
  version "..."end# remote_file リソース# ファイルを特定のパスに置くことができます
remote_file "/etc/nginx/nginx.conf"do
  source "nginx.conf"end# template リソース# remote_file リソースと同様ですが、eRuby (ERB)として評価した結果を書き出します
template "/etc/nginx/conf.d/itamae"do
  source "itamae.erb"end# execute リソース# 任意のコマンドを実行することができます
execute "echo Hello >> /etc/something"do
  not_if "grep Hello /etc/something"# このコマンドが失敗した場合のみ実行されます# only_if "grep -v Hello /etc/something" # このコマンドが成功した場合のみ実行されますend

ほかにもリソースが用意されているので一度 https://github.com/itamae-kitchen/itamae/wiki/Resourcesに目を通してみることをおすすめします。

もう一つよく使われる機能として、レシピから他のレシピを読み込む機能があります。

include_recipe "sl.rb"

上記のように、include_recipeに読み込みたいレシピのパスを渡すと他のレシピを読み込むことができます。パスはinclude_recipeを書いたレシピがあるディレクトリからの相対パスになります。 ちなみに、同じレシピを複数回include_recipeしても1度しか読み込まれないようになっているので、ご注意ください。

基本的な使い方は以上です。これだけ覚えれば使い始められるので、ぜひ導入してみてください。

さらに詳しい使い方などはドキュメントを参照してください。

Itamae Tips

クックパッドでの Itamae の使い方で特徴的な点をいくつか紹介します。

role、cookbook

前述の通り、Itamae には role や cookbook を管理する仕組みはありませんが、include_recipeで他のレシピを読み込むことで同様の機能を実現しています。

例えば、

├── bootstrap.rb
├── cookbooks
│   ├── nginx
│   │   ├── default.rb
│   │   └── templates
│   │       └── etc
│   │           └── nginx
│   │               └── nginx.conf.erb
│   └── ruby
│       └── default.rb
└── roles
    └── web
        └── default.rb

このように、cookbook と role のディレクトリを用意し特定の命名規則にしたがってファイルを置いています。

# bootstrap.rb# 2015/05/12 10:03 修正moduleRecipeHelperdefinclude_cookbook(name)
    include_recipe File.join(__dir__, "cookbooks", name, "default.rb")
  endendItamae::Recipe::EvalContext.include(RecipeHelper)

include_recipe File.join("roles", node[:role], "default.rb")
# roles/web/default.rb
include_cookbook "nginx"
include_cookbook "ruby"

role はサーバによって異なるので、node[:role]を参照して実行するようにしています。クックパッドでは EC2 インスタンスのタグで role を指定しているので specinfra-ec2_metadata-tagsを利用して、実行するレシピを決定しています。

このように準備しておくと、以下のように実行することができます。

$ echo '{"role": "web"}' > node.json
$ bundle exec itamae local --node-json=node.json bootstrap.rb

remote_file, templateのsource :auto

remote_file, template リソースにはsourceアトリビュートがあり、これでファイルやテンプレートを指定します。

$ ls
recipe.rb   nginx.conf.erb
# recipe.rb
template "/etc/nginx/nginx.conf"do
  source "nginx.conf.erb"end

通常、sourceにはファイルパスを指定しますが、特別な値として:autoが用意されています。:autoが指定されると、Itamaeは配置先のファイルパスから自動的にファイルを探します(詳細)。ちなみに、sourceのデフォルト値は:autoなので、これは省略することができます。

├── recipe.rb
└── templates
    └── etc
        └── nginx
            └── nginx.conf.erb
# recipe.rb
template "/etc/nginx/nginx.conf"doend

この方法を利用すると、ディレクトリ構成がわかりやすくなりファイルが増えた場合にも管理しやすくなると感じています。

Node#reverse_merge!

ノードアトリビュートにデフォルト値を設定する場合は、Node#reverse_merge!が便利です。Node#reverse_merge!はすでに値がセットされている場合は上書きせず、ディープマージを行います。

例えば、以下のように使います。

# recipe.rb
node.reverse_merge!({
  nginx: {
    worker_processes: 4
  }
})

template "/etc/nginx/nginx.conf"
$ bundle exec itamae local recipe.rb
# この場合、worker_processesは4になります

$ echo '{"nginx": {"worker_processes": 8}}' > node.json
$ bundle exec itamae local --node-json=node.json recipe.rb
# この場合、worker_processesは8になります

SSHを使わない

Itamae はコマンドを実行して、その結果を受け取ってから、次のコマンドを実行するため、レイテンシが高いサーバに対して SSH 実行を行うと、遅く感じることがあります。そのような場合は、対象サーバに Itamae をインストールすることで解消します。クックパッドでも最初は SSH 実行を使っていましたが、国外のサーバが増えるにつれ、レイテンシが気になるようになりサーバ上でのローカル実行に切り替えました。

システムに入っている Ruby を使って Itamae をインストールすることも可能ですが、Ruby のバージョンアップなどによって Itamae が動かなくなってしまうことを防ぐため、Ruby などの依存関係ごとインストールするパッケージを用意しています。現在、Ubuntu 14.04 用の Debian Package のみビルドしていますが、chef/omnibusを使っているので、他ディストリビューション用のパッケージもビルドできると思います。

オペレーションフロー

実際に Itamae を実行する際には複数台にまとめて実行するため、Capistrano でレシピを転送したあと Itamae を実行しています。

ただ、この方法だと

  • 台数が増えた時に遅い
  • 台数が増えてくるとオペレーションのコストがかかる
  • 手動で実行していると、レポジトリ上のコードとサーバの状態が食い違う
    • コミットされたからといって Itamae が実行されるわけではない

といった問題があり、現在自動実行の仕組みを開発中です。

f:id:ryotarai:20150511221427p:plain

GitHubへのプルリクエストの作成やプッシュを契機として、dry-run や実際の実行を行います。上図の通りクックパッドでは Consul を利用しようとしていますが、Itamae の SSH 実行など他のバックエンドも用意しようと考えています。

今後の方向性

今後も低い学習コストで使い始められ、軽量・シンプルに使えるという特徴を維持していきます。 それと同時に、大きな規模になっても使える機能を備えていきたいと考えています。

欲しい機能がある場合やバグを見つけた場合は、遠慮なく Issue や Pull Request を作成していただければと思います。

Itamae Meetup is coming soon

ついに実用段階に入った、と言っても過言ではない Itamae ですが、利用事例も少しずつ聞くようになってきたのでミートアップを開催する予定です。日時などはまだ未定ですが、ぜひお越しください&発表してください。

Viewing all 726 articles
Browse latest View live