关于 correctness-and-error-handling
此开发技能用于检查并修复指定区域或整个应用中的正确性与错误处理问题。它引导 Agent 检查数据流、界面状态、输入校验和清理逻辑,并报告需要人工决策的事项。
主要功能
- 01梳理异步数据流和已知缺陷
- 02添加错误边界及异步错误处理
- 03检查加载、错误和空数据界面
- 04校验外部数据及不安全访问
- 05添加副作用清理和边界保护
- 06报告尚未解决的问题
适用场景
- 01检查 React 和 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 中的正确性问题和缺失的错误处理(如果未提供参数,则检查整个应用)。按顺序完成以下每个步骤。每个步骤都会先查找问题,然后就地修复。只报告无法自动修复的问题。
步骤 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。每个 TODO 都必须解决或转换为安全的回退处理。
步骤 2 — 添加顶层错误边界
每个 Flows 应用都必须至少有一个 React Error Boundary 包裹主要内容,以便在渲染时发生意外异常时显示对用户友好的消息,而不是空白屏幕。
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>
编写更新后的 App.tsx,确保其中包含 ErrorBoundary。不要只提出建议 — 请直接修改。
步骤 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} />;
阅读每个文件,进行修改,然后写回文件。
步骤 4 — 为组件添加缺失的加载、错误和空状态
对于每个会获取数据的组件,都必须包含三种不同的 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 fallback:
const id = searchParams.get("id") ?? defaultId;
- 直接使用
localStorage.getItem(key)— 添加类型守卫和 fallback:
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 fallback:
// 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() |
异步 effect 的参考模式:
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]);
读取每个 effect,添加缺少的清理函数,并写入文件。
步骤 8 — 添加边界情况防护
对于每项功能,检查并添加防护:
- 空数据:如果未处理零项列表,则在渲染前添加空状态检查。
- 单项:如果列表渲染在只有一个条目时存在 off-by-one 错误,则修复逻辑。
- 最大数据量 / 分页:如果 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 — 报告剩余发现
生成一份结构化报告,涵盖:
- 每个步骤修复了什么 — 总结所做的更改(编辑的文件、修复的模式)。
- 剩余问题 — 仅列出无法自动修复的问题(例如,需要架构调整、需要产品决策,或超出此技能的范围)。
| 严重性 | 文件 | 行 | 问题 | 状态 |
|---|---|---|---|---|
| 高 | src/hooks/useAssets.ts | 34 | 未处理的 Promise 拒绝 | 已修复 — 已用 try/catch 包裹 |
| 中 | src/components/AssetList.tsx | 12 | 没有空状态 | 已修复 — 已添加空状态检查 |
| 中 | src/auth/flow.ts | 45 | Auth 错误处理需要产品决策 | 未修复 — 需要团队提供意见 |
如果某一步没有发现问题,请在该步骤注明“未发现问题”。不要默默跳过任何步骤。
已完成
按严重程度总结已修复的问题。标出任何仍可能导致数据丢失、生产环境崩溃或 UI 状态误导的 HIGH 问题,并将它们列在最前面以便立即处理。