mcpField
A
api-design

api-design

github.com/margelo開発178678 回インストール10 週間前に更新
リポジトリ

api-design について

実装前のパブリック API の設計やレビューに使うスキルです。明示的な型、安定した意味、実際の使用例を重視し、公開 TypeScript の検証を促します。

主な機能

  1. 01利用者向け API を先に設計
  2. 02実際の呼び出し例を作成
  3. 03型とライフサイクル設計を確認
  4. 04最新の情報源で検証
  5. 05公開 TypeScript を検証

活用シーン

  1. 01ライブラリのパブリック API の計画
  2. 02提案された API のレビュー
  3. 03TypeScript の型とライフサイクル設計
  4. 04実装前の API 変更の検証

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/margelo/react-native-skills --skill api-design

インストール後、Claude Code で/api-designを入力して読み込まれたか確認します。

詳細

API 設計

実装前、または公開 API のサーフェスをレビューする際に、このスキルを使用します。明示的な型、安定したセマンティクスを目指し、公開コントラクトに無関係な内部情報は含めないでください。

ライブラリが Nitro Module の場合は、まずこのスキルを使って公開する TypeScript と React の API の形を決め、その後 build-nitro-modules を使って Nitro 固有の仕様、ネイティブ状態、バインディングに関する制約を確認します。ライブラリが JS のみ、React のみ、または React Native の JS のみの場合は、このスキルだけを使用してください。

ワークフロー

  1. 内部を実装する前に、ユーザー向けの TypeScript API を大まかに設計します。
  2. エラー処理とクリーンアップの手順を含む、現実的な呼び出し例を 2~3 個作成します。
  3. 以下のルールに照らして API のサーフェスを確認します。
  4. API が完成したと判断する前に、リポジトリの型チェック、リンティング、ドキュメント作成のツールで、エクスポートする TypeScript を検証します。
  5. 公開する形に一貫性があり、検証も済んでから実装します。

API の最新性

公開 API の形、依存関係の API、プラットフォームの機能、実装方針を決める前に、記憶に頼らず、最新の公式情報源を確認してください。ライブラリ、React、React Native、プラットフォーム、ツールの API は急速に変化します。

  • 公式ドキュメント、ソースリポジトリ、リリースノート、変更履歴、パッケージの README、最新のパッケージメタデータを優先してください。
  • 利用可能な場合は、公式ドキュメントサイトの llms.txt または llms-full.txt を探し、最新情報を簡潔に把握するために活用してください。
  • 記憶している API の詳細は、あくまで出発点となる仮説として扱ってください。現在のドキュメントやソースと食い違う場合は、最新のドキュメントやソースに従い、必要に応じて変更点に言及してください。
  • 公式の最新情報源がある場合は、古いブログ記事、過去のコード例、古くなった学習時の前提に基づいた設計を避けてください。

API の形に関するルール

  • 信頼できる情報源は一つにすることを優先してください。一つの型付きの値で状態を表せる場合、関連する状態を複数のブール値や依存する値に分割しないでください。enableTimeout: boolean と timeoutMs?: number を組み合わせるより、timeoutMs?: number を優先してください。
  • 関数の引数が3つ以上ある場合、同じプリミティブ型の引数が複数ある場合、または値が増える可能性が高い場合は、オプションオブジェクトまたは名前付き構造体を使用してください。
  • あらゆる入力形式を受け入れるのではなく、API を具体的にしてください。ミリ秒単位のタイムアウトは number にし、number | string | bigint | object | null にはしないでください。
  • 何でもこなす巨大なオブジェクトは避けてください。責務が異なる場合は、ドメインまたはライフサイクルごとに分割してください。
  • API を簡素化または再設計する前に、その機能がサポートすることを想定されているワークフローを洗い出してください。実装しやすいからといって、ライブセッション API などのワークフローを黙って削除しないでください。必要に応じて、ワークフローごとに API を分けてください。
  • 有効な状態が分かっている場合は、リテラルユニオン、判別可能なユニオン、インターフェース、型付きオプショングループを優先してください。TypeScript ライブラリでは、利用者が実行時の値を必要とする場合を除き、実行時の enum より文字列リテラルユニオンを優先してください。
  • 有効な状態が分かっている場合、型付けされていない辞書、ブール値の寄せ集め、文字列で型付けされたコマンド、形状が曖昧なイベントは避けてください。
  • 二択のオプションを、'enabled' | 'disabled' のような省略可能な文字列ユニオンで表さないでください。省略時に「デフォルトを使用する」という意味で、指定時に true/false を表す場合は、省略可能なブール値を使用し、デフォルトを文書化してください。
  • 複数のオブジェクト状態を、省略可能なフィールドばかりの単一インターフェースで表さないでください。判別可能なユニオン、継承、または個別のバリアントインターフェースを使い、あり得ないフィールドの組み合わせを表現できないようにしてください。
  • 関連するフィールドは、それらが必須となるバリアントにまとめてください。barcode と barcodeType が常にセットで意味を持つ場合は、汎用の ScannedData で省略可能にするのではなく、ScannedBarcode で両方を省略不可にしてください。
  • バリアントインターフェースを作成する場合は、実際の公開型で使用してください。RecognizedTextDataType と RecognizedBarcodeDataType を定義しながら、RecognizedDataType にすべてのバリアントのフィールドが省略可能なままの recognizedDataTypes: RecognizedDataType[] を使用しないでください。
  • 値がないことには undefined または省略可能なフィールドを使用してください。「明示的に何もない」ことが「指定されていない」ことと異なる意味を持つ場合にのみ、null を使用してください。
  • ステートマシン、読み込み状態、結果のバリアントには、判別可能なユニオンを優先してください。
  • ネゴシエーションを伴う場合は、ユーザーの意図と解決済みの状態を分けてモデル化してください。たとえば、制約オブジェクトの順序付き配列で優先順位を表し、解決済みの設定オブジェクトでプラットフォームが実際に選択した内容を示せます。
  • 複雑なネゴシエーションでは、サポート状況の組み合わせ表を公開するより、順位付き制約または優先設定オブジェクトを優先してください。呼び出し元が意図を記述できるようにし、内部で最も近い動作可能な設定を解決して、戻り値、コールバック、または明示的なリゾルバーメソッドを通じて解決済みの設定を公開してください。
  • 一般的なプリセットは、as const satisfies Record<string, Type> オブジェクトとして公開してください。利用者が独自の値を指定できるよう、受け入れる型は構造的なものにしてください。
  • 呼び出し元がすでに知っている、たとえば Platform.OS を繰り返すだけの platform フィールドなど、周囲の状況に関する事実を公開しないでください。ただし、現在のランタイムとは異なるプラットフォームで生成されたデータを API が返す場合は除きます。
  • 現在のプラットフォームのサポート状況を、型の形に固定しないでください。プラットフォームごとに型を分けたり静的に除外したりするのではなく、availableTextTypes: [] や supportedFormats: [] など、実行時の機能フィールドを優先してください。これにより、JS API を再設計せずに、新しいネイティブ機能を利用できるようになります。
  • 統一されたオプションオブジェクトを優先してください。概念が本当にプラットフォーム固有で、クロスプラットフォーム機能や何もしない処理として記述できない場合を除き、ios/android のオプション群やプラットフォーム接頭辞付きのメソッドは避けてください。
  • 設定フィールドを必須条件と優先事項のどちらかに分類してください。必須条件を満たせない場合はエラーをスローしてください。品質、ガイダンス UI、高フレームレート追跡、自動ズーム、広いスキャン領域など、機能が本来の役割を果たせる場合は、優先事項を可能な範囲で適用してください。可能な範囲で適用するフィールドを文書化し、適用された内容を呼び出し元が把握する必要がある場合は、機能または解決済みの設定を公開してください。
  • オプション、結果、将来の拡張が異なる場合は、安定した意味上の機能ごとに分割してください。たとえば、今日では一つのネイティブ API が両方を実装していても、バーコードスキャンとテキストスキャンには、個別の BarcodeScanner API と TextScanner API が適している場合があります。テキストスキャンをサポートしていないプラットフォームでは、バーコードスキャン用の汎用スキャナーでテキスト固有のフィールドを省略可能にするのではなく、createTextScanner() を失敗させるか、isTextScannerAvailable: false を返してください。
  • 実行時の利用可否は、安定した機能が現在存在するかどうかを示すものであり、現在のプラットフォームのサポート状況に合わせて API の形を恒久的に制限するものではありません。後に Android でテキストスキャンが利用可能になった場合、バーコード API を変更したり、結果型の省略可能な型を増やしたりせずに、既存の TextScanner 機能を利用可能にできるべきです。
  • 機能の検出とオブジェクトの契約は分けてください。機能フィールドは、ワークフローを作成できるか、どの省略可能な優先設定を適用できるかの判断に使用してください。ファクトリーが特定のセッション/リソースオブジェクトを返した後は、その基本メソッドが構築時点で保証されているべきです。そうでない場合は、より限定された型を返すか、作成を失敗させてください。通常のメソッドを呼び出すたびに、呼び出し元が can* を確認する必要がある設計にはしないでください。
  • ワークフローの分割によって、実行時の機能チェックが減るようにしてください。大きすぎるオブジェクトを補うためだけに、広範な機能フラグを残さないでください。たとえば、単発スキャナーとアプリが所有するライブスキャナーは、別々の API にできます。ライブスキャナーの実装でズーム、写真撮影、領域制御が保証される場合、それらを単発バックエンドから引き継いだ省略可能なプロパティや can* チェックにせず、ライブスキャナーの契約に含められます。
  • ライフサイクルの遷移を API のオブジェクトグラフで表してください。configure、connect、start などのライフサイクル遷移後にのみ有効なコマンドは、「アクティブかもしれない」状態を確認する親オブジェクトではなく、その遷移によって返されるハンドルで公開してください。たとえば、session.configure(device, outputs) から setZoom(...) と focusTo(...) を持つ Controller を返し、Session はグラフの設定と開始/停止を担当できます。
  • 古くなった状態を扱う API は避けてください。再設定によってコマンドの対象となるネイティブリソースが変わる場合は、新しいハンドルを返し、古いハンドルを無効化するか破棄してください。どの設定済みデバイス/出力が対象なのか分からなくなった親オブジェクトに対して、呼び出し元が誤ってコマンドを実行できないようにしてください。
  • ネゴシエーション後に判明する事実には、コールバックまたは解決済みオブジェクトを使用してください。出力、コントローラー、またはセッション設定が接続後に初めて意味を持つ場合は、呼び出し元に省略可能なプロパティを繰り返し確認させるのではなく、onConfigured、返されるコントローラー/設定、または明示的な resolve...(...) メソッドを提供してください。
  • Nitro を使用した命令型 API では、デフォルトで公開 HybridObject を API として扱ってください。入力を事前解析したり、列挙値/文字列の形式を変換したり、公開形式を別の形式に正規化したりする JS ラッパーを追加するなどして、JS が生成された Nitro 仕様と異なる API を呼び出すようにしないでください。意図する公開形式を Nitro 仕様とネイティブ実装に直接定義してください。
  • 「すべて」が意味のある指定値である場合は、undefined を隠れたコマンドとして使わず、明示的にモデル化してください。たとえば、実装にとって具体的な値を扱うほうが望ましい場合は、targetFormats: 'all' | BarcodeFormat[] または TargetBarcodeFormat = BarcodeFormat | 'all' を優先してください。
  • 通常の使用前に別途 prepare()、initialize()、load() を呼び出す必要がある、初期化が不完全なオブジェクトを返さないでください。セットアップが必要な場合は、ファクトリーを非同期にし、使用可能な状態のオブジェクトを返してください。start()/stop() のように実際に繰り返し行う遷移にはライフサイクルメソッドを使用し、構築完了のためには使用しないでください。
  • 大規模なライブラリでは、小さな公開ルートまたはファクトリーを一つ公開し、状態を持つドメインオブジェクトを作成できるようにしてください。静的関数や初期化が不完全なインスタンスを呼び出し元に扱わせるのではなく、オブジェクトの構築、非同期セットアップ、I/O、検証はファクトリーメソッドの背後に置いてください。
  • undefined は通常のドメイン上の値がない場合にのみ返し、その条件を正確に文書化してください。明示されていないエラー経路として省略可能な戻り値を使用しないでください。操作に失敗した場合は、エラーをスローするか reject してください。
  • 返すデータが単なる値なのか、リソースなのかを決めてください。フィールドが少なく、不変で、意味的に完全なデータには、プレーンな構造体/インターフェースを使用してください。値がネイティブ状態を所有する場合、高コストのアクセスを遅延できる必要がある場合、振る舞いを拡張できる場合、または後からメソッドを公開する場合は、クラス/オブジェクト/リソースを使用してください。
  • データ表現は、まず意味に基づいて選び、その後にパフォーマンスを考慮してください。デコード済みのテキストペイロードには string を使用してください。ゼロコピーアクセスが契約の一部となる、生のバイナリ、不透明なバイト列、メディア、大きなデータには、ArrayBuffer またはバイト指向のオブジェクトを使用してください。
バリアントの例

オブジェクトが複数の異なる状態を取る場合は、nullable なクラスターを避けます。

interface ScannedData {
  position: Point
  text?: string
  barcode?: string
  barcodeType?: BarcodeType
  face?: Rect
}

基底型と、状態固有のフィールドを省略不可にしたバリアントを使用します。

interface ScannedData {
  position: Point
}

interface ScannedText extends ScannedData {
  text: string
}

interface ScannedBarcode extends ScannedData {
  barcode: string
  barcodeType: BarcodeType
}

interface ScannedFace extends ScannedData {
  face: Rect
}

type ScannedResult = ScannedText | ScannedBarcode | ScannedFace

公開 API の構成

  • 公開 API は用途ごとにファイルまたはモジュールを分け、明確なパッケージのエントリーポイントから再エクスポートします。機能の主要オブジェクトに、すべての列挙型、オプション、結果、イベント、ヘルパー型を含めた何でも入れるファイルは作成しないでください。
  • 原則として、エクスポートする公開型はファイルごとに 1 つにします。複数の公開型を 1 つのファイルにまとめるのは、それらが密接に関連した 1 つの論理的な構成要素を形成し、個別にインポートされることがほとんどない場合に限ります。たとえば、DynamicRange 型と、それを定義する正確なリテラル型のユニオンなどです。
  • 公開パッケージ型は、src/index.ts などのパッケージのエントリーポイントからのみ再エクスポートします。機能、仕様、型のファイルから、同じフォルダー内の無関係な型を再エクスポートしないでください。
  • パッケージのエントリーポイントでは、インポートしてから再エクスポートするのではなく、直接再エクスポートする構文を使用します。型のみのシンボルには export type { Foo } from './Foo' を、実行時の値には export { Foo } from './Foo' を使用します。
  • src/index.ts、index.ts、index.js、index.tsx などのパッケージのエントリーポイントは、エクスポート専用です。関数、クラス、フック、コンポーネント、ヘルパー定数、分岐、副作用、実装ロジックを定義しないでください。実行時の宣言として許可されるのは、export const camera = NitroModules.createHybridObject<Camera>('Camera') のような単純な 1 行のパッケージルートのみです。それ以外はすべて、用途に応じたファイルに記述して再エクスポートします。
  • 型をバーコード形式、スキャン値、設定、機能、サブスクリプションなどの概念ごとに分割できる場合、600 行の型/仕様ファイルは許容されません。
  • 共通のフィールドとメソッドは、基底インターフェースまたはクラスに配置します。サブタイプに追加するのは、そのサブタイプ固有のメンバーのみとし、format、valueType、id、bounds などのフィールドをすべての具象バリアントで繰り返さないでください。
  • ヘルパーと変換は、意味のある最小単位のレシーバーに配置します。変換が 1 つの要素のみを読み取る場合、返す対象値が 0 個、1 個、複数個のいずれであっても、その変換は要素自体に配置します。配列の合成には、呼び出し箇所で通常の map、flatMap、reduce、または Set(...) を使用します。
  • コレクション自体にドメイン上の意味がある場合にのみ、コレクションレベルのヘルパーを追加します。たとえば、要素全体の検証、重複排除、順序の保証、キャッシュ、パフォーマンス向上のためのバッチ処理、空でないことの確認、エラーの集約などです。標準的なコレクション操作を隠すだけのヘルパーは追加しないでください。
  • パフォーマンスや実装方針は、呼び出し側が API を使用する方法に影響しない限り、公開 API の形に反映させないでください。たとえば、API に明示的な変換メソッドを公開することはできますが、「遅延」、「軽量」、「正規化済み」などの内部詳細を、直接観測可能な動作でない限り、名前やドキュメントでアピールしないでください。

TypeScript と JavaScript のスタイル

  • 未定義の値として void 0 を使用しないでください。undefined を明示的に記述してください。
  • パッケージ/実行時コードでは、??=、||=、&&= などの論理代入演算子を使用しないでください。変更とフォールバックの動作が呼び出し箇所で明確になるよう、明示的な if ブロックまたは直接代入を使用してください。

名前とメンバー

  • コマンドには、動詞と対象を含む名前を付けます。数値には単位の接尾辞を付けます。timestamp() よりも getCurrentSystemTimestampMs() を優先します。
  • ブール値には、単なる型ではなく、その役割を表す名前を付けます。
  • 観測された状態、機能、または解決済みの設定を表すブール値には、is*、has*、can*、supports* などの述語名を使います。例:isFlashEnabled、isFlashAvailable、hasFlash、supportsFlash。
  • 今後の動作を制御するブール値のオプションには、enable*、allow* などのコマンド名や設定名、またはドメイン固有の動詞を使います。例:enableFlash、allowCellularAccess、preferHighAccuracy。
  • React と React Native のパブリック API では、use* の名前はフック専用にします。React のコンテキスト以外で use* を使ってもよいのは、ローカルのドメインで自然に読め、フックと混同されるおそれがない場合に限ります。
  • 機能がまだ有効になっていない入力設定には、is*Enabled を避けます。enableGuidance?: boolean は要求する設定として読めますが、isGuidanceEnabled: boolean は解決済みの状態として読めます。
  • 変化を伴わない観測可能な状態や機能には、プロパティを使います。accelerometerAvailable() よりも readonly isAccelerometerAvailable: boolean を優先します。
  • 副作用がある処理、コストの高い処理、割り当て、変更、非同期処理の境界、または失敗する可能性がある操作には、メソッドを使います。
  • 名前で単位を明示します。例:timeoutMs、byteSize、maxRetries、createdAt。
  • エクスポートするすべての型に、パッケージ名、モジュール名、または機能名を機械的に接頭辞として付けるのは避けます。インポートとパッケージの名前空間で、すでに文脈が示されています。必要に応じて呼び出し側で名前のエイリアスを付けられます。接頭辞を使うのは、パッケージのルートからインポートしたときに接頭辞なしの名前が曖昧な場合に限ります。すべての型に DataScanner を繰り返し付けるより、CameraPermissionStatus、ScannedResultSource、Point、Rect などのドメイン名を優先します。
  • 呼び出し側が参照するものの設定はしないパブリックな状態には、readonly を使います。プロパティへの代入自体が意図されたコマンドである場合に限り、変更可能なプロパティを使います。
  • 書き込み可能なプロパティは、低コストで同期的に処理でき、失敗する可能性が低いものにします。値を設定する際に、ネイティブとの調整、権限の取得、I/O、メモリ割り当て、失敗する可能性のある検証、スレッドやプロセスの切り替えが必要になる場合は、setZoomFactor(zoomFactor): Promise<void> のような明示的な非同期メソッドを公開します。
  • パブリックな文字列リテラルのユニオン型では、manualInput のようなキャメルケースの値よりも、manual-input のような小文字のケバブケースの値を優先します。

非同期処理、イベント、React

  • 結果が即座に得られ、決定的で、低コストかつローカルである場合は、同期 API を使用してください。実装がネイティブであるというだけで、単純な値の構築、キャッシュ済みメタデータ、純粋な変換を非同期処理でラップしないでください。
  • 1 回限りの非同期処理には Promise を使用してください。繰り返し発生するイベントには、リスナー API、ストリーム、またはオブザーバブルストアを使用してください。
  • 権限、ハードウェア/セッションのセットアップ、I/O、キャプチャ/録画、プラットフォームの非同期 API、ブロッキング処理、すでに非同期であるネイティブ API、またはスレッドやプロセスの境界を越える可能性がある処理には Promise を使用してください。負荷の高い処理や複雑な処理は、呼び出し元をブロックするのではなく、所有するワーカー/キュー/ディスパッチャー/ランタイムで実行してください。
  • 基盤となるプラットフォーム API が要求する場合を除き、メイン/UI スレッドを避けてください。メインスレッド API では必要な UI 処理だけを行い、負荷の高いセットアップ、変換、解析、I/O、計算は所有するバックグラウンドコンテキストに移してください。
  • 任意のレイヤーに Promise のリゾルバー状態を渡さないでください。非同期境界から Promise を返し、async/await、ネイティブの Promise ヘルパー、またはブリッジを行う箇所での小さな完了コールバックを使うことを優先してください。解決されないまま残る Promise や二重解決は、ライフサイクルのバグです。
  • 判断が難しい API はベンチマークしてください。通常の使用で、目に見える UI 処理をブロックする、別のスレッド/プロセスでの完了を待つ、または実行時間が予測できない場合は、そのメソッドを非同期にしてください。
  • 低コストの処理経路と負荷が高くなる可能性のある処理経路を持つ変換では、ブロッキング処理を曖昧な 1 つのメソッドに隠すのではなく、convertX() や convertXAsync() のように、同期メソッドと非同期メソッドを明示的に公開してください。
  • 内部でスレッド/キュー/ランタイムを何度も行き来するのは、API 設計上の問題の兆候と考えてください。実行コンテキストを所有するオブジェクト、またはそのコンテキストへ一度だけ移る明示的な非同期ライフサイクルメソッドを優先してください。別スレッドからの結果が必要な呼び出し元には、同期処理のように見えるメソッドやプロパティの裏に移動処理の連鎖を隠すのではなく、Promise、リスナー、ストリーム、または状態を持つハンドルを返してください。
  • 競合状態、準備完了、順序に関するバグを、setTimeout、スリープ、人工的な遅延、余分なスレッド移動、同じメソッドの二重呼び出しで修正しないでください。これらはライフサイクル契約の不備を隠すだけです。状態機械を修正する、明示的な準備完了/完了イベントを公開する、設定済みのハンドルを返す、または処理を適切に非同期化してください。再試行が適切なのは、ネットワーク、リモートサービス、OS サービス、ハードウェアなど、外部の非決定性が原因の場合に限られます。また、再試行は回数に上限を設け、キャンセル可能で、安全に繰り返せる必要があります。
  • 書き換え可能なコールバックプロパティではなく、クリーンアップ用の明示的なリスナーメソッドを使用してください。例:onValueChanged?: (...) => void ではなく、addOnValueChangedListener(listener): ListenerSubscription。
  • シンプルなリスナーコールバックのパラメーターは、メソッドシグネチャ内にインラインで記述してください。名前付きコールバック型をエクスポートするのは、利用者が直接インポートする場合、同じ関数契約が複数の API で再利用される場合、またはリスナー関数以上の明確なドメイン上の意味がある場合に限ってください。
  • リスナー登録では、クリーンアップ用ハンドルを返してください。addListener(...): number と removeListener(listenerId: number) の組み合わせは避けてください。呼び出し元が ID を管理するとリークしやすく、安全な型付けも難しく、隠れたグローバル/静的リスナーテーブルを必要とするためです。ネイティブまたは内部のクリーンアップを所有する、冪等な remove 関数を持つシンプルなサブスクリプションオブジェクトを優先してください。
  • 繰り返しの監視やイベントには、デフォルトで addOn...Listener(callback): ListenerSubscription を使用してください。これにより、複数の独立した利用者をサポートでき、各呼び出し元がクリーンアップを明示的に所有できます。ある呼び出し元が別の呼び出し元のリスナーを誤って置き換える可能性がある場合、単一の setOn... コールバックスロットを使用しないでください。
  • リスナーのクリーンアップでは、以降のイベント送信を防止するようにしてください。スナップショットからすでに配信中のイベントをキャンセルできるとは約束しないでください。ディスパッチ中に削除されたコールバックが現在のイベントを受け取れないよう保証するために、API や実装を過度に複雑にしないでください。
  • setOn...(callback | undefined) 形式の API は、置き換えが意図された意味である場合に限って使用してください。つまり、オブジェクトにコールバックスロットが 1 つだけある場合、コールバックがイベントサブスクリプションというより設定に近い場合、またはホットパスのネイティブパイプラインでリスナーを多重化することが現実的でない場合です。以前のコールバックを置き換えること、および undefined で削除することを文書化してください。
  • 所有関係を伝えるために Callback や Listener の接尾辞だけに頼らないでください。add... と set... の動詞、および戻り値の型によって、コールバックが追加され呼び出し元が所有するものか、単一の置き換え可能なスロットかを明確にしてください。
  • コールバックがキャプチャやアップロードの進捗コールバックのように 1 回のメソッド呼び出しに属する場合、その処理の進捗を伝えるにはコールバック用オプションオブジェクトを使用してください。
  • それ自体が機能である場合を除き、ネイティブ側の管理情報をイベント契約として公開しないでください。シンプルな状態の観測を優先し、安価でより明確な場合は、アプリ固有の差分や状態を JS 側で導出してください。
  • React または React Native のライブラリでは、値を UI に反映させる場合、安定した命令型 API の上にフックを提供してください。サブスクリプションには useEffect、外部状態には useSyncExternalStore など、適切な React のプリミティブを使用してください。
  • フックは命令型のコアを利用するためのアダプターとして実装し、ライブラリを使用する唯一の方法にしないでください。
  • React コンポーネントでは、並行する別の API を作成するのではなく、命令型のコアをラップしてください。コンポーネントはセットアップ、ref、ジェスチャー、ライフサイクルを管理し、フックは高度な用途向けに同じ下位レベルのオブジェクトを公開できます。
  • サブスクリプションには安定したコールバックを使用し、確実にクリーンアップしてください。イベントの見逃しが外部から観測可能な場合は、イベントを発生させる可能性のあるライフサイクル処理より先にサブスクライブしてください。

エラーとプラットフォーム間の違い

  • ユーザーが遭遇しうるエラーを、黙って握りつぶしてはいけません。単に return したり、ログに記録したり、何もしなかったりせず、例外をスローするか、拒否するか、専用のエラーリスナーを通じて通知してください。
  • 失敗やエラーイベントには、カスタムの { code, message } 構造体ではなく、実際の JavaScript Error オブジェクトを使用してください。ただし、プロトタイプを保持できない境界を越えて API がエラーをシリアライズする場合は除きます。
  • 必須機能がサポートされていない場合や、入力が無効な場合は、具体的なエラーをスローしてください。不足している機能、無効な値、有効な範囲、またはプラットフォーム上の制約を示してください。
  • あるプラットフォームではサポートされている任意の設定を、別のプラットフォームが無視するという理由だけで例外をスローしてはいけません。無視してもユーザーが要求した結果に影響しない場合は、適切にフォールバックし、機能 API や解決済み状態 API で、その設定が反映されたかどうかを確認できるようにしてください。
  • オプションを無視すると、ユーザーの意図、安全性、プライバシー、または目に見える正確性が損なわれる場合は、例外をスローしてください。たとえば、フラッシュモードが必須の場合は、フラッシュのないハードウェアでは失敗させる必要があります。一方、品質に関するベストエフォートのヒントは、フォールバックできます。
  • 実際に代替手段がある場合は、エラーメッセージで代替手段に触れてください。
  • 静的アサーションや致命的エラーは、JS ユーザーコードから到達できる状態ではなく、内部的に不可能な状態に限って使用してください。
  • React Native では、iOS と Android の API を 1 対 1 で反映するのではなく、クロスプラットフォームの概念として設計してください。低レベルのアクセス自体が機能である場合を除き、ネイティブのクラス名やフレームワークの詳細を表に出さないでください。
  • GPU、ゼロコピー、ネイティブリソース、ハードウェア API の低レベル概念は明示してください。所有権、コピー、同期/非同期の動作、スレッドアフィニティ、破棄に関する情報が隠れてしまうような、汎用的な Web 風の名前を無理に付けないでください。

回避策 API を避ける

  • 呼び出し側が実際に考慮する必要がある場合を除き、一時的なパッチ、プラットフォームのバグ、依存関係の癖、古くなった実装の詳細を、公開 API の形に持ち込まないでください。
  • 根本原因を修正するか、公式の拡張ポイントを使用するか、安定したドメイン API の背後に互換性コードを隠すことを優先してください。
  • 回避策が避けられない場合は、可能な限り対象を絞り、内部に留めてください。公開が必要な場合は、存在する理由、関連する課題、削除できる時期を説明してください。

TypeScript ファサード

  • 公開 Nitro HybridObjects を JS/TS ファサードでラップするのは避けてください。呼び出し側が命令型 Nitro API を使用する想定の場合は、.nitro.ts の仕様とネイティブ実装で定義されているとおりに、1 対 1 でエクスポートしてください。引数の事前解析、文字列や enum の変換、隠れたデフォルト値の注入、エラーやライフサイクルの動作変更を目的としてラップしてはいけません。
  • JS/TS ラッパーは、React フック、React コンポーネント、使いやすい UI の組み立てなど、意図的により高レベルな API にする場合や、Nitro HybridObject が内部実装の詳細であり、ユーザー向けのメンタルモデルと一致しない場合に限って使用してください。
  • JS ファサードが失敗時の動作、デフォルト値、ライフサイクル、所有権、または受け付ける値の形式を変更する場合は、ファサードを意図的に製品 API とし、HybridObject を内部実装とするのでなければ、そのルールを Nitro 仕様/ネイティブ実装に移してください。
  • 現実的なセットアップ、クリーンアップ、機能が利用できない場合の動作、無効な入力を示す例を優先してください。

ドキュメントの契約

  • エクスポートされた TypeScript は製品ドキュメントとして扱ってください。インポートするユーザー向けの、すべてのエクスポート宣言に JSDoc を追加してください。インターフェース、型エイリアス、文字列リテラルのユニオン、実行時列挙型、クラス、HybridObjects、オプションオブジェクト、イベントオブジェクト、フック、コンポーネント、公開定数が含まれます。
  • これらのコメントは TypeDoc などの生成ツールを通じて API ドキュメントになることを前提としてください。コード内だけの注釈ではなく、相互に参照できるリファレンスドキュメントとして記述してください。
  • すべての JSDoc の {@linkcode ...} または @see の参照先は、記述したファイル内で解決できる必要があります。必要に応じて、ドキュメント内でのみ参照する型専用のシンボルをインポートするか、すでにスコープ内にあるローカルシンボルにリンクしてください。たまたま tsc が通るからといって、エディターやドキュメント生成ツールの警告を残さないでください。
  • JSDoc を付けるためだけに、コールバックの型エイリアスをエクスポートしないでください。単純なコールバックパラメーターはインラインで記述し、それを受け取るメソッドまたはパラメーターをドキュメント化してください。コールバック型が意図的に公開 API の一部である場合に限り、その型をエクスポートしてドキュメント化してください。
  • 型では表現できない意味、ライフサイクル、プラットフォームごとの動作、デフォルト値、パフォーマンスコストには JSDoc を使用してください。
  • JSDoc はユーザー向けに記述してください。呼び出し元が API を正しく使用するために知る必要がない限り、ネイティブクラス名、フレームワークの実装詳細、現在のプラットフォームの制限には触れないでください。
  • API が JavaScript であるという事実ではなく、そのドメイン上の意味を説明する JSDoc を記述してください。「JavaScript 向けに正規化されています」のような内容のないコメントは避け、「バーコードの形式を表します」や「スキャンしたテキスト値のコンテンツタイプを表します」のように、具体的な意味を記述してください。
  • オプション、機能、結果、イベント、HybridObjects 上のプロパティを含む、すべての公開プロパティをドキュメント化してください。明らかなフィールドには短いコメントで構いませんが、デフォルト値、単位、利用可能性、失敗時の動作、関連オプションとの相互作用は、そのプロパティの説明に記載してください。
  • 型レベルの JSDoc はすべて、{@linkcode ...} または @see を使って、実際に関連する API と結び付けてください。たとえば、「{@linkcode Barcode} の形式を表します。」に加えて「@see {@linkcode Barcode.format}」を記載するなど、その型を公開するプロパティ、メソッド、ファクトリー、または結果にリンクしてください。このルールを満たすためだけに、存在しない参照先を作ったり、内容のないリンクを追加したりしないでください。
  • 基底インターフェースや抽象概念では、呼び出し元がその値をどのように受け取るかを説明し、パッケージからエクスポートされる具体的な型にリンクしてください。たとえば、「{@linkcode DataScanner} がスキャンした値を表します。具体的な値には {@linkcode ScannedTextValue} と {@linkcode ScannedBarcodeValue} があります。」のように記述します。
  • 現在の OS やプラットフォームを前提にしないよう漠然と警告するのではなく、関連するメソッド、設定オブジェクト、機能チェックを示すために {@linkcode ...} または @see を使用してください。
  • 生成された API リファレンス内をユーザーが移動できるよう、十分に密度の高いリンクを記載してください。ファクトリーからは返されるオブジェクトへ、オプションからはそれを使用するメソッドへ、結果オブジェクトからはそれを生成するメソッドへ、イベントからはリスナー登録メソッドへ、クリーンアップ/破棄の説明からはリソースを作成する API へリンクしてください。
  • 動作を明確にするために、@default、@throws、@platform、@example、@see、@discussion を使用してください。関連 API には {@linkcode ...} でリンクしてください。
  • リソースの所有権とクリーンアップを明示的にドキュメント化してください。返されたオブジェクトを破棄する必要がある場合は、いつ、なぜ破棄するのかを記載してください。

コメント