iTunes Connect から発行したプロモコードをカメラで読み取る

 こんにちは、iOS エンジニアの中村です。iOS クックパッドアプリの開発では、iTunes Connect から発行したプロモコードを使いリリース前に動作確認をしています。(2013年頃から審査通過後の動作確認がこの方法で出来るようになり、リリース時の緊張感が少し軽減されました)ここまではいいのですが、このコードは文字列で発行されるためキーボードで入力しなければなりません。毎回ランダムな文字列を手入力するのは避けたいです。そこで、このコードを iTunes Card に記載されているコードのようにカメラで読み取れる画像を生成するツール icccig を作りました。この記事では、このツールの製作過程をご紹介します。

  • App Store.app > おすすめ > コードを使う の画面(カメラで読み取るという選択肢がある)

f:id:nkmrh:20170406151004p:plain:w300

フォント

 テキストエディタに Helvetica Nune でコードを書き、その周りに枠線を付けたものが認識できるか試しましたが、枠線は認識してもコードを認識することはできませんでした。そこで、コードに使われているフォントを WhatTheFont で探しましたが、これはというものは見つかりませんでした。探している過程で「光学式文字認識のための字形(英数字)」というものを知り OCR-B がとてもよく似ていたのですが、これも認識することはできませんでした。

  • OCR-B で書いたコード

f:id:nkmrh:20170406151015j:plain:w300

  • iTunes Card のコード

f:id:nkmrh:20170323132333j:plain:w300

 フォント探しは諦め、Google の画像検索で iTunes Card のコードが沢山写っている画像を見つけることができたので、この画像からフォントを作るアプローチに変更しました。しかしよく見るとこの画像には足りない文字がありました。例えば 1 2 B Z 等々。ここで iTunes Connect からコードを100個発行して出現文字を確認したところ、そこで使われていたのは以下の20文字でした。

iTunes Connect から得られたコードの文字の種類
数字 3, 4, 6, 7, 9
英字 A, E, F, H, J, K, L, M, N, P, R, T, W, X, Y

 お店で売られている iTunes Card に使われているコードは16桁なのに対して、iTunes Connect から得られるのは12桁です。出現文字が少ないのは恐らくこのためですが、今回は iTunes Connect から得られるものに対応できればいいので足りない文字は無視して進めました。Sketch で文字をスライスした画像を並べて試したところ認識できました。これでフォントは完成です。

スクリプト

 スクリプトは2つ用意しました。1つは文字列からカメラで読み取れる画像を生成するスクリプトです。これは上記で作成したフォントでコードを描き、その周りに枠線を付けた画像を生成します。枠線の太さと文字からの距離も重要な点です。枠線をうまく認識できるように微調整してこのスクリプトを完成させました。

  • icccig で生成した画像

f:id:nkmrh:20170406151023j:plain:w300

 2つ目は生成した画像を社内画像共有サーバにアップロードし、そのリンクのリストを生成するスクリプトを用意しました。issue にこのリストをコピペしておけば、開発関係者がコードを手入力せずに済みます。

最後に

 上記のフォントとコード画像を生成するスクリプトは こちら のリポジトリにありますのでよかったら試してみてください。実際の運用で試したところ、確認項目の多いディレクターやテストエンジニアに好評でした。このツールで一旦解決できましたが、一方で Xcode プラグインとして作った方が良かったかもしれませんし、他にもっといいツールや方法があるかもしれません。何かいいアイデアをお持ちの方は是非教えていただきたいです。

利用者数の増加に伴う、ユーザー行動の変化と対応について

こんにちは。サービス開発部ディレクターの新里です。

iPhoneアプリ「みんなのお弁当 byクックパッド」を担当しています。本アプリでは、自分の作ったお弁当をクックパッドのレシピ付で記録したり、アプリ内で共有したりすることができます。リリースから約1年半経ち、35万ダウンロードを超えました。今回は、サービスディレクターとして、アプリの利用者数の増加に伴うユーザーの行動の変化について、サービス開発側で実施した対応と合わせて、まとめようと思います。

1.投稿数が増加

アプリのダウンロード数の増加に比例して、お弁当の投稿数*1も増加しました。大半の投稿はガイドラインに沿ったものですが、そうではない投稿も中にはあります。

ガイドラインに沿わない投稿の増加

みんなのお弁当のガイドラインでは『外出先に持っていく食べ物のこと』をお弁当と定義しており、このガイドラインに則っていないものに関しては、投稿ユーザー宛にメールをお送りし、投稿内容について確認をお願いしています。この目視で公開されたものをチェックし、メールをお送りする運用は、リリース当初から変わらずに継続しています。基本的には、メールで連絡させて頂くと、ガイドラインの要旨をご理解頂き、誤った投稿に関しては見直していただけます。

追加で実施した対応

しかし、新しい事案として、日本語以外の言語で投稿されている方がガイドラインに則っていないケースが複数回発生しました。考えられる要因としては以下の2つです。

  • 日本語のガイドラインが読めない
  • 日本の文化に馴染みがなく、お弁当がどういうものかという認識が曖昧

日本国内向けのアプリなので、ガイドラインの英訳を掲載するのは見送り、日本語の読解が出来ていない可能性のあるユーザーに対しては個別に、お弁当の定義や投稿内容のご確認について記載した英文メールをお送りするようにしました。少し地道な方法ではありますが、英文メールをお送りした方々において、ガイドラインに則っていない投稿は収束したので、効果はあったと言えそうです。

2.コミュニケーションが活性化

ブックマーク機能の使われ方が変化

アプリ内で公開されたお弁当には「参考になった」ボタンを押すことができます。これは、レシピや盛り付け方法が参考になったお弁当をブックマークして、後日見返しやすくするための機能として実装したものです。 しかし、利用者数が増えることでこのボタンがブックマークとしてではなく、「いいね!」のようなライトフィードバックの意図で使われることが増えました。1人あたりの「参考になった」タップ数の推移はGoogle Analyticsのイベント数から確認しています。 f:id:Teriyakky:20170410101534j:plain またmixpanelも併用しており、こちらはユーザーが想定した通りの動きをしているか、想定外の離脱などはしていないかなどを確認するのに使っています。

追加対応は実施せず

ブックマーク機能の使われ方が変化しつつありますが、さらにコミュニケーションを活性化するための機能の追加などは行っていません。あくまで本アプリの主目的は「作ったお弁当をレシピ付きで記録・共有」であり、コミュニケーションを活性化するための機能追加に関しては、本質からずれると判断したためです。 (ただし、ブックマークの側面が弱くなっていることで不便さが出てきたり、改修が必要と判断した場合は、今後対応する可能性があります。)

3.投稿後の反響が大きくなった

トップ画面掲載への反応が変化

アプリのトップ画面には毎日お弁当を1つ運営側でピックアップして掲載しています。これは、アプリを開いた時に更新性を感じさせることと、漠然とお弁当を作ろうと思っている方へのリコメンドの意図がありました。 それが時間の経過と共に、「ピックアップに掲載されるようなお弁当を作りたい」という憧れの対象になり、掲載されることが「日々のお弁当作りのモチベーションになる」という声を多く頂くようになりました。

追加で実施した対応

トップ画面への掲載がお弁当作りのモチベーションになる、嬉しい事としてユーザーが捉えるようになってきたので、ピックアップに選出されたユーザー宛にメールをお送りするようにしました。これによって、ピックアップ掲載という嬉しい出来事をユーザーが見逃さないようにしています。

4.クックパッド本体の活用事例が増加

レシピが紐付いたお弁当の投稿が増加

アプリリリース当初は、レシピが紐付いていないお弁当の投稿(写真だけなど)が大半でしたが、利用者が増え「日々のお弁当を共有する場」として定着してくると、お弁当の写真と共に参照したクックパッドのレシピを投稿する方も増えてきました。

  • 美味しかったレシピを他の人にも教えたい
  • 憧れのお弁当のレシピを作ったから報告したい
  • 自分のオリジナルレシピを見て欲しい

レシピを紐付けたお弁当を投稿する思いは様々あるようですが、こういう状況の中で、クックパッド本体サービスへの関わり方にも少しずつ変化が出てきました。 お弁当を公開しているユーザーのうち、つくれぽを送ったことがある人の割合は、リリース当初の25%前後から40%前後へと増加、レシピ投稿に関しては19%前後から30%前後へと、それぞれ増加しており、投稿することに積極的になってきている状況が伺えます。

追加で実施した対応

レシピやつくれぽを投稿することは、お弁当の記録の一環であり、モチベーションにもつながるため、よりスムーズに投稿できるように、みんなのお弁当アプリから直接つくれぽを送れるようにしました。 f:id:Teriyakky:20170410121613j:plain (右上・自分のお弁当詳細画面に導線を設置)

機能リリース後は、今までつくれぽを送ったことがなかったユーザーがつくれぽを送るようになったりと、順調に利用されています。

まとめ

人が集まると、機能自体は変わっていなくても、その使われ方や位置づけが変わったり、新たな課題が生まれたりします。 いち早く変化を察知するために、まずは自分自身で日々サービスを使い、変化に気づくことが大切です。その後、必要に応じて解析ツールを使って分析を行い、数値の裏付けを取って施策を検討するのですが、全てのニーズに応えようとはせず、コンセプトやペルソナをもとに実施する対応についてはきちんと優先順位付けし、時に対応を見送ることも重要です。

クックパッドでは、このような感じで一緒にサービスを成長させていきたい人を募集中です。ご興味のある方は、是非とも覗いてみてください。

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

*1:ユーザーは、お弁当を投稿する際に公開か非公開かを選択することができ、投稿数はその合計です。

ユーザー基盤を作り直しながらRailsでのサービス層に向き合う

こんにちは。パートナーアライアンス部の諸橋 (@moro) です。

突然ですが、わたしはいまクックパッドの「ユーザー基盤」を再構築しようとしています。 一口に「ユーザー基盤の再構築」といっても、そのゴールが何を指すかは(わたし自身にとってもまだ)漠然としており、固定されたゴールは見いだせていません。しかし後述するように、いくつかの問題は明確な形を取っています。言い換えると、それら明確な問題と向き合いながら『柔軟でいい感じのユーザー基盤を目指す』というのがこの再構築プロジェクトの目的です。

その第一歩目として、ユーザー登録部分を現状のクックパッド本体とは別の小さなRailsアプリケーションとして実装を進め、つい先日、一部の限定された利用者の方に向けて公開することができました。 今後も様子を見ながら公開範囲を拡大していく予定です。

再構築の背景

ではその「明確な問題」とはなんでしょうか。

最大のものは、ユーザー登録のあり方が時勢にそぐわなくなってきているのではないかという懸念です。たとえばクックパッドのアカウントはメールアドレスとパスワードを登録していただくのが基本になっています。しかし世の中のトレンドとしてEメール自体の利用シーンが減るに連れ、それを必須とするサービスまで使いづらいものになるのではないかという危機感がありました。そのため、ユーザー基盤に大きく手を入れたいというニーズがありました。

もう一つは、クックパッドのコードベースがかなり巨大化・複雑化していることです(参考)。巨大で複雑なコードベースの上では、上記のユーザー登録のあり方の抜本的に変更することは難しいですし、日常の開発においてもある機能の修正が他の機能のバグのきっかけになったり、自動テストの実行時間も長くなったりするなどの問題がでています。そのため各機能を別のサービスとして実装した上で連携させるマイクロサービス化を進めています。その方向性を踏まえ、今回のユーザー基盤の再構築でも、ユーザー登録やログインといった機能を別サービスとして実装することにしました。

サービス層への興味

また、せっかく新しくRailsアプリケーションを作るのだから「Railsにおけるサービス層の実現」という技術的関心にも向き合うことにしました。

Railsは、DBのテーブルをそのまま読み書きするような単純なアプリケーションを作ることは簡単にできますが、より複雑なアプリケーションではそのぶん慎重な設計が必要になるため、Trailblazerのような複雑なWebアプリケーション構築を支援するライブラリが注目を集めています。

私自身もこのサービス層の実現にはおおいに興味があるいっぽう、今回はまだコード規模が小さいためにTrailblazerそれ自体の導入は先送りし、Rails上でフォームオブジェクトパターンによるサービス層の実現に挑戦することにしました。

この記事では、そういったビジネス上の背景と技術的興味を踏まえてアプリケーションを作る過程の試行/思考を紹介します。

最小限の機能から少しずつ進める

再構築を決意したとは言え、サービスにおいて「ユーザー」というのはとても大事かつ複雑なドメインモデルです。

実際に、現クックパッドのActiveRecordモデルクラス(以下ARモデル)Userはファイル中で定義されるメソッドが328個、includeしているモジュールが14個、has_(one|many)合わせて175個の関連が定義されている3,000行を超えるクラスです。このすべてを一気に新しいユーザー基盤(以下、新基盤)に置き換えようとしても、影響範囲が大きすぎてリリースにたどりつけないのではないかという懸念がありました。

そこで今回は最小限の機能をリリースすることを重視し、従来より負担の少ないユーザー登録フローを実現するアプリケーションを開発しました。

現方式では(1-1)ファーストビューで必要な情報をすべて入力したあとで、(1-2)到達確認のためにメールを送信し、(1-3)メール中のURLにアクセスすると登録が完了するという流れで登録します。 対して新基盤では、(2-1)ファーストビューではメールアドレスのみ入力し、(2-2)すぐに到達確認メールを送信し、(2-3)メール中のURLにアクセスしたあとに残りの情報を入力する、という流れです。ファーストビューでの入力項目が減る分、ユーザー登録する際の負担が減るであろうと見込んでいます。

また、新基盤のデータの持ち方やインフラ構造についても、現行クックパッドからの漸次的な改善を進めていくために、以下のような方針を取りました。

  • 新基盤では、現クックパッドとDBを共有する。
    • 新しいフローでのみ必要なデータは新規のDBに、現クックパッドで使うデータは現DBに書き出す。
    • 現DBに書く場合でも、ビジネスロジックは新たに書き直す。
  • 今後もインクリメンタルな移行を進めるため、現サービス https://cookpad.com/ 以下の特定のパスをリバースプロキシで分岐して新基盤と統合する。
  • 技術要素は社内推奨かつ新しいものを採用する。
    • 現クックパッドからの移行がしやすく、開発者の熟練度も高いRuby on Railsを採用する。
    • 技術的な進歩にキャッチアップするため、Ruby 2.4 + Rails 5.1.rc1 + Webpack + React などで開発する。
    • ECS上にアプリケーションをデプロイするためのHakoなど、社内の標準的なインフラ構成に乗せる。

このように「最小限の機能で、ちゃんと動くソフトウェア」をリリースすることを優先した結果、ユーザー登録のみを行うアプリケーションとして最初のリリースを迎えられました。

アプリケーションを少しずつ設計する

さて、再構築の方針やファーストリリースのスコープは決めたものの、それだけではアプリケーションはできません。 今後の拡張に対して柔軟に対応でき、かつ複雑だったり無理のある構造にしないため、慎重にアプリケーションを設計しました。その過程も紹介します。

最初の一歩: メールアドレスを登録する

出来上がった新基盤でのユーザー登録フローでは、利用者が目にする最初の画面は下記のようなものになります。

f:id:moro:20170406152442p:plain

これをみてわかるように、入力フォームはメールアドレス一つだけというシンプルなものにしました。この画面からメールアドレスを登録すると、メールアドレス確認用のリンクが記載されるメールが送られます。利用者がそれをクリックすることで、メールアドレスの到達確認がなされたとみなします。

そのため最初は、「メールアドレス登録」を表すARモデルを作り、メールアドレスと確認用URLに付与する予測が困難なトークン文字列(以下、確認URL用トークン)を作りました。カラム定義は次のようにしました。

  • 登録対象のメールアドレス(文字列)
  • 確認URL用トークン(文字列)
  • 確認URL用トークンの有効期限(タイムスタンプ)
  • 登録完了日(タイムスタンプ, 初期値null)
  • Railsタイムスタンプ(created_at, updated_at)

このデータを永続化するとともに、入力されたメールアドレスに対し、確認用メールを送ります。

次の一歩: 確認URLから登録完了する

確認用URLからアクセスする画面はこのようなものになります。

f:id:moro:20170406152434p:plain

ここでパスワードと生年月日を入力してもらい、メールマガジンなどの設定を見直してもらうと登録が完了します。

一見すると単純な画面ではあるのですが、ここから登録すると下記のような、たくさんの処理が走ります。

  • 現DBのユーザーテーブルにデータを作る。
    • 合わせて、現DBのユーザーのデータ一式を作る。それらは複数のテーブルに分割されている。
  • 新基盤のDBでも、そのメールアドレス登録を使用済みとして更新する。
  • 登録完了のメールを送信する。
  • 他システムにユーザー登録完了を知らせるためのAPIを叩く。

そこで、この処理ではARモデルを直接読み書きするのではなく、フォームオブジェクトを導入し、「ユーザー登録完了フォーム」として抽出しました。

これにより、以下の観点で「無理のないアプリケーションコード」にできたと思っています。

  • フォームの入力値をrequestparamsハッシュから取り出したあとの、Plain OldなRubyオブジェクトにできた。そのため、モデルのユニットテストとして一連の処理をテストできた。
  • 複数ARモデルのオブジェクトを一気に保存する場合に、関連先オブジェクトの内部構造と密結合してしまいやすいaccept_nested_attributesを避け、ネストのないActiveModelモデル内にカプセル化できた。

これで主要な入力は出来てきましたが、前のメールアドレスの登録画面や、それを永続化するテーブル構造に気になるところがでてきました。

メールアドレス登録画面のフォームオブジェクト化

前述のように、メールアドレス登録フォームでは、入力されたメールアドレスと確認URL用トークン、有効期限を1つのテーブルに永続化していました。これは初手としては手頃な落とし所ではありましたが、いくつか気になる点がでてきました。

まず、再構築の目的には、近い将来にメールアドレス以外でのユーザー登録できるようにしたいというものもありました。そうすると「利用者がユーザー登録しようとしたこと」そのものと「そこで登録してくれたメールアドレス」は分割すべきように見えます。 また、登録完了した時点で現DBに書き込まれるユーザーも追跡可能にしておきたくなります。

それらを考慮し、テーブル構造を以下のようにしました。

  • ユーザー登録をしようとしたことテーブル

    • 確認URL用トークンの有効期限(タイムスタンプ)
      • メールではない「新規登録」であっても、有効期限は存在するはずという仮定で
    • 完了後現DBに作成されたユーザーのID(数値, 初期値null)
    • 登録完了日(タイムスタンプ, 初期値null)
    • Railsタイムスタンプ(created_at, updated_at)
  • メールアドレス登録テーブル

    • ユーザー登録したことテーブルへの外部キー
    • 登録対象のメールアドレス(文字列)
    • 確認URL用トークン(文字列)
    • Railsタイムスタンプ(created_at, updated_at)

なお「現DBのユーザーID」と「登録完了日」をのみを抽出して「登録完了したこと」テーブルに分けるアイディアもあり、個人的にもそのほうが好みではありました。しかし、そこを分割する利点がまだ少ないこと、将来的にそうしたくなった場合でも無損失に分割できそうなことなどを考慮して、いまの2テーブルの構成にしています。

さて、このようにテーブルを分割するとRailsからも複数のARモデルを扱うことになります。結果として、メールアドレス登録画面にもフォームオブジェクトを導入することでスッキリ書けるようになりました。

まとめ

このように、機能がまだ少ない簡単なWebアプリケーションであっても、アプリケーション設計で考えられることはたくさんありました。今後も継続的に開発を進めながら設計を洗練させていきたいと思っていますし、あるいはTrailblazerの導入なども考えていくつもりです。

またこれ以外にも、Reactを使ったクライアントサイド設計や、その前提となるサーバ側/JS側での責務の切り分け、自動テスト戦略などなど、あらためて言語化してみると様々なレベルで設計判断が必要となりました。また別の機会に、それぞれ紹介できればいいなと思います。 さらに、今回の「最小機能でリリースする」というスコープ決定や、今後もやりたいことリストから取り組む順番づけなど、ソフトウェア開発は大小の判断の連続なのだなあというのをあらためて実感します。

こういった判断は、それぞれの現場によって、やりたいことも前提も既存コードも大きく違うため一概には言えないと思いますが、今回の話が一つの例として参考になれば嬉しく思います。