Apple のアップデートによる不具合 ─ ユーザーより先に気づくための仕組み

Article by: (読了時間:9分)

 

本記事はフロントエンド開発者兼デザイナーであり、Morning Maker Show の共同ホストも務める Dan Mindru によるゲスト投稿です。Dan は現在、PageUI、Clobbr、CronTool をはじめとする複数のアプリケーションを開発しています。

 

リリースのたびに、綱渡りをしているようなものです。アプリは軽量で安定していなければならず、高いパフォーマンスも維持しなければなりません。その一方で、いつ予告なく変更されるか分からないAPIにも依存しています。160万ダウンロードを超えた今、私たちには品質を維持する責任があります。小さなチームでありながら、App Storeで4.7という評価を保ち続けられていることを誇りに思っています。では、それをどう実現しているのでしょうか。

答えはシンプルです。Sentryを使って築いたセーフティネットです。

本記事では、ユーザーが気付く前にクラッシュを修正する方法から、ビルドの問題を早期に検出する方法、さらにスタックトレースだけでは分からないユーザーの状況を把握する方法まで、私たちがSentryをどのように活用しているのかをご紹介します。

 

前提を整理する

今回取り上げるアプリは Usage という、iPhone・iPad・Mac で動作するシステムアクティビティモニターです。デバイスがCPU・メモリ・ネットワーク・ディスク・バッテリー・グラフィックスといったリソースをどのように使っているかを追跡します。これに加えて豊富な履歴データが得られることから、自分のデバイスを細かく把握したいパワーユーザーに人気があります。

これには、いくつもの難しさがあります。

Usageの役割はデバイスの動作を監視しながら、自らがその負荷にならないことです。この一見単純な要件が、少し変わったエンジニアリング上の制約を生みます。機能を追加するたび、依存関係を増やすたび、タイマーを1つスケジュールするたびに、その処理はユーザーに提供したい価値そのものとリソースを奪い合うことになります。自分自身のチャートに表示されてしまうシステムモニターなど、誰も望みません。

しかも、それはまだ序章にすぎません。足場そのものも安定していないのです。必要なデータの一部は、Appleが公開された代替手段を提供していないため、ドキュメント化されていないAPIに頼らざるを得ません。つまり、OSのアップデートによってデータ構造が予告なく変わり、どのデバイスでも、どのタイミングでも動作しなくなる可能性があります。非推奨になるという通知が届くわけでもありません。そのため、常に注意を払い続ける必要があります。

さらに、Usageは単一のプロセスではありません。メインアプリ、メニューバーアプリ、バックグラウンドサービス、そしてウィジェットが協調して動作する構成になっています。それぞれが独自のライフサイクルを持ち、障害もそれぞれ異なる形で発生します。そこで、各コンポーネントを個別のSentryプロジェクトとして構成し、それらを1つのワークスペースにまとめています。こうすることで、クラッシュやエラーは発生したプロセスに正確にひも付けられたまま、システム全体の健全性を一か所で把握できます。

こうした環境で、アプリ全体を軽快かつ高いパフォーマンスのまま維持するのは簡単ではありません。そのため私たちは、Sentryが提供するさまざまなツールを活用しています。

  • Crash Monitoring — あらゆる機能の土台となる基盤です。
  • Error Monitoring — OSアップデートによって依存しているデータ構造が変わることで起きるような、静かな失敗を検出します。
  • Size Analysis — ビルドごとの差分を追跡し、問題のあるリリースを見逃さないようにします。
  • Metrics — 最適化の取り組みが実際に成果を上げているかを測定し、検証します。
  • User Feedback — ユーザーがアプリ内から直接フィードバックを送れる窓口です。

 

それぞれをどのように使っているか、順に見ていきましょう。

 

Crash Monitoring

クラッシュモニタリングは基盤です。どんなアプリにも必要であり、本記事の後半で紹介する Sentry の価値の多くは、アプリの起動の早い段階で Sentry SDK を初期化しておくことの上に成り立っています。

各ターゲットにおける Sentry の初期化は、ごく標準的な形になっています。

この時点以降、ハードシグナルであれ、キャッチされなかった Swift のエラーであれ、NSException であれ、アプリ内で発生したあらゆるクラッシュが捉えられます。それぞれには、シンボリケート済みのスタックトレース、そこに至るまでの breadcrumbs、そしてそれが発生した正確なリリースの情報が付随します。

これによって実際に得られるのは、時間の経過に伴う安定性の明確な全体像です。本記事で紹介する他のすべての土台となるものです。

 

Error Monitoring

クラッシュの話が済んだところで、より捉えにくいエラーについて話しましょう。クラッシュは少なくとも自ら存在を知らせてくれます。私たちがより心配しているのは、そうではない失敗です。アプリは動き続けているのに、どこかで値の読み取りがひそかに間違っているというケースです。これはたいてい誤った計測値につながり、誤った計測値はユーザーの不満につながります。

Usage にとって、こうしたエラーを早期に検出し、素早く対応することは極めて重要です。先ほど触れたように、このアプリの多くの部分は、ドキュメント化されていないソースやAPIを通じてシステムデータを読み取っています。特定のデバイス・OSバージョン・ハードウェア世代ごとに、ある読み取り値がどのような形になるかを事前に必ず把握できるわけではありません。

その不確実性と付き合っていけるのは、網羅的なエラーロギングのおかげです。アプリが認識できない値を読み取るたびに、私たちはそれを Sentry で non-fatal error として記録します。これには、欠落しているフィールド、まだ対応していない型の構造、想定範囲を超えた数値などが含まれ、Sentry がプライバシー面でも役立つ場面です。

イベントには添付することを選んだコンテキストしか含まれません。送信するのは、予期しない読み取り値の形、たとえばグラフィックスカードのレスポンスの型やフィールドといった情報だけです。値そのものを送ることは決してありませんし、ユーザーに関する情報を送ることも決してありません。それでも何かがすり抜けてしまった場合にはbeforeSend が、各イベントがデバイスを離れる前にそれを洗い流す最後のチェックポイントになります。

私たちが最も頻繁に遭遇する具体的な例が、グラフィックスカードの読み取りです。Apple が GPU 統計として返すデータ構造は、macOS のバージョン間、そしてハードウェア世代間で微妙に変化してきました。少しだけ形の異なる新しい Mac が登場したとき、本来なら私たちは手探り状態に陥るはずです。ところが実際には、その予期しない構造が記録された Sentry イベントを当日のうちに受け取れるのです。

この早期警告のおかげで、新しい形への対応はたいてい次のリリースで出荷されます。ほとんどのユーザーがそのハードウェアを手にする頃には、修正はすでにリリース済みなのです

 

「Error」だけでは足りないとき

パースやデコードのエラーで何が問題だったのかを突き止めるには、スタックトレースだけではまず足りません。私たちはほぼ必ず、エラーそのものと並べて失敗した対象を見たいと考えます。

Swift の Error プロトコルは、デフォルトでは任意のペイロードを持たないため、追加します。小さなラッパー構造体と、たった1つの拡張メソッドがあれば十分です。

with(data:) メソッドは、呼び出し側のためのシンタックスシュガーです。エラーの型を書き換えることなく、任意のエラーにコンテキストを付け足すことができ、付与するものが何もないときには何もしないよう穏当に振る舞います。典型的な使い方は次のとおりです。

throw GraphicsHelperFetcherError.displayMissingProperty(“sppci_model”).with(data: rawResponse)

そのうえで、キャプチャ地点でデータを取り出し、Sentry の scope に extra として乗せます。

 

Sentry 上では、このデータはイベントの中でラベル付きのフィールドとして、スタックトレースのすぐ隣に表示されます。私たちが扱うような、認識できないシステムの読み取り値や予期しないフィールドの形といったエラーでは、そのたった1つのフィールドが決め手になることがよくあります。

このパターンが実際に動いている様子をお見せしましょう。以下のスクリーンショットは、macOS アプリから得られた実際の Sentry イベントです。グラフィックスの読み取りで発生したパースエラーで、display オブジェクトの1つが、私たちが想定していたプロパティを持たないまま返ってきたケースです。スタックトレースはどこで失敗したかを教えてくれますが、data フィールドはなぜ失敗したのかを教えてくれます。生の構造がそこにそのまま示されていて、これまで見たことのない GPU と macOS の組み合わせで、そのフィールドが欠けているのです。これは、グラフィックスデータ内の display オブジェクトの扱いを更新すべきだというシグナルであり、たいていは次のリリースで修正を届けられます。

 

Size Analysis

クラッシュモニタリングとエラーモニタリングは、アプリが動いている間を見張ります。しかし、誰かが起動する前の段階で壊れていたビルドを検出することはできません。私たちはそれを痛い経験から学びました。

少し前のことですが、あるリファクタリングで、アプリのローカライズの1つが誤って削除されてしまいました。どういうわけかその差分は通常のレビュープロセスを通っておらず、壊れたリファクタリングを含むビルドがアップロードされ、リリース対象として選ばれてしまいました。本番環境に反映されると、その影響は即座に、しかもはっきりと現れました。原因が分かる前に、影響を受けたロケールのアクティブユーザーが崖から落ちるように減少したのです。

めったにないこととはいえ、こうしたことはどんなチームでも起こり得ます。私たちには、差分レビューにとどまらない第二の防衛線が必要でした。

Sentry の Size Analysis が、まさにその役割を果たしてくれました。これはリリースビルドごとに実行され、Sentry の他の機能がすでに使っているのと同じ version+build のリリースタグにサイズレポートを添付し、新しいビルドを1つ前のビルドと比較します。ローカライズファイルやアセット、あるいはバンドルリソース全体が消えてしまった場合、その差分によって見逃すことは不可能になります。逆の場合も同様です。なぜか肥大化したリリースは、その原因が内訳のすぐそこに表示されます。

Size Analysis は(おそらく)アプリのサイズを抑えることだけを目的としたものですが(私たちもその目的で使っています!)、私たちが心を動かされたのは、これをリグレッションのゲートとして使うという点でした。人手や自動化されたプロセスがレビューでこうした問題を捉える保証はなく、Size Analysis は追加のサニティチェックとしてうまく機能します。

さらにサイズレポートに加えて、リリース間の差分を監視し、ビルドが1つ前と比べて目に見えて重くなったり軽くなったりするたびに通知してくれる Sentry のモニターも設定しました。「軽くなる」方向は、先ほどのローカライズの一件を分かりやすく浮かび上がらせます。「重くなる」方向は、忍び寄る肥大化を捉えます。いずれにしても、これで私たちはこうした問題が深刻になる前に気づけると安心していられます。

 

Metrics

ここまでの内容はすべて、うまくいかないことを検出する話でした。最後のピースは、うまくいっていることを証明することです。

Metrics は比較的新しい Sentry の機能で、私たちはちょうど導入している最中です。Usage のようなアプリにとって、パフォーマンスとリソース使用量は、あれば嬉しいという程度のものではなく、それ自体が製品そのものです。そのため、コードベースのどこからでも独自の Counter や Gauge を送出でき、それらをクラッシュやエラーの隣に並べて見られることには、大きな意味があります。

私たちはサンプリングのレイテンシ、バックグラウンド処理の所要時間、同期間隔といった、もともと気にかけている項目をインストルメントしています。そうすることで、最適化の取り組みが実際にそれらの数値を動かしていることを Metrics で確認できます。

今のところ、手応えは上々です。もう少し使い込んだところで、あらためて詳しくご紹介したいと思います。

 

User Feedback

ユーザーの行動のすべてが、クラッシュやエラーとして現れるわけではありません。アプリは技術的には健全なのに、誰かが何かおかしいチャートをじっと見つめている、ということもあります。そうした場合には、直接教えてもらえたほうがありがたいのです。

私たちは Sentry の User Feedback をまず別の製品である WinWinKit で試しました。価値を実感し、すぐに Usage にも追加しなければならないと確信しました。

これは、より速い反復と、より強いユーザーエンゲージメントを生む大きな原動力の1つになりました。私たちが自信を持っておすすめできるものです。数行のコードだけで、ユーザーがアプリを離れることなくフィードバックを送れる、小さなアプリ内フォームが手に入ります。寄せられたフィードバックはクラッシュやエラーと同じワークスペースに、同じリリースのタグ付きで届きます。

導入を迷っているなら、私たちは自信を持っておすすめします。1件1件の報告が、次のビルドを、それがなかった場合よりも少しだけ良いものにしてくれます。

 

まとめ

私たちは今も綱渡りを続けていますが、最近ではその眺めを心から楽しめています。Apple はまた新しいアップデートを出すでしょうし、どこかの構造体は新しい形になり、これまで触れたことのない Mac が見たこともないバイト列を送ってくるでしょう。望むところです!

もし自分ではコントロールできないプラットフォームに依存する何かを作っているなら(そして正直なところ、そうでない人などいるでしょうか?)、アドバイスはシンプルです。できるだけ多くのセーフティネットを用意しておくこと。ユーザーはきっと、そのことに感謝してくれるはずです。

 

 


 

 

Original Page: Any Apple update can break our app. Here’s how we find out first.

 

 

 




IchizokuはSentryと提携し、日本でSentry製品の導入支援、テクニカルサポート、ベストプラクティスの共有を行なっています。Ichizokuが提供するSentryの日本語サイトについてはこちらをご覧ください。またご導入についての相談は「お問い合わせ」からお気軽にお問い合わせください。

 

シェアする

Recent Posts

;