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

Amazon Redshiftへ継続的にデータをロードする際に気をつけること

$
0
0

こんにちは、インフラ部データ基盤グループの小玉です。

データ基盤グループでは、Amazon Redshift(以下、Redshift)へ継続的にデータをロードする仕組みを、約半年に渡り構築・運用してきました。この記事では、その中で学んだことを共有させて頂きます。

弊社では情報系システムの一部に、AWSが提供するRedshiftという分散データベースを利用しています。情報系システムとは、データ分析を主な用途とするシステムのことです。なかでもRedshiftはSQLを使った大量データの高速な分析に最適化されているため、DWH(データウェアハウス)としての利用に適しています。

DWHの構築に必要なタスクとしては、データソースの特定、モデリング、データの抽出・変換・ロード(ETL)、クエリツールやBIツール導入、パフォーマンス・チューニング、メタデータの管理、バックアップ・リストアなど、がありますが、今回は「データの抽出・変換・ロード(ETL)」に関する話になります。

DWHへの一般的なデータロード

DWHへのデータのロードは、日ごと、週ごと、月ごとなどの決まったタイミングで、DWHユーザの分析クエリ(以下、ユーザクエリ)の流れない深夜から早朝に、バッチ処理でドカっと行うことが多いです。例えば、毎日早朝に最新のユーザマスタをロードしたり、月初に前月分の売上データをロードする、といった具合です。

このような処理が一般的である理由としては、「日中はユーザクエリのためにリソースを空けたい」、「業務が月末締めなのでそれに合わせたい」、「DWH用データベースは細かいINSERTが苦手なことが多い」といったものが挙げられます。

とはいえ、スピード命のこの業界では、「今朝デプロイした機能のログを午後には見たい」、「10分前のログを元に分析を実施し、その結果を本番システムに反映したい」といった要望も珍しくありません。

Redshiftへ継続的にデータをロードする仕組み

そこで弊社では、一部のログデータ(約1万レコード/秒)について、数分から数時間間隔でRedshiftへ継続的にロードする仕組みを構築し、運用を始めています。その仕組はさっくり以下の通りです。

  • アプリケーション(ウェブサーバ等)がFluentdへログを送る
  • FluendがS3にログをまとめて書き込む
  • 独自ロードシステムが...
    • S3上のログを読み込み、クレンジングや変換を行い、再びS3へ書き込む
    • S3上のクレンジング済ログをRedshiftへロード(COPY)する

このような仕組みを構築、運用する上で気をつけるべきこと(苦労したこと)の一つは、「ロード処理のリソース消費を最小限に抑えること」です。

なぜロード処理のリソース消費を最小限に抑える必要があるのか?

継続的にデータをロードするということは、日中、ユーザクエリが実行されている最中にデータのロードが行われるということです。この場合、ユーザクエリとロード処理は、CPUやI/Oなどのリソースを分け合って実行されることになります。よって、ユーザクエリへの影響を出来るだけ小さくするために、ロード処理のリソース消費は最小限に抑えるべきです。

それを踏まえ、我々は以下のような方針で上記のロードシステムを構築・運用しています。

  • ELTではなくETLを選択する
  • ロードシステムのバックエンドDBは分ける
  • 基本的なロードの最適化を怠らない

ELTではなくETLを選択する

DWHへデータをロードする工程は、一般的にETLと呼ばれています。ETLとは、ソースシステムからデータを抽出し(Extract)、加工・変換をした上で(Transform)・ロードする(Load)する処理のことです。ただし、最近ではELT、つまりデータベースへロードした後に加工・変換をする流れも多く見られるようになってきており、ETLとELTのどちらを選択するかはDWH構築におけるデザイン・チョイスの一つになっています。

見出しにもある通り、Redshiftへ継続的にデータをロードする場合は、ELTではなくETL、つまりロード前に加工・変換処理を実施する方式を検討するべきです。なぜならETLの場合は、加工・変換処理のためのINSERT/SELECTをRedshift上で実行する必要が無く、リソース消費を抑えることが出来るからです。

弊社では独自システムを使い、Redshiftの外でデータの加工・変換を済ませた後で、ロードしています。このような仕組みを構築することで、ロード処理に関わるリソース消費を最小限に抑えることができました。また、このレイヤーを導入することで、SQLでは難しい加工・変換処理を行うこともできるようになりました。

ロードシステムのバックエンドDBは分ける

読み書きの多いウェブアプリケーションのバックエンドDBとしてRedshiftを使う方は居ないと思います。しかし、上記の独自ロードシステムのような比較的小さなシステムであれば、そのバックエンドDBとしてRedshiftを使っても良いと思うかもしれません。

実は、我々がそうでしたが、今はRDS上のPostgresSQLに移行しています。移行の理由は、ロードシステムからの書き込みの負荷が、無視できないレベルだったためです。

Redshiftを含むDWH用途の分散データベースは、MySQLやPostgresSQLとは異なったデータアクセスパスを持ち、またロックやトランザクションも分散システム用のアルゴリズムで実装されています。そのため、ロードシステムに限らず、アプリケーションのバックエンドDBとして利用する場合は、その仕組みを十分に理解しておく必要があります。

基本的なロードの最適化を怠らない

上記の独自ロードシステムには、まだ実装出来ていない部分もありますが、公式ドキュメントに記載されているような、基本的なロードの最適化も忘れずに行う必要があります。なかでも重要なのは「COPYの対象ファイル数を、Redshiftのスライス数の倍数にすること」です。

スライスとはRedshiftのノード上で処理の実行を担うプロセスのことで、専有のディスク、CPU、メモリを割り当てられています。ロードを含む多くの処理はスライス単位で並列実行されるため、Redshiftの能力を最大限引き出すには、全てのスライスに均等に負荷をかけることがとても重要です。

例えば、システム全体で4スライスある場合、500MBのファイルを2つロードするより、250MBのファイルを4つロードするほうが高速です。なぜなら、全てのスライスがロード処理を実行することになり、リソースを無駄なく活用出来るからです。なおこの場合、ファイルを分割するだけでなく、そのサイズを出来るだけ揃えることも大切です。

まとめ

以上、この記事では、Redshiftに継続的にデータをロードする際に気をつけるべきこととして、「ロード処理のリソース消費を最小限に抑えること」を挙げ、弊社での取組みについて紹介をさせて頂きました。

また、クックパッドでは、一緒にデータ基盤を作っていただけるエンジニアを募集しています。ご興味のある方は是非遊びにいらしてください。


MySQLを1〜2時間でスケールアウトする

$
0
0

最近、Elastic BeanstalkやECSと戦っているSREチームの菅原です。 P5をやりたいのにPS3もPS4も持っていないので指をくわえて羨ましがっている毎日です。

この記事では、突然のアクセス増に備えるために、MySQLのスレーブを1〜2時間でスケールアウトできるようにした話を書きます。

MySQL on EC2

クックパッドは周知の通りAWSを利用していますが、主要なデーターベースについてはAmazon RDSではなくMySQL on EC2を使っています。 これは以下のような理由によるものです。

  • 歴史的な経緯: AWS移行当時、RDSが無かった。また、移行後もしばらくはTritonnを使っていたため、RDSを使うことができなかった
  • オンラインメンテナンスの実現: VPCルートテーブルを使った仮想IPとMHA for MySQLを使ってダウンタイムゼロのマスタDBの切り替えを実現しています。 RDSによるDNSベースの切り替えでは、どうしてもダウンタイムが発生してしまいます。
  • 複雑なレプリケーション構成: 主要DBは内外様々なサービスが利用しているため、レプリケーションの構成が複雑になっています。あるスレーブでは特定のテーブルをレプリケーションしていなかったり、またあるスレーブでは別のDBと同居していたりなど。RDSでこのような複雑な構成に対応することは難しいです

スケールアウトと暖機

TV放映など突発的なアクセス増があった場合、DBの負荷も増大するためスケールアウトが必要になることがあります。クックパッドの場合、サービスの特性としてリードのアクセスが圧倒的に多いため、DBをスケールアウトする場合には主にMySQLのスレーブを増やして、サービスに追加することになります。

DBのデータはインスタンスにアタッチされているEBSのスナップショットとして、定期的にバックアップが取られています。新規にスレーブを作成する場合は以下のような手順になります。

  1. MySQL on EC2のインスタンスを立てる
  2. スナップショットからEBSを復元してインスタンスにアタッチ
  3. レプリケーションが追いつくのを待つ

これで新しいスレーブができました。「早速サービスに入れよう」…とはいきません。 作ったばかりのMySQLはデータがメモリにキャッシュされていないため、クエリが投げられるとディスクへの読み書きが発生し、処理に時間がかかってしまいます。 またスナップショットから復元されたEBSは、最初にブロックにアクセスしたときにはS3からデータをダウンロードしてくるため、その後のアクセスよりもレイテンシが増加します。

このように暖機の行われていないスレーブをサービスに投入すると、サービスの応答速度の低下を招き、障害にもつながります。

EBSの暖機

gp2/1000GBのEBSをfioで暖機してみたところ、約19時間ほどかかりました。

さすがに時間がかかりすぎるので「サービスに即時投入できるようにレプリケーションし続ける小さいインスタンスを用意しておく」とか「バックアップ用のスレーブのEBSを使って新しいスレーブを作る」などいくつか対策を考えてみたのですが、同じ部の先輩が「I2インスタンスを使うとよいのでは?」とアドバイスをくれました。

I2インスタンスは大容量で高速なインスタンスストアが使えるインスタンスタイプです。 DBで使っているファイルの総容量はEBSのサイズよりも小さいので、EBSからインスタンスストにコピーする方がEBS全体を暖機するより処理時間は速くなります。 また、インスタンスストアはインスタンスに物理的にアタッチされるボリュームなので、暖機などしなくても高速に使えます。

なるほどと思って、総容量300〜400GBのDBのファイル群をcpを使って8並列でコピーしてみたところ、だいたい3時間ほどかかりました。EBSを暖機するよりはずっと短くなったのですが、それでもまだ時間がかかります。 並列でcpを走らせるとある程度はスループットが出るのですが、ファイルごとの処理は直列なためサイズの大きいファイルがあるとそれに引きずられてスループットが下がってしまいます。

そこで一つのファイルをチャンクに分けてddでコピーするツールを作り、それを使ってコピーしてみたところ、3時間かかっていた処理を1時間程度まで短縮することができました。

MySQLの暖機

MySQLのデータをメモリに乗せる作業は、以前はMySQL::WarmerをRubyにポートした自作ツールを作って、手作業で行っていました。

サーバ上でメモリ使用量を見ながらウォームアップツールで主要なテーブルの暖機を行い、キャッシュが飽和したらサービスに少し入れてみて、スロークエリの出たテーブルをまた暖機…と悪い意味で職人的な作業であり、大量のMySQLに対して行うには非常に手間がかかりました。

そこでMySQL 5.6のInnoDBバッファープールのプリロード機能を使って、暖機作業を高速化しました。 InnoDBバッファープールのプリロード機能は、稼働しているMySQLのバッファプールの状態をファイルに出力しそれを起動時に読み込むことで、暖機の手間を省いてくれるものです。 基本的には同じサーバ上のMySQLでの利用が想定されていると思うのですが、今回は稼働中のスレーブのバッファプールをダンプしてそれを新しく作ったスレーブに読み込ませることで、暖機作業を機械的に行えるようにしました。

具体的には以下のような手順になります。

  1. 稼働中のスレーブの1台で定期的にバッファプールのダンプを行うcronを設定する
  2. ダンプファイルは圧縮してS3に保存しておく
  3. 新規に作成したスレーブはS3から最新のダンプファイルをダウンロードして、起動時に読み込む

ディスク上のデータの物理的な配置が完全に一致しているかはやや疑問でもあるのですが、手作業よりも圧倒的に手間が少なく、また十分な効果も得られているのでこの方法をとっています。

スケールアウトの手順

現在、MySQLのスケールアウトが必要な場合、以下のような手順で行っています。

  1. Kumogataを使ったCloudFormationのテンプレートを準備しておく
  2. 環境変数でサーバ台数を指定できるようにしておき、CloudFormationで必要な台数のインスタンスを起動する
  3. CloudFormationによって、起動したインスタンスには最新のバックアップから作成されたEBSがアタッチされる
  4. インスタンスが起動するとcloud-initの起動時スクリプトによって以下の作業が行われる
    • MySQLのセットアップ
    • EBSからインスタンスストアへのデータのコピー
    • S3からバッファプールのダンプファイルをダウンロード
  5. MySQLが起動してレプリケーションの再開とバッファプールのロードを行う
  6. レプリケーションが追いついてバッファプールのロードが終わると、Slackに通知が来る
  7. 新しいスレーブを人間がサービスに追加する

まとめ

この仕組みを導入することで、MySQLのスケールアウトのために「TV放映の前日、前々日から準備を始めて」「19時間ちかくを暖機に費やして」「職人芸でサービスに追加」していた作業が、「TV放映の当日、1〜2時間前にコマンドをたたいて」「Slackに通知が来るまで放置して」「通知来たらおもむろにサービスイン」といった具合に、大幅に省力化・短時間化できました。

オペレーション作業に特有の「長い時間かかって手持ちぶさたな割に目を離すことができないから他の作業がしにくい時間」って、ほんと嫌ですよね… 今後もそんな作業があればさっさと自動化して、QoLの向上を図っていきたいものです。

ペンとふせんで!スマホUIのアイデアプロトタイピング

$
0
0

f:id:transit_kix:20161011214557p:plain検索事業部のデザイナー倉光です。

今回は、開発現場でアイデア発散フェーズにやっていることの一例を紹介したいと思います。UIデザインの手法として比較的知名度は高く、デザイナー以外でも学びたいという要望も多い「ペーパープロトタイピング」についてです。

前提として

プロトタイピングにはフェーズと目的に応じて様々な手法がありますが、今回は「小規模チームでアイデアをぽんぽん出し、伝え合うためのプロトタイピング」の話です。ユーザーに実際に評価してもらうためのプロトタイプの作り方についてはこの記事では割愛させていただきます。

また非デザイナーの方は「いやいや、デザイナーじゃないと画面なんてうまく書けないよ…」と躊躇してしまうかもしれませんが、本記事では社内のディレクター/エンジニア/インターン生が書いた成果物も掲載していますので、そちらも参考になると思います。


目次


1.ペーパープロトタイピング(紙実装)とは

「プロダクトをこんなかたちにしたい」という画面案を紙に書き、それを成果物として利用/評価/改善をすることを指します。私は個人的に「紙実装」と呼んでいます。コードを用いた本実装との対比ですが、小さく価値検証するための実装方法の一つであるという捉え方をしています。

どんな時に有効?

主に開発初期の工程で使われます。以下のような場面です。

  • アプリの新機能について構想していて、自分の頭の中にある画面の関係性を整理したい時
  • 複数人で議論していて、その場でざっくりと画面案の方針を決めてしまいたい時
  • チームメンバーへ、自分のアイデアを伝える時

なぜこれをやるか?

正解のわからないサービス開発においては、初期に1つでも多くのアイデアを発散させてからデザイン→実装へと収束していくことが最も近道であると考えています。また、アイデア発散の際には早く具現化することで「果たして実現性のありそうなアイデアかどうか」の判断ができるからです。


2.実施方法

f:id:transit_kix:20161011214545p:plain

まずはこちらを準備しましょう。手元で揃う道具で構いません。(ちなみに実際に使っている道具名はこちらです*1。)スマートフォンは紙を撮影したり、プロトタイピングツールを動作させるために利用します。

2-1.かんがえる

画面の設計図を書くことに関しては絵の上手い下手はありません。画力に自信がないあなたも、まずは手書きのスケッチをオススメします。

PCを開く前に、まずはペンを持とう

ツールの習熟度に振り回されずに、アイデアを生み出すことができます。また、いきなりツールを触ることの弊害として、そのツールの機能で出来ることしか試さなくなってしまうことが挙げられます。サービスに載せたい情報が曖昧なままにツールを触りだすと、早くキーカラーを決めたくなったり、それっぽい高品質画像を素材サイトから探す方に熱心になってしまうという罠にも気をつけなければいけません。

頭の中の整理をする

そもそも、体験を考慮した遷移設計ってどう考えたらいいの?…そんな時は画面を書く前に頭の中を整理します。もし作る画面が1画面のみだったとしてでも、あなたがもっとユーザーのことを考えてみたいと思ったならばユーザーがその日アプリを起動するきっかけと、最後に何をしたら満足してアプリを閉じることになるか…その一連の流れをコピー用紙に矢印や図を使ってスケッチしてみましょう。

f:id:transit_kix:20161011215346p:plain [例]アプリ「みんなのお弁当」のSNSシェアページをデザインする前に書いたもの

書き出してみると、デザインするのはSNSシェアページの一画面のみであっても、シェア先で表示するカードのデザインや、それを見た人がどういった経路を辿るのかというのがだんだん見えてきますね。もう少し厳密に体験を定義したい場合は、ユーザーストーリーを書き出します。(詳しくは 「インターンシップ「サービス開発演習」の舞台裏」の回にて)

2-2.描く

長方形のふせんを用意します。縦向きにしましょう。 スマホっぽい比率の紙になりました。

f:id:transit_kix:20161011214557p:plain

今から作りたいと思っている画面を書いてみましょう。スクロールするような長い画面は、2枚目、3枚目のふせんを更に下につなげていきます。

慣れないうちはペンを使い分けなくても良いです。使い分けたい場合は、サインペン(太)はボタンの境界線や目立たせたい見出しに、サインペン(細)は注釈や説明文などに利用します。

f:id:transit_kix:20161011214638p:plainユーザーが実際に目にするであろうテキストを配置しよう

本当に有効なアイデアかどうかを検証するには可能な限り完成予想に忠実にしてあげましょう。「ここにテキストが入ります」「ああああ」「hogehoge」などの曖昧なテキストはやめましょう。

オーバーレイするUIパーツは別のふせんで

サイドメニューや部分スクロールするUI、ダイアログは違う色の小さなふせんで上から貼ると、いろんな画面で使い回し可能です。後述する「手動実演」の時にも、手軽に画面状態を変えられます。

どんどん捨てる

いくつかの案で悩んでいたら、上から別案を張り付けることもできます。書き損じたら、使える部分だけ残してハサミで切り取っても可です。とにかく躊躇せず、切り刻んでください。

実際に画面に置く部品の書き方についてはfladdictさんの 「ペーパープロトタイピング入門 – 第4回 見やすいペーパープロトの描き方」がわかりやすくまとまっていると思います。

2-3.つなぐ

ひとまず1画面ができたら、その画面の中にあるボタンを押したら進む次の画面など、ユーザーストーリーに登場する画面の集合体を作りましょう。作業中に考慮漏れに気づき、新しい画面が増えていくこともあります。プロダクトの全貌が明らかになっていくイメージです。

ちなみにふせんを壁に貼りながら、関係性に沿って配置&前後の画面を線で繋いでいくと、簡易遷移図が出来上がります。

f:id:transit_kix:20161011215349p:plain

2-4.さわってみる

出来上がった画面を、今度はユーザーの行動に沿って動かしてみましょう。

手動実演

上の図は、iOSウィジェット機能を活かしたアイデアを手動で動かしている動画です。「ここを押したら、画面がシュッと移動して、こうなります」…このように、手でふせんを入れ替えながら実演してみせるのも良いでしょう。施策相談中にそのままプロトタイピングが始まっちゃう時には、割と頻繁に見かける光景です。

f:id:transit_kix:20161011214650p:plain

実機にプロトタイプを入れて、ユーザーと同じ環境下で触ってみる

出来上がった画面は撮影して各種プロトタイピングツール(Prott/Invision/Flinto for macなど)に転送しリンクを設定すれば、「操作することのできる試作品」にすることができます。昼間に作ったプロトタイプを帰宅途中のスーパーで開き、食材を探しながら歩き回ってみたりしたこともありましたが、デスクにいる時と全く受ける印象が違います。

実際に操作ができるようになることで格段にアイデアの解像度はあがり、様々な課題が可視化されます。実際「最高かよ…」と思ってたアイデアが具現化したら全然イマイチだったみたいなことは日常茶飯事なので「紙実装のうちにわかって良かったですね!」ということで次の策を打ちましょう。


3.現場でどう活かすか

使われ方

アイデアのプレゼン/ミーティングの成果物/仕様概要/アイデアの自己検証…として利用されます。ちなみに紙実装はそれ単体の出来で評価されることはありません。どういったユーザーストーリーでその画面を操作していくのかを同時に定義しています。

周囲への共有方法

f:id:transit_kix:20161011215912p:plain最終的にはGitHub上の関連Issueに、情報を貼りつけます(プロトタイピングツールのシェアURLも)。

f:id:transit_kix:20161011224717g:plain紙実装である程度の方向性を確認した後、これを元にデザイナーがグラフィックやトランジションを考慮した設計に移行していきます。

紙の保管方法

f:id:transit_kix:20161011214620p:plainふせん一式は重ねることでコンパクトに保存することができます。またA4のコピー用紙に並べて貼ることで、一覧性を保ったまま保存することもできます。ただしあくまで中間成果物なので、Issueに貼った後は長期間実物を保管することはあまりありません。


4.開発に取り入れた影響

f:id:transit_kix:20161011214654p:plain

いろんなUXデザインの手法をクックパッドの現場流にアレンジして個人的にやっていた紙実装ですが、数ヶ月前に社内ブログで紹介したところディレクターを中心に反響があり、その後実際にどんな変化があったのかについて触れます。

(余談ですが画面プロトタイピングにおけるふせんの活用については、海外では電子書籍も出ています。少し利用方法は異なりますが( *2 )。)

再構成がしやすいので、開発へ移行するまでのスピードがより早く

画面同士の関係性を保ちながら壁に貼れたり、その場で別案を上から重ねたり…といった取り回しがしやすいことが何よりアイデアを考えることに集中できます。短時間で大量のアイデアを具現化し、その場で「それいいね!」と議論しながらアイデアを拡張する際にはおすすめです。

スマートフォンのUIアイデアを書き込む用紙としては、ペーパープロトタイピングのための専用ノートパッドや印刷して使えるテンプレートPDFなどがあります。社内デザイナーも各自が様々なツールを試していましたが、アイデア発散フェーズではこの方法が最も作業自由度が高く、コラボレーション性があるという声がありました。

デバイスのサイズ感を意識したアイデアが出せるようになった

スマホに似たサイズの紙にアイデアを書くことで、自然とデバイス実寸が考慮されたアイデアを考えやすくなりました。各種PCに入っているソフトウェアの図形ツールを駆使した画面案を見かけることがなくなった点は個人的に嬉しく思っています。(頑張って作ってくれたところ申し訳ないなとも思うのですが…これらは往々にしてディスプレイ上で拡大された状態で作成されているので、入りきれない量のテキストが極小サイズで画面に配置されていたりするのが悩みの種でした)。

詳細の説明なしで、やりたいことが伝わる

これは アラビア語版クックパッドを開発しているレバノン拠点スタッフ(非デザイナー)が作ったプロトタイプの一画面です。レシピ検索結果画面のアイデアスケッチを表しています。残念ながら私はアラビア語は全く読めないのですが、この画面を含む一連のプロトタイプを触ってみることで、彼がどんな機能を作ろうとしているのかが自然と理解できました。リモート開発や言語の壁がある環境においても、いち早く動くものを見せることは意思疎通のために有効だと考えています。


まとめ

最後に、紙実装のメリット / デメリットについてまとめておきます。 f:id:transit_kix:20161011214707p:plain

注意点は以下です。

サイズ感については、必ず実機で検証すること

手に入りやすいふせんですが、厳密に言うとスマートフォンとはサイズ/アスペクト比が異なります。ボタンに実機で問題ないタップ領域が確保されているか?などは、別途本番デザインの際に検証してください。

実装しちゃったほうが早いこともある

何を確かめるためにプロトタイピングするかによっては、実装に着手したほうが早く良い知見が得られることもあります。*3「手段が目的化する」ことは本末転倒ですので、場合に応じて使い分けてください。

…いかがでしたでしょうか?議論を加速し、皆でサービスの在るべき形を素早く生み出していくための1つの手法が紙実装です。デザイナーではない人が、または開発者ではない社内のスタッフが、もっと気軽に自分たちのサービスについてデザインを議論できるようになることで、よりサービスの開発速度を加速させていきたいと思います。

この他のデザイナーの取り組み事例は、こちらのデザイン記事一覧にてご覧ください。 クックパッドでは「プロダクトを率先して、具現化していきたい!」というデザイナーを募集中です。

*1:私は「アスクル 貼ってはがせるオフィスのノート 75x127mm / 38x 50 mm」「パイロット フリクションボールペン(黒 / 0.5)」「ぺんてる水性サインペン(黒)」を使用しています

*2:「The $1 Prototype: Lean Mobile UX Design and Rapid Innovation for Material Design, iOS8, and RWD」活用方法などの詳細は異なりますが、ふせんをスマートフォンに見立てるという点では同じです。洋書ですが、 「日本語解説版」はアジャイルUCD研究会が公開しています。

*3:例えば、実際のコンテンツ情報(240万品のレシピデータ、ユーザープロフィール、その人が興味のあるキーワードなど)をUIに反映しないと本当に良い体験が提案されるかどうかがわからない画面は、まずはstaging環境でHTMLプロトタイプを作成し、検討した上でネイティヴ化のためのUIデザイン/API準備を行なっています。

"使える"プロトタイプ主導の開発プロセス

$
0
0

検索事業部の須藤です。 クックパッドの検索周りのサービス開発を担当しています。

はじめに

最近ではプロトタイピングツールも充実し、コードを書かなくとも動的なモックアップが作れるようになるなど、思いついたアイデアをより早く、より最終的なアウトプットに近い形でメンバーに共有することができるようになったと感じています。

また、実際にコードを書いてユーザーに公開するための効率的な手法や、公開後の検証方法についても様々なツールや知見が共有されており、より精度の高い定量評価ができるようにもなってきたかと思います。

一方、これらの効率化が進んでも、実際に打った施策の数を増やせたか、最終的にサービスインできたプロダクトの数が増えたかというと、そこまで実感がありません。

その理由のひとつは、思いついたアイデアを具体化して作り始めるまでの初期段階と、実際にそのプロダクトを(テスト目的であっても)公開に耐えうる完成度に持っていくまでの間には、相変わらず高いハードルがあるからだと思います。

f:id:sudokohey:20161017095937p:plain

この「間」の部分にある、発想の仕方や開発の進め方こそが所謂「サービス開発」の勘所だと思うのですが、この段階でのプロダクトの作り込みに関しての具体的な知見は、あまり目にすることがありません。

もっとも、提供するサービスの種類や成熟度、作っている組織の規模やカルチャーによって、その扱いは千差万別で、なかなか共有可能なメソッドにならないことがその理由だと思うのですが、今回は敢えてその部分にスポットを当てて、プロダクトの完成度を効率良く高めるために実践している、"使える"プロトタイプ主導の開発プロセスについて書いてみようと思います。

本当にイケてるアイデアを見極める

実際の生活の中でユーザーと同じ立場になって使用感を確かめることができないプロトタイプは、その形態がどうであれ「良さそう」以上の評価はできません。 また、経験上、その段階での良さそうなアイデアの殆どは、作り込んでいく過程のどこかで「ダメそう」に変わります。

効率良くプロダクトの完成度を高めるためには、思いついたアイデアが、本腰を入れて作り込んでいくに値するものかどうかを、できるだけ早期に見極める必要があります。

実際に"使える"プロトタイプを作る

そのためには、プロトタイプと言えども、実際に「使える」ものをつくることが大事です。 これは、webサービスであれば、一般に公開されている本サイトと同じ体験ができるものをつくるということであり、例えば、

  • 思いついたキーワードで自由に検索ができる
  • リアルタイムに様々な情報が更新される
  • 思考が途切れずに操作するに足りる実行速度である

といった要件を満たすものでなければ、正確な見極めができません。*1

1日で作れるものをつくる

そこまでのものをつくるとなると、企画を出して、仕様を詰めて、デザイナーやエンジニアを集めてがっつり開発するのと変わらないのでは?と思うかもしれません。 また、プロトタイプである以上、プロダクションでの公開を前提とした開発と同等に時間がかかっていては本末転倒です。

そこで、1日で作れるものをつくるという制約を自分の中に持つようにしています。

アイデア段階での構想が大きなプロダクトであっても、体験を構成する要素を機能単位に分解し、その中から、そのコンセプトを成立させるために欠かせない機能や遷移を抜き出します。 まずはその部分にフォーカスし、それがユーザーのサービス内での行動動線の中でどのように機能するのかを確かめるために必要な分だけの実装をします。

スピード最優先でつくる

実装と言っても、書いているのは簡単なコードばかりです。 例えば「食べ方提案」と呼んでいる以下のようなUIを持った機能を例に挙げると、まず簡単なスクリプトを書いて、食べ方のデータを用意します。本来であれば、DBにテーブルを作ってそのデータをインポートして、さらにデータの更新方法を準備して...と進むはずですが、それをやっている時間すら惜しいので、UIに合った使いやすい形にデータを整形して出力し、ハードコードして済ませます。

コンセプトを確かめる程度であればこれで十分です。

f:id:sudokohey:20161017100328p:plain:w300
※開発中のプロトタイプの画面

また、ぱっと見は完成形と変わらない新機能が日々追加されていく環境を用意することは、いくつかの面でメリットがあります。

ちなみにアプリの場合は?

chankoのような機能もなく、プロダクションのAPIに手を入れる必要があること、また、デプロイゲートなどを使った確認のコストも高いと感じることから、現時点ではアプリ向けの機能のプロトタイピングもweb上で行うようにしています(web版で公開予定のないものであっても)。

f:id:sudokohey:20161017100648p:plain:w300f:id:sudokohey:20161017100704p:plain:w300
※chanokoを利用してweb上に実装したアプリ向けのプロトタイプの画面。(web版での公開予定は今所無い)

もちろんアプリならではの表現などは再現し辛いため、コアになる機能や遷移をまずweb上に用意し、細かなインタラクションについてはデザイナーが部分的に動的なモックを作るなどし、並行して検討します。

初期段階で必要以上に議論しない

ちょっと話が逸れますが、クックパッドでは「動くものを前に議論する」という言葉が時々出てきます。 これは裏返すと、実際に「使える」状態にないものに対してあれこれ言ってもあまり意味がないという事です。 ですので、余計なインプットを入れずに、使えるプロトタイプとして最初にデプロイするまでは、直感を信じて作り切るようにします。

ちなみに、ここで全くイケてないものを作ってしまった場合(結構あります...)のダメージを最小化するためにも、1日でつくれるものを意識しています。

基盤技術になりそうなものは、別ラインで開発する

さて、上記のようなプロトタイピングを繰り返していると、

  • あるレシピからその料理名を簡単に抽出したい
  • あるレシピの主材料が何かを判定したい

といったような具体的な要件が明確になってくる事があります。

例えば「簡単!うまうまゴマいんげん」というレシピがあったときに、人間が見ればこれが「いんげんの胡麻和え」であることは一目瞭然なのですが、機械的に正確にこれを判定しようとすると結構難しかったりするので、その分野の解決が得意なエンジニアに別ラインで開発を依頼します。

こういった技術開発を依頼される側からすると、開発する技術の具体的な使われ方がわからないため、正解の状態をイメージできなかったり、どの程度の精度のものを仕上げれば良いのかが分からず、仕様を定めるのに時間がかかるケースがあると思うのですが、使えるプロトタイプがそこにあるため、比較的スムーズに話がすすみます。

また、仕様通りに作るよりも、プロダクトに貢献できるイメージが持てると、気持ち的にだいぶ違います。先程いくつかのメリットと書きましたが、その一つがこれに当たります。

いつのまにかまともなコードに!?

捨てる前提のコードでプルリクエストを送っていると、「それ、他のところでも使えそうそうだからメソッド用意しといた」みたいな感じで、部分的な開発を肩代わりしてくれる神様のようなエンジニアが出てきたりします。依頼する前に解決するという奇跡のコラボレーションです。

これを繰り返していくと、いつの間にかハリボテ(?)のコードが、まともなコードになってたりします。これもメリットです。

実際に使ってみて評価する

さて、これが最も大事なことですが、使えるプロトタイプが用意できたら、これを実際の生活の中で使って評価します。平日に料理をするのはなかなか難しいのですが、毎日小さく開発していくと、週末には5つのアップデートが載っています。これを週末に使い倒します。

作ったプロトタイプは、次にあげるような不意に訪れるピンチを救ってくれるものになっているでしょうか?「良さそう」だと思ったアイデアが単なる妄想に過ぎなかった場合は、ポケットの中のプロトタイプがそれを教えてくれます。

  • 小松菜で何か作ろうと思って買いに行ったら、台風による日照不足の影響で、1束250円もした。隣には白菜が128円で積まれていた。さてどうする?*2

  • 29の日(肉の日)の特売目当てでお肉を買いに行ったら、時間が遅かったためにほとんど売り切れていた。残っていたのは普段あまり使わない手羽先だけだった。さてどうする?

  • 夕飯のおかずは冷凍してある鶏肉で...と考えていたが、いざ料理を始めると、冷凍庫の中には、あるはずの鶏肉が無かった(!)。かわりにすっかりその存在を忘れていた、鱈の切り身が見つかった。さてどうする?

このようなイレギュラーが現実では沢山おこります。そしてこれらは、机の前で開発しているときには想像できません。誰かが事前に用意したペルソナにはもっとざっくりとした、ありがちなシナリオしか設定されていません。

さてどうする?

これを解決するために、"使える"プロトタイプ主導の開発プロセスを実践しています。

誰がこの役割を担うのか?

このサイクルを一人で回すには、様々なスキルが要求されます。

  • 周辺にある実用段階の技術を幅広く理解する
  • それフル活用して、現時点で実現可能かつ斬新な企画をいくつもひねり出す
  • コンセプトを体現するシンプルなインターフェースをデザインする
  • 実際にコードを書いて、1歩先の姿を作り続ける

こう書くととてもムズカシイことのように見えますが、クックパッドには、これを実践するデザイナーやエンジニアがちらほらいます。新卒社員の中にもいたりします。

もしこの記事を読んで「自分にぴったりだ!」と感じた方がいらっしゃいましたら、ぜひこの 役割をお願いしたいと思っておりますので、ご応募お待ちしております!

*1:クックパッドでは、行単位でレプリケーションされた開発用のデータベースに接続して開発できるようになっていること、また、chankoを利用してプロダクションのコードに影響のない形でプロトタイプをそのままプロダクションに載せることができるようになっているため、この要件を満たすとは比較的容易です。

*2:先々週にあった自分の実体験です

来年も Cookpad TechConf やります

$
0
0

こんにちは! @yoshioriです。

今年年明けに Cookpad TechConf 2016という、クックパッドのエンジニア、デザイナーがサービスづくりの過程で得た技術的知見や経験をみなさんに発表させていただくイベントを開催させていただきました。
こういったイベントは続けてナンボ!!! ってことで来年もやります!!!
前回は会場キャパシティ 250 人の所、1000 人以上のお申込みを頂きました。
今回はその反省も含めレイアウトを変更し会場キャパシティ 350 人用意しました!!!

今年もエンジニア組織的な話から海外展開で得た知見、基盤技術、サービス開発、デザイン、機械学習など多種多様なコンテンツを用意していますので、知っている分野は更に知見が深まり、知らない分野のこともザックリ知れる。そんな一日にしたいと思っています。

一同お待ちしています! みなさま奮ってご参加ください!

Cookpad TechConf 2017

タイムテーブル

タイトル発表者
Cookpad under a microscope 成田 一生
Deliver apps to global audience 滝口 健太郎
Building infrastructure for our global service sorah
クックパッドで取り組めるデザイン(仮) 若月 啓聡
モバイルアプリのA/Bテスト基盤 加藤 龍
チームでサービス開発をするための取り組み 丸山 亮
サービスのプロトタイピング時に心がけていること(仮) 須藤 耕平
フェージングのすゝめ 〜サービスの大規模リニューアルの話〜(仮) 京和 崇行
快適なサービス開発を支える技術 国分 崇志
Real World Machine Learning 染谷 悠一郎
行動ログでプロダクトを改善するには 兼山 元太
Cookpad awakens 庄司 嘉織

お申込み

お申込みはコチラから

非SPAなサービスにReactを導入する

$
0
0

投稿開発部の外村(@hokaccha)です。今回はReactについてのお話です。

ReactとSPA

最近JavaScriptやそれを取り巻くフレームワークなどの話題では、サーバ側はAPIのみを提供し、View(HTML)は全てJavaScriptで描画するような、いわゆるシングルページアプリケーション(以下SPA)についてよく語られます。

一方で、SPAを構築するにはコストがかかることも事実で、特にフロントエンドエンジニアが多くない環境では、従来通りサーバーサイドでViewを書きつつ動的な部分だけJavaScriptで処理するというアーキテクチャのほうが現実的な場合も往々にしてあります。

今回はこのような、サーバー側でHTMLを生成し、一部の動的な部分だけをReactで書くためのTipsを紹介します。

なお、基本的にサーバーサイドはRails前提ですが、RailsにおけるReactの開発環境の構築方法などについて以下の記事や資料を参照ください。

コンポーネントの例

例えばブログ記事に「いいね」が押せる機能を考えてみましょう。機能としては

  • いいねできる
  • いいねが解除できる
  • 自分がいいねしているかどうかわかる
  • いいねしているユーザー数が見れる
  • いいねユーザーの一覧がポップアップで見れる
  • ユーザー一覧は20件ごとに読み込む
  • いいね押したときにログインしてなければログインの誘導ポップアップが出る

このように、小さいコンポーネントではありますが、複数の状態や機能があり、いいねの付け外しやユーザー一覧の取得はAjaxで行う必要があります。

テンプレートをhamlやerbで書いてjQueryでDOM操作をして実現することもできそうですが、このような機能をjQueryだけでメンテナブルなコードを書くのは簡単ではないと思っています。一方Reactは宣言的で見通しのよいコードでコンポーネントを記述でき、Viewの機能のみを提供するという単機能なライブラリのため、こういった部分的に利用するケースでも導入しやいです*1

また私自身、これと同じような機能をjQueryとReactの両方で実装した経験がありますが、例えこのぐらい小さい機能であってもReactのほうが楽に実装できると感じました。サーバー側のテンプレート言語とReact側のJSXとでテンプレートの言語が分かれてしまうというデメリットはありますが、個人的にはそこを差し引いてもメリットのほうが大きいと思っています。

react-railsを使った自動マウント

このようにReactを動的なコンポーネントだけに使っていくという手法の場合、面倒なのがReactコンポーネントのマウントです。SPAの場合は基本的にルートコンポーネント一つをマウントすれば済みますが、こういった構成の場合は1ページに複数のコンポーネントをマウントするケースが多くなります。

例えばブログ記事のページで、いいねとコメントの2つの動的なコンポーネントがあるとします。まずはテンプレートを次のようにして

<h1><%= @entry.title %></h1><%= @entry.body %><divclass="like-component"></div><divclass="comment-component"></div>

JavaScript側で対象のDOM要素に作成したReactコンポーネントをマウントします。

document.addEventListener('DOMContentLoaded', () => {let like = document.querySelector('like-component');
  let comment = document.querySelector('comment-component');

  ReactDOM.render(React.createElement(LikeComponent), like);
  ReactDOM.render(React.createElement(CommentComponent), comment);
});

2つくらいであればこれでもいいですが、コンポーネントを作る度にこのようなコードを書かないといけないのは面倒です。また、コンポーネントの初期値としてpropsを与えたいというケースも出てくるでしょう。

そこでRailsの場合はreact-railsを使うのがオススメです。react-railsにはサーバーサイドレンダリングなどの興味深い機能もありますが、今回はview helperとreact_ujsを使った自動マウントの機能を紹介します。

先程の例はreact-railsを使うと次のように書くことができます。

<h1><%= @entry.title %></h1><%= @entry.body %><%= react_component('LikeComponent') %><%= react_component('CommentComponent') %>

JavaScript側ではreact_ujsを読み込んでおき、コンポーネントをグローバルから参照できるようにしておくだけで自動的にコンポーネントがマウントされます。

また、引数でpropsを渡すこともできます。

<%= react_component('LikeComponent', liked: @current_user.liked?(@entry), likeCount: @entry.likes.count) %>

このようにすることでLikeComponentに初期値をpropsとして渡すことができ、Ajaxで通信せずとも初期描画を行うことができます。

また、react_ujsの自動マウントはturbolinksにも対応しており、turbolinksでページ遷移したときに自動でマウント・アンマウントを行ってくれるという機能があります。jQuery時代にturbolinksを使って$(document).ready()が走らなくてハマる、という経験されたことがある方には嬉しいかもしれません。

Railsを使わない場合や、それだけのためにreact-railsを使いたくない場合は同じような仕組みを実現するのは大した手間ではないので自作してもいいと思いますが、1ページに複数コンポーネントをマウントする場合は、何かしらこのような自動マウントの仕組みがあると便利です。

react-micro-container

Reactではルートコンポーネントが全ての状態を管理し、子のコンポーネントにはpropsとして値を渡すようにすることで、できるだけコンポーネントから状態を取り除くというプラクティスがあります。このとき子要素で発生したイベントをルートコンポーネントに伝える手段が必要になります。

愚直にやるとイベントハンドラを子要素に渡し、全てのコンポーネントでイベントを拾って一つ上の親にあげていくという処理が必要になります。例えば、いいねコンポーネントで、「いいね」や「もっと見る」を押したときのイベントをルートコンポーネントまで伝えるのは次のようなイメージです*2

f:id:hokaccha:20161026134922p:plain

これがいわゆるイベントのバケツリレーです。今回のような小さいコンポーネントの場合も、内部でコンポーネントを分割していくと容易に数段のネストしたコンポーネントになります。

何かしらのFluxフレームワークを使ってもよいですが、こういった小さいコンポーネントにFluxフレームワークはオーバースペックなことも多いです。そこで拙作ですが、react-micro-containerという小さいライブラリを使うと、イベントのバケツリレーだけを簡略することができます。

f:id:hokaccha:20161026134928p:plain

個人的には小さいコンポーネントではこれぐらいで十分なケースも多いと思っています。詳しい使い方などはこちらの記事を参照してみてください。

小さいReactアプリケーションのためのライブラリ書いた - Qiita

注意点として、これは小さいコンポーネントであればうまくいきますが、規模が大きくなってくるとイベントの数が多くなりすぎて破綻してくるので、そういった場合はFluxフレームワークを導入するなどの対応が必要になるかもしれません。

まとめ

サーバー側で静的なHTMLを出力しつつ、動的にしたい部分だけをReactを使って実装する際のTipsについて紹介しました。

ReactはFluxなどを使って大きいアプリケーションを作ろうとすると、とたんに設計が難しくなってきますが*3、こういった小さいコンポーネントから導入する方法であれば、JavaScriptの設計になれていなくても導入しやすいですし、現状のアプリケーションに一部分導入するということも可能です。

Reactに興味はあるが難しそうで二の足を踏んでいるという方はこのようなところから利用してみてはいかがでしょうか。

*1:PolymerやVue.jsも同じようなことが実現できそうですが今回はReactにフォーカスしています

*2:コンポーネントのツリーはわかりやすくするために簡略化しています

*3:Reactに限った話しではなく、規模が大きくなれば何を使っても難しくなります

【学生限定】夜の合同説明会を開催します【クックパッドxドワンゴxグリーxはてな】

$
0
0

将来に悩んでいる学生のみなさん、こんばんは。成田(@mirakui)です。

11/18(金)に、ドワンゴさん、グリーさん、はてなさん、そしてクックパッドという4社の合同で「夜の合同説明会」を開催することになりました。

エンジニア志望の学生さんに向けたパネルセッションなのですが、普通とは違った趣向ですので、この4社に興味がある方はぜひご参加ください。

お申込みは下記の connpass からお願いします。

夜の合同説明会 - クックパッド, ドワンゴ, グリー, はてな - connpass

以下、このイベントがどうすごいのか説明します。

パネリストが豪華

私を始め、各社のトップエンジニアが一同に介します。このメンバーが一箇所に揃うのはめったにない機会だと思います。レジェンドの皆さんの前に私も緊張しています。

  • 司会
    • 庄司嘉織 (クックパッド株式会社 人事部長/エンジニア) @yoshiori
  • パネリスト
    • 藤本真樹 (グリー株式会社 取締役 執行役員常務 最高技術責任者) @masaki_fujimoto
    • 大西康裕 (株式会社はてな 執行役員 サービス・システム開発本部長) @yasuhiro_onishi
    • 成田一生 (クックパッド株式会社 VP of Engineering) @mirakui
    • 清水俊博 (株式会社ドワンゴ 人事部長/エンジニア) @meso

全員飲んでいる

登壇時、パネリストは皆お酒を飲んでいます。お酒を交えながら本音で語り合うイベントというのが今回の趣旨です。

その場でしか聞くことができないぶっちゃけトークが飛び出す可能性が高いです。私は色んな意味で緊張しています。

※参加者のみなさまにもお酒を振る舞うため、20歳以上限定とさせてください

気軽に質問を投げられる

申し込み時に質問を書くところがあるので、偉い人が酔っ払っていれば答えてくれるかも? というギリギリを考えて攻めてみてください。

なにが飛び出るかわかりませんが、パネリスト一同みな覚悟して望んでいます。この業界に興味のある学生の皆さんは、この機会にぜひお越しください。

お申込みはお早めにこちらまで!

夜の合同説明会 - クックパッド, ドワンゴ, グリー, はてな - connpass

サービス開発におけるアプリデザイナーの役割について

$
0
0

投稿開発部デザイナーの辻(@tomoya_tsuji_)です。

去る10月26日、「Cookpad Tech Kitchen#3 サービス開発におけるアプリデザイナーの役割」と題して、デザイナー向けのイベントを行いました

このイベントでは、「クックパッド」「トクバイ」のiOS/Androidアプリサービス開発において、デザイナーの役割や大切にしていることをテーマに、4名のデザイナーが発表をしました。

この記事では、その様子についてお伝えします。 尚、今回のイベントでの発表資料は公開を予定しておりません。

登壇者発表

デザイナーの組織の話について

まず、VP of Product Design 兼 投稿開発部長の池田(id:tikeda)から、クックパッドのデザイナー組織について発表しました。

ここでは、

  • クックパッドには、多種多様なデザイナーがいること
  • 横串と縦串を意識したデザイナーの組織編成
  • デザイナーとしての目標や評価軸について
  • プロダクトオーナー思考について

などを紹介しました。

デザイナー組織の体制については以下の記事で解説しているので、是非一読下さい。

毎日の料理のためのアプリデザインの毎日

次に、検索事業部のデザイナー倉光から、クックパッドのアプリ開発で取り組んでいる開発フローやプロトタイプを事例と共に発表しました。 f:id:tomoya-tsuji:20161102145541j:plainユーザーインタビューやGitHubを利用したデザインレビュー、アプリエンジニアがデザインを提案するなどの工夫についていくつか紹介しました。 GitHubを利用した開発やプロトタイプを作り、開発を行っていくフローに関して、当技術ブログにいくつか記事がありますので、ご興味ある方はあわせてご覧ください。

楽しい買物を増やすためのデザイン

次に、株式会社トクバイのデザイナー吉井より、トクバイアプリの開発フローと知見について紹介しました。 f:id:tomoya-tsuji:20161102145555j:plain

この発表では

  • トクバイアプリ開発がどのように行われているか
  • 理想の形への到達プロセス
  • レビューの改善

などについての概略を紹介しました。 トクバイのアプリ開発については以下の記事でも解説しています。

複数プラットフォームで新機能を開発時に苦労したこと

最後に、投稿開発部デザイナー若月(@puzzeljp)より、iOSアプリで新しくリリースした「限定公開レシピ」の開発時に気をつけたことや苦労したことについて発表しました。 f:id:tomoya-tsuji:20161102145603j:plainプラットフォームごとに異なる機能を整理するために表を作って可視化し、共有したり、実装後のデザイン修正によるスケジュールの遅延時の対応などをお話しました。

プラットフォームに応じたデザインに関しては以下の記事でも紹介しています。

QAセッション

上記4名の発表の後、パネルディスカッション形式のQAセッションを行いました。 当日、参加者の皆様から沢山の質問をいただきました!

ここでは、当日時間がなくお答えできなかった質問から3つ選んで回答します。

f:id:tomoya-tsuji:20161102145437j:plain

Q:実装されたものとデザインとの差異が合った場合、どうやってエンジニアさんにフィードバックしていますか?

A:差異が合った場合、デザインの修正を行って Zeplin でエンジニアさんに共有して修正してもらうことが多いですが、最近では Xcode の Interface Builder を触ってデザインの確認・変更の一通りをやり、エンジニアさんには PR で確認してもらいなるべくやり取りを少なくなるよう努力しています。(投稿開発部・若月)

Q:ユーザーインタビューの頻度・実施のタイミングや関わるメンバー(役割分担)や人数などを教えてください。

A:実施タイミングは「特定の施策について、利用状況をユーザーさんの背後の生活環境も含めて聞きたいとき」です(参考までに、検索事業部では2016年10月は2名に実施)。インタビューユーザーの探し方ですが、クックパッドに会員登録されている方々の中で対象ユーザーとして条件の近しい方にまずはメールでお声がけしています。 インタビューに中心的に関わるメンバーは2名程度ですが、インタビュー結果はドキュメントにまとめ次第、すぐに開発チーム全体に共有されます。(検索事業部・倉光)

Q:デザイナーが少ない中でもうまく回す工夫などありますか?

A:自分がやるべき範囲を見極めることを意識しています。 トクバイでは隔週ごとに約1ヶ月先までの大枠の開発計画を立てているので、 そこでサービスの全体感やエンジニアの先の動きを見て、 デザイナーとしてどう動くべきかを早めに掴むようにしています。 また、デザイナーのコストをどこにかけるのかの判断も重要ですね。 細かい部分は直接のデザイン指示やレビューで賄うなど、 手が足りない範囲をコミュニケーションでカバーすることも必要だと思います。 (トクバイ・吉井)

懇親会

上記セッションの後、懇親会を行いました。

懇親会では、他の弊社デザイナー、エンジニアも参加しながら、参加者と様々な意見交換や談話をしました。 f:id:tomoya-tsuji:20161102145655j:plain

多くの皆様にご参加いただけたおかげで、より深掘りした話をしたり、各社のアプリ開発の知見を共有するなど、活発な情報交換が行えたと思います。

また、今回はハロウィンが近かったので、ハロウィンをテーマにした料理も振る舞われました。 f:id:tomoya-tsuji:20161102145710j:plain

まとめ

いかがでしたでしょうか。

この他のデザイナーの取り組み事例は、こちらのデザイン記事一覧をご覧ください。 クックパッドでは、今後もデザイナー向けのイベントを行っていきたいと考えています。

一緒にサービス開発を行っていくデザイナーも募集しています。 ご興味のある方は是非!ご応募お待ちしております。


分析SQLのコーディングスタイル

$
0
0

SQL、書いてますか?

こと大規模データ処理の分野においてはSQLはもはや標準インターフェイスであり、 分析やらバッチやらに関わっている皆様は日々大量のSQLクエリーを生産していることと思います。

そこでちょっと気になるのが、 SQLのコーディングスタイルってどうするのが一般的なんだっけ……? という点です。 イマドキはSQLなんてO/R mapperに吐かせることが多いからなのか、 それともコードを広い範囲で共有することがそもそもないからか、 SQLのコーディングスタイルについて見聞きすることは他のプログラミング言語に比べるとだいぶ少なく、 いまいち決定版と言えるスタイルがないなと感じています。

そんなわけで本日は、SQLのコーディングスタイルについての意識を活発化させるべく、 クックパッドでわたし(青木)が使っているコーディングスタイルから特徴的な点を紹介したいと思います。 特に、分析バッチで用いるような巨大なSQLのスタイルについて話します。

なお、この記事では、コーディングスタイルを網羅的に示すことはしません。 いまさら「演算子の周囲には空白文字を置く」やら「コードはインデントする」のように どうでもいい(積極的に抵抗する人がいない)スタイルについてえんえん書くのは 時間の無駄でしかありませんし、基本方針がわかりにくくなるからです。 意見の分かれやすい、抵抗の大きそうなところに集中して述べていきたいと思います。

サンプルコード

まず、わたしが普段採用しているコーディングスタイルをお見せします。

次のクエリーは、適当にそのへんのファイルからSQLを拾ってきて、適当にテーブル名やカラム名を変えたものです。 ある程度の長さがないと事情が理解してもらえないと思うので、まあまあ長めのものにしました。 なお、この記事の内容にはクエリーの意味は関係ないので、読む必要はありません。スタイルだけ見てください。

select
    user_id
    , user_session_id
    , min(log_time) as session_start_time
    , max(log_time) as session_end_time
    , count(*) as num_steps
    , max(case session_step when 1then keywords elsenullend) as step1
    , max(case session_step when 2then keywords elsenullend) as step2
    , max(case session_step when 3then keywords elsenullend) as step3
    , max(case session_step when 4then keywords elsenullend) as step4
    , max(case session_step when 5then keywords elsenullend) as step5
    , max(case session_step when 6then keywords elsenullend) as step6
    , max(case session_step when 7then keywords elsenullend) as step7
    , max(case session_step when 8then keywords elsenullend) as step8
from (
    select
        user_id
        , user_session_id
        , row_number() over (
              partition by user_id, user_session_id
              orderby log_time
          ) as session_step
        , log_time
        , keywords
    from (
        select
            user_id
            , sum(session_delta) over (
                  partition by user_id
                  orderby log_time
                  rowsbetween unbounded preceding andcurrentrow
              ) as user_session_id
            , log_time
            , keywords
        from (
            select
                user_id
                , case
                  when
                    lag(log_time) over (partition by user_id orderby log_time) isnullor log_time > lag(log_time) over (partition by user_id orderby log_time) + interval '00:30'then1else0endas session_delta
                , log_time
                , trim(word1
                      ||''|| coalesce(word2,'')
                      ||''|| coalesce(word3,'')
                      ||''|| coalesce(word4,'')
                      ||''|| coalesce(word5,'')
                      ||''|| coalesce(word6,'')
                  ) as keywords
            from
                activity.search_log
            where
                log_time between timestamp '2015-02-01 00:00:00'and timestamp '2015-02-01 01:00:00'
        )
    )
)
groupby user_id, user_session_id
orderby user_id, user_session_id
;

このスタイルは会社の先輩が使っていたスタイルが便利だったのでそれをベースに、 ウィンドウ関数やcase式のスタイルを追加・改良したものです。 ここから主な論点として、次の7点に注目したいと思います。

  1. 大文字と小文字の使い分け
  2. カンマの位置
  3. セミコロンの位置
  4. select、from、whereを左右どちらに寄せるか
  5. joinのインデント
  6. インデント幅
  7. 標準SQLとの向き合いかた

いかにも炎上しそうな項目から順に述べていきましょう。

1. すべて小文字を使う

SQLのコーディングスタイルについて語るとき、最初にして最大最悪の障害は大文字小文字の使いかたではないでしょうか。

JavaやRubyのように比較的新しいプログラミング言語はあまり大文字を使わない傾向にあります。 しかしSQLはメインフレーム全盛の時代から生き残っているだけあって、 大文字アルファベット文化がいまなお大きな影響力を持っています。 そのため、SQLを古くから書いている人ほど大文字をよく使うように感じています。

大文字小文字のスタイルを大きく分類すると、以下の3つに分けられるでしょう。

  1. すべて大文字
  2. キーワード(予約語)は大文字
  3. すべて小文字

わたしのスタイルは3の「すべて小文字」です。

まずスタイル1ではない理由は簡単です。 大昔からSQLを書いている人ならともかく、このサイトを見るような読者層にとってみると、 「すべて大文字」というのは、ほぼ誰もやりたくないスタイルと言ってよいでしょう。 個人的にも、正直すべて大文字のコードは読みたくありません。 よってこのスタイルは最初から検討もしませんでした。

次に2の「キーワードは大文字」について。これはおそらく最大派閥ですが、わたしはこのスタイルは採用しません。

この派閥を支持する意見としては、「大文字のほうが見やすい」という主張をよく見ます。 ですがこれは大変怪しい主張です。

「大文字は見やすいからキーワードのように構文上重要な役割を持つ語は大文字である」……と本当に考えるのなら、 JavaやRubyについても同じことを言うべきです。 しかしJavaやRubyで予約語が小文字であることに対して文句を言っている人は見たことがありません。

なぜか。小文字で十分見やすいからです。

最近はどんな開発環境だろうともシンタックスハイライトくらいは完備しているでしょうし、 わざわざ大文字にせずとも視認性は十分得られます。 あえて大文字にする理由は、歴史的な理由以外にはないと思います。

「おまえは今まで使ったSQLのキーワードがいくつあるか覚えているのか?」

しかしもちろん、「キーワードだけ大文字」に見やすさという理由がなかったとしても、歴史的な継続性はあるわけです。 少なくともわたしの主張する「すべて小文字」派よりは多少なりとも昔の面影を残しているぶん、 古くからSQLを書いている人たちにもアピールするという利点はあるでしょう。

ですが、それでもわたしが「キーワードだけ大文字」を採用しないのは、このルールがあまりにも厄介だからです。

SQLには、他のプログラミング言語に比べて遥かに多くのキーワードがあります。 selectやfrom、join、asなどは当然として、 関数名もたいていキーワードなのでcountもmaxもsumもキーワード、 ウィンドウ関数のrankもrow_numberもキーワード、rowやunpreceedingもキーワード、 extract関数で使うyearやmonth、dayなどもすべてキーワードです。 あまりにもキーワードが多すぎて、たぶん誰もすべてのキーワードを覚えていません。

特にuserやactionのようないかにもよく使いそうな単語がキーワードなのが最悪で、 これらが現実的な確率でカラム名として使われてしまいます。 そうなったときに、こいつらも大文字にするのか、クオートだけして小文字にしてしまうのか、 といったしょーもないことで悩む必要が出てきます。

また、RDBMSごとの特有のキーワードというものも存在します。 しかしそういったDB固有のキーワードはエディターがサポートしていないことが多く、 DB固有のキーワードだけは小文字にされてしまう……みたいな悲しい事態に陥ります。 UDF(User-Defined Function)やUDT(User-Defined Type)のような、 ユーザーが独自に定義する関数や型も同様の問題を起こします。

具体的にはこんな感じの見ためになるわけです。

SELECT"USER"
    , COUNT(user_id)
    , some_udf("USER")
    , EXTRACT(YEAR FROM created_at)

これが、本当に、見やすいのでしょうか?

もう正直気持ち悪い面倒だわ、どれがキーワードだか覚えられんわで、いいことは何一つないように思われます。 ここまでやるなら「すべて大文字で書くよ」派のほうがまだ一貫性があって楽かもしれません。

真面目に従えない使い分けルールを決めるくらいだったら、そもそも大文字小文字の使い分けをやめたほうがまだマシです。 そして大文字と小文字のどちらかに揃えるとしたら、小文字のほうが幸せになれる人が多いはずです。

2. 改行前後のカンマと演算子は次の行の先頭に置く

次の話題に行きましょう。

カンマや演算子のところで改行する場合は、次の行の先頭にカンマや演算子を置きます。 つまりこの部分ですね。

select
        user_id
        , user_session_id
        , row_number() over (
              partition by user_id, user_session_id
              orderby log_time
          ) as session_step
        , log_time
        , keywords

andなどの論理演算子も次のようにすべて先頭に置きます。

where
    hst.user_id isnulland hst.updated_at isnull

このスタイルを選んだ理由は、記述しているカラムや条件のかたまりが最もわかりやすいからです。

最初のコードを見てもらえばわかるように、ウィンドウ関数やcase式が入ってくると、 インデントだけではselect文にカラムがいくつ書かれているのかよくわからなくなってきます。 しかし不幸にしてSQLではカラムの順序に大変大きな意味があるので、 何カラムめに何の値が入っているかというのはかなり重要な情報です。 そこでカンマや演算子を先頭に並べて書いておくと、それを数えるだけで項目数がすぐわかるわけです。

カンマを前に置くスタイルについては編集上の利点を挙げる人もいますが、わたしは特にそこは重視していません。 あくまでカラムの数と位置のわかりやすさを重視します。

もっとも、演算子はともかくとして前カンマについては、単純に気持ち悪いと感じる人が多いでしょう。 正直わたしも最初のうちは「先頭キモいな〜、これ本当に前に書くのかな〜」と思っていたのですが、 ちょっと面白かったので試しに3日間書いていたら慣れました。 わりと簡単に慣れるので試してみてください。

ちなみに、他のプログラミング言語でも例えばLispのマクロでは前カンマを使いますし、前例がないわけでもありません。 プログラミング言語は自然言語ではないのだから、必ずしも自然言語由来の自然さにこだわる必要はないと思っています。

3. 文末のセミコロンは単独行に置く

3つめの話題。文末のセミコロンは単独で、別の行に置きます。 つまり次のように書くわけです。

select ...
from ...
where ...
;

これは実はあまり大きな理由はなくて、カンマの前置に揃えているだけです。 強いて言うと、psqlなどのコマンドラインクライアントにコピペするときにセミコロンだけ抜いてコピペがしやすい、という利点があります (セミコロンさえ打たなければCtrl-Cでキャンセルできるので、変な失敗が起きにくいのです)。

4. 各句のキーワードはインデントしない

4つめの話題。 ちょっとあまりに面倒くさすぎて信じられないのですが、 世の中には次のようにキーワードを右寄せにしたがる派閥も存在しています。

select count(*)
  from mst.users
 where user_id = 5;

わたしはsyntax off; set sw=4 ai smd ts=8 etですべてのエディタ設定が完了する オールドタイプ人類なのでさすがにこれに付き合う気はありません。 select、from、where、order byのレベルはすべてインデントを揃えます。

そもそも、この右寄せスタイルの効果があるのはそうとうに短いクエリーだけです。 サブクエリーのネスト5段、1 selectごとにカラムが10個……みたいな、 分析系ではよくありがちなクエリーには無用の長物でしかありません。 むしろ各句の左端が揃わなくなるため、サブクエリーのネストレベルがわかりにくいという欠点が目立ちます。

そして何よりも、キーワード右寄せスタイルは「select for locking access」のとき (あるんですよ世の中にはそういう文が……)に26文字インデントしなければならないのが最悪だと思います。 こんなことになるわけです。

selectfor locking access count(*)
                     from mst.users
                    where user_id = 5
;

カラムの右揃えは空白文字と時間の無駄です。黙って左に揃えましょう。

5. join式はfromよりもインデントする

5つめの話題はfrom句内のインデントについて。

ジョインが存在する場合のインデントも判断の分かれやすいところです。 わたしが採用しているスタイルは次のように、joinの記述をfromよりもインデントします。

from
    work.weighted_segments s
    inner join source.factor_score_flg f on s.guest_user_id = f.user_id
    inner join source.factor_score_mst m using (attr_id)

一方で、次のようなインデントスタイルも根強く存在します。

from work.weighted_segments s
inner join source.factor_score_flg f on s.guest_user_id = f.user_id
inner join source.factor_score_mst m using (attr_id)

ですが、このスタイルはわたしに言わせればインデントの役割を完全に放棄しているとしか思えません。

上記の式では3つのテーブルをジョインしていますが、その場合、3つのテーブルをジョインしたリレーション全体がfrom句の値です。 1つめのテーブルだけではありません。 ならば、文法の内包構造をインデントに表現しようとした場合、3つのテーブルと「inner join」は すべて「from」より下位にあるべきで、「from」と「inner join」を同列に並べる選択はありえないでしょう。 そもそも「from」は句ですが、「inner join」は二項演算子です。その時点で両者は対等ではありません。

「何もインデントしない」スタイルを採用するのでない限り、 from句に含まれるコードはすべてインデントすべきです。

6. インデントとネスト、そしてwith句について

6つめの話題はようやくインデントの深さについてです。 わたしのスタイルではSQLのインデントは空白4文字にします。このへんは趣味です。

強いて4スペースにする理由を挙げるなら、バッチではかなりネストの深いサブクエリーを使うことが多いので、 2スペースくらいだとすぐに見分け付かなくてきつい、くらいでしょうか。 4スペースでもよくわからなくなるときがあります。 しかし8スペースまで深くすると今度は200桁くらいになってしまうことがあり、さすがに深すぎます。 SQLの場合はCで関数を分けるような気軽さではサブクエリーを分離できないので、回避しにくいことも問題です。

ちなみにサブクエリーの代替になりネストも減っていいじゃんと話題のwith句ですが、 わたしは実行プラン(explain)を見るまでは絶対にwith句にはしません。 プランナーを信用していないからです。 基本的に、RDBMSのプランナーは新しい構文を使うほどよくしくじります。 新しい構文は用心してかかるのが得策だと考えます。

またSQLバッチに関して言えば、withを使って見ためのネストだけ下げるよりは、 ビューを作ったり、ワークテーブルを追加して対処したほうが見通しがよくなるだろうとも思います。 with句だろうがサブクエリーだろうが途中経過は人間には見えないので、 巨大なクエリーでは開発・運用が難しいことに変わりはないからです。

7. 標準SQLのこと、忘れてください……

最後に標準について。

ことSQLに関して言う限り、ポータビリティを考えてANSI標準だけを使うなどという選択は間違いです。 少なくとも、現実的ではありません。

そりゃあもちろん、O/R mapperが生成する類のこの程度のクエリーなら問題はないでしょう。

select * from entity_table where id = 230985;

そしてこれ以上に複雑なほとんどすべてのクエリーが標準をはみ出すことを覚悟しなければなりません。

……いや、それはさすがに言い過ぎでした。言葉の綾というやつでした。

ですが例えばPostgreSQLのリファレンスマニュアルから適当な構文のページを開いて、 「この構文はPostgreSQL独自拡張です」のフレーズが何回登場するかを数えてみてください。 インデックスを張ったとたんに、upsertをしたいと思った瞬間に、ちょっと特殊な制約を付けたいと思ったばかりに、 サクッとANSI標準から踏み出すことを覚悟しなければならないのがSQLの現実です。 そして、標準の範囲内ですべての処理を書くことよりは、DB固有拡張のほうが必要性が遥かに上です。 標準にこだわってDB固有拡張を避けるのは悪手と言わざるをえません。

そもそもポータビリティを考えるという場合、DBを乗り換えることが前提ですが、 DBをプロジェクトの途中で変えることなどまずありません。 これはむろん鶏と卵の関係にあります。SQLがまともに標準化されていないからDBを乗り換えることができず、 どうせDBを乗り換えることはできないから標準なんてどうでもいいのです。 たいへんウンザリします。

各種RDBMSの差がもうちょっと少なくなってもらえないかという気持ちはやまやまですが、 いまのところは標準は「使えるときだけ使う」のが現実解でしょう。

おわりに

この記事では、わたしがクックパッドで使っている分析SQLのコーディングスタイルについて、7点に絞ってお話ししました。 コーディングスタイルはいつの時代も戦争の引き金になるので、いま大変嫌な予感がしています。 みなさんの手元ではどんなスタイルが使われているのか、はてブコメントなどでお寄せいただければ幸いです。

最小限にこだわるサービス開発の試み

$
0
0

こんにちは、検索事業部の原田です。クックパッドでプロダクトマネージャーとして検索領域を中心にサービス開発に携わっています。

サービス開発を成功に近づけるためのフレームワークやプロセスについては、書籍等で多くの知見が紹介されています。こちらのブログでもクックパッドの例についてご紹介をしてきました。

クックパッドで大事にしているサービス開発の取り組みのひとつに「仮説をスピーディーに最小限の形で確かめる」というものがあります。言うは易しなのですが、そもそも取り組むべき最小限の形とは? それをどうやって見つけるのか? やっとの思いで何かを見つけられたとして、その後最小限にこだわり判断し続けるには? と、大変な困難が伴うと感じています。

今回は、サービス開発プロセスの中でも特に、どのように取り組むべき最小限の形を見つけ、最小限にこだわり続けながら開発を進めるのかについて、私が気をつけていること(気をつけたいと考えていることも含みます)をご紹介したいと思います。

取り組むべきサービスの判断基準

本題に入る前に前提の話として、そもそも取り組むべきサービスとは何でしょうか。会社の状況や責任範囲により異なりクックパッド内でも考え方は様々で一律の基準は提示できませんが、私の場合は以下のような着眼点を持つことで目指す的をなるべく狭くできるようにしています。

  • それは料理を楽しくするかどうか(情熱をもって取り組めるものか、ユーザーにとって価値のあるものであるか)
  • それは誰にも負けないことかどうか(実現可能なことか、世界一になれる部分はどこか)
  • それは儲かるかどうか(経済的な原動力になるのは何か)

この三つの重なりを意識することが、後々最小限を維持するための拠り所になります。サービス開発に限った話ではありませんが、このような考え方は、書籍『ビジョナリーカンパニー2~飛躍の法則~』の中では「針鼠の概念」として紹介されています。もちろん、初めから三つを重ねるようなアイデアはないことがほとんどです。重ならなかったら着手しないということではなく、開発していく過程で3つを重ねることを常に問い続けるプロセス自体が大事だと考えています。

最小限であることがなぜ大事なのか

ここからが本題なのですが、そもそもなぜ最小限にこだわる必要があるのでしょうか。反対側から考えてみると、最小限でないことにより以下のようなことを失うのではないかと考えています。

  • ユーザーに届くまでのスピード(製品化に至るまでには時間がかかる、機能が多ければそれだけ確かめるまでが遠くなる)
  • コア機能に集中できるメンバーの時間(一緒に働きたいと思うメンバーは常に有限。機能を足す判断をするごとに、最初に定義した取り組むべきコアな部分を開発できるメンバーを失う)
  • 検証のシンプルさ、明確さ(選択肢が多い=ユーザーは判断をせねばならない。ユーザーの生活は十分に忙しいはず。どの機能を使うかの判断をしなくてはならないようなサービスが本当に愛してもらえるかを考える必要がある)

機能を広げる判断をするときには、必ず同時に対で何かを失う判断をしているということを認識するようにしています。

最小限はなぜ難しいのか

初期の段階では、慎重に最小限の形をイメージしていることが多いと思うのですが、プロセスが進むにつれて最小限にこだわることへの難易度が上がっていきます。例えば具体的には以下のような状況が生まれます。

  • 関わるメンバーが増え、より良いアイデアについて議論の場が生まれる。
  • プロトタイプを使ったユーザーからこういう機能があったら便利、ここが不便、という具体的なフィードバックを得る場が生まれる。

上記の例はどちらも、サービス開発を進める上で必要であり不可欠あることは疑いようがないですが、機能を広げる方向に議論がおきやすくなるのは事実です。そもそも多様なニーズをサポートしていないという背景とともに、実現すべき体験・価値といったものの言語化が困難なことが機能追加へのプレッシャーを加速しがちです。上記の例で言うと一つ目のほうは「最小限とは?」の定義が不十分であれば当然は議論は発散しますし、二つ目のほうは利用者の具体的な声は説得力があるのに比べて、機能追加をするデメリットは言語化しづらいことが多いのではないでしょうか。

取り組むべき最小限を見つけ、集中し続けるために

ここまでは最小限であるべき理由の話でしたが、次は最小限の形を見つけるために具体的に実践していることをご紹介します。

企画発想:現実世界の代替手段を注視

企画発想の時点で価値を定める時に「機能ではなく体験をイメージする」という話があります。しかしそもそも体験を言葉にするのはとても難しいことです。

そこで、今その問題を抱えている人が現実世界で行っている解決方法に着目するようにしています。そのことで、言葉に表現せずともメンバー間で議論しようとしている体験のイメージを揃えることができるようになります。現実世界というところがポイントです。もし現実世界での解決方法がないのだとすると、それは取り組むべき十分切実な問題ではないかもしれないこともチェックできます。

例えば、献立に困っているユーザーさんは今どういった解決方法をとっているか? という問題に着目した時に、「Googleで検索している」だと、スマホを手にして情報を探索する体験に縛られてしまいます。現実世界で考えるとどうなるか。例えば、「本屋さんで棚にある雑誌をざーっとながめ、手に取りぱらぱら〜っとする」「母親に電話してアレどうやって作るの?と聞く」「給食の献立表を眺める」という具合になります。

そうすることで上記の例では以下のように体験を具体的にイメージすることができるようになります。

  • 「ざーっとながめたり、ぱらぱら〜っとする」という体験にはどういう意味があるのだろうか→一度にバッと見られることって何か意味がありそう
  • 電話は友だちに訊ねるではダメなのか?→母親に「あのよく出てたタコの炒めた感じのアレどうやって作るの?」って、家でよく出た「アレ」は名前がはっきりしていなかったりする
  • 給食の献立ってレシピないけれどどういうことなんだろう?→給食の献立表ってメニュー名だけだけれどなぜか大体どんな味か想像できるな

このように現実世界を注視することで、言葉にしにくい体験を具現化しやすくなり議論の発散を防いでくれると考えています。

施策開始前:最小限の形を見つけるためのプロトタイピング

上記で定義した体験を製品に落とし込んでいくわけですが、多くの場合はもやもやしながらスタートします。私は、この時点でコンセプトなど言語化の詰めを急がないようにしています。なぜなら、言葉の稚拙さのせいで可能性を閉じてしまうことがあるからです。その代わりに最小限の形を見つけるためのプロトタイピングを行っています。

プロトタイピングするというと、施策をスタートさせた後に行うイメージがありますが、私はコンセプトを決めた後に行うプロトタイピングと、取り組むべき最小限の形を見つけるためのプロトタイピングをわけて考えるようにしています。今回の題材である「取り組むべき最小限の形を見つけるためのプロトタイピング」は、言語化することの限界を超えるためのプロセスとして考えていて、ゴールは技術やビジュアルの仕様を固めることではありません。「私だったら今まで週1回しか使っていなかったけれども毎日使っちゃう」という感覚に、自分以外の人が触わり議論をスタートできる状態にできることがゴールです。ここまでは、言語化が必要ない人数で進める必要があります。

上記の状態を達成できてはじめて、施策化し開発を開始するかどうかの判断ができます。裏を返せば、上記の状態が達成できなければ「開発をしない」という判断をするということです。そういう意味で、開発スタート後のプロトタイピングとは大きく役割が異なります。

施策化:圧倒的な「売り」を見つけて言語化・数値化

もやもやとしたアイデアをめでたく 「私だったら今まで週1回しか使っていなかったけれども毎日使っちゃう」という形にできたところで、ようやく言語化に取り組みます。仲間も増えて開発をスタートできるこのフェーズでは、以下の問いに対する解をもった言葉(=売り)を持つことが、最小限からブレない判断をサポートしてくれます。

  • 利用者の生活をどう変えるか?
  • 競合と比較して圧倒的に差別化された利点は何か?

「売り」の概念や言語化については今回は詳しくご紹介しませんが、ご興味がある方は、『「売れ顔」の法則』をはじめとした消費財におけるマーケティングに関する情報がとても役立ちますのでご覧ください。

売りを一言にできたところでその言葉を数字で表現し、あとは数字を上げるよう行動します。 私の場合はこのように整理して共有したりしています。

f:id:a_harada:20161124182810p:plain

この後も常に機能追加のプレッシャーを受け続けることになるため、取り組むべき最小限にこだわるために何かを判断する時は以下のことに気をつけています。

  • それは数値を10倍にするか?
  • それは絶対に他の人も毎日使うはず、と思えるか?「増えそうだ」「良さそうだ」ということになっていないか?

なお、「数字を上げるよう行動する」についてはこちらブログでも紹介されている「新サービス立ち上げ時の重要指標のデザイン」等に詳しいので、ぜひご覧ください。

施策開始後:最小限以外の事項は、検討の「解禁日」を設定

開発を開始できるとコアな価値自体とは関係がなくても、サービスを届けるためには検討が必要な事項や機能が必ず出てきます。例えば既存のサービスのどこからアクセスしてもらうか、そこに今入っている機能とどう折り合いをつけるか、といったことがその一例です。これは検討しているサービスが公開へのフェーズに向かっている証拠であり、喜ばしい事態です。

同時に、例えば既存機能との調整は特にもやもやとした新しいサービスの価値よりもはるかに具体的にややらなくてはいけないことがはっきり可視化できるゆえ、まだ最小限の形ができてないうちから時間を使ってしまうことが問題です。結局ユーザーに使われなければいくら調整がうまくできても本末転倒、という事態を避けるために、この案件はここの日から考え始める、という「解禁日」を設定しスケジュールに明記して周囲に宣言するようにしています。そのことで、それまでは最小限の実現だけに集中できる環境を用意することができます。

「最小限」かどうかの確かめ方

あなたが今取り組んでる施策の「ターゲットでない人」、瞬時に言えますか?

もしも途中で「今最小限の形で進められているかな?」と不安になることがあったら、この問いを思い浮かべてみてください。瞬時に具体的に答えられない場合は危険信号。最小限の製品ではターゲットは限られた人であり、ターゲットではない人が明確に存在するはずです。

まとめ

ゼロからのサービス開発でも既存サービスに対する開発であっても、機能を広げない判断は大変です。良さそうな機能をどんどん追加できるなら… という誘惑にいつも揺らぎそうになります。しかし、機能を追加する判断をすることは、その機能が良いとか悪いということとは別に、必ず対でユーザーに判断という負担を強いること、開発する仲間の集中を失う判断をしているということに他なりません。開発する仲間の力が分散することは、サービス改善が遅れる、もしくはそもそもできないという形でユーザーに反映されます。

気がついたら複雑すぎてメンテナンスされていない機能がたくさんあるというような不幸な状態は誰も望んでいないはずですが、前述のように言語化に限界がある以上そうなりやすい状況は必然であることを前提に、判断をサポートする仕組みづくりに取り組んでいきたいと感じています。

最後になりますが、クックパッドでは既に存在するものにとらわれない視点でサービス開発に取り組むことに興味のある方を募集しています。ぜひご応募ください!

実例に基づいた大規模 iOS アプリの継続的な開発についての勉強会を開催しました

$
0
0

f:id:nano-041214:20161201191121j:plain

技術部モバイル基盤グループ新卒エンジニアの日高(@natan3)です。

去る11月17日、「Cookpad Tech Kitchen #4 〜Cookpad × MoneyForward〜」と題して、iOS エンジニア向けの技術交流イベントを行いました。

https://cookpad-tech-kitchen.connpass.com/event/43082/

このイベントでは、 iOS開発の中でも特に大規模アプリの品質を維持するための設計や、複数の言語圏や様々なパートナー企業に合わせたアプリの提供をテーマに、弊社のエンジニアから2つ、マネーフォワードさんから2つの発表がありました。

この記事では、その様子についてお伝えします。

iOSアプリケーションの海外展開

まず、海外事業部の西山(@yuseinishiyama)から、海外事業向けのiOSアプリケーションの開発フローについて紹介がありました。

How to make your app international by Yusei Nishiyama Published November 18, 2016 in Programming

この発表では

  • 言語圏に合わせたコンテンツや UI のローカライズ
  • RTL 対応
  • その他アプリのローカライズに関する Tips

などについて紹介しました。

私個人としては、普段日本向けの開発しかしていなかったので、アラビア語圏への対応などとても興味深かったです。

また、グローバルプラットフォームならではのサービス開発に対する取り組みは以下の記事でも紹介しています。

海外のユーザーを向いたプロダクト開発の工夫iOSアプリケーションの国際化と地域化

トクバイ新アプリと Swift と

次に、クックパッドの子会社であるトクバイの行川(@hatuyuki4)から、トクバイアプリがどう Swift で開発を進めているかについての発表がありました。

トクバイ新アプリとSwiftと by Hatuyuki4 Published November 21, 2016

この発表では、Swift + RxSwift + MVVM の実装について具体例を交えながら紹介しています。

リアクティブな処理にすることで複雑なコールバックによる辛さから開放されたり、UnitTest が書きやすくなったりと Rx のメリットがわかりやすく示されているので、Rx 導入を考えている方にぜひオススメしたいスライドでした。

Ruby on Rails にはなりますが、react-rails を用いたアプリを開発する際の知見についてはこちらの記事で紹介しておりますので、興味の有る方はぜひこちらも御覧ください。

非SPAなサービスにReactを導入する

iOS Clean Architecture のすすめ

3番目の発表では、マネーフォワードさんの iOS エンジニアである児玉さんから、マネーフォワードではどのような設計で iOS アプリを開発しているのかについての発表がありました。

iOS Clean Architecture のすすめ by koutalou Published November 17, 2016 in Technology

この発表では、Clean Architecture の実装例と導入する際のメリットを紹介しています。

児玉さんの Github アカウントで iOS-CleanArchitecture のサンプルが公開されているので、私も試してみようと思いました。

児玉さんが執筆されたマネーフォワード エンジニアブログの記事はこちらにありますので、tvOS やマネーフォワードの開発体制に興味がある方はぜひこちらも御覧ください。

マネーフォワードのtvOSアプリケーションを開発した話

マネーフォワードの変遷と「パートナー企業版」の展開

最後に、マネーフォワードさんの iOS エンジニアで、取締役の都築さんから、マネーフォワードがどのようにアップデートを重ねてきたかについて発表がありました。

マネーフォワードの変遷と「パートナー企業版」の展開 by Takayuki TSUZUKI

この発表では、マネーフォワードさんがかつては Titanium で開発されていたという意外な事実や、パートナー企業版の開発で起きた問題をどうやって解決したかについて紹介しています。

アプリのリニューアルはとても大変な作業なので、きっと色々な苦労を経て今のマネーフォワードさんのアプリがあるんだなと感じました。

都築さんが執筆されたマネーフォワード エンジニアブログの記事はこちらにありますので、マネーフォワードさんのアプリのリニューアルについてもっと詳しく知りたい方はぜひこちらも御覧ください。

iOS App資産タブリニューアルを終えて

質疑応答

f:id:nano-041214:20161201191148j:plain

質疑応答では、

  • Clean Architectureの導入に伴う初期コストをビジネスサイドにどのように説明したか
  • Rx を実際に取り入れる過程や導入後の開発効率の変化

に関した内容の質問に対して

  • 複雑化してメンテナンス性が下がったコードに対して、開発効率が10倍になる!などで熱意を示してきっかけを作ったこと
  • 全員が理解していればすごく効率が上がるが、移行期には同じ処理を二つのアーキテクチャで書く必要があるなどの問題もあること

といった回答を得られました。質疑応答も大変盛り上がり、とても有意義な時間になったと思います。

懇親会

上記の発表の後、懇親会を行いました。

懇親会では、他の弊社の iOS アプリエンジニアやデザイナも参加しながら、参加者と様々な意見交換や雑談をしました。

多くの皆様にご参加いただけたおかげで、iOS 開発にありがちなことで盛り上がったり、各社の iOS 開発の知見を共有するなど、有意義な時間を過ごすことができました。

今回は合同イベントということで、弊社のロゴとマネーフォワードさんのロゴを用いた押し寿司も振る舞われました。

f:id:nano-041214:20161201191234j:plain

まとめ

いかがでしたでしょうか。

クックパッドでは、今後もエンジニア向けのイベントを行っていきたいと考えています。来る 2017 年 1 月 21 日には、 Cookpad TechConf 2017を開催する予定です。

また、クックパッドでは、一緒に iOS アプリ開発を行っていくエンジニアを募集しています。ご興味の有る方は是非遊びにいらしてください。

青森観光アプリ開発コンテストに参加してきました

$
0
0

こんにちは。研究開発部の山田(@y_am_a_da)です。

11/18(金)〜20(日)の3日間、「観光」をテーマにしたアプリケーションの開発コンテストである「青森観光アプリ開発コンテスト」が星野リゾート 青森屋で開催されましたのでその報告をさせていただきたいと思います。

青森観光アプリ開発コンテストとは

青森県のファンを増やすため、青森屋が県内各地域の魅力を全てお客様に伝えきることの出来るアプリを企画・開発するコンテストです。

今回のコンテストでは、実際に観光客の目線を持てるように、参加者は青森屋に2泊3日の宿泊をしながら企画・開発を行いました。

クックパッドからは、投稿されているレシピのデータを提供するとともに、コンテスト当日もサービス開発やデータの使い方に関するアドバイザーが数名参加しました。

また、アマゾン ウェブ サービス ジャパンからもAmazon Web Serviceのアドバイザーが参加していました。

1チーム最大5名で、下は21歳、上は50歳と非常に幅広い年齢層の人々が集まり、アプリケーションの企画・開発を行いました。 当日に即興で決まったチームもいくつかありましたが、みなさんすぐに打ち解けて積極的にディスカッションを行っていました。

成果発表

コンテスト最終日に行われた発表は、作成したアプリケーションがどう役に立つのかを短時間の劇で表現するという形式のものでした。 いくつか賞がありましたが、その中でも青森屋賞、Amazon Web Service賞、クックパッド賞についてご紹介致します。

青森屋賞

  • チーム名:KBS
  • タイトル:青森屋探検隊 これなにアプリ

青森屋にはさまざまなものが展示されているのですが、それがなんなのかよくわからない!という自身の体験から生まれたアプリケーションです。

アプリケーションの内容は以下のとおりです。

  • 青森屋にある展示物にアプリを起動した状態でスマホのカメラをかざすとそれが何かを教えてくれる
  • アプリ内に青森屋の全体図を入れておき、ゲーム要素を追加して、青森屋を探検したくなる仕組みを作る

特に後者の機能が小学生の夏休みの自由研究のテーマとしても使ってもらえるのでは。ということで受賞となりました。 こちらは青森屋で来年サービス実現にむけて取り組むそうで、実現の際に入賞チームが宿泊御招待という副賞がついていました。

Amazon Web Service賞

  • チーム名:necco
  • タイトル:青森屋Watch

「体験をもう1つ多く」をコンセプトに青森屋で起きている色々なイベントを体験できなかった。いつどこでやるのかわからなかった。ということを防止するアプリケーションです。 青森屋では青森にまつわる様々なイベントが日々行われており、滞在中の満足度向上の他「他にもこういうのがあった、見たい」と知る事で再訪につながるそうです。

アプリケーションの内容は以下のとおりです。

  • 青森屋のどこでいつ何がやっているかがわかるようにタイムラインとかオススメが見れる

こちらのチームはある企業から5人組で参加しており、抜群のチームワークで最も高いデモンストレーションのクオリティを見せていました。

クックパッド賞

  • チーム名:青森フレーバー
  • タイトル:里山の郷土レシピからみつける、私だけの旅レシピ

こちらは独立したアプリケーションではなく、クックパッドに機能を追加するというものでした。内容は以下のとおりです。

  • 各地域の人が位置情報とともにレシピを載せることができる。
  • 「レシピから自分の旅のルートを決める」という項目をクックパッドにつけ、利用者は作ってみたいレシピベースで旅行の経路を決めることができる。

旅行ではその土地の郷土料理を食べることが楽しみの1つとなっているということ、実際に料理教室にその土地に旅行に来た人が来ることが少なくないということから考えられたそうです。

まとめ

私自身もコンテストが始まる前のアイスブレイクとして、頭の体操、ブレインストーミングのようなものに参加したのですが、置かれている環境や、価値観の違った人と意見をぶつけ合い、答えを出す作業というのはとても刺激的で楽しいものでした。

また、こういったコンテストにはメンターの方が来られることも多く、自分の知らない分野の疑問についても専門家の目線からアドバイスを頂けることがあるのでかなりお得感がありました。

クックパッドでは積極的にハッカソンを開催しており、直近では中高生No1を決めるcookpadハッカソンも開催します。今後も積極的にハッカソンなどを開催していきますので、その際にはぜひご参加頂ければと思います。

EarlGreyを使った画面操作を伴う自動テスト

$
0
0

こんにちは、技術部品質向上グループの茂呂一子です。

品質向上グループではモバイルアプリにおける自動化されたテストの一環として、画面操作を伴うテストを実施しています。 例えば、古くからAppiumを使い、その結果を判定するという施策を行っています。(参考: iOSアプリデザインリニューアルの舞台裏の舞台裏)

クックパッドではAppiumも利用していますが、より高速に実行できるツールとして、EarlGreyというGoogle製のフレームワークの導入を試みています。

この記事では、EarlGreyの導入と現状についてまとめます。

内容は、iOS Test Night #1でLT発表した内容からの抜粋です。LT資料はこちら

画面操作を伴うテストの自動化

クックパッドでは、 モバイルアプリの最低限の機能を確認する用途で画面操作を伴うテストを自動化しています。 このテストは、Android/iOSアプリのテストの区分戦略にて定義しているサイズをもとにすると、L/Eサイズに当たるような領域にを対象にしています。 ユーザーに提供する体験を簡易的なシナリオとしてまとめ、 それが完遂されることとレイアウトのくずれが発生しないことをみて、最低限のアプリの価値を確認しています。

画面操作の自動化を支援するツールとして、Appiumを聞くことが増えてきたように思います。

クックパッドでもAppiumは利用していますが、開発ラインに組込むにはよりCIへの組込みが容易で、より高速に実行できるツールが望ましいです。 その候補となるツールには、XCUITest、EarlGreyなどがあります。

Xcode 7が出た頃にXCUITestを試しましたが、テストプロセスの起動に失敗したり、画面上の要素(ボタンなど)の検出に不安定さがあり、実用を目指すには至りませんでした。 その後、EarlGreyが登場し、試用をすすめています。

EarlGreyを使ったテスト

EarlGreyは、Google製のUI Automation Test フレームワークです。 Xcode の提供する XCTest Framework の上で動き、Xcode上でコーディングして実行することができます。

Appiumに比べると、 実装が製品コードに近い場所にあり、実行速度が速く、XCTestの拡張なのでCIへの組込みが容易です。 一方、Swiftコードで記述するために可読性が下がるほか、 テストのライフサイクルが狭いためにシミュレータの初期化できず、前後のテスト実行の成否に影響を受けやすいです。

EarlGreyは、基本的な機能がそろっており、特定の要素が表示されるまでスクロールするなどの便利な機能も提供されています。 しかし、クックパッドアプリで使うにはひとつ足りなくて困ることがあります。

SystemAlertを操作したい

iOSには、カメラの使用やプッシュ通知の許可など、ユーザーに許諾を求めるダイアログがあります。(以下システムアラート) システムアラートは、許可する/しないを選択する必要があり、画面操作をする上で避けて通ることができません。

f:id:ichiko_revjune:20161209203230p:plain

XCUITestでは、システムアラートの操作が可能となっていますが、EarlGreyはXCTest上で動くため、この操作ができません。 EarlGreyのリポジトリにissueがありますが、すぐに解決ができない状態のようです。 このissueの中に、Facebook/WebDriverAgentが使えたというコメントがありました。 これをヒントに、WebDriverAgentLibを使用して実現を試みました。

以下の試みは、EarlGrey v1.3とXcode 7.3.1、Swift 2.2を使用しています。

WebDriverAgentを使ってシステムアラートを操作する

WebDriverAgentLibは、Carthage対応されていますが、試した時点ではコンパイルできないなどの不具合があり、一部のコードをインポートする形で導入しました。

WebDriverAgentLibの導入には手間がかかりましたが、これによってシステムアラートの操作は可能になりました。 以下のようなコードで、システムアラートの項目を選択することができます。 (FBAlertは、WebDriverAgentLibが提供するクラス)

letalert= FBAlert(application:XCUIApplication(privateWithPath:nil, bundleID:"BUNDLE_ID"))
ifletalertElement= alert.alertElement() {
    letbuttons= alertElement.descendantsMatchingType(XCUIElementType.Button).allElementsBoundByIndex
    letbutton= buttons.filter { $0.label == label }.first
    ifletbutton= button {
        button.tap()
    } else {
        GREYFailWithDetails("Labled Button '\(label)' on Alert Dialog was not found.", details:"")
    }
} else {
    GREYFailWithDetails("Alert view was not found.", details:"FBAlert.alertElement returned nil.")
}

残念なことに、製品コードの開発が Xcode 8 に移行してからは、FBAlertを使ってシステムアラートを操作することができなくなりました。 WebDriverAgentはPrivate APIを使用するため、Xcodeのバージョンが変わることで整合性がくずれたのでしょう。

しっかり使って育てていきたい

EarlGreyはOSS(オープンソースソフトウェア)として公開されているフレームワークなので、必要な実装は追加していくことが可能です。 今回は、コミットするところまでいけませんでしたが、自分たちが使っていきたいものなので、まだ育てるフェーズと思ってどんどん試していきましょう。 クックパッドでは業務時間中のOSSへの貢献も認められているので、フレームワークへも貢献しつつ、それを活用していきたいですね。

クックパッドではこのような取り組みを共に実施し、より良いサービスを提供し続ける為にエンジニアを募集しています。ご興味のある方は、是非とも覗いてみてください。

Interface Builderをデザイナーさんに触ってもらうにあたってやったこと

$
0
0

こんにちは、投稿開発部の市川です(@masaichi)
主に、クックパッドiOSアプリの投稿周りの機能を担当しています。

はじめに

みなさんはiOSアプリを開発する際に、どうやってレイアウトを調整していますか? クックパッドでは大体の場合は、デザイナーにZeplinなどでレイアウトの指示書を貰いエンジニアが実装するという流れで組んでいます。 しかし、このやり方の場合、終盤の細かなデザインの調整の際に、修正と確認が細かく発生してしまい、デザイナーとエンジニアの時間を細切れに使ってしまう、という問題がありました。

今回、この解決の手段として、終盤のデザインの調整をデザイナーさん自身にInterface Builderで調整をしてもらうトライを行いました。 10月頃、iOSアプリに追加した「みんなの投稿」機能を題材に、この過程と効果を紹介します。

なぜInterface Builderを触ってもらうことにしたのか

「終盤の細かなデザインの調整の際に、修正と確認が細かく発生してしまい、デザイナーとエンジニアの時間を細かく使ってしまう」という問題の解決として、時間を決めてペアプログラミングをする、というのもあります。 これも短い時間で集中して対応することが出来るのでうまくワークします。

とはいえ、この手段もエンジニアが実装をしてデザイナーが指示を出し、というやり方になるので、こだわりきれない点もあるのでは・・と考えていました。

そんな時、夏にあったiOSDCというカンファレンスで「デザイナーにStoryboardをお任せする技術」という話を聞いたことと、一緒に仕事をしているデザイナーさんからInterface Builderを覚えたいという話があったため、トライしてみよう、ということになりました。

みんなの投稿画面

今回対象にした「みんなの投稿」は以下のような画面です。

f:id:masarusanjp:20161226102904p:plain:w320

1つのタブの中身はUITableViewを使ったリストで、リスト中の1セルは高さも固定、表示する要素も条件で出たり消えたりしない、という画面設計になっています。
Interface Builderを使った最初のトライにはこのようなシンプルな画面が適してると考え、これを選択しました。

以下、実際に機能が取り込まれるまでに行ったことを書いていきます。 「デザイナーにStoryboardをお任せする技術」に沿った内容となっています。

1. ViewControllerとView, Autolayoutのことを教える

まずは座学とペアプロでViewController, View, AutoLayoutの事をざっくりと学んでもらいました。
Interface Builderはグラフィカルで取っ付き易いツールだと思いますが、 iOSアプリケーションの構造を知らずに触っても、何が起こるのか想像がつかず、混乱するだろうと考えたからです。

事前に資料を渡し、ホワイトボードで図を交えて教え、ペアプログラミングをして実際に動かしてもらいました。

f:id:masarusanjp:20161226103058p:plain

いきなり全部を覚えて貰うのは、新しいことが多すぎるので難しいと考えて、これは2回に分けて行いました。
それぞれを1時間〜1時間半くらいで2日に分けました。

本題に入る前に補足をしますと、座学がたったの2日で合計3時間というのはかなりすんなりと行ったと思っています。 その背景として今回一緒にトライを行ったデザイナーさんは 普段からCookpad本体のRailsアプリケーションのCSSやHTMLに関しては自ら修正してGitHubでPRを投げており、ターミナルでの作業や開発フローに慣れている。 また、その開発を行う関係で「手元にXcodeなどの必要なツールキットも揃っている」というのがあると思います。

ViewControllerとView

初回ではViewControllerViewについて学んでもらいました。
大事な点は2つあると考えています。

  • iOSアプリケーションで、ViewControllerViewがどういう役割で、どう見えているのか
  • Interface Builder上ではそれらはどう見えているのか

1つめについては、View Controller Programming Guide for iOSに十分な内容があり、こちらを利用して説明を行いました。
具体的にはiOSのアプリケーションは1つの画面に最低でも1つのViewControllerがあり、複数のViewViewControllerの持つViewの上に配置されることで、画面を構成している、というような内容です。

2つめについては、1つめの点を踏まえてペアプログラミングをしながら実際に手を動かして学んでもらいました。 題材はMaster-Detail Applicationにしています。初期状態で画面遷移があり、1つの画面に最低でも1つのViewControllerというのが動かしながら見せられるためです。

AutoLayout

次にAutoLayoutについてです。 AutoLayout制約をViewに与えることでレイアウトを決める、Appleの機構です。 ここでの大事な点は以下3点だと考えています。

  • AutoLayoutのレイアウトのための考え方
  • Interface Builder上での設定の仕方
  • Interface Builder上で発生し得るエラーとその対処方法

1つ目のAutoLayoutの考え方については、Auto Layout ガイドAutolayoutの考え方のページに詳しく書かれています。
ホワイトボードで図や式を書きながら、AutoLayoutの制約に基づいたレイアウトの考え方を教えます。「デザイナーにStoryboardをお任せする技術」でもお話がありますが、数式や図を交えて説明をすると、すんなりと腹落ちをしてくれていました。

2つ目はInterface Builder上での制約の設定の仕方です
急に粒度の細かな話しになりますが、大事だと考えています。Interface Builder上での制約の設定は結構クセがあって混乱の元になるからです。(例えば、8px空けていたところを12pxにしたいと考えて、pinを選択して12と入れると、同じ属性に対する制約が複数出来てエラーになる、というのはありがちでわかりにくいミスだと思います)
ここからは、ペアプログラミングをしながら、実際のツール上で触って設定の仕方を教えています。

3つ目は、Interface Builder上で発生するAutoLayoutに関するエラーがどういうケースで発生するのかとその対応について教えました。
エラーの場合は、AutoLayoutが座標を決められない状態なので、エラーの内容を見て決められるように制約を設定しようとか、ワーニングは単に制約とInterface Builder上の位置がズレているので、制約か配置のどちらかを合わせてあげれば問題ないよ、といった内容です。 Interface Builderで制約を設定する過程でどうしてもエラーが発生する事があり、それぞれの対応方法を教えておくと、デザイナーが作業するときに、エラーに悩まされて不安になることがなく、良いと思います。

2. 実装

座学が終わったので実装に入ってもらいました。

エンジニア側で「あとはInterface Builderでレイアウトの調整をすればmasterにPRが出せる」という状態にまでして、 GitHubのissueにどのファイルを編集する必要があるのか書いて受け渡しています。

f:id:masarusanjp:20161226103110p:plain

ファイルの受け渡しはFeatureブランチを切っておいて、それをpullしてもらう形です。 ファイルの内容の細かなところは口頭やSlackで説明しています。

渡したときには画面はこんな状態でした。

f:id:masarusanjp:20161226102916p:plain:w320

これをSlackや口頭で相談を受けながら整えていってもらいます。

f:id:masarusanjp:20161226105553p:plain

3. PRを送ってもらう

整ったところで、FeatureブランチにPRを投げ貰いました。

f:id:masarusanjp:20161226102938p:plain

xibのレビューも含めて行います。例えば、misplacedが残っていないか、下位互換を考慮すると厳しいものが入っていないか等。 一通りの修正をしてもらい、終わったところで、Featureブランチにマージします。 その後、エンジニアがFeatureブランチからmasterにPRを出し他のエンジニアのレビューも受けてmasterにマージしました。

f:id:masarusanjp:20161226102951p:plain

効果

今まで終盤で起こりがちだった、「iPhone6では良いのだけど、iPhone5や4sではレイアウトを調整をしたほうが良い」というケースがあります。 ここに細かくデザイナーとエンジニアの時間を細かく使ってしまっていました。
しかし、今回はデザイナー自身がシミュレータで確認をしながら調整をしてくれたので、この問題が解消されました。

今回のケースは「iPhone5の場合にサムネイルが大きすぎる」という問題でした。
左のレシピ作者のアイコンを少し小さくする等の調整はAutoLayoutの制約を利用して、デザイナーさん自信が調整をしてくれています。
(※ セルの高さのみコードで調整しています)

Before After
f:id:masarusanjp:20161226103043p:plain:w240f:id:masarusanjp:20161226103052p:plain:w240

感想と振り返り

実施後、KPTを用いて振り返りを行い、以下のことがでてきました。

エンジニア

エンジニアとしては、まず問題が解決出来たことも良かったと考えいます。 また自身のAutoLayoutへの知見も深まりました。leadingleftの違いや、equal以外の関係性の使い所等。ついコードを書いて解決してしまっていたところを、一緒にAutoLayoutの制約でなんとか出来ないかを考えて解決できたのは、良い経験でした。

デザイナー

みんなの投稿のデザイン調整を行うことができ、Interface Builderを触れるようになりました。 目的が、リソースがいっぱいいっぱいのエンジニアの細かいデザイン調整の時間を減らすことだったのでそれを達成できたのは大きかったです。逐一エンジニアの席にいって「あと数ptずらしてください…あっやっぱり戻してください…」のような時間がかかるコミュニケーションが無くなり自分自身で調整して、シミュレーターで確認できたのは大きかったです。Interface Builderを使いこなせてないので、今後もInterface Builderが使える場面では積極的に触っていこうと思います。

課題

まだ簡単な画面をトライしただけなので「他の画面ではどうなのか」「結局のところデザイン確認がコードレビューに逆転しただけになってしまっているのでは」など、考えられる課題はたくさんありそうです。

また、デザイナーにInterface Builderを触ってもらうのでなく、エンジニアがSketchを受け取ってレイアウトを調整する取り組みもありそうです。 実際にフリルさんでは、その取り組みについて記事が書かれています。(フリルのiOSアプリ開発におけるエンジニアとデザイナーの作業分担について)

Sympliのような、SketchのデータをInterface Builderへいい感じに取り込んでくれるプラグインも出てきており、試してみたいです。

どの手段を取るにせよ、大切なことはエンジニアとデザイナーがお互いを尊重して歩み寄っていく姿勢だと私は考えています。

まとめ

今回のトライでは、目論見通りエンジニアもデザイナーも双方で細かな調整と確認で時間が取られることがなく、終盤の細かなデザインの修正が行えて良かったのではないかと考えています。

アプリを開発をする上での1つの事例として参考になれば幸いです。

Swiftプロジェクトのビルド時間を計測・改善するxcprofilerを作った話

$
0
0

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

我々のチームでは、iOS/Androidアプリの認証、決済、ロギングと言った基幹部分の開発のほか、各事業部のモバイルエンジニアの開発効率を上げるための業務改善を日々行っています。

その一環として、さまざまなモバイル開発上の指標を収集・監視し、問題の発見や、施策への効果計測に利用できるようにしています。 例として、iOS/AndroidのCIの実行時間や、開発期間中のissueの量の変化、コード全体のSwift対応率などがあります。

f:id:gigi-net:20161228111203p:plain

f:id:gigi-net:20161228111221p:plain

収集したデータは、オープンソースのデータビジュアライゼーションツールであるGrafana上にダッシュボードを作成し、監視しています。

この記事では、iOS版クックパッドアプリでビルド時間を計測、改善をした事例についてご紹介します。

コマンドごとの実行時間の計測

まず、CIサーバーで実行されている各Shellコマンドについて、コマンドごとの実行時間を調べ、合計時間の経過を監視するようにしました。

しかし、コマンドごとに見てみると、この計測方法では、xcodebuildに大部分の時間がかかっていることが判明し、これだけでは具体的な改善が難しい状況でした。

f:id:gigi-net:20161228111238p:plain

ビルド時間の内訳について詳細に把握する方法はないのでしょうか。

コンパイラフラグによるビルド時間の計測

この問題はXcode標準のデバッグ機能を利用することで解決します。

Xcodeプロジェクトに以下のコンパイラフラグを追加すると、それぞれのメソッドについてのビルド時間を計測してくれるようになります。

-Xfrontend -debug-time-function-bodies

f:id:gigi-net:20161228111139p:plain

しかし、結果はプレーンテキストとして出力され、閲覧、収集のしづらいデータフォーマットでした。

また、このログを整形して表示してくれるGUIツールはありましたが、CI上で実行することができず、運用が難しい問題もありました。

xcprofiler

そこで、Swiftコードのビルド時間をメソッド単位で計測して様々な形式で出力するユーティリティ「xcprofiler」を開発しました。

xcprofilerはRubyGemsから導入できます。

gem install xcprofiler

xcprofilerは最新のビルドログを自動で取得し、整形して表示してくれるCLIを提供します。

例えば、以下はCookpadのiOSアプリで実行した例です。

$ xcprofiler Cookpad -l 10
+-------------------------------------------------------+------+---------------------------------------------------------------------------------------------------------------------------------------------------------------+----------+
| File                                                  | Line | Method name                                                                                                                                                   | Time(ms) |
+-------------------------------------------------------+------+---------------------------------------------------------------------------------------------------------------------------------------------------------------+----------+
| xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.swift              | 9    | final didSet {}                                                                                                                                               | 5003.8   |
| xxxxxxxxx.swift                                       | 44   | init(title: String?, message: String?, content: Content?, actions: [AlertAction])                                                                             | 143.8    |
| xxxxxxxxxxxxxxxxxxxxxxxxx.swift                       | 11   | func cellHeightFromConstraints<CellType : UITableViewCell>(tableView: UITableView, createFromNib: Bool = default, cellUpdater: ((CellType) -> ())) -> CGFloat | 117.0    |
| xxxxxxxxxxxxxxxxxxxxx.swift                           | 55   | @objc func myFolderDidRemoveRecipeNotification(_ notification: Notification)                                                                                  | 115.9    |
| xxxxxxxxxxxxxxxxxxxxxxxxxxxx.swift                    | 136  | private func assets(in rects: [CGRect]) -> [PHAsset]?                                                                                                         | 115.4    |
| xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.swift | 4    | private static func makeInconsistentNotificationSettingAlert() -> AlertViewController                                                                         | 114.0    |
| xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.swift           | 5    | @objc(showFromViewController:completion:) static func show(from viewController: UIViewController, completion: @escaping (Bool) -> Void)                       | 107.9    |
| xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.swift                 | 8    | @objc override init(frame: CGRect)                                                                                                                            | 102.5    |
| xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.swift                 | 31   | private final func configureAppearance()                                                                                                                      | 102.1    |
| xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.swift           | 7    | private static func makeAlertController() -> AlertViewController                                                                                              | 102.1    |
+-------------------------------------------------------+------+---------------------------------------------------------------------------------------------------------------------------------------------------------------+----------+

内部では、Xcodeの出力したビルドログを取得、デコードし、ビルド時間についての出力のみを抽出しています。

カスタムレポーターを実装して、メトリクスを監視する

xcprofilerにはReporterという仕組みがあり、独自のReporterを実装することで、解析結果を様々な方法で出力することができます。

今回は、実行結果をGrafana上で閲覧するために、データソースであるInfluxDB向けのReporterを実装し、最新の実行結果をテーブルにしてみました。

#!/usr/bin/env rubyrequire'xcprofiler'require'influxdb'includeXcprofilerINFLUX_DB_TABLE_NAME = 'table_name'classInfluxDBReporter< AbstractReporterdefreport!(executions)
    client ||= InfluxDB::Client.new(
      ENV['INFLUXDB_DB_NAME'],
      host: ENV['INFLUXDB_HOST'],
      port: ENV['INFLUXDB_PORT'],
      username: ENV['INFLUXDB_USERNAME'],
      password: ENV['INFLUXDB_PASSWORD'],
    )

    payload = executions.map { |e|
      key = "#{e.filename}:#{e.line}:#{e.column}#{e.method_name}"Hash[*[key, e.time]]
    }.reduce(&:merge)
    client.write_point(INFLUX_DB_TABLE_NAME, {values: payload})
  endend

profiler = Profiler.by_product_name('Cookpad')
profiler.reporters = [
  StandardOutputReporter.new(limit: 20, order: :time),
  InfluxDBReporter.new(limit: 20),
]
profiler.report!

このようなスクリプトを実装し、CI上から実行すれば、最新のビルド時間をGrafana上で定常的に監視することができます。

f:id:gigi-net:20161228111315p:plain

Swiftのビルド時間を高速化する

では実際に、これらのデータを元にSwiftコードのビルド時間をカイゼンしてみましょう。

一番遅いメソッドは、ビルドに5003ms、約5秒もの時間がかかっています。 この値はCPU時間で、実時間ではないのでしょうが、他のメソッドと比べると特異的に遅いヶ所であることがわかります。

該当箇所のコードを実際に見てみましょう。

// 日付ラベルを表示するために、日付の区切りとなる写真のインデックスを求める// (例) [photo(10月30日), photo(10月29日), photo(10月29日), photo(10月28日), photo(10月28日), photo(10月27日)]// という配列の場合、日付の区切りとなるインデックスは [0, 1, 3, 5]letindicesShowDateLabel= [0] + photos.reduce([]) { (photosGroupedByDate, photo) ->[[Photo]] invarresult= photosGroupedByDate
    ifletpreviousPhotoDate= photosGroupedByDate.last?.last?.cookedDate, calendar.isDate(photo.date, inSameDayAs:previousPhotoDate) {
        result[result.count -1].append(photo)
    } else {
        result.append([photo])
    }
    return result
}
.map { $0.count }
.reduce([]) { (result, element) ->[Int]inreturn result + [(result.last ??0) + element]
}
.dropLast()

このビルド時間の遅さは、このように大量のメソッドチェイニングが行われていることにより、型推論が複雑になっていることが原因だと推測できます。

試しに、メソッドチェイニングをやめ、適宜テンポラリな変数に格納し、型情報を与えてみました。

// 型を明示するletgroupedPhotos:[[Photo]] = photos.reduce([]) { (photosGroupedByDate, photo) ->[[Photo]] invarresult= photosGroupedByDate
    ifletpreviousPhotoDate= photosGroupedByDate.last?.last?.date, calendar.isDate(photo.date, inSameDayAs:previousPhotoDate) {
        result[result.count -1].append(photo)
    } else {
        result.append([photo])
    }
    return result
}
letcounts:[Int]= groupedPhotos.map { $0.count }
indicesShowDateLabel = [0] + counts.reduce([]) { (result, element) ->[Int]inreturn result + [(result.last ??0) + element]
}
.dropLast()

わずかこれだけの変更でビルド時間が5003msから152msに、 つまり 97%も短縮することができました。 この値は、複数の開発者が毎日アプリをビルドし続けることを考えると大きな差と言えます。

このように現在のSwiftコンパイラによる型推論は、特定の場合においてビルド時間に多大な影響を与えてしまうことが見て取れます。 コードの簡潔さを取るか、ビルド時間を取るかはトレードオフであり、今後の課題と言えそうです。

まとめ

今回開発したxcprofilerは、どのプロジェクトでも簡単に利用でき、ワンショットで実行してみるほか、定常的な監視にも利用することができます。 ぜひご自分のプロジェクトに導入してみてください。Pull Requestもお待ちしております。

クックパッドのモバイル基盤グループでは、開発者のための問題解決が好きなモバイルエンジニアを募集しています。

iOS/Android アプリエンジニア | クックパッド株式会社 採用情報


Cookpad TechConf 2017 はライブ配信も行います!

$
0
0

f:id:Yoshiori:20170118132915j:plain

こんにちは! @yoshioriです。

こちらで告知した「Cookpad TechConf 2017」 ですが、ライブ配信することが決定しました!!!

残念ながら抽選から漏れてしまった方々、エントリーに間に合わなかった方々、また、今この記事を見て知った方々、ご安心ください!

僕自身も色々なカンファレンスに参加、もしくはライブ配信でみたりしますが、Twitter などのハッシュタグを追いかけつつ見るのが一番楽しいと思っています。ですので、会場に来られない方も是非、ライブ配信で楽しんでいただければと思います。
ちなみにハッシュタグは #CookpadTechConfになります!

そして、肝心のライブ配信 URLはこちらです!
https://techconf.cookpad.com/2017/streaming

残り一週間を切って僕もドキドキしていますがどうぞお楽しみに!!! では!

Cookpad TechConf 2017 開催報告

$
0
0

f:id:Yoshiori:20170121154607j:plain

こんにちは! @yoshioriです。

先週の土曜日の 2017/01/21、技術系カンファレンスCookpad TechConf 2017を開催しました。
第二回となる今年は、ライブ配信・Wi-Fi 設置などの新しい取り組みやサプライズ発表など盛りだくさんでお届けしました。

この記事では色々なリンクをまとめたいと思います。

発表資料と動画

  1. Cookpad under a microscope by 成田 一生
  2. Go Global by 滝口 健太郎
  3. Building infrastructure for our global service by sorah
  4. サービス開発におけるデザインの取り組み方 by 若月 啓聡
  5. モバイルアプリのABテスト by 加藤 龍
  6. チームでプロダクト開発をするための取り組み by 丸山 亮
  7. よりよい検索体験の為の情報設計とプロトタイピング by 須藤 耕平
  8. 組織全体でGitHubを使うようになるまで by 長 俊祐
  9. 快適なサービス開発を支える技術 by 国分 崇志
  10. Real World Machine Learning by 染谷 悠一郎
  11. 行動ログでプロダクトを改善するには by 兼山 元太
  12. Cookpad awakens by 庄司 嘉織
  13. Spot Instances in Cookpad by 荒井 良太

記事やブログ等

笹田耕一入社について

「Go Global」 の発表内容について

「組織全体でGitHubを使うようになるまで」の発表内容について

参加者の方々の感想

togetter

* 僕が見つけたのを集めただけなので漏れがあるかもしれません><(新しい記事など見つけたら@yoshioriまで連絡いただければと思います)

最後に

今年もなんとか無事に開催することが出来ました。
お楽しみいただけていたら幸いです。
引き続き技術的な挑戦をドンドンして、来年の Cookpad TechConf 2018 で発表できるようにするつもりです!来年もぜひお楽しみに!!!

Cookpad TechConf 2017 提供 Wi-Fi の裏側

$
0
0

f:id:sora_h:20170121093202j:plain

インフラ部 id:sora_hです。

先週開催された Cookpad TechConf 2017如何でしたでしょうか。わたしは TechConf において Wi-Fi を担当していて、こちらも好評いただいたようでなによりでした。

というわけで、この記事では TechConf 2017 における Wi-Fi についての詳細を紹介します。

ネットワーク機器設定・サーバー mitamae レシピ等の公開

https://github.com/cookpad/techconf2017-network

今回の紹介する構成のうち、ネットワーク機器およびサーバ側の設定等、ほとんどを GitHub で公開しています。参考までにどうぞ。

TechConf 2017 NOC メンバー

実は外注などはしておらず、社内 IT と SRE グループのメンバーで構成されていました。

  • メイン (設計・運用・設営)
    • @sora_h (SRE)
    • 社内 IT ネットワーク担当 1 人
  • サポート (設営・調達等)

どのメンバーも特にカンファレンス Wi-Fi 運用に手慣れた訳ではありません。

Wi-Fi 運用に必要なこと

わたしとしては、最低でも以下が必要という認識です。

  • 人間 (設計・敷設/撤収・運用)
    • 敷設/撤収・運用… 特に敷設と撤収は会場の広さに必要な人数が比例します
  • 会場と仲良くする
    • 回線の調整、配線の調整…。
  • ちゃんとした AP を複数台
    • データレートのカスタマイズ、パワーレベル設定、ローミング対応など *1
  • そこそこの L2 スイッチとルータ
    • ルータは Linux ルータとかでも良いと思われる
    • AP が PoE 対応なら PoE 給電できるスイッチだと良い
  • ちゃんとした DHCP/DNS サーバ
    • Linux 入れたラップトップにセットアップすれば十分
  • 消耗品など
    • UTP ケーブル
    • 養生テープ *2
    • 電源タップ

ただ実際のところ、現実的に AP は Cisco Aironet (はんぺん) や YAMAHA の WLX シリーズを使わざるをえないと思います。*3

今回の規模と会場では有線な箇所にボトルネックはなく、とにかく Wi-Fi 環境の運用が重要でした。ただ、トラフィック的にはそこそこのルータは必要でしたね。

キャパシティ見積り

会場は 恵比寿 ザ・ガーデンルームで、想定来場数は 350〜370 となっていたため、

  • 想定 600 クライアント
  • AP 計 8 台
    • 会場内 AP 6 台
    • ホワイエ AP 1 台
    • 控室 AP 1 台

で進めていました。

結論で言うと、あのスペースに AP 6 台って密度高いかな…と思っていましたが、まあなんとかなりましたね。それでも会場内の AP は多くて 1 台あたり 60-70 associations を要求する見積りとなりました。 1 台あたりの要求がかなり多いですが、密度的にはこれ以上 AP を増やすのが厳しいので、耐えてくれ…と願いながらこの台数で臨んだ形です。

(なお、当初 4 台で検討していましたが、思ったより想定の来場数が多かった事であわてて直前に増やしました。実はこの会場は社内イベントで定期的に利用していて、普段の人数でそこそこ一杯だったため、その程度の人数だろうと思い込んで確認を怠っていました…。)

機材

f:id:sora_h:20170120105250j:plain

今回は社内で未使用だった機材 *4でまかなうことができました。

  • ルータ
    • Cisco 2921/K9 ISR (gw)
  • L2 スイッチ
    • Cisco Catalyst 2960S-24PS-L PoE+ (2 台; sw, sw2)
  • WLC
    • Cisco 5508 Wireless Controller (wlc)
  • AP (計 8 台: 会場内 6 台、ホワイエ 1 台、控室 1 台; ap1 .. ap8)
    • Cisco Aironet LAP1142N
    • Cisco Aironet CAP3602i
  • サーバー
    • ThinkPad X240 (tp)
  • その他
    • AP スタンドとしての譜面台 7 つ *5
    • UTP ケーブル Cat6 単線 (AP用)
    • 養生テープ等々

荷物の量としてはネットワーク機器だけで 6U と結構な量になってしまいました。本当は最小限の荷物にしたいところですが、オフィスに WLC などを設置の上 VPN…とするには機材が足りず、ざんねん。 この機材を気軽に運搬する事ができたのはオフィスと会場が縦移動だけで済むからこそですね。 (弊社オフィスは会場隣の恵比寿ガーデンプレイスタワーにあります)

なお、AP は在庫の都合モデルが混在する構成となってしまっています。 ThinkPad はまあ DNS などで必要になるだろうと入れましたが実際は DNS サーバー以外に役割を持たせる事はありませんでした。

設計

下記が今回の構成図です。 *6

f:id:sora_h:20170125193551p:plain

ご覧の通り、ごく当たり前の設計となりました。余裕があれば遊びを入れたかったところですが、残念ながら時間に余裕がありませんでした。次回もし担当する事があれば遊びたいですね。

ポイントとしては、可能な限りサービスは AWS 側に置いて省力化しています。なので、前述した会場持ち込みの ThinkPad は DNS 以外の仕事がありませんでした。

Linux ディストリビューションは Arch Linux を採用しました。

回線

今回は会場既設の回線を利用しました。 既設の機材も存在したため、ONU から一旦こちらの L2 スイッチで受けて既設の機材に戻すという構成を取らせていただきました。

さらにこちらの機材でグローバル IPv4 アドレスを持つ事もできた上、帯域も特に問題を感じませんでした。そのため、今回は直接会場からインターネットに出ていく素朴な構成をとる事ができました。

有線側設計

L1

f:id:sora_h:20170125193624p:plain

基本、会場図面や現地調査で配置を考えて UTP ケーブル発注等々をします。今回は最長 60m で、会場後方に別途スイッチを設置する事としました。 (配信ブースに有線接続を提供する都合もあり。)

f:id:sora_h:20170121092951j:plain

60m のケーブルを敷設するのとか巻くのとか結構たいへんでした。貴重な体験。

L2, L3

ネットワーク全体の CIDR は 10.200.0.0/16で会場側は下記のセグメントを持っていました。

  • クライアント (VLAN 100, client, 10.200.0.0/20)
    • DHCP プールは .1.0 - .15.250
  • 管理用 (VLAN 200, mgmt, 10.200.16.0/24)
  • サーバ (VLAN 210, srv, 10.200.17.0/24) *7

AWS 側は 10.200.128.0/17が VPC 、中に AZ 2 つで public, private subnet をそれぞれ持たせる構成でした。

会場 NAPT

カンファレンスネットワーク等、たくさんのクライアントが NAT 配下にいて活発に利用される状況ではセッション数に気を配る必要が出てきます。 そのため、このようなネットワークでは NAT 最大セッション数が万単位でサポートされているルータを利用する事が重要です。

今回は gw (Cisco 2921/K9) が NAT を担当していて、下記のように設定しました。 ホストあたり最大セッション数は 200、タイムアウトは一部を除いて 10 分になっています。

ip nat translation timeout 600
ip nat translation tcp-timeout 600
ip nat translation udp-timeout 600
ip nat translation finrst-timeout 15
ip nat translation syn-timeout 30
ip nat translation dns-timeout 15
ip nat translation routemap-entry-timeout 15
ip nat translation icmp-timeout 10
ip nat translation arp-ping-timeout 10
ip nat translation max-entries 65000
ip nat translation max-entries all-host 200

残念ながらこちらは SNMP で監視する事ができず、実際の消費最大セッション数は把握できていません。会期途中で show ip nat statisticsを確認した時は 15000 前後利用されていました。 だいたい 1 ホストあたり 50 セッションと見積れば良さそうですね。

L4

AWS への接続は VPC の VPN connection を利用していました。今回の会場はたまたま固定 IP だったから利用できたけれど、ほとんどのケースでは面倒な事になるので自前の EC2 インスタンスでルータを組んだほうが良さそうに思います。

L7

DNS

DNS は Linux サーバーでホスト、また Unbound を採用しました。これは AWS 側に 2 台 Multi AZ, t2.micro で設置しています。*8

Unbound のチューニングは 公式ドキュメントが参考になります。ここの書いてある以上のチューニングは特にしていません。

一応、会場側 ThinkPad でもキャッシュサーバーを用意しました。が、AWS 側に全て転送して、AWS 側に用意した 2 台でのみフルリゾルブを行う構成を取りました。 本来であれば、CDN 等に考慮しフルリゾルブは同じ回線、あるいは同じ AS から行いたいところです。しかし、今回はそれを実現するために public IP アドレスに余剰がない + NAT セッションを無駄に消費したくない + CDNにそこまで最適化する必要がないという状況だったため、AWS 側フルリゾルブを選択しました。

実際のところ、レイテンシを考慮しての会場側キャッシュでしたが、会期中はレイテンシ 5ms-10ms とかなり AWS への接続が安定していました。なので別に会場に無くても良いんじゃないかなぁ、と今は考えています。

ちなみに、気付いた方がちらほらいましたが、Route 53 private hosted zone で 200.10.in-addr.arpa.nw.techconf.cookpad.com.ゾーンを持っていました。

DHCP

DHCP も Linux サーバーに担当させ、ISC Kea を採用しました。こちらも AWS 側に t2.micro で設置しています。 AWS に設置、つまり会場側のブロードキャストドメインを越えているため、gwに DHCP relay の設定を入れました。

DHCP で気をつけたのはリースタイムとプールサイズで、こちらはプールは大きめに取ってリースタイムは 45 分としました。 今思えばもっと短くても良かったかもしれないし、セグメントサイズも小さくても良かったですね。思考停止して /20 とかにしていた。

冗長化は特にせず、memfile backend でディスクにファイルを書かせる形でリース情報の永続化を構成しました。裏を RDB にして構成しても良かったかなあ、と今は思いますが、AWS 側で failover の実装がやや面倒なので見送りました。

なお、relay agent を通して Kea 付属の perfdhcpで測定しましたが、300 4-way exchanges / second を記録できたため、特にそれ以上のチューニングはしませんでした。

有線その他

  • 配信は同じクライアントの VLAN に収容して特に QoS 等も設定しませんでした。これは上流が 200Mbps ほどあり余裕が確実に出るという予測 (配信は上り 4Mbps 程度しか使用しない) ためです。
    • 一応、会場内では TechConf 自体のライブ映像を見ないように協力のお願いをさせていただきました。ご協力ありがとうございました。

Wi-Fi 設計

有線ネットワークの数倍めんどうくさいのが Wi-Fi です。

基本的には、

  • データレートに気をつける
    • 遅いデータレートで通信されると他のクライアントも巻き込んで遅くなってしまうため (1台がチャンネルを占有する時間が長くなってしまう)。
    • なので、遅いデータレートを Disable するのは必須です。これが安定性にかなり寄与します。
  • チャンネル被りに気をつける
    • 敷設後イベント開始前に手動で調整しました。Automatic にすると離れてはいるが何台か被ってしまっていたので、結局 Manual で。
    • 2.4 GHz はもう無理なので諦め。どちらにせよ密度高く 6 台設置する環境だと被るので…。
  • パワーレベルに気をつける
    • クライアントは基本的に見えている一番電波強度が強い AP に接続しにいくため、遠い AP へ接続されないように気をつける
    • AP 間の距離が短めなので最弱に設定した上で、真ん中あたりが弱くならないように微調整。
  • セキュリティ面
    • クライアント間折り返し無効化、またスイッチ側で AP のポートに保護ポートを設定する (Cisco では switchport protected)
    • DHCP および DHCP 付与アドレスを強制

とすれば問題ない…と思いたいところです。

なお、データレートは今回下記を無効にしました。 (MCS index)

  • 5 GHz: 11a 6,9,12,18 / 11n 0,1,2,3,8,9,16
  • 2.4 GHz: 1,2,5.5,11,6,9,12,18

AP の配置

AP ごとに収容するクライアントの数をバランスよくできそうな間隔で配置していきます。

TechConf 2017 では会場内に計 6 つ、左右に 3 つずつ配置しました。また、ホワイエにも 1 つ、スタッフ向け控室にも 1 つ設置していました。

今回の会場であるガーデンルームはフラットでコンパクトですが、フラットじゃなかったり、広かったりすると真ん中にも配置したりと考える事が増えて大変になりそうです。

運用

Wi-Fi 運用は比較的筋肉運用の世界です。

f:id:sora_h:20170125194244p:plain

たとえば定期的に AP ごとのクライアント数を確認して、明らかに接続数が偏っていたらその AP のパワーレベル・譜面台の角度や高さを調整してクライアントを散らす必要があります。

f:id:sora_h:20170125193926p:plain:w270f:id:sora_h:20170125195425p:plain:w270

特に休憩・休憩終了時には入口付近の AP に接続したまま会場内に着席して、偏る、という事が発生しがちです。

トラブル

幸いにして目立ったトラブルはありませんでしたが、終盤 (17時前後) ある AP が定期的に association を全て落としたり、全然 association を持たないという現象が発生していて対処していました。最終的に一旦 AP を余剰機材に入れ替えたりしました。

f:id:sora_h:20170125194455p:plain

その前後で全体的にクライアント数調整で不安定になっていたと思います。ご迷惑おかけしました。

設定漏れ

上のトラブルに関しても今思えば、ログは取っていたが各種トラップを有効にしてない事にまとめを書いていて思いました。これは DFS 発生などのログが残らなそうに思う設定ですね…。

その他も Client Band Select が有効になっていない状態でイベントがスタートしてしまい、一部 2.4GHz がなぜか盛り上がってしまうみたいな状況にも悩まされてしまった。 *9

監視

監視には Zabbix を採用しました。これは慣れてるのと SNMP 対応がやはり理由としては大きいです。環境は完全に普段のものから分離していたため、0 から zabbix-server を構築しました。 アイテムに関しても、基本的なメトリクスはトラブル発生時に供えて各種取得できるように整えました。kea, Unbound に関しては社内にもテンプレートがなかったので新規に作成しています。

構成は DB に PostgreSQL を使用し、Web frontend は php-fpm + nginx でホストする形をとりました。会場側に zabbix-proxy を設置するかどうかは検討しましたが無駄に管理対象増えるなと思って見送りました。

Grafana ダッシュボード

f:id:sora_h:20170125194704p:plain

また、ダッシュボードの作成には Grafana + grafana-zabbix を使ってみました。個人的に最近 Grafana は便利な Zabbix フロントエンドという認識です。特に discovery で作成されたアイテムをまとめたグラフが簡単に作って自動で追加削除を反映させられるのが良い。

Grafana 自体もバイナリパッケージ入れて起動するだけで動くから手軽だなぁ、流行るわけだわーという好印象です。バイナリ置くだけで済んで便利じゃんみたいな奴、くぅ〜便利〜分かる〜とも思うのだけど、いろいろ複雑な心境を抱きますね。

ログ

起きてほしくはないですが、後で問題が起きた時のために DNS/DHCP/NAT ログを取得していました。

NAT ログに関しては Cisco から syslog での出力となるので、rsyslog を入れた EC2 インスタンスを AWS に設置して、WLC や L2 スイッチ含めてログ転送先としました。

敷設・撤収

前述の通り、今回は運搬がオフィスから会場で、かつオフィスと会場間は縦の移動のみで済ませられました。そのため、特に工夫はせず台車 3 台 + ダンボールで運搬をしていました。

他に確認するべき事項としては、会場やイベント運営スタッフと入り時刻・撤収時刻を確認しておきましょう。また、会場側でケーブルをどう配線しても問題がないか (養生テープ使用の可否など) を確認しましょう。

今回はほぼ 1 部屋というシンプルな配置だったため、特に深く考えず頑張ってケーブルを敷設・撤収する以上はありませんでした。

実績値

  • DL: 平均 48Mbps、最大 120Mbps
  • UL: 平均 16Mbps、最大 68Mbps
  • 最大同時クライアント数 330
  • DNS ヒットレート平均 45% (計測1分単位、会場側キャッシュのみの集計)
  • DNS 最大 49qps (会場DNSサーバのみ)

接続数は思ったより伸びず、上流回線も余裕がある状態で運用できていました。この伸びの悪さは張り紙などの周知不足かなぁと考えています。

接続先に関しては上から Google, Amazon (AS16509), Twitter, Apple, Amazon (AS14618), さくらインターネット, Akamai (AS20940), EdgeCast, Fastly, Facebook, Microsoft (AS8075), GitHub といった具合でした。AWS や Google はアテにならないとしても、それ以外から参加者の層がなんとなく掴める気がしています。

その他

今回助かった所

  • 手持ちの機材でまかなえた事
  • まともな回線が来ていた。会場にお願いして ONU 直収させてもらえたのは大変助かりました。
  • ガーデンルームを利用する社内イベントが開催の 2 週間ほど前にあった事
    • 実際の会場でホットステージを行えて、敷設や実際の問題点が早期に洗い出せたのだった。 特に敷設および撤退で気をつける点はおおよそ分かったため、当日の敷設・撤退の高速化に繋がりました。

今回困った所

  • IPv6 アクセスが無い。HE Tunnel Broker 使っても良かったんだけど…。
  • 弊社はオンプレ環境がなく、完全に AWS のみのためグローバル IPv4 アドレスも IPv6 アドレスも余らせておらず、構成の工夫に限度があった。
    • たとえば会場のインターネット接続性が微妙だった時に、EC2 経由でしか通信させられないよなとか。
    • 従量課金だし。あと EC2 ブロックしてるサービス実は結構あるので避けたいところ。
    • (ちなみに v6 アクセスはそもそもオフィスにも無い…)
  • AWS VPC の VPN connection、というか CGW は IP アドレス固定を想定している事
    • 今回はたまたま事前に確認できたし当日も変化がなかったけれど、AWS とくっつけるとしたら普通は自前の EC2 インスタンスと strongSwan でルータ組むのがよさそう。

反省点

  • 張り紙等の告知不足。おそらく告知不足から想定より接続数がだいぶ少なかったと見られるため。

やりたい所

まとめ

TechConf 2017 Wi-Fi の裏側についてご紹介しました。

参考

他にもカンファレンス Wi-Fi 運用について下記リンクが参考になると思います / 参考にさせて頂きました。

*1:VLAN は使えなくてもいいと思うけれど、まあこれができるAPはだいたい 802.1q 対応している

*2:会場によっては養生不可なので注意

*3:DD-WRT とかでも出来る…のだろうか。やったことない。

*4:正確に言うと、白金台オフィス時代の機材の残り

*5:スタンドとしても便利ですが、AP の電波出力の傾向を微調整するのにも便利。

*6:次の機会があるか分からないけれど、あった時に思い出すの困るのでちゃんと描いた。

*7:結局 DNS しか使わなかったけれど

*8:なお、public IP を付与していました。NAPT 配下に置かず直接再帰クエリをさせるのは DNS spoofing 防止につながるためです。

*9:なお、Client Load Balancing は過去に社内でトラブルが発生したため利用していませんでした。

Swift 3 マイグレーション

$
0
0

技術部モバイル基盤グループの ヴァンサンです。

西山が 以前紹介したように、クックパッドでは 2014 年から Swift を使っています。長い間、海外向けのアプリや みんなのお弁当だけに使われていましたが、去年の5月から、 クックパッド iOS アプリの開発にも Swift を使うようになりました。歴史のある iOS アプリなので Objective-C でのコードの方がまだ多いのですが、いまは既存の画面の変更を除いて新しいコードが Swift で書かれています。既存の画面を Swift で書き直すこともあります。

Xcode 8.0 がリリースされてから数ヶ月 Swift 2 を使っていましたが、去年の12月のリリース直後に Swift 3 へのマイグレーションをしてから、開発で Swift 3 を使っています。2017年2月1日にリリースされた 17.1.1.0 が Swift 3 を使ってビルドされた最初のバージョンです。

Swift 3 で変わったことを紹介するブログ記事は他に多くあるので、この記事では主にマイグレーションに焦点をあてて書こうと思います。

Swift 3 へのマイグレーションでは、殆どの Swift で書かれたコードが変わります。ですので、同じアプリでマイグレーションと並行して別の開発をすることは難しいです。そのため、年末年始辺りに開発のペースが落ちているのを利用して、2日間、他の開発を中断してマイグレーションを行いました。もちろん必要な期間はアプリのコードやその複雑さによりますし、早めに終わらせるために事前準備が必要ですね。

参考として、クックパッドiOSアプリのマイグレーションを行った時点では、ライブラリを含まなければ、 Swift のコードはファイル数264個(テストを含まなければ213個)、行数2.1万行(空行除けば1.7万)くらいでした。

事前準備

まず、アプリに使われる Swift でのフレームワークが Swift 3 に対応している必要があります。クックパッド iOS アプリでそれが問題になる可能性を見越していたのもあって、 Swift でのフレームワークの導入を抑えていました。マイグレーションの時点で社外 Swift フレームワークは HimotokiResultの2つだけだったので、社外フレームワークの Swift 3 対応に関して問題は特になかったです。

すべてのフレームワークが Swift 3 に対応していたら、試しに手元でマイグレーションをやってみることを強く推奨します。ビルド完成までいかなくても、何時間かやってみれば、色々見られると思います。特にマイグレーション中他の開発が進まないので、本番はできるだけスムーズに進みたいですよね。

僕が試しにマイグレーションをやって分かったことをいくつか紹介しましょう。

  • マイグレーションツールが全部やってくれると思わない方がいいですね。マイグレーションツールを掛けた後に修正がかなり必要になるでしょう。
  • マイグレーションツールが変な変更をすることがあります。例えば、クックパッドアプリでは setTitle(buttonTitle, forState: .Normal)がなぜかツールに setTitle(buttonTitle, forState: UIControlState())に変換されていました。そういう時、いつでもマイグレーション前のコードと比較できる状態でいる必要があります。あと、それをメモしておいて、次回ツールを掛けた直後にプロジェクトのファイルにそれを検索してすぐ直した方が早いですね。
  • #if / #else / #endifの間に挟まれているコードはマイグレーションツールに無視されて、 Swift 2 のままです。なので、マイグレーションツールを掛ける前にできるだけ #if / #else / #endifをコメントアウトして、ツールを掛け終わったら戻しておきましょう。
  • Carthage とかで入れたフレームワークはマイグレーションがアプリのコードと同時に行われたわけではないので、ツールが何が変わったのか把握しきれません。なので、フレームワークの変更の一部を自分でコードに適用する必要があります。試しにやったマイグレーションでどういう変更が必要そうなのかメモして、本番当日に一部を「全て置換」とかで変えられるようになった方がいいかもしれません。

また、マイグレーションの間に他の開発者が読める Swift 3 の仕様変更のドキュメントを用意してもいいですね。

マイグレーション本番

マイグレーション済みのコードがマージされるまで同じアプリで他の開発を中断しているので、本番は早めに終わらせる必要がありますね。本番の流れは基本的に以下の通りです。

  • 上記に書いた通り、 #ifはできるだけコメントアウトします。
  • マイグレーションツールを掛けます: Xcode で Edit → Convert → To Current Swift Syntax
  • 事前準備でメモしたものを利用しながらビルドできるまでコンパイラーが出しているエラーを修正します。

事前準備でやるべきことをだいたい分かったはずなので、早いペースで進められるはずです。

ビルドできて、テストが通って、アプリを実行してみても問題なさそうな状態にもっていくのが重要です。少し不自然なコードは軽く修正できるならすぐ直してもいいのですが、それ以外の修正やもっと Swift 3 っぽくするのも後日でいいはずです。他の開発者が開発をできるだけ早く再開できてほしいですからね。

コードレビューされてからマージするのですが、変更が多いし、開発者がまだ Swift 3 に慣れていないのもあるでしょうし、しっかりしたコードレビューは難しいですね。あとで不具合があってもリリースまで気づく可能性を高めるため、マイグレーションを新しいバージョンの開発の開始時点で行いました。

マイグレーション後

折角 Swift 3 にしたので、 Swift 3 っぽくしたくなりますね。弊社では少し前から Swift 3 の API Design Guidelinesをある程度使っていたので、既存の Swift コードが割りと Swift 3 っぽかったです。とはいえ、できることがいくつかありました。

enum

Swift 3 では、 enumの頭文字が小文字になっていて、マイグレーションツールは自動的に多くのを変えてくれるのですが、 StringrawValueを持つ enum (enum MyEnum: Stringで定義されたもの)は変えてくれません。値名を変えると値変わりますからね(実際値が明確に指定されているとしてもツールが変えてくれませんが)。そういう enumを1つずつ確認して、変えても挙動が変わらなければ頭文字を小文字にした方がいいと思います。

クラスプロパティ

Swift 3 に伴って、 Objective-C でクラスプロパティを使えるようになりましたね。使うと Swift で余計な ()が必要じゃなくなります。例えば

// 変更前
NS_ASSUME_NONNULL_BEGIN
@interface CKDActivityLogger : NSObject
+ (instancetype)defaultLogger;
// (省略)@end
NS_ASSUME_NONNULL_END

// 変更後
NS_ASSUME_NONNULL_BEGIN
@interface CKDActivityLogger : NSObject
// クラスプロパティは instancetype を使えないので、型は明確に@property (nonatomic, class, readonly) CKDActivityLogger *defaultLogger;
// (省略)@end
NS_ASSUME_NONNULL_END

それに合わせて Swift 3 側で CKDActivityLogger.default()CKDActivityLogger.default変える必要があります。そういうところで ()がない方が Swift っぽくて読みやすいと思います。

新しい Swift Foundation クラスのブリッジング

Swift 3 では、 Swift から見る Objective-C メソッドの引数の一部の型が変わりました。 NSDateDateに、 NSURLURLに、 NSIndexPathIndexPathに、 NSErrorErrorに、など。

Objective-C 用のクラスに生やしていたヘルパーメソッドは一時的に asで元の型にキャストしないと使えません。一番ややこしいのは Errorです。 Errorには、 NSErrorと違って codedomainuserInfoというプロパティがないので、 as NSErrorがけっこう必要になります。

マイグレーション後、 Swift のコードに Objective-C Foundation の型(NSDate, NSURL, NSError, …)が残ることありますが、 Swift でできるだけ Swift Foundation の型(Date, URL, Error, …)に変えた方がコードがもっと Swift っぽくて読みやすい気がします。 Objective-C 用のクラスに生やしていたヘルパーメソッドを Swift 用の型にも生やした方が良いと思いますね(Objective-C を使わない場合、 Objective-C用のクラスにまだ生やす必要もないですね)。

Any

Swift 2 で AnyObjectになっていたところの多くが Swift 3 では Anyになります。マイグレーション後、それに合わせてアプリ内(ディクショナリの値とかで)残っている一部の AnyObjectAnyに変えた方が、多くの as AnyObjectが消せてコードがもっとシンプルになる場合が多い気がします(実はマイグレーションツールも多くの as AnyObjectを入れたりします)。ただし、 AnyAnyObjectと違って別の型にキャストしないとメソッドを呼べないので要注意です。

マイグレーション後に気づいた問題

現時点で Swift 3 にした影響で起きた問題は iOS 8 でしか起きない1つの問題だけです。

具体的に、 UITableViewDelegatetableView(_:heightForRowAt:)で起きる問題です。 iOS 8 では、テーブルビューを使う画面から別の画面に遷移する時、 tableView(_:heightForRowAt:)が呼ばれて、それに渡されている IndexPathrowが間違っています。4行あるテーブルの場合、rowが 0, 1, 2, 3 ではなく、 0, 0, 1, 2 になってしまいます。

もう少し調べてみると、 iOS 8 では、遷移の時だけ、渡された index path のクラスが UIMutableIndexPath (NSIndexPathのサブクラス)になっています。 Swift のソースを見たら分かりますが、 NSIndexPathを Swift 用の IndexPathへの変換に getIndexes:range:というメソッドが使われています。 iOS 8 では -[UIMutableIndexPath getIndexes:range:]で取得される値が間違っているため、 IndexPathに変換されて間違っている値になります。因みに、 -[UIMutableIndexPath indexAtPosition:]は正しい値を返しているようなので、上記にリンクした IndexPathinit(nsIndexPath:)を以下のコードに変えたら動きそうですね(スピードとかに影響あるか分かりませんが)。

fileprivate init(nsIndexPath:ReferenceType) {
    letcount= nsIndexPath.length
    if count ==0 {
        _indexes = []
    } else {
        _indexes = (0..<count).map { nsIndexPath.index(atPosition:$0) }
    }
}

結局その問題が起きたビューコントローラは同じセクションでは高さ2種類だけだったので、2つのセクションを分けて回避できました。でもそういったバグが他にないか心配なので iOS 8 対応をまだしているアプリは Swift 3 が推奨しづらいですね。

最後に

クックパッド iOS アプリで Swift 3 のマイグレーションはしっかり準備した結果 iOS 8 での問題を除いて大きな問題は無かったです。 Swift 3 は変更が多いのですが、言語は色々改善されていると思いますので、 Swift 3 のマイグレーションをしてよかったと思っています。

そもそも近いうちに出る Xcode 8.3 はもう Swift 2.3 に対応しないので、 Swift で iOS の新しい機能を使いたかったら Swift 3 にしない選択肢はないですね。 Xcode 8 の Swift 2 対応にコードエディター関連問題がいくつかありますし。

最後に、 iOS 8 対応をしていない Swift アプリは早めに Swift 3 へのマイグレーションをした方がいい気がします。 iOS 8 対応をしているアプリは少し悩ましいですね。 iOS 8 対応を切るまで Xcode 8.2.1 で我慢するか、 Swift 3 にするけど iOS 8 での動作確認がしっかりするか、ですかね。後者は一部のコードを Objective-C で書く必要が出てくるかもしれませんが。

クックパッドでは Swift 3 で開発したいモバイルエンジニアを募集しています

『Swift実践入門』2月7日発売

$
0
0

 海外事業向けのiOSアプリケーション開発を担当している西山(@yuseinishiyama)です。より海外事業に注力するため、今年度から、海外事業の拠点であるイギリス、Bristolのオフィスに出向しています。クックパッドは現在、15言語、58カ国以上を対象にサービスを展開しています。

 先日、ヴァンサンが国内向けのアプリケーションのSwift 3化に関する記事を投稿しました。同じく、海外向けのアプリケーションも、昨年12月にSwift 3化した最初のバージョンをリリースしました。以前、Swift移行の記事で説明したとおり、このプロジェクトはほぼSwiftによって実装されているため、Swift 3化によってほぼ全てのコードが影響を受けました。幸いにも、大きなトラブルは起きませんでした。

 この度、こうした業務での経験を活かして、『Swift実践入門』という書籍を技術評論社のWEB+DB PRESS plusシリーズから2月7日に発売することになりましたので、この場を借りて宣伝させていただきます。APIKitなどで著名な@_ishkawaさんとの共著となります。

Swift実践入門 ── 直感的な文法と安全性を兼ね備えた言語 (WEB+DB PRESS plus)

Swift実践入門 ── 直感的な文法と安全性を兼ね備えた言語 (WEB+DB PRESS plus)

 また、刊行記念のイベントも開催することになりましたので、興味のある方はぜひご参加ください。

執筆の動機 〜Swiftのwhyとwhenの解消〜

 SwiftがAppleから発表されたのは2014年です。安全かつ簡潔な文法という触れ込みが印象的でした。iOS開発者としては当然居ても立ってもいられず、発表後すぐ、beta版の公式ドキュメントの全てに目を通しました。そこで強く感じたことは、「簡潔ではあるが簡単ではない」ということです。Objective-Cのユニークな記法と比較すると、フレンドリーな見た目にはなりましたが、その豊富な言語仕様を適切に使いこなすのは容易ではないと思いました。

 Appleの公式ドキュメントをはじめとして、どんな(what)言語仕様があり、それらをどのように(how)使うかに関しては早い段階から豊富な情報源がありましたが、それらがなぜ(why)存在し、いつ(when)使うべきかについてまとまった情報があるとは言えない状況でした。『Swift実践入門』はこうした状況を解消することを主眼としています。

Swiftの難しさ 〜Swiftらしいコードを書くために必要な知識〜

 単にSwiftでコードを書くことと、Swiftらしいコードを書くことは別のことです。ここで言うSwiftらしさとは、Swiftがその言語仕様を持って実現したい世界に倣ったコードのことです。Swiftは豊富な言語仕様を持っていますが、それは同時に、豊富な選択肢があるということを意味します。全てのケースに当てはめれるような解があるわけではなく、それぞれの仕様の存在意義を明確に把握し、都度適切な判断を下さなければなりません。

 初学者がSwiftの言語仕様をある程度理解した後に躓きやすい典型的な箇所として、次のようなものが挙げられます(「使い分け」という言葉が繰り返し登場することから、同じことをするにも複数の方法があることが見て取れるでしょう)。

  • Optional<Wrapped>の使い所
  • 高機能なパターンマッチ
  • モジュールのアクセスレベル
  • プロトコルと継承の使い分け
  • キャプチャリストの使い分け(weakunowned、何もつけない)
  • 値型と参照型使い分け
  • ifguardの使い分け
  • 定数と変数の使い分け
  • エラー処理の使い分け(Optional<Wrapped>do-catchResult<T, Error>)

 例えば、「Optional<Wrapped>の使い所」を説明するために、書籍内では次のようなコードが登場します。どちらにどのようなメリットがあるか適切に説明できるでしょうか。

// 全てのプロパティがOptional<Wrapped>型のケースstructUser {
    letid:Int?
    letname:String?
    letmailAddress:String?

    init(json:[String : Any]) {
        id = json["id"] as? Int
        name = json["name"] as? String
        mailAddress = json["mailAddress"] as? String
    }
}

// Failable Initializerを使うケースstructUser {
    letid:Intletname:StringletmailAddress:String?

    init?(json:[String : Any]) {
        guardletid= json["age"] as? Int,
              letname= json["name"] as? String else {
            returnnil
        }

        self.id = id
        self.name = name
        self.mailAddress = json["email"] as? String
    }
}

 『Swift実践入門』は、これらのこと1つ1つ言及し、読者のwhywhenを解消します。

おわりに

 『Swift実践入門』はその名の通り、単なる入門書や言語仕様の解説にとどまらない、実践的な内容を扱っています。著者2人で450ページ強というなかなかのボリュームですが、これからSwiftをはじめようという方から、よりその知識を深めたいという方にまで、ぜひ手にとっていただきたい一冊です。

 一方、『Swift実践入門』は実践的な書籍ではありますが、やはり本当の意味での「実践」からしか得られないこともたくさんあります。例えば、クックパッドでは長期間に渡って大量のユーザーを支えることができるアプリケーションを構築する必要がありますが、そのためにはさらに進んだトピックに取り組まなければいけません。例えば、チームに最適化されたStyle Guideを制定したり、肥大化するビルド時間に対処することがそれにあたります。

 クックパッドでは、こうしたAdvancedな課題に対処したいSwiftエンジニアも募集しています。

国内事業: https://recruit.cookpad.com/jobs/career_recruitment/ios-android/

海外事業: TokyoBristol

Viewing all 726 articles
Browse latest View live