毎週リリースを実現するテスト活動

こんにちは。 品質向上グループの茂呂一子(@ichiko_revjune)です。

クックパッドアプリは、サブミット・リリース作業を自動化して、アプリを毎週サブミットするようになりました。これを実現するリリースフローについては、 クックパッドアプリはみんなが寝ている間にサブミットされる で紹介しました。

このリリースフローを実現していく過程では、「機械に人間があわせる」という方針で、サブミット・リリース作業が自動化されていきました。つまり、毎週サブミット・リリースをするためには、何をどのように自動化するべきかという視点で自動化する対象が決まっていきました。

アプリは開発が終わればすぐにリリースできるというものではありません。この記事では、リリース前のテスト作業をどのように調整して、毎週リリースを実現しているのかを説明していきます。自動サブミットの導入はiOSアプリが先行したため、ここではiOS版クックパッドアプリについて説明します。

自動サブミット導入以前のリリース前テスト

この記事 にあるように、以前のリリースフローでは、開発期間、コードフリーズ、テスト期間を経てリリースをしていました。このテスト期間に行うテストをリリース前テストと呼びます。テスト期間は、3〜4営業日でした。

リリース前テストの内容は大きく3つです。

ひとつめは、UI操作を伴うシナリオテストを複数のテスト環境(iOSデバイスとiOSバージョンの組み合わせ)で実行することです。このシナリオテストは、Appiumを使って機械的に実行しています。

ふたつめは、自動化がむつかしいが重要な機能を手動でテストすることです。例えば、In App Purchase に関する機能や、写真撮影が必要な機能などです。

みっつめは、各リリースに含まれる変更に関連した機能を手動でテストすることです。変更内容から起こりそうな不具合を想像し、それを実際に確認するというような作業です。探索的テストと呼ばれるものに近いでしょう。

これらを実施して不具合が見つかれば修正し、リリース前にその修正がうまくいっているかを確認していました。

自動サブミット導入のために

自動サブミット導入後もリリース前テストはなくなりません。スケジュールされた自動サブミットが完了し、テスト対象が確定してからリリース前テストが始まります。

先に説明した、以前のリリースフローにあわせて設計したテストをそのまま実施すると3〜4営業日かかることになり、リリース前テスト以外の活動にあてられる時間がなくなってしまいます。わたしたちは自動化したシナリオテストを持っているため、テスト実施以外にも、これらをメンテナンスする時間は必要です。また、テストツールを改善したり、リリースフローを改善するなどの活動もリリースを続ける上では欠かせません。テストのやり方か内容を変更しなければ、毎週のサブミットに耐えられなくなってしまいます。

自動化できるところは自動化し、人間にしかできないことを人間が行うという方針で、テスト作業を効率化してきました。自動サブミット導入当初はサブミットからリリースまで、2〜3営業日かかっていました。しかし、最近は2営業日でリリースできるようになってきました。

幸いにして、大きな不具合を出すことなく、毎週のサブミットとリリースが実現できています。テスト作業をどのように変えていったのかを課題ごとに説明していきます。

一番時間がかかるのはシナリオテスト実行

既に自動化してあった200件弱のシナリオテストは、ひとつのテスト環境(iOS デバイスとiOSバージョンの組み合わせ)での実行に10時間以上かかっていました。加えて、以前はシナリオテストをCIマシン上で実行させる為の環境整備が追いついておらず、手元のPC上で実行していました。そのため、毎晩の就業時間外にひとつのテスト環境を選び、実行していました。

リリース前テストでは、iPhone/iPadいずれの環境でも動かすことと、サポートiOSバージョンを網羅することを満たすよう、複数のテスト環境で実行します。したがって、1リリースあたりのシナリオテストにかかる時間は、10時間かけるテストする組み合わせの数でした。

これらの問題はCIマシンでの実行と、シナリオテストの並列実行という方法で実行時間を短縮しました。

今年に入ってから、CIマシン上で実行することは試みていました。そして、自動サブミット導入の頃に準備が整い、本格的に実行をCIマシンに任せるようになりました。シナリオテストを実行しているCI環境はビルド用のCI環境とは別のものを使用しています。理由はふたつあり、ひとつめは、実行にかかる時間が長いため短時間で終了するビルドタスクと混ぜて実行するべきでないためです。ふたつめは、最新のOSでは動作が不安定になりやすく、ビルド環境と同じタイミングでOSを更新できないことが多いためです。

シナリオテストの並列実行は、今年チームに加わった加藤が、実行の高速化を目的に実現してくれました。並列実行と呼んでいるのは、同じデバイスとiOS を使用するiOS Simulatorを複数起動し、別のシナリオを同時に実行することです。UIテストのボトルネックのひとつにSimulatorを操作する時間があるため、Simulatorを増やすことに効果がありました。これによって、1つのテスト環境でのテスト実行にかかる時間が約3分の1になり、実行時間の総量も同じように少なくなりました。

実行時間の短縮という他にも、金曜日から日曜日にかけてシナリオテストを実行するようスケジュールして、実行開始が遅延してもシナリオテストが終わるのを待つ時間が発生しにくいように工夫しています。自動サブミットは金曜日の早朝に動きだすので、順調にいけば金曜日の午後にはシナリオテスト結果がそろい、レポートを見れるようになっています。

シナリオテスト実行の指示を毎回人間がしていた

シナリオテストは、1回のリリースのために複数のテスト環境で実行しています。テスト環境は固定しておらず、リリースごとに変え、ある程度の期間でみたときに、iOSデバイスサイズとiOSバージョンを網羅するように設計してきました。

自動サブミット導入以前は、このテスト環境の選定を人間が行い、実行のたびに指定していました。

自動サブミット導入後は、このテスト環境の選定をスクリプト化し、実行のたびに指定しなくて済むようにし、機械による無作為抽出を導入しました。

機械に任せる領域を増やして、リリース前テストの準備をひとつ減らしました。

お手本画像を人間が更新していた

シナリオテストツールには、表示くずれを見つけるために、シナリオテスト中で取得したスクリーンショット画像をお手本画像と比較して、差分を表示する機能があります。

このお手本画像の管理には2つの課題がありました。ひとつめは、テストツールの実行環境ごとにファイルを管理しており、すべての環境で同じファイルを使っている保証がなかったことです。ふたつめは、人間が差分画像を見ながら更新すべきファイルを探し、コピー&ペーストでお手本画像を更新していたことです。実行環境が複数存在したため、特定の環境への反映漏れということも起きていました。

この作業を改善するにあたって、まず、お手本画像はAWS S3に一元管理するようにしました。実行のたびに、S3から最新のお手本画像を取得し、どこでも同じお手本を使用するようにしました。

次に、更新作業の一部をスクリプト化し、更新するべきかの判断だけを人間が行うようにしました。人間は差分画像の一覧をみながら、更新する画像にマークをつけます。その選択結果をファイルに出力し、それをスクリプトに与えて実行すればS3上にファイルが送信されます。

単純作業を機械に任せることで、人間は不具合の探索に集中しやすくなりました。

シナリオテスト結果から不具合をみつける手助け

シナリオテストは、UI操作を伴うため、不具合がなくてもシナリオが失敗することがあります。本当に不具合があるのかは最終的に、人間が実機を操作して判断することになります。その失敗が不具合の可能性が高いのか、低いのかを知ることで、この再操作を減らすことができます。

失敗しやすいシナリオや不安定なシナリオを見つける目的で、シナリオごとの実行結果を蓄積、可視化するアプリケーションを構築しました。このアプリでは、同じバージョンのアプリに対する複数のテスト環境での実施結果の成否を一覧できます。「どのテスト環境でも失敗しているので不具合の可能性が高い」「ひとつの環境だけ失敗しているので不具合の可能性が低い」といった具合に参考情報を簡単に見つけることができるようになりました。

f:id:ichiko_revjune:20181212111114p:plain
シナリオごとの実行結果を蓄積、可視化

このアプリがない頃は、複数のレポートを開いて見比べていました。成功失敗というデータに絞って可視化することで、ずいぶんと容易に理解できるようになりました。

自動化できない手動テストの効率化

自動化することはできないけれど重要な機能、例えば In App Purchase に関する機能は毎回手動でテストをしています。リリースごとの追加機能を対象とするテストではないので、ほぼ内容は決まっています。テスト作業の見直しを始めた時点では、削ってしまえる機能もありませんでした。

これらの手動で実施するテストの項目は、厳密なテスト手順を定めておらず、この機能でなにができること、という満たすべき項目をリストにしています。探索的テストのチャーターのようなものでしょうか。

自動サブミット導入以前も、関連機能に変更がなければ省略してもよい項目はあり、省略することもありました。自動サブミット導入後は、これを厳格に適用して、関連機能に変更がなく不具合が発生しないであろう機能へのテストを省略するようにしました。合理的にテストしすぎない工夫としては、うまくいっている方ではないでしょうか。

これからの課題

小さな改善を重ねてリリース前テストに時間をかけ過ぎることなく、毎週のリリースを実現している方法を紹介しました。

サブミット自動化後のリリースフローは、リリース前テストで致命的な不具合が見つかった場合、リリースをあきらめるルールです。このフローがうまくまわっていくためには、不具合が入りにくいしくみや、機能変更をマージする前に不具合に気づきやすくするしくみが必要です。そのためには、開発チーム内のテスト技術を向上させることも必要でしょう。これらの点ではまだ効果的なアクションを取れていないので、急がず確実に小さな変化を積み重ねていきたいです。

iOSでの読みやすい幅

モバイル基盤グループのヴァンサン(@vincentisambart)です。

iOSの設定画面の右側は一定の幅を超えないように作られています。

  • iPadでは: iPadでの設定画面

  • 新iPad Pro 12.9"では: iPad Pro 12.9インチでの設定画面

iPadでTwitterのタイムラインのセルの中身も一定の幅を超えません。

iPad Twitter

このように、自分のアプリで広い画面でもコンテンツが広がりすぎないようにするためにはどうすればよいのでしょうか。AutoLayoutでいくつかの制約を使ってできるのですが、もっと簡単な方法はないのでしょうか。

iOS 9以上では、端末の種類を気にせず、複雑なAutoLayout制約を使わず、殆どのビューですぐ使える仕組みがあります。Appleのドキュメントで「readable content」や「readable width」と呼ばれているものです。以下日本語で「読みやすい幅」と呼ぶことにします。

注意点:下記の説明はAutoLayoutを使う前提で書かれています。AutoLayoutを使わない場合、親ビューのreadableContentGuidelayoutFrameで読みやすい幅の明確な数字が取れるので、それを元にレイアウトを計算することになるのではないでしょうか。

(Twitterは読みやすい幅機能が使える前からこの表示でしたので、実際独自で同じことを実装しているようです。)

読みやすい幅とは

文章の表示領域の幅が広がりすぎると、少し読みづらく感じてしまいませんか。iOS 9以上では、readableContentGuideを使って幅が広がりすぎないようにできます。ビューのレイアウトの制約を定義する時、親ビューのleadingAnchor/trailingAnchor/leftAnchor/rightAnchorlayoutMarginsGuideの代わりにreadableContentGuideを使います。(因みに読みやすい幅のためのガイドなので、readableContentGuideの垂直方向のアンカーtopAnchorbottomAnchorcenterYAnchorheightAnchorが実質layoutMarginsGuideのと同じです)

縦向きのiPhoneにしか表示されないコンテンツは読みやすい幅を使うメリットがあまりないのですが、iPadや横向きのiPhoneでも表示されるコンテンツは画面の全幅に渡ると少し読みづらくなるので読みやすい幅を適切に使えば良いではないでしょうか。

因みに「読みやすい」といっても、テキストの幅を合わせるだけでなく、セルに入っている画像やボタンの配置も読みやすい幅に合わせたほうが自然だと思います。

「読みやすい幅」は具体的にどういう幅なのでしょうか。全画面のroot view controllerのビューに子ビューを貼って、その子ビューの制約を親ビューのreadableContentGuideに合わせて、子ビューの幅を測ってみました。

iPadでの表示: f:id:vincentisambart:20181206092236p:plain

端末 向き 画面全幅③ 読みやすい
最大幅④
余白
(③-④)
左右マージン
((③-④)/2)
iPhone Xs Max
iPhone Xr
iPhone 8 Plus
414 pt 374 pt 40 pt 20 pt
iPhone Xs Max
iPhone Xr
896 pt 672 pt 224 pt 112 pt
iPhone 8 Plus 736 pt 696 pt 40 pt 20 pt
iPhone Xs
iPhone 8
375 pt 343 pt 32 pt 16 pt
iPhone 8 667 pt 627 pt 40 pt 20 pt
iPhone Xs 812 pt 772 pt 40 pt 20 pt
iPhone SE 320 pt 288 pt 32 pt 16 pt
iPhone SE 568 pt 528 pt 40 pt 20 pt
iPad 768 pt 672 pt 96 pt 48 pt
iPad 1024 pt 672 pt 352 pt 176 pt
iPad Pro 11" 834 pt 672 pt 162 pt 81 pt
iPad Pro 11" 1194 pt 672 pt 522 pt 261 pt

上記のサイズは画面の全幅のを使う場合の数字ですが、readableContentGuideが必ず自分のビューのlayoutMarginsGuideに収まるように設計されているので、上記の幅が最大値です。親ビューのマージンがもっと大きかったり、親ビューがもっと小さかったりすると、読みやすい幅が小さくなります。

また、注意すべき点として、readableContentGuidelayoutMarginsGuideの水平方向で中央になるように設計されているので、左右のマージンが違っていれば、readableContentGuideがビューの中央になりません。ただ様々な設定によって(マージンが例えばsafe areaを含むように)マージンが変わるので、指定した左右のマージンが違ってもreadableContentGuideがビューの中央になることもあります。

表の数字をまとめてみると、iPadでは、画面サイズや向きが何であろうと、幅が最大672 ptになるように計算されているようです(上記の表に載っていないiPad Pro 10.5"もiPad Pro 12.9"もそうです)。横向きのiPhone XsやiPhone 8 Plusでは、読みやすい幅がiPadより大きくなるのか少し気になりますが。

縦向きのiPhoneではreadableContentGuideを使うと左右の余白が最低でも16 ptになるように見えます(因みにiOSで殆どのビューの標準のマージンが8 ptです)。ただし、色んなケースを試してみると、親ビューが全画面でない場合、左右の余白が12 ptになったりしますし、layoutMarginsdirectionalLayoutMarginsで(またはInterface Builderで)親ビューのマージンを小さくすると、読みやすい幅の左右の余白がもっと小さくなることがあります。絶対なのはマージンに収まるように設計されているところだけのようですね。

上記の数字にはsafe areaが含まれていません。readableContentGuideをそのまま使うとsafe area外になる可能性があります。本当に画面全体でreadableContentGuideを使う場合、そのreadableContentGuideを持っているビューのinsetsLayoutMarginsFromSafeAreatrueにすれば良いかもしれません。insetsLayoutMarginsFromSafeAreatrueにすると、マージンにsafe areaが含まれるようになり、readableContentGuideがマージン内になるように設計されているので、readableContentGuideもsafe area内になります。因みに、safe area関連の機能は全部iOS 11以上でしか使えません。

Interface Builder

コードでは、readableContentGuideを使って制約を定義すれば良いのですが、ビューをInterface Builderで配置する場合、どうすれば良いのでしょうか。

やるべきことが2つあります:

  • 読みやすい幅に合わせたいビューの親ビューのレイアウトの設定に「Follow Readable Width」にチェックを入れます。

    f:id:vincentisambart:20181206090752p:plain:w295

  • 読みやすい幅に合わせたいビューの配置に使われるsuperviewに対する左右の制約がマージンに対する制約である必要があります。
    • 制約の詳細にある「First/Second Item」が「Superview.Leading」や「Superview.Trailing」ではなく、「Superview.Leading Margin」や「Superview.Trailing Margin」である必要があります。変えるには、その「First/Second Item」をクリックして、出てくるメニューに「Relative to margin」を選びましょう。

      f:id:vincentisambart:20181206091213p:plain:w295

    • 「First/Second Item」が「Safe Area」の場合、それをまず「Superview」に変えてから、「Relative to margin」を選びましょう。

      f:id:vincentisambart:20181206091038p:plain:w295

ビューが必ずsafe area内に配置されるようにしたかったら、親ビューのレイアウト設定に「Safe Area Relative Margins」にチェックを入れてください。コード上ではinsetsLayoutMarginsFromSafeAreaと同じです。

Dynamic Type

読みやすい幅は便利そうではありますが、使う前に注意すべき点が1つあります:Dynamic Typeです。

Dynamic Typeというのはユーザーが使われるフォントのサイズを変えられる機能です。iOS全体の設定に一般→アクセシビリティ→さらに大きな文字(英語だとSettings→General→Accessibility→Larger Text)で変えられます。対応しているアプリではフォントサイズがその設定に合わせられます。

readableContentGuideのドキュメントを見ると、従われるルールが3つあります:

  • The readable content guide never extends beyond the view’s layout margin guide.
  • The readable content guide is vertically centered inside the layout margin guide.
  • The readable content guide’s width is equal to or less than the readable width defined for the current dynamic text size.

1つめと2つめのルールは既に説明しましたが、3つめはまだでした。上記の表の数字はアクセシビリティ設定で標準のフォントサイズが選択されている場合の値です。もっと大きいフォントサイズが選ばれている場合、読みやすい幅が(マージン内で)広くなります。もっと小さいフォントサイズが選ばれている場合、読みやすい幅が狭くなります。

別の言い方をすると、iOSの「読みやすい幅」の制限が行のポイント数ではなく、その行に入れる文字数と言えるかもしれません。

Dynamic Type対応を既にしてあるアプリは幅が選ばれたフォントサイズに合わせられた方が綺麗に表示されますが、Dynamic Type対応をしていないアプリは大きいフォントサイズが選択されている場合表示が少し読みづらくなりそうです。

ユーザーの選んだフォントサイズカテゴリはUIApplication.shared.preferredContentSizeCategoryで取れます。カテゴリが多いので、どう変わるのかのスクリーンショットは標準サイズ(large)、一番小さいサイズ(extraSmall)、「さらに大きな文字」が有効になっていない場合の一番大きいサイズ(extraExtraExtraLarge)、「さらに大きな文字」が有効になっている場合の一番大きいサイズ(accessibilityExtraExtraExtraLarge)だけのにしました。

  • large (標準設定) f:id:vincentisambart:20181206092236p:plain
  • extraSmall f:id:vincentisambart:20181206092208p:plain
  • extraExtraExtraLarge f:id:vincentisambart:20181206092011p:plain
  • accessibilityExtraExtraExtraLarge f:id:vincentisambart:20181206091716p:plain

読みやすい幅のまとめ表

Dynamic Typeの設定によって読みやすい幅の数字をまとめてみました。上記のスクリーンショット同様4つのカテゴリだけに絞りました。

どう変わるのか見せるために明確な数字を載せていますが、その明確な数字をビューの配置のために使わないでおきましょう。今後iOS端末の画面サイズの種類がまた増えても不思議ではありませんし、今後iOSのメジャーアップデートで数字が少し変わる可能性もあります。ビューをpixel perfectで配置できていた時代はもう終わっています。

iPhone SE

preferred content size category 向き 画面全幅③ 読みやすい
最大幅④
余白
(③-④)
左右マージン
((③-④)/2)
large 320 pt 288 pt 32 pt 16 pt
large 568 pt 528 pt 40 pt 20 pt
extraSmall 320 pt 288 pt 32 pt 16 pt
extraSmall 568 pt 528 pt 40 pt 20 pt
extraExtraExtraLarge 320 pt 288 pt 32 pt 16 pt
extraExtraExtraLarge 568 pt 528 pt 40 pt 20 pt
accessibilityExtraExtraExtraLarge 320 pt 288 pt 32 pt 16 pt
accessibilityExtraExtraExtraLarge 568 pt 528 pt 40 pt 20 pt

フォントサイズが変わっても、読みやすい幅は変わらないようですね。

iPhone 8

preferred content size category 向き 画面全幅③ 読みやすい
最大幅④
余白
(③-④)
左右マージン
((③-④)/2)
large 375 pt 343 pt 32 pt 16 pt
large 667 pt 627 pt 40 pt 20 pt
extraSmall 375 pt 343 pt 32 pt 16 pt
extraSmall 667 pt 560 pt 107 pt 53 pt
extraExtraExtraLarge 375 pt 343 pt 32 pt 16 pt
extraExtraExtraLarge 667 pt 627 pt 40 pt 20 pt
accessibilityExtraExtraExtraLarge 375 pt 343 pt 32 pt 16 pt
accessibilityExtraExtraExtraLarge 667 pt 627 pt 40 pt 20 pt

縦向きの場合、フォントサイズが変わっても、読みやすい幅は変わらないようですが、横向きだと(少し余裕あるので)少し変わるようです。

iPhone 8 Plus

preferred content size category 向き 画面全幅③ 読みやすい
最大幅④
余白
(③-④)
左右マージン
((③-④)/2)
large 414 pt 374 pt 40 pt 20 pt
large 736 pt 696 pt 40 pt 20 pt
extraSmall 414 pt 374 pt 40 pt 20 pt
extraSmall 736 pt 560 pt 176 pt 88 pt
extraExtraExtraLarge 414 pt 374 pt 40 pt 20 pt
extraExtraExtraLarge 736 pt 696 pt 40 pt 20 pt
accessibilityExtraExtraExtraLarge 414 pt 374 pt 40 pt 20 pt
accessibilityExtraExtraExtraLarge 736 pt 696 pt 40 pt 20 pt

iPhone 8同様、縦向きの場合、フォントサイズが変わっても、読みやすい幅は変わらないようですが、横向きだと(少し余裕あるので)少し変わるようです。

iPhone Xs

preferred content size category 向き 画面全幅③ 読みやすい
最大幅④
余白
(③-④)
左右マージン
((③-④)/2)
large 375 pt 343 pt 32 pt 16 pt
large 812 pt 772 pt 40 pt 20 pt
extraSmall 375 pt 343 pt 32 pt 16 pt
extraSmall 812 pt 560 pt 252 pt 126 pt
extraExtraExtraLarge 375 pt 343 pt 32 pt 16 pt
extraExtraExtraLarge 812 pt 772 pt 40 pt 20 pt
accessibilityExtraExtraExtraLarge 375 pt 343 pt 32 pt 16 pt
accessibilityExtraExtraExtraLarge 812 pt 772 pt 40 pt 20 pt

iPhone 8同様、縦向きの場合、フォントサイズが変わっても、読みやすい幅は変わらないようですが、横向きだと(少し余裕あるので)少し変わるようです。

iPhone Xr、iPhone Xs Max

preferred content size category 向き 画面全幅③ 読みやすい
最大幅④
余白
(③-④)
左右マージン
((③-④)/2)
large 414 pt 374 pt 40 pt 20 pt
large 896 pt 672 pt 224 pt 112 pt
extraSmall 414 pt 374 pt 40 pt 20 pt
extraSmall 896 pt 560 pt 336 pt 168 pt
extraExtraExtraLarge 414 pt 374 pt 40 pt 20 pt
extraExtraExtraLarge 896 pt 856 pt 40 pt 20 pt
accessibilityExtraExtraExtraLarge 414 pt 374 pt 40 pt 20 pt
accessibilityExtraExtraExtraLarge 896 pt 856 pt 40 pt 20 pt

他のiPhone同様、縦向きの場合、フォントサイズが変わっても、読みやすい幅は変わらないようですが、横向きの場合、フォントサイズによって(もう少し余裕あるので)それなりに変わるようです。

iPad

preferred content size category 向き 画面全幅③ 読みやすい
最大幅④
余白
(③-④)
左右マージン
((③-④)/2)
large 768 pt 672 pt 96 pt 48 pt
large 1024 pt 672 pt 352 pt 176 pt
extraSmall 768 pt 560 pt 208 pt 104 pt
extraSmall 1024 pt 560 pt 464 pt 232 pt
extraExtraExtraLarge 768 pt 728 pt 40 pt 20 pt
extraExtraExtraLarge 1024 pt 896 pt 128 pt 64 pt
accessibilityExtraExtraExtraLarge 768 pt 728 pt 40 pt 20 pt
accessibilityExtraExtraExtraLarge 1024 pt 984 pt 40 pt 20 pt

小さめなフォントサイズの場合、縦向きと横向きでは幅が同じですが、大きめなフォントサイズの場合そういうわけでもありませんね。

iPad Pro 11"

preferred content size category 向き 画面全幅③ 読みやすい
最大幅④
余白
(③-④)
左右マージン
((③-④)/2)
large 834 pt 672 pt 162 pt 81 pt
large 1194 pt 672 pt 522 pt 261 pt
extraSmall 834 pt 560 pt 274 pt 137 pt
extraSmall 1194 pt 560 pt 634 pt 317 pt
extraExtraExtraLarge 834 pt 794 pt 40 pt 20 pt
extraExtraExtraLarge 1194 pt 896 pt 298 pt 149 pt
accessibilityExtraExtraExtraLarge 834 pt 794 pt 40 pt 20 pt
accessibilityExtraExtraExtraLarge 1194 pt 1154 pt 40 pt 20 pt

通常のiPad同様、小さめなフォントサイズの場合、縦と横では幅が同じですが、大きめなフォントサイズの場合そういうわけでもありませんね。

セル

一番上に載せたiOSの設定画面の例もTwitterの例もテーブルでした。Twitterの方は左右の余白でもセルがタッチに反応するので、読みやすい幅に合わせられたのはテーブル自体ではなく、セル内のコンテンツでしょう。

セルの場合、普通のビューと少し違うところあるので、それを見ましょう。

UITableView

UITableViewのセルの挙動は他のビューと同じです: - コードでビューを配置する場合、readableContentGuideを使えます。 - Interface Builderを使う場合、制約はマージンに対して定義し、親ビュー(おそらくcontent view)のレイアウト設定の「Follow Readable Width」にチェックを入れたら読みやすい幅に合わせられます。

でも、よりよい方法として、UITableViewcellLayoutMarginsFollowReadableWidthというプロパティを使う方法もあります。Interface Builderでは、UITableViewの「Follow Readable Width」にチェックを入れるのと同じです。

cellLayoutMarginsFollowReadableWidthは自分が定義したカスタムセルだけではなく、テーブルの区切り線にもUIKitが提供しているカスタムでないCellStyleにも影響しています。

いくつかのセルCellStyleの一例、cellLayoutMarginsFollowReadableWidthfalseの場合: f:id:vincentisambart:20181206092608p:plain

cellLayoutMarginsFollowReadableWidthtrueの場合: f:id:vincentisambart:20181206092711p:plain

因みにcellLayoutMarginsFollowReadableWidthtrueの場合、content viewのlayoutMarginsGuidereadableContentGuideのどっちを使っても同じことになります。

UICollectionView

UICollectionViewに全幅に渡るセルがあれば、読みやすい幅を使いたくなることあるかもしれません。ただ、UICollectionViewにはcellLayoutMarginsFollowReadableWidthのようなプロパティがありません。

コードではreadableContentGuideは使えますが、Interface Builderでセル(実質セルのcontent view)のレイアウト設定の「Follow Readable Width」にチェックを入れても効果がありません(バグ)。でもワークアラウンドとしてcontent viewの中にビューを入れ、そのビューの「Follow Readable Width」にチェックを入れ、他のビューをその中に入れ、制約をそのビューのマージンに対して定義すれば動くようです。

まとめ

iOSでは、対応すべき画面サイズが少しずつ増えています。先日発表された新しいiPad Proも以前のと画面サイズが少し変わりました。事前に分かっている数少ない画面サイズを元にアプリの配置を決められる時代が終わっています。

その中でできるだけ多くの画面サイズに合わせて綺麗に配置できるツールの1つとして、読みやすい幅、または読みやすいコンテンツ、というのがあります。

読みやすいコンテンツを使うときはドキュメントにも書いてある3つのルールを覚えておきましょう:

  • readableContentGuideが自分のビューのlayoutMarginsGuide外に出ることはありません。
  • readableContentGuideが水平方向でlayoutMarginsGuideの中央にあります。
  • readableContentGuideの幅が選ばれたDynamic Typeのフォントサイズ次第で決まる読みやすい幅以下です。

得られた知見をフリーズドライ〜情報共有のための仕組み Report.md の紹介〜

こんにちは、会員事業部の新井(@SpicyCoffee66)です。今年はレシピサービスにおける体験改善を主な業務としていました。
サービス開発かラブライブ!の話をすると早口になります*1。今日はついにスマブラが発売されるのでおそらく早退します。

さて、本記事ではサービス開発において重要な要素である施策結果・知見のプールや共有について、社内でどのような取り組みが行われているのかを紹介したいと思います。

施策の結果から最大限に学びを得たい

私たちはサービス開発を進める中で日々多くの施策を実施することになります。
サービス開発のプロセスにおいて、施策は実施して終わりではなく、その結果からいかに多くの学びを得るのかということが重要になります。
施策の結果から学びを得るためには、その施策の意図や結果を可能な限り 正しく 解釈し、それを(将来入ってくるメンバーを含めて)より多くの人に 共有 することが必要です。
しかし、サービス開発の現場では以下のようなことが往々にして発生します。

  • 間違った知見が共有される
    • 結果の数字の読み取り方がおかしい
    • そもそもKPIの設定がおかしい
    • 検証の期間が短すぎて正確性に欠ける
    • 施策の目的と手法が噛み合っていなかった
    • といったようなことに気がつかず正しい知見として共有される
  • 知見がまるで共有されない
    • どこにプールされているか判然としない
    • 知見が属人的になる
    • ならまだいい方で時が経って本人もはっきり覚えていない感じになる
    • 最悪プールすらされてなくて闇に消える
    • 結果新規メンバーが似たような失敗を繰り返す

僕自身、入社して間もない時期には過去に実施された施策の詳細を探すも見つからず、歯がゆい思いをしたことが数多くありました。
クックパッドのように歴史の長いサービスであれば、自分が思いつくアイディアについて過去に誰かが似たような施策に取り組んでいたことも多く、その時の結果を参照できないのは非常にもったいないと感じました。

このような問題を解消するため、社内では昨年度途中から Report.md という仕組みが利用され始めました。

Report.md

Report.md を一言で言うと「施策の結果を Pull Request で管理する仕組み」となります。
具体的には

  1. 担当者は施策の終了後、Markdown形式でその内容をまとめた report を作成し、PRを送信する
  2. チームメンバーがその report をレビューする
  3. いい感じになったらマージし、report をプールする

という手順を踏んで施策の結果をレビューし、ストックしていきます。

f:id:spicycoffee:20181206192507p:plain
PR を出して
f:id:spicycoffee:20181206192525p:plain
レビューして
f:id:spicycoffee:20181206192536p:plain
マージする

こうすることで

  • 施策の結果を正しく解釈する → レビューを通すことによって精度が上がる
  • 多くのメンバーに共有する → report のプール箇所が明確になり参照しやすくなる

といった効果が上がり、前述した二つの目的を果たすことが可能になりました。
コードをレビューして(質を高めて)、マージする(永続化する)という普段エンジニアがやっているフローが、施策の結果に対しても有効だったわけです。

いつ書くか?

上記手順では、Report.md のイメージを持っていただくために施策終了後としましたが、施策の実施前に Report.md を作成することで、仮説や検証内容が明確になり、施策がブレにくくなるという効果があります*2
導入初期は施策後に作成して「結果の共有」のためにのみ利用するのもよいと思いますが、慣れてきたらぜひ施策の実施前に作成するフローで運用することをオススメします。
その場合の手順は

  1. 担当者は施策の実施前に、Markdown形式でその内容をまとめた report を作成し、PRを送信する
  2. チームメンバーがその report をレビューする
  3. いい感じになったらマージし、 report をプールする
  4. マージされた report をもとにして施策を実施する
  5. 施策終了後、その結果を report に追記する
  6. チームメンバーが report をレビューする
  7. いい感じになったらマージし、完成版の report としてプールする

といった形になります。

f:id:spicycoffee:20181207115725p:plain

何を書くか?

Report.md に何を書くかについては、運用しているチームによって様々です。
ここでは、私が見た事例の中で一番よくまとまっていると感じたテンプレートをもとにして内容を紹介していきます。
また、以下にあげる内容のうち、「仮説」「仮説分析」「検証方法」「結果の想定」については、施策の実施前に記載することでその指針とすることができる内容になります。

3 行まとめ

作成された report を読むのは自分だけではないことを考えると、まずは概要を読んでから詳細を読むべきか判断できる構造になっていた方が親切です。

仮説

report の中でも非常に重要な項目です。
仮説をしっかり定義できていないと検証設計がぶれ、結果を次に活かすことが難しくなります。
仮説をどのように表現するかはチームによりますが、社内独自のフレームワークである価値仮説シート*3に沿って表現することが多いです。
形式に迷う場合は
(ターゲット) は (ジョブ/インサイト) したいが (何らか要因でそれができていない状況) なので、 (体験) すると (目的) になる
といったフォーマットで表現してみるのがよいと思います。

仮説分析

そもそも上記の仮説はどこからきたのか、その妥当性はあるのかといったような仮説の価値自体やその詳細を示す項目です。
具体的には

  • ターゲットボリューム
    • 「仮説」で定義したターゲットの条件に当てはまる人がどのくらいいるのかを試算する
  • 事前検証・前回施策の結果
    • この施策に繋がる情報を持った施策 report へのリファレンスを貼っておくとわかりやすい
  • 落としてはいけないコア機能
    • 「仮説」の内容をより明確にするためにコアとなる要素を明記する
  • 諦めること
    • 検証のブレを無くすためにやらないことを明記する
  • 技術的に難しいこと
    • 期間やリソース都合を含め、実装側の都合で諦めたことを明記する

「仮説」を所与のものとしても report は成立しますし、あまり情報量が多くなっても report の骨子が読みづらくなるため、施策によって項目は柔軟に変更するのがよいと思います。
なるべく外部資料や別 report への参照を貼るよう工夫したり、<details> タグを利用して情報を畳んでおくのもおすすめです。

検証方法

「仮説」の項目で書いた「体験」を実現するための方法を示し、その内容を具体的に解説します。

  • 検証項目
    • 施策によって答えを出したい問い
    • 仮説そのものに対する問いになることもあれば、仮説をもとに考えた機能の有用性に対する問いになることもあります
  • 検証方法・提供機能
    • 仮説に対してどのような機能を提供してそれを検証するか
    • あるいはどのような資料をもとにユーザーにヒアリングを行うか
  • 検証資料・機能詳細
    • それらの機能や資料の詳細
  • 検証期間・人数
  • 評価方法

といったような内容を盛り込むとよいでしょう。

結果の想定

「検証方法」で定義した「評価方法」に対し、どのような結果が出たときに「仮説」をどう評価すればよいかについて可能な限り明記しておく。

  • 定量検証の場合
    • CVR が x % 以上向上した場合は仮説が正しかったと考える
  • 定性検証の場合
    • ユーザーインタビューで y というような反応が z 人以上見られた場合は仮説が正しかったと考える

根拠を持って詳細な数値を設定するのは難しいことが多いですが、過去の施策やチーム内での議論をもとに目安を設定してメンバーの目線を揃えておくことが重要です。

検証結果

検証の結果をまとめます。
主観的な考えや分析は後の「考察」に記載するとし、ここではそれに必要な結果のみを記載します。

考察・ギャップ分析

「結果の想定」と「検証結果」を比較し、自分たちの仮説や認識について合っていたことと間違っていたことを明らかにしていきます。
長文で書き綴るよりも検証項目ごとに箇条書きで簡潔にまとめる方が振り返りやすくてよいかと思います。

Next Action

「考察・ギャップ分析」の内容を受けて、この施策を次にどうするか、具体的なアクションを記載します。

Report.md 運用の所感

メリット

Report.md を社内で運用していくうちに、以下のようなメリットがあることが感じられるようになりました。

  • 施策結果の解釈の精度が上がる
  • 施策そのものがブレづらくなる
  • 施策の結果を気軽に共有できるようになる
    • Slack で「こういう感じのことやったことある人いないですか?」「お、それなら前にやりましたよ(report のリンクを貼る)」といったやり取りが数多く見られるようになりました
  • サービス開発者の成果が可視化されやすくなった*4
    • 期末の評価期間に自己評価を執筆する際に上長に成果を提示しやすくなりました

デメリット

個人的には非常に有用な仕組みだと感じてはいますが、デメリットも存在しているとは思います。
最も大きいのは「施策に関して目先の実行速度が遅くなる」ことです。
Report.md をしっかり作成しようとすると、それ相応のコストが掛かります。
その分施策に対する理解が深まったり、後々参照できる資産になったりという大きなメリットがあるとは思いますが、サービスのフェーズやチームの雰囲気によっては、メリットがコストに見合わないかもしれません。
そういった場合には、記載する項目を取捨選択したり施策と同時並行で作成するなどの工夫によって、report 作成のコストを抑えるのがいいでしょう。

今後の課題

今後の課題として、report を通した情報共有をより活発にしたいと思っています。
Report.md 自体はあくまでも「施策の結果がチームごとに一箇所に集まる」ものでしかなく、組織横断的に情報を提供してくれる仕組みではありません。
したがって、より容易に情報が社内に行き渡るような仕組みと組み合わせることでその効果をさらに高めることができるのではないかと考え、その方法を模索しています*5

まとめ

本記事では、サービス開発において実は失敗しがちな知見のプール・共有について社内でどのような取り組みがなされているかを紹介しました。
クックパッドでは、技術力を大切にしているのはもちろんのこと、サービス開発そのものについてもその手法を洗練させていくことで、よりすばやくユーザーに価値を届けようと日々努力しています。
そして、そういった想いのもと一緒にサービスを作り上げていってもらえるメンバーを募集中です!
このような姿勢や働き方に興味を持っていただけたなら、ぜひ一度採用サイトをチェックしてみてください。
興味はあるけどいきなり採用の話は……という方は、気軽に @SpciyCoffee66 まで連絡してください。
クックパッド名物の(?)キッチンラウンジで美味しいご飯を食べながら社内の様子についてお話しましょう!

*1:早口になるほど好きなのでサービス開発者のコミュニティである s-dev talks を運営しています。

*2:施策の実施前にどのようなことを考えればいいかについては別の記事を投稿させていただいているので、興味のある方はご一読ください。

*3:具体的なフォーマットは TechConf 2018 での講演や、この夏に開催されたインターンシップの資料をご参照ください。

*4:Report.md の前身となった取り組みに関する記事でも少し触れられています。

*5:最近になってまずは専用のドメインを切った社内ブログに集約してみるという動きが始まりました。

/* */ @import "/css/theme/report/report.css"; /* */ /* */ body{ background-image: url('https://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527163350.png'); background-repeat: repeat-x; background-color:transparent; background-attachment: scroll; background-position: left top;} /* */ body{ border-top: 3px solid orange; color: #3c3c3c; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; line-height: 1.8; font-size: 16px; } a { text-decoration: underline; color: #693e1c; } a:hover { color: #80400e; text-decoration: underline; } .entry-title a{ color: rgb(176, 108, 28); cursor: auto; display: inline; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; font-size: 30px; font-weight: bold; height: auto; line-height: 40.5px; text-decoration: underline solid rgb(176, 108, 28); width: auto; line-height: 1.35; } .date a { color: #9b8b6c; font-size: 14px; text-decoration: none; font-weight: normal; } .urllist-title-link { font-size: 14px; } /* Recent Entries */ .recent-entries a{ color: #693e1c; } .recent-entries a:visited { color: #4d2200; text-decoration: none; } .hatena-module-recent-entries li { padding-bottom: 8px; border-bottom-width: 0px; } /*Widget*/ .hatena-module-body li { list-style-type: circle; } .hatena-module-body a{ text-decoration: none; } .hatena-module-body a:hover{ text-decoration: underline; } /* Widget name */ .hatena-module-title, .hatena-module-title a{ color: #b06c1c; margin-top: 20px; margin-bottom: 7px; } /* work frame*/ #container { width: 970px; text-align: center; margin: 0 auto; background: transparent; padding: 0 30px; } #wrapper { float: left; overflow: hidden; width: 660px; } #box2 { width: 240px; float: right; font-size: 14px; word-wrap: break-word; } /*#blog-title-inner{*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-position: left 0px;*/ /*}*/ /*.header-image-only #blog-title-inner {*/ /*background-repeat: no-repeat;*/ /*position: relative;*/ /*height: 200px;*/ /*display: none;*/ /*}*/ /*#blog-title {*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-image: url('https://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527172848.png');*/ /*background-repeat: no-repeat;*/ /*background-position: left 0px;*/ /*}*/