correctness-and-error-handling について
指定範囲またはアプリ全体の正確性とエラー処理を点検し、修正する開発スキルです。エージェントにデータフロー、UI状態、入力検証、クリーンアップを確認させ、手動判断が必要な問題を報告します。
主な機能
- 01非同期データフローと既知の不具合の把握
- 02エラーバウンダリーと非同期エラー処理の追加
- 03読み込み・エラー・空状態のUI確認
- 04外部データと危険なアクセスの検証
- 05エフェクトのクリーンアップと境界条件の保護
- 06未解決事項の報告
活用シーン
- 01React・TypeScriptアプリの信頼性確認
- 02データ取得コンポーネントへの失敗状態の追加
- 03外部入力の解析と配列アクセスの確認
- 04修正内容の構造化レポート作成
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ cognitedata/ builder-skills --skill correctness-and-error-handlingインストール後、Claude Code で/correctness-and-error-handlingを入力して読み込まれたか確認します。
詳細
正確性とエラーハンドリングの修正
$ARGUMENTS(引数が指定されていない場合はアプリ全体)における正確性の問題と不足しているエラーハンドリングを見つけて修正します。以下のすべての手順を実施してください。各手順では問題を探し、その場で修正します。自動修正できない問題のみ報告してください。
Step 1 — データフローを把握し、既知の不具合を修正する
確認を始める前に、以下のファイルを読んでください。
src/main.tsx/src/App.tsx— トップレベルのエラーバウンダリーと認証フロー**/hooks/*.ts、**/contexts/*.tsxに一致するすべてのファイル — 共有される非同期状態**/api/*.ts、**/services/*.tsに一致するすべてのファイル — CDF SDK の呼び出し箇所
非同期データソースごとに、以下を確認してください。
- リクエストに失敗した場合(ネットワークエラー、CDF の 403、タイムアウト)はどうなりますか?
- 読み込み中、UI には何が表示されますか?
- 結果が空の場合、UI には何が表示されますか?
重要な経路にある既知の不具合を見つけて修正する
# Find TODO/FIXME/HACK in critical code paths (not test files)
grep -rn --include="*.ts" --include="*.tsx" -E "(TODO|FIXME|HACK|XXX):" src/ | grep -v ".test." | grep -v ".spec."
# Find "fix" or "broken" or "workaround" markers
grep -rn --include="*.ts" --include="*.tsx" -i -E "(TODO.*fix|workaround|broken|known.?bug|temporary.?hack)" src/
重要な経路(データ取得、レンダリング、認証、ナビゲーション)で該当箇所が見つかった場合は、次の手順を実行してください。
- 周辺のコードを読んで、未完成または壊れている動作を把握します。
- 根本原因を修正します — 不足しているロジックを実装する、壊れた動作を正す、または適切なエラーハンドリングを追加します。
- 修正にこのスキルの範囲を超える大幅なアーキテクチャ変更が必要な場合は、TODO を安全な失敗時の動作に置き換えます:適切なエラーハンドリング、妥当なフォールバック値、または機能が制限されていることを説明する明示的なユーザー向けメッセージを用意します。
- 修正後は TODO/FIXME/HACK コメントを削除します。コード自体で意図が分かるようにしてください。
重要な経路に TODO を残さないでください。すべて解決するか、安全なフォールバックに置き換える必要があります。
Step 2 — トップレベルのエラーバウンダリーを追加する
すべての Flows アプリには、メインコンテンツをラップする React Error Boundary が少なくとも 1 つ必要です。これにより、予期しないレンダリング時の例外が発生しても、画面が真っ白になる代わりに、ユーザーフレンドリーなメッセージが表示されます。
grep -rn --include="*.tsx" --include="*.ts" -E "ErrorBoundary|componentDidCatch|getDerivedStateFromError" src/
エラーバウンダリーが存在しない場合は、ErrorFallback コンポーネントを作成し、App.tsx に ErrorBoundary のラッパーを追加してください。react-error-boundary がインストールされていない場合はインストールします。
pnpm add react-error-boundary
次に、App.tsx に追加してください。
import { ErrorBoundary } from "react-error-boundary";
function ErrorFallback({ error }: { error: Error }) {
return (
<div role="alert" className="p-8 text-center">
<p className="text-lg font-semibold">Something went wrong</p>
<pre className="mt-2 text-sm text-muted-foreground">{error.message}</pre>
</div>
);
}
// Wrap the main content:
<ErrorBoundary FallbackComponent={ErrorFallback}>
<MainContent />
</ErrorBoundary>
ErrorBoundary を追加した状態で App.tsx を更新してください。提案するだけでなく、実際に編集してください。
Step 3 — 未処理の非同期関数を try/catch で囲む
エラーハンドリングがない async 関数と Promise チェーンをすべて探します。
# Find async functions
grep -rn --include="*.ts" --include="*.tsx" -E "async\s+function|async\s+\(" src/
# Find .then() without .catch()
grep -rn --include="*.ts" --include="*.tsx" -E "\.then\(" src/ | grep -v "\.catch\("
それぞれ修正してください。
- try/catch がない
async関数は、関数本体を try/catch で囲みます。コンテキスト情報とともにエラーをログに記録し、呼び出し元やクエリレイヤーで処理できるよう再スローします。
async function fetchAssets(sdk: CogniteClient) {
try {
const result = await sdk.assets.list({ limit: 100 });
return result.items;
} catch (error) {
console.error("Failed to fetch assets:", error);
throw error;
}
}
.catch()がない.then()には、チェーンに.catch()を追加します。
somePromise.then(handleResult).catch((error) => {
console.error("Operation failed:", error);
});
isErrorの処理がない TanStack Query の利用箇所(useQuery/useMutation)では、コンポーネントに エラーチェックとエラー UI を追加します。
const { data, isLoading, isError, error } = useQuery({
queryKey: ["assets"],
queryFn: () => fetchAssets(sdk),
});
if (isError) return <ErrorMessage error={error} />;
各ファイルを読んで編集し、変更内容を書き戻してください。
Step 4 — コンポーネントに不足している読み込み中、エラー、空の状態を追加する
データを取得する各コンポーネントには、明確に異なる 3 つの UI 状態が必要です。
| 状態 | 必須の UI |
|---|---|
| 読み込み中 | スピナー、スケルトン、または読み込みインジケーター |
| エラー | ユーザーが理解できるメッセージ(生のエラーオブジェクトや空白ではないもの) |
| 空 | 「結果がありません」/「ここにはまだ何もありません」というメッセージ(空のリストではないもの) |
読み込み状態を確認せずにデータを表示しているコンポーネントを探します。
grep -rn --include="*.tsx" -E "\.(map|filter|find)\(" src/ | grep -v "isLoading\|isPending\|skeleton\|Skeleton"
該当箇所ごとにコンポーネントを読み、不足している状態をその場に追加してください。
- 読み込み中の状態がない場合 — データを表示する前に追加します。
if (isLoading) {
return <div className="flex items-center justify-center p-8"><Spinner /></div>;
}
- エラー状態がない場合 — 読み込みチェックの後に追加します。
if (isError) {
return (
<div role="alert" className="p-4 text-center text-destructive">
<p>Failed to load data. Please try again.</p>
</div>
);
}
- 空の状態がない場合 — エラーチェックの後、
.map()の前に追加します。
if (!data || data.length === 0) {
return (
<div className="p-8 text-center text-muted-foreground">
<p>No results found.</p>
</div>
);
}
既存のデータ表示より前に、チェックを正しい順序(読み込み中、次にエラー、最後に空)で挿入してください。修正した各ファイルを書き戻してください。
ステップ 5 — 外部データに型の絞り込みを追加する
外部データ(CDF のレスポンス、URL パラメーター、localStorage、JSON.parse)は、使用前に検証する必要があります。TypeScript の型だけでは、実行時の保証にはなりません。
# Find JSON.parse without validation
grep -rn --include="*.ts" --include="*.tsx" -E "JSON\.parse\(" src/
# Find localStorage reads
grep -rn --include="*.ts" --include="*.tsx" -E "localStorage\.(get|set)Item" src/
# Find useSearchParams usage
grep -rn --include="*.ts" --include="*.tsx" -E "useSearchParams|searchParams\.get" src/
それぞれ修正してください:
JSON.parse(x) as T— Zod の safeParse に置き換えます:
import { z } from "zod";
const MySchema = z.object({ /* fields */ });
const parseResult = MySchema.safeParse(JSON.parse(raw));
if (!parseResult.success) {
console.warn("Invalid stored data, using defaults:", parseResult.error);
return defaultValue;
}
const validated = parseResult.data;
searchParams.get("id")に null チェックがない場合 — nullish フォールバックを追加します:
const id = searchParams.get("id") ?? defaultId;
localStorage.getItem(key)をそのまま使用している場合 — 型ガードとフォールバックを追加します:
const raw = localStorage.getItem(key);
if (raw === null) return defaultValue;
try {
const parsed = JSON.parse(raw);
// validate parsed shape
return isValidShape(parsed) ? parsed : defaultValue;
} catch {
return defaultValue;
}
外部データを as MyType でキャストしないでください。実行時の安全性が損なわれます。各ファイルを読み込み、修正して、書き戻してください。
ステップ 6 — null、undefined、安全でない配列アクセスを修正する
CDF から返されたデータ、または props で渡されたデータのプロパティにアクセスするすべてのコンポーネントを読み込みます。
grep -rn --include="*.tsx" --include="*.ts" -E "\w+\[0\]\." src/
見つかった安全でないパターンをそれぞれ修正してください:
- 安全でないネストされたプロパティアクセス — オプショナルチェーンと nullish coalescing を追加します:
// Before: asset.properties.space.Asset.name
// After:
const name = asset.properties?.["my-space"]?.["Asset"]?.name ?? "Unknown";
- undefined の可能性がある配列に対するガードのない
.map()— nullish フォールバックを追加します:
// Before: items.map(renderItem)
// After:
(items ?? []).map(renderItem)
- 安全でない配列インデックスアクセス — オプショナルチェーンとともに
.at()を使用します:
// Before: items[0].name
// After:
const first = items.at(0)?.name ?? "—";
該当箇所がある各ファイルを読み込み、修正を適用して、書き戻してください。
ステップ 7 — useEffect のクリーンアップ関数を追加する
サブスクリプション、タイマー、イベントリスナー、またはコンポーネントの終了後も実行される可能性がある非同期処理を設定するすべての useEffect は、クリーンアップ関数を返す必要があります。
grep -rn --include="*.tsx" --include="*.ts" -B 2 -A 15 "useEffect" src/
各 useEffect について、クリーンアップが必要か確認し、ない場合はクリーンアップ関数を追加してください:
| パターン | 追加する修正 |
|---|---|
addEventListener | return () => removeEventListener(...) を追加 |
setInterval / setTimeout | return () => clearInterval(id) / clearTimeout(id) を追加 |
| CDF ストリーミング / SSE | return () => stream.close() を追加 |
fetch / CDF SDK 呼び出し | AbortController を追加します:先頭に const controller = new AbortController() を置き、controller.signal を fetch に渡し、return () => controller.abort() を追加して、状態更新を if (!controller.signal.aborted) でガードします |
| Zustand / イベントエミッターのサブスクリプション | return () => unsubscribe() を追加 |
非同期エフェクトの参考パターン:
useEffect(() => {
const controller = new AbortController();
async function load() {
try {
const data = await fetchWithSignal(controller.signal);
if (!controller.signal.aborted) setState(data);
} catch (err) {
if (err instanceof Error && err.name !== "AbortError") {
setError(err);
}
}
}
load();
return () => controller.abort();
}, [id]);
各エフェクトを読み込み、不足しているクリーンアップを追加して、ファイルを書き戻してください。
ステップ 8 — エッジケースのガードを追加する
各機能について確認し、次のガードを追加してください:
- データが空の場合:0 件のリストが処理されていない場合は、レンダリング前に空状態のチェックを追加します。
- 単一アイテム:リストのレンダリングで、エントリーが 1 件の場合に境界値のバグが発生するなら、ロジックを修正します。
- 最大データ数 / ページネーション:CDF が
limit件すべてを返し、さらにページがある場合は、ページネーションがユーザーに伝わるようにします。不足していれば、「さらに読み込む」またはページネーションのインジケーターを追加します。 - 同時リクエスト / 古い結果:前のリクエストの完了前にユーザーが新しいリクエストを実行できる場合は、古いリクエストをキャンセルする仕組み(AbortController またはリクエスト ID チェック)を追加します。
- ネットワークオフライン:オフライン時にアプリが何も通知せず失敗する場合は、適切なエラーメッセージを追加します。
Atlas ツールの execute 関数については、すべての execute 関数の先頭に引数の検証を追加してください:
execute: async (args) => {
if (!args.assetId || typeof args.assetId !== "string") {
return { output: "Missing or invalid assetId", details: null };
}
// ... safe to proceed
}
execute 関数を検索し、それぞれを読み込み、検証を追加して、ファイルを書き戻してください。
ステップ 9 — 残りの指摘事項を報告する
次の内容を含む構造化レポートを作成してください:
- 各ステップで修正した内容 — 実施した変更(編集したファイル、修正したパターン)を要約します。
- 残りの問題 — 自動修正できなかった問題のみを記載します(例:アーキテクチャの変更が必要な問題、プロダクト上の判断が必要な問題、このスキルの範囲外の問題)。
| 重要度 | ファイル | 行 | 問題 | 状態 |
|---|---|---|---|---|
| HIGH | src/hooks/useAssets.ts | 34 | 未処理の Promise 拒否 | FIXED — try/catch でラップ済み |
| MEDIUM | src/components/AssetList.tsx | 12 | 空状態がない | FIXED — 空状態のチェックを追加済み |
| MEDIUM | src/auth/flow.ts | 45 | 認証エラーの処理にプロダクト上の判断が必要 | UNFIXED — チームの判断が必要 |
あるステップで問題が見つからなかった場合は、そのステップに「問題は見つかりませんでした」と記載してください。ステップを黙って省略しないでください。
完了
修正内容を重大度別にまとめてください。データ損失、本番環境でのクラッシュ、または誤解を招く UI の状態につながる可能性がある、未解決の HIGH の問題があれば、最優先で対応できるよう最初に列挙してください。