在这里插入图片描述

一、什么是工程化

写代码是"手艺活",但做项目是"工程活"。

一个人写代码,怎么写都行;十个人一起写,如果没有规范、没有流程、没有工具,很快就会乱成一锅粥——

  • 代码风格千奇百怪,看别人代码像读天书
  • 你改的功能我也在改,合并冲突不断
  • 测试全靠手点,改一个 bug 引出三个新 bug
  • 发布靠人打包,每次上线像抽奖

工程化就是用工具和流程,把"人"的不确定性降到最低,让项目可预测、可重复、可维护。

这一篇,我们从三个维度聊工程化:

  1. 代码规范:怎么写代码大家都能看懂
  2. 自动化测试:怎么保证改了代码不出 bug
  3. 构建与发布:怎么把代码变成可安装的包

二、代码规范

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 工具做什么?

  1. 语法检查:有没有语法错误
  2. 风格检查:命名、缩进、换行符合规范吗
  3. 最佳实践:有没有不好的写法(比如 eval、var)
  4. 类型检查: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 不是"装了就完了",要融入开发流程:

  1. 编辑器集成:写代码时实时提示,有问题立刻改
  2. 保存自动修复:能自动修的(如格式),保存时自动修好
  3. 提交前检查:Git 提交前自动跑 Lint,有问题不让提交
  4. 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 的三个层次

  1. Fake:有完整实现,但不走真实流程(如内存数据库)
  2. Stub:只返回预设数据,没有实现逻辑
  3. Mock:不仅返回数据,还能验证被调用的情况

根据测试需要选择合适的层次。

3.4 测试的最佳实践

  1. 测试行为,不测试实现:测"结果对不对",不测"内部怎么实现的"
  2. 一个测试只测一件事:一个 it 只验证一个点,出了问题一眼就知道哪错了
  3. 测试要独立:每个测试都能单独跑,不依赖其他测试
  4. 测试要快:单元测试几秒内跑完,不然没人愿意跑
  5. 先写测试再写代码?(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 构建优化

生产环境构建,要做这些优化:

  1. 代码压缩:删除注释、空白,缩短变量名
  2. Tree Shaking:删除没用到的代码
  3. 资源压缩:图片、音频压缩
  4. 混淆:代码混淆,增加反编译难度
  5. 优化编译选项:开启各种编译优化

这些配置通常在 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 看什么?

业务逻辑层(最核心)

  1. 业务正确性:逻辑对不对?有没有遗漏边界场景?
  2. 异常处理:出错了怎么处理?有没有 try-catch?错误信息友好吗?
  3. 数据一致性:并发情况下数据会不会乱?事务处理对不对?

代码质量层
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",而是——

  1. 知识共享:每个人都知道别人在做什么
  2. 代码共识:团队对"好代码"有统一的标准
  3. 质量兜底:多一双眼睛,多一份保障
  4. 共同成长:评审者和提交者都能学到东西

六、「民族图鉴」工程化清单

说了这么多理论,回到我们的项目。「民族图鉴」的工程化建设,按阶段推进。

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 成熟 ⭐⭐⭐
工程化价值 短期投入、长期收益 ⭐⭐

工程化是"内功"——不像新功能那样看得见摸得着,但它决定了项目能走多远、团队能做多大。

把工程化做好,项目越大,收益越明显。

希望这篇的内容,能帮你建立系统化的工程化思维,也能在「民族图鉴」项目中落地。

下一篇,我们来聊聊「调试方法论」——出了问题怎么高效地找到原因、解决问题。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐