Node.jsのLoaderパフォーマンスを向上させる

Node.jsは2種類のモジュールをサポートしています。 EcmaScriptモジュールとCommonJSモジュールです。 ESモジュールはJavaScriptにおけるモジュールの公式な標準であり、すべてのモダンブラウザでサポートされています。 CommonJSモジュールは、Node.jsがデフォルトで使用するモジュールです。これらはブラウザによってサポートされておらず、公式の標準でもありません。しかし、現在でも広く使われています。 Node.jsはどのようにエントリーポイントをロードするのか? どのローダーを使うかを区別するために、Node.jsはいくつかの要因に依存することを理解しておきましょう。 最も重要なのはファイルの拡張子です。 ファイル拡張子が .mjs の場合、Node.js は ES モジュールローダを使用します。 ファイル拡張子が.cjsの場合、Node.jsはCommonJSモジュール・ローダーを使用します。 ファイル拡張子が .js の場合、 package.json ファイルに “type”: “commonjs” があれば(または単に “type “フィールドがない場合)、Node.js は CommonJS モジュールローダを使用します。 package.jsonファイルに “type”: “module”があれば、Node.jsはESモジュールローダを使用します。 この決定はlib/internal/modules/run_main.jsファイルで行われます。 以下にコードの簡略版を記載します。 readPackageScope は、package.json ファイルを見つけるまで、ディレクトリツリーを上方向に走査します。 この投稿で最適化する前は、readPackageScopeはpackage.jsonファイルを見つけるまで内部バージョンのfs.readFileSyncを呼び出します。 この同期呼び出しはファイルシステム操作を行い、Node.js C++レイヤーと通信します。 この操作には、データのシリアライズ/デシリアライズのコストがかかるため、返す値/タイプによってパフォーマンスのボトルネックがあります。 そのため、readPackageScope内でreadPackage(別名fs.readFileSync)を呼び出すことはできるだけ避けたいです。 Node.jsはどのようにpackage.jsonを解析するの? デフォルトでは、readPackageは内部バージョンfs.readFileSyncを呼び出してpackage.jsonファイルを読み込みます。 この同期呼び出しは、Node.js C++レイヤから文字列を返し、後でV8のJSON.parse()メソッドを使用して解析されます。 このJSONの妥当性に応じて、Node.jsは残りのローダーの実行に必要なオブジェクトをチェックした後作成します。 これらのフィールドは、pkg.name、pkg.main、pkg.exports、pkg.imports、pkg.typeです。JSONの構文に誤りがある場合、Node.jsはエラーを投げて処理を終了します。 この関数の出力は、同じパスに対して readPackageScope を再度呼び出さないように、後で内部 Map にキャッシュされます。このキャッシュは、プロセスの寿命が尽きるまで保存されます。 package.jsonフィールドとリーダーの使用法 最適化の前に、Node.jsがこれらのフィールドをどのように使用しているかを見てみましょう。 Node.jsコードベースでpackage.jsonフィールドをパースして再利用する一般的なユースケースは以下の通りです。 pkg.exportsとpkg.importsは、入力に応じて異なるモジュールを解決するために使われます […]

Reactにおけるメモ化の不具合を修正する

Reactのメモ化を使用することで、ウェブアプリケーションを小さなコンポーネントに分割し、再利用しやすくすることができます。 コンポーネントの更新が必要な場合、Reactは再レンダリングを契機に、動的なデータやアニメーションなどを表示する方法です。 しかし、再レンダリングが必要ないコンポーネントを再レンダリングするとなると、アプリケーションのパフォーマンスに悪い影響を与えます。 以下の状況を想像してみてください。 親コンポーネントが、コールバック関数を子コンポーネントにpropsを通じて渡す場面です。 そして、子コンポーネントはメモ化されているにも関わらず、親コンポーネントが再レンダリングされるたびに子コンポーネントを再レンダリングしてしまう。 この問題を調査し、その修正方法を学んでいきましょう。 問題点 親コンポーネントは、コールバック関数を子コンポーネントにpropsを介して渡します。 子コンポーネントはメモ化されていますが、Reactは親コンポーネントが再レンダリングされるたびにそれを再レンダリングします。 何かが原因で、メモ化の特性を失わせています。 以下は親コンポーネントと子コンポーネントのコードスニペットです。 このコードを試したい場合は、こちらのCodeSandboxリンクをご覧ください。 _Numberコンポーネントには、再レンダリングごとに発生する重い操作が含まれています。 これが問題であることを特定するために、私たちはReact SDKに付属するSentryのwithProfilerメソッドを使用して、関心のあるすべてのコンポーネントをラップします。 これにより、その特定のコンポーネントのui.react.mountおよびui.react.updateイベントがキャプチャされます。アプリをリロードし、「増加」ボタンを数回クリックすると、Sentryパフォーマンスダッシュボードで以下のように表示されます。 トランザクションのうち50%がUI操作に費やされたことがわかります。 こちらについて詳細に調べる必要がありそうです。 しかし、なぜでしょうか? 私たちは_NumberコンポーネントをReactのmemo()でラップしました。 なぜそれが再レンダリングされ続けるのでしょうか? Reactと再レンダリングに関して知っていることを考えると、Reactはコンポーネントを再レンダリングするとき、それらの状態またはプロパティのいずれかが変更されたときです。 _Numberコンポーネントを見てみると、状態変数が定義されていないことがわかりますが、propsからsetMessageコールバックを受け入れています。 問題は「増加」ボタンをクリックしたときに発生します。 _Numberコンポーネントには全く関係ありませんが、それによりClosureRerenderコンポーネントが再レンダリングされ、それが_Numberコンポーネントに渡されるonClickメソッドを再作成します。 _Numberコンポーネントはメモ化されていますが、親が再レンダリングされるたびにsetMessageプロパティに異なる値を受け取り、これによりメモ化をバイパスし、再レンダリングします。 onClickメソッドは変更されないにもかかわらず、その参照が変わります。 自分で確認したい場合は、このページでコンソールを開いて、次のように一行ずつ入力してみてください。 最後のx===yコマンドはfalseを出力します。 それにもかかわらず、両方のオブジェクトは同じ値(’Lazar’)を持つ同じ名前のプロパティを持っています。 JavaScriptは、非プリミティブ型を扱う際に変数の値として参照を保持し、手動で両方のオブジェクトを作成したため、xとyは異なる参照を持ち、したがってx===yはfalseになります。 Reactでも同様です。ClosureRerenderが再レンダリングされるとき、onClickメソッドが再作成されるため、実質的に新しい参照が渡されます。 古いsetMessageプロパティは新しいものの値と一致しないため、Reactは_Numberコンポーネントを再レンダリングします。 では、これを修正するにはどうすればよいのでしょうか? 解決策 解決策としては、`useCallback`フックを使用する必要があります。 `useCallback`フックは、コールバックに対する`useMemo`や`memo()`がコンポーネントに対するものであるようなものです。 依存関係の配列に変更がない限り、コールバックの再作成を防ぎます。 新しい`onClick`メソッドは次のように記述します。 このメソッドを`useCallback`フックでラップし、`props.setMessage`を依存関係の配列に配置します。 それが変更されない限り、再レンダリングの間に`onClick`は同じ参照値を保持し続けます。 もはや「増加」ボタンをクリックしても_Numberコンポーネントの再レンダリングがトリガーされません。そして、それをSentryで検証できます。 ずっと良くなりました。 不必要なui.react.updateイベントもなく、長時間実行されるUIブロッキングタスクもありません。 結論 `useMemo`フックや`memo()`メソッドは、常にコンポーネントが不必要に再レンダリングされるのを防ぎません。 今回の記事のようにメモ化を壊してしまう場面があり、それによってパフォーマンスが損なわれることがあります。 これは、`useCallback`フックを使用しないコールバックメソッドを渡す場合だけでなく、`setMessage={(number) => props.setMessage(number)}`のようにコールバックをインラインで定義する場合にも発生する可能性があります。 慣習的にコードを書いていると思うので「それが何を引き起こすか」にまでは、あまり注意を払っていないかもしれません。 今回のように、これらの状況をアプリ全体で修正したことを検証し、Reactアプリのパフォーマンスを監視し始めるために、アプリにSentryを導入してみてください。 開始は無料で、インストールも簡単です。 SentryがReactアプリに対して何ができるかをもっと詳しく知りたい場合は、Sentry […]

Reactの「フェッチウォーターフォール」について

早速ですが、以下のような問題に遭遇したことはありますか? または、これはどうですか? これはおそらく見たことがあるでしょう。 ちなみに、上記で挙げたものはすべて同じです。 1つ目の画像はSentryのイベント詳細ページ、2つ目はChromeのネットワークタブ、そして3つ目は、それを引き起こす原因となるコードスニペットです。 もし上の質問に「はい」と答えた方は、今回の記事が大いに役立つと思います。 そうでない場合でも、将来の自分のためにぜひ読んでみてください。 これは「フェッチウォーターフォール」と呼ばれ、Reactにおける一般的なデータフェッチ問題です。 これはデータをフェッチし、「ローディング」状態を表示してから子コンポーネントをレンダリングする(そして同様のことをするなど)コンポーネントの階層を作成するときに発生します。 ざっくりいうと、Webページを表示するために必要なデータをサーバから取得する際に起こる問題であるということです。 各コンポーネントのデータが親のものに依存する場合には問題ありませんが、常にそうとは限りません。 各フェッチが少なくとも1秒かかる場合、ページのロードに3秒以上かかることになります。現代のデジタルネイティブにとって、3秒のロード時間はとても長く感じますよね。 しかし、並行してフェッチすれば1秒で得られるデータを、3秒以上も待たせる理由は一体何なのでしょうか? この問題のCodeSandboxがこちらですので、ご自身の目でご確認ください。 良いパフォーマンスを維持するため、3つの方法をご紹介します。 そして、各ソリューションを使用したい場面を探っていきましょう。 対処法① Suspenseを使用する Suspenseは、フェッチウォーターフォールを避けるための有効な手段の1つです。これは、コンポーネントツリー全体のフェッチを並行してトリガーするため、データがずっと速くフェッチされます。しかしこの方法は本番環境に適していません。 Reactのドキュメントでは、Suspense対応データソースのみがSuspenseコンポーネントを起動し、それらはRelayやNext.jsといったフレームワーク、またはlazyでの遅延ロードコンポーネント、あるいはuseでの約束値の読み取りであると説明しています。 遅延ロードとuseフックも必ずしも良い対応策だとはいえません。 遅延ロードコンポーネントは、フェッチウォーターフォールを確実に排除することはありません。それは同じように振る舞い、Suspenseからの「ローディング」フォールバックが唯一の利点です。 useフックはReactのcanaryバージョンでのみ利用可能なため、まだ安定版としてリリースされていません。 ですので、Next.jsやRelayを使用していない限り、Suspenseを対処法として使うことはお勧めしません。 しかし、本当に使いたい場合は、このCodeSandboxの例を確認してください。本番環境に適したものが出れば、良いパフォーマンスを維持し、フェッチウォーターフォールを避ける素晴らしい方法になり得ます。 対処法② サーバー上でデータをフェッチする Next.jsを使用する場合は、サーバーコンポーネントを使用します。 そうすれば、クライアントはデータとともにHTMLを受信し、データ要求をする必要がなくなります。 リクエストがない……つまりフェッチウォーターフォールがない!ということになります。 では、このようにサーバーサイドでデータをフェッチするのでしょうか? サーバーは、クライアントに処理結果を返す前に、一連のデータをすべてフェッチするのを待つことになります。 これは並列実行の問題であって、レンダリングの問題ではありません。 サーバーでデータをフェッチするだけでは解決することができません。 上の画像を見ると、確かに滝のように見えますね。 古典的なSSR(または “loading “コンポーネントのないServer Components)では、TTFBはデータをフェッチする時間よりも遅くなっています。 そして、TTFBの値が大きくなったので、他のすべてのウェブバイタルも大きくなっています。 Next.jsでもローディングコンポーネントを提供することでTTFBを修正できますが、それでもウォーターフォールは落ち続けます。 もうひとつ考慮すべきことは、SSR/Server Componentsを使用する場合でも、ブラウザ上でレンダリングするということです。 クライアントは、サーバーから受け取ったDOMを表示し、ユーザーのインタラクションに反応できるようにしなければなりません。 つまり、サーバーで生成した静的なWebページに、クライアント側のスクリプトが操作可能にする必要があります。 2台のコンピュータ(ブラウザとサーバー)がレンダリングタスクを実行します。 サーバーは各リクエストに対してより多くのタスクを実行します。 ブラウザは(SSRのとき)ユーザーにはまだ空白のページを表示しており、レスポンスが返ってきたときにもまだレンダリングを行っています。 サーバーでのフェッチは悪いアイデアではありません。 しかしその方法を使う場合は「トレードオフ」であることを踏まえて検討する必要があります。もし問題がないのであれば、このように並列にデータをフェッチするようにしてください。 3つのfetchメソッドは、他のAPIリクエストやデータベース呼び出しなど、どんなものでも構いませんが、Promiseを返さなければなりません。Promise.allはすべての入力プロミスを並行して起動し、入力プロミスの最後が解決されたときに解決される新しいプロミスを返します。 フェッチ・ウォーターフォールを避けるためにデータ・フェッチを持ち上げる フェッチ・ウォーターフォールの問題を解決するもう1つの方法は、データ・フェッチをコンポーネント階層の上位レベルに引き上げることです。 コンポーネント・レベルでデータをフェッチする代わりに、コンポーネント・ツリーの最上位レベル(データをフェッチし始める最初のコンポーネント)でデータをフェッチし、それをコンポーネント・ツリーに渡すことができます。 この場合でも、並行してデータをフェッチすることは重要なので、必ずそのようにしてください。 […]

Flutter開発で知っておくべきヒントとツールについて

現代のアプリケーションは、複雑なサービスの集合体です。 故障する可能性や、期待どおりに動作しない可能性を持つサービスが、相互に連携し合って構成されています。 Flutterとその開発言語であるDartは、イベント駆動型、並行処理、そしてパフォーマンスの高いアプリケーション開発のために設計されています。 それらを使用する開発者にとって、適切なデバッグツールを使いことなすことは必要不可欠です。 デフォルトのFlutterツールには、他言語よりも優れたデバッグとプロファイリングのツールスイートが含まれており、多くの開発者をサポートしています。この堅牢な基盤によって、人気のあるIDE向けのプラグインや外部モニタリングプロバイダが、Flutterアプリケーションに対する深い洞察を得ることができます。 この記事では、いくつかのオプションを紹介し、それらがFlutterのデバッグとプロファイリングにどのように役立つかを説明します。 Flutterデバッグの例 Flutterデバッグに必要なトップツールとヒントを網羅するために、例を交えながらお伝えしていきます。 この情報収集の目的は、複数のタスク、課題、およびToDoのソースを統合し、それらをソートおよびフィルタリング可能なリストで一緒に表示するアプリケーションを作成することです。 では、TrelloとGitHubの課題からアイテムを取得し、リストで表示するアプリケーションを例として挙げて進めていきます。 このアプリケーションは2つのAPIからデータを取得し、JSONレスポンスを解析し、データ構造を統合し、それらをスクロール可能なリストで表示する必要があります。 このアプリケーションの進捗状況はGitHubで確認できます。 ログ記録のオプション 一般的な開発者であれば「console.log()」といったログを出力するコードを書く所から解析を始めるでしょう。 これが最適な方法ではないことはわかっていますが、簡易なデバッグであればこれで充分です。 FlutterとDartには、これを行うための3つの方法があります。 print()、stdoutそしてstderrを使用してメッセージをコンソールに記録できます。 詳細な情報を受け取るためにdart:developerのlog()関数を使用できます。 log()関数を使用すると、タイムスタンプ、エラーレベル、スタックトレースなどの複数のオプションパラメータが追加された状態で出力されます。 Flutterでフラグをデバッグする方法は FlutterはUIに焦点を当てたアプローチを取り、アプリケーションインターフェースはしばしば数十のカスケードウィジェットから構成されるため、アプリケーションのUIに対する視覚的なフィードバックはとても重要です。 Flutterにはクラスに追加できるいくつかのデバッグフラグがあります。ただし、FlutterのDevToolsにはこれらのフラグを複製(および改善)する機能があるため、それらを使用する目的がない限り、出来るだけ使用しない方が良いでしょう。 Flutterのドキュメントには、これらのフラグの概要が記載されており、レンダリングパッケージのプロパティリストには完全なリファレンスがあります。 DevToolsパッケージ Flutterの最も強力な機能の1つは、DevToolsパッケージです。これはデバッグやパフォーマンス分析機能を提供するツール集です。Android Studio、IntelliJ、またはVS Codeのプラグインと組み合わせて使用するのが最適ですが、ブラウザ内またはコマンドラインからも使用できます。 組み込みツールとして、多彩な機能を提供しており、以下が含まれます。 UIのレイアウトと状態を検査 UIパフォーマンスの問題を診断 CPUプロファイリング(プロファイルモードのみ) ネットワークプロファイリング(プロファイルモードのみ) ソースレベルのデバッグ トレースをサポートするタイムラインビュー メモリの問題をデバッグ 実行中のアプリに関する一般的なログと診断情報を表示 コードとアプリのサイズを分析 アプリケーションをデバッグモードまたはプロファイルモードで実行すると、DevToolsは自動的に開始し、エディター内で表示されるか、コマンドラインから実行した場合はブラウザで表示されます。DevToolsはデフォルトで多くの情報を表示しますが、エディタープラグインを使用してブレークポイントを設定することで、その機能を最大限に活用することができます。 DevToolsを使用して、ブラウザからUSBケーブルで接続した物理デバイスまで、どこからでも実行中のFlutterアプリケーションをデバッグすることができます。 Flutterアプリケーションをデバッグするためのエディタープラグインには Android Studio、IntelliJ、およびVS Codeのプラグインは、DevToolsをIDEにバンドルしており、アプリケーションが実行中でも上記でリストに挙げた機能をリアルタイムで活用できます。 ブレークポイントを設定すると、DevToolsはアプリケーションをブレークポイントで一時停止し、アプリケーションが宣言する変数を含む現在のアプリケーションの状態を表示します。その後、DevToolsのUIを使用してアプリケーションの状態を検査し、実行を続行できます。 Sentry Flutterプラグイン Flutterはアプリケーションを構築する際に包括的なデバッグツールを提供しています。 本番環境でのエラー追跡とモニタリングが重要であり、Sentryはそのサポートを提供しています。 これには手動で追加された例外のキャッチングや、本番環境のパフォーマンス監視が含まれ、Sentryはこれらに加え、メタデータやスクリーンショットなどの情報を提供します。 実際の問題をデバッグする これらの様々なツールは、アプリケーションのデバッグをどのようにサポートするのでしょうか? 以下は、私が遭遇したエラーの具体例と、それらを解決するのに役立ったツールの一部をご紹介します。 もちろん、他にも多くのエラーに悩まされましたがそれらのほとんどは人為的なものでした 😅 開発にはVisual […]

JavaScriptプロジェクトのノイズを減らす

あなたがJavaScriptのエラー監視にSentryを使用している場合、よくある課題にお気づきかもしれません。それはSentryが挙げるすべての問題に目を通し、無視してもよい帝優先度のエラーと、修正が必要な高優先度のエラーを分類する作業のことです。 JavaScriptのブラウザ・プロジェクトでエラーを捕捉するのはとても手間がかかります。それは、単一の環境だけではないからです。一般的に数多くの主要ブラウザ、JavaScriptエンジン、オペレーティングシステム、そしてブラウザ拡張のエコシステムが存在します。そのため、関連するエラーを効果的に捕捉するのは難しいのです。 Sentryは、このようなノイズをできるだけ減らすために、多くの機能を迅速に実行します。しかし、いくつかのステップを追加するだけで、簡単にエラーに優先順位をつけることができるので、重要なことに集中することができます。そのため、開発者はすぐに修正する必要があるものだけに集中することができます。 ソースマップとタグリリースのアップロード ソースマップは、コードを最小化しないことで、本番環境のスタックトレースデバッグを容易にします。また、ソースマップはSentryがエラーを個々のissueにグループ化することを容易にします。これは、イベントがより小さく、より管理しやすいissueの集合にバケットされ、issueストリームのノイズが少なくなることを意味します。 ソースマップのアップロードを設定する最も簡単な方法は、Sentry Wizardを使用することです。以下のコマンドを実行するだけで必要なSentryパッケージをインストールし、ソースマップを生成してアップロードするようにビルドツール/CIを設定することができます。 ここでは、ソース・マップ・ドキュメントをサポートするためのドキュメントをいくつか紹介します。 ソース・マップは、ノイズを減らすのに役立つだけでなく、モニタリングとデバッグのワークフローを改善する上で、最もインパクトのある改善となる可能性を秘めています。 ソースマップをアップロードしたら、リリースバージョンをSentry SDKに渡すようにしてください。以下のようになるはずです。 本番ビルドのために、本番稼働中に環境変数経由で渡すことで対応してもよいでしょう。 元のエラー原因を渡す try…catchステートメントを使うと、同じエラーを再スローする代わりに新しいエラーをスローすることがあります。元のエラーのコンテキストを保持し、新しいエラーに原因を追加することができます。これは、Chrome 93、Safari 15、Firefox 91以降のブラウザでサポートされています。 リンクされたエラーは、SDKでデフォルトサポートされており、エラースタックはグループ化され、元のメッセージと共に表示されます。元のエラーを原因として渡すと、スタックトレースにコンテキストが追加され、デバッグ時に役立ちます。 実行不可能なエラーを無視する Sentryではあらゆる種類の興味深いエラーを見てきました。ページに直接注入されたカスタムJavaScript、ブラウザの拡張機能、無駄なエラーを投げるベンダーのコードからエラーを出力します。あなたのアプリケーションでも、これらのエラーのいくつかを見たことがあるかもしれません。 一般的に、エラーはあなたのコードからではないので、これを修正することはできません。 ノイズの原因になっている場合は、ignoreErrorsを使用してSDKで無視することができます。 Sentryの開発者は、実際に私たち自身の環境でいくつかのエラーを黙殺してきました。 TypeError: can’t access dead object というエラーは、ブラウザ拡張機能が削除された DOM 要素にアクセスしたときに Firefox がスローするエラーです。これらのエラーは、ブラウザ拡張機能を作成していない場合、通常は対処できません。 ‘Node’ で ‘removeChild’ の実行に失敗しました: 削除するノードはこのノードの子ではありません。これは、Chromeなどのブラウザでtranslate機能が有効になっていて、Reactがコンポーネントを更新しようとしたときにReactがスローするエラーです。このエラーは、文字列リテラルをスパンでラップすることで修正できますが、大規模なアプリケーションでは、これを強制するのは困難な場合が多いです。Sentryアプリは、ユーザー設定で有効にできる翻訳機能も提供しています。 これらのエラーは、ignoreErrorsを宣言することで、報告される前にクライアント上で直接フィルタリングすることができます。 プロジェクトの受信フィルタを有効にする プロジェクトレベルのフィルタは、Sentry JavaScript SDKのignoreErrorsと同様に、issueストリームにissueが表示されないようにすることができます。設定 → プロジェクト → プロジェクト設定 → 受信フィルタ で有効にできます。アプリケーションのコードに起因しないブラウザのエラーによって引き起こされる可能性が高いエラーを抑制するために、ブラウザの拡張機能によって引き起こされることが分かっているエラーをフィルタリングする、およびローカルホストから来るイベントをフィルタリングするをオンにすることをお勧めします。 インバウンドフィルタページで独自のルールを作成し、カスタムエラーメッセージをフィルタリングすることもできます。例えば – *ResizeObserver loop limit […]

Androidの例外を処理し、アプリケーションのクラッシュを回避する方法

Androidの例外を処理

まず、2つのことについて説明します。 例外とは、プログラムの実行中に発生し、本来のプログラムの動作を中断させるものです。そして、例外処理とは、例外に対応するための処理のことです。 Androidでは、例外を処理しないとアプリケーションがクラッシュし、恐ろしい「App keeps stopping」ダイアログが表示されることになります。 クラッシュし続けるアプリを使う人はいないですよね。ですので、例外処理を行うことは非常に重要なことであるといえます。 この記事では、例外を処理するいくつかの方法と、「処理されない例外」を処理する方法を紹介します。 また、これらの例外をキャッチする方法も紹介します。今回の記事における例については、Kotlinで解説します。 例外の階層構造 すべての例外は、Throwableのサブクラスです。ドキュメントによると… ”Throwableクラスは、Java言語におけるすべてのエラーと例外のスーパークラスです。このクラス(またはそのサブクラスの1つ)のインスタンスであるオブジェクトのみが、Java仮想マシンによってスローされるか、Javaのthrow文によってスローすることができます。” 例外の種類 Javaでは、例外は主に大きく2つに分けられます。チェックされた例外と、チェックされていない例外の2種類です。 チェックされた例外: コンパイル時にチェックされる例外です。例外を投げるメソッドを呼び出す場合は、呼び出し側として例外を処理するか、throws句で宣言する必要があります。 チェックされる例外の例: IOException、NoSuchMethodException、ClassNotFoundException チェックされていない例外: コンパイル時にチェックされない例外のことです。 チェックされていない例外の例: NullPointerException, ArithmeticException, ArrayIndexOutOfBoundsException。 興味深いのは、Kotlinにはチェックされた例外がないということです。次のコードをご覧ください。 このメソッドを呼び出したら、どうなると思いますか?「実行時例外が発生する」と思ったあなたは正しいです。 別のコードを考えてみましょう。 doSomethingメソッドを呼び出すと、ClassCastExceptionが発生します。 これらのスニペットを実行したい場合は、ここで実行することができます。 とあるシナリオ(処理のパターン)を定義するために、内蔵の例外クラスでは不十分な場合があります。このような場合、独自の例外を作成することも可能です。 例外処理のさまざまなやりかた 例外について理解したところで、例外をどのように扱えばよいかを見ていきましょう。まずは、例外を投げる小さなコードの一部を考えてみましょう。 このメソッドを呼び出して例外を処理しないと、アプリケーションがクラッシュし、「App keeps stopping」ダイアログが表示されることになります。 この例外を処理する最も簡単な方法は、メソッド呼び出しをtryブロックで囲んで、例外をキャッチすることです。必要であれば、finallyブロックで処理を追加することもできます。 例外をキャッチするだけで、finallyブロックで実行する追加処理がない場合、KotlinはrunCatching関数を提供します。 これらのスニペットを実行したい場合は、ここで実行することができます。 例外を伝播させる あるメソッドがさまざまな例外を投げる場合、そのうちの一部だけを受け取り、他の例外は上位に伝搬させるというパターンが考えられます。 次のようなコードを考えてみましょう。 try/catch文を使うと、次のようなことができます。 runCatchingを使うと、以下のようなことができます。 ここで重要なことは、例外を伝播させるため、より上位で例外を処理する必要があるということです。 これらのスニペットを実行したい場合は、ここで実行することができます。 Androidの例外処理 上記のテクニックを使って例外を処理するサンプルアプリケーションを考えてみましょう。 このアプリには1つの画面があり、2つのボタンがあります。それらがクリックされると何らかの処理が行われるようになっています。 最初のボタンは必要な例外処理をしており、2番目のボタンをクリックするとアプリがクラッシュします。 コードは非常にシンプルです。 実際のところ、どんなに最善を尽くしてもすべての例外を処理することは非常に困難です。もしかしたら、内部ライブラリの例外が発生するかもしれませんし、稀に処理されない「エッジケースシナリオ」があるかもしれません。 では、アプリケーションがクラッシュする頻度を最小限にするためには、どうすればよいのでしょうか。 Thread.UncaughtExceptionHandlerインターフェイスを使用することで、デフォルトの例外ハンドラにフォールバックすることが可能です。 しかし、デバッグアプリでこれを行うことは決してお勧めしません。なぜなら、クラッシュが発生したらすぐにフィードバックが欲しいからです。 これで、2つ目のボタンをクリックしても、例外を明示的に処理していないにもかかわらず、アプリがクラッシュすることはなくなりました。 これまで、さまざまな例外処理の方法と、デフォルトの例外処理されていない時の例外ハンドラを提供する方法について見てきました。では、さらに一歩進んで、これらの例外を監視する方法を見てみましょう。 […]

SentryのUnity SDKの始め方 – その1

SentryのUnity SDKの始め方

ユーザー体験とパフォーマンスは、どんなゲームでも最も重要な2つの指標です。どのようなプラットフォームでも、可能な限り最適な状態で動作するようにする必要があります。 理想を言えば、プレイヤーから「動かない」「壊れている」と怒られるのは避けたいところです。また、ゲーム内で発生した問題が、その関連情報とともに通知されることが求められます。 そこで、Sentryの出番です。 Sentryは、開発者がアプリケーションを監視して、重大なエラーやパフォーマンスの問題について把握するのに役立ちます。さらに、ゲーム内のエラーを追跡する重要なコンテキスト情報を提供するため、迅速に修正することができ、プレイヤーに満足してもらいやすくなります。 また、Unityと統合したSDKを提供することで、開発者がゲームを監視することを容易にしてくれます。 このシリーズでは、SentryのUnity SDKをセットアップして、ゲームのエラーやパフォーマンスの問題を監視する方法について説明します。 入門編 前提条件 はじめに、以下のものがインストールされていることを確認してください。 Unity Hub Unity Package Manager – Unity Hubをインストールした場合、Unity Package Managerもインストールされているはずです。 Sentryのアカウントを作成する すでにSentryのアカウントをお持ちの場合は、このステップをスキップすることができます。お持ちでない場合は、Sentryに移動し、無料のアカウントを作成してください。 アカウント作成後、このような画面が表示されます。 「Install Sentry」をクリックして進むと、新しい画面に遷移します。そこで、監視するプラットフォームとしてUnityを選択します。 最後に、「Create Project」をクリックして、Sentryプロジェクトを作成します。 Sentry SDKの設定 Sentry SDKは、Unityエディタ内で直接インストール、設定することができます。 Sentry SDKのインストール ここでは、Unity Package Manager経由でSDKをインストールすることをお勧めします。 プラスアイコンをクリックすると、パッケージマネージャがパッケージを取得するためのGitHubリポジトリURLを指定するオプションが表示されます。 https://github.com/getsentry/unity.git#0.27.0 インストールが完了すると、以下のようなアウトプットが表示されるはずです。 いくつかのバグをトリガーに、ゲームをクラッシュさせるデモシーンを含むサンプルがあり、オプションでインポートすることができます。 そして、SDKがイベントの送信先と関連するプロジェクトを知るためには、SentryプロジェクトのDSN(データソース名)を追加する必要があります。これは、[ツール] -> [Sentry]に移動して設定することができます。 [Sentry]をクリックすると、以下のようなセットアッププロンプトが表示されるはずです。 「Start Wizard」をクリックし、ウィザードを初期化します。すると、Sentryは自動的にブラウザタブを立ち上げ、既存のプロジェクトをエディタにインポートします。 もしプロジェクトが1つしかない場合、設定ウィザードは終了します。複数ある場合、Unityのゲームと関連づけるプロジェクトを選ぶプロンプトが表示されます。 最後に、同梱のサンプルを使用するか、次のようなスニペットで独自の例外を投げることで、Sentryが期待通りに動作しているかを確認することができます。 上図は、スニペットで発生した例外のエラーイベントを示しています。 SDKはこれを自動的にキャッチアップし、Sentryに送信し、issueとしてリストアップしてくれます。 最新リリースでの新機能 エラーのキャプチャーは、Sentryの多くの機能の1つに過ぎません。Sentryの最新版では、Unityのエラー診断と修正に役立つ多くのユーティリティが追加されました。 iOS、WebGLなどでもUnityをサポートするようになり、開発者はデスクトップアプリケーションの監視サポートも可能になりました。しかし、Sentryはそれだけにとどまりません。次のような機能も追加されました。 すべての対応プラットフォームに対応したシンボルの自動アップロード 以前は、画面のフリーズを検出する方法がありませんでしたが、メインスレッドが5秒以上フリーズした場合に、自動的にイベントを作成するようになりました。 […]

アプリ開発時のReact Nativeのデバッグとエラートラッキングについて

アプリ開発時のReact Nativeのデバッグ

優れたソフトウェア開発者は、コードをデバッグする方法を知っています。 実際、ほとんどのソフトウェア開発者は新しいコードを書くことよりも、既存のコードをデバッグすることがほとんどです。ネイティブアプリの開発に関しては、開発中のデバッグとエラーの追跡は手間のかかる面倒な作業として有名です。 そこで今回は、React Nativeアプリケーションをデバッグし、実際にエラーを追跡する方法をご紹介していきます。 また、React Native Debuggerというツールを使ったエラー追跡についても触れていきたいと思います。 React Nativeにおけるデバッグの攻略法 開発中のコードを効率的にデバッグするためのデバッグ手法を知っておくと便利です。場合によっては、専用のデバッグツールを使用する方が効率的かもしれません。 まず、React Nativeアプリでコードをデバッグするために使用できる一般的なテクニックをご紹介します。 今回の記事を通して使用する、新しいExpoベースのReact Nativeプロジェクトを作成しました。次のコマンドを実行すると作成できます。 ここでは、簡単なReact Nativeのコードを紹介します。 これはボタンをレンダリングして関数を起動し、コンソールに文字列を表示する簡単なアプリケーションです。 デバッグ用端末を使う 新しいExpoプロジェクトを起動したり、React Nativeアプリを実行したりすると、開発モード中に操作した全てのログがターミナルに表示されます。 これは、expo startコマンドを実行する別のターミナルでも、IDEに統合されたターミナルでもかまいません。 例えば、VS Codeを使用した場合、ターミナルにはアプリで行ったすべてのログが表示されます。 これは、ローカルでアプリケーションを実行する方法と似ています。 ReactやReact Nativeなどのフレームワークを使ったクライアントサイドの開発では、このターミナルでクライアントサイドのログを取得し、開発モードのアプリのデバッグに利用でき、どんな操作をしたときにどんなエラーが起きたのか手軽に確認できます。 さて、それではわざと構文エラーを起こしてみましょう。 上記のセクションのレイアウト定義において、最後にセミコロンを追加してみます。するとターミナルには、このようなエラーが表示されます。 何のファイルで、どんなエラーが、どこの行で起きたのか……エラーメッセージとともに表示されていますね。 Expoターミナルは、すべてのログが最初に表示される場所です。 そのため、アプリをデバッグしているときはいつでも、そのターミナルを最初に見ることができます。 エキスポデバッガー Expo を React Native で使用する場合、ビルトイン(備え付け)のデバッガーを入手することができます。 このデバッガーは、エラーメッセージを表示し、エラーが表示されたコードの部分を明示し、コード内のエラー部分をマークしてくれます。 React Nativeプロジェクトを物理デバイスで実行している場合、アプリのコードを実行しているExpoアプリでエラーがどのように表示されるかは次のとおりです。 エラーを確認し、コードを簡単にデバッグすることができますが、この場合、スタックトレースはあまり役に立ちません。なぜなら、エミュレータでExpoを使っているのであれば、ログの方が綺麗に整形されているためです。 スタックトレースはありませんが、こちらの方が簡潔で必要な情報だけが表示されていますね。これで混乱を減らし、より早くエラーにたどり着くことができます。 ブラウザで見るReact開発ツール 先ほどはネイティブアプリでした。 続いては、Web上でフロントエンドアプリケーションをデバッグしてみましょう。 ブラウザを開くだけで、コンソールのログやエラー、レンダリングされたUI要素、関連するスタックトレースなど、あらゆるものが表示されます。 ありがたいことに、React NativeやExpoベースのプロジェクトでも同じことができます。React Developer Tools Chrome拡張をインストールするか、次のコマンドを使用してシステムにグローバルインストールするだけで導入可能です。 Expoアプリの端末で、「w」を押せばWeb上でアプリが開きます。 エラー情報が表示され、Webアプリと同じようにデバッグすることができます。 これは、先ほどデバッグターミナルで見たのと同じエラーで、Expoアプリとエミュレーター上でも同じですね。 […]

コマンド1つでSentryをインストールする方法

Sentryをインストール

今回は、Sentryのインストールと初期設定を簡単に行う方法を共有いたします。 Next.jsを使用し、ターミナル(コマンドプロンプト)でコマンドを1つ実行するだけで、新しいSentryアカウントの設定や新しいSentry Next.jsプロジェクトの作成ができるようになります。 始めるのはとても簡単です。 sentry.io/signupにアクセスしてアカウントを作成したり、アプリ内からプロジェクトを作成したりすることももちろん可能です。 しかし現在は、面倒なクリック作業なども省略し、自分のリポジトリに移動してこのコマンドを実行するだけで必要な設定は完了します。 スクリプトが起動すると、ブラウザのタブを開いてサインアップフォームを表示します。 必要な情報を入力してターミナル(コマンドプロンプト)に戻ると、インストールが完了した旨の文言が表示されます。 この新しい方法では、next.config.jsファイルにSentry固有の要件が自動的に追加され、例外や遅延の問題の追跡を開始するために必要なファイルが作成されます。その内容を確認したい場合は『git status』を実行してご確認ください。 Sentryが無事導入できているか確認する場合は、ローカルでエラーをトリガーするか、変更をデプロイして例外や遅いトランザクションを実際に実行する必要があります。 それもSentry for Next.jsで、たった1つのコマンド(npx @sentry/wizard -s -i nextjs)で完了します。詳しくは、GitHub、Twitter、またはDiscordでご意見をお聞かせください。 Sentryは、アプリケーションコードの健全性を監視するために不可欠です。 エラートラッキングからパフォーマンスモニタリングまで、開発者はフロントエンドからバックエンドまで、アプリケーションをより明確に把握し、より迅速に解決し、継続的に学習することができます。 Sentryは、350万人以上の開発者と世界中の85,000以上の組織に愛され、Disney、Peloton、Cloudflare、Eventbrite、Slack、Supercell、Rockstar Gamesといった世界的有名企業の多くにコードレベルの監視機能を提供しています。 毎月、世界中で人気のサービスやアプリケーションから、数十億件の例外を処理し続けています。     IchizokuはSentryと提携し、日本でSentry製品の導入支援、テクニカルサポート、ベストプラクティスの共有を行なっています。Ichizokuが提供するSentryの日本語サイトについてはこちらをご覧ください。またご導入についての相談はこちらのフォームからお気軽にお問い合わせください。

Next.jsのよくあるエラーとその解決方法

Next.jsのよくあるエラーとその解決方法

バグは、ソフトウェア開発において最も厄介なものの1つです。ほとんどの場合、すぐに解決することができますが、中には修正に数時間から数日かかるような恐ろしいバグもあります。 Next.jsは、現在の世界で最も人気のあるWeb開発フレームワークの1つですが、バグから逃れることはできません。 そこで、この記事では、さまざまなプラットフォームの開発者から報告された、最も一般的なNext.jsのエラーをいくつか紹介します。 なぜこのようなバグが発生するのか、そしてどのように解決するのかについて説明します。 ドキュメント/ウィンドウオブジェクトエラー Next.jsでよくあるエラーのひとつに、「document/windowが定義されていない」というエラーがあります。なぜ起こるのでしょうか?このエラーは、通常、インストールされたパッケージが、ページコンポーネントの中でブラウザのウィンドウオブジェクトに直接アクセスしようとしたときに発生します。 例えば、Next.jsアプリケーションにindex.jsのサンプルページがあり、以下のようにブラウザのlocalStorageにアクセスしようとすると、「document/windowが定義されていない」というエラーが発生します。 解決方法 このエラーを解決するにはいくつも方法があります。 ひとつは、ブラウザのウィンドウオブジェクトを必要とするコードブロックを実行するためにreactのuseEffect()フックを使用し、ページコンポーネントがマウントされているときにのみコードを実行するようにすることです。 もうひとつの方法は、ブラウザのウィンドウを必要とするコードを独立したコンポーネントに変換し、Next.jsの動的インポート機能を使ってページコンポーネントにインポートすることです。 Nextの動的インポートは、必要に応じてコンポーネントを遅延ロードまたは動的にロードするために使用される機能です。 さらに、ssrオプションを追加することで、使用時にサーバーレンダリングの有効・無効を設定することができます。※ちなみに「ssr」は、サーバーサイドレンダリングの意味があります。 ssrの値をfalseに設定するだけで、ブラウザのウィンドウやドキュメントに依存するコンポーネントや外部パッケージを読み込むことができるようになります。 下図のようにすることで、ページ内で動的に読み込むようになります。 ミドルウェアエラー ミドルウェアは、リクエストの完了前にコードが実行され、受信したリクエストに基づいて、ユーザーに送信するレスポンスを書き換え、リダイレクトしたり、ヘッダーを追加したりすることができます。 次の例は、Next.jsでミドルウェアを扱うときに遭遇する可能性のあるエラーの1つです。 Next.jsをカスタムサーバーで使用しており、設定でサーバーホストのURLを明示的に指定していない場合に発生するエラーです。 また、Nxのようなモノレポビルドシステムを使用している場合、このようなツールがバックグランドでカスタムサーバーを作成するため、このエラーが発生します。 ※「モノレポ」とは、複数のプロジェクトやアプリケーションのソースコード を一元管理することです。 解決方法 このエラーは、サーバー設定ファイルでホスト名を指定することで簡単に修正することができます。 Nxを使用している場合は、プロジェクト設定ファイル(project.js)のserveオプションを修正することで対応できます。つまり、プロジェクト設定ファイル(project.js)を次のように修正します。 ミドルウェアがトリガーされない まれにミドルウェアのコードが全くトリガーされない場合がありますが、それはミドルウェアのファイルが正しく配置されていない可能性があります。 解決方法 Next.jsの古いバージョンでは、ミドルウェアのファイルは/pagesディレクトリに_middleware.jsとして作成していました。 しかし、Next.jsの最新版では、このファイルをルートフォルダ、つまり/pagesディレクトリと同じ階層にmiddleware.jsとして保存する必要があります。 そのため、ファイル構造は次のようになります。 API/スラッグエラー Next.jsには、データ取得のためのAPIとしてgetStaticPathsとgetServerSidePropsがありますが、これらの使い方を誤るとエラーが発生することがあります。 そのうちのgetStaticProps APIでのエラーは、以下の通りです。 このエラーは、next.jsのSSG(Static Site Generation)機能を使ってサーバーサイドで動的ページをレンダリングしているときに、ページのコンポーネントにgetStaticPaths関数が定義されていない場合に発生します。 動的経路を使用するサーバーサイドレンダリング(SSR)または静的生成(SSG)ページを構築するためには、getStaticPaths関数を追加する必要があります。 解決方法 例えば、/pageName/[slug]がブログ記事に関する情報を表示するページとした場合、getStaticPaths関数でデータベースから利用可能なすべてのブログ記事のスラッグのリストを取得し、[slug]パラメータの値としてそれらを戻り値として返す必要があります。 また、Next.jsのデータ取得APIは、すべてページコンポーネントの内部でのみ使用でき、通常のコンポーネントの外部では動作しないことも特筆すべき点です。 モジュールエラー また、Next.jsでよくあるエラーは、次のような「モジュールが見つかりませんでした。’moduleName’を解決できない」というエラーです。 このエラーは、Next.jsではアクセスできないモジュールをインポートしているときに発生します。 例えば、fsモジュールのように、クライアント側で利用できないモジュールを、あなたやサードパーティのパッケージがアクセスしようすると発生します。 解決方法 このエラーを解決するには、Node.jsまたはサーバー関連のコードをすべてNext.jsのデータ取得API(getServerSideProps、getStaticPaths、getStaticProps)の内部に置くようにしてください。 Node.jsモジュールに明示的にアクセスしようとしているのではなく、インポートしたパッケージが原因でエラーが発生した場合は、Next.jsの設定ファイル(next.config.js)に、次のようにwebpackのエントリを追加する必要があります。 このコードでは、optionsオブジェクトのisServerプロパティをチェックして、現在のビルドがサーバーサイド用かクライアントサイド用かを判断しています。 ビルドがサーバーサイド用でない場合(つまりisServerがfalseの場合)、fsモジュールはwebpack設定のresolveセクションでフォールバックリストに追加されます。 これは、webpackがfsモジュールのimportに遭遇したとき、importを解決しようとせず、fsモジュールがアプリケーションにバンドルされないことを意味します。 もし、Node.jsのモジュールを使っていないのに「モジュールが見つかりません」というエラーが発生する場合は、モジュールが正しくインストールされ、インポートされていることを確認してください。 または、ファイル名やモジュール名が間違っている可能性もあるため、そちらについても確認するようにしましょう。 クロスオリジンエラー(Next […]

;