Rubyの開発を支える技術

こんにちは、遠藤(@mametter)です。RubyKaigi Takeout 2020お疲れさまでした。

現在クックパッドには、フルタイムでRubyの開発をしている人が2人います(笹田と遠藤)。 それぞれ、Ruby 3の目標である並列性と静的解析の実現をメインミッションに据えて活動していますが、実はそれ以外にもRubyの開発を支えるための活動をいろいろやっています。

今回は、遠藤が関わっている範囲で、「Ruby開発者会議を支える技術」「Ruby開発のリモート議論を支える技術」「Rubyの品質を支える技術」についてざっと紹介してみます。

1. Ruby開発者会議を支える技術

Rubyに対する機能提案などの議論は、原則として、バグトラッカ上で行われます。 しかし、設計者であり最終決定権を持つmatzの多忙などの理由で、それだけでは議論が停滞してしまうのも事実です。 そこでRubyでは、開発促進のために月例で開発者会議を行っています。 私はここ数年、この会議を運営することにコミットしています。

会議のプロセス

毎月、次のことを行っています(検討以外はだいたいすべて私がやってます)。

  • 開催の約1ヶ月前に議題を募集するチケットを作る(例:先月のチケット)。
  • 開催の数日前、挙げられた議題をmarkdownにまとめ、有志のコミッタとともに事前検討をする(準備会)。
  • 会議当日、matzおよび有志のコミッタと本検討をする。結論の出た提案については、なるべくその場でmatzに回答してもらう。
  • 開催の数日後、議事録を清書して公開する(例:先月の議事録)。

コミュニケーション技術

会議当日のコミュニケーションには、クックパッド提供のzoomと、オンラインmarkdown共同編集ツールであるhackmdを使っています。 コロナ以前は東京近郊でのオフライン会場もありました(matzは大体リモート参加)が、今年は完全オンラインです。 アジェンダおよび議事録の共同編集はいろいろ試した末、「リアルタイムの共同編集ができ、markdownで書けて、コード断片も書きやすい」ということでhackmdに落ち着いています。

準備会

当日の会議は午後半日、約5時間をかけていますが、20件程度の議題をさばくには十分とは言えず、効率化が課題でした。 そのため今年から、数日前に有志での事前検討を行っています。 これにより、当日の参加者が誰も議題を理解していない、という非効率が回避できるようになりました *1 。 またこのフェーズで、事前に担当者に話題を振っておいたり、議題が曖昧なチケット・matz判断が不要そうなチケットに事前にフィードバックを返したりもします。 これにより、当日に議題をさばき切れないことはだいぶ減りました。

非互換の影響検討

仕様変更を議論する際には、かならず非互換の影響が議論されます。かつては互換性を気にしない言語とたびたび揶揄されていたRubyですが、現代ではかなり気を使うようになりました。

たとえば先日から、set.rbを組み込みにするという提案がたびたび議論されています(結論は出ていない)。 こういうとき、「トップレベルにSetクラスを自力定義している人は実際どのくらいいるか?」というような疑問が浮かびます。 このとき役に立つのがgem-codesearchです。

gem-codesearchがセットアップされているコミッタ用共用サーバにログインし、次のようにすることで、全最新版gemに対する高速grepができます。

$ gem-codesearch "^class Set$"
/srv/gems/ConstraintSolver-0.1/lib/extensions.rb:class Set
/srv/gems/GoNodes-0.0.1/lib/monkeypatch/set.rb:class Set
/srv/gems/Narnach-minitest-0.3.3/lib/set_ext.rb:class Set
/srv/gems/Ron-0.1.2/lib/ron.rb:class Set
/srv/gems/acapela-0.8.1/script/create_voices.rb:class Set
/srv/gems/annlat-0.0.1/lib/annlat/LaRuby.rb:class Set
/srv/gems/antlr4-0.9.2/lib/antlr4/base.rb:class Set
...

もちろん、この検索対象はあくまで公開gemだけなので、非公開アプリケーションのコードでどうかはわかりません。一方ここでコンフリクトする例が1つでも見つかったら諦めるというわけでもありません。それでも、非互換の影響の見積にはそうとう腐心しています。

なお、gem-codesearchはバックエンドにgoogle/codesearchを利用しています。codesearchはインデックスサイズに4GB制限があり、最近gemのコードサイズがこの制限に触れてしまったので、頭文字のアルファベット26文字+非アルファベット文字の27個にインデックスを分割するという延命をしたりしました(google/zoektも試しましたが、検索の起動がかなり遅く感じたので)。

クレジット:gem-codesearchはakrさんが開発し、hsbtさんがメンテナンスしています。コミッタ用共用サーバはRuby Associationのご提供で、gem-codesearchは私が管理しています。

dev-meeting-bot

議題を募集するチケットはコピペと手編集で作っていましたが、毎月となると、意外と重労働でした。 そこで、Slackボットを作りました。上述のチケットは、このボットにポンと作らせています。

他にも、最新の会議チケットを教えてくれる機能や、参加者にzoomやhackmdのURLをメールする機能が付いています。

クレジット:dev-meeting-botを含め以後紹介するSlackボットの多くは私が開発していますが、チケット作成機能は yebis0942 さんや znz さんのコントリビューションです。また、Herokuが計算機リソースを提供してくれています。

2. Ruby開発のリモート議論を支える技術

開発者会議以外でも、我々は日々Rubyの開発のための議論を行っています。 必要に応じてzoom(クックパッド提供)を使うこともありますが、ほとんどはSlack上での議論です。 そのためRuby開発者のSlackでは、議論を円滑にするためにいろいろなボットがうごめいています。 ざっとご紹介。

クレジット:Ruby開発者のSlackはRuby Associationにご提供いただいています。

all-ruby

all-rubyとは、これまでにリリースされたほぼすべてのruby(0.49から最新版まで)をビルドするスクリプト、およびその生成実行ファイルを集めたDockerイメージです。 みなさんのお手元でも、次のようにdocker pullして試すことができます。

$ docker pull rubylang/all-ruby
$ docker run --rm -ti rubylang/all-ruby
root@50d2785e9b39:/all-ruby# ./all-ruby -e 'puts "Hello"'
ruby-0.49             -e:1: syntax error
                  exit 1
ruby-0.50             -e:1: syntax error
                  exit 1
ruby-0.51             -e:1: undefined method `puts' for "main"(Object)
                  exit 1
ruby-0.54             -e:1:in method `puts': undefined method `puts' for "main"(Object)
                  exit 1
ruby-0.55             -e:1: undefined method `puts' for "main"(Object)
                  exit 1
...
ruby-0.76             -e:1: undefined method `puts' for "main"(Object)
                  exit 1
ruby-0.95             -e:1: undefined method `puts' for main(Object)
                  exit 1
...
ruby-1.0-961225       -e:1: undefined method `puts' for main(Object)
                  exit 1
ruby-1.0-971002       -e:1: NameError: undefined method `puts' for main(Object)
                  exit 1
...
ruby-1.0-971225       -e:1: NameError: undefined method `puts' for main(Object)
                  exit 1
ruby-1.1a0            -e:1: NameError| undefined method `puts' for main(Object)
                  exit 1
ruby-1.1a1            -e:1: NameError| undefined method `puts' for main(Object)
                  exit 1
ruby-1.1a2            -e:1: NameError:undefined method `puts' for main(Object)
                  exit 1
ruby-1.1a3            -e:1: NameError: undefined method `puts' for main(Object)
                  exit 1
...
ruby-1.1a9            -e:1: NameError: undefined method `puts' for main(Object)
                  exit 1
ruby-1.1b0            Hello
...
ruby-2.7.1            Hello
root@50d2785e9b39:/all-ruby#

Kernel#putsが入ったのはruby-1.1b0からだとわかりますね。このように、Rubyの仕様変更やバグを議論しているとき、どのバージョンから変わったのかを調べるのに役に立ちます。

これをより手軽に・議論中に試すために、Ruby開発者のSlackには「all-rubyボット」がいます。

all-ruby-bot

ボットのソースコードは公開されているので、興味がある人は設置してみてください(dockerコマンドが起動できて、かつSlackからのWebhookが受けられる計算機が必要です)。

クレジット:all-rubyはakrさんが作り(akrさんによる発表資料)、dockerイメージはhsbtさんがメンテナンスしています。Slackボットは私が作っています。

rubyfarm

all-rubyはリリース版ごとの実行ファイルでしたが、コミットごとの実行ファイルをためたイメージも作っています。 それがrubyfarmです。

これは、rubyfarm-bisectというgemから使うことを想定しています。 git bisectは、挙動が変わった(エンバグした・バグ修正された)タイミングをコミット単位で特定できるツールですが、rubyを毎回ビルドするのは比較的大変でした。 rubyfarm-bisectを使うと、コミットごとにビルドではなくdocker pullで済むので、待ち時間が大幅に減り、試行錯誤も容易になります。自動git bisectはいろんな理由で失敗することで有名なので、ビルド失敗がない&試行錯誤がやりやすい、というのはだいぶストレスを下げます。

ただ、Docker Hubがストレージを制限する方針を発表したので、非公開ローカルのレジストリに移行する予定です。

commit-link

挙動変更のあったコミットを特定できたら、それについてSlackで議論をするでしょう。 そのとき、コミットハッシュをコピペするだけでは、読む人が自分でgit showにかけたり、GitHubのリンクをたどったりする必要があって、ストレスフルでした。

そこで、Ruby開発者のSlackにはcommit-linkというボットがいます。

commit-botは全発言をウォッチしていて、10桁以上のコミットハッシュっぽいものがあったらGitHubを見てコミットが実在するか確認し、発見したらリンクを貼ってくれます。 地味ですが、非常に便利です。

その他

他にも、標準添付ライブラリのメンテナを教えてくれる"who-is-maintainer"や、脆弱性報告を議論する専用チャンネルを作ってくれる"h1-channel-creator"など、いろいろなボットがRuby開発議論を支えています。

3. Rubyの品質を支える技術

最後に、Rubyの品質を高めるためのCIの活動について。

Rubyには、CIがたくさんあります。 私が把握している限りで、GitHub Actions、Travis、AppVeyor CI(Windows)、弊社笹田による独自CI、そしてrubyci.orgです。 このようになっているのは(歴史的経緯もありますが)テストの目的がいろいろあるためです。 たとえば笹田独自CIは、まれにしか発生しないハイゼンバグ(GCやVM最適化周りでしばしば発生する)を洗い出すために同じコミットを何度も繰り返しテストし続けます。

rubyci

rubyci.orgは、たぶんRubyで一番古くからあるCIです。

もともとは、chkbuildというRuby専用のテスト実行ツールがあり、有志が自分の環境でchkbuildを実行した結果をキュレーションしたのがrubyci.orgでした。 しかし現在では、通常のCIはGitHub ActionsやTravisで十分になったので、主な特徴が変わっています。

  • GitHub Actionsにないような、ややマイナーな環境もカバーしている
  • コミッタは(ほとんどの)テスト環境に直接sshログインでき、デバッグができる
  • テスト中に表示される警告数も監視している

とくに、再現性の低いバグと戦わなければならないRubyコア開発においては、2番目の特徴は重要になっています。

現在では多くのテスト環境はAWS EC2インスタンス上で動作しています。これらの環境をセットアップしたり、コミッタのアカウントを自動定義したりするために、mitamaeを使った自動化もなされています(ruby/ruby-infra-recipe)。

クレジット:chkbuildはakrさんが作り、rubyci.orgはnaruseさんが作りました。その後のメンテナンスは主にhsbtさんと私が行っています。mitamaeによるプロビジョニングはhsbtさんが整理しました。AWSの費用はRuby Associationがカバーしてくれています。他にも、一部の環境はご提供を受けています(rubyci.org末尾のスポンサーリストを参照のこと)。

alerts-emoji

CIの結果を監視するのは苦行なので、みんなSlackなどへの通知を活用していると思います。 しかしRubyには多数のCIがあり、それぞれ好き勝手なフォーマットで通知を投げていたので、そのチャンネルを眺めて理解するだけでも苦行となっていました。

また、テスト実行に数時間かかる環境などもあるため、ちょっと古いコミットに対する通知が遅れてくることが多く、一体どのコミットに対する通知なのかパッと見でわからない、という問題もありました。

そこで、すべての通知を集約し、統一フォーマットで通知するようにしました。 また、どのコミットに対する通知かを視覚的にわかりやすくするため、コミットに適当な幾何学図形を割り当てることにしました。

なお、絵文字については非常に議論がありました。 私は10桁程度の16進数をパッと見で比較するのが苦手 *2 なので、ランダムに選んだ絵文字を振るようにしました(絵文字のほうが視覚的に比較しやすい)。 しかしそうすると、絵文字から意味を読み取ってしまう人から、逆に認知コストが上がったという反対の声があがりました(たとえばスマイルマークや㊙のように意味のある絵文字)。 議論と試行錯誤の末、色違いの幾何学図形の絵文字を使うようにすることで、だいたいの人が満足できるようになりました。

クレジット:まず笹田が独自CI用の通知チャンネルを作りました。そのチャンネルに各種通知が参入して破綻したので、この統一チャンネルを遠藤が作り、その後k0kubunさんがいい感じに整理してくれました。

まとめ

Ruby開発は、多数の技術、および(自分を含めた :-)多数の人々によって支えられています。

ここに上げたものはあくまで遠藤がかかわっているものの一部だけで、上げきれなかったものもいろいろあります(すみません)。みなさんに感謝しながらやっています *3

*1:正確には、去年も私がすべての議題を理解するように努めていたのですが、丸一日消費してしまうことに加え、一人では議題を誤解したり、論点を洗い出せなかったりして、いまいちだったので、笹田と相談して準備会にトライすることになりました。

*2:ハッシュの桁数が、GitHub Actionsは7桁、rubyciは10桁、というように通知ごとにバラバラだったのが特に最悪でした。

*3:特にCIのAWS費用がなかなか大変なので、その費用をカバーしてくれているRuby Associationに寄付をいただけるとありがたいです :-)

Amazon RDS/Auroraをクローンするシステムを作った話

こんにちは、技術部SRグループの菅原です。

最近、Ninja650からNinja1000に乗り換えました。パワーがあるせいで3速発進・4速発進が平気でできてしまい、シフトワークがどんどん下手になっています。精進したいものです。

この記事では、Amazon RDS/Auroraをクローンするシステムを作った話を書きます。

Amazon RDS/Auroraをクローンするシステム

サービス開発を行っていると、調査や検証でプロダクション環境で使われているデータベースが必要になることがあります。開発環境やステージング環境にもデータベースは存在するのですが、プロダクション環境のデータでしか再現しないバグの調査や、プロダクション環境のデータ量でのスキーマ変更の負荷の検証など、開発環境やステージング環境のデータベースではできない作業も多いです。しかし、オペレーションミスや個人情報へのアクセスを考えると、プロダクション環境のデータベースで直接作業をすることは大きなリスクを伴います。

Amazon Auroraのクローン作成機能を使うと、プロダクション環境に影響を及ぼさないクローンを作成できるのですが、個人情報にアクセスできてしまう点は解決できません。また、クローンの作成や削除には強力なIAMの権限が必要なため、管理者がクローンを作成して利用者に渡すような手間が発生していました。

そこで、それらの問題を解決し、開発者が手軽にプロダクション環境のデータベースを触れるように、Amazon RDS/Auroraをクローンするシステムを作成しました。

クローン作成の手順は以下の通りです。

  1. SlackでRubotyに対して @ruboty rds clone db-cluster:my-cluster db.t3.small 4h というコマンドを送る
    • 普段からChatOpsでデプロイが行われていること、作成したクローンDBの情報を共有しやすいことなどからインターフェースとしてSlackを利用しました
  2. RubotyがBarbequeのジョブを起動する
  3. Barbequeのジョブがクローンを作成する
    • Auroraの場合はクローン作成機能、RDSの場合はスナップショットから復元
  4. クローンDBのマスターユーザーのパスワードを変更する
  5. クローンDBのデータをマスキングする
  6. セキュリティグループを変更して、社内ネットワークからクローンDBにアクセスできるようにする

f:id:winebarrel:20200819092303p:plain

f:id:winebarrel:20200819092422p:plain

データのマスキングには同僚の@mozamimyが作ったDumptruckという社内ツールを利用しており、以下のようなJsonnetの設定ファイルに従ってデータをマスキングします。

{
  database: 'db_name',
  except: ['secure_%'], // `secure_`プリフィックスのテーブルはクローンDBにコピーしない
  rules: [
    {
      table: 'users',
      transforms: [
        {
          column: 'tel',
          value: "lpad(id, 12, '0')", // SQLでデータをマスキング
          inline_sql: true,
        },
        {
          column: 'email',
          value: "concat(id, '@example.com')",
          inline_sql: true,
        },
      ],
    },
  ],
}

作成したクローンDBは、利用後に開発者が自分で削除するか、利用期限が切れるとバッチ処理が自動的にクローンDBを削除します。

f:id:winebarrel:20200819092447p:plain

開発者が自分でDBの削除やパラメータの変更を行えるようにするため、クラスタIDやインスタンスIDには rcc- というプレフィックスを付け、IAMの対象リソースをrcc-*とした権限を開発者に付与しています。

まとめ

Amazon RDS/Auroraのクローンが手軽にできるようになったことにより、プロダクション環境のデータの調査や、データベースのパフォーマンスの検証がはかどるようになりました。また、データのマスキングを設定ファイルで管理することにより、どのカラムに秘匿情報が入っているかもわかりやすくなったと思います。プロダクション環境のデータベースを使った作業は管理者やSREに作業が集中しがちなので、このような形でなるべく開発者に権限を委譲していきたいです。

大規模なiOSアプリの画面開発を効率化するために動作確認用ミニアプリを構築する

こんにちは、モバイル基盤部の大川(@aomathwift)です。

iOSアプリの開発途中で画面のレイアウトなど僅かな変更を確認したい場合、最も確実な方法はアプリをビルドして該当の画面まで手動で遷移して確認する方法です。

この方法は特別なセットアップが必要なく単純明快な確認方法ですが、効率の面で問題があります。例えば一番の問題として挙げられるのがビルド時間の長さという問題です。アプリ開発の規模が拡大していくと、ちょっとした変更でもビルド待ちの時間が無視できないものとなっていきます。

本稿では、クックパッドアプリの開発において、機能単体で動作するミニアプリを構築して、プレビューサイクルを改善した取り組みについてお話しします。 f:id:aomathwift:20200731184036p:plain

iOSアプリの動作確認における問題点

クックパッドアプリの開発は、開発規模の拡大によって、ビルド時間の改善が大きな課題になっていました。 そこで、最近はその問題を解決すべく、大きなアプリを複数のモジュールに分け、分割してビルドできるようなマルチモジュール化に取り組んできました。

詳しくは2019年のCookpad Tech Confでの講演、「 〜霞が関〜 クックパッドiOSアプリの破壊と創造、そして未来」をご覧ください。

f:id:aomathwift:20200731184155p:plain これは、マルチモジュール化が活発化した2019年9月から2020年6月まで、同一のマシンで計測した平均ビルド時間を集計したものです。 このマルチモジュール化の取り組みの結果、一回あたりのビルド時間が徐々に改善されてきているのがわかります。

しかしながら、小さな変更を確認したい場合やレイアウトを調整したい場合を考えると、まだまだストレスを感じる長さです。

また、新規のモジュールは、アプリを起動してからそのモジュールの機能や画面への接続が出来ていない状態から開発を始めます。 そのため、開発するモジュールの画面への遷移を先に実装することが必要です。

これをすべて同じ開発者が担当しているなら然程問題ではないかもしれませんが、この起動画面からの導線部分の実装とモジュールの開発を別の開発者が行っていた場合、モジュール開発を担当する人は仮の画面遷移を実装するなどの余計なコストが生じてしまいます。

Sandbox - 機能ごとに動作するミニアプリ

f:id:aomathwift:20200731184247p:plain
TechConf2019より引用

クックパッドのマルチモジュール化では、レシピの表示画面や、検索結果画面など、1機能に関連するいくつかの画面を1つのモジュールとして扱っています。この単位をFeature Moduleと呼んでいます。

Feature Moduleの導入により、アプリ全体をビルドせずとも、部分的にビルドすることができるようになりました。

これらのFeature Moduleはframeworkとしてクックパッドのメインターゲットでimportして利用しますが、先に述べたような動作確認における問題を解決するため、Feature Moduleを単体のアプリとして動作可能にしたのがSandboxアプリです。

以降、この部分的にビルドするSandboxアプリに対して、アプリ全体を結合してビルドするアプリは本体アプリと呼ぶことにします。

Sandboxアプリのメリット

本体アプリをビルドするより速くビルドできる

このSandboxアプリのわかりやすい恩恵は、本体アプリよりも極めて短い時間でビルドが終わる点です。

同じ少量の差分のビルドにかかる時間の計測結果を比較すると、本体アプリのビルドでは平均約20秒かかるのに対しSandboxアプリのビルドでは約5秒で済みます。 単純計算でビルド時間を1/4に抑えられるということになります。

実際のビルドの様子を見ても、Sandboxがものの一瞬で起動できることは一目瞭然です。

f:id:aomathwift:20200731184359g:plain
本体アプリのビルド
f:id:aomathwift:20200731184538g:plain
Sandboxアプリのビルド

Viewのレイアウトの僅かな値を変更して差分を確認したいときなどでも、ストレスなく開発することが可能になりました。

確認したい画面にすぐ辿り着ける

クックパッドのように機能の多いアプリでは、アプリトップから開発している画面にたどり着くまでがやや面倒な場合があります

また、決済終了後の画面など、表示する条件が複雑な画面も存在しています。

そこでSandboxアプリを利用すると、確認したい画面を一番最初に、好みの条件で起動することが出来ます。

繰り返し起動したい画面は一覧になっていて、ここから選択して表示することができます。

f:id:aomathwift:20200731184643g:plain

先に述べたように、まだアプリ起動時の画面からの導線が出来ていない画面のデバッグも容易に可能です。

Sandboxアプリの実現方法

上記のようなメリットを得られるSandboxアプリを実現するために、解決しなければならない問題がありました。

一つは、本体アプリでは多くの外部ライブラリに依存していますが、ビルド速度向上のためにできるだけこの依存ライブラリの利用を避けなければいけないということ、 もう一つは、Feature Module内の画面から、別のFeature Module内の画面に遷移するケースがありますが、このためには検証に必要ないモジュールのビルドも必要になるため、これもまた避けなければならないということです。

これらを考慮した上で、画面遷移やネットワークリクエストといった副作用を本体アプリに近い形で提供する工夫が必要になります。

そこで、クックパッドアプリでは Dependency Injection を利用した副作用を取り出すためのオブジェクトを用意し、これを経由して各画面から副作用を呼び出せるようにしています。 この仕組みをEnvironmentと呼んでいます。

これを利用し、Sandboxアプリではスタブ可能なダミーの実装を注入することで、本体アプリに影響を与えずに副作用を実現できるようにしました。

これによって、例えばネットワークリクエストも実際にリクエストを送るのではなく、予め用意したデータを注入して表示することができるようになっています。

f:id:aomathwift:20200731184817p:plain

Sandbox用のターゲットは各Feature Moduleごとに作成し、ターゲットごとにビルドすることでモジュール単位でのミニアプリの起動を実現しています。

Sandboxアプリを開発者に快適に利用してもらうための工夫

Sandbox用のコードはできるだけ自動生成する

Sandboxアプリを動かすためのコードは実際のプロダクションコードとは別に実装する必要があります。

画面の実装自体はプロダクションコードを参照するとはいえ一つのアプリとして立ち上げるわけですから、データのスタブやEnvironmentの実体の注入などそれなりにコードを記述する必要があります。

この手間が障壁となり、導入当初はSandboxアプリを利用せずアプリ全体をビルドするという開発者が多い状況でした。

そこで、Sandboxアプリのセットアップを簡単に行えるよう、コード生成の仕組みを用意しました。

コード生成にはGenesisというSwiftで実装されたOSSを利用しています。 これは、同じくSwiftで実装されたテンプレートエンジンであるStencilを利用し、簡単な設定とテンプレートを用意すれば、ソースコード生成の仕組みを実現できるツールです。

options:
  - name: sceneName
    question: Sandbox scene name?
    description: new Sandbox scene name to generate. (e.g. RecipeDetails).
    type: string
    required: true
  - name: moduleName
    question: Destination target?
    description: module name to generate new sandbox scene for. (e.g. RecipeDetails)
    type: string
    required: true
files:
  - template: AppDelegate.swift.stencil
    path: "{{ moduleName }}AppDelegate.swift"

例えばこのようなコード生成定義を書いて、オプションとして作成したいSandboxのモジュール名や画面の名前を与えると、以下のテンプレートファイルの中で展開されます。

@testable import {{ moduleName }}
import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    private let environment = StubbableEnvironment()
    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        window = UIWindow(frame: UIScreen.main.bounds)
        
        // Inject Scenes to RootTableViewController
        let rootViewController = {{ sceneName }}ViewBuilder.build(environment: environment)
        window?.rootViewController = rootViewController
        window?.makeKeyAndVisible()
        return true
    }
}

そして、この定義に基づくコード生成スクリプトを実行すると、自分であれこれコードを書かなくとも、Sandboxを生成したいモジュール・画面の情報が反映されビルドできるようになります。

$ ./scripts/generate-sandbox
[14:00:14]: Welcome to Sandbox Scene generator
[14:00:14]: What target do you want to make sandbox for
1. MyFeature
2. MyAwesomeFeature
?  1
[14:00:22]: Enter new Sandbox Scene name to generate. Upper camel case is recommended. (like RecipeDetails)
MyFeatureDetail
[14:00:40]: Generating MyFeature/MyFeatureDetail
[14:00:40]: $ /path/to/ios-cookpad/scripts/mint run Genesis genesis generate /path/to/ios-cookpad/templates/SandboxScene.yml --destination /path/to/ios-cookpad --option-path /var/folders/p7/g0t6l0zx00sbdxxrnm7wq8d80000gp/T/options20200714-98239-1lwms1g.yml
[14:00:40]: ▸ Generated files:
[14:00:40]: ▸   Sandbox/MyFeature/MyFeatureDetailSandboxScene.swift
[14:00:40]: ▸   Sandbox/MyFeature/AppDelegate.swift

できるだけ実際のアプリに近い挙動になるようにする

Sandboxアプリでは、マルチモジュール化での依存関係の問題により、他のモジュールにある画面に遷移することはできません。

基本的に一つの画面をプレビューすることを想定しているSandboxアプリでこの画面遷移を厳密にプレビューできるようにする必要はありませんが、スムーズな動作確認ができるように、簡易的なViewをモックとして表示できるようにしました。

これにより、本体アプリとほぼ同じ挙動を想定した動作確認をすることができるようになっています。

f:id:aomathwift:20200731190028g:plain

この機能は、先に述べたコード生成により自動で実装されるほか、自分で実装する場合も一つのイニシャライザメソッドを呼べばセットアップできるように整備されています。

今後の展望

先日のWWDC2020で、SwiftUIの新しいPreview機能についての発表がありました。

Structure your app for SwiftUI previews - WWDC 2020 - Videos - Apple Developer

昨年発表されたXcode Previewsは、SwiftUIで構築した画面を、Xcode上でリアルタイムに確認できるような仕組みです。

今回のアップデートでは、プレビュー中にサンプルデータを流し込んで利用したり、Xcode Previewsによって起動した画面を実機上でインタラクティブに操作しながら確認したりする機能が加わり、今まさにSandboxアプリで実現していることがXcode Previewsで実現できるようになります。

加えて、Dynamic TypeやダークモードのPreviewなどの機能とも併せることで、より効率的に開発を行うことが可能になるでしょう。

現在、クックパッドアプリの画面はほぼUIKitで実装されたものですが、新しい機能の実装にSwiftUIを利用できないか試しているところです。 来る新しいXcode Previewsが利用可能になる日に向けて、SwiftUIによるView実装への移行と共に、プレビュー機能全体をXcode Previewsを利用したものに移行していく必要があると考えています。

既存のUIKitによる実装に関してはXcode PreviewsとSandboxアプリの機能を併せて利用したものを試行しながら、プレビュー確認環境全体の改善を進めていく予定です。

Xcode PreviewsとUIKitの併用については、メルカリさんが下記の記事で自社の事例を紹介しています。

まとめ

この記事では、クックパッドアプリにおけるSandboxアプリを利用した動作確認の効率化について紹介しました。

開発効率を上げるために、スピーディーで快適な動作確認環境は必要不可欠です。

クックパッドでは、より便利なプレビュー機能への改善を一緒に行っていただけるエンジニアを募集しています。