可读性
代码是否容易看懂、容易顺着读下去、容易快速确认主流程。
命名
业务含义应被显式表达,避免依赖上下文猜测、个人习惯或隐含约定。
魔法值
是否存在缺少业务语义的数值或字符串。
- 魔法数值:直接写入代码但缺少解释的数字
- 魔法字符串:直接写入代码但缺少业务语义的字符串
- 建议:优先通过常量、枚举、配置项等方式具名表达
命名
命名是否清晰、准确、一致。
常量
- 谨慎使用下划线:前端业务代码中通常避免使用下划线
_,下划线通常用于常量中的语义分隔,如MAX_VALUE - 常量命名:普通常量建议使用全大写加下划线,即
UPPER_SNAKE_CASE
变量名
看名字能否看出来源、用途、状态。
- 前端最容易起空的词:
data、list、item、info、value、obj、arr、temp、flag、status、type、result、process、manager、util、common、current、next、active - 什么时候这些词可以用:作用域较小、上下文已足够明确时可以接受;一旦变量跨函数、跨模块或承载明确业务语义,应优先使用更具体的业务命名
函数名
看名字能否看出动作和对象。
- 常用动作前缀
- 提交:
submit、save、send、create - 获取:
get、fetch、load、query - 更新:
update、set、reset、patch - 删除:
remove、delete、clear - 校验:
validate、check - 判断:
is、has、can、should - 转换:
build、map、transform、normalize、format、parse、serialize - 交互:
handle、open、close、toggle、show、hide、select - 同步:
sync、persist、track、emit、notify
- 提交:
- 什么时候可以省略对象:在表单、表格、弹窗、鼠标交互等事件语境很强的场景中,
handleSubmit、handleClick、handleChange这类命名通常可以接受;若函数承担明确业务动作,仍应优先补充业务对象
组件名
看名字能否看出职责和交互形态。
- 常见交互形态:
Page、List、Table、Form、Modal、Dialog、Drawer、Panel、Card、Tag、Badge、Selector、Picker、Detail、Tabs、Toolbar、Filter、Menu、Item、Row、EmptyState、Skeleton - 组件命名建议:组件名通常使用名词或名词短语,推荐优先体现业务对象和交互形态,如
UserTable、OrderForm、FilterPanel
布尔值
看名字是否像一句可判断真假的话。
- 普通布尔变量:常用
is、has、can、should - 可见性、进行中的状态:如
isVisible、isRunning - 属性或 props:可直接使用状态词,如
disabled、selectable、hovered、clickable - 其他常见表达:组件附加能力如
withTab;启用能力如enableFilter;允许或限制如allowXXX、noXXX
逻辑复杂度
本质上通常是职责膨胀:一个函数或组件承担了过多判断、状态、分支和依赖,导致主流程不清晰、阅读路径变长、修改风险升高。
- 核心判断:是否违反单一职责原则
- 常见表现:分支过多、嵌套过深、职责混杂、重复判断、状态组合过多
- 直接结果:代码未必很长,但主流程难以一眼读懂,改一个条件容易影响别的路径
圈复杂度
圈复杂度(Cyclomatic Complexity)是衡量判定结构复杂度的指标,本质上反映代码中独立执行路径的数量。
- 可理解方式
- 独立执行路径越多,理解成本越高
- 覆盖所有可能情况所需的最少测试用例通常也越多
- 常见来源
if / else if / elseswitch / case- 三元表达式
- 逻辑短路分支
- 循环中的条件判断
- 需要注意:圈复杂度只是“路径数量”的度量,不完全等于可读性;有些代码圈复杂度变化不大,但认知复杂度和阅读成本已经明显下降
需要重点识别的问题
分支过多
条件路径是否过多,主流程是否被拆碎。
- 典型表现
- 连续
if / else if / else - 大段
switch / case - 同一业务条件在不同位置反复判断
- 多个布尔值交叉组合决定不同行为
- 连续
- 风险
- 分支一多,遗漏路径、冲突路径、无效路径的概率会明显上升
- 测试覆盖难度同步上升
嵌套过深
层级是否过深,理解路径是否过长。
- 典型表现
if套if- 回调里再包回调
- 组件里套很多仅为转发逻辑的中间组件
- 一个 Hook 内部串很多 Hook 和派生逻辑
- 风险
- 阅读者需要同时记住更多上下文
- 主流程被包裹在多层条件里,不容易快速定位
职责混杂
一个函数或组件是否同时承担了过多不同责任。
- 典型表现
- 既做数据转换,又做权限判断,还做 UI 渲染
- 既决定业务状态,又顺手发请求、打点、弹 toast
- 同时处理正常流程、异常流程、边界兜底、埋点和缓存
- 风险
- 逻辑膨胀后,状态会越来越多,分支会进一步放大
- 小改动也容易牵动无关逻辑
常见优化手法
提前返回
提前返回不一定降低圈复杂度,但通常能降低嵌套深度,让主流程更清晰。
- 价值
- 先处理异常路径、边界路径
- 把主流程留在最外层
- 减少一层层
else
- 核心收益:不一定减少“路径数”,但能明显改善“阅读顺序”
用策略表替代 if / switch
把“分支选择”改成“按 key 查表”,把流程分叉转成配置分发。
if (status === 'idle') { ... }
else if (status === 'loading') { ... }
else if (status === 'error') { ... }
const handlerMap = {
idle: handleIdle,
loading: handleLoading,
error: handleError
}
handlerMap[status]?.()
- 适用场景
- 状态到行为的一一映射
- 类型到渲染器的映射
- 平级分支很多,但结构相似
- 注意
- 只有在“分支本质是查找映射”时才适合
- 如果每个分支本身逻辑差异极大,硬塞策略表反而可能增加跳转成本
合并条件 / 去重复判断
同一个前置条件不要在多个位置重复判断,能合并就合并,能收敛就收敛。
if (a) {
doSomething()
}
if (a && b) {
doAnother()
}
if (a) {
doSomething()
if (b) {
doAnother()
}
}
- 收益
- 减少重复阅读同一条件
- 让条件归属关系更清晰
- 降低后续修改条件时漏改的概率
函数拆分
不是为了拆而拆,而是把复杂判断、复杂规则、复杂分支“命名”掉,让主流程留在当前层。
- 好的拆分
isRetryAllowed(record)resolveDisplayStatus(record)buildExportPayload(config)
- 价值
- 主流程复杂度下降
- 复杂判断被具名后,语义更清晰
- 细节可以被局部封装,但主流程仍然容易读
- 注意
- 不要拆成大量只有一两行、却没有新语义的小函数
- 如果拆分后必须来回跳多个文件才能看懂,反而会增加理解成本
评审时可追问的问题
- “主流程能否一眼看出来?”
- “这里的复杂度来自必要业务,还是职责混杂?”
- “这个分支是否能前置 return、合并判断或改为查表?”
- “这个拆分是在降低复杂度,还是在转移复杂度?”
- “修改其中一个条件时,会不会影响到别的隐含路径?”