关于 control-flow
control-flow 是帮助开发者重构复杂控制流的编程技能,强调按自然的判断顺序组织代码。它以提前返回、互斥条件和清晰命名减少嵌套,并将失败处理与正常路径分开。
主要功能
- 01优先处理可提前返回的情况
- 02用互斥条件描述当前状态
- 03将主要操作集中在正常路径
- 04使用自然语言风格的布尔变量名
- 05把错误边界与后续逻辑分开
适用场景
- 01简化含重复逻辑的嵌套条件
- 02整理带多个失败点的处理器
- 03改写仅有单一风险调用的 try-catch
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ epicenterhq/ epicenter --skill control-flow安装后在 Claude Code 中输入/control-flow确认已加载。
详细介绍
易读的控制流
重构复杂控制流时,应贴合人类自然的推理模式:
相关技能:有关分支折叠和调用方证据,请参阅重构方法。
核心模式
- 先提出人的问题:“能用已有的东西吗?”→ 在符合预期的路径上提前返回
- 评估情况:“我目前处于什么状态?需要做什么?”→ 条件清晰且互斥
- 采取行动:“获取我需要的东西”→ 在末尾集中处理逻辑
- 使用自然语言变量:
isUsingNavigator、isUsingLocalTranscription、needsOldFileCleanup:名称读起来就像人的思路 - 避免人为构造:不要使用与人实际思考问题的方式不符的嵌套条件
将这种结构:包含重复逻辑的嵌套条件 改为这种结构:贴合人类决策过程的线性流程
示例:使用自然语言变量提前返回
// From apps/whispering/src/routes/(app)/_layout-utils/check-ffmpeg.ts
export async function checkFfmpegRecordingMethodCompatibility() {
if (!window.__TAURI_INTERNALS__) return;
// Only check if FFmpeg recording method is selected
if (settings.value['recording.method'] !== 'ffmpeg') return;
const { data: ffmpegInstalled } =
await rpc.ffmpeg.checkFfmpegInstalled.ensure();
if (ffmpegInstalled) return; // FFmpeg is installed, all good
// FFmpeg recording method selected but not installed
toast.warning('FFmpeg Required for FFmpeg Recording Method', {
// ... toast content
});
}
示例:自然语言布尔值
// From apps/whispering/src/routes/(app)/_layout-utils/check-ffmpeg.ts
const isUsingNavigator = settings.value['recording.method'] === 'navigator';
const isUsingLocalTranscription =
settings.value['transcription.selectedTranscriptionService'] ===
'whispercpp' ||
settings.value['transcription.selectedTranscriptionService'] === 'parakeet';
return isUsingNavigator && isUsingLocalTranscription && !isFFmpegInstalled;
示例:带注释的清理检查
// From packages/epicenter/src/indexes/markdown/markdown-index.ts
/**
* This is checking if there's an old filename AND if it's different
* from the new one. It's essentially checking: "has the filename
* changed?" and "do we need to clean up the old file?"
*/
const needsOldFileCleanup = oldFilename && oldFilename !== filename;
if (needsOldFileCleanup) {
const oldFilePath = path.join(tableConfig.directory, oldFilename);
await deleteMarkdownFile({ filePath: oldFilePath });
tracking[table.name]!.deleteByFilename({ filename: oldFilename });
}
示例:将 try-catch 线性化为守卫条件 + 符合预期的路径
try-catch 块会形成嵌套的双分支结构:try 主体和 catch 主体。如果 try 中只有一个调用可能实际抛出异常,就可以将 try-catch 替换为带守卫条件的调用 + 提前返回,让代码自上而下地阅读。
之前(嵌套结构,混合抛出与返回):
async ({ body, status }) => {
const adapter = createAdapter(body.provider);
try {
const stream = chat({ adapter, messages: body.messages });
return toServerSentEventsResponse(stream);
} catch (error) {
if (error instanceof Error && error.name === 'AbortError') {
throw status(499, 'Client closed request');
}
const message = error instanceof Error ? error.message : 'Unknown error';
throw status('Bad Gateway', `Provider error: ${message}`);
}
};
之后(线性结构,返回方式一致):
async ({ body, status }) => {
const adapter = createAdapter(body.provider);
const { data: stream, error: chatError } = trySync({
try: () => chat({ adapter, messages: body.messages }),
catch: (e) => Err(e instanceof Error ? e : new Error(String(e))),
});
if (chatError) {
if (chatError.name === 'AbortError') {
return status(499, 'Client closed request');
}
return status('Bad Gateway', `Provider error: ${chatError.message}`);
}
return toServerSentEventsResponse(stream);
};
这种转换遵循相同的人类推理模式:
- 尝试执行有风险的操作:只对可能失败的部分使用封装
- 检查是否失败:使用适当的错误提前返回
- 继续执行符合预期的路径:函数的其余部分默认操作成功
这样可以消除嵌套结构,统一 return 与 throw 的使用方式,并将错误边界与后续安全代码分开。
示例:处理器中的顺序守卫条件
处理器有多个可能失败的地方时,每个守卫条件都遵循相同的模式:执行操作、检查结果,然后提前返回或继续。
async ({ body, status }) => {
// Guard 1: validate input
if (!isSupportedProvider(body.provider)) {
return status('Bad Request', `Unsupported provider: ${body.provider}`);
}
// Guard 2: resolve dependency
const apiKey = resolveApiKey(body.provider, headers['x-api-key']);
if (!apiKey) {
return status('Unauthorized', 'Missing API key');
}
// Guard 3: risky operation
const { data: stream, error } = trySync({
try: () => chat({ adapter: createAdapter(body.provider, apiKey) }),
catch: (e) => Err(e instanceof Error ? e : new Error(String(e))),
});
if (error) return status('Bad Gateway', error.message);
// Happy path: all guards passed
return toServerSentEventsResponse(stream);
};
每个守卫条件的形式都相同:检查 → 失败时提前返回。符合预期的路径在底部逐步汇集。自上而下阅读时,你会先看到函数可能失败的所有方式,然后才看到成功情形。
评论
支持 Markdown