代码质量学习文档
核心要点(TL;DR)
- 代码质量是系统工程,需通过静态检查、自动化测试、Code Review 与 Git 规范在每个环节把关。
- ESLint 负责逻辑与风格问题,Prettier 负责格式,Husky + lint-staged 在提交前只检查暂存区,避免冲突。
- 测试金字塔指导资源分配:大量单元测试、适量集成测试、少量 E2E 覆盖核心路径。
- Conventional Commits 与 Commitizen 统一提交信息,支撑 changelog 与版本管理。
- 不要迷信覆盖率,应关注测试有效性、核心路径与边界条件,并通过质量门禁持续改进。
学习时长与前置知识
- 建议学习时长:2-3 周(每周投入 6-8 小时)
- 前置知识:TypeScript、工程化基础、单元测试概念
一、前言:为什么代码质量如此重要?
写代码就像写文章。一篇文章如果错别字连篇、逻辑混乱、段落不清,读者读起来会很痛苦。代码也是如此。低质量的代码会带来:
- 难以维护和扩展
- Bug 频出
- 新人上手困难
- 协作效率低下
- 技术债务越积越多
代码质量不是一朝一夕的事情,而是一整套工程实践。它包括静态检查、格式化、测试、代码评审、Git 工作流、提交规范等多个方面。本章将带你系统了解如何打造高质量的代码。
二、ESLint / Prettier / Husky / lint-staged
2.1 ESLint:代码的"语法老师"
ESLint 是 JavaScript/TypeScript 最常用的静态代码检查工具。它会检查代码中的潜在问题,如未使用变量、类型错误、不符合规范的写法等。
// .eslintrc.js
module.exports = {
env: {
browser: true,
es2021: true
},
extends: ['eslint:recommended', 'plugin:react/recommended'],
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module'
},
rules: {
'no-console': 'warn',
'eqeqeq': 'error'
}
};ESLint 的价值:
- 在编码阶段发现错误
- 统一团队代码风格
- 避免常见的 JavaScript 陷阱
2.2 Prettier:代码的"美容师"
Prettier 是代码格式化工具。与 ESLint 不同,Prettier 不关心代码逻辑,只关心代码外观(缩进、换行、引号等)。
// .prettierrc
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5"
}2.3 ESLint 与 Prettier 的关系
两者容易冲突。推荐方案:
- ESLint 负责代码质量和风格规则
- Prettier 负责纯格式化
- 使用
eslint-config-prettier关闭与 Prettier 冲突的 ESLint 规则
2.4 Husky:Git 钩子的"管家"
Husky 可以在 Git 钩子(如 pre-commit、commit-msg)中执行脚本。最常见的用法是在提交前运行代码检查。
npx husky-init && npm install# .husky/pre-commit
npm run lint2.5 lint-staged:只检查暂存区
lint-staged 配合 Husky 使用,只对 git add 的文件运行检查,避免每次提交都检查整个项目。
// package.json
{
"lint-staged": {
"*.{js,ts,jsx,tsx}": ["eslint --fix", "prettier --write"]
}
}生活化比喻:ESLint 是老师检查作业对错,Prettier 是誊写员整理字迹,Husky 是门口的安检,lint-staged 是只检查你当天带的包。
三、单元测试:Jest 与 Vitest
3.1 为什么需要单元测试?
单元测试是对代码最小单元的验证。它能:
- 保证核心逻辑正确
- 方便重构,改完代码敢跑测试
- 作为代码的使用文档
3.2 Jest
Jest 是 Facebook 开源的测试框架,功能全面,配置简单。
// sum.js
export const sum = (a, b) => a + b;
// sum.test.js
import { sum } from './sum';
test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3);
});常用断言:
toBe:严格相等toEqual:深度相等toBeTruthy/toBeFalsytoContaintoThrow
3.3 Vitest
Vitest 是 Vite 生态的测试框架,语法与 Jest 高度兼容,但速度更快,原生支持 ESM 和 TypeScript。
// vitest.config.js
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
environment: 'jsdom'
}
});3.4 Mock
Mock 是测试中的重要概念。它用假的对象替代真实的依赖,避免测试受到外部系统影响。
import { vi } from 'vitest';
const fetchUser = vi.fn(() => Promise.resolve({ name: 'Tom' }));四、E2E 测试:Cypress 与 Playwright
4.1 什么是 E2E 测试?
E2E(End-to-End)测试模拟真实用户从打开页面到完成操作的完整流程。它最接近真实场景,但执行速度较慢。
4.2 Cypress
Cypress 是老牌 E2E 测试工具,以调试体验好著称。
describe('Login', () => {
it('should login successfully', () => {
cy.visit('/login');
cy.get('input[name="username"]').type('admin');
cy.get('input[name="password"]').type('123456');
cy.get('button[type="submit"]').click();
cy.url().should('include', '/dashboard');
});
});4.3 Playwright
Playwright 是微软开源的 E2E 测试工具,支持多浏览器(Chromium、Firefox、WebKit)、多语言、并行执行。
import { test, expect } from '@playwright/test';
test('homepage has title', async ({ page }) => {
await page.goto('https://example.com');
await expect(page).toHaveTitle(/Example/);
});4.4 选择建议
- Cypress:调试体验好,社区成熟
- Playwright:速度快,多浏览器支持好,现代化
五、测试覆盖率
测试覆盖率衡量代码被测试覆盖的程度。常见指标:
- 行覆盖率(Line Coverage)
- 分支覆盖率(Branch Coverage)
- 函数覆盖率(Function Coverage)
- 语句覆盖率(Statement Coverage)
npm run test -- --coverage注意:覆盖率不是越高越好。100% 覆盖率不等于没有 Bug。关键是覆盖核心逻辑和边界情况。
六、代码评审规范
6.1 Code Review 的价值
- 发现潜在问题
- 传播知识和最佳实践
- 统一代码风格
- 避免个人单点故障
6.2 Review checklist
- 功能是否正确
- 是否有明显的性能问题
- 是否有安全风险
- 是否好懂、好维护
- 是否有对应的测试
- 命名是否清晰
6.3 Review 心态
- 对代码不对人
- 提出建议而非命令
- 及时响应,不要拖延
七、Git 工作流
7.1 Git Flow
Git Flow 是一种经典分支模型:
main:稳定分支develop:开发分支feature/*:功能分支release/*:发布分支hotfix/*:热修复分支
7.2 GitHub Flow / GitLab Flow
更轻量的分支模型:
- 主分支随时可部署
- 新功能从主分支切出 feature 分支
- 通过 Pull Request / Merge Request 合并
7.3 选择合适的模型
- 小型团队:GitHub Flow
- 需要版本发布:Git Flow
- 持续交付:GitLab Flow
八、Commit 规范
8.1 Conventional Commits
规范的提交信息便于生成 changelog 和版本号。
<type>(<scope>): <subject>
<body>
<footer>常见 type:
feat:新功能fix:修复docs:文档style:格式refactor:重构test:测试chore:构建/工具
示例:
feat(auth): add login with WeChat8.2 Commitizen
Commitizen 是辅助生成规范提交信息的工具。
npx cz九、TDD 与 BDD
9.1 TDD(测试驱动开发)
TDD 的核心是"先写测试,再写代码",遵循红-绿-重构循环:
- 写一个会失败的测试(红)
- 写最少代码让测试通过(绿)
- 重构代码保持测试通过
9.2 BDD(行为驱动开发)
BDD 更关注业务行为,常用自然语言描述测试场景。
Feature: User login
Scenario: Login with valid credentials
Given the user is on the login page
When the user enters valid credentials
Then the user should be redirected to dashboard十、常见误区与最佳实践
误区一:测试是测试团队的事
前端代码的单元测试和 E2E 测试应该由前端工程师负责。
误区二:追求 100% 覆盖率
覆盖率只是参考,更重要的是测试用例的质量。
误区三:Code Review 流于形式
Review 要真正发现问题,而不是简单地点个赞。
最佳实践
- 提交前自动运行 lint 和测试
- CI 中设置测试门禁
- 使用 Conventional Commits
- 核心模块优先写单元测试
- 关键用户路径必须有 E2E 测试
- 定期回顾和更新测试用例
十一、总结
代码质量是团队协作的基石。通过 ESLint、Prettier、Husky、lint-staged 把好代码风格关,通过 Jest/Vitest、Cypress/Playwright 把好功能正确性关,通过 Code Review、Git 工作流、Commit 规范把好协作流程关,才能构建出高质量、可维护的前端项目。
十二、前端测试策略
12.1 测试分层
前端测试通常分为三层:
单元测试:测试函数、组件的最小单元。特点是快速、独立、定位准确。
集成测试:测试多个模块协同工作。比如表单组件与验证逻辑的组合。
E2E 测试:模拟真实用户操作,验证完整业务流程。
12.2 组件测试
对于 React/Vue 组件,可以使用 React Testing Library 或 Vue Test Utils:
import { render, screen, fireEvent } from '@testing-library/react';
import Counter from './Counter';
test('increments counter', () => {
render(<Counter />);
fireEvent.click(screen.getByText('Increase'));
expect(screen.getByText('Count: 1')).toBeInTheDocument();
});12.3 快照测试
快照测试可以捕获组件的渲染结果,用于检测意外变更。但不要滥用,因为快照容易失效。
十三、代码评审文化
13.1 建立 Review 规范
- 每个 PR 至少需要一个人 Review
- Reviewer 应该理解改动的背景和目的
- 提交者要积极回应评论
13.2 Review 的技巧
- 先看测试:改动是否有测试覆盖
- 再看设计:实现是否符合项目架构
- 最后看细节:命名、注释、边界处理
13.3 处理分歧
当 Review 出现分歧时,应该:
- 基于代码质量和项目规范讨论
- 必要时引入第三方仲裁
- 把共识沉淀为团队规范
十四、Git 提交规范进阶
14.1 Commit Message 结构
<type>(<scope>): <subject>
<body>
<footer>好的提交信息应该:
- 描述清楚做了什么、为什么做
- 一行不超过 72 个字符
- 使用祈使句,如 "Add" 而不是 "Added"
14.2 Commitizen 与 Husky 配合
{
"scripts": {
"commit": "cz"
},
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
}
}十五、技术债务管理
15.1 什么是技术债务?
技术债务是为了快速交付而做出的技术妥协。短期看加快了速度,长期看会增加维护成本。
15.2 如何管理技术债务?
- 定期重构:预留时间偿还债务
- 记录 TODO:用 issue 或注释标记
- 避免完美主义:不是所有债务都需要立即偿还
- 量化影响:评估债务对开发效率和稳定性的影响
十六、总结
代码质量是一个系统工程。它不是靠某一个人或某一个工具就能解决的,而是需要团队形成共识,建立规范,使用合适的工具,持续改进。通过 ESLint、Prettier、测试、Code Review、Git 工作流等手段,我们可以把代码质量牢牢把控在每一个环节。
十七、ESLint 规则定制
17.1 自定义规则
团队可以根据项目特点编写自定义 ESLint 规则。
// rules/no-console-in-prod.js
module.exports = {
create(context) {
return {
CallExpression(node) {
if (node.callee.object?.name === 'console') {
context.report({ node, message: '生产环境避免使用 console' });
}
}
};
}
};17.2 规则分级
off:关闭warn:警告error:错误,会阻断 CI
十八、Prettier 与编辑器集成
18.1 保存时自动格式化
在 VS Code 中配置 editor.formatOnSave 和 editor.defaultFormatter,可以在保存时自动调用 Prettier。
18.2 忽略文件
.prettierignore 文件可以指定不需要格式化的文件或目录。
node_modules
dist
coverage十九、测试中的最佳实践
19.1 测试数据管理
避免测试之间共享状态。每个测试用例应该独立准备数据、执行断言、清理环境。
19.2 Mock 外部依赖
单元测试应该隔离外部依赖,如网络请求、定时器、随机数等。
vi.useFakeTimers();
vi.spyOn(global, 'fetch').mockResolvedValue({ json: () => ({}) });19.3 测试即文档
好的测试用例可以说明代码的预期行为。测试名称应该清晰描述场景。
二十、代码质量度量
20.1 常用指标
- 测试覆盖率
- 代码重复率
- 圈复杂度
- 技术债务率
20.2 不要迷信指标
指标是参考,不是目标。高覆盖率不代表高质量,低复杂度也不代表好设计。
二十一、构建质量门禁
21.1 CI 中的门禁
在 CI 流水线中设置门禁:
- Lint 必须通过
- 单元测试必须通过
- 覆盖率不能低于阈值
- 构建不能失败
21.2 预提交检查
通过 Husky 和 lint-staged 在提交前自动检查暂存区文件。
二十二、总结
代码质量是一个持续改进的过程。通过 ESLint、Prettier、测试、Code Review、Git 规范等手段,我们可以在开发的每一个环节把好质量关。最终目标是让代码易于理解、易于维护、易于扩展,让团队能够高效协作,持续交付高质量的产品。
二十三、Playwright 进阶
23.1 多浏览器测试
Playwright 原生支持 Chromium、Firefox、WebKit,可以在配置中指定多个浏览器。
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } }
]
});23.2 截图与录屏
Playwright 可以在测试失败时自动截图和录屏,便于排查问题。
test('example', async ({ page }, testInfo) => {
await page.goto('https://example.com');
await testInfo.attach('screenshot', {
body: await page.screenshot(),
contentType: 'image/png'
});
});二十四、Cypress 的特点
24.1 实时重载
Cypress 测试运行时可以看到应用实时状态,调试体验非常好。
24.2 网络请求控制
Cypress 可以方便地 Mock 网络请求。
cy.intercept('GET', '/api/users', { fixture: 'users.json' }).as('getUsers');24.3 与 Playwright 的对比
Cypress 更适合调试和小型项目,Playwright 更适合大规模、多浏览器、并行测试场景。
二十五、前端性能测试
25.1 Lighthouse CI
Lighthouse CI 可以把性能评分纳入 CI 流程。
- name: Audit URLs using Lighthouse
uses: treosh/lighthouse-ci-action@v10
with:
urls: 'https://example.com'25.2 性能预算
为项目设置性能预算,如首屏加载时间、JS 体积等,超出预算时 CI 失败。
二十六、代码质量的持续改进
26.1 定期回顾
团队应该定期回顾代码质量实践,讨论遇到的问题和改进方案。
26.2 知识分享
通过技术分享、文档、Code Review 等方式传播代码质量意识。
26.3 工具升级
及时升级 ESLint、测试框架等工具,利用新特性提升效率和准确性。
二十七、总结
代码质量不是终点,而是一个持续追求的目标。通过 ESLint、Prettier、测试、Code Review、Git 规范等手段,我们可以在日常开发中不断打磨代码质量。最终目标是建立一种团队文化:每个人都关心代码质量,每个人都为高质量代码负责。
二十八、前端安全测试
28.1 XSS 防护
前端代码要注意防范 XSS 攻击:
- 不要直接拼接 HTML
- 对用户输入进行转义
- 使用 Content Security Policy
- 避免使用
dangerouslySetInnerHTML等危险 API
28.2 CSRF 防护
- 使用 SameSite Cookie
- 添加 CSRF Token
- 验证 Referer/Origin
28.3 依赖安全
使用 npm audit 或 Snyk 定期检查依赖漏洞,及时升级有问题的包。
二十九、代码质量工具链整合
29.1 一体化配置
建议在项目初始化时就配置好完整的质量工具链:
- ESLint:代码检查
- Prettier:代码格式化
- Husky:Git 钩子
- lint-staged:暂存区检查
- Jest/Vitest:单元测试
- Cypress/Playwright:E2E 测试
29.2 脚手架模板
团队可以维护一套项目脚手架模板,新项目的质量工具链一键生成。
三十、代码评审中的软技能
30.1 如何提出建设性意见
- 解释为什么这样改更好
- 提供具体示例
- 区分必要修改和可选建议
30.2 如何接受反馈
- 保持开放心态
- 不要急于辩护
- 把反馈当作学习机会
三十一、度量与改进
31.1 质量指标
- 缺陷密度
- 测试覆盖率
- Code Review 平均时间
- 技术债务比例
31.2 持续改进
定期回顾指标,识别薄弱环节,制定改进计划。代码质量提升是一个持续的过程。
三十三、Biome / Oxc:下一代前端工具链
33.1 为什么需要 Biome 和 Oxc?
传统前端工具链(ESLint、Prettier、Babel、TypeScript)虽然成熟,但各自独立、配置复杂、运行速度有限。Biome 和 Oxc 代表了新一代用 Rust 编写的高性能工具链,目标是统一格式化、Lint、编译等环节,大幅提升开发者体验。
生活化比喻:传统工具链像由不同厂家生产的工具,每件都要单独插电、单独维护;Biome 和 Oxc 像一把集成电钻,一个电源、一个接口,干活更快更稳。
33.2 Biome
Biome(前身 Rome)是一个用 Rust 编写的前端工具链,目标是“一个工具完成所有事”。目前主要提供:
- Formatter:替代 Prettier,速度极快。
- Linter:替代 ESLint,内置大量规则。
- 未来计划:编译、打包、测试等。
// biome.json
{
"formatter": {
"enabled": true,
"indentStyle": "tab"
},
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
}
}33.3 Oxc
Oxc 是另一款用 Rust 编写的高性能 JavaScript/TypeScript 工具集,目前包含:
- Parser:比 babel/parser 更快的 AST 解析器。
- Linter:轻量级 Lint 工具。
- Resolver:模块解析器。
- Formatter / Minifier:正在开发中。
Oxc 的设计哲学是“只做一件事并做好”,可以作为构建工具(如 Rspack、Farm)的底层基础设施。
33.4 与传统工具链的对比
| 特性 | ESLint + Prettier | Biome | Oxc |
|---|---|---|---|
| 语言 | JavaScript/Node | Rust | Rust |
| 速度 | 较快 | 极快 | 极快 |
| 功能范围 | Lint + Format | Lint + Format + 未来更多 | 解析/Resolve/Lint |
| 生态成熟度 | 非常成熟 | 快速成长 | 新兴 |
| 配置复杂度 | 较高(多配置文件) | 单一配置文件 | 偏底层 |
33.5 选型建议
- 成熟项目继续用 ESLint + Prettier,生态和规则最丰富。
- 追求极致速度和简化配置的新项目可尝试 Biome。
- 自研构建工具或需要高性能解析的场景可关注 Oxc。
三十四、Codemod:大规模代码迁移的“机器人”
34.1 什么是 Codemod?
Codemod 指通过脚本批量修改代码的技术。当 API 变更、框架升级、代码规范调整时,手动修改成百上千个文件既低效又容易出错。Codemod 可以把这些重复工作自动化。
生活化比喻:Codemod 像工厂里的自动化机械臂,按照图纸(规则)把旧零件(代码)统一替换为新零件。
34.2 常用工具
- jscodeshift:Facebook 开源的 Codemod 工具,基于 recast AST 转换。
- ts-morph:基于 TypeScript AST 的代码操作库,类型感知的转换更安全。
- eslint --fix:规则级别的自动修复。
- prettier --write:格式级别的自动修复。
34.3 jscodeshift 示例
把老旧的 React.PropTypes 迁移到 prop-types 包:
// transform.js
module.exports = function transformer(file, api) {
const j = api.jscodeshift;
return j(file.source)
.find(j.MemberExpression, { object: { name: "React" }, property: { name: "PropTypes" } })
.replaceWith(j.identifier("PropTypes"))
.toSource();
};执行:
npx jscodeshift -t transform.js src/34.4 Codemod 最佳实践
- 先写测试:对关键转换规则写单元测试,确保不会误改。
- 小步快跑:先在小范围验证,再全量执行。
- 代码审查:Codemod 的结果仍需人工 Review。
- 版本控制:在 Git 中执行,方便回滚。
- 结合 AST:尽量用 AST 转换而不是正则替换,减少误伤。
三十五、架构测试:用 ts-arch 守护代码结构
35.1 为什么需要架构测试?
随着项目规模增长,模块间的依赖关系容易变得混乱:底层工具函数调用上层业务代码、UI 层直接访问数据库、不同功能模块循环依赖……这些问题会破坏架构的可维护性。架构测试通过声明式规则约束模块依赖,让违反架构的代码无法通过 CI。
35.2 ts-arch 简介
ts-arch 是一个基于 TypeScript 项目结构的架构测试工具,允许你写类似自然语言的规则:
import { filesOfProject } from "ts-arch";
describe("架构约束", () => {
it("领域层不能依赖 UI 层", async () => {
const rule = filesOfProject()
.inFolder("src/domain")
.shouldNot()
.dependOnFiles()
.inFolder("src/ui");
await expect(rule).toPass();
});
});35.3 常见架构测试规则
- 分层依赖方向:domain → application → ui,禁止反向依赖。
- 禁止循环依赖:模块 A 不能间接依赖模块 A。
- 公共工具位置:utils 只能被业务层使用,不能反向依赖业务层。
- 命名约定:所有 Hook 必须以
use开头。
35.4 架构测试的收益
- 把架构约定变成可执行的测试。
- 新成员容易理解项目边界。
- 防止技术债务在不知不觉中积累。
三十六、属性测试:用 fast-check 发现边界 Bug
36.1 什么是属性测试?
传统单元测试是“举例测试”:你写几个具体输入,验证输出是否符合预期。属性测试(Property-Based Testing)则相反:你定义一个“属性”(即对所有输入都成立的断言),工具自动生成大量随机输入来验证这个属性。
生活化比喻:举例测试像老师抽查几个学生;属性测试像让全班学生都做一次测验,更容易发现隐藏的问题。
36.2 fast-check 示例
import * as fc from "fast-check";
// 属性:数组反转两次等于原数组
fc.assert(
fc.property(fc.array(fc.integer()), (arr) => {
return JSON.stringify(arr) === JSON.stringify(arr.reverse().reverse());
})
);如果存在反例,fast-check 会尝试“收缩”(shrink)输入,给出最小可复现的 case。
36.3 属性测试适用场景
- 纯函数、算法、数据转换。
- 排序、去重、序列化/反序列化。
- 状态机、表单校验、编码解码。
36.4 属性测试 vs 单元测试
| 特性 | 单元测试 | 属性测试 |
|---|---|---|
| 用例来源 | 人工编写 | 自动生成 |
| 覆盖范围 | 有限的例子 | 大量随机输入 |
| 发现边界 Bug | 依赖开发者经验 | 更容易发现 |
| 可读性 | 高 | 需要抽象属性 |
| 适用场景 | 具体业务逻辑 | 通用算法和转换 |
36.5 最佳实践
- 属性测试不替代单元测试,而是补充。
- 属性描述要准确,避免过于宽松导致无法发现 bug。
- 结合示例测试使用:属性测试覆盖边界,示例测试覆盖业务语义。
三十七、总结
代码质量是软件工程永恒的话题。通过合理的工具配置、严格的流程规范和积极的团队文化,我们可以在日常开发中不断提升代码质量。新一代工具如 Biome、Oxc 让我们更快地发现和修复问题;Codemod 让大规模迁移变得可控;架构测试和属性测试则从更高维度保障代码的结构正确性和鲁棒性。高质量的代码不仅能够减少 Bug、提高开发效率,更能让团队成员在维护和扩展时感到愉悦。这是每一位前端工程师都应该追求的目标。
领域编号:E04 代码质量与测试体系
最后更新:2026-06-24