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

開発者テストの失敗を追跡しやすくすることで大人数での Web サービス開発を加速する

$
0
0

会員事業部サービス開発グループ長の村田です。

私は2015年1月から会員事業部でサービス開発エンジニアをやっていますが、2014年4月までは技術部開発基盤グループで Web サービス開発を加速させる様々な取り組みを実施していました。本稿では、開発基盤グループ時代に私が取り組んだ開発者テストの失敗を追跡しやすくする取り組みについて説明します。

クックパッドの Web サービス開発と CI

クックパッドのサービス開発は、大きくても5名くらいの小さなチームが一つの機能を担当します。しかし、多数のチームが1つの大きな Rails アプリケーションを同時に変更するのが特徴です *1

Web サービス開発を加速する工夫には様々な方向性が考えられますが、ここでは、クックパッドのようなスタイルでの Web サービス開発を加速するために開発者テストを何如に円滑にするかを考えます。

f:id:mrkn:20150519094528p:plain:w120

図: オムキンス

クックパッドではオムキンスと呼ばれる CI システムがあり、CI でのテストをパスしたリビジョンだけがデプロイを許されます。

サービス開発はデプロイしてからが本番です。開発中のサービスをユーザに出して、使われ方を分析して改善していくサイクルを何度も回すには、何度もデプロイする必要があります。 CI でのテストが円滑に成功し続けることが高速なサービス開発の肝です。

コミットと CI の監視

クックパッドでは、開発者は基本的に自分のコミットを自分で master ブランチにマージし、デプロイも自分でやります。そのため、開発者は自分のコミットがマージされた後に CI で走るテストの結果には十分気を付けています。

CI でのテスト結果はチャットに通知されます。そのような環境では、開発者は、自分のコミットが CI でテストされているときは、いつもよりチャットに注目して失敗にすぐ反応できるよう準備しています。その最中はいつもより開発に集中できません。開発に集中してしまうと、テストの失敗にすぐ反応できないからです。

そのような状況を解消し開発者が開発に集中できるようにするため、2012年に jenkins-hipchat-publisher プラグインをベースに、CI でテストが失敗したときにコミットした人をチャット通知で自動メンションするプラグインが、当時開発基盤グループに所属していた id:sora_hによって開発されました*2。そのプラグインによる通知の様子を以下に示します。

f:id:mrkn:20150519103844p:plain:w600

図: テスト失敗通知でのメンション

このようなメンション通知があるおかげで、チャットに張り付いていない開発者でもテストの失敗に気付きやすくなります。

チャットでの失敗通知をリッチにする

テストの失敗通知がチャットに流れたときの開発者の動きを見てみましょう。通知でメンションされた開発者は CI のテスト実行ログを確認します。どのテストが失敗したかを把握して次の行動に移るためです。このとき、下図で示す4つの場合に分かれます。

f:id:mrkn:20150519110911p:plain:w500

図: テスト失敗時の行動4パターン

失敗が自分の変更に関係ある場合は、チャットで修正中である旨を報告し、テストがきちんと通るように修正します (図の左上 Case 1) 。

失敗が自分の変更と無関係である場合は2つに分かれます。図の右上 Case 2 は、自分のコミットが原因で他人のテストを失敗させてしまった場合です。この場合は、原因を調査するために、失敗したテストの関係者を git blame で調べてチャットで質問したり、修正を移譲したりします。この作業は、CI の実行ログと手元のターミナルとを行き来する必要があり地味で面倒な作業です。

他人のコミットによって自分が書いたテストが失敗する場合もあります (図の左下 Case 3)。 この場合は、自分が書いたテストの内容が間違っていたり不完全だったりするので、テストを自分で修正する必要があります。しかし、自分ではすぐに失敗に気付けません。

このように、CI でテストが失敗した後に起きる行動には、CI の失敗に注目していない他人を巻き込む必要がある場合の方が多く、たいていその対象者は git blame で調べる必要があります。この工程は、サービス開発を遅延させる大きな要因です。

これを改善するため、以下に示す新しい通知を導入しました。

f:id:mrkn:20150519111046p:plain

図: テスト失敗の通知完全版

1行目が新しい通知です。下の2行は先ほどお見せした jenkins-hipchat-publisher プラグインによる通知です。

この通知の内容は、rspec がログの最後に出力してくれる、失敗した examples を再実行するコマンドラインとほとんど同じです。違いは、以下の要素が加わっていることです。

  • 「ファイル名:行番号」の部分が GitHub Enterprise へのリンクになっている (もちろん、該当行への直リンク)
  • その行の最終更新リビジョン (git blame の結果で、もちろん GHE へのリンクになってる)
  • その行を最後に変更した人と時期 (これも git blame の結果)

これらの情報がチャットに流れてくるだけで、失敗した example をすぐに調べられます。失敗が自分の変更と直接関係なさそうなときでも、git blame をしないで関係者をすぐ呼べます。開発者は、自分が必要なときだけテストの失敗ログを見に行けば良いし、手元で再実行したい場合もチャットで通知された rspec のコマンドラインを端末にコピペするだけです。

まとめ

本稿では、CI で失敗したテストについの情報をチャットに通知することで、開発者テストの失敗を追跡しやすくする方法について説明しました。

最後に、この通知内容を生成するスクリプトを紹介します。このスクリプトは、標準入力に rspec がログの最後に出力する rspec コマンドのリストが与えられる事を前提に書かれています。コマンドライン引数で、欲しいフォーマット (html, json, plain-text) と git のブランチ名を与えます。

#! /usr/bin/env rubyrequire'pathname'require'time'require'rubygems'require'bundler/setup'require'action_view'includeActionView::Helpers::DateHelperGHE_REPOSITORY_ROOT = ENV["GHE_REPOSITORY_ROOT"]

defshort_ref(ref)
  `git show --oneline #{ref}`.each_line.first.split(//)[0]
end

format = ARGV[0]
branch = ARGV[1]

root_dir = Pathname.pwd
app = root_dir.basename

entries = $stdin.read.lines.map { |line|
  rspec, filename, lineno, description = line.chomp.sub(/\s*#\s*?(.*)$/, "\\1").split(/[ :]/, 4)
  nextnilunless rspec && filename && lineno
  description ||= ''

  spec_real_path = Dir.chdir(File.dirname filename) { Pathname.pwd.join(File.basename filename).relative_path_from(root_dir) }

  [ filename, lineno ].tap do |ary|
    blame = `git blame -w -l #{spec_real_path}#{additional_argument}`
    hash, author, timestamp = blame.match(/^([0-9a-fA-F]+)\s+(?:\S+\s+)?\(([-+=^:;<>_@\.0-9A-Za-z ]+?)\s+(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}\s+[-+\d]+)\s+#{lineno}\)/m)[1,3]

    relative_timestamp = time_ago_in_words(Time.parse(timestamp))
    ary << hash << "#{author}, #{relative_timestamp} ago"<< spec_real_path << description
  end
}.compact

case format
when'html'if branch
    entries.each do |filename, lineno, hash, info, spec_real_path, description|
      message = "rspec "

      path = root_dir.join(spec_real_path).relative_path_from(root_dir.parent)
      message << %Q[<a href="#{GHE_REPOSITORY_ROOT}/blob/#{branch}/#{path}\#L#{lineno}">#{filename}:#{lineno}</a>]
      message << %Q[ \# (<a href="#{GHE_REPOSITORY_ROOT}/commit/#{hash}">#{short_ref(hash)}</a>) #{info}<br />]

      puts message
    endendwhen'json'require'json'

  failures = entries.map do |filename, lineno, hash, info, spec_real_path, description|
    path = root_dir.join(spec_real_path).relative_path_from(root_dir.parent)
    { :file => filename, :line => lineno,
      :commit => hash,
      :description => description,
      :real_path => path,
    }
  end

  payload = {:failures => failures}
  payload.merge!(:build_url => ENV["BUILD_URL"]) ifENV["BUILD_URL"]
  payload.merge!(:build => ENV["BUILD_NUMBER"]) ifENV["BUILD_NUMBER"]
  puts payload.to_json
else
  entries.each do |filename, lineno, hash, info, spec_real_path, description|
    puts "rspec #{filename}:#{lineno} \# (#{hash}) #{info}"endend

*1:Akira Matsuda. The recipe for the worlds largest rails monolith. Ruby on Ales 2015

*2:このプラグインは社内サービスから情報を取得する必要があるためオープンソースにしてません


クックパッドの新卒研修2015

$
0
0

技術部の牧本 (@makimoto) です。 新卒社員研修の企画・運用を担当しています。

このエントリをご覧になっている方の中には、この春から新社会人として働いている方も多いのではないでしょうか。

クックパッドでもこの春に新卒社員が入社し、現在新卒社員研修の真っ最中です。

本稿では、クックパッドにおける新卒社員向けの技術・サービス開発領域の研修についてご紹介をします。

みんなが技術を理解すること

今年の新卒研修では、エンジニア職ではない総合職の新卒社員にも技術研修を実施しました。

職種や役割にかかわらず、すべてのメンバーが技術を正しく理解し、活用することが、組織の成長に役立つと考えているからです。

営業やサービス開発のディレクターなどエンジニア以外のメンバーが、技術的な知識にもとづいた意思決定をできるようになることで、もっと事業の精度や速度の改善に貢献できるのではという思いがあります。

とは言え、エンジニア職と総合職とで同じ研修プログラムを受けてもらうのは困難なので、セキュリティや品質などの共通で理解してほしい研修を経たあと、職種ごとに組まれた技術研修プログラムに参加してもらいました。

総合職向け技術研修

今年度の総合職向けの技術研修は、以下のようなメニューにしました。

  • 社内のエンジニアによるケーススタディ (レシピ検索、分析システムなど)
  • 情報技術の活用 (検索技術と暗号技術の仕組み)
  • ハードウェア
  • ソフトウェア
  • ネットワークとインターネット
  • ウェブアプリケーションの構造
  • プログラミングの基礎

直接は業務には関わらない (ので配属後に学ぶのが難しい) ハードウェアやソフトウェアの仕組みから、インターネットがどのように繋がっているか、われわれが提供しているウェブアプリケーションがどのような構造になっているかを学ぶプログラムです。 プログラミングについては、概念的な話だけではなく実際触ってみて身につく面も多いので、基礎的なプログラミングの研修も加えました。

Brian Kernighan 『ディジタル作法*1などからカリキュラムを選定し、独自の教材を開発しました。

総合職新卒社員は、理工系出身から社会学系出身まで多様なバックグラウンドを持っていますが、実際に手を動かすワーク (例えば、RSA 暗号を手元で計算して体験する、など) を取り入れたり、先に理解できた人がまだの人を教える仕組みを作ったりして取り組んでいます。

エンジニア職向け技術研修

エンジニア職の技術研修では、個人開発では体験できない内容にフォーカスしました。

  • インフラ研修
  • チーム開発研修
  • クックパッドのウェブアプリケーションの例外調査・修正

インフラ研修では、インフラストラクチャー部の協力のもと、実際に本番環境で使える sudo 権限や AWS のキーを新卒社員に与えて、サービスのサーバ構成の調査やサーバのセットアップと本番への導入などを体験します。

チーム開発研修はワイクル株式会社の角征典様に協力いただき、『Team Geek』やアジャイル・スクラムのプラクティスを取り入れたチームで成果を出すための技法を学ぶ研修でした。

f:id:makimoto:20150508173822j:plain

チーム開発研修の一幕
アイディアを組織に広めるゲーム

サービス開発研修

サービス開発における PDCA サイクルを高速に回す考え方は、どのような仕事にも応用が効ききます。 ユーザーの課題発見、施策の効果を検証するまでの、ひと通りの流れを体験しながら学ぶプログラムとして設計しました。

  • サービス企画フレームワーク
  • ユーザー調査
  • プロトタイピング
  • 施策検証

サービス開発に携わっているエンジニア・デザイナーを講師に、座学や過去の施策から学ぶケーススタディ、具体的なテーマに沿った課題などを行ないました。

リースタートアップなどの理論と実際の過去の施策における成功事例・失敗事例などを知るとともに、特定のテーマに沿って EOGS *2やリーンキャンバスを書いてもらったり、ユーザーインタビューの設計・実施をしたりなど、実際にサービス開発で行なうプロセスを実践します。

f:id:makimoto:20150522085026p:plain

サービス開発研修の一幕
ユーザーインタビューを行ない、そこで得られた知見をもとにペルソナを構築する

おわりに

本稿では、クックパッドにおける新卒社員向けの技術・サービス開発領域の研修についてご紹介をしました。

この規模の研修プログラムを組むのはクックパッドとしても初めての取り組みでしたが、われわれの技術や知識、アイディアの棚おろしをして、見直す良い機会になりました。*3

新卒研修のゴールは、新卒社員が実務で活躍できるようになっていることです。 この研修を通して、新しい仲間たちが現場で活躍できるように引き続きサポートしていきます。

最後に宣伝ですが、クックパッドでは今年も学生の皆さんを対象に夏のインターンシップの開催が決定しました。 詳しいことが決まり次第、本ブログなどで紹介しますので、今しばらくお待ちください。

*1:この本は新卒ソフトウェアエンジニアのための技術書100冊 - クックパッド開発者ブログのリストにも含まれています

*2:Emotion Oriented Goal Setting. クックパッド内製のサービス企画フレームワーク

*3:特に、研修全般を取りまとめている私にとっては、研修教材をまとめて見ることができるので非常にお得な機会でした。

サービス開発におけるエンジニアの役割

$
0
0

会員事業部*1の小川(@conceal_rs)です。

会員事業部ではプレミアムサービスの価値を向上させるために、日々機能改善や新しい機能やサービスの開発をしています。今回はサービス開発をするときにエンジニアがどういう役割を果たすといいかについて、私なりの経験からの話をしたいと思います。

サービス開発とは

サービス開発とはユーザのみなさんに、アプリやウェブを通じて何らかの価値を提供することだと考えています。価値と言ってもいろいろなものや形態があり、Webサービスというとだいたいがツールぽいものを想像しますし、最近だとゲーミフィケーションを使った脳トレサービスなどもあります。また既存のサービスに新しい機能や体験を追加して価値を届けるということもサービス開発です。私が所属している会員事業部はプレミアムサービスを利用して頂いているユーザのみなさんに新しい価値を提供すべく、日々業務に勤しんでいます。

もちろんサービス開発とは、何も新しいものだけではありません。例えばバグ修正や既存機能の改善、また他社と連携した企画など、さまざまなものを含んでいます。開発規模も数日で終わるものから数ヶ月かかるものまで、果ては数人で数ヶ月以上かかるものまで多種多様な開発があります。

クックパッドでのサービス開発

ではクックパッドにおけるサービス開発はどのように行っているのか。このブログでもサービス開発についての幾つか記事が書かれていますが、ここでは私がいままで行ってきたサービス開発についてお話したいと思います。

チームで開発する

クックパッドではサービス開発をするとき、ディレクターとエンジニアがチームを組むことがほとんどで、その多くがディレクター1名とエンジニア1名の2名体制という少人数です。少人数での開発になる理由としては、クックパッドでのサービス開発ではそこまで大規模なものはあまりないということと、少人数だとスピード感があるためです。

大規模なものがないというと先ほどの開発規模の話と齟齬があるかもしれませんが、実際にサービス開発に関わるときに関わる範囲というのはそれほど大きくはありません。例えば小さな機能であればRailsで言うところのコントローラー単位になることが多く、また大きなものになったとしてもchankoのような限定公開する仕組みを使っているため、影響範囲を閉じ込めることが可能です。

別の観点では、ディレクターとエンジニアが一対一で話をするほうが、より充実した議論ができる可能性が高いということも理由の一つです。改善や機能追加であっても、どのようなものをユーザのみなさん提供するかについてしっかり議論しなければならないと考えています。議論するときにあまり人数が多くなると、論点が発散してしまって収集がつかなくなることもあります。そのようなときに二〜三人であれば、議論もそこまで発散しないですし、落とし所を探りやすいという印象があります。

ユーザへ提供する価値を考える

サービスを開発するときには、まずユーザのみなさんに提供する価値がどんなものであるのかをしっかり考えるようにしています。当たり前のように思えて意外に難しいのがこの価値を考えるプロセスです。EOGSやAARRRなど色々なツールを使うことが多いですが、まずは使ってもらうユーザさんのことをしっかり考えるように心がけています。例えばクックパッドの主なユーザは主婦層ですが、じゃあいつ使うのか、昼時ならどういう気持ち・状況で使っているのか、重要度・切迫感はどの程度かなど、いろいろなことを考えるように心がけています。

とは言え私は男性なのですべてわかるとは思っていません。そういうときは身近にいる人のことを考えたり、社内でターゲットユーザに近い人を探して話を聞いたりします。それで全てがわかるわけではないですが、よりユーザさんの目線に近い人とたくさん話をすることで、いろいろなことに気づけるので積極的にやるべきだと考えています。

またデータもわかる範囲で調べるようにしています。例えば官公庁が出している白書や調査会社が公表しているデータからわかることも多いですし、クックパッドへのアクセス情報からわかることもあります。

これらの気づきやデータも参考にして、提供する価値についてしっかりと考えることを心がけています。

効果測定する

さて価値を考えてチームで実装すればあとは公開するだけですが、ただ公開すればいいというわけではありません。事前に考えた価値に本当に効果があったかを検証しなくてはなりません。この辺りをしっかり考えておかないと、漠然と「なんとなく使われている」ということしかわからず、どう改善していいものか判断できなくなってしまう可能性があります。よりよいものを作り続けるためには、この効果測定をしっかり設計しなければなりません。逆に言うと、測定できる指標をしっかり設計しないと、何が良いものであるかを判断できないということでもあります。

エンジニアのサービス開発への関わり方

サービス開発の概要は説明してきましたが、ではエンジニアはどのように関わるべきなのでしょうか。これも私の体験からくる持論なのですが、簡単に紹介したいと思います。

企画段階では実現可能性について考えない

エンジニアであれば企画を考えたり相談したりしているときに、どれ位の期間で実装できるか、実装可能かどうかについて考えることが多いと思います。もちろん実現不可能なものを考えても仕方ないのですが、私自身は企画段階では実現可能性についてあえて考えないようにしています。「そんなことしたら無理ゲーやらされることにならない?」と不安に思うかもしれませんが、企画が固まりきるまでに実現できるかどうかを考えてしまうと、逆に発想が制限されてしまう可能性もあります。この制限によって、本当に作りたかったものではないものとは微妙に違うものができあがってしまっては元も子もありません。作りたい価値を明確にするまではできるかぎり自由な発想で議論できるようにしたいので、実現可能性についてはあえて考えないようにしています。

サービスの価値を一歩引いて考える

じゃあサービス開発するときにエンジニアが気をつけるべきことってなんでしょうか。例えばディレクターから企画が出されたときに漠然と良さそうだと感じたとしたら、そのまま実装に入ってしまうことって多いかもしれません。ただこの漠然とした感想というのが危険です。本当に良いと思っていないのに作り始めると、たいてい中途半端なものになってしまいます。そのようなときは、できれば一歩引いた状態で考えることを心がけています。ただ一歩引いて考えると言ってもなかなか難しいものです。漠然と良いとか悪いと感じることってなかなか言語化できないので説明もしづらいし、そうすると何も言えなくなって「黙っておこうかな」と思うこともあるかもしれません。

あえて「なぜ」と問いかける

そういうときの一つの方法として、あえて「なぜ何ですか?」と問いかけるようにしています。例えば「料理が楽しくなる機能」についての企画であれば、「料理が楽しくなる必要があるの?」と問いかけます。個人的には楽しくなったほうがいいと思っていますが、なぜ「楽しいほうがいい」のかについて詳しく議論されていないことも多く、また企画者がその理由を言語化しきれていない場合、チームが漠然とした価値に向かって進んでいってしまう危険性があります。そのような状態では精度が低いものが生まれる可能性が高く、公開しても価値を感じ取ってもらえずに終わることになってしまいます。これでは意味がありません。そのためにもできるかぎり問いかけを続けるようにしています。

そもそも論、極論を提示する

もう一つの方法として、「そもそも論」や極論を言うときもあります。例えば先ほどの「料理が楽しくなる機能」であれば、「そもそも料理って自分でする必要があるの?」という問いかけになります。そこで「例えばある業者が1食100円でバランス的にも食材の安全性についても完璧な料理を作ってくれるとしたら、自分で料理する意味はあるの?」という極論を出します。もちろん現実的には実現不可能だとは思うのですが、価値を考える上ではこのような問いかけをすることも重要です。

このような方法を使う理由としては、一般的に何らかの議題について話をしたり考えたりするときには、人は何かしら前提条件をおいている可能性が高いと考えているからです。考えるべき価値というのは議題の奥深くに存在していることがほとんどなのに、その表層だけをたどってしまって価値に行き着かないことがわりと多いと感じています。そのためにも「そもそも論」や極論を提示してみるのも一つの方法です。

よりよいサービス開発に向けて

このようにエンジニアが行うサービス開発といっても、企画を考えて実装するだけではなく、いろいろな役割があることをつらつらと書いてみました。もちろんこれが正解というわけではなく、他にもっといい方法があるかもしれません。日々試行錯誤しながらよりより価値を提供できるようにサービス開発を続けています。クックパッドでは一緒にサービス開発をするエンジニアを募集しています。我こそはと思われる方はぜひご応募ください。

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

モバイルファースト時代のネットワークレイヤデバッグ手法

$
0
0

こんにちは。インフラストラクチャー部 セキュリティグループの星 (@kani_b) です。
クックパッドでは主に "セキュリティ"か "AWS"というタグのつきそうな業務全般を担当しています。

ここ数年、クックパッドではいわゆるネイティブアプリの開発が非常に盛んです。
私達インフラストラクチャー部はネイティブアプリの直接の開発者ではありませんが、開発が円滑に進むように色々なレイヤでそのお手伝いをしています。

PC 向けサービス開発と比較して、スマートフォン向け、特にネイティブアプリにおいては、何かトラブルがあった際に どこで何が起きているか、そのデバッグを行うことが若干難しいと感じています。 今回はいわゆる jailbreak や root 化をせず、ネットワークのレイヤからデバッグを行う方法についていくつかご紹介します。

HTTP プロキシによるキャプチャ

まずは HTTP プロキシを利用してリクエスト/レスポンスのキャプチャを行う方法です。 アプリが行っている通信が HTTP/HTTPS だけで、対象のスマートフォンに自由にアクセスでき、 HTTP レイヤで様子を伺うことができれば良い場合はこちらを使います。

以下のようなソフトウェアが使えます。

  • Charles Proxy
    • シェアウェア。GUI で操作できる
  • Burp Proxy
    • Free 版と Professional 版がある。GUI で操作可能。脆弱性検査などにも多く利用されている (主に Professional 版)
  • Fiddler
    • Burp と同じく、脆弱性検査などにも利用されているプロキシ。無料ソフトウェアとして公開されている
  • mitmproxy
    • OSS。Python で実装されている。ターミナルから使う

これらのツールの使い方は基本的には共通しています。起動すると PC に HTTP Proxy を作成してくれるので、 開発用のスマートフォンのネットワーク設定を変更し、起動した Proxy を経由するようにするだけです。 PC が接続されているネットワークと同じネットワークに接続して設定するのが楽で良いでしょう。 他のソフトウェアにも様々な機能がありますが、デバッグ用途であれば大半のケースで mitmproxy が使えると思います。

以前のバージョンの mitmproxy で HTTPS 通信をキャプチャするためには、初回起動時に生成された証明書一式を 何らかの手段でスマートフォンに転送して…という設定を行う必要があったのですが、 現在はプロキシの設定をした状態で mitm.itにアクセスすると証明書をダウンロードできます。 (上記リンクを踏むとわかりますが、プロキシを通さないと正しく設定ができていないことがわかります)

これで、HTTP/HTTPS 通信をキャプチャする用意ができたので、スマートフォン側でプロキシの設定をするだけです。 試しにクックパッドアプリ開発環境の通信をキャプチャしてみると以下の画面のように見えます。 f:id:kani_b:20150528164909p:plain

任意のリクエスト/レスポンスについて詳細 (header, body, etc) を確認することもできます。 また、リクエスト/レスポンスのリプレイや書き換えも可能なので、 例えばクライアント、サーバ共に細工したリクエストやネットワーク環境悪化による再送などが悪影響を及ぼさないか調査することも可能です。

パケットキャプチャ

HTTP プロキシを使うとだいたいのケースでデバッグを行うことが可能ですが、 以下のような場合は HTTP レイヤではなく IP レイヤでキャプチャを行う必要があります。

  • キャリア回線を使っている時の通信をそのまま解析したい
  • (DNS|SSL/TLS|その他 HTTP 以外のプロトコル)で行われている通信を確認したい
    • 例えば TLS handshake で決まった CipherSuite の確認など

iOS の場合

iOS と Mac を利用している場合、Remote Virtual Interface (RVI) が使えるようになっています。 RVI は Apple のドキュメントにも記載されていますが、USB 接続した iOS デバイスが行う通信を Mac 側にミラーしてくれるインタフェースです。 通信を Mac 経由で行うのではなく、あくまで iOS デバイスのメインネットワークインタフェース (4G や Wi-Fi) が行っている通信のコピーが Mac に流れてきているだけですので、自然な形でキャプチャを行うことができます。 RVI のセットアップも、Mac と iOS デバイスを USB で接続し、対象の iOS デバイスの UUID を調べたら以下のように RVI デバイスを作成します。

$ rvictl -s xxxxxxxxxxxxxxxxxxxxxx (デバイスの UUID)

この状態で ifconfig を見ると rvi0 というインタフェースが作成されています。

$ ifconfig rvi0
rvi0: flags=3005<UP,DEBUG,LINK0,LINK1> mtu 0

このインタフェースは他のネットワークインタフェースと同様に扱えますので、tcpdump や Wireshark など お好みのネットワークアナライザでキャプチャや解析を行うことが可能です。 一例として、Wireshark を使えば GUI 上で簡単に解析を行うことができます。 f:id:kani_b:20150528164934p:plain

Android の場合

Android は root 化なしに RVI のようなものを使う手段がないため、 tPacketCaptureなどキャプチャ用 Android アプリを利用するか、 Android が利用するネットワークの経路上 (例えば PC を Wi-Fi AP にしてそこに接続する) でキャプチャを行う必要があります。
他に良いキャプチャ方法をご存知の方がいたら是非教えてください!

デバッグできるレイヤを増やす

スマートフォンアプリに問題が起こった際、上記のようにネットワークレイヤからも調査できるようにしておくと 調査の幅が広がります。
例えば、筆者は以下のようなことをこれまで紹介したような方法で調査していました。

  • アプリの不具合により、HTTP ヘッダに予期していないバイト列が含まれリクエストに失敗する現象
  • アプリが利用する特定のホストに対して名前解決が行われなくなる現象
  • 特定のアプリ/端末が HTTPS 接続で利用する CipherSuite
  • API エンドポイントに不正な文字列やリプレイされたリクエストを送出した時の挙動

まとめ

この記事では、主にネットワークのレイヤから、スマートフォンアプリのデバッグを行う手法について解説しました。 ここで紹介した手法はアプリ開発者に限らず、サーバ側の開発・運用に携わる方も覚えておいて損はないと思います。
クックパッドではエンジニアの役割によらず、それぞれの得意とするレイヤで協力しながら調査を行うことがよくあります。
色々な視点から、楽しいデバッグライフを送りましょう。

モバイルファースト時代のWebアプリケーションデバッグ手法

$
0
0

買物情報事業部の前田 (@TakatoshiMaeda) です。

Webアプリケーションを開発していると、思ったようなスタイルが適用されなかったりJavaScriptの挙動が意図しないものとなっているケースがままあります。そのような時に、Chrome Developer ToolsSafari Web Inspector等を用いてスマートフォン実機に接続をしてデバッグしますが、

  • Android標準ブラウザ
  • Android/iOSアプリケーション内部のWebView*1

ではインスペクタとの連携に対応しておらず、上記ブラウザでのみ再現がされる不具合に対してはHTML/CSSの確認やJavaScriptを実行しながらのデバッグは基本的に出来ません。 weinreというデバッグツールを用いて、上記ブラウザ環境下でWebアプリケーションのデバッグを行う方法について紹介します。

weinre

weinreはNode.jsで開発されているWebアプリケーションのデバッグツールです。開発PC上でweinre serverを立ち上げ、webページに埋め込んだweinreクライアントとやりとりを行うことで、ブラウザがインスペクタに対応していない場合でもインタラクティブなデバッグが可能です。

f:id:takatoshi-maeda:20150528172447p:plain

JSConsole等類似のデバッグツールもありますが、weinreはブラウザ標準のインスペクタと似たようなインターフェースと機能を備えており違和感なく利用できることからweinreを利用しています。

開発PCと同一LANにスマートフォンが接続されている環境で、実際に実機でデバッグを行う手順を解説します。

インストールから起動まで

Node.jsがインストールされていればnpmコマンドでインストール可能です。

$ npm install -g weinre
$ weinre --boundHost $YOUR_LOCAL_IP

上記コマンドでweinreが立ち上がります。boundHostオプションの指定ですが、スマートフォンから開発サーバーにアクセスをする場合、localhostを指定してしまうとデバッグに必要なJavaScriptの取得が出来ないため、開発機のローカルIPを指定して下さい。

weinreの起動確認と、対象となるJavaScriptの埋め込み

開発機のローカルIPの8080ポートに対してブラウザでアクセスをすると、以下の画面が表示されます。

f:id:takatoshi-maeda:20150528172457p:plain

この画面が表示されれば起動しています。

ここからデバッグ対象のWebアプリケーションにデバッグコードを挿入します。

  • 対象となるスクリプトをscriptタグで事前に挿入する方法(Target Script)
  • ブックマークレットでscriptタグを動的に挿入する方法(Target Bookmarklet)

の2つの方法がありますが、今回はブックマークレットを用います。 Target Bookmarkletセクションに

Javascript:(function(e){e.setAttribute("src","http://10.0.1.9:8080/target/target-script-min.js#anonymous");document.getElementsByTagName("body")[0].appendChild(e);})(document.createElement("script"));void(0);

上記タグが生成されています。スマートフォンにコピーして、デバッグを行いたいページでアドレスバーに貼り付けて下さい。貼り付けた後、開発PC上でhttp://$YOUR_LOCAL_IP:8080/client/#anonymousにアクセスすると、インスペクタが立ち上がっています。

f:id:takatoshi-maeda:20150528172511p:plain

Targetsに対象となるサイトが表示されていれば接続に成功しています。 通常のインスペクタと同様、選択しているDOMエレメントの配置確認や動的なスタイル変更、JavaScriptの実行が可能です。

f:id:takatoshi-maeda:20150528172518p:plain

ネイティブで提供されているインスペクタと比べると手間がかかりもたつきを感じることは事実ですが、不具合の手がかりが全くない状況でバグを潰すよりも遥かに効率が良いと感じるはずです。

最後に

AndroidやiOSでは新しいバージョンのOSであれば標準でデバッグが可能となってきており取り巻く状況は改善してきています。しかし、まだまだ古い環境で利用されているユーザーさんが多いことも事実です。 この記事が問題に直面した方の開発効率化の一助となれば幸いです。

*1:AndroidはKitKat以降ではコードでデバッグオプションを有効にするとWebViewもインスペクタと接続でき、iOSは6.0以降であればXcodeでビルドし端末に転送したアプリケーションはインスペクタと接続できます

サービスを通じて日々の嬉しい体験を増幅する

$
0
0

こんにちは。ユーザファースト推進室エンジニア兼デザイナーの長野です。

クックパッドでは、今年に入ってからレシピやつくれぽ*1を外部のSNSに共有できる機能を拡充しており、そのデザインおよび開発を担当しています。

本エントリでは、特につくれぽ共有の施策について、サービスの背景やねらいをご紹介したいと思います。進行中のプロジェクトのため数値的な成果などをご紹介することができないのですが、本エントリを通じてクックパッドのサービス開発の考え方をお伝えできればいいなと思います。

つくれぽ共有施策の概要

クックパッドのサービス上でつくれぽ写真をタップすると、写真を大きく見ることができ、そこからつくれぽをFacebookやTwitterなどの外部SNSへ共有することができます。

f:id:yoshiko-nagano:20150601110955p:plain

つくれぽ拡大表示画面(左:PC、右:スマートフォン)

このつくれぽをSNSで見た人は、リンクを押すとつくれぽ固有のパーマリンクへ遷移することができます(これまでつくれぽにはパーマリンクがありませんでした)。 さらに、パーマリンクや拡大表示の画面には「いいね」ボタンがあり、つくれぽの作者さんに向けてライトなフィードバックを送ることができます。

f:id:yoshiko-nagano:20150601110947p:plain

つくれぽパーマリンクページ(左:PC、右:スマートフォン)

施策の背景

これまで、つくれぽはレシピの作者さんへ感謝の気持ちを伝えるためのものとして機能してきました。送られたつくれぽはレシピ作者さんの元へ届き、レシピの作者さんはそこに返信コメントを返すことができます。 あくまでつくれぽはレシピに従属するものとして設計されており、それ故に各つくれぽのパーマリンクも用意されていませんでした。

しかし、実際つくれぽを送る工程を想像してみると、他人のレシピを見ながらではあるものの、自分なりのアレンジや工夫を加えたり、盛り付けにこだわったり、写真を綺麗に残すために創意工夫をしたりと、とてもクリエイティブな作業があふれています。 そこで今回の施策では、つくれぽも一人のユーザーさんによって作られた貴重なコンテンツ(作品)であると捉え、サービスの設計を進めました。

施策のねらい

自分がみつけた美味しいレシピを共有できれば、それが誰かの役に立つかもしれない。美味しく上手につくれた料理を誰かに共有すれば、家族やレシピ作者さん以外にも、注目されたり褒めてもらえるチャンスがあるかもしれない。そんな風に、つくれぽを送る人の料理や生活がより楽しくなるチャンスを広げることが本施策のねらいです。

また、レシピ作者さんにとっても、つくれぽが外部に拡散されることによって、よりレシピを見てもらえるチャンスが増えます。 SNS上でつくれぽに出会うユーザは、美味しそうな料理写真をきっかけに、その作り方まで詳細に知ることができるようになります。 もちろんクックパッドは拡散されたつくれぽからクックパッドを訪れてくれるユーザが増えればバンザイです。

このように、各登場人物がwin-winの関係になるよう設計を行いました。

f:id:yoshiko-nagano:20150601101847p:plain

本施策のEOGS(Emotional Oriented Goal Setting*2

リリースと改善

本施策のリリースは以下のステップで段階的に進めました。

  1. つくれぽのパーマリンクをリリース
  2. 各種デバイス・画面にSNSシェアのリンクを順次追加していく
  3. ライトフィードバックをリリース

2の最初でスマートフォンWebにだけ導線を追加し、ある程度流入の傾向が見えてきたところで、一度パーマリンクのデザインを見直しました。 具体的には、FacebookからスマートフォンWebに流入した場合の直帰率が他に比べて高くなっていることが分かったので、そこを重点的に改善対象としました。

直帰率が上がっている原因として以下の仮説を立て、デザインを修正しました。

  • Facebookの場合、友人の投稿した料理写真に興味をもってクリックしている
  • レシピよりも投稿者に関心があるが、初期デザインのパーマリンクでは投稿者の他の投稿などがファーストビュー(および1スクロールくらいで見える範囲)で目に入らない
  • 結果、自分に関心のある情報に出会えず離脱する
f:id:yoshiko-nagano:20150601111004p:plain

パーマリンクデザイン改善(左:Before、右:After)

改善のポイントは以下です。

  • ファーストビューで投稿者の人感を感じられるようにし、その人の他の投稿に遷移しやすくする
  • 同時にレシピの位置は下げずに、興味を持った料理写真のレシピにはすぐアクセスできる状態を確保する

この変更の結果、問題となっていた直帰率はぐっと下がり、PCへの流入の場合や他のSNSからの流入とほぼ同等の数値にまで改善することができました。

このように細かく改善を行いながら、各デバイスでリンクの設置場所を増やし、現在は3のフェーズのライトフィードバック導入へと進んでいます。 拡散したものが見てもらえて、嬉しいフィードバックとして届くところまでがこの施策なので、今後も機能追加と改善を続けていきます。

日常の嬉しい体験を増幅するという考え方

本施策の本質的なゴールは、「レシピを見て美味しい料理が作れた!」という体験で得られる楽しさや喜びを、サービスを通じて増幅させることです。 日常生活で日々行われている作業をテクノロジーの力でより喜びの多いものにする。クックパッドでのサービス開発の根底には、常にこのような考え方があります。

そもそも、クックパッドというサービスの始まりも「レシピを検索する」よりも先に「レシピを載せる」ためのサービスでした。 毎日料理をしていても、褒めてくれるのは家族ぐらい。ともすればとても孤独な作業になりかねない料理を、レシピという形でインターネットに発信することで、遠いどこかの家庭で自分の料理が役に立つ可能性が生まれる。日々の生活に埋もれてしまっているけれど、実はとてもクリエイティブで誰かの役に立てるはずの作業を、ツールとプラットフォームを提供することで世の中にオープンにし、誰かの役に立つ喜びや人に褒められたり注目される喜びを作り出すのが、私たちのサービスの役割だと思っています。

もしこのエントリを通じて、クックパッドのサービス開発にご興味を持っていただけた方がいらっしゃいましたら、ぜひエンジニア・デザイナー採用へエントリーください。ご応募おまちしております!

*1:クックパッド上でレシピを見て料理を作ったユーザから、料理写真にコメントを添えてフォトレポートを送れる仕組み

*2:クックパッド内製のサービス企画フレームワーク。サービスの各登場人物の疑いようのない欲求を整理し、それらを満たす解を導く。企画時にこれをきちんと設定すると、サービスの登場人物と欲求が明確になり、開発の目的がぶれにくく、成功のイメージを共有できる。

Cookpad Apple Watch App 誕生の舞台裏

$
0
0

f:id:yskm39:20150603171838j:plain

買物情報事業部の三浦です。

クックパッドではApple Watchの発売に合わせて、iOSアプリをApple Watch対応にアップデートしました。 クックパッドのWatchアプリは、レシピを閲覧するという機能だけをもったとてもシンプルなアプリです。 実はそんな王道なアプリでも、いくつかの紆余曲折を経て完成させました。今回はその開発の経緯を通して、改めて実感したシンプルさやユーザー視点の大切さをお伝えできればと思います。

アプリの機能紹介

クックパッドのWatchアプリでできることは以下の2点のみです。 とてもシンプル。

  • 一番最後に見たレシピの閲覧(材料と手順のみ)
  • 調理時間から起動できるタイマー機能

f:id:yskm39:20150603115507p:plain

レシピ閲覧機能にした経緯

クックパッドでWatchアプリをつくるとなれば、「レシピを見る以外他にはないでしょう」と思われるかもしれません。 でも開発当初は「Watchでレシピを読まなくない?」、「レシピ見るならiPhoneでよくない?」という意見があり、私自身も最初はそう思っていました。

クックパッドではレシピ閲覧以外のサービス機能を提供しているので、それらに関連したものをつくろうと考えました。 いくつか上がったアイデアの中に、買物リストアプリというものがありました。クックパッドには買物リスト機能があり、レシピにある材料を買物リストにいれて管理することができます。 買物時は片手でかごを持ちながらもう片方で食材を選ぶ。その場面で、iPhoneをかばんから取り出して買物リストをチェックするよりも、手首にあるWatchでチェックする方ができ、ユーザー体験としてもWatchだからより便利になるアイデアだと感じ当初はこの企画を進めていました。

ユーザーのことを考える

しかし、WatchアプリはiPhoneアプリと一対の関係なので、一つしかつくることができません。企画を進めていく中で、クックパッドのユーザーが本当に期待しているもの、多くのユーザーが対象になるものは何かという部分にフォーカスして、今一度企画を考えなおしました。そうすると、やはり「クックパッド=レシピ」というところに戻ってきました。 レシピ x Watch で何かできないかを考え直しました。

f:id:yskm39:20150603115510p:plain

サブとして考える

ただ、iPhoneアプリのメイン機能であるレシピの検索やレシピを読むという体験は、Watchである優位性を出すことができません。 そんな中で、料理中というシーンだけにフォーカスをした時に可能性を感じました。

料理中のアプリの役割としては、材料や分量、次の手順など、ちょっとした確認作業で十分です。手のふさがる料理中であれば、Watchの優位性が出てきます。 料理中のちょっとしたことを手助けするツールとして、この料理中の機能だけ切り出してWatchアプリに担わせてはどうかと考えました。 それぞれデバイスの特性があるので、あくまでもiPhoneアプリをリプレイスするものではなく、サブとして役割分担をしてあげるとより効果的になりそうだと感じました。

f:id:yskm39:20150603115512p:plain

ユーザーストーリーを組み立てる

Watchアプリの開発にあたってユーザーストーリーをたてました。

ペルソナ

  • 名前: 石原 聡美
  • 年齢: 29歳
  • 性別: 女性
  • 職業: 専業主婦
  • 家族: 夫と2人のこども (6歳と3歳)

ストーリー

  • iPhoneで夕飯の献立を決めて、材料や作り方はざっと覚えて調理開始 → 台所は狭くiPhoneは少し離れたキッチンボードにおいている
  • 合わせ調味料の分量はどれくらいだったか?手元でさっと確かめた → Cookpad Watch Appを立ち上げて、材料を確認
  • 材料も切り揃って、まずは何から炒めるのか再度確認をした → 作り方を見て確認
  • 最後は具材に味がしみるまで5分計って煮た → 作り方のところにあるタイマーをタップして5分計る

これは一緒に開発したデザイナーの元山が作成しました。 このユーザーストーリーは後の開発で機能の取捨を迫られたり意見が食い違う場面でも、常にこの人が本当に欲しいものは何かという軸で議論することができたので焦点がぶれず、プロダクトをつくるときに非常に重要な役割になりました。

最近見たレシピ機能をもたせる

f:id:yskm39:20150603115515p:plain

iPhoneアプリには"最近見たレシピ"というレシピ履歴機能があります。 多くの場合、この履歴の中にレシピ決定されたものが入っていると想定されるので、この機能をWatchアプリにのせることに決め開発を進めました。

レシピの情報を最低限に

f:id:yskm39:20150603115516p:plain iPhoneアプリでのレシピ詳細は、レシピの検索、決定、料理中、料理後と様々なシーンで使われる情報を網羅しています。 これを料理中に必要な情報と限定すると、タイトル、写真、作者名、材料、作り方の5つの要素に絞られます。情報としてもとてもシンプルになります。 あくまでもその他の要素はiPhoneアプリで見てもらうという分担です。

f:id:yskm39:20150603115518p:plain

こちらがWatchアプリの詳細画面になります。作り方も、スクロールで一覧性を上げるよりも、料理中に自分がどこの工程にいるかがわかるように、ページングで1工程ずつ確認できるようにしました。

レシピ選択画面をどうするか

f:id:yskm39:20150603115521p:plain

履歴機能ということなので、レシピ選択をできる画面も用意しました。 上記が当初の画面遷移図の一部です。レシピ選択画面もページングで選択可能にしています。

この遷移だとタイマー画面を表示するには、レシピ選択→レシピ詳細→タイマー と二回モーダルで画面を表示させる必要があります。これを実装してみたところ、モーダルで表示された画面の左上に表示されるはずのOS提供の閉じるボタンが出ず、Watchではモーダルにモーダルを重ねることが想定されていないようでした。

これは困ったぞとなり、選択画面をリストビューにすることも考えましたが、リストビュー(hierarchical interface) x ページング(page-based interface)という遷移になり、これはApple Watch Human Interface Guidelines上でも推奨されていないスタイルになります。 タイマーのモーダルをやめることも選択肢にありましたが、開始/停止/リセットをレシピ詳細に入れてしまうのにもムリがありました。

そこで、いっそのことレシピ選択画面をなくそうという意見が挙がりました。

正直、最初はアプリとしてそんなミニマルな機能を提供するだけでいいのか、見た目上も素っ気なさすぎるのではないかと考えました。 でもチームで議論する中で、この考えは自分の作り手側の勝手な視点なのだと気付かされました。見た目やアプリとしてどうではなく、ユーザーストーリーに立ち返り、ユーザーが料理をするときに本当に必要な機能は何なのかを考え直しました。

シンプルであることを恐れない

チームで議論を重ねた上で、レシピ選択はiPhoneアプリ側に任せ、レシピ選択画面をなくすことに決定し実装しました。

結果的にとてもシンプルなアプリになりました。Watchは料理中のレシピの確認の役割のみを担う。Watchアプリとしては必要十分なものになりました。

その後の開発でタイマー機能の実装上、タイマーをどの画面でどう制御するかという課題にもぶち当たりましたが、その時もベースがシンプルであるがゆえに、シンプルな方法で解決することができました。このタイマー実装の部分は割愛しますが、Watchの制約の中でレシピ選択画面があるだけで、より複雑でユーザーを迷わせる部分が間違いなく出ていたと思います。

「シンプルであることを恐れない」

これは開発中にある方に言われた言葉ですが、今でも心に響いている言葉です。

まとめ

開発を通して学んだことは、以下3点です。

  • デバイスの役割を明確にする
  • 極力シンプルにする
  • 困ったときはユーザー視点にたつ

Watchの開発を通して、デバイスの特性を見極めながら、何をやらせて何をやらせないかを明確にすることが、とても重要でした。「あったら便利そう」は使われない可能性が高い機能。逆にその機能がユーザービリティを落とすことになりかねませんでした。特にWatchの場合は、デバイスや開発上の制約が多いので、極力シンプルにして、コア機能にしぼっていく必要があります。

そして、そんな制約の中、開発をしていくと、迷ったりチーム内で議論する場面が多く発生します。そんな時はいつも、企画当初に設定したユーザー視点に立って、その人が本当にやりたいことは何か、本当にその人が求めている機能なのかという軸で議論をすると、自ずと1つの方向へ集約してより良いプロダクトになっていくことを実感しました。

とても当たり前でシンプルなことですが、開発中はデザインや実装の都合、あるいは機能やサービスとしての事情など、どうしても作り手側の視点が混じり、軸がぶれてしまいます。

"誰のためのプロダクトなのか"

いつもその本質を問いながら開発を進めていくことで、よりよいプロダクトを開発していきましょう。

Dokumi (日本語)

$
0
0

(English version here)

技術部モバイル基盤グループのヴァンサン(@vincentisambart)です。今日は最近作ったツール「Dokumi」の話をしようと思います。

紹介

他部署のエンジニアの仕事をもっと楽にすることが、技術部の重要な目的の1つです。その中で、Dokumiはモバイル開発者のコードレビューの負荷を減らすためのツールです。

なぜ「毒味」という名前にしたかと言うと、人間がレビューする前に、コードに毒(バグ、不自然なコードなど)が入っているかどうか毒味するツールだからです。別の言葉で言うと、少し進化したCI用のlintツールですね。pull requestが出る度に、Jenkinsがそのpull requestにDokumiをかけます。現在はDokumiはiOSアプリだけに対応してしていますが、今後はAndroidアプリへの対応も考えています。

現時点でDokumiは以下のことをやっています。

  • 静的解析(Xcodeの「Analyze」機能)をかけます。 f:id:vincentisambart:20150603121414p:plain
  • 自動テストを走らせます。 f:id:vincentisambart:20150603121409p:plain
  • コミットされたけどXcodeのバージョンデータしか変わっていないXIB/Storyboardファイルを指摘します。 f:id:vincentisambart:20150603121400p:plain
  • pull requestにmilestoneが指定されていないとそれを指摘します。 f:id:vincentisambart:20150603121419p:plain
  • 静的解析や自動テストを走らせるためにアプリをビルドするので、ビルド中に出ている警告やエラーも同時に拾います。

以上のキャプチャーで見られるように、Dokumiは、GitHub Enterpriseと連動して、指摘をpull requestのコメントとして投稿します。指摘の対象がこのpull requestで変わる行であれば、行にコメントを付けます。対象が変わっていない行であれば、pull requestと関係ありそうなものは普通のコメントにまとめて投稿します。pull requestと関係なさそうなもの(例えばこのpull requestで変わっていないファイルに出る警告)は無視されます。

メリット

XcodeのTestやAnalyzeは、開発者が自分自身でかけることができますが、時間かかるし、かけるのを忘れる時もあります。また、 gitに変更を入れるのを忘れることもあります。なのでDokumiはすべてのpull requestに強制的にかけます。因みにXcodeの静的解析のお陰で人間がレビューしたコードでも不具合や変なコードを見つけることができたのでやってみることをおすすめします。

また、直接GitHub Enterpriseのpull requestにコメントを付けるメリットも大きいと思います。pull requestの度に自動テストを走らせるのは、以前から行われていました。しかし、失敗したテストを知るには、Jenkinsのログのxcodebuildの出力を見るしかなく、簡単ではありませんでした。

実は元々Dokumiの結果はウェブページとして整えようと思っていたけれど、良いデザインが思いつかなかったし、わざわざデザイナーに頼んでもな…と思っていたら、単に、普通の人間のレビューと同じようにすればいいだけだと気づきました。

困ったこと

GitHubのAPIを使って実装しようとした時に、特定の行にコメントを付けるには現時点(2015年6月頭)のAPIでは不足しているところがあると気づきました。

特定の行にコメントを付けるエンドポイントの説明を見ると、指定する必要あるのは「The line index in the diff to comment on.」です。ファイルの行番号ではなくdiffの中の行番号ですね。diffから計算するしかない…そしてこれだけだったらまだしも、そもそもAPIでdiffを取得する方法がない…

GitHubはpull requestのURLに.diffや.patchを付けるとdiffを取得できるのですが、そのdiffはpull requestのコメントに使われているdiffと相違があります:ファイル名変更の対応が違います。.diff/.patchで取得できるdiffはファイル名変更が:

  • 旧ファイル名の削除
  • 新ファイル名の追加

になります。pull requestのコメントで使われているdiffはファイル名変更が:

  • ファイル名変更自体
  • 変わっている行だけの追加・削除

になります。gitをコマンドラインで使うと、git diff--find-renames (略して-M) を付けるかどうかの違いと同じです。

GitHubのAPIで取得できるdiffに find-renamesを指定できないならどうすればいいのでしょうか。いまDokumiはレポジトリを手元でcloneして、Ruggedというライブラリを使ってdiffを計算します。もし計算のやり方がGitHubのと違っていれば、ズレが出てくる可能性がありますが、それでも大きな問題にならないでしょう。

pull requestでnew-featuremasterにマージしたいと仮定すると、僕の理解が合っていれば、pull requestのページに出ているdiffをコマンドラインで見たいときは:

$ git diff --find-renames `git merge-base new-feature master`..new-feature

質問コーナー

どの言語で実装されていますか? Rubyですね。僕自身も昔からRubyを使っていますし、社内にはRubyエンジニアが多いからです。

どうやって警告やエラーを拾っているのですか?主に正規表現を使ってxcodebuildの出力から拾っているだけです。静的解析の指摘はxcodebuild analyzeがビルドディレクトリに生成したplistファイルを見ています。

オープンソース化の予定はありますか?まだ未定です。どの環境でも動くようにするにはコストが高いと思いますし、他人が使えないものをオープンソースする意味もあまり感じないですね。でも実装に関する質問あればお答えします。ソースコードがGitHubにて公開されました


Dokumi (English)

$
0
0

(日本語版はこちらへ)

Let's talk about Dokumi, a tool I have been recently working on.

Introduction

I am part of Cookpad's "Technical Department". One of the department's goals is to make life easier for other engineers. I am in charge of iOS, so I wrote Dokumi to decrease the time that mobile engineers spend on code reviews.

In Japanese, Dokumi means "food tasting", or more literally "poison tasting" (for example, tasting food before a king to make sure the food is not poisoned). I named this tool "Dokumi" because it "tastes" the code to check if any "poison" (bug, strange code) is in it. In other words, it's an advanced lint tool intended to be run by your CI server. Every time a pull request is created or updated, Jenkins runs Dokumi on it. Currently, Dokumi only supports iOS apps, but we are thinking about adding Android support.

Dokumi currently does the following:

  • Runs a static analysis of the code (available in Xcode as "Analyze"). f:id:vincentisambart:20150603121414p:plain
  • Runs your automatic tests. f:id:vincentisambart:20150603121409p:plain
  • Points out the XIB/Storyboard that are changed by the pull request but for which only the Xcode version information changed. f:id:vincentisambart:20150603121400p:plain
  • Points out if the milestone of the pull request has not been set yet. f:id:vincentisambart:20150603121419p:plain
  • To run the static analysis or automatic tests, Dokumi has to build the application, so it also picks up the warnings and errors that occurred during the compilation.

As you can see in the screenshots above, Dokumi posts the issues found as comments to the GitHub Enterprise request. Issues on lines modified by the pull request are added as comments to those specific lines. Issues that are not on a line modified by the pull request but seem to be related are regrouped in one normal comment. Issues that do not seem to be related to the pull request (for example warnings found on a file not modified by the pull request) are ignored.

Merits

Any developer can run Test and Analyze in Xcode by himself. However, it takes time and it is easy to forget to run them. Also it's easy to forget to add some change to git. That is why Dokumi is run on all pull requests. By the way, thanks to static analysis, we did find problems in code that had already be reviewed by humans. So if you never used it, it might be worth a try.

I also think adding remarks directly to GitHub Enterprise's pull requests saves a non-negligible amount of time. Running automatic tests on each pull request was something we had been doing for a while. However, when tests were failing, you had to spend time finding the failure reason in the xcodebuild output inside Jenkins's log.

In fact, at first, I was thinking about putting all the errors in a nice web page, but I could not think of a design I liked, and I did not really want to bother a designer for that. That is where I realized that I could simply do it the same way humans review code.

Obstacles

When I tried implementing that idea using GitHub's API, I realized the current (as of June 2015) version of API makes it difficult to comment on a specific line.

Looking at the documentation for the endpoint to comment on a specific line, you have to pass it "The line index in the diff to comment on.". You first need to map file lines to diff lines. And to do that, first you have to solve an other problem: there is no way to get the diff you need via the GitHub API.

If you add .diff or .patch to a GitHub pull request URL, you can download its diff. However, that diff, and the one used for the pull request's comments, handle file renames differently. In the diff you get by adding .diff/.patch to the pull request URL, a rename becomes:

  • old file name deletion
  • new file name addition

In diffs used for pull request comments, a rename is:

  • file rename itself
  • addition/deletion of only the lines that changed

When using git from the command line, it is the same difference as passing --find-renames (shortened to -M) or not to git diff.

The GitHub API does not let you get the diff it uses for pull request comments, so what can you do? Dokumi currently clones the repository locally, and using the Rugged library, it generates its own version of the diff for the pull request. Of course if the way the diff is generated is different from the way GitHub does it, the line the comment ends up on might be incorrect, but even it this happens it should not be a big problem.

If a pull request is to merge new-feature into master, the way to display its diff from the command line is the following:

$ git diff --find-renames `git merge-base new-feature master`..new-feature

Q&A

In what language was Dokumi implemented in? Ruby. I have been programing in Ruby for a while, and at Cookpad we have many Ruby engineers, making it a good fit.

How do you detect warnings and errors? Mainly using regular expressions on the output of xcodebuild. For static analysis, the plist files generated by Xcode Analyze are also used.

Do you have any plan to open-source it?Not decided yet. It would be costly to make it work on most environments, and I do not feel it is really useful to open-source something that most people could not use easily. But I would be happy to answer any question about its inner workings. The source code has been made public on GitHub.

チーム開発の進め方

$
0
0

f:id:Yoshiori:20150604175344p:plain

こんにちは!クックパッド編集室メディア開発グループ長の @yoshioriです。

今回はウチのチームの開発の進め方や見積もりの仕方を説明しようと思います。

実はコレ系の話は 5 年前にもデブサミで発表したのですがこの時はリリースまで 1 年とかのレベルのプロジェクトの進め方の話でした。今回は 1,2 ヶ月でリリースまで持っていく開発の進め方を説明します。

動画サービス部分を microservices 化するときに実際に行った事を元に説明します。開発者は 3 人で 1.5 ヶ月位の開発です。

何故このようなことを行うのか

誰だって楽しく仕事がしたいし、なるべく不安などは無い方が良いはずです。 例えば自分がやっている作業がどうなったら終わりなのかわかっていなければ不安でしょうし、いつまでに作ればいいのかわかっていなければ不安でしょう。

そういった不安をなるべく無くすためにうちのチームでは 見える化コミュニケーションを重視し、メンバー全員で楽しく開発することを目標にしています。

まずは作らなきゃいけないものの洗い出し

いわゆるタスク出しってやつですね。正直この作業はあんまり面白いものではないです。特に開発初期などはエンジニアはまっさらな状態からコードをドンドン書いていけて凄く楽しいのでいきなり手を付けたくなります。ですがチーム開発でそれをやってしまうと比較的早く破綻しますし、その時にはメンバーそれぞれのモチベーションにも差が出てきたりして軌道修正をするのが難しくなります。なのでメンバーの意識も高い初期になるべく楽しく行うのが大事だと思います。

まずは全員で時間を作りやらなきゃいけないタスクの洗い出しをします。 1,2 ヶ月で完成させるようなものはアジャイル開発でよく使われているユーザーストーリーで分けると粒度が少し大きくなってしまうため機能単位でタスク出しをしています。

どの程度の粒度で出すかというと

  • DB 設計
  • Video テーブル移行スクリプト
  • Video 取得内部 API

のような感じで出しています。 実際に全員で「もう出すもの無いよね」というところまで出し切ったらそれを一個づつ付箋に書いていきます。

出したタスクの重さを決める

f:id:Yoshiori:20150604175437p:plain

次に出したタスクの重さを決めていきます。ユーザーストーリーで分けているときはストーリーポイントとか言われているものですね。コレはなるべく抽象化したポイントであらわし、かかる時間などの具体的な数値で出さないことが大事です。以下説明のためにこの数値を SP (ストーリーポイント) と書きます。

実際の数値の出し方ですがプランニングポーカーと呼ばれている手法を使って行います。簡単にうちのチームで行っているプランニングポーカーの説明をします。

  1. 「1,2,3,5,8,13,∞,?」 どこかで見た数列 + 無限と ? のカードを人数分用意します。
  2. すべてのタスクの中からちょうど真ん中くらいの難易度だろうと思うのもをみんなで選びその SP を 5 にします。
  3. まだ SP を決めていないタスクを選びます。
  4. 簡単にそのタスクの機能の説明をします。
  5. それぞれがカードの中から SP を選び同時に出します。
    • 最初に出したタスクが 5 だというのを参考に考えます。
    • 専門分野など情報が不足しすぎていて自分には見積もりできない時は ? を出します
      • これはその後説明を求めます。
    • タスクが大きすぎて 13 を大幅に超えると思ったら ∞ カードを出します
      • タスクをさらに分割します
  6. 数値が合わなかったら一番大きい数字と小さい数字を出した人間がそれぞれの根拠を説明します。
  7. 6 で出た意見を参考にもう一度全員で SP を出します。
  8. 全員の数字が揃うまで 6,7 を繰り返します。
  9. すべてのタスクに SP が振られるまで 3 〜 8 を繰り返します。

これですべてのタスクに SP が振り分けられます。 プランニングポーカーは面倒臭い見積もりをゲーム感覚で楽しく行えるのでオススメです。

イテレーション期間

次にイテレーション期間を決めますが、もうコレは色々経験してきた結果

水曜日始まりの 1 週間を 1 イテレーションとする

が一番しっくり来るので僕の独断でそうしています。簡単に理由を説明すると

  • 1 週間より短いと細かすぎて朝会などと区別があやふやになる
  • 1,2ヶ月の開発に 2 週間だと 2 〜 4 イテレーションしか回せずうまくリズムにならない
  • 月曜日で振り返りを行うと先週の問題点があんまり気にならなくなってる
  • 金曜日に振り返りを行うと月曜日には改善しようと思ってたことを忘れる

という理由からそうしています。

スケジュールぎめ

1 週間というタイムボックスが決まったので、それぞれのイテレーション期間でどのタスクを行うかを決めていきます。 最初はだいたいザックリ「今週はこのくらい出来るんじゃね?」という量を割り当てていき、次の週などもそれを元に割り振っていきます。この時に大事なのはタスクの順番を意識することです。例えば最初に例に上げた「DB 設計」を終わらせないと「Video テーブル移行スクリプト」は実装できません。こういった順番を考慮しつつ各イテレーションに作業を割り振って行きます。(後で説明しますがココで上げたスケジュールは大体破綻しますw)

実際に開発を回していく

f:id:Yoshiori:20150604175516p:plain

スケジュールも決まったので実際に開発にかかります。うちのチームでは基本的に個々の作業管理はツールとしてのかんばんで行っていますが少しアレンジしています。

  1. タスク自体はイテレーションを表した大きめの紙に貼られている
  2. 自分が実際に作業しているものには自分の名前の書かれたマグネットを置く
  3. 終わったものには猫のシールを貼る
  4. 予定になかったタスクが発生したら赤い付箋に書き、そこにも SP をふる
    • 突然降ってきた仕事
    • 仕様考慮漏れ
    • バグ修正

タスクが終わったものはシールを貼って表していますがコレはその作業が完全に完了するまで行わないことを徹底しています。(アジャイルで「完全 Done 」と呼ばれているものですね)

振り返りを行う

水曜日には振り返りを行います。まず計画で上げたタスクで完全に完了したものの SP をすべて合計して算出します。 コレがチームの 1 週間で行える作業量という指針になります。ついつい「この 8SP の作業、あとちょっとで終わるから」と言って合計数に含めたくなりますがやめましょう。

さて、だいたい最初のイテレーションが終わるとすでに最初の計画が破綻していると思います。 今回うちのチームも 1 イテレーションで 30SP 位の作業は出来るだろうと見込んでいたのですが、 20SP しか完了出来ませんでした。つまり 10SP の作業はあぶれ、今後のイテレーションに割り振られている 30SP をすべて 20SP に修正すると一気に 1.5 倍ほどのスケジュールになってしまいます。

ここで大事なのが振り返りです。

まずは我々は1イテレーションに 20SP しか消化できないという認識をします。 そこで何故 20SP しか消化出来なかったのかなどを話し合います。今回は 3 人なので KPT のような比較的きっちりした振り返りではなくスタンディングでお互いの意見を言い合う形で行いました。 最初のイテレーションはデータ移行などのタスクに引きづられ、大きめなタスク( 13SP )が実際にデータを入れてみたら修正する箇所が出てきたりして完全 Done にならなかった事がわかりました。 幸いにもその修正はすぐに終わるだろうという事で今回はスケジュールの見直しはしないで行けそうだということになりました。

ここで厳密に今後のスケジュールを見直すことも可能ですが、いきなりここで厳密にやってもテンションが下がるだけなので行いませんでした。実際、本当に持ち直せなかったら次のイテレーションでスケジュールの切り直しをするつもりでしたが、うまく持ち直したのでそのまま進みました。

イテレーションを回していく

上記のようにイテレーションを回しながら

  • 抜けているタスクはないか
  • タスクに割り振られている SP は妥当か
  • イテレーションに割り振られているタスク量は適切か
  • スケジュールを切り直す必要はないか

は話し合っていき、スケジュールをドンドン正確にしていきます。

チームのパフォーマンスチューニング

f:id:Yoshiori:20150604175552p:plain

このようにイテレーションを回していくとベロシティ(チームの平均消化 SP)がわかってきます。あくまで指針としてですがこのような数値を毎週出しておくとチームの健康状態を図れます。ベロシティが落ちていればなにか問題があるのでしょう、逆に上がっていれば何か改善する事があったのでしょう。

振り返りの時に「なんか今週仕事があんまり進まなかった気がするよね!なんでだろ?」と感覚的にいうよりも「何故今回はベロシティ下がったんだろう?」というほうが数字にも出てるのでチームメンバーも共感でき、一緒に改善案を考えれます。

パフォーマンスチューニングの基本「推測するな、計測せよ」です。

まとめ

チーム開発の進め方として主にタスク出しやスケジュールの切り方を説明してみました。僕はもともと「スケジュール立てるのとか苦手だし、誰か得意な人がやればいいんじゃね?」とか思っていましたが今では

タスク出しやスケジュール立てるのは才能ではなく技能なのでやり方覚えれば誰でも出来る

と感じています。実際にやっていることはプログラムを書くときと同じで大きい問題を小さく切り分けて順番に対処していっているだけです。そして計測してそこから逆算しているだけです。

やり方を覚えて身に付ければ誰にでも出来る事なので、まだ習得していない人や昔の僕の考えと同じようなことを考えている人は早めに習得しちゃうことをオススメします。

アジャイルな見積りと計画づくり ~価値あるソフトウェアを育てる概念と技法~

アジャイルな見積りと計画づくり ~価値あるソフトウェアを育てる概念と技法~

  • 作者: Mike Cohn,マイクコーン,安井力,角谷信太郎
  • 出版社/メーカー:毎日コミュニケーションズ
  • 発売日: 2009/01/29
  • メディア:単行本(ソフトカバー)
  • 購入: 74人 クリック: 764回
  • この商品を含むブログ (225件) を見る

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年の技術を見極めたいエンジニアを募集しています!

Viewing all 726 articles
Browse latest View live