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

心地よいアニメーションを求めて

$
0
0

こんにちは、買物情報事業部の三浦です。

日々アプリを使っていて、ふとしたところでさりげないアニメーションや気の利いた効果音があると心地よく感じますね。
UIKitには手軽にアニメーションを実装できるようにAPIが用意されています。少し工夫するだけで効果的な動きを作ることができます。

サンプルを見ながらみていきましょう。

Basic

まずはUIViewのクラスメソッドのシンプルなアニメーションです。
オブジェクトを下にアニメーションさせます。

UIView.animateWithDuration(
    0.5,
    delay: 0.0,
    options: nil,
    animations: { () -> Void in
        self.circle.center = CGPoint(x: 0, y: 100)
    }, completion: nil)

20151001140928

動きの加減をコントロールするイージングもUIViewAnimationOptionCurveとして指定することができます。
デフォルトでイージングのオプションがCurveEaseInOutが指定されています。

UIViewAnimationCurve - UIView Class Reference

これは始めと終わりを緩める動きになります。
時間と位置の関係をグラフにすると以下のようなイメージになります。

20150930011203

デフォルトで動きのニュアンスがつくように指定されていますね。

Linear

イージングにあえて緩急をつけずに等速運動をさせてみましょう。
optionsにCurveLinearを指定します。

UIView.animateWithDuration(
    0.5,
    delay: 0.0,
    options: .CurveLinear,
    animations: { () -> Void in
        self.circle.center = CGPoint(x: 0, y: 100)
    }, completion: nil)

2015093001071420150930011204

グラフはまっすぐな直線になります。
上と比べてどうでしょうか?少し動きが硬い印象になりますね。
もちろんアニメーションさせる値によっては緩急の必要のない場面もあるので、アニメーションさせる用途にあわせて使い分けましょう。

Spring

次にiOS7からUIViewのメソッドとして追加されたanimateWithDuration(_:delay:usingSpringWithDamping:initialSpringVelocity:options:animations:completion:)を使ってみましょう。
このメソッドは名前にもある通りばねのような動きを簡単に実現できます。
先ほどのアニメーションのメソッドと基本は変わりません。ポイントになるのはdumpingRatioです。

UIView.animateWithDuration(
    0.5,
    delay: 0.0,
    usingSpringWithDamping: 0.5,
    initialSpringVelocity: 0.0,
    options: nil,
    animations: { () -> Void in
        self.circle.center = CGPoint(x: 0, y: 100)
    }, completion: nil)

dampingRatioが1の場合には、振幅がなく最終値に向かいます。
1以下の場合、値が小さいほど振幅が大きくなります。
アニメーションの初速を変更したい場合はvelocityの値で変更することが可能です。

animateWithDuration(_:delay:usingSpringWithDamping:initialSpringVelocity:options:animations:completion:) - UIView Class Reference

20150930014307

動きに対する勢いが反映されるので、より抑揚のついた動きをこのメソッドひとつで実現することができますね。
ポップアップでviewが表示される時やボタンのフィードバックなど、ちょっとした振幅があるとユーザーとしては触れた(操作した)感覚を感じることができます。
ただあまりやり過ぎると過剰な演出になってしまうので注意して値を調整しましょう。

Loop

アニメーションのオプションにはRepeatやAutoreverseも用意されているので、動きをループさせることもできます。

UIView.animateWithDuration(
    0.5,
    delay: 0.0,
    options: .Repeat |.Autoreverse,
    animations: { () -> Void in
        self.circle.center = CGPoint(x: 0, y: 100)
    }, completion: nil)

20150930021043

アニメーションカーブはデフォルトのCurveEaseInOutになっているので、初速と終速が緩やかになるアニメーションが連続しています。
まるでゴムが伸び縮みしているような動きになりますね。
アニメーションカーブを変化させるとまた違った動きが表現されます。

複数の連続したアニメーション

ここからはCore Animationを使っていきます。
CALayerのサブクラスにCAReplicatorLayerがあります。
Sublayerを指定した個数分複製して表示するLayerです。
SublayerにはCAAnimationを組み合わせることができるので、短いコードでいろんなバリエーションの動きをつくることができます。

CAReplicatorLayer Class Reference

まずCAReplicatorLayerを使ってオブジェクトを複数置いてみましょう。

// CAReplicatorLayerを生成、追加let replicatorLayer = CAReplicatorLayer()
replicatorLayer.frame = view.bounds
view.layer.addSublayer(replicatorLayer)

// sourceになるSublayerを生成、追加let circle = CALayer()
circle.bounds = CGRect(x: 0, y: 0, width: 10, height: 10)
circle.position = view.center
circle.position.x -=30
circle.backgroundColor = UIColor.blackColor().CGColor
circle.cornerRadius =5
replicatorLayer.addSublayer(circle)

replicatorLayer.instanceCount =4
replicatorLayer.instanceTransform = CATransform3DMakeTranslation(20, 0.0, 0.0)

CAReplicatorLayerを生成します。次にsourceとなるcircleのlayerを生成し、CAReplicatorLayerに追加します。
instanceCountでコピーしてできあがる数を指定し、instanceTransformでインスタンスの移動や回転などを渡します。これだけで以下のようなサークルを並べることができます。

20150930033906

次にアニメーションを追加します。

// 上下のアニメーションlet animation = CABasicAnimation(keyPath: "position.y")
animation.toValue = view.center.y +20
animation.duration =0.5
animation.autoreverses =true
animation.repeatCount =.infinity
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
circle.addAnimation(animation, forKey: "animation")

replicatorLayer.instanceDelay =0.1

サークルのレイヤーに上下するアニメーションを追加してあげます。
アニメーションはCAAnimationを使用していますが、値はUIViewのメソッドで指定していたものと基本は同じです。
あとはinstanceDelayでインスタンス間の遅延時間を指定します。
アニメーションさせている場合は、その指定した時間分ずれてアニメーションされるので個々の動きをバラすことができます。

CAAnimation Class Reference

20150930123244

先ほどのシンプルな上下のアニメーションも複数で動かすことでウェーブしながら浮遊しているように見えますね。
静かで心地よい動きなので、ロードのちょっとした合間に表示してもよさそうですね。

アニメーションと配置の変更

先ほどのものを少し変更するだけで、カスタムのインジケーターにすることもできます。

// サークルのスケールアニメーションlet scaleAnimation = CABasicAnimation(keyPath: "transform.scale")
scaleAnimation.toValue =0.8
scaleAnimation.duration =0.5
scaleAnimation.autoreverses =true
scaleAnimation.repeatCount =.infinity
scaleAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
circle.addAnimation(scaleAnimation, forKey: "scaleAnimation")

// replicatorLayerの回転アニメーションlet rotationAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotationAnimation.toValue =-2.0*M_PI
rotationAnimation.duration =6.0
rotationAnimation.repeatCount =.infinity
rotationAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
replicatorLayer.addAnimation(rotationAnimation, forKey: "rotationAnimation")

replicatorLayer.instanceCount =6
replicatorLayer.instanceDelay =0.1var angle = (2.0*M_PI)/Double(replicatorLayer.instanceCount);
replicatorLayer.instanceTransform = CATransform3DMakeRotation(CGFloat(angle), 0.0, 0.0, 1.0);

サークルの上下のアニメーションの代わりにスケールアニメーションに変更します。
これだけでも十分ですが、ローディングのニュアンスを強調するためにreplicatorLayer自体も回転させています。
あとはサークルの数を増やしtransformで円上に配置されるように変更すれば完成です。

20150930040704

これだけの変更だけで見た目が大きく変わります。
サークルの数を増やしたり、配置やアニメーションを変更することで様々なバリエーションを作りだしていくこと可能です。
CAReplicatorLayerにも今回紹介していないパラメータがあるので、ぜひいろいろと試してみてください。

まとめ

動きは値を調整することで、気持ちいいポイントや思いがけない動きに出会うことが多くあります。アプリは日々使うものだからこそ、ロード時やインタラクションのフィードバックなど、ちょっとしたユーザー体験の向上がアプリ全体の印象に大きく影響します。
みなさんもぜひ心地よいアニメーションを探求してみてください。


Railsエンジニアに役立つJupyter NotebookとiRuby

$
0
0

こんにちは。新規広告開発部所属エンジニアのレオ(@lchin)です。普段は広告配信関係のシステムを開発していますが、ここ最近「データサイエンス」に興味を持ち始めました。雑に説明すると、データサイエンスは統計学や機械学習などを用いて莫大のデータから価値を引っ張り出す分野です。今回のtechlifeは、そのデータサイエンスを学ぶ過程で知ったツールJupyter NotebookをRuby on Railsの開発に役に立つ使い方を紹介します。

Jupyter Notebookとは何か

Jupyter Notebook*1は科学者の「実験ノート」にインスパイアされたウェブ上のインタラクティブシェル環境です。ただのインタラクティブシェル環境ではなく、ソースコード、その実行結果、解説する文書、数式、画像などをまとめて1つの「ノートブック」ドキュメントとして扱えることが特徴です。

Jupyter NotebookはPythonで実装されていますが、ウェブのUIとノートブックのソースコードを実況する環境が分かれている設計のため、Python以外に様々なプログラミング言語に対応しています。ソースコードを実行するコンポーネントはkernelと呼ばれています。Rubyを実行するkernelはiRubyです。

Jupyterの試し方

Jupyterをインストールせずに試す方法としてTry Jupyterがあります。Rubyのサンプルノートブックはないのですが、iRubyがインストールされてる環境ですので、MenuからRubyを使ったノートブックを作成できます。

f:id:lchin:20151006185319p:plain

ちょっと試すのに、Try Ruby上に入ってるGemを調べてみます。

local_gems = Gem::Specification.sort_by{ |g| [g.name.downcase, g.version] }.group_by{ |g| g.name }
local_gems.keys.each {|gem_name| puts gem_name }

作成したノートブックはnbviewerを使って、実行結果を含めて共有できます。例えば、上記のRubyをTry Jupyterに実行してみた結果は以下のURLに閲覧できます。

JupyterとRails

さて、Jupyterを使ってRuby on Railsの開発に役立つには、Railsアプリに接続する必要があります。まず、JupyterとiRubyを手元の環境に用意します。

JupyterとiRubyをインストールする

Pythonのある環境であれば、pipなどでインストールできます。

pip install jupyter

Pythonのない環境の場合、Jupyter公式ドキュメントのインストール手順に従い、Anaconda(Jupyterを含むデータサイエンス向けPython版)をインストールします。

iRubyはRubyGemsでインストールできますが、環境に応じてlibmzq3など依存ライブラリのインストールも必要です。

gem install iruby

そして、Jupyterにirubyを登録して、jupyterを起動します。

ruby register
jupyter notebook

Railsと繋ぐ

iRuby kernelを使ったノートブックは少し特殊なirbみたいなものです。Railsと繋ぐには、rails consoleと同じようにRAILS_ROOTでbootして環境を読みこめばよいです。

FileUtils.cd rails_root
require'./config/boot'APP_PATH  = File.expand_path('config/application') unlessdefined?(APP_PATH)
requireAPP_PATHRails.application.require_environment!

Jupyterの応用

Jupyter Notebookは、ad-hocなソースコードを実行し、その実行結果を文章などと合わせてドキュメントにまとめてnbviewerに共有することがチームでRailsアプリを開発する様々な場面で役に立ちます。

  • IRB, pryなどの代わりのプロトタイピングと検証(結果が残るから再現しやすい)
  • 新機能をプルリクエストにまとめる時、その動作や実行結果を共有して簡単なでもにする
  • デバッグする時に、バグを再現した結果データや原因の仮説などを別のファイルにコピペせずに、1つの資料に残せるため、時間が掛かるバグ調査が少し楽になる
  • プログラムやアルゴリズムの最適化を行うときもデバッギング同様に、実験の繰り返しですので、仮説、コード、データ、が揃えてる残す必要のある作業である
  • また、RailsアプリのDBに入ってるデータを直接引っ張りだして、darustatsampleなどSciRubyの科学計算ライブラリを応用してデータ分析もできる

終わりに

科学者同様にエンジニアも科学的方法を応用することに価値があると思います。そこで、Jupyter Notebookは役に立つツールですし、iRubyと合わせるとRuby on Railsアプリの開発過程でも便利に使えます。是非試してみてください。

最後に、クックパッドでは私達と一緒に「科学する」エンジニアを積極的に募集していますので、是非ご応募ください!

*1:以前はiPython Notebookと呼ばれましたが、Pythonだけを実行する環境ではなくなったため、Jupyterに命名変更されました

インフラエンジニアの責任範囲と評価

$
0
0

f:id:mirakui:20151007180203j:plain

インフラストラクチャー部の成田です。2015年10月現在、インフラストラクチャー部には私を含め7人のインフラエンジニアが所属しており、このメンバーでクックパッド本体サービスをはじめ様々な新規事業やいくつかの子会社のサーバを運用しています。私自身もエンジニアではありますが部のマネージャも兼ねているため、立場上、社外の方からインフラエンジニアのマネジメントについて質問されることがよくあります。今回は、私自身の考え方とクックパッド社における事例を紹介したいと思います。

「インフラエンジニア」とは

「インフラエンジニア」という言葉の定義はあいまいで、しばしば議論の的になります。傍目からは明らかにインフラエンジニアであるように見えるにも関わらず「私はインフラエンジニアでは無い」と主張する人たちもいます。このような状況になっているのは、サーバ運用に関する業務分掌が会社ごとに異なるからであると私は考えています。

今回はそこが本題ではないので、その議論は避け、クックパッドで「インフラエンジニア」と呼ばれているような人たちの事をインフラエンジニアと呼ぶことにします。サービス開発はせず、サーバ構築と運用を専門に行う人たちです。他にも「サーバエンジニア」「運用エンジニア」など様々な表現がありますが、ここではクックパッド社内の呼び方に合わせて「インフラエンジニア」と統一することにします。 なお弊社では現在物理サーバを持っておらず、AWS をはじめとする IaaS でサーバ運用を行っているため、弊社のインフラエンジニアは物理作業を行いません。

責任範囲

弊社では、インフラエンジニアは次の五つを責任範囲として持つ、と決めています。部内ではこれらをまとめて「五本柱」などと呼んだりします。

  • パフォーマンス
  • 可用性
  • キャパシティ
  • バックアップ
  • セキュリティ

この五つの責任範囲は長年受け継いできたものですが、なかなか便利な分類なので今でも活用しています。 責任範囲を定義することは、業務分掌だけでなく、目標設定と評価に役立ちます。また、メンバーの仕事をこれらに分類してみると、どこに偏っているのか、何が足りないのかがわかりやすくなります。サーバの管理権限を持ち、なんでもできてしまう立場であるからこそ、自分たちにどういう責任があるのかをきちんと線引きしておくことは大切です。

以下、それぞれの責任範囲について説明します。

パフォーマンス

ユーザがサービスにアクセスした際に、適切な速度でサービスを利用できるようにする責任です。弊社のインフラエンジニアはサーバのパフォーマンスチューニングによってユーザ体験を改善する責任があるという意味です。 サービス開発自体はしませんが、サービスの変化によってレスポンス速度が悪化したときに、いち早くそれに気づき、改善まで責任を持って行います。改善においてはサービス開発エンジニアと連携をとりますが、インフラエンジニア自身がアプリケーションコードを書き換えてチューニングを行うこともよくあります。

余談ですが、弊部はサーバサイドのパフォーマンスチューニングを競技化した ISUCONにも積極的に参加しており、インフラエンジニア7人のうち4人が参加する3チームが ISUCON 5 予選を勝ち抜き、本選出場が決まっています。去年の ISUCON 4 では出題を担当しました。

可用性

サービスの稼働率を適切に保つ責任です。冗長化や監視技術などがこれに含まれます。可用性の追求には限りがなく、冗長化には冗長度に応じてコストがかかるので、どこまでやるのかを自分たちで決めなくてはなりません。従って、インフラエンジニアであっても事業ドメインの知識や収益構造を理解し、適切なサービスレベルを設計できる感覚が求められます。

キャパシティ

ユーザトラフィックを確実に処理できるキャパシティを確保する責任です。ピークトラフィックを予測し、適切な台数のサーバを用意するのはもちろんですが、台数を増やせばスケールするような設計をできる技術力が必要です。また、サーバ台数というのはコストに直結する問題なので、サーバのコストを最適化する責任があるという意味でもあります。従って、事業の収益構造を理解した上でサーバコストのかけ方を調整できるコスト感覚が求められます。

バックアップ

事業継続性を確保するために、事業にとって重要なデータをバックアップし、必要に応じてリストア可能な状態にしておく責任です。 バックアップ先ストレージとしては主に Amazon S3 や Glacier を活用していますが、最近では以下の記事のように、 AWS 以外のクラウドもバックアップに使い始めています。

複数のクラウドサービス間でオブジェクトストレージの中身を同期する - クックパッド開発者ブログ

セキュリティ

サービスのセキュリティを担保し、攻撃からサービスや情報を守る責任です。弊社のインフラエンジニアのうち1名はセキュリティエンジニアも兼ねていて、各サービスのセキュリティ診断を行ったり、サービス開発のスタッフにセキュアな設計のアドバイスを行ったりなど、セキュリティに関する様々な取り組みを担当しています。なお、サービスのセキュリティだけではなく社内の情報セキュリティについてもインフラストラクチャー部で担当しています。

インフラエンジニアとユーザファースト

クックパッドのスタッフが大切にしている行動規範として、「ユーザファースト」があります。 インフラエンジニアのユーザとは誰でしょうか。「ユーザ」という言葉を「インフラを提供する相手」として広く捉えると、次の2種類のユーザがいることが分かります。

  1. サービスのユーザ
  2. サービス開発をするエンジニア

インフラエンジニアはサーバを守るという役割上、どうしても保守的な考え方になりがちです。しかし、本当に守るべきなのはユーザ体験です。より良いユーザ体験を提供するために私達がするべきことは、決して権威的にならずに、開発エンジニアと密に連携して、サービスをスムーズに送り出すことです。

以下は2013年の資料ですが、開発と運用の連携について私が発表したものですので、詳しくはこちらをご覧いただければと思います。

Being healthy dev and ops in Cookpad // Speaker Deck

評価

他社のインフラ部門のマネージャから、インフラエンジニアの評価はどうしているのか、と聞かれることがあります。評価というのはつまり、成果の大きさなどを基準に、昇降給を決めることです。インフラエンジニアは事業に直接貢献するわけではない部門なので、評価が難しいという意見をよく聞きます。

私の考えでは、インフラエンジニアはウェブサービスに関わるエンジニアのなかでも、比較的評価がしやすい職種であると思っています。それは、成果を定量化しやすいからです。

先に述べたように、弊社のインフラエンジニアは「パフォーマンス」「キャパシティ」「可用性」「バックアップ」「セキュリティ」という五つの責任範囲を定めていて、メンバーが立てる目標は必ずこのうちのどれかに属しています。そして、それぞれの責任範囲には部として達成の定量的な達成指標を毎期掲げています。例えば、「パフォーマンス」であればサーバサイドのレスポンスタイム等、「可用性」であればサービスの稼働率や深刻な障害の数、といった指標を立てています。指標が定量的なので、メンバーがどれだけ部の責任範囲に貢献したかというのは明確にしやすいのです。

五つの責任範囲を守ることは確かに直接売上に貢献するわけではありませんが、ウェブサービスで事業を行っている以上、事業の基盤強化に貢献することに疑いはないでしょう。 インフラエンジニアの責任を明確にし、事業にどう貢献するのかを経営に理解してもらうのは、インフラ部門のマネージャとしての大切な役割です。

おわりに

今回は、弊社のインフラエンジニアの責任範囲と、その評価の事例を紹介しました。他社の事例もぜひ聞いてみたいので、各社のインフラマネージャの皆さんもぜひブログ記事に書いていただけると幸いです。

ディレクターの知見共有の仕組み

$
0
0

こんにちは、検索・編成部の五味と申します。

現在はディレクターとして、クックパッドiOS/Androidアプリを使いやすくするための施策を主に担当していますが、年初に異動して来るまでは、広告クリエイティブ制作というまったく異なる仕事をしていました。

サービス開発に関しては初心者状態だった私にとって、貴重な学習機会を与えてくれている、クックパッドのディレクター同士の情報共有の仕組みをご紹介します。

ディレクター同士の連携は難しい?

まずはじめに、現在クックパッドでは事業部制が採用されており、ディレクターは複数の部署に数名ずつ分かれて働いています。

ところが、ディレクターは同職間での連携が難しい職種でもあります。通常1つの施策を複数名で担当することはないので、お互いの業務進捗を報告しあってもいまひとつ理解しきれませんし、担当する施策の内容もバラバラであることが多いため、業務フローに問題があっても一緒に議論できることが少なかったりします。

部署をまたぐと尚更で、何もしなければ他部署のディレクターとの活発な連携は起こりづらい状況です。

「ディレクター知見共有会」

そこでクックパッドでは「ディレクター知見共有会」というミーティングが週に1回実施されています。サービス開発に関わるディレクター達が、部署を越えて集まる会合です。

参加者が持ち回りでプレゼンテーションを行う形式で、聴講はNG。雰囲気は和やかに行われていますが、出席したければ発表も必須で行う、という鉄則があります。 f:id:natsuki53:20151008084320p:plain

「知見共有」ができる仕掛け

この会ではディレクター同士の「知見共有」実現のため、以下の工夫が行われています。

- 発表は「5分以内」

時間制限が短く設定されることで、発表側は情報を重要なものに絞り、聞き手に伝わりやすいボリュームにまとめ、簡潔な構成で伝えることを意識するようになります。

- テーマは「自分のリリースしたプロダクトについて」が基本

どんな課題にどう仮説を立て、解決策として何をし、結果どうなったのか。身を以って学んだことを、自分の言葉で説明までできるようになるためです。実際にユーザーにリリースした画面のデモまでできると最良です。

- 2週に1度の頻度で発表する

「知見」を発表する当番は、2週に1度という高頻度で回ってきます。他のディレクターに共有できる知見を意識して作るようにするためです。プロダクトリリースがその頻度に間に合わないこともありますが、その場合は業務上のtipsや日常生活で見つけたユーザー課題などテーマを工夫して発表します。

f:id:natsuki53:20151007185335p:plain発表テーマの一例

「ディレクター知見共有会」のいいところ

この制度の主な長所は、以下の4点があると思います。

1. 他のディレクターの働き方がわかる

課題・仮説設定から実際リリースした画面まで、施策単位で事例を発表してもらえるため、各ディレクターが課題に対してどのような解決策を考え、何に留意しながらプロジェクトを進めているのか、進捗報告では見えない働き方やスキルを知ることができます。

2. 他部署の施策がわかる

部署をまたいだ発表会であるため、各部署が注力している施策を、実際に出ている画面やユーザーの反応とともに知ることができます。各施策がどのような目的下でリリースされているかがわかると、自部署の施策の参考にもなり、業務上発生する部署間の調整も円滑になります。

3. お互いの施策に意見し合うようになる

共有会では、各発表の後に必ず質疑応答の時間が設けられています。必然的に他のディレクターの施策について考え、意見があれば伝えることが習慣化します。

4. キャリアやスキルについて一緒に考えるようになる

社内のディレクターと部署をまたいでお互いの施策について共有・意見し合う機会を持つと、自ずと自分が習得すべきスキルや、ディレクターとしてのキャリアアップとは何かを考えるようになりました。 社内のディレクターの評価制度を整える試みも、この会を始点に始まっています。

進展

さらにこの会のメンバーを主体に、クックパッドのディレクターとしての社外への情報発信も、今後積極的に行っていこうとしています。他社との勉強会を実施するなど、徐々に取り組みが始まっているところです。

f:id:natsuki53:20151007185342p:plain

最後に

実は今回この「クックパッド開発者ブログ」に、ディレクターの私が投稿させていただいていることも、「ディレクターも社外へ情報発信して行こう!」という、「ディレクター知見共有会」の取り組みの一環です。

これから他のディレクター達も順次登場して参りますので、楽しみにしていてください!

マイクロサービス時代を乗り越えるために、Rack::VCRでらくらくアプリケーション間テスト

$
0
0

こんにちは、会員事業部の小室 (id:hogelog) です。気づけば弊社に入社してから2年と2ヶ月が経っていました。

今回はその2年2ヶ月で初めて会社プロダクトを rails newしたRailsアプリケーションと、そのアプリケーションで利用したRack::VCR (https://github.com/miyagawa/rack-vcr) について簡単に解説します。

新規アプリケーションの構成

今回私が新規に作成したRailsアプリケーションは仮にここではomoikane(仮)と呼ぶことにします。omoikaneはリクエストがあると社内の汎用APIサーバにアクセスし、APIサーバから取得した情報を元にレスポンスを返すアプリケーションです。omoikaneの実装・構成そのものはさほど難しくなかったのですが、一つ問題点がありました。

このアプリケーションはAPIのレスポンスの仕様が破壊的に変更された場合に正しく動作しなくなってしまいます。そのような変更を入れてしまわないためにはいくつかの選択肢が考えられます。

  • 気をつける
  • 関係するアプリのコードをよく調べて問題ないことを確認する
  • 関係各位*1に確認する
  • テストコードを書いてCIでAPIの破壊が無いかチェックし続ける

気をつけるのは大変なので、もちろんテストコードでチェックしておきたいものです。しかしそれには

  • omoikane側ではAPIのレスポンスをモックし、APIモックレスポンスを元にしたレスポンスが正しいかテストし
  • API側ではomoikane側のリクエストを模したリクエストに対して、omoikane側が必要とするレスポンスとなっているかテストする

必要があります。

汎用APIとomoikaneが別のRailsアプリではなく、モノリシックRailsアプリの別エンドポイントなどであったならもっと簡単なテストで済んだでしょう。マイクロサービスのためにはしょうがない、がんばって書こうと言われたら書けるかもしれませんが、がんばるのは疲れます。疲れたくありません。

そこで現れるのがちょうど社内で @KazuCocoa@adorechic@miyagawaの議論から生まれたRack::VCRです。

Rack::VCR

Rack::VCRとはRailsやSinatraなどのRackアプリケーションに導入することで、アプリケーション へのリクエストとそのレスポンスをVCRカセット形式で出力・またはVCRカセットのデータを元にモックサーバとして動作させることができるRackミドルウェアです。

あるAPIへのリクエストとレスポンスを一度実行して記録することでテストデータを作成するのが通常のVCRであるのに対し、APIを提供する側があらかじめテストデータを生成するという考え方です。

以下に今回のコード例とともにその役割を解説します。ここで例示するコードは https://github.com/hogelog/rack-vcr-sampleにまとめてありますので、詳しく知りたい場合はそちらをご確認ください。

リクエストの記録

Rack::VCRの基本機能はリクエストのVCRカセットへの記録です。

例では api/というAPIアプリのspecでRack::VCRを利用してVCRカセットを記録します。

ifRails.env.test?
  Rails.configuration.middleware.insert(0, Rack::VCR)
end

api/config/initializers/rack_vcr.rb

テスト実行時のみRackミドルウェアの先頭にRack::VCRを入れておきます。

RSpec.configure do |config|
  config.around(:each, type: :request) do |example|
    host! "api.example.com"

    vcr_cassette = example.metadata[:vcr]
    if vcr_cassette
      VCR.use_cassette(vcr_cassette, record: :all) do
        example.run
      endelse
      example.run
    endend

  ...
end

api/spec/spec_helper.rb

vcr: "cassette_name"のようなメタデータがついたspecでのみVCRカセットを記録するように設定しておくと、以下のように自然な形でVCRカセットを生成するspecを書くことができます。

RSpec.describe "Books", type: :requestdo
  ...

  describe "books#index", vcr: "books_index"do
    it "returns books"do
      get "/books"
      expect(response).to have_http_status(200)
      data = JSON.parse(response.body)
      expect(data.size).to eq(2)
      expect(data.map{|book| book["title"] }).to eq(%w(K&R Camel))
    endend

  ...

api/spec/requests/books_spec.rb

https://github.com/hogelog/rack-vcr-sampleは例示のために api/spec/fixtures/cassettes以下のyamlファイル(VCRカセット)をリポジトリに追加していますが、実際に運用する場合はspec実行のたびに変更が発生してしまうので .gitignore に入れるなどリポジトリに入れない運用が適切です。

リクエストのモック

これはRack::VCRの機能ではなくVCRの機能なのですが、Rack::VCRで記録したVCRカセットはテストに利用することができます。

例ではrails-app/というapiを利用するRailsアプリのテストで上述のRack::VCRで生成したVCRカセットを利用しています。

ここは特にRack::VCR特有の処理はないですが、こちらもvcr: "cassete_name"のような指定があるspecでのみVCRカセットを利用するように設定します。

(また、この例だと活用してませんがmatch_requests_onに渡す値を調整することで意図的に一部のクエリやパスを無視することでidの値が不定になるようなテストも記述することが出きます)

require"vcr"VCR.configure do |config|
  config.cassette_library_dir = "spec/fixtures/cassettes"
  config.hook_into :webmockendRSpec.configure do |config|
  config.around(:each) do |example|
    vcr_cassette = example.metadata[:vcr]
    if vcr_cassette
      match = example.metadata[:match] ? example.metadata[:match] : %i(host path query)
      VCR.use_cassette(vcr_cassette, record: :none, match_requests_on: match) do
        example.run
      endelse
      example.run
    endend 

  ...
end

rails-app/spec/spec_helper.rb

RSpec.describe BooksController, type: :controllerdo
  describe "#index", vcr: "books_index"do
    it "show books"do
      get :index
      expect(response).to have_http_status(200)
      expect(assigns(:books).map{|book| book["title"] }).to  eq(%w(K&R Camel))
    endend

  ...
end

rails-app/spec/controllers/books_controller_spec.rb

リクエストの再生

Rack::VCRは以下のように簡単なコードでVCRカセットを利用してレスポンスするモックサーバとして動作させることができます。このようなモックサーバーを使うことで、VCRカセットを直接扱えないRubyアプリケーション以外のアプリケーションでもVCRカセットを利用できます。

require"rack"require"rack/vcr"VCR.configure do |config|
  config.cassette_library_dir = File.join(File.dirname(__FILE__), "cassettes")
endclassMockAppdefself.call(env)
    [501, {}, ["Not Implemented"]]
  endend

app = Rack::Builder.new do
  use Rack::VCR, replay: true, cassette: "test"
  run MockAppend

run app

ただしこれではどのリクエストでも一つのカセットのレスポンスのみ返すためあまり柔軟な利用ができません。 よって以下のように"HTTP_X_VCR_CASSETTE"ヘッダが付与されたリクエストのみヘッダで与えられたカセットを使うようにします。

classCassetteLocatordefinitialize(app)
    @app = app
  enddefcall(env)
    cassette = env["HTTP_X_VCR_CASSETTE"]
    match = (env["HTTP_X_VCR_MATCH"] || "path query").split.map(&:to_sym)
    if cassette
      VCR.use_cassette(cassette, record: :none, match_requests_on: match) do@app.call(env)
      endelse@app.call
    endendend

...

app = Rack::Builder.new do
  use CassetteLocator
  use Rack::VCR, replay: true
  run MockAppend

run app

mock/config.ru

これを通常のRackアプリのように起動するだけで"HTTP_X_VCR_CASSETTE"ヘッダが付与されたリクエストのみヘッダで与えられたカセットを利用したモックレスポンスを返すようになります。

$ bundle exec rackup
[2015-10-09 02:07:08] INFO  WEBrick 1.3.1
[2015-10-09 02:07:08] INFO  ruby 2.2.0 (2014-12-25) [x86_64-darwin14]
[2015-10-09 02:07:08] INFO  WEBrick::HTTPServer#start: pid=76284 port=9292
$ curl -H 'X_VCR_CASSETTE: books_index' 'http://localhost:9292/books' | jq .
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   214  100   214    0     0  19113      0 --:--:-- --:--:-- --:--:-- 19454
[
  {
    "id": 1,
    "title": "K&R",
    "created_at": "2015-10-08T11:45:00.000Z",
    "updated_at": "2015-10-08T11:45:00.000Z"
  },
  {
    "id": 2,
    "title": "Camel",
    "created_at": "2015-10-08T11:45:00.000Z",
    "updated_at": "2015-10-08T11:45:00.000Z"
  }
]

おまけ: Androidアプリのテスト

上述のモックサーバを利用するとAndroidアプリなどVCRカセットを直接読めないアプリケーションのテストも可能になります。

モックサーバを利用して以下のようなインターフェースのAPIクライアントクラスのテストします。

publicclass ApiClient {
    public ApiClient(String url);

    public String getUrl();
    public Observable<List<Book>> getBooks();

    protected Request createRequest(String path);
}

https://github.com/hogelog/rack-vcr-sample/blob/master/android-app/app/src/main/java/org/hogel/androidapp/ApiClient.java

テスト時にはHTTP_X_VCR_CASSETTEヘッダを追加で付与するためモック用APIクライントを利用することにします。

publicclass MockApiClient extends ApiClient {
    privatefinal String cassette;

    public MockApiClient(String url, String cassette) {
        super(url);
        this.cassette = cassette;
    }

    @Overrideprotected Request createRequest(String path) {
        returnnew Request.Builder()
            .url(getUrl() + path)
            .get()
            .addHeader("X_VCR_CASSETTE", cassette)
            .build();
    }
}

android-app/app/src/test/java/org/hogel/androidapp/MockApiClient.java

細かいことは https://github.com/hogelog/rack-vcr-sample/blob/master/android-app/app/src/test/java/org/hogel/androidapp/ApiClientTest.java等を直接読んでもらうとして、ちょっと工夫すると以下の様にアノテーションでどのVCRカセットを利用するかテスト毎に指定することが出来ます。

@Test@VcrCassette("books_index")
publicvoid testBookIndex() {
    apiClient.getBooks().subscribe(new Action1<List<Book>>() {
        @Overridepublicvoid call(List<Book> books) {
            assertThat(books.size(), is(2));
            assertThat(books.get(0).getTitle(), is("K&R"));
            assertThat(books.get(1).getTitle(), is("Camel"));
        }
    }, new Action1<Throwable>() {
        @Overridepublicvoid call(Throwable throwable) {
            assertTrue(false);
        }
    });
}

android-app/app/src/test/java/org/hogel/androidapp/ApiClientTest.java

弊社での利用例

先に書いた社内汎用APIとomoikaneにおいてはこれらの機能のうち記録とモックのみ利用しています。

Rack::VCRを利用したテスト追加の流れとしては

  • omoikaneにspecを追加して実行
  • 当然対応するカセットが存在しないのでVCRに怒られます
$ ./bin/rspec 
FF

Failures:

  1) BooksController#index show books
     Failure/Error: get :index
     VCR::Errors::UnhandledHTTPRequestError:
       
       
       ================================================================================
       An HTTP request has been made that VCR does not know how to handle:
         GET http://api.example.com/books

...
  • 上記エラーを元に汎用APIにomoikaneが必要とするリクエストを投げるspecを追加、実行
  • 汎用APIのspecが生成したVCRカセットを使ってomoikaneのspecが成功することを確認、コミット

のような流れでおこなっています。

またCIでは

  • 汎用APIのCIが走るとomoikane用のVCRカセットが生成され、S3にアップロードされる
  • 汎用APIのCIが完了した時にomoikaneのCIがキックされる
  • omoikaneはテスト開始時にS3から最新のVCRカセットをダウンロードしてくる

のように常に最新の汎用APIとomoikaneの組み合わせが正しく動作することをテストし続け、汎用APIに破壊的な変更をコミットしてしまった場合にすぐにCIでそれを検知できるようにしています。

テスト追加の流れにある

  • 上記エラーを元に汎用APIにomoikaneが必要とするリクエストを投げるspecを追加、実行

という部分は工夫すればもっと自動化できそうな気がするのですが、現状ちょっとがんばって目で読んで手で書く作業をしています。改善の余地があります。

未来

Rack::VCR自体はかなり汎用的な機能を提供するライブラリであり、ここに示した利用方法がベストプラクティスとは限りません。

アプリケーション連携テストに関してはomoikaneへのRack::VCR導入にも大きく貢献してくれた @taiki45に火がついて、日々 @KazuCocoaなどと激論を交わし、Rack::VCRの改善なりそれ以外の何かなり、現状のRack::VCRでは足りないどこかを目指して頑張っているのでそのうちまた面白いものが生まれてくると思うので楽しみにお待ち下さい。

弊社のテストエンジニアはRack::VCRのような開発ツールの開発・運用からテスト・開発プロセスそのものの改善など、弊社のエンジニアリングの中核部分を担う重要な役職です。

すごい面白そうなので私自身も社内配置転換を願って参入しようかと思えるぐらいの役職なので、興味のある方はぜひ一度遊びに来てみてください。*2

クックパッド テストエンジニアの募集

それ以外の職種の方ももちろんたくさん募集してます

*1:多くの場合誰が関係各位なのかも自明ではない

*2:でもやっぱりそんな高度なテストエンジニアや高い技術力を持つ技術部、インフラ部などの力をつまみ食いしながらアプリ開発して価値創造していく現在の部署も面白いのでもうしばらくやっていこうと思います

「お料理アルバム」のアプリデザイン

$
0
0

こんにちは。投稿推進部の長野です。

先日クックパッドでは、お料理アルバムというアプリのAndroid版をリリースしました。 お料理アルバムは、毎日の料理写真をプライベートに記録・整理出来るアプリです。昨年iOS版をリリースし、その後バージョンアップを重ねて、この度Androidへと展開しました。

私はiOS版の開発時からデザイナーとして本プロジェクトに参加してきたので、このエントリーではデザインの観点からお料理アルバムの開発について、ご紹介したいと思います。

アプリが実現したい世界観

お料理アルバムが実現したい世界観は、毎日生まれている料理(レシピの卵)を気軽に記録に残していく受け皿をつくり、その中から特に美味しくできたものや誰かに伝えたいものがレシピとして発信される自然な流れを作ることです。 クックパッドのサービスは、ユーザーさんが日々投稿してくださるレシピによって成り立っているわけですが、日常生活のなかで「レシピを書く」という行為はなかなかハードルの高いものです。そこで、いきなり「レシピを書こう」ではなく、日々の料理とレシピの間をつなぐ、気軽なステップを用意しようというのが、このアプリのねらいです。

どうしたらこのアプリで記録したくなるのか

「毎日の料理を気軽に残す」という行為をどうしたらデザインできるのか、ユーザーの動機付けとなる価値を探すために、プロジェクト開始後しばらくはプロトタイピングと検証を繰り返しました。 以下に検証した仮説とプロトタイプをいくつかご紹介します。

仮説①

  • 作った料理写真をカテゴリ分けして簡単に記録できれば、自分のレパートリーが整理されるので記録のモチベーションになる?
f:id:yoshiko-nagano:20151009111427p:plain

ワイヤーフレーム画像をプロトタイプツールで動かし
画面遷移を体験できるようにしたプロトタイプ

仮説②

  • きれいな料理写真を撮ることができれば、記録のモチベーションになる?
f:id:yoshiko-nagano:20151009111441p:plain

お手本となる構図の料理写真がオーバーレイした状態で
写真を撮れるプロトタイプアプリ

仮説③

  • 日々の積み重ねを実感できる仕掛け(登録件数やカレンダービュー)があれば、記録のモチベーションになる?
f:id:yoshiko-nagano:20151009111514p:plain

写真を登録すると、件数や日数、カレンダービューなどで
記録の蓄積をみることができるプロトタイプアプリ

上記のようなプロトタイプを使ってユーザーテストやインタビューを行い、仮説検証を繰り返した結果、カテゴリ分けや写真をきれいに撮るなどの「機能」は、ユーザーがアプリを使う動機付けにはあまり直結しないということが見えてきました。それよりも、仮説③で検証した、記録したことによって味わえる「達成感」のようなエモーショナルな価値の方が、このアプリを使う動機付けとしては重要だという結論に達しました。

プロダクトの価値をデザインで強める

上記の価値検証を受けて、お料理アルバムではミッションステートメントを以下のように定めました。

「写真を記録するだけで、日々の料理に達成感を感じられる」

アプリのインタラクションデザインやビジュアルデザインは、すべてこのステートメントを軸に置いて設計しています。「簡単に記録できる」価値と「達成感を感じられる」価値をデザインによっていかに強められるかを考え、個々の要素をデザインしていきました。

写真が自動できれいに整理される感覚

お料理アルバムに写真をアップロードすると、撮影日ごとにタイムライン上に自動でレイアウトされ、表示されます。

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

タイムラインビュー

カメラロールの様に淡々と写真を並べるのではなく、複数写真をあえてギュッとまとめたレイアウトにし、日付ごとのカタマリ感を強調するデザインにしています。そうすることで、「自分は写真を選んだだけなのに、アプリがいい感じに整理してくれた!」という印象を強めるのがねらいです。

積み重ねを実感できる見え方

タイムライン表示とは別に、カレンダービューもあり、こちらでは記録した日付部分が写真で埋まっていくような見え方になっています。

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

カレンダービュー

プロトタイプではカレンダー上に丸くトリムした写真を並べていたのですが、空白を埋めていく達成感を演出するねらいから、びっしり写真を敷き詰めるレイアウトに変更しました。

楽しさ・気持ちよさの演出

お料理アルバムは基本的に一人でプライベートに使うものです。友達が使っているからとか、誰かに見てもらえるといったコミュニティ要素をモチベーションにすることができないため、記録が面倒になり負担に感じられてしまったら、すぐに離脱してしまいます。そのため、使っていて楽しい感覚、細部の気持ち良さの演出にはとても気を使いました。

例えば、タイムライン画面から写真をタップして詳細画面へ遷移する際のトランジションや、写真をアップロードした直後に画面上部でフライパンが揺れる演出などは、そのための工夫の一部です。

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

写真を登録すると該当の日付部分に
品数分のお皿が並び、フライパンが揺れる

一方で、華美な装飾は日常使いをするツールとしては逆に邪魔になることもあります。特に、お料理アルバムではあくまで料理写真が主役なので、それを邪魔しないことは大前提として考えました。その上で、極力シンプルさを保ちつつ、細かい部分のインタラクションで使う楽しさを感じられるようなデザインを目指しています。

ユーザーの行動に基づいたブラッシュアップ

リリース後に追加した機能の一つに、1ヶ月分の料理写真を月末にSNSシェアできる機能があります。

リリース後、カレンダー画面のキャプチャをSNSでシェアするユーザーが現れ、日々の料理の蓄積が可視化されて達成感を感じたときに、シェアの欲求が高まることがユーザーの行動から推測できました。 そこで、ひと月分の料理画像をSNSシェアに適した形に成形してシェアできる機能をデザインし、追加しました。

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

SNSシェア用画像

毎日料理をしていなくても、頑張った日の分だけの達成感が感じられる設計にしています。数日埋まっていなくても見栄えのする、カレンダーのようでカレンダーでないデザインです。さらに、シェアされた画像を見た人にアプリに興味を持ってもらえるよう、画像全体でアプリの世界観が伝わるデザインを心がけました。

この機能により、最近では月末・月初にたくさんのお料理アルバム画像がシェアされるようになっています。ハッシュタグ #お料理アルバムでinstagramなどを見てみると、様々な家庭のひと月の食卓が見えてきて、とても楽しいです。

まとめ

以上、ざっとですが、お料理アルバムの実現したい世界観から、それをどのようにアプリデザインに落とし込んでいったのかをご紹介しました。

「写真を記録するだけで、日々の料理の達成感を感じられる」

お料理アルバムの開発・デザインは常にこのステートメントを振り返り、照らし合わせながら進めています。日々の何気ない料理を気軽に記録するところを徹底的にサポートし、そこからクックパッドのサービス全体を使う楽しみを広げていけるようなアプリへと成長させていきたいと思います。

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

開発環境のパフォーマンスチューニング

$
0
0

こんにちは。技術部の吉川です。

クックパッドでは、ユーザーが快適にサービスを利用できるように本番環境でのパフォーマンスを向上させるための様々な工夫がなされています。

ところでパフォーマンスを気にするのは本番環境だけで良いのでしょうか? 開発環境に目を向けると、そこにもユーザーがいます。開発者です。開発環境のパフォーマンスが向上することで、開発者が快適にサービスを開発できるようになります。 今回はそういった開発環境でのパフォーマンス向上のための取り組みについてご紹介します。

※ なお先日 Ruby2.2化されましたが、今回紹介するものはそれ以前に実施されたため、Ruby2.2で同じ結果になるとは限りません。

状況

今回対象とするのはcookpad.comのアプリケーションです。 近年はMicroservices化を進めていますが、それでも本体のレシピサービスのアプリケーションは依然として非常に巨大なRailsアプリケーションです。

まずは施策無しの状態でのパフォーマンスがどの程度なのかを調べてみました。

rails consoleやrspecを手元で実行する際に、起動・初期化にかかる時間が20秒程度。 rails server(実際にはforemanを使って関連するミドルウェアも同時起動したりしています)で起動する時間が30秒程度でした。

ページ表示時間は、当然箇所によって異なりますが、トップページやレシピ詳細ページを起動後初回表示すると、JSなど含めて全てのロードが終わるまで30秒程度かかっていました。 なおこの数値は初回表示の場合で、二回目以降の表示ではキャッシュが効いて30秒だったものが12秒程度になります。

起動処理

まずは起動時間の20秒に着目しました。何に時間がかかっているのか? おおよそ8秒程度が Bundler.requireしている時間でした。また、Railsの初期化処理にも8秒程度かかっていました。 Railsの初期化処理をもっと細かく見ていくと、ほとんど(7秒程度)はbundleしているGemがhookしている初期化処理に費やされていました。 つまり75%程度がGemのロードに使われていました。

何かロードに時間がかかるGemでもあるのかというとそうではなく、単純に量が多かったのです。 約300ものGemがロードされていました。まさに塵が積もった状態です。

ただ起動についてはRailsが提供しているSpringを使えば、一度起動してしまえば次回以降は時間がかからなくなります。 Springを使うことで、ほとんど手をかけずに20秒かかっていた起動が3秒程度になりました。

とはいえクックパッドのように古くからあるアプリケーションの場合、そもそもRailsのアップデートをしていなければ導入できません。 こまめにアップデートに追随しておくことでこういった恩恵を得ることもできます。

クエリ発行数の削減

さて次はページ表示です。初回表示と二回目以降で大きく違うのはDBとSolrへのクエリがmemcachedなどにキャッシュされるためです。つまりIOが大きく寄与していることになります。 実際に調べてみると、キャッシュが全く無い場合、あるページではDBとSolrへのクエリがあわせて190本程度発行されていました。

N+1クエリが発生していたというわけではありません。純粋に量が多いのです。 レシピを表示するのにも、例えば関連キーワードでのレシピ検索結果など、様々な関連情報をロードするためです。

こういった情報は本番環境ではキャッシュを活用しています。誰かのアクセスでロードされればキャッシュされるため、ほぼ常にキャッシュされているような情報です。 しかし開発環境だとそもそもキャッシュにのる機会が少ないためヒット率が悪い。

これは性質上避けられないので初回時のキャッシュヒット率は諦め、開発環境であることを逆手にとって、もっと幅広くキャッシュすることにしました。 開発時に発行するクエリを全てキャッシュするのです。

開発時の動作確認、例えばviewの調整などのために何度も表示するような場合、期待するクエリ結果が変わることはほとんどありません。 更新系のクエリが発行された場合にだけキャッシュを破棄し、それ以外は、一度発行されたクエリは結果を常にキャッシュすることで大幅にIOを軽減させることができます。

DBへのクエリをキャッシュするために、クックパッドの開発環境では弊社森田が開発したreuse_query_resultsというGemが使われています。

またGemにはなっていませんが、Solr(Sunspot)クエリも同様にキャッシュされるようになっています。

もちろんデメリットもあります。手元のアプリケーションからの更新系クエリしか検知しないので、例えばDBスキーマを頻繁に変更しながら開発する場合はむしろ邪魔になります。 そこで起動時に環境変数でオンオフできるようになっています。

これらにより主なIOをほぼ無くすことができました。12秒だった表示時間は10秒を切るようになります。 それでも10秒・・・道は長そうです。

例えばGCを止める

IOが無いのにどこに時間がかかっているんだ?ということで細かく見ていきます。

表示にかかる時間のうち、60〜70%程度がコントローラーの処理が終わった後のテンプレートエンジンがviewを構築する時間に費やされていました。

テンプレートエンジンが遅いのか?と思いきや、簡単なペライチのviewだと300ms程度で終わるので、もう少し細かく見ていくことにしました。 するとどうやらpartial viewが多いほど遅くなっていることがわかりました。10秒かかるページでは、ループによって複数回renderされる数も含めると70以上ものpartial viewのrender処理が走っていたのです。

さらにその内訳を見ていくと、ほとんどは1ms以内からちょっと大きめでも10ms程度で終わっているものの、しかし一部のviewが300〜400msと非常に時間がかかっていることがわかりました。 これはただ重い処理をしている箇所があるだけか?と思いきや・・・計測する度に時間がかかる箇所が変動します。 さらに計測範囲を細かくして見ていくと、あるviewのrender処理が終わったあと次のviewのrender処理が始まるまでの間に時間がかかっていることがわかりました。 コードの実行の合間に実行されるものといえば・・・GCですね。そこでためしにGCを止めてみることにしました。

クックパッドの本番環境ではかなり前からGCを止めて運用しています。 止めるといっても完全に止めるのではなく、リクエスト処理開始時にGCを止め、レスポンス後にGCを再度有効にすることで、リクエスト処理中にGCが発生しないようにするOut-of-Band GCの手法です。 OoBGCにすることで、renderに300〜400msかかるようなviewがなくなりました。 これによって表示時間は10秒から7秒程度になりました。

棚卸しする

ここまで高速化するための施策について書いてきましたが、視点を変えて現状を維持するための施策もご紹介します。

機能はどんどん増えていくものですが、一方で既に必要ないものも存在するはずです。 必要ない処理やコードはどんどん整理していくことで、オーバーヘッドの増加を食い止めることもできるはずです。 その機能でしか使っていないGemがあってそれも消せるのであれば、その分Gemのロード時間にも寄与します。

しかし必要ないもの、というのは裏を返せば開発が終わっているということでもあり、開発者が既に別プロジェクトに移っていたり、 既にチームが無かったりして、どれが必要ないのかといったコンテキストが既にわからなくなっているケースもあります。 掃除と同じでこまめにやる工夫が必要です。

その工夫の1つとして、プロトタイピング開発に使われているChankoのunitの棚卸しを行っています。 unitとは、Chankoにおける機能をひとかたまりにした単位で、assetやテストも含み、unit単位で公開範囲の切り替えを行うものです。 全体公開せずにベータ版のまま開発終了してしまったが、コードがそのままになっているものや、期間限定公開で公開終了したのに残っているというものを棚卸しするのが目的です。

unitごとにコミットしたauthorを抽出し、1ヶ月に一度 GitHub上でissueが作られmentionするようにしています。 その際アプリケーション内でinvokeされているかどうかも合わせて出力しています。既に機能を落としたが残りっぱなしになっているものなどがわかりやすくなります。 また一時的に下げているだけだったり、期間限定機能だったりする場合は、設定でこのアラートを抑制することもできます。 例えば3ヶ月限定公開なら、3ヶ月後からアラートが飛ぶようにできます。

まとめ

開発環境高速化のための取り組みについてご紹介しました。いかがでしたでしょうか。

本番環境と比較すると、開発環境のパフォーマンスは実ユーザーが使うものではないため軽視されがちです。 しかし開発者の生産性が向上することで、結果的によりユーザーに価値が届けられるようになると考えています。

例えば起動に20秒かかっていれば、たとえテストが1秒で終わったとしても1分間に3回程度しか実行できませんが、3秒で済むなら20回テストが実行できます。 12秒が7秒になれば、1時間かかっていた動作確認は35分で済むようになります。これがエンジニアの人数分だけ効果が広がるのです。

ただ現状ではキャッシュに頼った高速化なので、初回アクセスが重い問題は解決していません。表示速度もまだ快適というレベルには至っていません。 今後も開発環境の高速化の取り組みは継続していこうと思っています。

次世代ビルドツールBazelを使ってAndroidアプリをビルドする

$
0
0

会員事業部所属エンジニアの山下(@tomorrowkey)です。
去年はモバイルファースト室でバリバリとAndroidアプリを書いていたのですが、今年に入ってサーバーサイドもやってみたいと思い、最近はRubyを書いている日々です。 Rubyはあまりやったことがなかったのですが、REPLがあってとても助かります。Java 9でREPLが使えるようになるらしいですが、Androidは縁遠い話ですね。
さて、今回は来年ビルドツールとして脚光を浴びそうなBazelをご紹介したいと思います。

Bazelとは何か

Bazel http://bazel.io/

BazelはGoogleが社内で使用していたビルドツールをオープンソース版として開発をしているものです。2015年3月にAlpha版が公開されました。
Alpha版ではクライアントアプリケーションやiOSアプリのビルドなどがサポートされていて、最近Beta版が公開され、Androidアプリのビルドも可能になりました。 Beta版のサポート動作環境はUbuntuとOSXだけですが、将来Windowsもサポートする計画もあります。
実装のロードマップはここで参照することができます。

http://bazel.io/roadmap.html

2016年5月以降のStable版ではAndroid Studioとの統合という計画もあります。現在AndroidアプリではGradleでのビルドが主流ですが、Bazelでのビルドもできるようになるので、いまのうちに味見しておきましょう。

Bazelの特徴

Bazelの特徴は以下のようなものがあります。

  • Bazelは複数のプラットフォームをサポートします。
    • 現在サポートしている動作環境はUbuntuとOSXです。
  • サーバーアプリケーションや、クライアントアプリケーション、iOSやAndroidのアプリなどさまざまなソフトウェアのビルドスクリプトを記述できます。
  • Bazel独自の言語(Pythonに似ています)を使用し、ビルドスクリプトの役割である、WORKSPACEファイルやBUILDファイルを記述します。

Gradleとの違い

ここまで聞くとAndroidアプリ開発者なら、Gradleとどう違うんだ?と思いますが、Bazelではこの問に対して以下のように回答しています。

Gradleより構造的に記述することができ、それによりビルドの並列化と同時に再現性も担保することが可能

また、BraintreeではJavaアプリケーションやライブラリのビルドをGradleからBazelに移行したというエントリを書いており、その中でBazelはGradleよりも高速であると記載しています。

https://www.braintreepayments.com/blog/migrating-from-gradle-to-bazel/

Androidアプリのビルドは非常に時間がかかるものなので、これだけでも興味が湧いてくるのではないでしょうか。

Bazelの構成

BazelでビルドするにはWORKSPACEとBUILDというファイルが必要になります。

WORKSPACEファイル

Bazelを使用したビルドはworkspaceという場所で行われます。 workspaceの場所はどこでも大丈夫ですが、必ずWORKSPACEファイルを配置する必要があります。
WORKSPACEファイルが配置された場所がworkspaceのトップディレクトリとなります。
WORKSPACEファイルはライブラリ外部参照の依存関係を記述します。もし、依存関係がない場合は空ファイルになります。
WORKSPACEはPythonに似た独自の言語を使用して記述します。
workspaceには複数のプロジェクトを含むことができます。

BUILDファイル

BUILDファイルにはソースコードの配置やプロジェクトの依存関係、ライブラリの依存などを記述します。

Androidアプリのビルド

概念の説明ではよくわからないと思うので、具体的なAndroidアプリプロジェクトを例にBazelを使ったビルドをやってみましょう。

Bazelのインストール

ここを読みながらインストールします。 Androidが開発できる環境であればスクリプトを実行するだけで終わると思います。

今回はv0.1.0を使用します。

$ bazel version
Build label: 0.1.0
Build target: bazel-out/local_darwin-fastbuild/bin/src/main/java/bazel-main_deploy.jar
Build time: Tue Sep 8 23:14:50 2015 (1441754090)
Build timestamp: 1441754090
Build timestamp as int: 1441754090

プロジェクト構成

サンプルプロジェクトはここにあります。 https://github.com/tomorrowkey/HelloBazel

プロジェクトの構成は以下のようになります。

.
├── BUILD
├── HelloBazel
│   ├── app
│   │   ├── build.gradle
│   │   ├── libs
│   │   ├── proguard-rules.pro
│   │   └── src
│   │       └── main
│   │           ├── AndroidManifest.xml
│   │           ├── java
│   │           │   └── jp
│   │           │       └── tomorrowkey
│   │           │           └── android
│   │           │               └── hellobazel
│   │           │                   └── MainActivity.java
│   │           └── res
│   │               ├── drawable
│   │               ├── layout
│   │               │   └── activity_main.xml
│   │               ├── mipmap-hdpi
│   │               │   └── ic_launcher.png
│   │               ├── mipmap-mdpi
│   │               │   └── ic_launcher.png
│   │               ├── mipmap-xhdpi
│   │               │   └── ic_launcher.png
│   │               ├── mipmap-xxhdpi
│   │               │   └── ic_launcher.png
│   │               ├── values
│   │               │   ├── dimens.xml
│   │               │   ├── strings.xml
│   │               │   └── styles.xml
│   │               └── values-w820dp
│   │                   └── dimens.xml
│   ├── build.gradle
│   ├── gradle
│   │   └── wrapper
│   │       ├── gradle-wrapper.jar
│   │       └── gradle-wrapper.properties
│   ├── gradle.properties
│   ├── gradlew
│   ├── gradlew.bat
│   ├── local.properties
│   └── settings.gradle
└── WORKSPACE

このプロジェクトはGradleとBazelを共存させるように作られています。
一般的なAndroidStudioのプロジェクト構成をベースに作っており、親ディレクトリにWORKSPACEファイルとBUILDファイルが存在するのが特徴です。

WORKSPACEの定義

前述の通り、WORKSPACEにはビルドに必要なフレームワークやライブラリの参照を定義します。
今回はAndroid SDKの定義と、Mavenで配布されているcommons-langを使用します。

android_sdk_repository(
    name="androidsdk",
    path="/usr/local/opt/android-sdk",
    api_level = 23,
    build_tools_version="23.0.1"
)

maven_jar(name = "commons-lang", artifact = "org.apache.commons:commons-lang3:3.4")

pathにAndroid SDKの配置パスを指定します。api_levelbuild_tools_versionはビルドするアプリによって適宜変更してください。
Mavenで公開されているライブラリの定義はmaven_jarルールを使用します。

BUILDファイルの作成

BUILDファイルにはソースコードやリソースがどこに配置されているか、どのライブラリを参照するのかを定義します。

android_binary (
    name = "android",
    srcs = glob(["HelloBazel/app/src/main/java/**/*.java"]),
    custom_package = "jp.tomorrowkey.android.hellobazel",
    manifest = "HelloBazel/app/src/main/AndroidManifest.xml",
    resource_files = glob(["HelloBazel/app/src/main/res/**"]),
    deps = ["//external:android/appcompat_v4", "//external:android/appcompat_v7", "commons-lang"],
)

java_library(
  name="commons-lang",
  exports = [
    "@commons-lang//jar",
  ],
)

android_binaryはAndroidアプリをビルドするためのキーワードで、Bazelではこれをルールと呼びます。
Android向けのルールは他にAndroidライブラリを定義するandroid_libraryがあります。

各属性に名前やファイル名を指定します。globはワイルドカードを使ってファイルの配列を取得する関数です。
nameはandroidとしていますが、これはビルドやアプリインストールする際に使われる名前なので、何でも構いません。
他の属性は名前からだいたいの想像がつくのではないでしょうか。

どのようなルールや属性などが使えるかはBuild Encyclopediaに定義されています。

java_libraryにcommons-langを定義し、android_binaryのdepsにnameを指定します。

BUILDファイルの場所について

BUILDファイルはworkspace直下ではなく、プロジェクトに配置するのが一般的だと思われるのですが、モジュール内に配置するとGradleが生成するbuildディレクトリと名前が衝突してしまうためworkspace直下にBUILDファイルを配置しています。
AndroidStudioサポートする際はこの問題をどうするのか気になりますね。

ビルド

WORKSPACEファイルがあるディレクトリで以下のコマンドを実行することでビルドできます。

bazel build :android

androidはさきほどBUILDファイルに指定した名前です。
:の前に何も書かれていませんが、BUILDファイルがカレントディレクトリ以外にある場合に、BUILDファイルが配置してあるディレクトリまでのパスを指定します。
例えば以下のようなディレクトリ構成だった場合

.
├── app
│   ├─── BUILD
│   ├─── hoge
│   └─── fuga
└── WORKSPACE

このようなコマンドでビルドできるようになります。

bazel build app:android

インストール

Bazelにはアプリをインストールするためのコマンドも用意されており、以下のコマンドでインストールすることができます。

bazel mobile-install :android

ただし、環境によってはインストールしたアプリがクラッシュすることがあり、adbコマンドでインストールすることによって問題を回避することができる場合があります。

adb install ./bazel-bin/android_signed.apk

今回解説しなかったこと

例えばQueryを使えば、ビルドコマンド実行時に参照するライブラリを差し替えることができたり、Macroを使えばBUILDファイルで使える関数を増やすことなどができます。

今回説明したもの以外にもいくつかのルールや関数が公開されており、Build Encyclopediaに使用できるルールや関数が一覧できます。一部はv0.1.0には含まれておらず、次バージョンのv0.1.1に含まれるものもあるので注意してください。

おわりに

Android開発ではAndroid Studio移行と同時にGradleが使われてきましたが、Gradleは遅いという意見を多く聞きます。Bazelを使うことによって少しでもビルド時間が短くなればよいですね。Stable版がリリースされるまで半年ほどありますが、すこしずつ触って慣れておきましょう。

クックパッドでは最新のオープンソースプロジェクトやツールに敏感なモバイルアプリエンジニアを募集しています。


品質の向上に対する取り組み

$
0
0

こんにちは。ユーザーファースト推進室ディレクターの大黒です。
私が所属しているユーザーファースト推進室では、「クックパッドに訪れた全てのユーザーが、期待する以上の品質に常に触れている状態にする」というミッションを持っています。今回はその中の取り組みの一つである「気になる!報告」という仕組みをご紹介します。

「 気になる!報告」とは

スタッフが普段、何気なくクックパッドを使っている中で、気になったことを簡単に報告することができる仕組みです。休日や外出先などでは、気になったことを後で担当部署にフィードバックしようと思っていても、ついつい忘れてしまいます。そこでサイト内に「気になる!報告」のリンクを設置し、いつでもどこでも報告できるようにしています。

f:id:kotsuru0812:20151014193717p:plain

スタッフアカウント*1でログインすると、クックパッドのフッターエリアにスタッフにしか見えないリンクがあり、どのページにいてもすぐに報告をすることができます。

f:id:kotsuru0812:20151014193847p:plain:w500

報告画面に遷移すると、閲覧していたページのURL、その時のUser agent が自動で入力されます。これはどいう環境でスタッフが「気になる!報告」を送ったのかが分かるため、不具合や表示崩れの場合に原因が特定しやすく、すぐに対応できるからです。

報告内容は全てユーザーファースト推進室に集約され、各担当部署へフィードバックされます。

この仕組みは、2012年11月にデザインの不具合報告を簡単にできる仕組みとして、主にデザインに携わっているスタッフが使っていました。2014年5月に報告内容の範囲を拡大し、ユーザーにとって使い勝手が悪い、混乱しそう、不適切な表現では?と思ったことをスタッフ全員が報告できるように変更しました。今ではサービス開発に携わっていないスタッフもこの仕組みを使い、ユーザー体験の向上に寄与することが可能となっています。

事例

ここで「気になる!報告」によってユーザー体験が改善された事例とともに、運用方法をご紹介します。

スタッフからの報告

スタッフから報告があると、ユーザーファースト推進室に「気になる!報告」の内容がメールで送付されます。送付された内容は全て、Googleスプレットシートに自動的に保存され、そこでステータスの管理などを行います。

f:id:kotsuru0812:20151014194215p:plain:w500

スタッフが気になった点

スマートフォンウェブの検索結果画面において、一部、スワイプの動きがスムーズではない箇所がありました。実際に確認してみたところ、動きにひっかかりがあり、もたつくような感じがありました。

f:id:kotsuru0812:20151014194405p:plain:w320

担当部署へのフィードバック

報告の内容を確認し、対応が必要な場合と内容を検討する必要がある場合は、担当部署へフィードバックを行います。フィードバックでは特別なツールは使わずに、誰でも使うことができるメールでやりとりをしています。

f:id:kotsuru0812:20151014194445p:plain

担当部署からどのように対応するかの明確な返答がきたら、その内容を報告者に伝えるため、BCC.に報告者を追加し、担当部署にお礼のメールを送信します。報告者に対応内容を伝えることにより、ユーザー体験の向上に寄与できたことを意識付け、また「気になる!報告」をしてもらうためです。

みんな良いものを作ろうと思っている

フィードバックを行う際に気をつけている点が一つあります。それは「みんな良いものを作ろうと思っている」ということを強く意識することです。何も考えずにフィードバックしてしまうと、意図せず相手を傷つけてしまう場合があります。お互い気持ち良く仕事をするためにも、相手の気持ちを思いやることがとても大切です。

最後に

今回はスタッフが普段サービスを使っている際に、気になったことをいつでも簡単に報告できる仕組みについてご紹介しました。この仕組みの良いところは

  • ユーザーに近い環境で発見した問題をいつでもどこでも報告することができる
  • ユーザーからご意見がこないような小さな問題でも発見することができる
  • 小さな問題をフィードバックすることにより、開発段階から品質に対する意識が高まる
  • 普段サービス開発に携わっていないスタッフでも「気になる!報告」をすることによってサービス開発への理解が深まる

だと思います。まだまだ運用方法などは改善できると思うので、これからも試行錯誤しながら品質の向上に取り組んでいきたいと思います。

*1入社すると付与されるスタッフ権限がついたアカウント

nginx で omniauth を利用してアクセス制御を行う

$
0
0

インフラストラクチャー部 id:sora_hです。クックパッドでは、社内向けの Web アプリ (以降 “社内ツール”) を社外のネットワークから利用する際、アプリケーションレベルでのアクセス制御とは別に、リバースプロキシでもアクセス制御を実施しています。*1

これまで BASIC 認証あるいは VPN による社内ネットワークを経由した接続という形で許可していました。しかし、iOS の Safari などでは BASIC 認証時のパスワードを保存できない上、頻繁に入力を求められてしまいますし、VPN はリンクを開く前に接続をしておく必要があります。これにより、社内ツールを社外で開く時に手間がかかってしまう問題がありました。

これに対し、一部では typester/gateなどを導入し Google Apps での認証を行なっていました。しかしいくつか問題があり、非アドホックな対応では PR を送りつつ独自にパッチを当ててメンテナンスしている状況でした。また、新しい社内ツールで利用する際、新規に設定して起動・監視設定をする必要があるなど、他の社内ツールへ簡単に適用するのが難しい状態でした。

最近になり Microservices 化を進めていく中、社内ツールもあちこちで実装・分離されるようになってきました。一部社内ツールで利用されていた Google Apps 認証を他でも気軽に利用したい、と思い今回 nginx_omniauth_adapterを開発したのでご紹介します。

nginx_omniauth_adapter とは

https://github.com/sorah/nginx_omniauth_adapter

nginx_omniauth_adapter は nginx の ngx_http_auth_request_moduleと組合せて、Ruby の omniauth gem を利用して nginx のアクセス認証・認可を行うための小さな Rack アプリになっています。omniauth とそのプラグインが持つ豊富な認証手段をそのまま nginx で活用できます。

クックパッドでは基本的にこれを Google OAuth2 と組み合わせて利用しています。ログインされたアカウントが社用の Google Apps アカウントであるのを検証する設定にしています。

使い方

Rack アプリのため、設定は config.ruファイルで行います。omniauth 側と、nginx_omniauth_adapter 側の設定を config.ruに書いて起動すれば利用できます。参考までに、クックパッドでは Gemfileconfig.ruを置いたリポジトリを作成し、それを capistrano でデプロイしています。

また、GitHub や Google OAuth2 であれば、添付の Dockerfile と config.ruに環境変数を渡して簡単に利用できます。詳細は README をごらんください。

nginx 側の設定例は examplesディレクトリを参考にすると良いでしょう。

仕組み

さて、上記 nginx の設定を見ると、若干トリッキーな内容になっていることが分かると思います。本節ではそれを踏まえて、nginx_omniauth_adapter がどのように ngx_http_auth_request_module と連携しているかを解説します。

ngx_http_auth_request_module とは

まず、肝心の ngx_http_auth_request_moduleについて軽く紹介します。このモジュールは nginx でリクエストは処理させつつ、アクセス認可処理はどこかへ移譲したいという時に利用できます。

具体的には、auth_request directive が設定されているパスへのリクエストを受信した時、クライアントにレスポンスを返す前に nginx が内部リクエストとして auth_requestで指定されたパスへリクエストを送信します。 この内部リクエストのレスポンスが 200であればページが表示できますが、401403の場合アクセス拒否とみなされます。その時、元のリクエストには 401403が返答され、リクエストの処理が中断されます。

実際に利用する時は、このモジュールによる認可ができなかった場合、別の設定で認証処理へ遷移させます。このモジュール自体はアクセスを許可するかどうかの判定しかできないためです。詳細は後述します。

auth_requestで発生する内部リクエストでは元のリクエストと同じヘッダ・ボディが送信されるので、それを利用して認可を行います。内部リクエスト先に proxy_passを仕掛けておくことで外部プロセスへ処理を移譲できます。

location / {
  auth_request /_auth/challenge;
}
location = /_auth/challenge {
  internal;
  proxy_pass_request_body off;
  proxy_set_header Content-Length "";
  proxy_set_header Host $http_host;
  proxy_pass http://auth_adapter/test;
}

このモジュールをうまく活用できると、前述の typester/gate や bitly/oauth2_proxyと違い、認証・認可のためのミドルウェアでリバースプロキシを実装する必要がなくなるという点が便利です。

auth_request による認証が失敗した時に認証ページへリダイレクトさせたい

omniauth による認証を開始するためには /auth/…のパスへリダイレクトさせる必要があります。しかし、ngx_http_auth_request_module では auth_requestのレスポンスをそのままブラウザに返せません。auth_request先に届くヘッダーやクッキーで認証や認可を行うことはできますが、失敗時に認証ページへリダイレクトさせるといった事をどうやれば良いのかドキュメントを見てもいまいち分かりません。

そこで、nginx_omniauth_adapter では error_page directiveを利用してリダイレクトさせています。error_page directive はステータスコードに応じて内部リクエストを発生させ、それをレスポンスとする事ができます。また、 =オプションを利用すると、その内部リクエストのレスポンスコードを元のエラーのかわりにクライアントへ返答できます。

つまり、auth_requestによるアクセス認可が失敗すると、401あるいは 403がエラーとしてクライアントに返答されます。これは nginx 自体が送信するエラーページのため、error_page 401 = …を利用して内部リクエストを発生させ、そこでリダイレクトを行ないます。なお、nginx_omniauth_adapter の auth_request用エンドポイントは、認証がされていないとき 401を返答します。 *2

location / {
  auth_request /_auth/challenge;
  error_page 401 = /_auth/initiate;
}

location = /_auth/initiate {
  internal;
  proxy_pass_request_body off;
  proxy_set_header Content-Length "";
  proxy_set_header Host $http_host;
  proxy_set_header x-ngx-omniauth-initiate-back-to http://$http_host$request_uri;
  proxy_set_header x-ngx-omniauth-initiate-callback http://$http_host/_auth/callback;
  # ↓が必要な処理を行い、リダイレクトさせる
  proxy_pass http://auth_adapter/initiate;
}

nginx_omniauth_adapter では実際には戻り先 URL 周りの処理があるため、ここでも一度 nginx_omniauth_adapter へ proxy_passさせています。

ただし、この挙動を利用する注意点として proxy_intercept_errors directiveは off に設定しておく必要があります。on の場合、proxy_passでプロキシした先のレスポンスが 401, 403の時に error_pageの設定が作動して、必ず認証ページへ飛ばされてしまうためです。

認証後、元のページへリダイレクトさせる

nginx_omniauth_adapter によってリダイレクトされる先は nginx_omniauth_adapter 自体の FQDN にリダイレクトされ、そこから omniauth の処理が開始されます。これは OAuth 2 のプロバイダなどでコールバック URL が固定だったりするため、nginx_omniauth_adapter 自体に FQDN を割り当てる必要があります。

omniauth による認証処理が終わった後、nginx_omniauth_adapter 側の domain にセッションクッキーがセットされます。元のアプリケーションが動く URL にリダイレクトして戻る必要がありますが、domain が違うためセッションをアプリケーションが動く domain へ引き継ぐ必要があります。

そのため、nginx_omniauth_adapter セッションの中身を認証付き暗号 aes-256-gcm で暗号化してクエリパラメータに載せ元の domain へリダイレクトさせています。 リダイレクト先でも一度 nginx_omniauth_adapter に proxy_passしてもらい、そこで内容を検証・復号した上でアプリ側の domain に再度セッションをセットするようにしています。その後、実際に元のページへのリダイレクトが発生します。元のページを戻った時、正しいセッションクッキーがリクエストに含まれるため、auth_requestが成功してリクエストが継続され無事にページが表示されます。

# ここへ戻ってくる
location = /_auth/callback {
  auth_request off;
  proxy_set_header Host $http_host;
  proxy_pass http://auth_adapter/callback;
}

セッションの引き渡しについては、実際のところ Redis, Memcached など KVS をうまく利用してセッションを引き継ぐべきな気がしています。現状の実装だと大きなセッション情報の引き渡しができなかったり、無駄なトラフィックが発生しているためです。今後の改善点の一つになります。

ちなみに、アプリ側の domain で持つセッションは nginx_omniauth_adapter 側 domain のセッションより有効期限を短く設定して、より定期的に検証させるようにしています。nginx_omniauth_adapter 側の認証処理は、nginx_omniauth_adapter 側のセッションが失効していない限りスキップされ、単にアプリ側のセッションを更新するような挙動になります。 これによりセキュリティレベルを低くせず、頻繁に認証ページへリダイレクトされボタンを押す必要をなくしています。

実際のフロー

以上を図に起こすとこのような形になります。箇条書きで順番に解説しているのは README にあります

実際の効果

一部の社内ツールで検証してから、問題なく運用できると判断して、他の社内ツールも (既に typester/gate を利用していた社内ツールを含め) BASIC 認証から置き換えを行いました。社内ではメールのリンクを外で開いた時に煩わしくなくなったと高評価を貰っています。

また、nginx_omniauth_adapter は nginx と同じサーバー上で動作させる必要はなく、かつ、アプリ毎に細かく nginx_omniauth_adapter を設定する必要はありません。 つまり単に nginx の設定だけ挿入すれば利用できるため、nginx さえ導入されていれば新規に何かをインストールして consumer key 等を設定して…という手間なしに omniauth による認証を導入できるので、インフラ側の手間もかなり低くなりました。

FAQ: なぜ社外ネットワークからの直接アクセスを許可しているのか

まず、クックパッドの社内ネットワークに VPN が導入されたのはここ数年での出来事になります。導入以前から BASIC 認証で社外アクセスを許可している社内ツールが存在しました。

では、今現在 VPN が利用できる中、なぜ VPN のみにせず他のアクセス手段を提供しているのか。前述したような VPN 接続の手間を省く利便性もそうですが、クックパッドで現在進められているグローバル展開に関係して子会社の拠点が世界中に存在し、増えている状況にあります。その中で拠点間 VPN、海外で勤務するスタッフ向けの VPN の整備が追い付いていないというのも一つの理由になっています。

また、その代替手段に Google Apps を利用している理由ですが、Google Apps のアカウントは全スタッフに付与されている事と、2 段階認証を全スタッフに対して必須としているためです。下手な BASIC 認証でオープンにするよりも安全だと判断しています。

まとめ

クックパッドの社内ツールで利用されているアクセス制御の仕組み、 nginx_omniauth_adapterを紹介しました。どうぞご利用ください。

*1:アプリケーションレベルでのアクセス制御に加え、アプリケーションの手前で基本的なアクセス制御を実施することになります。これにより万が一アプリケーション側にバグが混入し、インターネットから認証無しで見えるといった事故を防いでいます。

*2:余談ですが、nginx_omniauth_adapter では 403 も利用しています。リクエスト情報とユーザー情報を使いリクエスト毎にアクセス許可するかの判定処理を設定でき、そこで拒否された場合 403 が返され、認証処理への遷移は発生しません。

市場規範と社会規範の間でポジティブ・フィードバックを受け取る

$
0
0

こんにちは、特売サービスのうさぎ好きエンジニア、伊尾木です。

今回のエントリでは、特売情報サービス上で実施しているポジティブ・フィードバックを回す施策を紹介いたします。

お店へのポジティブなご意見をもらおう!

特売情報サービス ( https://cookpad.com/bargains/ )とは、日本全国のスーパーやドラッグストアなど(クライアントと呼んでいます)が、クックパッド上でチラシを配信するサービスです(よく有料サービスだと誤解されますが、無料ですのでぜひ使ってみてください)。

スーパーなどのクライアントは、忙しい業務に加えてクックパッドにもチラシを投稿してくれており、特売情報サービスの品質は全国のクライアントのモチベーションによってなりたっているとも言えます。 そこで、クライアントのモチベーションを支援する施策として、一般ユーザからお店に対するポジティブなご意見を集められないかという案がでてきました。特売情報上の店舗のページに投稿フォームを設置して、ポジティブなご意見を投稿してもらおうという施策です。

市場規範 vs 社会規範

良いアイデアだとは思うのですが、本当にポジティブなご意見が集まるのか、かなり疑問がありました。というか、私はネガティブな意見しか来ないんじゃないかなと思っていました。

ところで、人間関係には、市場規範と社会規範という2つの異なる規範が存在します。これは「予想どおりに不合理」という本で詳しく述べられています。簡単にいうと、金銭的価値で判断されるのが市場規範で、愛情や友情などで判断されるのが社会規範です。 市場規範は社会規範を壊してしまいやすいということが知られています。例えば、恋人と楽しい夕食のあと家まで送っていくことは社会規範の範疇ですが、その帰り際に「今日の夕食はX円で、車の送迎もY円でだけど、十分にその価値があったよ(市場規範)」というと一気に引かれてしまいますね。(同様に、デートでは男性が奢るべきと主張することも、社会規範に市場規範を持ち込んでいることになりますね。蛇足ですが。)

スーパーでのお買物は明らかに市場規範です。100円で売っているものを100円で買っても、等価交換が行われただけであり、社会規範が関与しているとは思えません。このような市場規範が優勢な状況でご意見を募っても、ネガティブなご意見しかこない可能性があります。 実際、多くのスーパーが店頭でご意見箱のような施策を行っていますが、ネガティブなものが圧倒的に多いようです(店舗によっては、9割がネガティブな意見であったりするようです)。

小さく試す

そこで、小さく試してみることにしました。

  • 4日間だけ投稿フォームを設置する
  • 投稿フォームは店舗ページの目立たない下部に設置
  • 投稿機能だけにする(クライアントへの自動通知、自動公開機能などは作らない)

機能名は「応援メッセージ」とし、以下のようなフォームを設置しました。

f:id:woochanx:20151019154819p:plain

結果、4日間で数百のご意見があつまり、なんとその大半がポジティブなものでした。例えば以下のようなご意見が来ました。

先日、くじで半額シールが当たりました。 それで高級な牛肉を買って主人と結婚してから初めて我が家で焼き肉をいたしました。 とても嬉しくて主人と二人して焼き肉を楽しく頂きました。 こんな楽しい時間を過ごさせていただき、感謝しております。 ありがとうございました。

とか

まだ歩けない子供を抱えてのお買いもの。 買った商品を持って頂いたり、レジ袋につめて頂いたり、お客様本意の対応にいつも感激しております。

.

いつも鶏肉や野菜が安くて助かっています!

先週のこだわりトマトが美味しかったです。

など、かなりいいご意見ですね。

これらのご意見をクライアントにお知らせすると

「普段、感謝されることはあまりないから、本当に嬉しい!!」

「ちゃんと細かい仕事まで見てくれて、嬉しい!」

といった反応があったり、メッセージが表示された画面をプリントアウトして大事にそう持って帰ったクライアントもおられました。

この結果を受けて、機能を整備し、投稿フォームを常設するようにしました。

機能をチューニングする

投稿フォームの常設後もポジティブご意見が多く集まってきましたが、一方ネガティブなご意見も日々一定数投稿されていました。そこで機能のチューニングを実施しました。

具体的には機能名を「応援メッセージ」から「このお店のここが好き」(通称:ここスキ)に変更しました。

「応援」は社会規範の言葉であるものの、叱咤激励のニュアンスもあるため、機能名自体をよりポジティブなものに変更してみました。また、これに合わせて細かいUIの修正も行いました。

f:id:woochanx:20151019154826p:plain

結果、ポジティブご意見が増え、逆にネガティブご意見は半分に減りました。改めて機能名、重要ですね。

なぜポジティブな意見が集まったのか

私の浅はかな予想を裏切り、多くのユーザはポジティブな意見を投稿してくれました。

ポジティブ意見の中身を見ると、半分が「安い」や「新鮮、美味しい」といった内容で、もう半分が「親切・元気で嬉しい」という感じです。「親切・元気で嬉しい」というのはまさに社会規範の範疇ですね。面白いのは「安い、新鮮、美味しい」というご意見が多いことです。これらは主に市場規範の範疇ですが、おそらく店舗側の大変な努力をユーザが実感し、市場規範を超える社会規範が現れているのかもしれません。

ところで、なぜ店頭ではネガティブが多く集まり、特売情報サービスではポジティブが多く集まったのでしょうか?

機能名

機能名が「口コミ」や「店舗へのご意見」では、もっとネガティブが多かった可能性があります。「応援」や「ここが好き」という社会規範側のネーミングにしたことが良かったと考えています。実際、機能名を変更してからさらにポジティブご意見が多くなったことからも、機能名の影響が大きかったと思います。 一方、店舗自身が、このようなネーミングを使って意見を集めることはかなり無理があります。この点で、本施策の「ここスキ」はプラットフォーマーであるクックパッドならではと言えます。

いつでもネットから投稿できる

いつでもネットから投稿できるというのも良かったのかもしれません。というのは、店頭でのご意見投函は、買物袋を手に下げつつ、ご意見を記入することになります。しかし、そこまでしてポジティブなご意見を投稿しようというモチベーションはなかなか湧きません。このため店頭では、ネガティブ意見が多くなる傾向にあるのかもしれません。

つくれぽの影響

また、クックパッドには「つくれぽ」というレシピ作者さんへのポジティブ・フィードバックを返す機能があります。ここスキは、つくれぽとある部分非常に似ています。クックパッドユーザの多くがつくれぽを知っているので、クックパッド上でポジティブご意見を投稿しやすいという可能性があるのかもしれません。実際、ログインユーザのほうがポジティブご意見を投稿する割合が高くなっています。

ただし、つくれぽは完全に社会規範の範疇ですが、ここスキは市場規範と社会規範の間に立っているという違いはあります。

おわりに

以上、ここスキを通してポジティブ・フィードバックを受け取る施策のご紹介でした。

最後に、この機能の副次的効果を紹介して終わりたいと思います。

ここスキにご意見が投稿される度に、社内のチャットにも投稿されたご意見を表示させました。 この結果、どこの店舗がこんな親切な対応しているのかぁと、理解できるようになりました。

さらに、チャットという常に見るツールの中に多くのポジティブなご意見があると、それだけでモチベーションが上がるように感じています。もちろん、それらのご意見は自分たちに向けられたものではないですが、ポジティブループの中に自分たちも関与できていると実感できるので、非常に良い効果があると感じています。

夏のインターン講義「1営業日で書くJavaScriptコンパイラ」の設計と実装

$
0
0

今年、クックパッドでは夏のインターンと題して20名弱のインターンを受け入れました。 このインターンは前半と後半に大きく分かれており、 後半が社員に混じって業務をするいわゆる普通のインターンで、 前半は7日間にわたってプログラミング関連の講義を受けるという仕組みです。

わたし(青木)はその前半の過程において、「プログラミングパラダイム」という 1 日の講義を担当し、 JavaScriptの処理系を書くという、ツッコミどころの多い課題を実施しました。 本稿では、その講義を開発する際に考慮したこと、特に難易度調整についてお話しします。 また講義のために開発したJavaScript処理系「JetSpider」についても軽くふれます。

▼講義資料

講義のコンセプト

「プログラミングパラダイム関係でなんかやって、1日で」という注文を聞いて最初にわたしが思ったことは、 「それは無理だろ」この一言に尽きます。「関数型」だの「オブジェクト指向」だのと言った プログラミングパラダイムの話をゼロから講義するなんて1日でできるわけないですし、 パラダイムの背後にある言語の話を4つか5つはこなす必要があります。 どう考えても分量的に無理であることに加え、完全に講義になってしまい、面白くありません。

ではどうするか。

こういうときのわたしの戦略はだいたい決まっていて、 なんとかギリギリ許されるかもしれない範囲でテーマを勝手に極大解釈することにしています。 今回は、「プログラミングパラダイム……を学ぶうえでの礎になることならなんでも可」 という文言を脳内で勝手に付与して、言語処理系の話をすることにしました。 わざわざわたしに振ってきたということは、たぶん言語処理系っぽい内容が 期待されているのだろうな(わたしは言語処理系の本を何冊か書いているので)というメタな読みも働いていました。

わたしの講義資料を見ていただければわかるように、 なぜプログラミングパラダイムで言語処理系の話をするのか…… といった雰囲気の話が冒頭に書いてあっていくらかそれっぽく見えるのですが……

まあ、ぶっちゃこのへんは全部後付けですね!! 結論を先に決めてあとから書きました。

講義におけるUXの設定

言語処理系で何か講義をしようと決めましたが、もう少し具体化しなければいけません。 具体的に課題を決めるうえで考えたことは、なによりも、 「できるだけマゾい(つらい)課題にしよう」ということでした。

ここで言う「マゾい」とは、分量が多いということではなく、 全力で考えないと対処できない、ということです。 わざわざあえてマゾくするのは、わたしの趣味……ではなく、 困難な課題のほうがやりきったときの達成感がある、難しいほうが楽しい、という信念によります。 UX的に言うと、次のような体験を得てほしいと思ったわけです。

f:id:mineroaoki:20151020135407p:plain

また、わたし以外の講義はどれも入門的な内容や半日の講義だったため、 難易度を上げるならこの講義しかないだろうなという思惑もありました。

ですからわたしの講義のコンセプトは「とにかくマゾく!」。 ちょうどわたしの講義は前半最後の日だったので、 この講義のラスボスとなってインターン生を地獄に叩き落とす気持ちで講義を作りました。 いたいけなインターンたちを苦しませるのは超たのし……心が痛みますが、 インターン全体のクオリティを上げるためにはやむをえません。

課題を具体化する

さて、そんなことを頭の片隅に置きつつ、 3日くらい他のことをやりながら考えたマゾい課題の一例がこちらです。

  • JavaアセンブラだけでHaskellインタプリタを書く
  • Prologの処理系をOCamlで書く
  • ネイティブコードコンパイラを設計・実装する
  • x86アセンブラでインタプリタを書く

いやいやいやいや……。

これは無理だろ!!!(一人ツッコミ)

いくらマゾくすると言っても、誰もやりきれない課題にしてしまっては意味がありません。 こんな酷いリストの中でも「JavaScriptの処理系を実装する」という 課題は比較的無難そう(?)だったので、これに仮決めしました。

ですがJavaScript処理系という課題にしても「全部書け」だとやはり分量が多すぎますから、 やるなら処理系の一部だけにすべきでしょう。具体的には処理系のモジュール 1 つ…… つまり、パーサー、コードジェネレーター、VM、インタープリターの評価器、のどれか、くらいが妥当だろうと当たりを付けました。 そしてパーサーはわりとよくある話ですし、言語処理系以外でもさわる機会があるので、 コードジェネレーター(構文木からターゲットのコードを生成するモジュール)を書いてもらうことにしました。

それに、コードジェネレーターならばバイトコードをさわる機会も作れます。 きっと最近の若者はバイトコードのようにオフセット計算が必要だったり制御構造がgotoしかないような言語には それほど慣れていないでしょうから、効果的にマゾくできることが期待できそうです。

ソース言語を決める

処理対象の言語(ソース言語)の選定については、次のような基準で選びました。

  1. 多くの人が言語を知っていること(ソース言語の仕様を調べなくていいから)
  2. マルチパラダイムである(例えばJavaだとOOPすぎる)
  3. 基本部分がそれほどリッチでなく動的型付け(実装が楽になるから)
  4. 見ためがあまりRubyに似ていない(Rubyはコンパイラを書くのに使うので混乱を避けたい)
  5. バイトコードVMの実装がすでにある(自力実装するのはダルい)

1でLisp、Haskell、OCamlが消え、2と3でJava、C#、Cが消え、3と4でRubyとPythonが消えて、 3、4、5でPerlが消えます。残るのはJavaScriptくらいなので消去法でJavaScriptになりました。

さきほど述べたように、バイトコードを生成させると決めた時点でかなりマゾくなっているはずなので、 このあたりで難易度を上げることは避けて、できるだけ無難に選びます。

記述言語を決める

コンパイラ自体を何の言語で書くかも重要です。例えばHaskellで書くとしたら、 (あまり知らない)Haskellを覚えながら(あまり知らない)コンパイラを書くということになってしまい、主眼がブレます。 今回のインターンでは、スマホアプリ以外はRubyで書く講義が多かったので、 ここも無難にRubyで書いてもらうことにしました。

そして偶然ですが、RKellyという Ruby製のJavaScriptパーサーも見付けたので、 これを使えばパーサーは書かなくて済みそうという点も大きなポイントでした。 コンパイラのモジュールの中でもパーサーはちょっと面倒ですから、 ここを省けると開発の手間を大きく削減できます。

JavaScriptのVMを決める

以上で課題の大枠は決定しました。 即ち、「JavaScriptコンパイラのコードジェネレーターをRubyで書く」です。

残る課題はコンパイラの残りの部分とVMを用意することです。 コンパイラは適当に書けそうな目処が立っているので、VMをどうにかしなければいけません。 JavaScriptのVMなんてそのへんに死ぬほど転がっているので適当に選べばよいだろうと たかをくくっていたのですが、なかなかどうして意外とぴったり目的にかなうものがありません。

  • みんな大好きGoogle V8はJavaScriptを直接機械語にコンパイルするのでバイトコードを経由しない
  • WebKitのJSエンジン(JavaScriptCore)はWebKit内蔵なのでWebKitが全部必要になって超ダルい
  • Rhinoはバイトコードが露出してないのと、JVMの面倒な話(クラスローダーとか)に関わりたくないのと、できれば最終的にネイティブコードにまで落ちるVMを選びたい
  • FirefoxのJSエンジン(SpiderMonkey)もバイトコードが露出していない

そんな中でもDukTapeという組み込み向けJS処理系はコンパクトで バイトコードを突っ込むインターフェイスもあり、ドキュメントも揃っていて なかなかよさそうだったのですが、今回は最終的にSpiderMonkeyを選びました。

そのときの決め手はズバリ、「作るのが面白そうだから」です。 VMのコードに触れるのわたしだけであり、講義の難易度には影響しないので、ここは完全に自分の趣味で決めました。 きれいでコンパクトで最小限の機能のほうが楽であろうことはわかっているのですが、 しかし、そんなものはさわってても(わたしが)面白くありません。 どうせいじるなら、巨大でユーザー数が多くて高速な本気のコードをさわりたいのです。

なお、いちおう追加の言い訳をしておくと、コンパイラを作る側の気持ちを考えても、 いかにも講義のために作ったような半端なVMをターゲットにするのでは楽しくないだろうと思います。 たとえコンパイラ側が 1 日で作ったざっくりコンパイラであっても、 VMさえ速ければそれなりの速度を出すことができます。 どうせなら十分に手のかかっている強力なVMを使ったほうが面白いでしょう。

ちなみにSpiderMonkey単体のソースコードはこのへんから入手できます。 Firefoxを全部コンパイルするとか心底やりたくないので、これは大変嬉しいですね。 コンパイルはわりと普通に ./configure ; make で通ります。 このあたりがすんなり行けたこともSpiderMonkeyを選んだ理由の1つです。

JetSpider VMのアーキテクチャ

ここで、今回講義のために作成したJavaScript処理系ーーJetSpiderーーのアーキテクチャを簡単に紹介します。 結論から言うと、JetSpiderは次のようなアーキテクチャになっています。

f:id:mineroaoki:20151020135413p:plain

SpiderMonkeyを単体のバイトコードVMとして使ううえで最大の問題は、バイトコードが露出していないことでした。 SpiderMonkeyのAPIリファレンスを眺めてもらえるとわかるのですが、 JavaScriptのソースコードをコンパイルしたりファイルから読み込むAPIはあっても、 コンパイル後のバイトコードを保存したり、ロードしたりするためのAPIがありません。

せいぜい使えそうなAPIと言えば、 バイトコードを保持する内部データ構造であるJSScript構造体を実行する JS_ExecuteScript関数くらいでしょうか。 つまり、Javaで言うクラスファイルのような、オブジェクトファイルに類するものが足りないわけです。 そこは自力で実装してやる必要がありそうです。

最初は適当にJSScript構造体のメンバを全部吐けばどうにかなるだろうと思っていたのですが、 実際に手をつけてみたらあまりにも面倒で嫌になってしまいました。 そこで手当たりしだいにソースコードをgrepしまくったところ、 XDR(eXternal Data Representation)という データ直列化の仕組みが実装されていることに気付きました。

XDRという概念は歴史的にはSun RPCが由来っぽく、 XDR APIというAPIを使うことで様々なデータ構造を直列化できます。 そしてSpiderMonkeyでも、非公開関数のJS_XDRScript関数を呼べば、 JSScript構造体をセーブ・ロードできることがわかったのです。

(え、非公開なのにどうやって呼ぶかって? そんなの、extern付けて再コンパイルすればいいでしょ!! 具体的には SpiderMonkey.diffをごらんください)

つまり、あとはRubyからこのXDR仕様のフォーマットでJSScript構造体を吐いてやり、 それを読み込んで実行するだけの簡単なラッパーを書けば済むわけです。 Rubyからバイナリを吐くのはひたすらpackしまくるだけの簡単なお仕事ですね。 JetSpiderでは以下のようなコードでオブジェクトファイル生成を実装しました。

      def serialize_JSScript(f)
        f.uint32 JSXDR_MAGIC_SCRIPT_CURRENT
        f.uint16 local_variables.size
        f.uint16 arguments.size
        f.uint16 upvars.size
        f.uint16(padding = 0)
        f.uint32(*vars_bitmap)
        var_names.each do |name|
          f.jsstring name
        end
        code = bytecode()
        f.uint32 code.length
        f.uint32 prolog_length
        f.uint16 JS_VERSION
        (以下略)

f.uint32やf.uint16は内部でpackを使ってバイナリを出力するDSLです。

これでVM側の実装も目処が立ちました。

可視化で難易度を下げる

効率的に講義を進めるために、コンパイラ・VMの両方で特に気を付けていた点があります。 それは、とにかくできる限りのデータと動作を可視化するということです。

JetSpider(今回講義のために作成したJavaScript処理系)では、コンパイラもVMも、 コマンドラインオプションを指定することでほとんどあらゆるものをダンプできます。 例えば--dump-astオプションで構文木をダンプ、--dump-semオプションで意味情報付き構文木をダンプ、などです。 VMのほうも、--disassembleオプションを付けるとコードを逆アセンブル、--traceオプションでVMの命令をトレースすることができます。

▼--dump-astオプションによるAST可視化

% ./bin/jetspider --dump-ast exp/arith.js
type: SourceElementsNode
value:
- type: FunctionDeclNode
  value: f
  arguments:
  - type: ParameterNode
    value: x
  function_body:
    type: FunctionBodyNode
    value:
      type: SourceElementsNode
      value:
      - type: ReturnNode
        value:
          type: AddNode
          left:
            type: NumberNode
            value: 1
          value:
            type: MultiplyNode
            left:
              type: ResolveNode
              value: x
            value:
              type: NumberNode
              value: 3

今回の講義では、コードジェネレーター以外はすでに用意されています。 言い換えると、コードジェネレーター以外は自分の知らないコードなわけです。 すべてのコードを読まないと動作が理解できないようだとやはり 1 日では終わらなくなるので、 残りの部分については、ソースコードを読まずとも内部動作を理解できるようにしておく必要があります。 その手段が可視化です。

以前に言語処理系の本を書いたときに、 可視化を充実させれば処理系が容易に理解できることはわかっていましたから、 言語処理系の課題を出すと決めた時点で徹底的に可視化することも決めていました。

逆アセンブルで難易度を下げる

可視化機能を充実させたことで、わたしの開発も楽になりました。 特に役立った機能がVMに付けた逆アセンブラです。 SpiderMonkeyのコンパイラにコンパイルさせたバイトコードを逆アセンブルすると 「正解」のコンパイル結果がわかるので、コンパイラの実装が凄まじく簡単になるのです。 例えば次のような簡単なif文のコンパイル結果を逆アセンブルすると下記のような出力が得られます。

▼if文

if (1 < 3) {
    print(7);
} else {
    print(9);
}

▼コンパイル結果の逆アセンブル

main:
00000: 3f         one 
00001: dd 03      int8 3
00003: 14         lt  
00004: 07 00 10   ifeq 20 (16)
00007: d9 00 00   callgname "print"
00010: dd 07      int8 7
00012: 3a 00 01   call 1
00015: 02         popv
00016: 06 00 0d   goto 29 (13)
00019: bd         nullblockchain
00020: d9 00 00   callgname "print"
00023: dd 09      int8 9
00025: 3a 00 01   call 1
00028: 02         popv
00029: c5         stop

ここまでわかっていれば、ほとんど何も考えずに実装できますね!

ここで手を抜けたおかげで、コンパイラはコードジェネレータの参考実装を含めてほぼ 2 日で実装できました。 あたかもすでにテストが実装されているTDDのようなものです。 このプロセスがあまりにラクで楽しかったので、講義でもこの方式を大々的に取り入れたくらいです。

ただ、この手法にはちょっとした罠もあります。 SpiderMonkeyのコンパイラが賢いおかげで、いかにもテスト用っぽい簡単なコードを書くと、 定数たたみこみ最適化によってプログラムがことごとくシンプルに変形されてしまい、 目的のコードがわからなくなるのです。

▼簡単すぎるコードを書いてしまうと……

print(1 + 2);

▼足し算が消滅してしまう

main:
00000: d9 00 00   callgname "print"
00003: dd 03      int8 3
00005: 3a 00 01   call 1
00008: 02         popv
00009: c5         stop

この最適化の罠は講義においても猛威を奮い、多くのインターン生が苦しみました。 今度また同じ講義をやるときには、最適化をオフにする実装を入れておきたいところです。

まとめ

本稿では夏のインターンで行った「プログラミングパラダイム」の講義について、 そのコンセプトと設計、および実装をお話ししました。

実習を含む講義の設計は難易度の調整が肝だとわたしは考えています。 難しくするための要素(バイトコードの生成)とそれ以外の要素(記述言語など)とで扱いを明確に分けて、 難しくするところ以外はできるかぎり理解しやすくする工夫をする、というところがポイントでしょう。 今回は幸いにもそこの難易度調整がうまくできたようで、 6割くらいのインターン生が時間ギリギリで全部の課題を終わらせることができました。

ちなみに、インターン生の中にも言語処理系に慣れている人と慣れていない人がいたので、 「追加課題」というかたちで課題数を増減させることでも多少難易度が変わるようにしておきました。 しかし、こちらはだいぶ自由選択にまかせたこともあり、それほどうまくいかなかったように思います。 次に同じような課題をやることがあれば、講義の運用についてももう少し詰めておきたいところです。

(青木峰郎 / Minero Aoki)

iOS9 のリリースでクックパッドに起きたこと

$
0
0

こんにちは、技術部モバイル基盤グループの茂呂(@slightair)です。

モバイル基盤グループでは、クックパッドの iOS/Android アプリに関する様々な仕事をしています。

  • 不具合を抑え、品質を保ちながら安定してリリースサイクルを回せる環境づくり
  • アプリの開発者がサービス開発に専念できるように、コードリファクタリングやライブラリの整備
  • OSやライブラリ、開発ツールのバージョンアップに伴う調査・検証・対応

この記事にはiOS9がリリースされた結果、クックパッドのサービスに何が起き、どういう対応をしてきたかをまとめます。

Universal Links

iOS9 で Universal Links という機能が入りました。これは、Safari で開いた Web ページ中のリンクに対応したアプリが端末にインストールされていれば、アプリでリンク先のコンテンツを表示できるというものです。 うまく Web サイトとアプリが連携すれば検索結果などからアプリへスムーズに誘導できてよさそうなこの機能が、クックパッドのアプリの場合は困ったことになりました。

iOS9 beta がリリースされてすぐの頃、検証していると Safari でクックパッドサイトのリンクを開こうとするとアプリが起動する事に気がつきました。Universal Links の機能を使うような設定をしてないのになぜ…?

Universal Links の機能は AppDelegate の application:continueUserActivity:restorationHandler:メソッドの実装を必要とします。クックパッドのアプリは iOS8 から導入された Handoff に対応しており、このメソッドを実装していました。そして cookpad.com の Webサイトにも apple-app-site-associationファイルを配置しています。 あとから(といってもiOS9リリース直前、GMが出たタイミングに)わかったのですが、この apple-app-site-associationファイルに activitycontinuationフィールドがあり、かつ Universal Links のための applinksフィールドがない場合、applinksフィールドに [ "*" ]が指定されているものと見なすようです。 つまり、意図せず cookpad.com の全てのページが Universal Links に対応しているという状態になってしまっていたのです。アプリケーション側でもリンクを検証していなかったので、cookpad.com に Safari でアクセスすると常にアプリが起動する状態になってしまいました。

iOS9 beta の初期の頃は全てのリンクでアプリが開いていましたが、その後リリースされたあるバージョンからは https のリンクのみアプリへ遷移するように変わっていました。そのため、iOS9の不具合だったりするのかなと思いバグレポートを送りましたが、答えがわからないままiOS9 GMがリリースされてしまいました。

https のリンクは、ログインページやご意見をいただくフォームに使っていました。なので、ユーザがWebページからログインしようとしたときにアプリが起動します。ユーザはアプリが起動した意味がわかりません。疑問に思ったユーザがどういうことなの?と「ご意見」のリンクからメッセージを送ろうとした時にもアプリが起動してしまいます。さらに混乱を招きます。この流れは、ユーザにとって最悪の体験になってしまうでしょう。

結局、Handoff の機能を一旦落とすことがユーザの体験を保つために必要なことだと僕らは判断し、8.2.0 で無効にしています。サーバ上の apple-app-site-associationファイルを更新することでインストールされている端末でもアプリに遷移しないようにしようと考えました。ただ、apple-app-site-associationファイルを取りに来るタイミングもよくわからず(インストール時?)効果はありませんでした。このファイルには Handoff の実装時にも瞬間的に大量のアクセスがiOS端末からやって来て泣かされています(MacからiPhoneに遷移させよう)。

無効にした 8.2.0 も iOS9 リリース直前ということで審査待ち時間が長引いていて、iOS9リリース前に間に合いませんでした。そのため一部のユーザにご迷惑をお掛けしてしまったことを残念に思っています。 きちんと Universal Links への対応を行い、Handoff の機能も今後のアップデートで復活させたいと考えています。しかし、現状はこれらの機能の検証が困難という問題があります。

App Transport Security

App Transport Security(以下 ATS)はアプリのセキュリティを向上させるものです。以前の記事や弊社エンジニアのブログでも言及されているので説明は省きます。

後述する様々な理由によりクックパッドのiOSアプリではまだ Xcode6 系を使用しています(2015/10/21現在、v8.5.0 以降で使用予定)。 なので、まだ ATS の設定をしていないのですが、最初は API サーバのみ有効にしようと考えています。 API サーバはすぐ有効にできそうなことが分かっています。

iPad Multitasking

iPad の一部の端末のみ対象ですが、2つのアプリを画面分割して表示できる機能が入りました。これにより、様々な画面サイズで iPad 向けアプリが表示されるようになりました。

AutoLayout や SizeClass を利用し、複数の画面サイズにアプリがきれいに対応できていれば問題ないのかもしれませんが、クックパッドのアプリは残念ながらそうはなっていませんでした。今のiPad向けの画面はAutoLayoutなどの概念が登場する前にデザインし、実装されたためです。 Multitasking に対応するには画面デザインを根本から考えなおさなければいけないようで、すぐに対応するのは難しそうです。 そもそもAndroidもなんですが、電話機向け・タブレット向けと画面を分けるのは正しくない時代になっているんじゃないかと考えています。

Safari

iOS アプリだけでなく Mobile Safari で表示する Web サイトにも色々な影響がありました。

コンテンツブロッカー

クックパッドのサイトは、iPhoneの場合はスマートフォンWebサイト、iPadの場合はPC向けWebサイトを表示しています。コンテンツブロッカーの中には、広告を取り除くだけでなくWebサイトの表示を一部崩してしまうものもあるようです。 様々なコンテンツブロッカーがあるので、コンテンツブロッカーを導入した環境での表示崩れに全て対応するのは困難であると考えています。そのため、コンテンツブロッカー利用時の表示崩れ・機能不全に関するご意見を頂いても申し訳ないのですがサポート対象外と案内しています。

収益への影響に関しては、まずは計測をしてみようということになっています(広告ブロッカーの検知と計測について)。

キャッシュがおかしい?

iOS9 の Safari でクックパッドのサイトを開くと毎回ログインしないといけなくなってしまった、というお問い合わせを頂きました。検証してみると、Safari のページキャッシュの挙動がなんだか怪しい事がわかりました。

具体的には、Safari を一旦終了し、起動し直すと前回開いていたページが表示されるのですが、この時にリクエストは飛んでいないことがわかりました。 ページのキャッシュ設定によりリクエストが飛ばないことは正しいのですが、表示されている内容がどうもおかしい。このためにブラウザとしてはログインしている状態なのにも関わらず、ログアウトしているように見える状況があるようです。この現象はGoogle Chrome などの他のブラウザアプリでは再現しませんでした。iOS 8 以前の Safari でも再現しないことを確認しています。ページをリロードをすると問題なく使える状態になるのですが、不穏な動きです。

僕の環境では、クックパッドに限らず他のサイトでも古いページが表示される現象に何度か遭遇しています。 クックパッドではキャッシュ設定を見直すなどして様子を見ていますが引き続き調査をしています。

閲覧履歴を削除すると Cookie も消える

こちらも iOS9 からの仕様変更なのかなんなのか理由がわからず困っている問題です。 Cookie が消えるということはログインセッションも消えてしまうので、ユーザは意図せずログインセッションを消してしまうことになり、毎回ログインを求められるようになってしまいます。「履歴の削除の操作でログイン状態も解除されてしまうのでそのような操作は行わないようにしてください」と案内するのもなんだかおかしな状況で、どうしたらいいんでしょう。ログインが必要なサイトを運営している方々はみんな困っているんじゃないでしょうか。

Xcode7/iOS9SDK

iOS9 で入った新しい機能を使うためには Xcode7 を使う必要がありますが、場合によってはビルドそのものができなくなってしまったり、iOS9 SDK を使うことで描画が崩れたり動作が変わるものがありました。

Swift2

Xcode7 から Swift2 がサポートされました。そのため、Swift 1.2 で書かれたコードはまずコンバートする必要があります。 クックパッドからリリースしているアプリには Swift で書かれたものがいくつかあります。 このアプリを修正して新しいバージョンをリリースするためには Swift2 に対応する必要があります。

Xcode には Swift2 への移行ツールが付属しているので、だいたいはそれで移行できます。 ただし、利用しているライブラリが Swift2 に対応しているか、対応していてもライブラリの修正にアプリを追従させなければいけないなどの問題があります。各プロジェクトで移行を進めている段階です。

言語仕様変更に追従していく大変さはありますが、どんどん便利になっているので Swift を採用していきたい気持ちはとても大きいです。

UIApplication canOpenURL: が使えなくなった

iOS9 から指定したカスタムURLスキームが開けるかどうか確認する canOpenURL:が何も設定しないと常に false を返すようになりました。アプリ間連携のために遷移先のアプリがインストールされているか調べるために必要なメソッドでした。これはクックパッドでアプリ間連携するアプリのリストを作り、Info.plistに指定するだけで問題なさそうです。

iOS9SDK でビルドすると見た目が変わる・描画が崩れる

アプリを実行する端末のOSバージョンと、アプリをビルドするSDKのバージョンの組み合わせで、描画内容が変わったり、動作が微妙に異なるケースがあることに気が付きました。クックパッドのテスト環境では、テストケースを回しながら画面のスクリーンショットをとり、その差分を検出する仕組みがありました。そのため、新しいSDKを使った場合にどのような変化がアプリに起こるか網羅的に確認する事ができました。

iOS9 でフォントが変わったのもありますが、それ以外にもコードの記述やその他の理由で以前のSDKでは発生しなかった不具合を見つけられたので、その対応が終わるまで新しいSDKの使用を延期するという手をとることができました。

iOS9 のみで起きるクラッシュ

クックパッドのアプリでは fabric.io を利用してアプリのクラッシュ数・クラッシュ原因を収集しています。iOS9 がリリースされてから iOS9 のみで発生するクラッシュの数が多いようで悩んでいます。内容を見てみると、iOS の UIKit のとあるメソッドで例外が発生しているようです。デベロッパにはどうしようもない類のこのクラッシュが他のクラッシュの20倍くらいの数で起きているようなので、早めにこの不具合が修正されることを望んでいます。

今後に備えて対応したもの

困ったことばかり書いていてもつらいだけなので、今後に向けて対応したもの、対応していこうとしているものも書きます。

App Thinning

ユーザが端末にアプリをインストールするときに、必要なリソースのみをAppStoreからダウンロードできるようになる仕組みが入るようです。例えば、iPhone6 でアプリが動作するときには @2xの画像リソースが使われるので、@1x, @3xのリソースは必要ないはずです。そのような状態で @3xの大きな画像までダウンロードしないといけないのは無駄です。

画像リソースをきちんと AssetCatalog で管理するようにすればよいとのことだったので、つい最近重い腰を上げて修正しました。クックパッドのアプリではたくさんの画像を使っているので、大変な作業になるのではないかと躊躇していましたが、意外とすんなり移行することができました。ついでに不要な画像を取り除くこともできたのでいいことづくしでした。

Bitcode というコンパイルしたプログラムの中間表現を作成し、アップロードすることで、AppStore が最適化したプログラムを配信できるようにするという仕組みも App Thinning の一部として追加されました。 ただ、こちらは依存しているライブラリの対応をそれぞれ待つ必要がありそうなので、まだクックパッドではしばらく有効にできないなと判断しています。

Nullability, Lightweight Generics

Xcode6 で Nullability, Xcode7 で Lightweight Generics が Objective-C に導入されています。コードがわかりやすく、安全になるだけでなく、Objective-C のプロジェクトの Swift への移行のサポートにもなるので、積極的にコードを修正する際には付けて回りたいと考えています。

まとめ

iOS9 がリリースされたことでクックパッドのサービスに起きたこと、それにどう対応したか、どう対応していくかについて書きました。 まだ対応が完了せずに調査を進めているものもありますが、サービスを安定して続けるために、新しい価値をとり込んだり作っていくために、日頃からアンテナを高くして変化に対応していかなければなりません。

なかなか外的要因で難しくなってしまう場合もあるのですが、一番はユーザのみなさんに不都合なく安定したサービスを続けていけることだと思っています。また、新しい体験を使ったより良いサービスを届けられるよう、努力していきたいと考えています。

施策の効果をみんなで納得して前に進むための「箱ひげ図」

$
0
0

こんにちは、検索・編成部ディレクターの岡根谷です。

クックパッドを訪れてレシピ検索するユーザーさんの検索成功率を上げるために、日々施策を行っています。

自信を持って進めるためには客観的なデータ

はじめはどんなによさそうと思った施策でも、進めていく中で、自分や一緒にやっているエンジニアが施策の価値に自信をなくして停滞する瞬間が必ずあります。

そんな時、A/Bテストの結果などの客観的な定量データは非常に心強いです。客観的な裏付けがあると、判断に対しての迷いがなくなり、前向きに改善に取り組んで価値を生み出していけるようになります。

客観的データを自分の言葉で伝えたい

しかし、このよく言う「施策の効果を数字で」というのは、いざちゃんとやろうとすると非常に手間のかかるものだったりします。 ある機能が検索成功率を上げるのに有効ということを示すために、

「機能ありの方がなしの場合より検索成功率高めだから効果ありそう」

と唱えるだけではもちろん不十分で、その程度ならやっぱり誤差かも...という不安が生じて容易に施策が減速してしまいます。 かと言って統計学的に十分な証明をしようとすると、

  • 機能ありの場合の方がなしの場合の方より成功率が高くて、
  • かつサンプルサイズが十分に大きくて、
  • なおかつ5%水準で有意差があって...

といった細かい検証作業が必要になり、数字と格闘しているうちに気がついたら一日が終わっていた...ということになりかねません(実際私はありました)。施策の評価のために価値創出に時間が割けなくなってしまっては本末転倒です。 その上、精緻な検証の結果「z=2.13だから5%水準で有意差あり!」と言っても、やっぱり自分が使い慣れない言葉では人の心は動かせず、チーム全員で自信を持って前に進むことは難しいです。

「箱ひげ図」で施策の効果を視覚的に伝える

そこで使い始めたのが「箱ひげ図」です。

対象ユーザーの検索成功率ががだいたいどのあたりに固まっていて、どれくらいのばらつきがあるのかがひと目でわかります。

下の例では、定番メニュー機能を使って「ダイエット」で検索したユーザーのうちの半分が、成功率0.13~0.25の範囲に収まっていることがわかります。この「箱」部分がボリュームゾーンで、上位25%と下位25%の「ひげ」部分はばらつきとみなします。 機能あり群となし群の箱がかぶっていないことから、十分に差が出ていそうということが言えます。

f:id:m_okaneya:20151021200739p:plain

この施策の初期では、箱ひげ図を用いて4つのテストクエリについて評価しました。

f:id:m_okaneya:20151021202319p:plain

私がチームで箱ひげ図を使っていて、最大の利点だと感じるのは「施策の効果にみんなが合意して前に進めること」です。 上の例では、「ダイエット、糖質、朝ごはんというクエリでは有効だったけど風邪というクエリでは効果なさそうだね。この差ってなんだろう。じゃあこの3クエリのいい所を伸ばしてxxしよう」という、よい所に目を向けた改善のためのコミュニケーションが可能になります。

有望な施策を押し進めて価値を生み出すという目的のためには、統計学的な有意性を示すことよりも、その有効性を信じ込めることの方が重要だと考えています。これは、会社で働き始めて、大学の研究と一番大きく違うと感じるところです。有意差検定しないとわからないくらいの微々たる差しか出ないならばその施策にエネルギーを費やすのはやめた方がいい、と言われたこともあります。これにははっとさせられました。

Googleスプレッドシートで誰でも作れる箱ひげ図

ディレクター知見共有会でこの箱ひげ図を使って施策の話をした際に、予想外な好評価をいただきました。

そこで、スプレッドシートで使える箱ひげ図のテンプレートを作成して共有したところ、他のディレクターの方にも使ってもらえるようになりました。いろんなところで使われるようになると、図の見方にも早く馴染めて、コミュニケーションの道具としてのメリットは倍増します。

集計したデータをテンプレートにぺたっと貼れば図ができるので、数字が苦手なディレクターでも便利に使えるのがよいところです。

f:id:m_okaneya:20151021202408p:plain

どんな時でも前に進めるのがディレクターの仕事

自信がない時にも、施策のよい所を見つけて伸ばして前に進めていくのがディレクターの仕事だと教えられました。チーム全員が施策の有効性に自信を持って前に進むために、我々のチームで箱ひげ図は大変心強いコミュニケーションの道具になっています。

言語だけでなく、技術や数字や図も自分の言葉として使ってチームを動かし、より多くの人に「クックパッドで作りたいものが見つかった」体験を届けられるよう努力していきます。

このエントリを読んで、(箱ひげ図を使って)検索をよくしたいと思ってくださったエンジニア・デザイナーの方は、こちらからご応募お待ちしています!

部署の課題を継続的に改善する取り組み

$
0
0

はじめに

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

約1年前、「サービス開発エンジニアからマネージャになった話」というエントリを投稿しましたが、現在も試行錯誤しながらマネジメントに取り組みつづけています。

組織は生きもの」とも言いますが、私の部署もまた生きもののように、日々いろいろな課題が生まれ、それに取り組んでいます。今回は、そのような部署で私が感じた課題と、それに対する具体的な取り組みについて、いくつか事例とあわせてご紹介します。

1. 業務外の問題に目を向ける

私の部署では、毎日約5分間の朝会を開いています。 1人30秒くらいで、「今日取り掛かること」「参加するミーティング」「その他勤怠など含めて共有すべきこと」を共有します。

f:id:ryokatsuma:20151022094834j:plain

朝会を行うことでそれぞれの業務的な進捗を確認でき、また、内容について疑問に思ったこともすぐに確認、理解できる状態を作ることができていました。

一方で、業務と直接関係ないところでは、メンバーの中には気になることも出てきていました。

  • どうも浮かない顔をしているメンバーがいるが理由がわからない
  • 会話のトーンが低めなメンバーがいるが理由が分からない
  • ... etc

つまり、業務以外の点で、自分のメンバーへの理解が進んでいなかったことに気づいたわけです。

よもやま会

このような背景を踏まえ、私の部署では月に1度「よもやま会」という1-1の定期面談を開催しています。 名前の「よもやま会」は、その名の通り「四方山(よもやま)話をする」ところから採っています。^1

面談は、次のような方針で行っています。

  • 1人あたり30分
  • 正社員、パートスタッフ、学生アルバイトふくめ全員を対象

面談では、毎回ざっくばらんに「最近、どうですか?」のような軽い話から始めるようにして、仕事以外にも、体調、対人関係、こちらからの相談などを行っています。 この夏からはエンジニアリーダーの制度が再整備され、@hokacchaがエンジニアリーダーとして部署のエンジニアに対して、エンジニアリング観点での面談を行ってくれているので、私は特にエンジニアリング以外の面について面談で話をするようにしています。

このあたりの具体的な面談の進め方は@lchinも以前エントリーにまとめてくれています。

効果

面談では、あまりに生々しい話(!)がよく出てくるので、具体的に書くことは憚れますが、たとえば次のような取り組みにつながりました。結構細かい話が多いです。

  • 業務の進め方で他のメンバーに直接言いづらいことを抱えてるメンバーの対話の仲介
  • 自部署以外のコミュニケーションがなかなか広がらないメンバーが多かったのでチームランチの運営方針の工夫
  • モチベーションが上がってこないメンバーに対する業務内容の見直し

これらは、数字で効果が測れるものではありません。 一方で「まさかこんな悩みを抱えていたとは...」と気づける機会を増やすことができていることは、現状は自分にとって大きな効果になっています。 メンバーには、できるだけストレスを貯めない状態で仕事に取り組んでもらいたいと思っているので、よもやま会を通して仕事のアウトプットを高めることに繋がることを願っています。

2. 改善を支える

サービス開発では誰も正解が分からないものなので、じっくり時間をかけたものづくりが求められます。

私の部署でも、今期の初めに新規サービス開発とそれに伴うプロトタイプを多く作っていましたが、 プロジェクトに集中して開発に取り掛かるものの、なかなか思うような成果が上がらない日々が続いていました。あわせて、サービス開発に集中しすぎるがあまり、既存のプロダクトの磨きこみも進まず、結果として全く何も形にならないという課題が生まれてきました。

新規サービス開発はなかなか成果も出づらく、進みづらいことは理解していたものの、それに伴い既存のプロダクトまで影響が出て、全体としてモヤモヤ思う状況になっていました。 つまり、日々のサービス開発もしつつも、「既存のプロダクトの磨きこみをメンバーによって自発的に進められるサイクルを作りだしたい」と思うようになりました。

KAIZENの日

そこで、毎週金曜日は「KAIZEN(改善)の日」に設定し、全員強制的に最大丸1日かけて普段の開発以外に改善に時間を使うことをルールにしました。

ここでの改善は、プロダクトの改善以外にも、自分自身の改善もOKにしています。つまり、

  • Issue化されているような、既存のプロダクトの磨きこみ

のようなものはもちろん、

  • 定期的なプロジェクト振り返りとしてのデータ分析と社内ブログへのまとめ記事のPost
  • 話題になっているツール、ソフトウェアの使用レポート

のようなものもOKにしてます。 たとえばサービス開発ディレクターの場合は「SQLの勉強」などもOKにしていて、とにかく昨日より少しでも良い状態になることを目指しています。

f:id:ryokatsuma:20151022095244p:plain

また、改善を金曜に設定しているのは理由があります。 クックパッドでは金曜はもともと休日前ということで、大きめの変更を伴うデプロイは原則禁止で、デプロイ可能な時間も普段より3時間ほど短く設定されています。 つまり、デプロイがどんどん行われるような日では無い、自分たち自身も金曜日はリリースに間に合わせるための急ぎの開発も基本的には無い、ということから普段の通常業務以外のことを行う日に割り当てるに最適な日であるということが背景にあります。

効果

「KAIZENの日」はルール化して半年ほど経過していますが、結果としては停滞していたプロダクトの磨きこみは目に見えて進むようになってきました。これは、たまっていたIssueが消化されていくという一方で、逆にIssueに追加される速度も増えたという想定外の効果も生まれました(!)

また、普段自分たちでプロダクトを触る中で不具合や体験向上のネタを見つけた場合は「これはKAIZENネタに回しちゃおう」のような会話もでてくるようになりました。普段からプロダクトに対する課題を見つけやすくなり、通常業務の開発と日々の改善をそれぞれ並行して進められるようになってきたように感じています。

週に1日、20%の時間を改善に割り当ててしまうのは、リソース配分の観点で見ると本音では多い気もしていますが、メンバーの動きを見ているとこれくらいリソースを割いた方が動きやすそうに見えるので、この進め方はもうしばらく続けて様子を見ようと考えています。

3. 中長期的に物事を捉える視点を持つ

マネージャ業務の中で「年間計画を立てる」ということがあります。

年間計画は、1人でまとめあげることもできます。実際、今年の年間計画も自分1人で大筋をまとめていたのですが、来年度の計画を意識し始めた頃に、今年と同様に1人で完全にまとめるのでいいのか漠然と課題感を感じました。

自分が全く関わっていない上から降りてきた計画と、自分たちがまとめることに関わった計画を比較すると、実際に計画を遂行する際は、前者よりも後者の方が自分事として取り組めるはずです。 計画立案からメンバーを巻き込むことは、マネジメントの原理原則ともされていますが、この巻き込みについて自分はもっと工夫する余地があることを感じました。

視点を変えて考えると、普段の業務では今動いているプロジェクト、または、実際に自分が手を動かしている業務に意識は集中してしまいます。そこでは「5年後、食の領域ではどのような課題がありそうか」「5年後はどのような課題に取り組むことを想定すべきか」「そのために自分たちは来年はどうするべきか」のような目線を上げて物事を考えることは、メンバーはもちろん、マネジメントをしている自分自身も強く意識していないと忘れがちです。

未来を考える会

そこで、目線を上げる機会を強制的に作る「未来を考える会」という会を最近始めることにしました。

未来を考える会は、1ヶ月に2時間、メンバー全員で以下のような内容を議論します。

  • 5年後、日本の食はどうなっているか、いろんな方面から調査して1人づつ5分ほどのプレゼン
  • プレゼンを元に、将来はどのような課題がありそうか、自分たちはどのような課題に取り組むべきか?をディスカッション

効果

未来を考える会は、まだ始めたばかりのもので、つい先週に第二回目を開催したばかりです。どういう成果につながるか、または全く成果につながらないかはもうしばらく様子を見ないと見えてこなそうです。一方で、メンバーのプレゼンテーションを見ていると「食料自給率の変化」「労働人口の変化」など、割と似通った話が出てきやすい傾向があるのは興味深く感じています。

このように、自分で手を動かして考えた結果、(課題が適切なものかどうかはさておき)共通の課題意識をお互いに持てそうなことは、全体として良い流れができつつあるのかなと考えています。

まとめ

今回は私の部署を題材に、マネジメントの観点で部署内で感じた課題への取り組みを紹介しました。

これらの取り組みは、「よもやま会」「KAIZENの日」のように効果を実感しているものもあれば、「未来を考える会」などまだこれからどうなるか分からないものもあります。 今は効果を実感できているものも、組織変更や人事異動などで環境が変化すると、そこからまた新たな課題が発生することもあるでしょう。今後も現状にとらわれず、違和感を持ったものについては解決すべき課題を定義し、解決に努めようと考えています。

皆さんも身近なところに目を向けると、多かれ少なかれいろんな課題が転がっていると思います。 本エントリがその課題解決を導くヒントになると幸いです。


iOS クックパッドアプリのプッシュ通知まわりを改善している話

$
0
0

 こんにちは。検索・編成部の中村 (@_nkmrh) です。本エントリでは、iOS クックパッドアプリ(以下アプリ)に対して行った、プッシュ通知(以下通知)の改善策を紹介します。

はじめに

 アプリで通知を送る目的はなんでしょうか。様々な目的があると思いますが、大きくは次のようなものではないでしょうか。

  • アプリをインストール後、ほとんど利用しないユーザーに対してサービスを利用してもらう機会を提供したい

  • サービスをより身近なものとして利用してもらい、新しいファンを増やしたい

アプリの問題点

 アプリは、「特売情報」「今日のおすすめレシピ」「届いたつくれぽ」などの通知が受け取れ、通知の種類によって ON / OFF が選択ができます。

 そこまではいいのですが、1つ問題がありました。それは、(※1)iPhone 側の通知設定は OFF、(※2)アプリ側の通知設定が ON の場合、通知が届かない状態が発生するというものです。つまり、iPhone 側とアプリ側の通知設定が一致していない状態が発生してしまう問題がありました。

(※1) iPhone 側の通知設定 (設定.app >通知 から設定します)

f:id:nkmrh:20151023085154p:plain

(※2) アプリ側の通知設定 (iOS クックパッドアプリ >設定・その他 >通知・メール設定 から設定します)

f:id:nkmrh:20151023085158p:plain

実際の通知設定はどうなっているか

 そこで、ユーザーの通知設定の状態を調査しました。その結果、iPhone 側とアプリ側の設定が一致していないケースが、約半数も発生していました。(A と C にあたります)

設定 アプリ側 iPhone 側 比率
A ON OFF 45%
B ON ON 32%
C OFF ON 18%
D OFF OFF 5%

1番多い A 設定に着目すると、次のストーリーが考えられます。

  • アプリをインストール後、 iPhone 側の通知設定を OFF 、利用するうちにアプリ側の設定を ON にした

  • アプリをインストール後、 iPhone とアプリ側の両方の通知設定を ON 、利用するうちに iPhone 側の設定を OFF にした

前者の場合、ユーザーの意に反して通知が届いていない状況が発生していることになります。

改善策

 A 設定のユーザーに対し、iPhone 側を ON にするように促す(※3)アラートを表示するようにしました。表示のタイミングは、アプリアップデート後の初回起動時です。選択肢は「今はしない」「設定する」があり、後者を選択すると iPhone 側 の通知設定画面に遷移します。あわせて、通知設定の不一致が発生しないようにアプリの(※4)通知設定画面を改修しました。

(※3)アラート (画像を使い内容を伝わりやすくしています)

f:id:nkmrh:20151023085203p:plain

(※4)改修したアプリの通知設定画面 (iPhone 側が OFF の場合、上部に通知に関するイラストと「通知を設定する」ボタンを配置しました。これをタップすると iPhone 側の通知設定に遷移します。各スイッチは選択できない状態になり、設定の不一致を防ぎます。)

f:id:nkmrh:20151023085207p:plain

結果

 アラートを表示して選択肢がタップされた結果です。

選択肢 比率
「今はしない」 94%
「設定する」 6%

残念ながら「設定する」を選んだユーザーは少数です。比率としては少ないのですが、通知を受け取れていなかったユーザーが受け取れるようになりました。「今はしない」を選択したユーザーがほとんどという結果は、上記の後者のストーリー(通知を不要と判断し iPhone 側を OFF にしたユーザー)が多そうだ、と予想できます。通知設定画面を改修したことで、ユーザーの意に反して通知が届かない状態が発生しなくなりました。

次の改善策

 アラートを表示するタイミングをアップデート直後の初回起動時ではなく、もっと考慮されたタイミングにすると「設定する」が選択される可能性が上がることも考えられます。また、初めてアプリをインストールしたユーザーに(※5) iOS が出す通知の許諾を求める直前に、どのような通知なのか、メリットは何かを伝えるようにすることも検討しています。この iOS が出す通知許諾アラートは iPhone 側の通知設定を決定するものです。 iOS 8 以降は1度表示された後、次に表示できるタイミングがわからない仕様のため、このタイミングは重要です。( iOS 9 からアプリをインストールするたびに表示される仕様に変更されています)

(※5) iOS が出すプッシュ通知の許諾

f:id:nkmrh:20151023085210p:plain

おわりに

 アプリにとって通知は重要な機能です。特に頻繁に利用しないユーザーにリーチする方法は通知しかありません。うまく活用できれば大きな効果が期待できます。今までなかなか手を入れられなかった通知まわりですが、ユーザーに価値あるサービスを提供できるよう今後も改善していきたいと思います。

検索結果の疑問を解消するための基礎知識

$
0
0

こんにちは、買物情報事業部の荒引 (@a_bicky) です。 業務ではクックパッド特売情報のサーバーサイドや商品検索周りを担当しています。

突然ですが、とある商品検索の機能を使っていて次のようなことが起きたら不思議ですよね。

  • 「ねぎ」で検索したら「たまねぎ」がヒットした!
  • 「ドレッシング」で検索したのに「たまねぎドレッシング」がヒットしない!
  • 「豚 薄切り」で検索したのに「豚ロース肉薄切り」がヒットしない!
  • 「たまご」と「卵」の検索結果が違う!

今回は上記の疑問を解消するために検索の基礎的な内容について説明します。 以下、特売情報の商品を検索することを例に説明しますが、一般的な内容なので「商品」を「レシピ」等に読み替えることも可能です。

大量のページから目的のページを探すための索引

たいていの本の巻末には索引が載っていますよね。特定の内容が載っているページを探す場合、1ページ目から順番に探すととても時間がかかりますが、索引を使えば一瞬で所望のページを探し出すことができます。

商品検索機能を実現する場合も同様に、毎回対象となる全商品の内容を確認して目的の文字列があるかどうか探す方式と、索引を利用する方式があります。 ある程度の規模のサービスになってくると、毎回全商品の内容を確認していると遅いので、索引を利用するのが一般的です。

以降、索引を利用する前提でお話します。

どのようにして索引を作るか?

大量の商品がある場合、商品内容を 1 つ 1 つ人手で確認して索引語を選定することは無理なので、機械的に索引語を抽出して索引を作成する必要があります。 特売情報の各商品には商品名だけでなく商品に対するコメントなどもありますが、商品名のみから索引語を抽出することにします。

例えば、次の 4 つの商品から索引を作ることを考えます。

商品 ID 商品名
1 たまねぎ
2 たまねぎドレッシング
3 千葉産ねぎ
4 たまご千葉産

索引語としては単語か文字 N-gram がよく使われます。文字 N-gram とは連続する N 文字のことで、N = 2 の場合は 2-gram (bigram) です。

f:id:a_bicky:20151027155152p:plain

上記右側の表が索引です。いわゆる本の巻末の索引のようなデータですが、このようなデータ構造のことを転置インデックスと呼びます。

なお、クックパッド特売情報では索引語として固有表現を使用しています。商品名の中から「商品」、「産地」、「数量」等の固有表現を抽出し、検索に使用したい表現のみを索引語として採用しています。

f:id:a_bicky:20151027155330p:plain

詳細は「クックパッド特売情報 における自然言語処理 〜固有表現抽出を利用した検索システム〜」をご参照ください。

検索キーワードにヒットする商品を探す

検索キーワードにヒットする商品を探す際には、商品名から索引語を抽出した時と同じようにキーワードから索引語を抽出し、全ての索引語が含まれている商品を探します。

それでは、先ほど作成した索引から、「ねぎ」で検索する場合と「たまねぎドレッシング」で検索する場合を考えてみます。

まず、索引語として単語を使う場合です。

f:id:a_bicky:20151027155346p:plain

「ねぎ」で検索すると「千葉産ねぎ」がヒットし、「たまねぎドレッシング」で検索すると「たまねぎドレッシング」がヒットしていることがわかります。

次に、索引語として 2-gram を使う場合です。

f:id:a_bicky:20151027155357p:plain

「ねぎ」で検索すると「千葉産ねぎ」だけでなく「たまねぎ」や「たまねぎドレッシング」までヒットしてしまっています。

このように、索引語として N-gram を使うと意図しないものにヒットする可能性があるので、正確性(精度)を重視する場合は単語を索引語として使用することが好まれます。 一方で、単語を索引語として使用する場合は単語の分割結果に強く依存するため、網羅性(再現率)を重視する場合は N-gram が使われます。

単語分割の曖昧性

ここまで単語の分割に関しては適切に行われる前提で説明してきましたが、単語を適切に分割するのは難しいことです。例えば次のような問題があります。

  • 単語分割単位の曖昧性
  • 単語境界の曖昧性

単語分割単位の曖昧性は「たまねぎドレッシング」を「たまねぎ」と「ドレッシング」の 2 語として扱うか「たまねぎドレッシング」の 1 語として扱うかの曖昧性です。 単語境界の曖昧性は「ロース肉薄切り」を「ロース / 肉 / 薄切り」と「ロース / 肉薄 / 切り」のどちらで分割するかの曖昧性です。

もし「たまねぎドレッシング」を 1 語とした場合、「たまねぎ ドレッシング」で検索しても「たまねぎドレッシング」はヒットしません。

f:id:a_bicky:20151027155419p:plain

もし「ロース肉薄切り」を「ロース / 肉薄 / 切り」と分割した場合、「ロース 薄切り」で検索しても「ロース肉薄切り」はヒットしません。

f:id:a_bicky:20151027155433p:plain

このように、単語分割結果の違いが検索結果にも大きな影響を与えます。

単語分割単位の問題は絶対的な基準を設けることの難しい問題ですが、個人的には分割するかどうか迷ったら分割し、検索結果を表示する際の順位でより適切なものが上位に来るようにするのが良いと思っています。 単語を分割する技術の詳細について興味のある方は次のエントリーもご覧ください。

日本語形態素解析の初歩

名寄せの必要性

例えば「たまご」と「卵」は同じものを表しているので、商品検索の観点では多くの場合どちらで検索しても同じ結果になってほしいものです。 しかし、機械には「たまご」と「卵」が同じものであるという知識がないので全く別のものとして扱われてしまいます。 よって、表記の違い(表記揺れ)などを統一する作業が必要です。これを名寄せと呼びます。

f:id:a_bicky:20151027155449p:plain

名寄せが必要なパターンには次のようなものがあります。どのパターンに対応すべきかはサービスの特性によります。

  • 漢字と平仮名の違い
    • e.g. 「卵」と「たまご」
  • 送り仮名の違い
    • e.g. 「薄切」と「薄切り」
  • 別名
    • e.g. 「パクチー」と「コリアンダー」
  • 略称
    • e.g. 「産地直送」と「産直」

ここでちょっとした豆知識ですが、ほとんどの検索システムでは英数字の全角と半角、大文字と小文字をどちらかに統一していると思います。また、平仮名も片仮名に寄せることが多いです。 よって、多くのサービスの検索機能では、検索キーワードの表記を全角から半角に変えたり、平仮名から片仮名に変えたりしても検索結果は変わらないことでしょう。

まとめ

  • 高速に検索するためには索引が使われる
  • 検索結果は索引語として何を使うかに依存する
  • 索引語として単語を使う場合は単語分割の精度が問題になる
  • 所望の検索結果にするには名寄せが必要

というわけで、冒頭の検索結果の原因には次の可能性が考えられます。

  • 「ねぎ」で検索したら「たまねぎ」がヒットした!
    • → 索引語として 2-gram を使っているのかも
  • 「ドレッシング」で検索したのに「たまねぎドレッシング」がヒットしない!
    • → 索引語として単語を使っていて「たまねぎドレッシング」が 1 語になっているのかも
  • 「豚 薄切り」で検索したのに「豚ロース肉薄切り」がヒットしない!
    • → 索引語として単語を使っていて、分割がうまくいっていないのかも
  • 「たまご」と「卵」の検索結果が違う!
    • → 適切に名寄せされていないのかも

以上、検索の基礎的な内容について説明しました。今回の記事で検索結果に対する疑問が少しでも解消されれば幸いです。

電子工作に必要な道具達

$
0
0

最近電子工作を始めだしたクックパッドの舘野 ( id:secondlife, @hotchpotch )です。昨今 Arduino や Raspberry Pi を初めとしたボードの登場により、気軽に電子工作プログラミングができるようになり、プロトタイピングの敷居が非常に下がってきていますね。電子工作における “Hello World!” である LED を発光させる実装の “Lチカ” や、ブレッドボード上での簡単な電子機器やモジュールを組み合わせてプログラムから操作などを実際にしてみたことがある方も多いのでは無いでしょうか。

ただそこまでは非常に気軽にできるのですが、その先の電子工作へ進む時に溝があるのも確かです。ソフトウェアのみで完結する開発の場合、初心者でも解らないなら解らないなりに調べ、ソースコードをコピペして弄って理解してみたり、ライブラリを使っていくうちに徐々に進んでいくことが可能です。

f:id:secondlife:20151027104842j:plain

しかしながら、電子工作では初心者が開発を進める時に、以下の問題に何度も遭遇しがちでなかなか前に進みません。というか私が何度も遭遇しました…。

  • 手元に道具・パーツが無いので先に進められない
  • 知識が無いので、何を揃えるべきなのか解らない
  • パーツを購入できる場所が、東京なら秋葉原に行くか通販での購入となるため、時間と送料がかかる

何かを学ぶには、短い期間でトライアンドエラーを繰り返すことは効果的なのですが、電子工作はソフトウェアのみでは完結せずにリアルの道具だったりパーツが必要になってしまい、簡単には前に進めません。そのため、本エントリーでは、最初に発生しがちな道具・パーツ足りなり問題をできる限り回避しするために、私が電子工作を始めるにあたり必要だった道具や、買っておいたほうが良いパーツ、その他もろもろを紹介します。

道具

はんだこて

温度調整でき、セラミックヒーター(10秒ほどで使用可能になる)はんだこてが便利です。温度調節は半固定(ドライバーなどで回す、何かに引っかけて温度がうっかり変わることを防ぐ)の物と普通に手で回せるダイアル式がありますが、好みでどうぞ。

糸はんだ

いわゆるはんだなんですが、鉛フリーはんだと鉛入りの従来のはんだがあります。鉛入りのハンダの方が融点が低く溶けやすいため扱いやすいのですが、環境問題のため鉛フリーはんだへの切り替えが進んでいます。

鉛フリーハンダを使う場合、従来のハンダよりハンダごての温度を高めにセットする必要があります。

はんだこて台

はんだを置くための台です。重量がある台がはんだを置いたときに動きにくく好きです。こて先クリーナーがセットになってる物となってない物があるので、なってない場合はこて先クリーナを別途買う必要があります。

スッポン (簡易はんだ吸い取り機)

はんだをミスしたときに、そのはんだを再び温め直し吸い出すために使います。初めて使ってみたときはスッポンと吸い取れて感動的でした。

はんだ吸い取り線

こちらもはんだをミスしたときに、吸い取り線をかぶせ上から熱することで吸い取り線にはんだを吸収させます。基本スッポンですむならそちらでいいのですが、場所によっては吸い取り線の方が便利なことがあります。

フラックス

フラックスは糸ハンダ(中心部に入ってる)やはんだ吸い取り線にも含まれていますが、そのフラックスだけでは足りない場合や表面実装などに使います。ハケつきのフラックスが扱いやすいです。

テスター

電圧・抵抗値が測れるテスターでまずは困らないと思います。電流は電圧/抵抗 (オームの法則・ I = V / R ) で求められるので電流表示が無いテスターもありますが、電圧・抵抗をおのおのはかるのも面倒なこともあるので、電流表示に対応してるテスターを選ぶのも良いでしょう。

またアナログとデジタル表示のテスターがありますが、デジタル表示のテスターはオートレンジに対応している物が多いので、測定範囲を手動で設定しなくとも良いので便利ですね。

なおテスターは機種によって大きさが結構異なるので、小型で持ち運べる・収納しやすいタイプを選ぶのか、それなりの大きさで机の上に置きっ放しで使うのかを考えて選びましょう。

ピンセット

指先だけではできない細かい作業に。

ニッパー

いろいろな物を切るのに使います。

ラジオペンチ

いろいろな物を挟んだり曲げたりします。なおピンセットもニッパーもラジオペンチも100円ショップで売ってますが、刃物の切れ味や精度が違うので、良い物を買って置いた方が良いです。

ワイヤーストリッパー

ワイヤーのビニールなどを剥くときに利用します。はさみやカッター・ニッパーでもできますが、ワイヤーストリッパーがあると段違いに便利です。電子工作向きの細いワイヤーを剥くため、AWG 30 に対応してるワイヤーストリッパーを買いましょう。なお AWG は米国ワイヤゲージ規格 American Wire Gauge の略で、数値が大きければ大きいほど細いです。

ルーペ

ハンダ付けしたあとのチェックや、ICなどに書かれた型番などを見るのに使います。可変倍率でつかえるルーペ(私は3~15倍の物を使ってる)があると便利です。

精密ドライバー

普通のドライバー用としてはもちろん、半固定抵抗の調整などで多用します。

マスキングテープ

はんだ時の機器固定に使います。特に表面実装部品では必須です。Cerevo さんの TechBlog に解りやすい使い方が載ってます。

カッティングマット

机の上に敷いて作業します。これで汚したり傷つけても安心。

ヒートクリップ

はんだ付けの時、IC等を壊さないために熱を逃がしたいときに使います。アルミ製なのではんだがつきません。またケーブルや機器の固定用途としても使えます。

ワニ口クリップ

ヒートクリップ用途としても使えますし、それなりの大きさなので固定用途にも使えます。個人的にはアルミの薄いヒートクリップよりこちらを主に使ってます。50mmぐらいのサイズの物が便利です。

ミニバイス ( 簡易クランプ )

卓上で何かを挟んで固定したいときに役立ちます。またゴム製の吸盤で結構しっかりと机の上に固定することができます。こんなやつです。

ブレッドボード

ブレッドボードは長いタイプ(830穴)と普通のサイズのブレッドボード、小型のブレッドボードがおのおの5つぐらいあると便利です。ブレッドボードが足りないがために、プロトタイピングで電子回路組んだのに、ブレッドボードが足りないから全部ばらして…となると勿体ないので、そこそこの数を持っておきましょう。

ジャンプワイヤ

各種ピンとブレッドボードの配線に使います。オスオス・メスメス・メスオスの3タイプがあって、オスオスをブレッドボード配線で使うため基本一番よく使うのですが他の二つもそろえておきましょう。また長さも様々ですが、よく使うオスオスはいろいろな長さの物を用意しておくと便利ですね。

ワニクリップコード

両端がワニクリップになってるコードです。仮の配線などに重宝します。

USB シリアル変換アダプター

PC と電子機器の通信に使われるシリアル通信を行うためのアダプターです。定番の FTDI 社のチップが載ったアダプターを使うのが無難ですね。以下の商品は、USB micro-B でつなげ、かつ電圧を5V/3.3V どちらでも出力可能なので便利です。

パーツ

ピンヘッダ

ボードのピンを生やすのに多用するピンヘッダ。かなりよく使うため、一般的な 2.54mm * 一列のピンヘッダを5つぐらい買っておくと良いでしょう。

また以下のような詰め合わせパックを一つ持っておくと、だいたいの場合に対応できます。

ユニバーサル基板

2.54mm ピッチで縦横に部品をつけるための穴が空いているユニバーサル基板。用途に合わせて大きさを選ぶのですが、最初は基本的なCタイプ(72x47mm)の両面にスルーホールがあるものを買っておきましょう。

スズメッキ線

ユニバーサル基板の配線などに使うスズメッキ線。太さがいくつかあるんですが、私は 0.6mm が堅すぎず柔らかすぎず丁度良いのでその太さを使ってます。

耐熱ワイヤー線

こちらも配線で使うワイヤー線。周りがポリエチレンなどの絶縁体で覆われています。太さがいろいろあるのですが細め(AWG26~24程度)と太め(AWG20~18程度)を持っておくと便利です。

熱収縮チューブ

むき出しの配線の絶縁等に使います。たいていの熱収縮チューブはドライヤーの熱でも収縮します。

抵抗

必須品の抵抗。種類もいくつもあり、金属皮膜抵抗やセメント抵抗、カーボン抵抗等ありますが、一般的なカーボン抵抗を買いそろえておきましょう。といっても沢山の抵抗値があるので、以下のような様々な抵抗の詰め合わせパックを買っておきましょう。

可変抵抗

つまみを回すことで抵抗値を変えられる抵抗です。ボリュームやポテンショメータともよばれます。この抵抗も様々な抵抗値があり用途によって適切なレンジの可変抵抗を選ぶ必要があります。こちらも詰め合わせパックを一つ買っておくと便利です。以下は半固定抵抗の詰め合わせですね。

電解コンデンサ

いわゆるコンデンサです。こちらも用途によって適切な容量を選ぶ必要があるため、まずは詰め合わせパックを買っておきましょう。

セラミックコンデンサ

パスコン用途として多用するセラミックコンデンサです。こちらも(以下略)

水晶発振子

クロック作成のために必要な発振回路です。こちらも用途によって必要な周波数は変わってくるので詰め合わせパックを…と思ったら詰め合わせパックが無くなっているようなので、4,8,10,16,20,24MHzあたりを買っておきましょう。なお例えば Arduino Unoなどの積まれてるマイコン、ATmega328P を16MHzで動かすのに16MHzの外部水晶発振子が必要です。

トランジスタ

まずは日本で広く使われているトランジスタ、東芝の 2SC1815 (NPN) とそのコンプリメンタリ(型がNPN/PNPの対になってる物) の2SA1015 (PNP) を用意しておきましょう。

ダイオード

電流を一方向に流す整流用のダイオードを用意しておきましょう。他にもいくつかの種類のダイオードの詰め合わせを買っておくと便利でしょう。

レギュレータ

一定の電流・電圧を保つレギュレータ。電子機器でよく使う 5V と 3.3V に降圧するための物を用意しておきましょう。最初のうちは以下のようなコンデンサがセットになったパックを持っておくと困りません。

スイッチ

押しているときにオンになるタクトスイッチと、オンオフを切り替えるスイッチを用意しておきましょう。

こんなやつですね。

その他

以下はArduino等と組み合わせて初心者向けの電子工作で出てくることが多いパーツ類です。持っておくと初心者向けの工作を進めやすいと思います。無くても大丈夫。

  • LED
    • なんだかんだで結構よく使うので、赤緑青おのおの10本ぐらいもって置いても損は無いです。
  • CdSセル (光センサー)
    • 光の量によって抵抗値が変更するため、光センサーとして利用されます
  • 7セグメントLED と 8x8マトリクスLED
    • ダイナミック点灯、シフトレジスタの挙動を理解するのによく使われます
  • シフトレジスタ 74HC595
    • 上述の用途で良く利用されます
  • A/D コンバーター
    • Raspberry PI など A/D コンバータが無いデバイスを使ってる場合、A/D 変換するときに使います。MCP3208を1個ぐらい持っておくと便利ですね。
  • 各種センサー類
    • 温度や湿度、加速度といったセンサー類です。i2c のインターフェイスで使い慣れているセンサーを一つ持っておくと、とりあえずの挙動確認にも使えて便利です
  • 小型液晶

収納

様々なパーツ・道具を扱うため、アクセスしやすい収納は大事です。

いろいろな収納を試してみたのですが、小型のパーツや機器類は100円ショップの16分割(可変!)できる収納ボックスに入れ、その他の道具はこちらも100円ショップに置いてある 30cm x 8cm x 8cm のボックスにジャンルごとに物を入れ使ってます。透明なのも、中身が解りやすくて良いですね。

16分割できる小型パーツ入れ

f:id:secondlife:20151025204102j:plain

30cm x 8cm x 8cm のボックス

f:id:secondlife:20151025204151j:plain

便利グッズ

USB電源

PC とは別口で USB の電源ポートが卓上にあると、USB 機器への給電はもちろん、 5V のDC電源としても使え便利です。

USB をオンオフするだけのスイッチ

USB 接続で開発していると電源のオンオフを繰り返すため、そのたびそのたび USB ケーブルを抜き差しして、ということをしていたのですが、オンオフするスイッチがを使うとケーブルの抜き差しが必要なくなり、めっちゃ便利になりました。

f:id:secondlife:20151007222442j:plain

コンセントの電源オンオフスイッチ

こちらはACアダプタやハンダごての電源をオンオフするのに多用してます。電源タップにこの機能がついてない場合、単品の物を買っておくと良いでしょう。

f:id:secondlife:20151025213048j:plain

ネットショップ

私個人が電子機器やパーツを購入するのに使ってるネットショップは主にこの4つです。

  • 秋月電子通商
    • 細かなパーツ類は大概そろいます
  • スイッチサイエンス
    • ボードやモジュールを海外の有名どころから輸入して取り扱っていたり、自社でも便利なモジュール出していたり、今風の物を取り扱ってます
  • Amazon
    • 細かいパーツの単品は無いのですが、そこそこいろいろ置いてあります
  • ebay
    • 面白商品・格安商品が沢山あります。届くまでに時間がかかりますが、それでも良い物は ebay で買うことが多いです。amazon.co.jp で海外直送のため届くまで1~2週間かかるような電子機器・パーツはたいてい ebay に出品されており、さらに安く買えます

書籍

初心者が電子工作の知識を深める当たって参考になった書籍を紹介します。

  • 図解 つくる電子回路―正しい工具の使い方、うまく作るコツ (ブルーバックス)
    • 非安定マルチバイブレータ回路(この回路自体知らなくて大丈夫です)を、道具の解説から、ブレッドボード上へ仮実装し、最後にユニバーサル基板上にはんだやスズメッキ線などをつかい、どのように実装していくかをイラスト入りで丁寧に解説しています。基本的な道具の使い方を学べるでしょう。
  • 電子工作入門以前
    • "「入門以前」の電気・電子の基礎理論や電子回路・部品の知識,マイコンの基本,プログラミングの基本などを1冊にまとめました。"と書籍概要にあるとおり、基礎から基本的な部品の使い方、最後は時計を作を例に、どう時計を設計していくかを学ぶことができます。
  • Make: Electronics ―作ってわかる電気と電子回路の基礎
    • 電気の基礎から、実際に手を動かして試してみることをセットに学んでいきます。舌に9Vの電気を流したり、LEDを焼き切ってみたり、リレースイッチをカッターで分解してみたりと、大変楽しく学べます。

おわりに

本エントリーでは、私が電子工作を始めるに当たり必要になった道具やパーツ類を紹介しました。全部そろえるとそれなりにコストがかかるのですが、例えばスマートフォン開発するときに実機を買うコストを考えると、大して差は無いかなぁと思います。またアレが足りないやソレいるの?という物もあるかも知れませんが、私視点で必要になった物ということで…。

冒頭で触れたとおり、今は Arduino を初めとしたプログラミングしやすいボートが登場し、WiFiやBLE・その他無線ネットワークを通し電子機器とインターネットとの連携も非常にやりやすくなってきています。そんな電子機器の開発が本エントリーの道具をあらかじめそろえることで、歩みが早くなれば幸いです。

新規アプリを開発する際にディレクターとして心がけたこと

$
0
0

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

先日、iPhoneアプリみんなのお弁当 byクックパッドをリリースしました。みんなのお弁当では、作ったお弁当をクックパッドのレシピ付で記録したり、アプリ内で共有したりすることができます。 今回は、新規アプリを開発する際にディレクターとして心がけたことをご紹介したいと思います。

1.ユーザの気持ちを代弁できるぐらい理解する

まず企画を考えるにあたり、お弁当作りをしているユーザーの気持ちを知るために、日々のお弁当作りからはじめました。試しに数日作るのと、習慣として日々作るのでは、全く感じ方が変わるので、週4〜5日ペースでお弁当を作り続け、これまでに作ったお弁当は300個以上にのぼります。 お弁当を作っていると、たくさんの課題に遭遇しました。以下は一部抜粋です。

  • おかずがマンネリになる
  • 彩りのいい盛り付けって難しい
  • 寝坊したから10分で作りたい
  • カレーをお弁当に持っていきたい

しかし、たくさんの課題がある反面、お弁当作りには楽しさもあります。日々記録としてたまっていくお弁当の写真をSNSにUPすると、知らない人から反応をいただけて、それがモチベーションにつながることを知りました。 また、ほかの人のお弁当を参考に作り続けていくと、お弁当作りが上達し、お弁当作りが楽しみに変わるという経験をすることができました。

f:id:Teriyakky:20151027151543j:plain

2.サービスイメージを具体化する

「ほかの人のお弁当を参考にしながら、お弁当作りを楽しくするサービスを作りたい」頭の中でイメージすることまでは出来ましたが、そこからどうやってエンジニアやデザイナーに共有するか?という時に、遷移図とあわせて使ったのがProttというサービスです。

f:id:Teriyakky:20151027163542j:plain

Prottでは画像さえあれば、コーディング経験がない私でも簡単に動くモックを作ることができました。

Prottのメリット

  • ユーザーインタビューに早い段階でかけられる
  • 遷移の抜け漏れが発見しやすい
  • コーディング前に、端末でデザインチェックができる

具体例

お弁当を投稿する画面で「保存」「公開」というボタンを2つ並べてレイアウトしたところ、ユーザーテストで引っかかるユーザーが続出しました。 話を聞くと「保存して公開したいんだけど、その場合はどっちを押せばいいのか分からない」とのこと。

f:id:Teriyakky:20151027152800j:plain

インタビューの結果をふまえて、デザイナーと相談し、より押して欲しい「公開する」ボタンを上に配置し、その下に「保存」ボタンを置くことにしました。

f:id:Teriyakky:20151027153220j:plain

ディレクターの作業としては、確かに上乗せにはなりますが、コーディング後の手戻りなどは大幅に削減できたため、プロジェクト全体で考えると有効な取り組みだったと思います。

3.ユーザーの声をきちんと聞く

1〜2時間のユーザーインタビューで試作品の感想を聞くことはよくあると思いますし、今回のアプリ開発でも行いましたが、それに加えて、みんなのお弁当ではTestFlightという仕組みを利用しました。 TestFlightを使うと、指定した相手にだけアプリを配信することができます。今回は日常的にお弁当作りをしているユーザーの声が聞きたかったので、クックパッドのサービス内でお弁当を投稿している数十名の方にご協力いただき、1ヶ月間アプリを利用してもらいました。

f:id:Teriyakky:20151027153412j:plain

TestFlightのメリット

  • 複数名のユーザーに同時配信できる
  • クローズドな環境でテストできる
  • 長期間使ってこそ分かる、意見をいただける

具体例

いただいた意見の中に「日々投稿されるお弁当は参考になる。けれど毎回お弁当を拡大しないと、レシピが付いているかどうか分からない」といったものがありました。

f:id:Teriyakky:20151027153814j:plain

これは、ユーザーに『レシピ付きのお弁当にはサムネイルの下にマークが付く』という仕様が伝わっていなかったためです。そこで、アプリの初回起動時に以下の説明画面を追加することにしました。

f:id:Teriyakky:20151027153959j:plain

リリース前の貴重な1ヶ月。長期間のテストを実施するかどうかは、プロジェクトの内容や案件にもよると思います。しかし、テスト期間終了後に回答いただいたアンケートは、最後の作り込みの部分で、非常に有用なものとなったと個人的には思っています。

4.メンバーに企画の熱量を伝え続ける

そして最後は手法というよりも、気持ちの問題です。この企画を始めた当初から心がけているのが、メンバーに対して企画の熱量を意識して伝え続けることです。 デザイナー、エンジニアは他の案件を抱えていたりもするので、その中でプロジェクトへのモチベーションを持続してもらうために「お弁当ってこんなに素晴らしい!」というのをしつこいまでに伝え続けています。

具体例

  • お昼休みにお弁当を作ってきて食べよう!という「お弁当会」を負担にならない頻度で実施(もちろん男性も参加)
  • みんなのお弁当に関するポジティブな情報をこまめにグループチャットに共有
  • ネットで見つけたお弁当に関するおもしろ情報を都度共有

アプリに関する課題や問題点はミーティングなどで話し合われますが、良いこととなると意識しないとキャッチできないので、ここは私が意識的に情報収集するようにしています。

まとめ

新規プロジェクトの中でディレクターとして心がけたことをまとめてみました。はじめての新規アプリ開発で、正直右も左も分からない状況からスタートし、こういうツールがあるよというアドバイスを都度いただきながら、リリースにこぎつけました。 試行錯誤してみて便利だな、いいなと思ったことをまとめているので、このエントリが次の新しいサービスの参考になれれば幸いです。

iPhoneアプリ みんなのお弁当 byクックパッド

総合職で入社した新卒がクックパッドでエンジニアになるまで

$
0
0

はじめに

こんにちは、技術部の土谷です。

現在、私は2015年4月に総合職の新卒社員として入社したのですが、自ら希望してエンジニアに転向するために6ヶ月間の技術教育(トレーニング)を受けています。

この記事では、私が受けているトレーニングの内容に関してご紹介したいと思います。

なぜやっているのか

トレーニングのゴールは「クックパッドで一人前のエンジニアとして働ける技術力を身につける」ことです。

クックパッドでは、ディレクターや営業職であってもサービスに関わるスタッフは全て最低限の技術的な知識を持っているべきと考えられています。 そのため総合職の新入社員研修にも、クックパッドで働く上で最低限の技術的な知識を持つために技術研修が組み込まれています。

ただ、私自身、総合職で内定をもらったものの、「自分でものづくりがしたい」「それも、手を動かして納得のいくものをつくりたい」という思いを持っていて、その葛藤を人事に相談し、新入社員研修を受けながらエンジニアと面談する機会を設けてもらいました。 いろいろ相談した結果、やはり自分でものが作れるようになりたいと思い、トレーニングに挑戦することにしました。

なにをやっているのか

さて、実際に行われている研修の内容は、基本的には大きく

  • 開発実習
  • 書籍学習

の2種類があります。それぞれ具体的にどういうことに取り組んできたかご紹介します。

書籍学習(&コードリーディング)

書籍学習では、以下の技術書を読んで理解した内容を、トレーニング担当のエンジニアに説明します。

基本的に私がメインで説明しますが、理解の曖昧そうな箇所は事前に質問したり、説明の途中で質問します。

書籍学習をする目的は3つあると考えています。 1つ目は、「エンジニアが標準的に知っていることを学ぶ」ということ。そもそも私は知識や経験が圧倒的に足りないので、「標準的なWebアプリケーションに必要となる知識」を書籍学習を通じて体系的に学ぶ必要があったのです。

2つ目は、「技術の学び方を学ぶ」ということです。技術書を読む時、どこに注目すべきか、何を考えながら読むべきか。 初めて見るメソッドや単語であったら、その都度公式ドキュメントで調べてその挙動を理解するようにしました。

3つ目は、「人にきちんと説明できるようになる」ことです。まだまだできていない部分ではありますが、自分の理解したことを相手に伝えることで、理解が曖昧そうな点や、説明が漏れている箇所を突っ込んでもらいます。 読んだだけで終わらず、内容をレジュメにまとめて自分の言葉でエンジニアに説明することで、理解したと思っていた内容が実は間違っていた、とわかることが徐々に理解度の向上につながっています。

書籍学習の技術書のうちいくつかは高度な内容を扱っていたり、中級者を想定して書かれている本もあるので、そこは用意してもらった参考書籍を自分で読んで足りない部分を補います。 以下、自分が追加で読んだ書籍や文献です(一部は研修が始まる前に読み進めていたものもあります)。

コンピュータ科学: なぜプログラムは動くのか

Ruby:

Rails:

DB: SQL ゼロからはじめるデータベース操作

JavaScript: JavaScript本格入門

今は書籍学習が一段落したので、RubyやRails関連のオープンソースソフトウェアから中〜大規模のライブラリやgemのソースコードを読んで、実装内容を説明するということをしています。

開発実習

書籍学習と並行して、手を動かしてコードを書いています。

Rubyでアルゴリズム演習

最初は、UNIXコマンドと同等の機能を持つプログラムとアルゴリズムを実装することが課題でした。実装時はまずテストを書いて、それからテストを満たすようにプロダクトコードを実装するテスト駆動開発で行いました。

コマンド

  • wc, head, tail, expand

アルゴリズム

  • ヒープソート、二分探索木、ハノイの塔

RailsでWebアプリケーション開発

Rubyで上記のものを実装した後は、RailsでWebアプリケーションを開発しました。 最初の課題はタスク管理アプリでした。タスクが登録できて、タグを付けられる、というシンプルなものです。以前に読んでいたRails Tutorialは1対多のモデル構造だったので、多対多のモデルで実装しました。

次の課題は独自の認証機構をもった画像投稿SNSです。認証や画像投稿を扱うための便利なgemはありますが、今回はそれらは使わずに自分で実装してみました。

Webアプリケーション開発では、「標準的なWebアプリケーション開発のフローを学ぶ」ことと、「見積もりをたててスケジュールを修正できるようになる」という目的がありました。 前者に関しては、「プルリクエストを出してコードレビューをしてもらい、修正したものをマージする」という実際の業務で開発する際のフローと近い形で開発をすることができ、最後にはデプロイできるように設定しました。 コミットの粒度、コミットメッセージの書き方と細かい部分まで見ていただきました。

実務研修

現在はこの段階です。事業部に入り、実際の案件を担当させてもらっています。

いまの研修スタイルに関して

簡単な紹介にはなってしまいましたが、以上が私がこの4ヶ月クックパッドでやってきたことです。最後に、私が受けた(今も受けている)研修の内容や進め方に関して、特に良かったと思う点を述べたいと思います。

初期の段階で基礎的な分野を学べた

今まで、Rubyの本やRailsの本は読むことはあっても、入門コンピュータ科学のような本は読んだことがありませんでした。 概念的な説明が多く、コードもほとんど出てこなくて、書かれている内容に関して、自分の中でどういうこと(もの)かイメージしづらい分野でしたが、初期の段階で一通り基礎を学んでおくことで、結果的に後で役に立つケースが何度もでてきました。

例えば、実習で開発していたアプリを本番環境にデプロイする際に、変更したはずの修正が反映されていないことがわかった時。 「きちんとアプリのファイルが配置されているのになぜ?」と疑問に持ち、「もしかして昔のプロセスが生きてて、それで反映されてないのではないか」と当たりをつけて、プロセスを調べ、サーバを再起動したら、修正が反映することができました。自分の学んだことから構築して、ぱっと指摘された時もだいたい自分で解決できるようになりました。

周りのエンジニアに質問しながら、自分で短時間で問題を見つけて、解決することができた経験は、以前の自分より前進することができてる成功体験を得ることができました。

網羅的に知識の索引がわかる

一度、コンピュータとは?というところから初めて、言語処理系、DB、フロントエンド、のことを網羅的に学んだので、わからないことがあったり詰まった時、「ここに載ってるのではないか?」と当たりをつけたり、「あ、たしかこれ一回学んだ内容だから、あそこ見ればきっとわかる」という状態を少しずつ持つことができました。

一度本や文献で読んでいるので、2回目以降は同じ所を読んでも、一度目よりも理解も深まります。

一方、仮にいきなりRailsで動くアプリを書いていっていたとすると、動くものを作れるから楽しいと思いますが、なんとなく動くものが作れたので、わかった気になってしまうかもしれないのと、新しいことやわからないことにぶち当たった際に調べて理解するのにかかるコストが大きそうだなと思います。 これはどちらが正しいというものでは無いと思いますし、本人にとっての向き不向きによって当然左右されます。

私の場合、先述したように新卒社員の3ヶ月の研修中に何度かエンジニアと面談(技術書を読んだり、チュートリアルに取り組み、その内容を説明するというもの)をしました。 何回かエンジニアと面談を重ねていく中で、「コードは少し書くことはできるが、業務の中で新しい問題に直面した時にも応用できるための基礎力が必要だ」ということを言われました。それもあって、最初に社内のエンジニアが(独学もしくは大学など)どこかで学んだことがある基礎的な内容も含めたカリキュラムを組んでもらいました。

まとめ

今回は、私がチャレンジしているトレーニングの内容をご紹介しました。

まだトレーニング半ばではありますが、これからエンジニアリングを学ぼうと考えていたり、同じような境遇の方に少しでも役に立てれば幸いです。

Viewing all 726 articles
Browse latest View live