Code Review
# 前端 Code Review 评审规范
# 一、哪些情况必须进行 Code Review
1、核心 UI 组件代码:涉及到核心用户界面和交互的代码必须进行 Code Review,因为这些代码直接影响用户体验的质量和稳定性。
2、团队新人:当新加入的团队成员或经验较少的前端开发人员提交代码时,及时进行 Code Review,帮助他们养成良好的代码习惯。
3、重构或组件优化后:进行大规模代码重构(如组件拆分、样式结构调整)或性能优化后,需进行 Code Review,确保没有引入新的问题并保证代码的维护性和可扩展性。
4、复杂 UI 交互逻辑:当实现复杂的交互效果、动画、或数据处理逻辑时,需进行 Code Review,以确保交互流畅且代码易于理解和维护。
5、安全相关代码:涉及到用户认证、表单提交、安全性检查(如防止 XSS 攻击、CSRF 等)的代码必须经过 Code Review,确保安全性。
# 二、何时发起 Code Review
最佳时机:当一个前端功能或 bug 修复完成之前,并通过开发人员的自测后,应及时发起 Code Review。在代码合并到主分支之前,完成 Code Review 是确保代码质量的重要环节。
如果遇到紧急情况,可以先提交修复代码,事后补充 Code Review,确保问题得以及时修正。
# 三、Code Review 的步骤
步骤一: 介绍功能需求,确保评审人员对前端交互目标有清晰了解;讲解技术方案,说明代码在前端架构中的影响,特别是公共组件、API 调用部分。
步骤二: 从全局出发,介绍模块和组件设计,重点关注各组件间的逻辑关系、数据流向以及对系统整体架构的影响。
步骤三: 讲解 UI 组件的设计,关注组件划分是否合理,是否遵循单一职责原则,确保组件可以复用且易于维护。
步骤四: 梳理 JavaScript 和 CSS 的逻辑,检查是否符合设计原则,代码是否易于理解,是否符合团队的风格指南。
步骤五: 逐行审查代码,关注 DOM 操作、API 请求、状态管理、样式实现、异常处理等,确保代码清晰且可读,同时注意性能和安全问题。
# 四、Code Review 重点内容
1、代码质量
代码风格与规范:确保代码遵循团队的编码规范(如 ESLint 规则、Prettier 格式),命名规范,缩进、注释等是否符合要求。
代码清晰度:代码是否简洁、清晰易读,函数、变量命名是否合理,避免过度嵌套和冗余代码。
单一职责原则:每个组件、函数是否只负责一个功能,避免过于复杂的业务逻辑。
代码复用:检查是否有重复代码,是否遵循 DRY(Don’t Repeat Yourself)原则,组件和逻辑是否能复用。
2、UI 和业务逻辑
功能正确性:确保实现的功能符合需求,前端交互逻辑、视图更新是否正确,UI 响应是否符合预期。
边界与异常处理:处理好边界情况和异常场景,例如:空数据、错误提示、表单校验等,确保系统在各种输入下都能稳健运行。
3、性能与优化
性能瓶颈:检查是否存在性能问题,例如:频繁的 DOM 操作、大量的事件监听、过多的 re-render 等。
优化策略:合理使用懒加载、虚拟滚动、节流防抖等技术,确保前端性能的高效运行。
4、CSS 规范和布局
样式规范:检查是否遵循 CSS/Sass 的命名规范(如 BEM 命名法),确保样式结构清晰明了,避免全局污染。
样式复用:确保使用了变量、混合、CSS 模块化等方法提高样式的复用性,避免重复代码。
响应式布局:确保页面在不同设备和分辨率下的布局合理,是否遵循移动优先的设计理念,充分利用媒体查询和自适应技术。
5、安全性
输入验证:确保用户输入(如表单提交)有适当的验证和过滤,防止 XSS、CSRF 等攻击。
敏感信息处理:确保前端不会暴露敏感信息(如 API 密钥、用户隐私数据),并通过 HTTPS 进行传输。
# 五、Code Review 后续跟进
问题修复跟踪:确保 Code Review 中提出的问题得到开发人员的及时修复和反馈。
记录共性问题:将 Code Review 中发现的常见问题或改进点记录下来,并在团队中分享,推动团队代码质量的持续提升。
# 目的
- 提高代码质量和可维护性、可读性等
- 通过交叉排查缺陷,查漏补缺, 发现一些潜在的问题点等
- 最佳实践, 能够更好更快的完成任务的方法
- 建立团队意识,知识分享, Review他人代码时, 其实也是一个学习的过程, 自己也会反思&总结,团队成员在相互督促与改进中共同成长
# 怎么做
提测前code review
主要关注一下几个方面:
- 项目架构规范
- 代码编写规范
- 代码逻辑、代码优化
- 业务需求
# 具体的Code Review实践
# 深层对象判空
// 深层对象
if (
store.getters &&
store.getters.userInfo &&
store.getters.userInfo.menus
) {}
// 可以使用 可选链进行优化
if (store?.getters?.userInfo?.menus) {}
# 复杂判断逻辑抽离成单独函数
// 复杂判断逻辑
function checkGameStatus() {
if (remaining === 0 ||
(remaining === 1 && remainingPlayers === 1) ||
remainingPlayers === 0) {
quitGame()
}
}
// 复杂判断逻辑抽离成单独函数,更方便阅读
function isGameOver() {
return (
remaining === 0 ||
(remaining === 1 && remainingPlayers === 1) ||
remainingPlayers === 0
);
}
function checkGameStatus() {
if (isGameOver()) {
quitGame();
}
}
# 异常逻辑前置,正常逻辑后置
// 判断逻辑不要嵌套太深
function checkStatus() {
if (isLogin()) {
if (isVip()) {
if (isDoubleCheck()) {
done();
} else {
throw new Error('不要重复点击');
}
} else {
throw new Error('不是会员');
}
} else {
throw new Error('未登录');
}
}
// 将判断逻辑的异常逻辑提前,将正常逻辑后置
function checkStatus() {
if (!isLogin()) {
throw new Error('未登录');
}
if (!isVip()) {
throw new Error('不是会员');
}
if (!isDoubleCheck()) {
throw new Error('不要重复点击');
}
done();
}
# 函数传参优化
// 形参有非常多个
const getMyInfo = (
name,
age,
gender,
address,
phone,
email,
) => {}
问题:
- 传实参的时候,不仅需要知道传入参数的个数,还得知道传入顺序
- 有些参数非必传,还要注意添加默认值,且编写的时候只能从形参的后面添加,很不方便
// 行参封装成对象,对象函数内部解构
const getMyInfo = (options) => {
const { name, age, gender, address, phone, email } = options;
// ...
}
getMyInfo(
{
name: '张三',
age: 18,
gender: '男',
address: '北京',
phone: '123456789',
email: '123456789@qq.com'
}
)
# 分支逻辑优化
什么是分支逻辑呢?
使用 if else、switch case ...,这些都是分支逻辑
// switch case
const statusMap = (status: string) => {
switch(status) {
case 'success':
return 'SuccessFully'
case 'fail':
return 'failed'
case 'danger'
return 'dangerous'
case 'info'
return 'information'
case 'text'
return 'texts'
default:
return status
}
}
// if else
const statusMap = (status: string) => {
if(status === 'success') return 'SuccessFully'
else if (status === 'fail') return 'failed'
else if (status === 'danger') return 'dangerous'
else if (status === 'info') return 'information'
else if (status === 'text') return 'texts'
else return status
}
这些处理逻辑,我们可以采用 映射代替分支逻辑
// 使用映射进行优化
const STATUS_MAP = {
'success': 'Successfull',
'fail': 'failed',
'warn': 'warning',
'danger': 'dangerous',
'info': 'information',
'text': 'texts'
}
return STATUS_MAP[status] ?? status
# 解耦,解耦,解耦
- 一定不要耦合,解耦,封装组件,在自己内部。单文件不要超过 500 行
- hooks 是一个具有 vue 生命周期的工具方法,里面不要有业务场景。目的是 hooks 纯粹。不要有业务串联。能用方法实现的不用 hooks。尽量用 vueuse,不要自己写 hooks。hooks 不要超过 100 行
- 不要使用集中式的状态管理,比如 vuex,pinia
- api.js 放到自己目录里面,不要放到全局集中管理
# 没有用的代码一定要删除,对代码负责
# 状态提升,数据闭环,公用才考虑提级,不画蛇添足,够用就好
# 弹窗示例
<DialogTrigger>
<template #content>
<MaterialDialog />
</template>
<button>查看准备材料</button>
</DialogTrigger>
import { DialogTrigger } from "@fdd/front-saas-admin-components";
源码
<template>
<div v-frag>
<slot></slot>
<el-dialog
v-if="dialogVisible"
v-bind="$attrs"
v-on="$listeners"
>
<slot name="content" :trigger="this" />
</el-dialog>
</div>
</template>
# 入口文件,要是index.vue或者index.js
# 减少数据转化,洗数据,哪里用哪里洗,不能放在上层业务
# 拆分组件合理(数据业务闭环)
# 接口要统一
例如:模板详情接口、批量创建模板详情接口,应该用一个
# 页面500行上限 文件不能无限写,设置上限max=500
# 动态组件VS v-if ,如果可以通过引入能让dom更加清晰的,建议用v-if
# 模块划分,company和org分开,图片收拢
# 当做组件拆分案例
# 补充readme(添加到模板里面)
- 业务功能说明
- 依赖,环境,启动步骤,端口,技术栈
- 路由(在哪个文件夹)
- 线上地址
- 发布(wenzhi名字尽量和gitlab一致,如果不一致,要记录名字)
- 常用开发账号 例子:https://gitlab.fabigbig.com/fadada-frontend/fdd-contract (opens new window)
- localstorage 命名问题
- localstorage存储的key比较多,大家没有统一的规范,容易冲突。建议加上命名空间,加上工程名字,例如:front-saas-account-pc-XX
- dialog公共组件
- 我们用官方的el-dialog ,一般大家都会把padding去掉,用css把header去掉。会出现大量的覆盖el-dialog的css代码.所以我们都用封装的组件,里面什么都没有,只有dialog弹窗,关闭都不要。
- 文件夹默认输出应该是index
- slot用法
传递slot的时候用
<template #name="params">
default默认不用
反例:
<div slot="name" >
# 项目组件名字首字母用大写,页面首字母小写,引用组件用驼峰,不要用“-”
反例: <min-header />
推荐写法: <MinHeader />
# 所有api里面定义的方法,要加上后缀名Api
例如getListApi, 调用这个接口的方法名字尽量保持一致,叫做getList,引用api的时候不要用 全部引入,要局部引入,利于Tree Shaking,而且便于排查错误,跳转到对应方法。反例: import * as Api from './api' 推荐写法 import { getList } from './api'
# 行内样式不要超过两项,超过两项要用class
# css样式优先级
https://developer.mozilla.org/zh-CN/docs/Web/CSS/Specificity (opens new window)
# if里面的条件判断不能超过两个,比如v-if="A && B && C"
# Fragment组件常用于根节点,不要在组件内部出现
# js里面的if 嵌套不能超过三层,else if不能超过三个
# 推荐使用computed代替在dom里面写大量的js代码
# Dom里面l不要写三元运算符的判断,要写成显性的if-else
# 工程命名规范
front-saas-signtask-h5
工程采用4段式命名,以front-saas-account-pc为例:
- 第一段:表示服务分层,front表示前端
- 第二段:表示产品领域,saas表示公有云领域产品
- 第三段:表示具体的业务,比如signtask表示签署,bill表示费用第四段:表示具体的“端”,mini表示小程序,android/iOS/pc/h5表示各自的端
# onMounted、onBeforeMount钩子函数的书写位置,明确第一个函数的调用位置
# 尽量不要使用类(Class)对后端返回的数据进行二次转换
# 不要在补丁上摞补丁,该聚拢的逻辑聚拢
# 一个按钮的不同逻辑不应该通过数据去同一个按钮,而是根据不同的业务封装出不同按钮,职责分明
# 纯粹逻辑该提取的要提取为单独的方法
# ●与组件相关的逻辑尽量在组件内闭环,多余的操作不要向外暴露
# 利用AI
做Code Review
传统的做法是PR时查看,对于不合理的地方,打回并在PR中备注原因或优化方案
每隔一段时间,和组员开一个简短的CR分享会,把一些平时CR过程中遇到的问题做下总结
当然,不要直接指出是谁写出的代码有问题,毕竟这不是目的,分享会的目的是交流学习
人工CR需要很大的时间精力,与心智负担
随着 AI 的发展,我们可以借助一些 AI 来帮我们完成CR