软件开发进阶技能之代码规范与测试(一)

简介: 教程来源本文系统讲解代码规范(命名、格式、注释、设计原则、代码审查)与软件测试(单元/集成测试、TDD、Mock、覆盖率、CI)两大核心实践,强调规范是协作契约、测试是质量护栏,助力开发者写出可读、可维护、可演进的高质量代码。

规范与测试 —— 软件质量的左右护法
在软件开发领域,我们经常听到一句话:“代码是写给人看的,只是顺便让机器运行。” 当项目从一个人的“独角戏”变成多人协作的“交响乐”,当代码的生命周期从几周延长到几年,代码规范和软件测试就从“可有可无”变成了“生死攸关”。

没有规范的代码库,如同一座没有建筑图纸的违章搭建——每个新功能都像在危房上加层,随时可能坍塌。没有测试的系统,仿佛一架没有仪表的飞机——起飞时一切正常,但一旦遇到气流,你完全不知道哪里会出问题。

进阶开发者与普通开发者的重要区别之一在于:他们不仅写出能工作的代码,更写出可读、可维护、可测试、可演进的代码。本文将系统地讲解代码规范(命名、格式、注释、设计原则、代码审查)和软件测试(单元测试、集成测试、测试驱动开发、Mock、测试覆盖率、持续集成)两大领域,并提供大量可直接落地的示例、工具配置和最佳实践。

预备知识:你已经掌握至少一门编程语言,写过一些完整功能,但可能对代码风格和测试体系缺乏系统认识。

第一部分:代码规范 —— 让代码像散文一样清晰

1.1 为什么需要代码规范?
代码规范不是束缚创造力的枷锁,而是团队协作的契约。它带来以下实际好处:

降低沟通成本:所有人都用同一种风格,不需要猜测变量的含义、缩进的层级。

减少低级错误:如命名混淆导致的变量覆盖、缺少空格导致的运算符歧义。

便于代码审查:审查者专注于逻辑,而不是花时间纠正格式。

新人快速上手:统一的规范让新成员能更快理解和融入。

1.2 命名规范 —— 好名字是最大的注释
命名是编程中最困难的两件事之一(另一件是缓存失效)。一个好的名字应该自解释、符合语言惯例、长度适中。

1.2.1 常见命名风格
image.png
1.2.2 命名原则与反模式
原则 1:名副其实 —— 不要用注释掩盖坏名字

反例:

// 反例:名字没有传达含义
int d; // 经过的天数
List<Object> list; // 存储用户的列表

正例:

int elapsedDays;
List<User> userList;

原则 2:避免误导

不要使用容易混淆的名字:

accountList —— 除非它真的是 List 类型,否则用 accounts 或 accountSet

delete() —— 如果操作是逻辑删除,用 remove() 或 archive() 更准确

避免小写 l 和大写 O 作为变量名(与 1 和 0 混淆)

原则 3:使用可读的名称,而非缩写

除非是业界公认的缩写(如 id, url, http),否则不要创造新缩写:

# 反例
def proc_msg(m):
    # 处理消息

# 正例
def process_message(message):
    ...

原则 4:类名是名词或名词短语,方法名是动词或动词短语

class UserAuthenticator { ... }          // 名词
class TransactionProcessor { ... }       // 名词

void calculateInterest() { ... }         // 动词
boolean isAuthenticated() { ... }        // 动词 + 形容词(返回布尔值)

原则 5:布尔变量/方法使用肯定形式

// 反例
let isNotActive = true;
if (!isNotActive) { ... }

// 正例
let isActive = false;
if (isActive) { ... }

1.3 代码格式化 —— 让视觉结构表达逻辑
代码格式的一致性对可读性至关重要。不同语言有社区公认的格式化工具:

JavaScript/TypeScript:Prettier、ESLint

Python:Black、autopep8

Java:Google Java Format、Checkstyle

Go:gofmt(官方强制)

Rust:rustfmt

1.3.1 缩进与空格
大多数语言使用 2 或 4 个空格,禁止使用 Tab(或规定 Tab 显示为空格)。在 Python 中缩进是语法的一部分,必须保持一致。

示例:Python 缩进

# 正确
def calculate_average(scores):
    if not scores:
        return 0
    total = sum(scores)
    return total / len(scores)

# 错误:缩进不一致
def calculate_average(scores):
    if not scores:
       return 0  # 这里用了 3 个空格,与上面 4 个空格不一致,会报错
    total = sum(scores)
    return total / len(scores)

1.3.2 换行与行长
建议每行不超过 80-120 个字符。过长的行应该合理换行。

Java 中的换行示例:

// 反例:一行太长
Map<String, List<Order>> userOrdersMap = orderService.getOrdersByUsers(userIds.stream().filter(u -> u.isActive()).collect(Collectors.toList()));

// 正例:适当换行
List<User> activeUsers = userIds.stream()
        .filter(User::isActive)
        .collect(Collectors.toList());
Map<String, List<Order>> userOrdersMap = orderService.getOrdersByUsers(activeUsers);

1.3.3 大括号风格
不同语言有不同的约定:

Java/Kotlin:左大括号不换行(Egyptian 风格)

C#:左大括号换行

JavaScript:通常不换行(但 Prettier 默认会调整)

// Java 风格
public void process() {
    if (condition) {
        doSomething();
    } else {
        doOther();
    }
}

1.3.4 空行与分组
用空行分隔逻辑块,而不是无脑地每隔三行加空行。

# 良好分组
def save_order(order):
    # 验证部分
    if not order.items:
        raise ValueError("订单无商品")
    for item in order.items:
        if item.quantity <= 0:
            raise ValueError("数量无效")

    # 计算总价
    total = sum(item.price * item.quantity for item in order.items)
    order.total = total

    # 持久化
    db.session.add(order)
    db.session.commit()

1.4 注释 —— 解释 Why,而非 What
好的代码应该自注释(self-documenting),注释应该解释为什么这样做,而不是做了什么。如果代码本身不够清晰,首先考虑改进代码。

1.4.1 好注释 vs 坏注释
坏注释(冗余注释):

// 反例:注释和代码重复
// 将 counter 加 1
counter++;

// 反例:误导性注释
// 此处设置超时时间为10秒
setTimeout(callback, 5000);  // 实际是5秒

好注释(解释背景和意图):

// 此处使用双重检查锁,因为 getInstance() 被频繁调用,
// 而 synchronized 方法会导致不必要的性能开销。
// 参考 Effective Java 第83条。
private volatile static Singleton instance;
public static Singleton getInstance() {
    if (instance == null) {
        synchronized (Singleton.class) {
            if (instance == null) {
                instance = new Singleton();
            }
        }
    }
    return instance;
}

1.4.2 TODO 与 FIXME 标记
在代码中临时标记待办事项,但应该在版本管理工具中跟踪,不应长期存在。

# TODO(zhang): 待优化,使用批量查询减少 N+1
for user in users:
    # FIXME: 当订单表很大时,此查询会超时
    orders = Order.query.filter_by(user_id=user.id).all()

1.4.3 文档注释(API 文档)
为公共 API(类、方法、函数)编写文档注释,这可以被工具提取生成文档。

Java Javadoc:

/**
 * 根据用户ID查找用户信息。
 *
 * @param userId 用户唯一标识,不能为 null
 * @return 用户对象,如果不存在返回 {@code Optional.empty()}
 * @throws IllegalArgumentException 如果 userId 为 null
 */
public Optional<User> findUserById(String userId) {
    ...
}

Python Docstring:

def read_config(file_path: str) -> dict:
    """
    读取 JSON 格式的配置文件。

    Args:
        file_path: 配置文件的路径。

    Returns:
        解析后的字典对象。

    Raises:
        FileNotFoundError: 文件不存在时。
        json.JSONDecodeError: 文件内容不是合法 JSON。
    """

1.5 设计原则与代码结构 —— 可维护性的基石
1.5.1 单一职责原则(SRP)
一个类/模块应该只有一个引起它变化的原因。换言之,一个类应该只做一件事。

反例:User 类既有数据字段,又包含数据持久化和邮件发送逻辑。

# 反例
class User:
    def __init__(self, name, email):
        self.name = name
        self.email = email

    def save_to_db(self):
        # 数据库逻辑
        pass

    def send_welcome_email(self):
        # 邮件逻辑
        pass

正例:职责分离

class User:
    def __init__(self, name, email):
        self.name = name
        self.email = email

class UserRepository:
    def save(self, user):
        # 数据库逻辑
        pass

class EmailService:
    def send_welcome(self, user):
        # 邮件逻辑
        pass

1.5.2 不要重复自己(DRY)
重复代码是维护的噩梦。如果同一段逻辑出现在两处,应该抽取成公共方法。

// 反例:重复的税率计算
function calculateTotalPrice(items) {
    let subtotal = items.reduce((sum, i) => sum + i.price, 0);
    let tax = subtotal * 0.08;
    return subtotal + tax;
}

function generateInvoice(items) {
    let subtotal = items.reduce((sum, i) => sum + i.price, 0);
    let tax = subtotal * 0.08;
    console.log("Subtotal:", subtotal);
    console.log("Tax:", tax);
}

正例:

function calculateSubtotal(items) {
    return items.reduce((sum, i) => sum + i.price, 0);
}
function calculateTax(subtotal) {
    return subtotal * 0.08;
}
function calculateTotalPrice(items) {
    let subtotal = calculateSubtotal(items);
    return subtotal + calculateTax(subtotal);
}

1.5.3 最少知识原则(Law of Demeter)
一个对象应该尽可能少地了解其他对象的内部结构。不要链式调用多层方法(除非它们稳定且明确)。

// 违反迪米特法则
String city = user.getAddress().getCity().getName();

// 改进:在 Address 类中直接提供 getCityName()
String city = user.getAddress().getCityName();

1.6 代码审查(Code Review)
代码审查是保证规范落地的最有效手段。它不仅是找 bug,更是知识分享和团队规范强化的过程。

1.6.1 审查清单
image.png
1.6.2 审查评论的原则
就事论事:讨论代码,不攻击人。

建议而非命令:使用“也许可以...”、“是否考虑...”,而不是“你必须...”。

解释理由:不仅仅说“这样不好”,还要说“因为...会导致...问题”。

区分“必须修改”与“可选建议”:使用标签如 [mandatory] 和 [nit]。

示例评论:

[mandatory] 这里可能发生空指针异常。建议使用 Optional 或者增加判空。
[optional] 变量名 `tmp` 不够清晰,可以改为 `normalizedValue`。

来源:
https://bncne.cn/

相关文章
|
18天前
|
人工智能 自然语言处理 文字识别
阿里云百炼Qwen3.7-Max简介:能力、优势、支持订阅计划参考
Qwen3.7-Max是阿里云百炼面向智能体时代推出的新一代旗舰模型,对标GPT-5.5、Claude Opus 4.7等闭源旗舰。该模型支持百万级token上下文窗口,具备顶级推理能力、多模态搜索与视觉理解增强、流式输出低延迟响应等核心优势,覆盖编程、办公、长周期自主执行等复杂场景。同时支持OpenAI接口兼容,便于系统快速迁移。用户可通过Token Plan团队或节省计划等订阅方式灵活调用,适合企业级高要求场景使用。
6738 30
阿里云百炼Qwen3.7-Max简介:能力、优势、支持订阅计划参考
|
3天前
|
数据采集 人工智能 前端开发
让 Coding Agent 从黑盒到透明:阿里云 Agent 观测审计数据采集实践
AI Agent 规模化落地带来执行黑盒、行为难追溯、成本难度量三大难题。阿里云基于 OTel 标准,面向 Coding Agent、个人通用助理和框架型 Agent,推出 LoongSuite Pilot、插件及探针等无侵入采集方案,让 Agent 实现可看见、可分析、可审计、可治理。
603 138
|
3天前
|
人工智能 弹性计算 运维
阿里云发布堡垒机智能运维Agent,运维交互进入自然语言新时代
支持自然语言运维,提升效率与安全双保障。
1143 0
|
10天前
|
人工智能 安全 定位技术
CodeGraph深度解析 让Claude Code工具调用直降七成的核心原理与实操教程
如今以Claude Code为代表的AI编程智能体已经成为开发者日常编码、项目重构、漏洞修复的必备工具。但在长期使用过程中,几乎所有开发者都会遇到同一个明显痛点:AI虽然具备强大的代码生成与分析能力,却常常陷入盲目探索的循环中。
1158 1
|
13天前
|
存储 定位技术 数据库
CodeGraph 如何让 Claude Code减少 7 成工具调用?
CodeGraph 为 Coding Agent 提供本地代码知识图谱,把函数、类、调用链和框架路由提前整理成“项目地图”,减少盲目搜索和文件读取。它不是新 Agent,而是上下文基础设施,让 Agent 更快找到正确代码路径,平均减少 7 成工具调用。
1269 3
|
10天前
|
人工智能 弹性计算 安全
阿里云618活动时间、活动入口、优惠活动详细解读
2026年阿里云618创新加速季已全面开启,作为年度力度最大的云产品促销活动,本次大促覆盖轻量应用服务器、ECS云服务器、GPU云服务器、数据库、AI算力、安全服务、CDN等全品类产品,推出5亿元算力补贴、新用户限时秒杀、普惠满减、企业专享、免费试用、云大使返佣等多重福利,个人开发者、中小企业、AI团队均可享受专属低价。本文将系统梳理2026年阿里云618活动的完整时间节点、官方参与入口、各类优惠细则、使用规则、热门产品推荐及实操代码,帮助用户精准参与、高效省钱,以最低成本完成上云部署。
948 5
|
9天前
|
人工智能 自然语言处理 安全
Vibe Coding 实战:别盲目跟风,先分清 vibe coding 适合什么场景
本文系统总结vibe coding实战经验:明确其适用场景(原型、小工具、标准化模块),剖析5步落地流程(场景判定→结构化提示词→目录初始化→分模块生成→自动化校验),指出四大常见误区,并推荐适配工具Trae。强调“场景匹配+规则前置”是提效关键,避免盲目套用。
787 1