代码整洁之道 — 6 注释规范

目录

1 注释不能美化糟糕的代码

2 好注释

3 坏注释

3.1 注释要准确

3.2 勿过度注释

3.3 保持注释整洁


1 注释不能美化糟糕的代码

带有少量注释的整洁而有表达力的代码,要比带有大量注释的零碎而复杂的代码像样得多。与其花时间编写解释你搞出的糟糕的代码的注释,不如花时间清洁那堆糟糕的代码。

2 好注释

(1)法律信息注释:公司代码规范有时要求写与法律相关注释,像版权及著作权声明等应放在源文件开头注释处,且尽量指向外部标准许可文档,避免把大量条款全放注释里。

/*
 * Copyright (C) [年份] by [版权所有者]
 * All rights reserved.
 *
 * [许可证信息]
 */

(2)提供信息的注释:可用来提供基本信息,但更好的做法是利用函数名传达信息,让注释变得多余。例如,以下注释解释了函数的返回值:

// 返回两个整数中较大的一个
int compare(int a, int b) {
    return a > b ? a : b;
}

注释提醒了函数的返回值,但更好的方式是尽量利用函数名称传达信息。比如,在本例中,只要把函数重新命名为getMax,注释就是多余的了。下面的例子中,注释说明了正则表达式的用途:

// 匹配一个利用特定格式字符串格式化的时间和日期
const char* regex = "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}";

 为了减少注释,我们可以将正则表达式的用途和代码移到一个专门处理日期和时间格式化的结构体中,这样注释就变得多余了:

typedef struct {
    const char* format;
    const char* regex;
} DateTimeFormatter;

DateTimeFormatter formatter = {
    .format = "%Y-%m-%dT%H:%M:%S",
    .regex = "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}"
};

在这个结构体中,format regex 成员变量的名称已经足够说明它们的用途,因此不需要额外的注释。这样的代码结构更加清晰,也更容易理解。

(3)对意图的解释注释:注释不仅提供了有关实现的有用信息,还能展现某个决定背后意图,方便他人理解程序员的想法。

/*@brief:安全的字符串复制函数
 *@param:dest      目标字符串的指针。
 *       src       源字符串的指针。
 *       destSize  目标字符串缓冲区的大小。
 *@return:成功复制返回0,如果目标缓冲区太小而无法容纳源字符串,则返回-1。
 */
int safeStrcpy(char *dest, const char *src, size_t destSize) 
{
    if (dest == NULL || src == NULL) {
        return -1;
    }

    size_t i = 0;
    while (src[i] != '\0' && i < destSize - 1) {
        dest[i] = src[i];
        i++;
    }

    dest[i] = '\0';
    if (src[i] != '\0') {
        return -1;
    }
    return 0; 
}

(4)阐释注释:用于把晦涩参数或返回值意义翻译为可读形式,不过存在注释本身不正确风险,写这类注释前要考虑有无更好办法并谨慎确认正确性。

(5)警示注释:可警告其他程序员可能出现的后果,虽现在有其他替代做法(如 JUnit4 里的@Ignore属性),但以前常用注释方式,且在合适场景下注释还是有用的,能避免一些不当做法。

(6)TODO 注释:有时,用//TODO形式在源代码中放置要做的工作列表,TODO是一种程序员认为应该做,但由于某些原因目前还没做的工作。

void error_handler(int error_code) {
    // 错误处理逻辑
    // TODO: 根据错误代码执行不同的错误处理
    printf("Error occurred with code: %d\n", error_code);
}

现在,许多 IDE 都具备了专门用来查找所有TODO注释的功能。TODO只是暂时性的提醒注释,因此,定期检查这些注释,并删除那些已经不再需要的,是一个很好的习惯。

3 坏注释

3.1 注释要准确

(1)喃喃自语:在编写代码时,注释的质量至关重要。仅仅因为觉得需要添加注释而随意写上几句,并不会带来任何好处。如果你决定添加注释,就应该确保注释是清晰、准确的。例如,嵌入式系统用于初始化硬件设备的代码,这些注释却显得含糊不清,甚至可能引起困惑。

void initializeHardware() {
    // 初始化相关功能
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    // 使能一些相关配置
    __HAL_RCC_GPIOA_CLK_ENABLE();
}

仅仅写着 “初始化相关功能”“使能一些相关配置” 太过笼统宽泛,没有指出到底是针对整个硬件系统的哪一部分具体功能在进行初始化。

(2)误导性注释:误导性注释会对阅读代码的人产生误导,使他们对代码的理解和预期产生偏差。例如,有一个用于控制嵌入式系统中LED灯的C语言函数,如下所示:

void LED_On(void) {
    // 关闭LED灯
    GPIO_ResetBits(GPIOC, GPIO_PIN_13); 
}

注释明确指出函数的目的是“打开LED灯”,然而,实际的代码却是调用GPIO_ResetBits来关闭LED灯。

3.2 勿过度注释

(1)函数头:短函数不需要太多描述,对于只做一件事的短函数,选个好名字比写函数头注释要好。

(2)多余的注释:下面代码中其头部位置的@note部分注释全属多余,读这段注释花的时间没准比读代码花的时间还要长。

/**
 * @brief 初始化时钟系统。
 * @note 该函数配置微控制器的时钟源,包括外部晶振(HSE)、内部高速时钟(HSI)和PLL。它还设置系统时钟(SYSCLK)、AHB总线时钟(HCLK)和APB总线时钟(PCLK1和PCLK2)。此外,该函数还配置了时钟安全系统(CSS),以防止时钟故障。该函数必须在任何外设初始化之前调用,以确保系统时钟正确配置。如果HSE未启动或配置失败,系统将使用HSI作为时钟源。
 * @param None
 * @return None
 */
void SystemClock_Config(void) {
    // 使能时钟控制寄存器的时钟
    __HAL_RCC_SYSCFG_CLK_ENABLE();

    // 配置外部高速时钟(HSE)
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
    RCC_OscInitStruct.HSEState = RCC_HSE_ON;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
    RCC_OscInitStruct.PLL.PLLM = 8;
    RCC_OscInitStruct.PLL.PLLN = 336;
    RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
    RCC_OscInitStruct.PLL.PLLQ = 7;
    if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {
        Error_Handler();
    }
}

这段注释并不能比代码本身提供更多的信息,而且在该领域编程人员完全可以看懂各行代码的含义。

(3)过度注释:当每个函数和变量都被要求添加注释时,可能会导致过度注释。如果注释没有提供额外的价值,它们只会增加代码的冗余,而不是提高可读性。

(4)日志式注释:记录每次修改的注释方式,类似于日志。这种注释风格在没有源代码控制系统(如git)的年代可能有一定的价值,但在现代开发实践中,它被认为是不必要的,甚至有害的。

// 2024-01-01: 张三 - 添加了LED初始化代码
// 2024-02-15: 李四 - 修改了LED亮度调节逻辑
void LED_Init(void) {
    // 初始化LED的GPIO端口
    // ...
}

3.3 保持注释整洁

(1)位置标记:程序员有时喜欢在源代码中标记某个特别位置,例如:

// Actions 

把特定函数放在这种标记栏下面多数时候是无理的,尤其是尾部那一串无用的斜杠是应该删除的。

(2)括号后面的注释:有时,会在括号后面放置特殊的注释。这对于含有嵌套结构的长函数可能有意义,但我们更愿意缩短函数,理清代码逻辑结构。

// 不推荐的做法:使用括号后的注释来标记代码块结束
if (condition1) { // 条件1成立
    if (condition2) { // 条件2成立
        // 执行操作
    } 
} 

(3)归属与署名:源代码控制系统非常善于记住是谁在何时添加了什么。没必要用那些小小的签名搞脏代码。你也许会认为,这种注释大概有助于他人了解应该和谁讨论这段代码。不过,事实却是注释在那儿放了一年又一年,越来越不准确,越来越和原作者没关系。

/* Added by Rick */

(4)注释掉的代码:直接把代码注释掉是讨厌的做法,其他人不敢删除注释掉的代码。他们会想,代码依然放在那儿,一定有其原因,不能删除。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF - 8">
    <title>HTML Comment Example</title>
</head>
<body>
<?php
// 以下是一个简单的PHP函数
function greet($name) {
    // <strong>这个函数用于打招呼</strong>
    return "Hello, ". $name;
}
?>
</body>
</html>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

几度春风里

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值