代码质量(Code Quality)是衡量软件可维护性、可靠性和可扩展性的核心指标。在现代软件开发中,保证代码质量需要系统化的方法和工具支持,从项目搭建的代码组织与包管理,到编写阶段的智能分析、格式规范、风格检查,再到调试定位、自动化测试、静态分析、代码审查,最后通过持续集成与质量门禁,形成完整的质量保障体系。
本文点到为止地介绍主流的代码质量保证方法,聚焦 TypeScript 和 C++ 两种语言的常用工具和实践。内容并不全面,仅覆盖基础且重要的质量保证环节,旨在提供快速上手的参考。
代码组织与包管理(Code Organization & Package Management)
良好的代码组织和依赖管理是代码质量的基础。包管理器统一管理项目依赖,确保环境一致性。
使用 pnpm 管理 TypeScript 项目
pnpm 通过硬链接节省磁盘空间,依赖安装速度快,且严格管理依赖关系。
初始化项目:
pnpm init
生成 package.json:
{
"name": "my-project",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"test": "vitest"
}
}
安装依赖:
# 生产依赖
pnpm add axios
# 开发依赖
pnpm add -D typescript @types/node vitest
# 全局安装
pnpm add -g typescript
依赖管理特性:
pnpm-workspace.yaml(Monorepo 支持):
packages:
- "packages/*"
项目结构:
my-project/
├── pnpm-workspace.yaml
├── package.json
├── packages/
│ ├── core/
│ │ ├── package.json
│ │ └── src/
│ └── utils/
│ ├── package.json
│ └── src/
└── pnpm-lock.yaml
依赖版本锁定:
pnpm-lock.yaml 锁定依赖版本,确保团队环境一致:
# 安装锁定的版本
pnpm install --frozen-lockfile
# 更新依赖
pnpm update
使用 CMake 管理 C++ 项目
CMake 是跨平台的构建系统生成器,管理编译流程和依赖。
基础 CMakeLists.txt:
cmake_minimum_required(VERSION 3.14)
project(MyProject VERSION 1.0.0)
# 设置 C++ 标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 源文件
add_executable(app
src/main.cpp
src/utils.cpp
)
# 头文件目录
target_include_directories(app PRIVATE include)
依赖管理:
使用 FetchContent 管理第三方库:
include(FetchContent)
# 添加 fmt 库
FetchContent_Declare(
fmt
GIT_REPOSITORY https://github.com/fmtlib/fmt.git
GIT_TAG 10.1.1
)
FetchContent_MakeAvailable(fmt)
# 链接库
target_link_libraries(app PRIVATE fmt::fmt)
项目结构:
my-project/
├── CMakeLists.txt
├── include/
│ └── utils.h
├── src/
│ ├── main.cpp
│ └── utils.cpp
├── tests/
│ └── test_utils.cpp
└── build/ # 构建输出目录
构建流程:
# 配置项目
cmake -B build -DCMAKE_BUILD_TYPE=Release
# 编译
cmake --build build
# 运行
./build/app
多目标管理:
# 库目标
add_library(mylib STATIC
src/utils.cpp
)
# 可执行文件
add_executable(app src/main.cpp)
target_link_libraries(app PRIVATE mylib)
# 测试
enable_testing()
add_executable(tests tests/test_utils.cpp)
target_link_libraries(tests PRIVATE mylib Catch2::Catch2WithMain)
add_test(NAME unit_tests COMMAND tests)
智能代码分析(Intelligent Code Analysis)
智能代码分析通过 Language Server 提供实时类型检查、错误诊断、代码补全等功能,在编写阶段发现问题,提升代码质量和开发效率。
Language Server Protocol (LSP)
LSP 是微软提出的语言服务器协议,定义了编辑器与语言服务器之间的通信标准。
工作原理:
编辑器 (VSCode/Vim/Emacs)
↓ JSON-RPC
Language Server (tsserver/clangd)
↓
代码分析、补全、诊断
LSP 将语言相关功能(语法分析、类型检查、补全)从编辑器中解耦。一个 Language Server 可以被多个编辑器使用,避免为每个编辑器单独实现语言支持。
核心能力:
textDocument/completion:代码补全textDocument/hover:悬停提示类型信息textDocument/definition:跳转到定义textDocument/references:查找所有引用textDocument/rename:重命名符号textDocument/formatting:代码格式化textDocument/publishDiagnostics:发布错误诊断
通信示例:
编辑器请求补全(JSON-RPC):
{
"jsonrpc": "2.0",
"id": 1,
"method": "textDocument/completion",
"params": {
"textDocument": { "uri": "file:///path/to/file.ts" },
"position": { "line": 10, "character": 5 }
}
}
Language Server 响应:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"items": [
{ "label": "push", "kind": 2 },
{ "label": "pop", "kind": 2 }
]
}
}
使用 TypeScript Language Server
TypeScript 内置 Language Server,提供智能提示和类型检查。
配置 tsconfig.json:
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
核心功能:
1. 类型检查:
// 类型错误会实时提示
const num: number = "hello"; // Error: Type 'string' is not assignable to type 'number'
function add(a: number, b: number): number {
return a + b;
}
add(1, "2"); // Error: Argument of type 'string' is not assignable to parameter of type 'number'
2. 智能补全:
interface User {
id: number;
name: string;
email: string;
}
const user: User = {
id: 1,
// 输入时会提示 name 和 email 属性
};
3. 跳转定义:
按住 Cmd/Ctrl 点击函数或变量名,跳转到定义位置。
4. 重构支持:
- 重命名符号(F2):自动更新所有引用
- 提取函数/变量:选中代码块,快速提取
- 导入自动修复:自动添加缺失的 import
使用 clangd 辅助 C++
clangd 是基于 Clang 的 C++ Language Server,提供快速准确的代码分析。
生成编译数据库:
clangd 需要 compile_commands.json 理解项目结构。
在 CMakeLists.txt 中配置:
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
构建项目:
cmake -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
cmake --build build
生成的 build/compile_commands.json:
[
{
"directory": "/path/to/project/build",
"command": "clang++ -I/path/to/include -std=c++17 -c /path/to/src/main.cpp",
"file": "/path/to/src/main.cpp"
}
]
配置 clangd:
创建 .clangd 配置文件:
CompileFlags:
Add:
- -std=c++17
- -Wall
- -Wextra
CompilationDatabase: build
Diagnostics:
UnusedIncludes: Strict
MissingIncludes: Strict
核心功能:
1. 代码补全:
#include <vector>
int main() {
std::vector<int> vec = {1, 2, 3};
vec. // 输入 . 后自动提示 push_back、size、empty 等方法
}
2. 诊断与快速修复:
// 未使用的变量
int unused = 42; // Warning: unused variable 'unused'
// Quick fix: Remove unused variable
// 缺少头文件
std::cout << "hello"; // Error: use of undeclared identifier 'std::cout'
// Quick fix: #include <iostream>
3. 代码导航:
// utils.h
class Calculator {
public:
int add(int a, int b);
};
// main.cpp
Calculator calc;
calc.add(1, 2); // Ctrl+Click 跳转到 utils.h 中的 add 定义
4. 重构支持:
- 重命名:F2 重命名类/函数/变量,自动更新所有引用
- 提取函数:选中代码块,提取为独立函数
- 实现声明:在头文件声明函数后,自动生成实现
5. 格式化集成:
clangd 内置 clang-format 支持,保存时自动格式化:
# .clangd
InlayHints:
Enabled: Yes
ParameterNames: Yes
DeducedTypes: Yes
代码格式规范(Code Formatting)
统一的代码格式消除团队协作中的格式差异,让代码审查聚焦逻辑而非样式。
代码格式规范主要统一以下内容:
- 缩进(Indentation):空格或 Tab,缩进层级
- 换行(Line Breaking):行长度限制,何时换行
- 空格(Spacing):运算符、括号、逗号周围的空格
- 空行(Blank Lines):类、函数、代码块之间的空行数
格式化工具读取配置后自动应用这些规则。
使用 Prettier 格式化 TypeScript
Prettier 是 TypeScript/JavaScript 生态的主流格式化工具,零配置即可使用。
安装与配置:
npm install --save-dev prettier
创建 .prettierrc 配置文件:
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"printWidth": 80
}
使用示例:
格式化前:
function calculateTotal(items: Array<{ price: number; quantity: number }>): number {
const total = items.reduce((sum, item) => sum + item.price * item.quantity, 0);
return total;
}
格式化后:npx prettier --write file.ts
function calculateTotal(items: Array<{ price: number; quantity: number }>): number {
const total = items.reduce((sum, item) => sum + item.price * item.quantity, 0);
return total;
}
使用 clang-format 格式化 C++
clang-format 是 LLVM 项目提供的 C++ 格式化工具,支持多种预设风格。
配置文件 .clang-format:
BasedOnStyle: Google
IndentWidth: 4
ColumnLimit: 100
使用示例:
格式化前:
int calculateSum(std::vector<int>& nums){int sum=0;for(auto n:nums){sum+=n;}return sum;}
格式化后: clang-format -i file.cpp
int calculateSum(std::vector<int>& nums) {
int sum = 0;
for (auto n : nums) {
sum += n;
}
return sum;
}
代码风格检查(Linting)
Linter 检查代码中的潜在错误、不符合最佳实践的写法和代码异味(Code Smell)。与格式化工具不同,Linter 关注代码质量而非格式。
Linter 主要检查以下内容:
- 语法错误:未声明变量、类型错误
- 潜在 Bug:空指针引用、未处理异常
- 最佳实践:使用 const 而非 let、避免 any 类型
- 代码异味:函数过长、圈复杂度过高
使用 ESLint 检查 TypeScript
ESLint 是 TypeScript/JavaScript 生态的标准 Linter 工具。
安装与配置:
npm install --save-dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin
创建 .eslintrc.json 配置文件:
{
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint"],
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
"rules": {
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/no-unused-vars": "warn"
}
}
使用示例:
问题代码:
function processData(data: any) {
// 使用 any 类型
const result = data.value;
let temp = result; // 应使用 const
return temp;
}
检查结果:npx eslint src/processor.ts
src/processor.ts
1:23 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
3:3 warning 'temp' is never reassigned. Use 'const' instead prefer-const
自动修复:npx eslint --fix src/processor.ts(仅修复 prefer-const 规则,any 类型需手动修改)
修复后:
function processData(data: { value: number }) {
// 明确类型
const result = data.value;
const temp = result; // 使用 const
return temp;
}
使用 Clang-Tidy 检查 C++
Clang-Tidy 是 LLVM 项目提供的 C++ Linter 工具。
配置文件 .clang-tidy:
Checks: "clang-analyzer-*,bugprone-*,modernize-*"
WarningsAsErrors: "bugprone-*"
使用示例:
问题代码:
void processVector(std::vector<int> vec) { // 应传引用
for (int i = 0; i < vec.size(); i++) { // 应使用范围 for
std::cout << vec[i] << std::endl;
}
}
检查结果:clang-tidy src/processor.cpp
src/processor.cpp:1:24: warning: parameter 'vec' is passed by value and only used as const reference [performance-unnecessary-value-param]
src/processor.cpp:2:5: warning: use range-based for loop instead [modernize-loop-convert]
自动修复:clang-tidy -fix src/processor.cpp(部分规则支持自动修复)
修复后:
void processVector(const std::vector<int>& vec) { // 传引用
for (const auto& item : vec) { // 范围 for
std::cout << item << std::endl;
}
}
调试与问题定位(Debugging)
调试是发现和定位代码问题的核心手段。有效的调试能快速缩小问题范围,理解程序运行时状态。
调试的基本方法:
- 断点调试(Breakpoint):暂停程序执行,检查变量状态
- 单步执行(Step):逐行执行代码,观察执行流程
- 变量监视(Watch):实时查看变量值的变化
- 调用栈(Call Stack):追踪函数调用链
Debug Adapter Protocol (DAP)
DAP 是微软提出的调试器协议,定义了编辑器与调试器之间的通信标准。VSCode、Vim、Emacs 等编辑器通过 DAP 适配器连接各种语言的调试器。
工作流程:
编辑器 <--DAP--> Debug Adapter <---> 语言调试器(Node.js Inspector / LLDB)
DAP 统一了调试操作(设置断点、单步执行、查看变量),使编辑器无需为每种语言单独实现调试支持。
调试信息(Debug Info)
调试信息是编译器生成的元数据,帮助调试器将目标代码映射回源代码。不同语言使用不同的调试信息格式。
Source Map(TypeScript/JavaScript):将编译后的 JavaScript 映射回 TypeScript 源代码。
Source Map 格式示例:
{
"version": 3,
"file": "output.js",
"sourceRoot": "",
"sources": ["../src/input.ts"],
"mappings": "AAAA,SAAS,QAAQ..."
}
mappings 字段使用 VLQ(Variable Length Quantity) 编码记录 JavaScript 行列号与 TypeScript 行列号的映射关系。每个分号代表 JavaScript 中的一行,逗号分隔同一行的多个映射。
映射格式示例:
AAAA -> [生成列偏移, 源文件索引, 源代码行偏移, 源代码列偏移]
例如 AAAA 表示:生成代码第 0 列对应源文件 0 的第 0 行第 0 列。VLQ 使用 Base64 字符编码数字,支持增量存储以压缩体积。
DWARF 调试信息(C++):C++ 使用 DWARF 格式存储调试信息,直接嵌入到可执行文件中。编译时使用 -g 选项生成:
clang++ -g main.cpp -o app # 生成包含 DWARF 信息的可执行文件
DWARF 信息包含:
- 源文件路径和行号映射
- 变量名称和类型信息
- 函数调用栈信息
查看 DWARF 信息:
llvm-dwarfdump app | head -20
调试符号分离
-g 选项将 DWARF 信息嵌入可执行文件,增加文件体积。发布版本可以分离调试符号:
# 提取调试信息到单独文件
objcopy --only-keep-debug app app.debug
# 剥离可执行文件中的调试信息
strip app
# 建立链接,调试器可通过 app 找到 app.debug
objcopy --add-gnu-debuglink=app.debug app
这样生产环境部署小体积的 app,保留 app.debug 用于事后调试。调试时 LLDB 会自动加载 app.debug。
使用 Node.js Inspector 调试 TypeScript
Node.js 内置调试器基于 Chrome DevTools Protocol。
配置 tsconfig.json 生成 Source Map:
{
"compilerOptions": {
"sourceMap": true,
"outDir": "./dist"
}
}
编译后生成 .js 和 .js.map 文件。
调试示例:
问题代码:
function calculateDiscount(price: number, discount: number): number {
const discountAmount = price * discount;
const finalPrice = price - discountAmount;
return finalPrice;
}
const result = calculateDiscount(100, 0.2);
console.log(result);
调试步骤:
node inspect dist/index.js
debug> setBreakpoint(2) # 在当前文件第 2 行设置断点
debug> cont # 继续执行
debug> exec price # 查看变量:100
debug> exec discount # 查看变量:0.2
debug> next # 单步执行
debug> next # 单步执行
debug> exec finalPrice # 查看结果:80
node inspect 使用编译后的 .js 文件行号。要调试源代码,使用 Chrome DevTools:
node --inspect-brk dist/index.js
# 打开 chrome://inspect,点击 "inspect"
# DevTools 通过 Source Map 自动显示 index.ts 源文件
使用 LLDB 调试 C++
LLDB 是 LLVM 项目的调试器,支持 macOS、Linux、Windows。
编译时添加调试信息:
clang++ -g -o app main.cpp # -g 选项添加调试符号
调试示例:
问题代码:
#include <vector>
#include <iostream>
int findMax(const std::vector<int>& nums) {
int max = nums[0]; // 如果 nums 为空会崩溃
for (size_t i = 1; i < nums.size(); i++) {
if (nums[i] > max) {
max = nums[i];
}
}
return max;
}
int main() {
std::vector<int> empty;
int result = findMax(empty); // 崩溃位置
std::cout << result << std::endl;
return 0;
}
调试步骤:
lldb ./app
(lldb) breakpoint set --name findMax # 在 findMax 函数设置断点
(lldb) run # 运行程序
(lldb) frame variable nums.size() # 查看 nums 大小:0
(lldb) next # 单步执行
(lldb) print max # 尝试访问 nums[0],发现越界
(lldb) bt # 查看调用栈
输出:
* frame #0: findMax(nums=size=0) at main.cpp:5
frame #1: main at main.cpp:16
自动化测试体系(Automated Testing)
自动化测试通过代码验证代码,确保功能正确性和回归问题可控。测试体系分为不同层次,从单元测试到集成测试,覆盖不同的验证范围。
测试金字塔(Test Pyramid)
测试金字塔描述了不同测试层次的数量比例:
/\
/E2E\ 端到端测试:少量,验证完整用户流程
/------\
/ 集成 \ 集成测试:中等,验证模块间交互
/----------\
/ 单元测试 \ 单元测试:大量,验证单个函数/类
/--------------\
特点对比:
| 测试类型 | 数量 | 速度 | 范围 | 成本 |
|---|---|---|---|---|
| 单元测试 | 多 | 快 | 单个函数/类 | 低 |
| 集成测试 | 中 | 中 | 多个模块 | 中 |
| 端到端测试 | 少 | 慢 | 完整流程 | 高 |
使用 Vitest 测试 TypeScript
Vitest 是基于 Vite 的现代测试框架,原生支持 TypeScript。
安装配置:
npm install --save-dev vitest
创建 vitest.config.ts:
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
coverage: {
provider: "v8",
include: ["src/**/*.ts"],
},
},
});
单元测试示例:
被测代码 src/calculator.ts:
export function add(a: number, b: number): number {
return a + b;
}
测试代码 src/calculator.test.ts:
import { describe, test, expect } from "vitest";
import { add } from "./calculator";
describe("Calculator", () => {
test("add should return sum of two numbers", () => {
expect(add(2, 3)).toBe(5);
expect(add(-1, 1)).toBe(0);
});
});
运行测试:npx vitest run
✓ src/calculator.test.ts (3)
✓ Calculator (3)
✓ add should return sum of two numbers
使用 Catch2 测试 C++
Catch2 是 C++ 的现代测试框架,header-only,易于集成。
安装(使用 CMake):
CMakeLists.txt:
cmake_minimum_required(VERSION 3.14)
project(MyProject)
include(FetchContent)
FetchContent_Declare(
Catch2
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
GIT_TAG v3.4.0
)
FetchContent_MakeAvailable(Catch2)
add_executable(calculator_test calculator_test.cpp)
target_link_libraries(calculator_test PRIVATE Catch2::Catch2WithMain)
include(CTest)
include(Catch)
catch_discover_tests(calculator_test)
单元测试示例:
被测代码 calculator.h:
#pragma once
class Calculator {
public:
int add(int a, int b) {
return a + b;
}
int divide(int a, int b) {
if (b == 0) {
throw std::invalid_argument("Division by zero");
}
return a / b;
}
};
测试代码 calculator_test.cpp:
#include <catch2/catch_test_macros.hpp>
#include "calculator.h"
TEST_CASE("Calculator add returns sum", "[calculator]") {
Calculator calc;
REQUIRE(calc.add(2, 3) == 5);
REQUIRE(calc.add(-1, 1) == 0);
}
TEST_CASE("Calculator divide returns quotient", "[calculator]") {
Calculator calc;
REQUIRE(calc.divide(10, 2) == 5);
}
TEST_CASE("Calculator divide throws on zero", "[calculator]") {
Calculator calc;
REQUIRE_THROWS_AS(calc.divide(10, 0), std::invalid_argument);
}
编译运行:
cmake -B build
cmake --build build
ctest --test-dir build
输出:
Test project /path/to/build
Start 1: calculator_test
1/1 Test #1: calculator_test .................. Passed 0.00 sec
100% tests passed, 0 tests failed out of 1
查看详细测试结果:./build/calculator_test
All tests passed (3 assertions in 3 test cases)
静态代码分析(Static Analysis)
静态代码分析通过代码度量评估代码质量,发现复杂度过高、重复代码等深层问题。与 Linter 的语法检查不同,静态分析关注可维护性和技术债务。
代码度量指标
圈复杂度(Cyclomatic Complexity)
圈复杂度衡量代码的分支数量,值越高代码越难理解和测试。
计算公式:M = E - N + 2P(E 是边数,N 是节点数,P 是连通分量数)
简化理解:每个 if、for、while、case、&&、|| 增加 1 点复杂度。
示例代码:
// 圈复杂度 = 4
function validateUser(user: User): boolean {
if (!user.name) return false; // +1
if (!user.email) return false; // +1
if (user.age < 18) return false; // +1
if (!user.verified) return false; // +1
return true;
}
建议阈值:
- 1-10:简单,易维护
- 11-20:中等复杂,需关注
- 21+:高复杂度,建议重构
代码重复率(Code Duplication)
重复代码增加维护成本,修改时容易遗漏。度量标准:
- 重复行数:相同或高度相似的代码行
- 重复块数:连续重复的代码片段数量
- 重复率:重复代码占总代码的百分比
建议目标:重复率 < 5%
可维护性指数(Maintainability Index)
综合度量代码可维护性,基于圈复杂度、代码行数、注释密度计算。
公式:MI = 171 - 5.2 * ln(V) - 0.23 * G - 16.2 * ln(L)
- V:Halstead Volume(代码量)
- G:圈复杂度
- L:代码行数
指数范围:
- 0-9:难以维护
- 10-19:中等可维护性
- 20+:良好可维护性
使用 SonarQube 分析项目
SonarQube 是企业级代码质量管理平台,支持多种语言。
安装 SonarQube Scanner(TypeScript):
npm install --save-dev sonarqube-scanner
创建 sonar-project.properties:
sonar.projectKey=my-project
sonar.sources=src
sonar.tests=src
sonar.test.inclusions=**/*.test.ts
sonar.javascript.lcov.reportPaths=coverage/lcov.info
运行分析:
npx sonar-scanner
代码审查机制(Code Review)
代码审查通过人工检查发现自动化工具难以检测的问题,包括逻辑错误、设计缺陷、安全漏洞。审查也是知识传递和团队协作的重要方式。
审查重点
逻辑正确性:检查代码是否实现了预期功能,边界条件是否处理。
// 问题:未处理数组为空的情况
function getAverage(numbers: number[]): number {
const sum = numbers.reduce((a, b) => a + b);
return sum / numbers.length; // numbers.length 为 0 时返回 NaN
}
性能问题:识别性能瓶颈和资源浪费。
// 问题:不必要的拷贝
void printUsers(std::vector<User> users) { // 值传递,拷贝整个 vector
for (const auto& user : users) {
std::cout << user.name << std::endl;
}
}
安全漏洞:检查输入验证、权限控制、数据泄露风险。
TypeScript 示例:
// 问题:SQL 注入风险
function getUser(userId: string) {
const query = `SELECT * FROM users WHERE id = ${userId}`; // 直接拼接
return db.query(query);
}
// 改进:使用参数化查询
function getUser(userId: string) {
const query = "SELECT * FROM users WHERE id = ?";
return db.query(query, [userId]);
}
代码审查 Checklist:
- 代码符合项目规范
- 逻辑正确,边界条件处理完整
- 有相应的单元测试
- 无明显性能问题
- 无安全漏洞
- 注释清晰,复杂逻辑有说明
使用 GitHub Pull Request
GitHub PR 是代码审查的主流方式。
创建 PR 的最佳实践:
- 小而专注:每个 PR 只解决一个问题,避免大型 PR
- 清晰描述:说明改动目的、实现方式、测试情况
- 自查清单:提交前运行测试、格式化、Linter
审查流程:
# 1. 创建分支
git checkout -b feature/add-validation
# 2. 提交代码
git add .
git commit -m "Add user input validation"
# 3. 推送并创建 PR
git push origin feature/add-validation
gh pr create --title "Add user input validation" --body "
## Changes
- Add email validation
- Add age range check
## Testing
- Unit tests added
- Manual testing completed
"
审查者操作:
# 1. 检出 PR 分支本地测试
gh pr checkout 123
# 2. 运行测试
npm test
# 3. 添加审查意见
gh pr review 123 --comment --body "建议添加错误提示信息"
# 4. 批准或请求修改
gh pr review 123 --approve
# 或
gh pr review 123 --request-changes
持续集成与质量门禁(CI & Quality Gates)
持续集成(CI)在代码提交时自动运行质量检查,质量门禁(Quality Gates)设定通过标准。未达标准的代码无法合并,确保主分支代码质量。
CI 质量检查点
典型的 CI 流程包含以下检查:
提交代码
↓
代码格式检查 (Prettier/clang-format)
↓
代码风格检查 (ESLint/Clang-Tidy)
↓
单元测试
↓
覆盖率检查
↓
构建验证
↓
质量门禁评估
↓
合并或拒绝
使用 GitHub Actions
TypeScript 项目 CI 配置:
.github/workflows/ci.yml:
name: CI
on:
pull_request:
push:
branches: [main]
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Install dependencies
run: npm install
- name: Format check
run: npm run format:check
- name: Lint
run: npm run lint
- name: Test
run: npm run test:coverage
- name: Check coverage
run: |
COVERAGE=$(cat coverage/coverage-summary.json | jq '.total.lines.pct')
if (( $(echo "$COVERAGE < 80" | bc -l) )); then
echo "Coverage $COVERAGE% is below 80%"
exit 1
fi
- name: Build
run: npm run build
质量门禁
基础标准:
- 代码格式检查通过
- Linter 无 error 级别问题
- 所有测试通过
- 测试覆盖率 ≥ 80%
- 构建成功
在 GitHub Actions 中实施门禁:
- name: Quality Gate
run: |
# 检查覆盖率
COVERAGE=$(cat coverage/coverage-summary.json | jq '.total.lines.pct')
if (( $(echo "$COVERAGE < 80" | bc -l) )); then
echo "❌ Coverage gate failed: $COVERAGE% < 80%"
exit 1
fi
echo "✅ Coverage gate passed: $COVERAGE%"
# 检查 Linter 错误
ERRORS=$(npm run lint 2>&1 | grep -c "error" || true)
if [ "$ERRORS" -gt 0 ]; then
echo "❌ Lint gate failed: $ERRORS errors"
exit 1
fi
echo "✅ Lint gate passed"
失败反馈与修复
CI 失败时的反馈:
❌ CI Failed
Format Check: ✅ Passed
Lint: ❌ Failed (3 errors)
Tests: ✅ Passed (120/120)
Coverage: ❌ Failed (78% < 80%)
Build: ✅ Passed
Details:
src/validator.ts:15:3 - error: 'userId' is not defined
src/api.ts:42:10 - error: Missing return type
src/utils.ts:88:5 - error: Unreachable code
Coverage Report:
File | Lines | Branches | Functions
----------------|-------|----------|----------
src/validator.ts| 72% | 65% | 80%
src/api.ts | 85% | 90% | 100%
src/utils.ts | 75% | 70% | 85%
修复流程:
# 1. 本地修复问题
npm run lint:fix
npm run test:coverage
# 2. 确认本地通过所有检查
npm run format:check && npm run lint && npm run test && npm run build
# 3. 提交修复
git add .
git commit -m "fix: resolve linter errors and improve coverage"
git push
# 4. CI 自动重新运行