我用一个 Codex Skill 重构了真实项目里的复杂条件逻辑
最近我在维护一个 Tauri + React + Rust 的桌面项目时,集中做了一轮前端逻辑重构。这个项目不是 demo,而是一个已经有不少真实功能的应用:搜索、剪贴板、收藏、外部文件、设置页、会话记录、笔记、计划、Claude/Cursor 配置等页面都已经跑在生产代码里。
这类项目发展到一定阶段后,最容易出现的问题不是“代码不会跑”,而是“代码越来越难判断是否还对”。
尤其是在 React 页面里,经常会看到这样的代码:
const showList = !isMobile || !selectedFile;
const showDetail = !isMobile || !!selectedFile;
if (isMobile && trimmed && !remoteTokenInput.trim() && !gitRemote) {
showToast(t("settings.remoteTokenRequired"), true);
return;
}
const selectedCanDelete = selectedFile
? sourceType === "note" || sourceType === "clip" || (isDesktop && sourceType === "conversation")
: false;
这些代码单独看都不复杂,但当它们散落在十几个页面、几十个事件处理函数、多个 hook 和共享组件里时,维护成本会迅速上升。你每改一个规则,都要在脑子里搜索:还有哪里也写了同样的判断?移动端有没有另一套?删除后应该选中哪一个文件?这个按钮什么时候显示,什么时候禁用?
这次重构我使用了一个专门处理这类问题的 Codex Skill:
它的中文名可以理解为:语义逻辑建模 Skill。
核心原则很简单:
不要把业务逻辑藏在裸表达式里。先命名,再复用,再测试,再组合。
它解决的不是“代码风格”,而是“逻辑失控”
很多重构建议会停留在“拆组件”“抽 hook”“减少重复代码”。这些当然有用,但它们经常没有触及真正的问题。
真正的问题是:业务判断没有名字。
比如下面这类表达式:
isMobile && trimmed && !remoteTokenInput.trim() && !gitRemote
它到底是什么意思?
从语法上看,它只是几个布尔条件相与。从业务上看,它其实是在表达:
移动端首次配置远端仓库时,如果用户填写了远端地址但没有填写 access token,就不能保存。
如果这个判断只出现一次,也许还能接受。但一旦它和保存逻辑、提示文案、按钮状态、远端检测流程交织在一起,页面代码就会慢慢变成“布尔表达式迷宫”。
Semantic Logic Modeling Skill 的方法不是让你为了抽函数而抽函数,而是要求你按语义层级建模:
- 识别外部输入:API、数据库、配置、路由、表单、运行时状态。
- 抽取原子语义函数:每个函数只回答一个最小业务问题。
- 组合派生语义函数:把多个原子判断组合成中间含义。
- 命名复合场景函数:让分支读起来像业务语言。
- 输出受控结果函数:按钮状态、流程路由、提示文案、动作开关都由命名函数产生。
- 最后再接入 React、Controller、Hook 或其他框架代码。
换句话说,它不是“把 if 搬到另一个文件”,而是把条件逻辑变成一个可以被阅读、复用和测试的模型。
一次真实重构:从页面内判断,到 domain logic
这次我对 GitMemo Desktop 的剩余前端主要页面做了一轮集中重构。最终提交里,涉及 28 个前端源码文件,新增了一批按业务域划分的 logic 模块,例如:
desktop/src/components/domain/settings/settingsLogic.ts
desktop/src/components/domain/search/searchLogic.ts
desktop/src/components/domain/clipboard/clipboardLogic.ts
desktop/src/components/domain/conversations/conversationsLogic.ts
desktop/src/components/domain/editor-home/editorHomeLogic.ts
desktop/src/components/domain/external-files/externalFilesLogic.ts
desktop/src/components/domain/favorites/favoritesLogic.ts
desktop/src/components/domain/files/fileActionsLogic.ts
desktop/src/components/domain/files/fileWorkspaceLogic.ts
desktop/src/components/domain/notes/notesLogic.ts
desktop/src/components/domain/claude-config/claudeConfigLogic.ts
desktop/src/utils/platformLogic.ts
重构前,很多页面同时承担四件事:
- 调用 Tauri/Rust 命令读取数据;
- 管理 React state;
- 判断当前业务状态;
- 渲染 UI。
重构后,页面仍然负责状态连接和副作用,但大量“条件到结果”的逻辑被移动到纯函数里。
例如设置页里,远端仓库保存逻辑原本是直接写在事件处理函数中的:
const trimmed = remoteInput.trim();
const hasNewMobileToken = isMobile && !!remoteTokenInput.trim();
if (trimmed === gitRemote && !hasNewMobileToken) {
setEditingRemote(false);
setRemoteTokenInput("");
return;
}
if (isMobile && trimmed && !remoteTokenInput.trim() && !gitRemote) {
showToast(t("settings.remoteTokenRequired"), true);
return;
}
重构后,页面调用一个语义明确的决策函数:
const decision = getRemoteSaveDecision({
isMobile,
remoteInput,
remoteTokenInput,
currentRemote: gitRemote,
});
if (decision.kind === "unchanged") {
setEditingRemote(false);
setRemoteTokenInput("");
return;
}
if (decision.kind === "missing_mobile_token") {
showToast(t("settings.remoteTokenRequired"), true);
return;
}
对应的业务判断集中在 settingsLogic.ts:
export function getRemoteSaveDecision(input: {
isMobile: boolean;
remoteInput: string;
remoteTokenInput: string;
currentRemote: string;
}) {
const url = input.remoteInput.trim();
const token = input.remoteTokenInput.trim();
const hasNewMobileToken = input.isMobile && Boolean(token);
if (url === input.currentRemote && !hasNewMobileToken) {
return { kind: "unchanged", url, accessToken: null };
}
if (input.isMobile && url && !token && !input.currentRemote) {
return { kind: "missing_mobile_token", url, accessToken: null };
}
return {
kind: "save",
url,
accessToken: input.isMobile ? (token || null) : null,
};
}
这个变化看似只是多了一个函数,但维护方式完全不同了。
以前读代码时,你需要自己翻译布尔表达式。现在读函数名就能知道业务意图:这是一个“远端保存决策”。而且它的输出被限制为 unchanged、missing_mobile_token、save 三种受控结果,页面只需要消费结果。
React 页面最应该瘦身的,是 UI 派生状态
React 里很容易出现一种代码味道:JSX 看起来很正常,但每个 props 后面都藏着一段条件判断。
例如:
showList={!isMobile || !selectedFile}
showDetail={!isMobile || !!selectedFile}
disabled={disabled || !hasTarget || loading}
hidden={editing || !selectedCanDelete}
这些判断本质上都是 UI 派生状态。它们不属于 DOM,也不属于组件样式,而属于业务语义。
这次重构里,我把这类判断抽成了类似下面的函数:
export function getFileWorkspacePaneState(isMobile: boolean, selectedFile: string | null) {
const hasDetail = selectedFile !== null;
return {
hasDetail,
showList: !isMobile || !hasDetail,
showDetail: !isMobile || hasDetail,
};
}
页面里就变成:
const { showList, showDetail } = getFileWorkspacePaneState(isMobile, selectedFile);
这类函数非常小,但价值很大。因为它把“移动端是否显示列表/详情”的规则从页面细节里拿出来了。以后如果移动端交互规则改变,不需要挨个页面找 !isMobile || ...。
同样,收藏按钮和更多菜单也被抽出了共享动作逻辑:
export function shouldEnableFavoriteShortcut(input: {
active: boolean;
isMobile: boolean;
disabled?: boolean;
hasTarget: boolean;
loading: boolean;
}) {
return input.active && !input.isMobile && !input.disabled && input.hasTarget && !input.loading;
}
这比在组件里反复写:
if (!active || isMobile || disabled || !hasTarget || loading) return;
更容易维护,也更容易测试。
会话解析:最典型的“业务逻辑不该留在组件里”
这次重构里,我最有感触的是 ConversationsPage。
原本页面里直接包含 Markdown frontmatter 解析、消息段落解析、删除后选中下一条记录、列表计数展示等逻辑。它们都和 UI 有关,但并不是 UI 本身。
重构后,会话相关逻辑被收进:
desktop/src/components/domain/conversations/conversationsLogic.ts
例如:
export function parseConversationMarkdown(raw: string) {
const { meta, body } = parseConversationFrontmatter(raw);
const parsedBody = parseConversationBody(body);
return {
meta,
body,
intro: parsedBody.intro,
messages: parsedBody.messages,
};
}
页面只负责调用:
const parsed = parseConversationMarkdown(raw);
setCurrentMeta(parsed.meta);
setRawBody(parsed.body);
setIntroContent(parsed.intro);
setMessages(parsed.messages);
这个变化让页面代码的职责更接近“读取文件后更新状态”,而不是“读取文件、解析 Markdown、理解消息结构、再更新状态”。
这也是 Semantic Logic Modeling Skill 很强调的一点:框架代码应该消费语义逻辑,而不是承载语义逻辑。
这类 Skill 和普通提示词有什么区别?
很多人会问:这不就是让 AI “帮我重构一下”吗?
差别很大。
普通提示词容易得到局部优化:AI 可能会帮你拆几个函数、整理几个组件,但它不一定有稳定的方法论。今天这样拆,明天那样拆,最后还是容易变成另一种风格混乱。
Skill 的价值在于给 AI 一个稳定的操作框架。
Semantic Logic Modeling Skill 会持续约束 AI:
- 不要把业务逻辑藏在裸表达式里;
- 重复判断必须抽成共享纯函数;
- 原子判断、派生判断、复合场景、受控结果要分层;
- React 中先抽纯函数,再考虑 hook,最后组件只负责渲染;
- 高风险或多分支逻辑优先补测试。
这就像给 AI 配了一套“代码审美和重构准则”。你不是每次都重新解释你的偏好,而是让它默认按同一套模型工作。
适合使用它的场景
我个人认为,下面这些场景非常适合使用这个 Skill:
- 权限判断:角色、订阅、组织、资源归属共同决定是否可操作;
- 表单校验:字段之间存在依赖关系,不只是单字段 required;
- 功能开关:平台、版本、灰度、配置共同决定功能是否显示;
- 流程路由:不同状态进入不同下一步;
- 搜索/筛选:类型、平台、来源共同决定结果展示;
- UI 状态:按钮显隐、禁用、详情页/列表页切换;
- 删除/保存后行为:删除后选中谁,保存后刷新哪里;
- 价格/计费/额度:多个条件共同决定最终价格或可用额度。
一句话:只要你的代码里存在大量“条件决定结果”,它就有用。
安装和使用
推荐通过 Codex plugin marketplace 安装:
codex plugin marketplace add sahadev/semantic-logic-modeling-skill --ref main
codex plugin add semantic-logic-modeling-skill@semantic-logic-modeling
也可以直接查看仓库:
- GitHub:https://github.com/sahadev/semantic-logic-modeling-skill
- 官网:https://semantic-logic-modeling-skill.vercel.app/
使用时可以显式告诉 Codex:
使用 $semantic-logic-modeling 检查这段权限逻辑,并按语义逻辑建模方式重构。
或者:
用 semantic-logic-modeling skill 重构这个 React 页面里的复杂条件判断。
我的实际感受
这次重构之后,我最大的感受是:代码不是“更短了”这么简单,而是“更能解释自己了”。
以前很多判断需要靠人脑翻译:
isMobile && url && !token && !currentRemote
现在变成:
decision.kind === "missing_mobile_token"
以前页面里到处都是:
path.startsWith("clips/")
path.startsWith("plans/")
path.startsWith("notes/")
现在变成:
getSearchSourceTypeFromPath(path)
isPendingPathForFolder(path, "clips/")
getNotesTabForPath(path)
以前 JSX 中散落着按钮是否显示、菜单是否启用、移动端是否进入详情页等判断。现在这些规则有了名字,并且集中在对应的 domain logic 模块里。
这对长期维护非常关键。因为复杂项目最怕的不是一次写错,而是规则改了之后你不知道哪里还藏着同一个判断。
需要注意的边界
当然,它不是银弹。
使用这套方法时,我建议注意三点:
第一,不要为了抽象而抽象。只有当一个判断代表明确业务语义,或者会被复用、测试、演进时,才值得命名。
第二,事件处理和副作用不一定都要抽走。比如 Tauri 调用、toast、state 更新、刷新列表,这些可以留在页面里。Skill 更适合抽取“条件到结果”的部分。
第三,抽出来的逻辑最好逐步补测试。纯函数最大的好处就是容易测试,尤其是设置页、搜索来源、会话解析、权限判断这类多分支逻辑。
结语
AI 编程不应该只停留在“帮我写代码”。更重要的是,让 AI 按稳定的方法论帮助我们治理复杂度。
Semantic Logic Modeling Skill 给我的感觉,就是把“复杂业务逻辑应该怎么写”这件事,变成了一套可以反复执行的工程流程。
如果你的项目里已经出现大量这样的代码:
if (a && !b || c && d) { ... }
或者你的 React 页面越来越像“状态、请求、权限、UI 判断、平台差异”混在一起的集合体,那它值得一试。
项目地址:
https://github.com/sahadev/semantic-logic-modeling-skill
官网:
https://semantic-logic-modeling-skill.vercel.app/
它的核心价值可以用一句话概括:
先命名逻辑,再让它被复用。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)