编程精粹—— Microsoft 编写优质无错 C 程序秘诀 02:设计并使用断言

这是一本老书,作者 Steve Maguire 在微软工作期间写了这本书,英文版于 1993 年发布。2013 年推出了 20 周年纪念第二版。我们看到的标题是中译版名字,英文版的名字是《Writing Clean Code ─── Microsoft’s Techniques for Developing》,这本书主要讨论如何编写健壮、高质量的代码。作者在书中分享了许多实际编程的技巧和经验,旨在帮助开发人员避免常见的编程错误,提高代码的可靠性和可维护性。


不记录,等于没读。本文记录书中第二章内容:设计并使用断言。


一个好的开发策略是维护程序的两个版本:发布版本调试版本。通过在调试版本中使用 断言 语句,你可以检测以下错误:

  • 错误的函数参数
  • 未定义行为
  • 其他程序员做出的错误假设
  • 不知何故出现的不可能条件

仅在调试模式下启用的备用算法有助于验证函数结果和函数中所使用算法的正确性。

第一章介绍了一些方法,以便检测到更多的错误,比如启用所有可选的编译器警告、使用使用语法和可移植性检查工具等。这些措施当然是好的,这是这些措施只能查出所有错误的一小部分。举一个例子:

strCopy = memcpy(malloc(length), str, length);

这句代码在多数情况下都会工作良好,直到 malloc 函数调用失败。当 malloc 失败时,就会给 memcpy 函数返回 NULL 指针,从而出错。

编译器检查不出这种错误,同样,编译器也检查不出算法错误,无法验证程序员所作的假设。寻找这种错误非常艰苦,但如果在实现 memcpy 函数时,检查一下参数是否有效,就可以自动捕获这个错误。

聪明的程序员将调试代码隐藏在断言 assert。断言的好处是用户在错误发生时,可以自动地把它们检查出来。

assert 是一个宏,如果其参数的计算结果为假,就中止调用程序的执行。

assert不应该产生其它副作用。因此assert应该为宏而不是函数,因为函数调用会引起不期望的内存或代码交换。

使用断言对函数参数进行确认

比如前面提到的 memcpy 函数,我们可以使用断言进行参数确认,代码如下:

void memcpy(void* pvTo, void* pvFrom, size_t size) {
	void* pbTo = (byte*)pvTo;
	void* pbFrom = (byte*)pvFrom;
	
	assert(pvTo != NULL && pvFrom != NULL);		//<--- 使用断言
	
	while(size-->0)
		*pbTo++ == *pbFrom++;
		
	return(pvTo);
}

这样,当给参数 pvTopvFrom 传递 NULL 时,就会触发断言,从而自动的发现程序代码错误。

使用断言避免未定义行为

要从程序中删除未定义的特性或者在程序中使用断言来检查出未定义特性的非法使用

使用库函数memcpy时,如果在存储空间相互重叠的对象之间进行了拷贝,结果是未定义的。

可以使用断言来进行重叠检查:

ASSERT(pbTo >= pbFrom + size || pbFrom >= pbTo + size);

假如你之前从来没有见过重叠检查,当这个断言捕获到错误时,你能看出来它是什么意思吗?

断言捕获了一个错误,我们却不知道断言的作用,没有什么比这件事更令人沮丧了。

如果搞不清楚相应断言检查的是什么,就很难知道错误是出现在程序中,还是出现在断言中。

解决这个问题的方法是:给不够清晰的断言加上注解

不要浪费别人的时间——详细说明不清晰的断言

一个人在穿过森林时,看到树上钉着一块大牌子,上面写着两个红色大字:“危险”。但危险到底是什么?

除非告诉人们危险是什么或者危险非常明显,否则这个牌子起不到提高人们警觉的作用,人们会忽视牌子上的警告。

同样,程序员不理解的断言也会被忽视。

利用断言消除隐式假设

如果假设long占用4个字节,可以使用以下断言来检查假设是否正确:

ASSERT(sizeof(long) == 4);

在编写函数时,要进行反复的思考,并且自问:“我打算做哪些假设?”

一旦确定了相应的假设,就要使用断言对所做的假设进行检验,或者重新编写代码去掉相应的假设。

另外,还要问:“这个程序中最可能出错的是什么,怎样才能自动地查出相应的错误?”努力编写出能够尽早查出错误的测试程序。

利用断言检查不可能发生的情况

断言是检查那些在正常情况下绝不应该发生的情况,而不是用于检查错误。换句话说,断言是防止程序员失误的一种手段,绝不要把必须执行的代码放入断言中

比如函数参数不可能超过某个范围,可使用断言来验证;malloc 函数返回是否为 NULL 就不能使用断言来检查,判断是否为 NULL 是错误检查的一部分;

比如一个解压缩协议规定:如果遇到0xA5,那么0xA5后面必须是 字符压缩序列,字符压缩序列占用两个字节,第一个字节表示 压缩的字符,第二个字节表示 字符重复的个数

我们认为 字符序列 只能有两种情况:

  1. 压缩的字符是 0xA5,字符重复个数固定为1
  2. 压缩的字符不是 0xA5,字符重复个数必须大于等于4

则可以使用以下断言对规则进行验证:

#define REPEAT_CODE    0xA5
//...
ASSERT( size>=4 || (size==1 && b==bRepeatCode) )

如果这一断言失败说明内容不对或者字符压缩程序中有错误。无论哪种情况都是错误,而且是不用断言就很难发现的错误。

利用断言发现静默的异常行为

防错性程序设计常常被誉为有较好的编码风格,但它却隐瞒了绝不应该发生的错误。

核反应堆堆芯过热,程序自动地向堆芯灌水、插入冷却棒或者做其它能让堆芯冷却下来的方法。只要程序已经控制了事态,就不会向有关人员报警。

这和我们写的防错程序很像,当某些意料不到的事情发生时,程序只进行静默处理。

但是堆芯不会无缘无故地出现过热现象,一定是发生了某种不同寻常的事情,才会引起这一故障。但在值班人员眼里,核反应堆一切正常,错误被隐瞒了。

即便如此,防错性程序仍然有它的价值。我们希望在进行防错性程序设计时,不要隐瞒错误

一方面一如既往地利用防错性程序设计进行编码,另一方面在事情变糟的情况下利用断言进行报警

核反应堆堆芯过热,立即向工作人员发出警报。同时,程序自动地向堆芯灌水、插入冷却棒或者做其它能让堆芯冷却下来的方法。

如果使用到防错性程序设计,在编码之前都要问自己:**“在进行防错性程序设计时,程序中隐瞒错误了吗?”**如果答案是肯定的,就要在程序中加上相应的断言,以对这些错误进行报警。


有很多书都有讲解断言,有很多开源代码都在使用断言。在工作了几年之后,我对断言也有了一些见解,我将它们写在了《随想012:断言》 这篇文章中,推荐阅读。






每一份打赏,都是对创作者劳动的肯定与回报。
千金难买知识,但可以买好多奶粉

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/718951.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

51单片机宏定义的例子

代码 demo.c #include "hardware.h"void delay() {volatile unsigned int n;for(n 0; n < 50000; n); }int main(void) {IO_init();while(1){PINSET(LED);delay();PINCLR(LED);delay();}return 0; }cfg.h #ifndef _CFG_H_ #define _CFG_H_// #define F_CPU …

nacos注册中心配置中心集群搭建

文章目录 学习连接1.Nacos安装与简单使用1.1. Nacos安装指南Windows安装下载安装包解压端口配置启动访问 Linux安装安装JDK上传安装包解压端口配置启动 1.2.服务注册到nacos使用步骤引入依赖配置nacos地址重启 示例父工程pom.xmluser-servicepom.xmlapplication.ymlUserApplica…

Jupyter Notebook 中 %run 魔法命令

目录 基本用法运行 Python 脚本运行 Jupyter Notebook 的其他单元格传递命令行参数 示例运行 Python 脚本示例运行其他 Jupyter Notebook 示例传递命令行参数示例 注意事项与 import 命令的区别%runimport 结论 %run 是 Jupyter Notebook 中的一个强大工具&#xff0c;它允许你…

【机器学习】第4章 决策树算法(重点)

一、概念 1.原理看图&#xff0c;非常简单&#xff1a; &#xff08;1&#xff09;蓝的是节点&#xff0c;白的是分支&#xff08;条件&#xff0c;或者说是特征&#xff0c;属性&#xff0c;也可以直接写线上&#xff0c;看题目有没有要求&#xff09;&#xff0c; &#xff0…

MySQL----InooDB行级锁、间隙锁

行级锁 行锁&#xff0c;也称为记录锁&#xff0c;顾名思义就是在记录上加的锁。 注意&#xff1a; InnoDB行锁是通过给索引上的索引项加锁来实现的&#xff0c;而不是给表的行记录加锁实现的&#xff0c;这就意味着只有通过索引条件检索数据&#xff0c;InnoDB才使用行级锁…

【开发工具】git服务器端安装部署+客户端配置

自己安装一个轻量级的git服务端&#xff0c;仅仅作为代码维护&#xff0c;尤其适合个人代码管理。毕竟代码的版本管理是很有必要的。 这里把git服务端部署在centos系统里&#xff0c;部署完成后可以通过命令行推拉代码&#xff0c;进行版本和用户管理。 一、服务端安装配置 …

【Kubernetes】k8s--安全机制

机制说明 Kubernetes 作为一个分布式集群的管理工具&#xff0c;保证集群的安全性是其一个重要的任务。API Server 是集群内部各个组件通信的中介&#xff0c; 也是外部控制的入口。所以 Kubernetes 的安全机制基本就是围绕保护 API Server 来设计的。 比如 kubectl 如果想向 …

新版FMEA培训内容中关于团队协作的部分可以怎么展开?

团队协作&#xff0c;作为新版FMEA的核心要素之一&#xff0c;其重要性不言而喻。在FMEA的分析过程中&#xff0c;团队成员的密切合作与沟通是确保分析全面性和准确性的关键。通过团队协作&#xff0c;不同领域的专家能够共同参与到潜在故障模式的识别、评估与预防中来&#xf…

解决ubuntu22.04共享文件夹问题

刚开机发现ubuntu里面的共享文件夹访问不了了 ubuntuwxy:/mnt/hgfs$ ls找了几篇博客&#xff0c;设置如下指令即可&#xff0c;记得退出当前目录重新进入刷新一下 sudo vmhgfs-fuse .host:/ /mnt/hgfs/ -o allow_other -o uid1000 仅供参考

针对indexedDB的简易封装

连接数据库 我们首先创建一个DBManager类&#xff0c;通过这个类new出来的对象管理一个数据库 具体关于indexedDB的相关内容可以看我的这篇博客 indexedDB class DBManager{}我们首先需要打开数据库&#xff0c;打开数据库需要数据库名和该数据库的版本 constructor(dbName,…

[WTL/Win32]_[中级]_[MVP架构在实际项目中应用的地方]

场景 在开发Windows和macOS的界面软件时&#xff0c;Windows用的是WTL/Win32技术&#xff0c;而macOS用的是Cocoa技术。而两种技术的本地语言一个主打是C,另一个却是Object-c。界面软件的源码随着项目功能增多而增多&#xff0c;这就会给同步Windows和macOS的功能造成很大负担…

Aigtek高压放大器在柔性爬行机器人驱动性能研究中的应用

实验名称&#xff1a;柔性爬行机器人的材料测试 研究方向&#xff1a;介电弹性体的最小能量结构是一种利用DE材料的电致变形与柔性框架形变相结合设计的新型柔性驱动器&#xff0c;所谓最小能量是指驱动器在平衡状态时整个系统的能量最小&#xff0c;当系统在外界的电压刺激下就…

开发一个python工具,pdf转图片,并且截成单个图片,然后修整没用的白边

今天推荐一键款本人开发的pdf转单张图片并截取没有用的白边工具 一、开发背景&#xff1a; 业务需要将一个pdf文件展示在前端显示&#xff0c;但是基于各种原因&#xff0c;放弃了h5使用插件展示 原因有多个&#xff0c;文件资源太大加载太慢、pdf展示兼容性问题、pdf展示效果…

应急便携式气象观测站

TH-BQX5自然灾害&#xff0c;如台风、暴雨、洪涝、干旱等&#xff0c;给人们的生命财产安全带来了巨大威胁。在应对这些灾害时&#xff0c;准确的气象观测数据是制定有效应对策略的基础。近年来&#xff0c;应急便携式气象观测站在自然灾害的监测和预警中发挥了越来越重要的作用…

在 Blazor 中在子组件和父组件之间共享数据

介绍 可以在Blazor 中创建一个子组件并在另一个组件中重用它。我们将非常轻松地在这些组件之间共享数据。我们将创建一个自定义文本框作为子组件。此自定义文本框将显示文本框中的当前字符数&#xff0c;并在需要时限制字符总数。我将逐步解释所有操作。 在 Visual Studio 中…

购物App需要进行软件测试吗?包括哪些测试内容?

随着移动互联网的飞速发展&#xff0c;购物App在人们的日常生活中扮演着越来越重要的角色。然而&#xff0c;由于App开发的复杂性和用户对于购物体验的高要求&#xff0c;保证App的质量成为了一项重要的任务。而软件测试作为确保App质量的关键环节&#xff0c;也日益受到重视。…

文件操作(1)(C语言版)

前言&#xff1a; 为什么要学习文件操作&#xff1a; 1、如果大家写过一些代码&#xff0c;当运行结束的时候&#xff0c;这些运行结果将不复存在&#xff0c;除非&#xff0c;再次运行时这些结果才能展现在屏幕上面&#xff0c;就比如之前写过的通讯录。 现实中的通讯录可以保…

智游剪辑手机版发布!

耗时一个多月&#xff0c;手机版终于开发的差不多了&#xff0c;下面带大家一起来看下效果咋样吧&#xff01; 功能介绍 打开应用就可以直接看到我们的所有功能了&#xff0c;支持分类查看和关键词搜索功能&#xff0c;每个功能都可以查看帮助教程和收藏&#xff0c;点击即可进…

Day40

Day40 监听器 概念&#xff1a; 监听器用于监听web应用中某些对象信息的创建、销毁、增加&#xff0c;修改&#xff0c;删除等动作的 发生&#xff0c;然后作出相应的响应处理。当范围对象的状态发生变化的时候&#xff0c;服务器自动调用 监听器对象中的方法。 常用于统计在线…

AWS——01篇(AWS入门 以及 AWS之EC2实例及简单实用)AWS

AWS——01篇&#xff08;AWS入门 以及 AWS之EC2实例及简单实用&#xff09; 1. 前言 2. 创建AWS账户 3. EC2 3.1 启动 EC2 新实例 3.1.1 入口 3.1.2 设置名称 选择服务 3.1.3 创建密钥对 3.1.4 网络设置——安全组 3.1.4.1 初始设置 3.1.4.2 添加安全组规则&#xff08;开放新…