代码质量保证方法与实践

74 阅读15分钟

代码质量(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 是连通分量数)

简化理解:每个 ifforwhilecase&&|| 增加 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 的最佳实践:

  1. 小而专注:每个 PR 只解决一个问题,避免大型 PR
  2. 清晰描述:说明改动目的、实现方式、测试情况
  3. 自查清单:提交前运行测试、格式化、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 自动重新运行