HarmonyOS应用《民族图鉴》开发第44篇:工程化实践——代码规范、测试与构建

一、什么是工程化
写代码是"手艺活",但做项目是"工程活"。
一个人写代码,怎么写都行;十个人一起写,如果没有规范、没有流程、没有工具,很快就会乱成一锅粥——
- 代码风格千奇百怪,看别人代码像读天书
- 你改的功能我也在改,合并冲突不断
- 测试全靠手点,改一个 bug 引出三个新 bug
- 发布靠人打包,每次上线像抽奖
工程化就是用工具和流程,把"人"的不确定性降到最低,让项目可预测、可重复、可维护。
这一篇,我们从三个维度聊工程化:
- 代码规范:怎么写代码大家都能看懂
- 自动化测试:怎么保证改了代码不出 bug
- 构建与发布:怎么把代码变成可安装的包
二、代码规范
2.1 为什么需要规范
“为什么要管代码怎么写?能跑不就行了?”
当项目只有你一个人的时候,确实无所谓。但一旦有第二个人加入,问题就来了:
| 没有规范的痛点 | 有规范的好处 |
|---|---|
| 代码风格不统一,看着累 | 所有代码像一个人写的,读起来顺畅 |
| 新人要猜"这是什么意思" | 按规范写的代码,一目了然 |
| Code Review 纠结风格 | 工具自动检查,Review 只看逻辑 |
| 容易出低级错误 | 规范能避免很多低级 bug |
💡 代码是写给人看的,顺便给机器执行。
你写的代码,未来的你、你的同事、接手的人都要读。写代码的时间是一次性的,读代码的时间是无数次的。为读代码的人考虑,就是为未来的自己考虑。
2.2 规范的制定原则
制定规范不是"拍脑袋",也不是"越严越好"——好的规范有几个原则:
原则一:共识优先
规范是团队的契约,不是某个人的喜好。最好的规范是——团队所有人都认同、都愿意遵守的规范。
- 不要拿"最佳实践"压人,要讨论、要达成共识
- 可以有不同意见,但一旦定下来,所有人都要遵守
- 规范可以迭代,但要走流程,不能随便改
原则二:工具能查的,就不要靠人
人是不可靠的——再细心的人也会有疏忽。所以:
- 能用 Lint 工具检查的,就不要靠 Code Review 查
- 能自动格式化的,就不要纠结"应该怎么缩进"
- 工具检查不出来的,才需要人来 review
原则三:越简单越好
规范不是越多越好——太多了记不住,等于没有。
- 核心规范控制在 20 条以内,大家能记住
- 复杂的交给工具,不用人记
- 定期清理没用的规范,保持精简
原则四:与时俱进
规范不是一成不变的——技术在变,团队在变,规范也要跟着变。
- 定期回顾,哪些规范还适用,哪些已经过时了
- 新的最佳实践出来了,可以考虑加入
- 团队变大了,可能需要更严格的规范
2.3 命名规范
命名是最基础、也是最重要的规范。
文件命名
| 类型 | 规范 | 示例 |
|---|---|---|
| 页面 | 大驼峰 + Page | EthnicListPage.ets |
| 组件 | 大驼峰 | EthnicCard.ets |
| 服务 | 大驼峰 + Service | StorageService.ets |
| 模型 | 大驼峰 + Models | EthnicModels.ets |
| 工具 | 大驼峰 | HttpClient.ets |
| 常量 | 大驼峰 + Constants | RouteConstants.ets |
变量命名
// ✅ 好:语义化、小驼峰
const userName = '张三';
const isVisible = true;
const ethnicList: EthnicGroup[] = [];
// ❌ 不好
const yonghu = '张三'; // 拼音
const u = '张三'; // 缩写,看不懂
const data = []; // 太笼统,什么数据?
const isShow = true; // is/has/can 前缀不对
常量命名
// ✅ 好:全大写下划线,有模块前缀
const STORAGE_KEY_THEME = 'theme_mode';
const MAX_PAGE_SIZE = 100;
const THEME_COLOR_PRIMARY = '#409EFF';
// ❌ 不好
const maxSize = 100; // 不是大写
const THEME = '#409EFF'; // 没有模块前缀
函数命名
// ✅ 好:动词开头,清晰表达做什么
function getUserInfo() {} // 获取用户信息
function handleSubmit() {} // 处理提交
function formatDate() {} // 格式化日期
function calculateTotal() {} // 计算总数
// ❌ 不好
function userInfo() {} // 缺少动词
function data() {} // 太笼统
function doSomething() {} // 做了什么?
🎯 命名的黄金法则:如果一个名字需要注释才能解释清楚,那它就不是一个好名字。
2.4 代码格式
缩进
统一 4 个空格,不用 Tab。
为什么是 4 个?2 个太挤,8 个太宽,4 个刚刚好。这是行业共识。
换行与空行
// ✅ 好:逻辑块之间空一行
import { A } from './a';
import { B } from './b';
const CONSTANT = 'value';
class SomeClass {
private field1: string;
private field2: number;
method1() {
// ...
}
method2() {
// ...
}
}
不同逻辑块之间空一行,像段落一样,读起来有节奏感。
大括号风格
// ✅ 同一行风格(K&R)
if (condition) {
// ...
} else {
// ...
}
// ❌ 换行风格(Allman)
if (condition)
{
// ...
}
保持统一就行,没有绝对的对错。本项目用同一行风格,和大多数前端项目保持一致。
行长度
建议单行不超过 120 字符。太长了换行拆一下,不用左右拖滚动条。
2.4 注释规范
文件头部注释
/**
* 文件用途:XXX
* 创建时间:YYYY-MM-DD
* 兼容环境:macOS/Linux/Docker/TRAE 云端
* 版本:v1.0
* 风险提示:XXX
*/
函数注释
复杂函数加 JSDoc 注释:
/**
* 根据用户ID查询用户详细信息
* @param userId - 用户ID
* @param options - 查询选项
* @param options.includeRole - 是否包含角色信息
* @returns 用户详细信息对象
* @throws {NotFoundError} 用户不存在时抛出
*/
async function getUserDetail(userId: string, options?: { includeRole?: boolean }): Promise<UserDetail> {
// ...
}
什么该注释,什么不该
| 该注释 | 不该注释 |
|---|---|
| 业务逻辑的"为什么" | 显而易见的"是什么" |
| 复杂算法的思路 | i++ // i 自增 1 |
| 踩过的坑、注意事项 | 废话文学 |
| TODO / FIXME 标记 | 注释掉的代码(直接删了) |
💡 好的代码是自解释的。
代码本身就能说明"做了什么",注释应该说明"为什么这么做"。如果需要大量注释才能看懂代码,说明代码写得不够清楚——先想办法把代码写清楚,而不是加注释。
2.6 Lint 工具
人是不可靠的——再细心的人也会有疏忽。所以需要工具来自动检查。
Lint 工具做什么?
- 语法检查:有没有语法错误
- 风格检查:命名、缩进、换行符合规范吗
- 最佳实践:有没有不好的写法(比如 eval、var)
- 类型检查:TypeScript 类型对不对
HarmonyOS 项目有 code-linter.json5 配置文件,就是做这个的。
Lint 的工作流:
写代码 → 保存 → Lint 自动检查 → 有问题标红 → 修改 → 没问题
最好在提交代码前自动跑一遍 Lint,有问题就不让提交。
2.6.1 Lint 规则的三个级别
| 级别 | 含义 | 表现 |
|---|---|---|
| error | 错误,必须改 | 标红,编译不通过 |
| warning | 警告,建议改 | 标黄,编译能过但要注意 |
| off | 关闭,不检查 | 不提示 |
原则:能定为 error 的就不要定为 warning——warning 太多了大家就都忽略了。
2.6.2 「民族图鉴」项目的 Lint 规则
我们项目中重点检查的规则:
命名类:
- 变量、函数用小驼峰
- 类、组件用大驼峰
- 常量用全大写下划线
- 文件命名符合规范
格式类:
- 缩进 4 个空格
- 同一行大括号风格
- 行长度不超过 120 字符
- 末尾空行
最佳实践类:
- 禁止使用 eval
- 禁止使用 var,用 let/const
- 禁止 any 类型(实在不确定用 unknown)
- 禁止未使用的变量和导入
- 禁止空函数(至少要有注释说明为什么空)
代码质量类:
- 禁止循环中定义函数
- 禁止嵌套过深(超过 4 层提示)
- 函数过长提示(超过 50 行提示)
- 禁止重复代码
2.6.3 自定义规则
通用规则不够用?可以写自定义规则。
比如我们可以加一条"业务规则":
- 所有页面组件必须以 Page 结尾
- 所有服务类必须以 Service 结尾
- 所有 API 函数必须有错误处理
自定义规则的价值:把团队的"最佳实践"固化成工具规则,不用每次 Code Review 都提醒。
2.6.4 Lint 接入流程
Lint 不是"装了就完了",要融入开发流程:
- 编辑器集成:写代码时实时提示,有问题立刻改
- 保存自动修复:能自动修的(如格式),保存时自动修好
- 提交前检查:Git 提交前自动跑 Lint,有问题不让提交
- CI 检查:服务器上再跑一遍,防止本地绕过
💡 Lint 的价值:
Lint 不只是"找问题",更是"统一认知"。
当规则定下来了,所有人按同一套规则写代码——不用争论"应该怎么写",工具告诉你怎么写。
把时间花在更有价值的事情上,而不是纠结风格。
三、自动化测试
3.1 为什么需要测试
“测试不是测试同学的事吗?”
不是。自动化测试是开发的事——你写的代码,你最清楚它应该怎么工作,你最适合给它写测试。
没有测试的痛点:
- 改一个功能,不知道会不会影响别的功能
- 每次上线前,回归测试要测好几天
- 重构像赌博,不知道改完还能不能跑
- bug 要等到用户用了才发现
有测试的好处:
- 改完代码跑一遍测试,有问题立刻知道
- 重构有底气——跑一遍测试就知道改坏没
- 文档作用——测试用例就是最好的功能说明
- 质量有保障——上线前心里有数
3.2 测试金字塔
/\
/E2E\ 端到端测试:少量、关键路径
/------\
/集成测试\ 集成测试:适中
/----------\
/ 单元测试 \ 单元测试:大量、全面
/--------------\
测试不是越多越好,而是要有合理的分层:
| 层级 | 测试对象 | 数量 | 速度 | 维护成本 | 发现问题的能力 |
|---|---|---|---|---|---|
| 单元测试 | 单个函数/类/组件 | 多(~70%) | 快(ms级) | 低 | 单个模块的逻辑错误 |
| 集成测试 | 模块间协作 | 中(~20%) | 中(s级) | 中 | 模块间接口问题 |
| E2E 测试 | 完整用户流程 | 少(~10%) | 慢(min级) | 高 | 全链路问题、用户体验 |
原则:
- 底层的单元测试要多、要全——成本低、见效快
- 中层的集成测试要适中——重点测核心模块的协作
- 上层的 E2E 测试要精——只测最关键的用户路径
为什么叫"金字塔"?因为越往上,数量越少——倒过来就成了"冰淇淋蛋筒",那是反模式。
🎯 测试金字塔的本质:
用最低的成本,获得最高的测试覆盖率和最快的反馈速度。
单元测试跑得最快、最便宜,所以多写;E2E 跑得最慢、最贵,所以少写但要精。
3.3 单元测试基础
单元测试是最基础的——测试一个函数、一个类、一个组件,验证它的行为是否符合预期。
3.3.1 测试框架与断言
HarmonyOS 的单元测试框架是基于 Jest/ArkTS 测试体系的。
测试的基本结构:
// describe:一组相关的测试用例
describe('测试套件名称', () => {
// beforeAll:所有测试开始前执行一次
beforeAll(() => {
// 初始化
});
// beforeEach:每个测试开始前都执行
beforeEach(() => {
// 每个测试前的准备
});
// it / test:一个测试用例
it('测试用例描述', () => {
// 1. 准备数据(Arrange)
const input = 1;
// 2. 执行操作(Act)
const result = addOne(input);
// 3. 断言结果(Assert)
expect(result).toBe(2);
});
// afterEach:每个测试结束后都执行
afterEach(() => {
// 清理
});
// afterAll:所有测试结束后执行一次
afterAll(() => {
// 最终清理
});
});
AAA 模式:每个测试用例都遵循 Arrange-Act-Assert 三步。
3.3.2 常见断言
| 断言 | 说明 | 示例 |
|---|---|---|
toBe(value) |
严格相等(===) | expect(1+1).toBe(2) |
toEqual(value) |
深度相等(对象/数组) | expect(obj).toEqual({a:1}) |
toBeTruthy() |
真值 | expect('hello').toBeTruthy() |
toBeFalsy() |
假值 | expect(0).toBeFalsy() |
toContain(item) |
包含 | expect([1,2,3]).toContain(2) |
toHaveLength(n) |
长度 | expect(arr).toHaveLength(3) |
toThrow() |
抛出异常 | expect(() => fn()).toThrow() |
toBeGreaterThan(n) |
大于 | expect(10).toBeGreaterThan(5) |
3.3.3 Mock 与隔离
单元测试要"独立"——不能依赖数据库、网络、其他模块。
Mock 的作用:
- 把外部依赖替换成"假的"
- 可以控制依赖的返回值
- 可以验证依赖被调用的情况
Mock 示例:
// 伪代码:Mock 一个服务
class MockStorageService {
private data: Map<string, string> = new Map();
getString(key: string): string {
return this.data.get(key) || '';
}
saveString(key: string, value: string): void {
this.data.set(key, value);
}
}
// 测试时用 Mock 替换真实服务
it('should save theme correctly', () => {
const mockStorage = new MockStorageService();
const themeService = new ThemeService(mockStorage);
themeService.setTheme('dark');
expect(mockStorage.getString('theme_mode')).toBe('dark');
});
Mock 的三个层次:
- Fake:有完整实现,但不走真实流程(如内存数据库)
- Stub:只返回预设数据,没有实现逻辑
- Mock:不仅返回数据,还能验证被调用的情况
根据测试需要选择合适的层次。
3.4 测试的最佳实践
- 测试行为,不测试实现:测"结果对不对",不测"内部怎么实现的"
- 一个测试只测一件事:一个 it 只验证一个点,出了问题一眼就知道哪错了
- 测试要独立:每个测试都能单独跑,不依赖其他测试
- 测试要快:单元测试几秒内跑完,不然没人愿意跑
- 先写测试再写代码?(TDD):看情况,不是所有场景都适合
测试覆盖的场景
写测试时,这些场景要覆盖到:
- 正常流程(Happy Path):最常见的使用场景
- 边界值:最大值、最小值、0、空字符串、空数组
- 异常情况:错误输入、网络错误、超时
- 特殊情况:null、undefined、NaN、超大数值
💡 测试覆盖率不是越高越好:
100% 的覆盖率不代表没有 bug——它只能说明"所有代码都执行过了",不能说明"所有情况都考虑到了"。
关键路径覆盖率 100%,比整体覆盖率 80% 更有价值。
3.5 集成测试与 E2E 测试
单元测试是基础,但光有单元测试不够——模块拼在一起能不能工作?整个流程跑不跑得通?
这就需要集成测试和 E2E 测试。
集成测试
测"模块之间的协作"。
比如:
- StorageService + ThemeService 能不能正常工作?
- 数据从 Repository 到 Service 再到 ViewModel,流转对不对?
- 网络请求出错了,上层能不能正确处理?
集成测试的重点是接口和数据流,不是每个模块的内部逻辑。
E2E 测试(端到端测试)
测"完整的用户流程",从用户操作到最终结果。
比如「民族图鉴」的核心流程:
- 打开 App → 看到民族列表 → 点进详情 → 收藏 → 返回 → 查看收藏
- 搜索民族 → 查看结果 → 播放音频 → 切换主题
E2E 测试最接近真实用户场景,但也最慢、最脆弱。
E2E 测试的原则:
- 只测最核心的用户路径(比如 Top 5 用户场景)
- 不要测细枝末节(那是单元测试的事)
- 尽量稳定,减少因为环境问题导致的失败
3.6 本项目的测试
本项目的 test 目录下有一些测试文件:
entry/src/
├── ohosTest/ # 真机/模拟器测试(端到端、UI测试)
│ └── ets/test/
│ ├── Ability.test.ets
│ └── List.test.ets
└── test/ # 单元测试(本地运行,不需要设备)
├── ApiService.test.ets
├── CoreService.test.ets
├── List.test.ets
└── LocalUnit.test.ets
两种测试:
- 本地单元测试:在开发机上跑,不需要设备,快
- 设备测试:在真机/模拟器上跑,可以测 UI 和系统能力,慢
四、构建与发布
4.1 构建工具 Hvigor
HarmonyOS 的构建工具叫 Hvigor(对应 Web 领域的 Webpack/Vite)。
Hvigor 是华为自研的构建工具,专为 HarmonyOS 应用开发设计,支持 ArkTS 编译、资源处理、HAP 打包等全流程。
项目里的构建相关文件:
| 文件 | 作用 |
|---|---|
hvigorfile.ts |
构建脚本配置,定义构建任务 |
build-profile.json5 |
构建配置(签名、编译选项、产品形态) |
oh-package.json5 |
依赖管理(对应 package.json) |
hvigor/ |
构建工具本身的目录 |
oh_modules/ |
依赖安装目录(对应 node_modules) |
4.1.1 build-profile.json5 配置详解
这是最重要的构建配置文件,控制着编译的方方面面。
{
"app": {
"signingConfigs": [
{
"name": "debug",
"type": "HarmonyOS",
"material": {
"certpath": "debug.cer",
"storePassword": "******",
"keyAlias": "debugKey",
"keyPassword": "******",
"profile": "debug.p7b",
"storeFile": "debug.p12"
}
}
],
"products": [
{
"name": "default",
"signingConfig": "debug",
"compatibleSdkVersion": "5.0.0(12)",
"runtimeOS": "HarmonyOS"
}
],
"buildModeSet": [
{
"name": "debug"
},
{
"name": "release"
}
]
},
"modules": [
{
"name": "entry",
"srcPath": "./entry",
"targets": [
{
"name": "default",
"applyToProducts": ["default"]
}
]
}
]
}
关键配置项:
- signingConfigs:签名配置,不同环境用不同签名
- products:产品形态,可以定义多个产品(如免费版、付费版)
- buildModeSet:构建模式,debug 和 release
- compatibleSdkVersion:兼容的最低 SDK 版本
4.1.2 Release 构建优化
生产环境构建,要做这些优化:
- 代码压缩:删除注释、空白,缩短变量名
- Tree Shaking:删除没用到的代码
- 资源压缩:图片、音频压缩
- 混淆:代码混淆,增加反编译难度
- 优化编译选项:开启各种编译优化
这些配置通常在 build-profile.json5 里设置,不同的构建模式用不同的配置。
4.2 构建流程
源码 + 资源
↓
编译(TS → JS → 字节码)
↓
资源处理(压缩、合并、优化)
↓
打包(生成 HAP)
↓
签名
↓
最终安装包
4.3 多环境配置
一般至少要有三个环境:
| 环境 | 用途 | 接口地址 |
|---|---|---|
| 开发环境(dev) | 日常开发用 | 本地/测试服务器 |
| 测试环境(test) | 测试同学测 bug 用 | 测试服务器 |
| 生产环境(prod) | 线上用户用 | 正式服务器 |
怎么区分环境?
通过构建配置,不同环境用不同的配置文件:
.env.development # 开发环境
.env.test # 测试环境
.env.production # 生产环境
构建的时候指定环境,自动用对应的配置。
4.4 持续集成(CI)
"持续集成"就是——代码一提交,自动跑 Lint、跑测试、构建打包。
提交代码到 Git
↓
CI 服务器自动拉取
↓
安装依赖
↓
跑 Lint 检查
↓
跑单元测试
↓
构建打包
↓
有问题 → 通知开发者
没问题 → 部署到测试环境 / 生成安装包
CI 的价值:
- 提前发现问题,不等上线才发现
- 自动化,减少人工操作
- 保证每次提交的代码质量
- 构建产物统一,不会"我这能跑你那不能跑"
4.5 发布流程
完整的发布流程:
开发完成 → 提交代码 → CI 检查 → 代码评审(CR)
↓
合并到开发分支 → 部署到测试环境 → 测试验证
↓
测试通过 → 打 Tag → 构建生产包 → 灰度发布
↓
观察数据 → 全量发布
不要一上来就全量推给所有用户——先灰度一小部分用户,观察有没有问题,没问题再逐步扩大。
五、Git 与版本管理
5.1 三种常见 Git 工作流
没有"最好的"工作流,只有"最适合你团队的"工作流。
5.1.1 Git Flow——最经典的
Git Flow 是最经典的分支模型,适合有明确发布周期的项目。
main(生产)
↑ 合并
release(发布准备)
↑ 合并
develop(开发集成分支)
↑ 合并
feature/xxx(功能分支)
特点:
- 分支多,流程规范
- 适合版本发布比较正式的项目
- 学习成本相对高
适合场景:中大型团队、有明确版本计划、发布周期固定。
5.1.2 GitHub Flow——最简洁的
GitHub Flow 非常简单,只有一个主干分支 + 功能分支。
main(主干,随时可发布)
↑ PR/MR 合并
feature/xxx(功能分支)
特点:
- 简单,只有 main + feature
- 功能完成就提 PR,合并就发布
- 适合持续部署的团队
适合场景:小团队、快速迭代、持续部署。
5.1.3 Trunk Based——最激进的
Trunk Based 更激进——直接在主干上开发,或者功能分支生命周期极短。
trunk(主干,所有人都往这提交)
↑
短期 feature 分支(最多存在几天)
特点:
- 分支最少,合并成本最低
- 依赖很强的测试和 CI 能力
- 发布频率最高
适合场景:成熟团队、自动化测试完善、追求极致效率。
5.1.4 「民族图鉴」的选择:简化版 Git Flow
对于我们的项目,推荐简化版 Git Flow:
| 分支 | 作用 |
|---|---|
| main / master | 生产代码,永远是可发布的 |
| dev / develop | 开发集成分支,日常开发合并到这里 |
| feature/xxx | 功能分支,开发新功能用 |
| bugfix/xxx | Bug 修复分支 |
| hotfix/xxx | 线上紧急修复 |
开发一个功能的流程:
从 dev 拉 feature 分支
↓
在 feature 上开发
↓
开发完了,提 PR/MR
↓
代码评审
↓
合并回 dev
↓
测试环境验证
↓
没问题,等版本发布合并到 main
这个模型平衡了规范性和灵活性,适合中小团队。
5.2 提交信息规范
Commit message 不是随便写的。规范的提交信息:
- 一看就知道改了什么
- 可以自动生成 CHANGELOG
- 可以按类型筛选提交
Conventional Commits 规范:
类型(模块): 简短描述
例如:
feat(user): 添加用户注册功能
fix(order): 修复订单列表分页错误
refactor(utils): 重构日期格式化工具
docs(readme): 更新安装说明
style(theme): 调整按钮圆角
test(login): 补充登录测试用例
chore(build): 升级构建工具版本
| 类型 | 说明 |
|---|---|
| feat | 新功能 |
| fix | 修复 bug |
| refactor | 重构(不影响功能) |
| perf | 性能优化 |
| docs | 文档 |
| style | 格式调整 |
| test | 测试 |
| chore | 构建/工具/依赖 |
5.3 代码评审(Code Review)
CR 不是"走流程",而是保障代码质量的重要环节。
5.3.1 CR 看什么?
业务逻辑层(最核心):
- 业务正确性:逻辑对不对?有没有遗漏边界场景?
- 异常处理:出错了怎么处理?有没有 try-catch?错误信息友好吗?
- 数据一致性:并发情况下数据会不会乱?事务处理对不对?
代码质量层:
4. 命名与可读性:命名好不好?代码容不容易懂?
5. 单一职责:函数/类是不是只做一件事?
6. 重复代码:有没有可以抽取复用的重复逻辑?
7. 注释:复杂逻辑有没有注释说明?
安全与性能层:
8. 安全性:有没有注入风险?有没有越权?敏感信息有没有泄露?
9. 性能:有没有明显的性能问题?(如循环内查库、大对象频繁创建)
10. 资源释放:数据库连接、文件句柄、定时器有没有及时释放?
工程规范层:
11. 测试:有没有补测试?覆盖够不够?
12. 文档:有没有更新相关文档?
13. 规范合规:符不符合代码规范?Lint 过了吗?
5.3.2 CR 的礼仪
CR 是协作,不是挑刺。
评审者(Reviewer)要注意:
- 对事不对人:评论针对代码,不针对人
- 说清楚"为什么":不要只说"要改",要说"为什么要改"
- 区分优先级:重大问题 block,小问题可以记下来后续改
- 及时 review:不要让别人等太久,最好 24 小时内
- 肯定好的地方:不要只挑问题,写得好的地方也可以夸
提交者(Author)要注意:
- 小步提交:一个 PR 不要改太多,最好不超过 400 行
- 写清楚描述:PR 描述说明"改了什么、为什么改、怎么测"
- 虚心接受:有不同意见可以讨论,但不要抵触
- 自己先过一遍:提交前自己先 review 一遍,别把明显的问题留着
5.3.3 CR 的常见误区
❌ 误区一:CR 就是找 bug
- CR 不只是找 bug,更是知识共享、代码质量提升、团队对齐的过程
- 新人通过 CR 学习,老人通过 CR 了解业务
❌ 误区二:CR 只看风格
- 风格问题让 Lint 工具去管
- CR 重点看逻辑、架构、安全这些工具查不出来的
❌ 误区三:CR 是某个人的事
- CR 是整个团队的事,每个人都要参与
- 不能只有"资深的人"评审,新人也可以参与 review
5.3.4 代码评审的 10 条最佳实践
结合「民族图鉴」项目的实践,我们总结出 CR 的 10 条最佳实践:
实践一:小步提交,一次别改太多
- 一个 PR/MR 最好控制在 400 行以内
- 改太多了,reviewer 看不过来,容易漏问题
- 大功能拆成多个小 PR,逐步合并
实践二:写好 PR 描述
- 说明"改了什么"、“为什么改”、“怎么测”
- 附上相关的需求文档、设计稿链接
- 有截图的放截图,直观
- 好的描述能让 review 效率提升一倍
实践三:自己先 review 一遍
- 提交前自己先过一遍 diff
- 有没有不小心提交了调试代码?
- 有没有忘记删的 console.log?
- 命名、格式有没有问题?
- 自己先把明显的问题改掉,别浪费 reviewer 的时间
实践四:对事不对人
- 评论针对代码,不针对人
- 不说"你这里写错了",说"这里可能有问题,因为…"
- 用建议的语气,不用命令的语气
- 好的代码也可以夸一夸,正向反馈很重要
实践五:说清楚"为什么"
- 不要只说"要改",要说"为什么要改"
- 比如:“这里建议用 const 而不是 let,因为这个变量不会被重新赋值”
- 知其然还要知其所以然,大家才能一起成长
实践六:区分优先级
- 不是所有问题都必须改
- 重大问题(bug、安全隐患):必须改,block 合入
- 中等问题(可读性、可维护性):建议改,最好改
- 小问题(命名、格式):可以改,也可以下次改
- 不要纠结风格问题,让 Lint 去管
实践七:及时 review
- 不要让别人等太久,最好 24 小时内完成
- 实在没时间,先说一声"我下午看"
- 卡着 review 不看,阻塞别人的开发进度,是很不好的习惯
实践八:工具能查的就不要靠人
- 风格问题、命名问题、简单的语法问题
- 这些让 Lint 工具去检查
- CR 重点看工具查不出来的:业务逻辑、架构设计、安全问题
- 把人力用在刀刃上
实践九:新人也要参与 review
- 不要觉得"我是新人,我看不懂"就不参与
- 看别人的代码是最好的学习方式
- 新人的视角有时候能发现老人忽略的问题
- 提问也是 review 的一部分——“这里为什么这么写?”
实践十:CR 不是终点
- 不要以为 CR 过了就万事大吉
- CR 只能发现一部分问题
- 测试、监控、用户反馈都是质量保障的一环
- CR 是"质量网"的一道,不是全部
💡 CR 的真正价值:
CR 最大的价值不是"找到多少 bug",而是——
- 知识共享:每个人都知道别人在做什么
- 代码共识:团队对"好代码"有统一的标准
- 质量兜底:多一双眼睛,多一份保障
- 共同成长:评审者和提交者都能学到东西
六、「民族图鉴」工程化清单
说了这么多理论,回到我们的项目。「民族图鉴」的工程化建设,按阶段推进。
6.1 v1.0 基础工程化(必做)
这是项目起步阶段必须搭好的"基础设施"。
代码规范:
- ✅ 命名规范(文件、变量、函数、类)
- ✅ 代码格式(缩进、换行、大括号风格)
- ✅ 注释规范(文件头、函数、复杂逻辑)
- ✅ Lint 工具配置与接入
Git 规范:
- ✅ 分支模型(简化版 Git Flow)
- ✅ 提交信息规范(Conventional Commits)
- ✅ .gitignore 配置
构建配置:
- ✅ debug / release 构建配置
- ✅ 多环境配置(开发/测试/生产)
- ✅ 签名配置
6.2 v2.0 进阶工程化(体验升级)
项目走上正轨后,做这些提升效率和质量。
测试体系:
- ⭕ 单元测试框架搭建
- ⭕ 核心模块单元测试(StorageService、ThemeService 等)
- ⭕ 测试覆盖率统计
- ⭕ 集成测试(核心流程)
CI/CD:
- ⭕ CI 流水线搭建(提交自动跑 Lint + 测试 + 构建)
- ⭕ 自动构建测试包
- ⭕ 构建产物管理
代码质量:
- ⭕ 代码评审流程落地
- ⭕ 代码质量门禁(Lint 不通过不能合并)
- ⭕ 定期代码重构与技术债清理
6.3 v3.0 成熟工程化(追求卓越)
项目成熟后,做这些让工程化更上一层楼。
测试体系:
- ⭕ E2E 测试(核心用户路径)
- ⭕ 性能测试(启动时间、FPS、内存)
- ⭕ 测试数据管理与 Mock 平台
发布体系:
- ⭕ 自动化发布流程
- ⭕ 灰度发布能力
- ⭕ 回滚机制
- ⭕ 发布后数据监控
效能提升:
- ⭕ 脚手架工具(快速创建页面/组件)
- ⭕ 代码生成工具
- ⭕ 开发文档与知识库
- ⭕ 效能度量(开发周期、发布频率、缺陷率)
6.4 工程化成熟度自评
可以用这个清单评估一下当前项目的工程化水平:
| 维度 | 成熟度 1 分 | 成熟度 3 分 | 成熟度 5 分 |
|---|---|---|---|
| 代码规范 | 靠口头约定 | 有规范文档 + Lint | 规范强制执行 + 自定义规则 |
| 自动化测试 | 没有测试 | 核心模块有单元测试 | 测试金字塔完善 + 覆盖率门禁 |
| CI/CD | 手动打包 | 自动构建 + 测试 | 自动部署 + 灰度发布 |
| 代码评审 | 没有 CR | 关键变更 CR | 所有变更 CR + CR 规范 |
| 发布流程 | 随意发布 | 有发布计划 + checklist | 自动化发布 + 一键回滚 |
| 监控告警 | 没有监控 | 有基本的错误监控 | 全链路监控 + 自动告警 |
🎯 工程化不是目的,是手段:
不要为了"工程化"而工程化——不要一上来就搞最复杂的流程、最完善的体系。
从最痛的点开始,一点点改进:
- 先加 Lint,解决"代码风格不统一"的问题
- 再加单元测试,解决"改完没底"的问题
- 再搞 CI,解决"每次打包都要手动"的问题
适合的,才是最好的。
七、工程化的价值
可能有人觉得:“搞这些条条框框,不累吗?直接写代码多快。”
短期看,搞工程化确实要花时间——搭 CI、写规范、补测试… 初期确实慢一点。
但长期看,工程化是省时间的:
| 没有工程化 | 有工程化 |
|---|---|
| 改一个 bug 花半天排查 | 跑一遍测试就定位了 |
| 新人上手要一周摸代码 | 规范清晰,两天上手 |
| 每次上线提心吊胆 | 自动化检查,稳得一批 |
| 代码越写越乱,最后改不动 | 结构清晰,持续迭代 |
🎯 工程化不是目的,是手段。
目的是——让团队在长期内,可持续地、高质量地交付软件。
工程化也不是一步到位的。先从最痛的点开始,一点点改进——先加 Lint,再加单元测试,再搞 CI,逐步完善。
八、小结
| 主题 | 核心要点 | 难度 |
|---|---|---|
| 代码规范 | 制定原则、命名、格式、注释、Lint 工具 | ⭐⭐ |
| 自动化测试 | 测试金字塔、单元测试、Mock、集成测试、E2E | ⭐⭐⭐ |
| 构建发布 | Hvigor、build-profile 配置、多环境、CI/CD | ⭐⭐⭐ |
| Git 管理 | 三种工作流、分支模型、提交规范 | ⭐⭐ |
| 代码评审 | CR 看什么、CR 礼仪、CR 误区 | ⭐⭐⭐ |
| 工程化清单 | v1.0 基础 → v2.0 进阶 → v3.0 成熟 | ⭐⭐⭐ |
| 工程化价值 | 短期投入、长期收益 | ⭐⭐ |
工程化是"内功"——不像新功能那样看得见摸得着,但它决定了项目能走多远、团队能做多大。
把工程化做好,项目越大,收益越明显。
希望这篇的内容,能帮你建立系统化的工程化思维,也能在「民族图鉴」项目中落地。
下一篇,我们来聊聊「调试方法论」——出了问题怎么高效地找到原因、解决问题。
更多推荐

所有评论(0)