编程精粹—— Microsoft 编写优质无错 C 程序秘诀 01:假想的编译器

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

When I set out to write the first edition of Writing Solid Code twenty years ago, I had a simple thought in mind: Give programmers proven tools, techniques, and philosophies to help them write rock-solid, bug-free code.

二十年前,当我开始编写《Writing Solid Code》第一版时,我心中有一个简单想法:给程序员们提供经过验证的工具、技术和开发哲学,帮助他们编写出坚如磐石、零错误的代码。

Over the years, programmers have regularly asked me if my views have changed much since the book’s original publication.

多年来,程序员们经常问我,自从该书首次出版以来,我的观点是否发生了很大变化。

Not only have my views not changed over all those years, but I’ve embraced the concepts and philosophies expressed in the book’s pages even more staunchly.

这么多年来,我的观点不仅没有改变,反而更加坚定地支持书中阐述的概念和开发哲学.

I had no idea that the book would become a runaway best-seller, eventually being translated into more than 16 different languages. I certainly didn’t expect that so many software development companies would make the book required reading for their developers. Nor did I anticipate that Universities around the world would use the book in their computer science courses.

我完全没有想到这本书会成为畅销书,最终还被翻译成超过16种不同的语言。我当然也没有预料到会有这么多软件开发公司将这本书列为他们的开发人员的必读书目。我也没有预料到全球各地的大学会在他们的计算机科学课程中采用这本书。

作者 Steve Maguire 曾在微软担任高级项目经理和软件工程师。这是作者的第一本书,它的后续之作是《Debugging the Development Process》,中译版名字为《微软研发:致胜策略》,我会在将来介绍这本书。在这两本书中,他分享了大量关于他在微软工作期间的经验和见解。


编写无错误代码的关键是要更加了解 错误 是如何产生的。多年以来,作者经常问自己两个关键的问题,从这两个问题中得到的答案,形成了本书介绍的诀窍。程序员遇到的每一个错误,都要问自己这两个关键的问题:

  1. 怎样才能自动地查出这个错误?

    How could I have automatically detected this bug?

  2. 怎样才能避免这个错误?

    How could I have prevented this bug?

本书提供的指南在大多数情况下你都应该遵循,但当你打破它们时可以获得更好的结果时,那么就打破它

要记住:

在任何时候,跟在大多数人的后面常常是所能选择的最坏道路。因此在成为别人的追随者之前一定要确定这样做确实有意义,而不是仅仅因为其他人如此自己也亦步亦趋。


不记录,等于没读。本文记录书中第一章内容:假想的编译器。


假想的编译器

如果存在一个理想的编译器,它能检测到程序中的每个错误并给出错误信息,那么消除代码中的错误将会非常简单。问题是,这种无所不能的编译器并不存在,但是我们可以有一些方法,可以自动检测到更多的错误:

  • 启用所有可选的编译器警告
  • 使用语法和可移植性检查工具(比如 CppcheckPC-LIntSplint 等 )
  • 使用自动化单元测试

先考察一下测试人员是如何发现 BUG 的:

  1. 向程序输入数据,然后观察输出。
  2. 如果他注意到一些数字是错误的,或者某个功能没达到预期,或者程序崩溃了,那么就发现了一个BUG。

如果他输入的数据恰好无法触发 BUG 呢?如果他一时疏忽错过异常现象呢?那么这个 BUG 就会溜到正式版本中,然后在将来的某个时候被用户遇到。

所以说,测试人员发现 BUG 是靠运气吗

是的。

这并不是批判测试人员的所做所为的,这只是想指出一个事实:很难用黑盒测试一个程序。利用黑盒测试能做的只是往程序里填数据,并看它输出什么。这就好比确定一个人是不是疯子一样:你问一些问题,你倾听对方回答,然后你做出判断。但这样你还是不能确定这人是不是疯子,因为你不知道对方脑袋里在想些什么。

你永远会质疑黑盒测试的输入数据

  • 数据够吗?
  • 数据对吗?

不要依赖黑盒测试。去做一些事情,抓住每一个机会 自动地 捕获BUG,而不是靠运气。这些事情包括:

  • 启用所有可选的编译器警告
    好的编译器能够把屡次出错的合法 C 习惯用法看成是拼写错误。比如某些编译器可以将警告级别设置为 MISRA C 2004 或者 MISRA C 2012,而 MISRA C 是一套用于嵌入式系统中编写高可靠性和高安全性软件的编码准则,最初是为汽车行业制定。它实际上是标准 C 的一个子集,对标准 C 附加了一系列规则和指导。使用 MISRA C 会更严格的检测代码,有些标准 C 允许的用法会被警告。本文附录 1 给出一些这样的例子。
  • 使用语法和可移植性检查工具(比如 CppcheckPC-LintSplint 等 )
    通常这些检查器检查的错误更详细、更彻底。一旦原程序变成了没有 PC-Lint 错误的形式,继续保持就变得很简单了。
  • 如果有单元测试,请使用它们
    不要过高估计自己编写正确代码的能力,要做测试!即使没有新增代码,只是调整代码;或者做很小的修改都要运行单元测试。
    有时,似乎可以跳过一些安全措施,这些安全措施用来避免程序出错。但走捷径之时,就是麻烦将近之日

消除代码中 BUG 的最好方法是找到它们,越早越好。寻找自动捕获 BUG 的方法。

努力减少程序员迫不得已的排错,优先让编译器和工具(比如 PC-Lint)指明错误。

附录 1

1 while 循环错放了一个分号

如下代码给出了一个复制内存代码,但 while 表达式后误写了一个分号。

void* memcpy(void *pvTo, void *pvFrom, size_t size) {
	byte *pbTo = (byte *)pvTo; 
	byte *pbFrom = (byte *)pvFrom; 
	while(size-->0); 					//<--- 这里错误的键入了分号
		*pbTo++ = *pbFrom++; 
	return(pvTo); 
}

对于这个代码,逻辑上是错误的,但编译器认为这是一个完全合法的 while 语句,其循环体为空语句。由于使用空语句的场景比较少,所以编译器常常在遇到循环体为空语句时给出一条可选的警告信息,如果你选择启用这个警告信息,编译器就会自动提醒你注意这样的错误。如果你真正要使用循环体为空语句的时候,可以使用编译手册建议的解决方法,比如使用一个将被优化掉的常量表达式 ( NULL ),或者使用一个空块 ({ })。这里使用空块举例:

char *strcpy(char *pchTo, char *pchFrom) {
	char *pchStart = pchTo;
	while(*pchTo++ = *pchFrom++)
		{}								//<--- 这里使用了空块
	
	return (pchStart);
}

2 错误的赋值

C 语言允许在编写表达式的任何地方使用赋值 (=) ,但如果不小心,这种额外的灵活性可能带来错误。比如:

if(ch = '\t')							//<--- 本想与制表符比较,但实际是赋值
	ExpandTab();

这种代码编译器不会产生错误,因为代码是合法的。现代的编译器一般能检查到这种可能的错误,并给出警告信息。除了 if 控制表达式外,还有:

  • forwhile 控制表达式
  • &&|| 表达式

在以上表达式中,如果确实需要使用赋值,比如上面例子的拷贝代码:

while(*pchTo++ = *pchFrom++)			//<--- 这里的赋值可能会产生编译警告
	{}

为了不让编译器产生警告信息,可以改写成:

while(()*pchTo++ = *pchFrom++) != '\0')
	{}






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

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

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

相关文章

34、shell数组+正则表达式命令

0、课前补充 jiafa () { result$(echo " $1 $2 " | bc ) print "%.2f\n" "$result" } ##保留小数点两位 薄弱加强点 a$(df -h | awk NR>1 {print $5} | tr -d %) echo "$a"一、数组 1.1、定义 数组的定义&am…

Native开发工具之应用开发编辑器打包发布(一)

Nuclide 是基于 Atom 之上构建的单独的一个包&#xff0c;其提供可编程性且社区非常活跃。它为 React Native、Hack 和 Flow 项目提供一流的开发环境。 2. Atom 官网&#xff1a;https://atom.io/ Github 项目地址&#xff1a;atom(https://github.com/atom) 文档&#xff1…

SpringBoot-注解@PropertiySource读取外部属性文件

ConfigurationProperties和Value两个注解能从配置文件中获取数据&#xff0c;但是前面讲了他们是从全局配置文件中获取&#xff0c;且只能从全局配置文件中获取&#xff0c;那么如果是一些数值类的数据放在全局配置文件里&#xff0c;是不怎么合适的&#xff0c;我们往往会把他…

gitlab 获取指定分支下指定路径文件夹的解决方案

第一步&#xff1a; 获取 accessToken 及你的 项目 id &#xff1a; 获取 accessToken ,点击用户头像进入setting 按图示操作&#xff0c;第 3 步 填写你发起请求的域名。 获取项目 id , 简单粗暴方案 进入 你项目仓库页面后 直接 源码搜索 project_id&#xff0c; value 就…

QT自定义标题栏窗口其一:实现拖动及可拉伸效果

1、效果 2、核心代码 #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent<

Android intent 打开链接跳转到外部浏览

前言: 各位同学大家好, 最近接到一个比较诡异的需求 ,不是通常的webview 加URL显示网页 是需要跳转到外部浏览器 ,我这边处理好了就分享给大家 效果图 : 点几就跳转到外部浏览器 如图 具体代码实现: 点击打开链接并跳转外部浏览器方法 public void openBrowser(Con…

算法刷题总结

1. 排序算法 1.1 快速排序算法 public abstract class Sort<T extends Comparable<T>> {public abstract void sort(T[] array);protected boolean less(T first, T two) {return first.compareTo(two) < 0;}protected void swap(T[] array, int i, int j) {T…

《人生苦短,我用python·四》pybind11多场景使用

引言 Pybind11作为一个强大的工具&#xff0c;不仅可以轻松地将简单的C函数和类暴露给Python&#xff0c;还可以处理更复杂的场景&#xff0c;比如支持C标准库容器、处理C异常、以及自定义数据结构的转换。本文将深入介绍Pybind11的一些高级用法&#xff0c;帮助你在实际项目中…

修复 pprof ---node_exproter访问漏洞(go-pprof-leak)

前言&#xff1a; ** 在Go语言中&#xff0c;pprof和debug包是用来检测和避免goroutine泄漏&#xff0c;避免导致goroutine泄漏&#xff0c;进而消耗大量系统资源。不过对于安全而言确又存在一定风险&#xff0c;** 风险&#xff1a; 通过node_exporter web发现 190.168.46.1…

Unity Meta Quest 开发:关闭 MR 应用的安全边界

社区链接&#xff1a; SpatialXR社区&#xff1a;完整课程、项目下载、项目孵化宣发、答疑、投融资、专属圈子 &#x1f4d5;教程说明 这期教程我将介绍如何在应用中关闭 Quest 系统的安全边界。 视频讲解&#xff1a; https://www.bilibili.com/video/BV1Gm42157Zi 在 Unity…

DVWA 靶场 Weak Session IDs 通关解析

前言 DVWA代表Damn Vulnerable Web Application&#xff0c;是一个用于学习和练习Web应用程序漏洞的开源漏洞应用程序。它被设计成一个易于安装和配置的漏洞应用程序&#xff0c;旨在帮助安全专业人员和爱好者了解和熟悉不同类型的Web应用程序漏洞。 DVWA提供了一系列的漏洞场…

VirtualBox虚拟机声音设置

最近发现VirtualBox创建的Windows 10和11虚拟机没有声音&#xff0c;但是另外一个Windows 7的虚拟机确有声音&#xff0c;检查对比了一下虚拟机的声音设置&#xff0c;发现是Host Audio Driver的设置不一样&#xff0c;Windows10和11的是Default&#xff0c;而Windows7的是Puls…

【C++】初始化列表、匿名对象、static成员、友元、内部类

文章目录 一、初始化列表构造函数体赋值初始化列表explicit关键字 二、匿名对象三、static成员四、友元友元函数友元类 五、内部类六、练习题 一、初始化列表 构造函数体赋值 实际上&#xff0c;构造函数的函数体内&#xff0c;并不是对 对象 初始化的地方&#xff0c;而是对…

html做一个雷达图的软件

要实现一个在线输入数据并生成雷达图的功能&#xff0c;可以使用HTML表单和JavaScript来处理用户输入的数据。以下是一个示例代码&#xff0c;演示了如何实现这个功能&#xff1a; <!DOCTYPE html> <html lang"zh"> <head><meta charset"…

C++初学者指南第一步---13.聚合类型

C初学者指南第一步—13.聚合类型 文章目录 C初学者指南第一步---13.聚合类型1. 类型分类&#xff08;简化&#xff09;2. 如何定义和使用3. 为什么选择自定义类型/数据聚合&#xff1f;4. 聚合类型初始化5.混合6. 复制7. 值和引用的语义8.聚合的向量(std::vector)9.最令人烦恼的…

文件创建与查看

touch touch命令用于创建一个新的文件。 语法&#xff1a;touch Linux路径 其中路径可以是相对路径、绝对路径或者特殊路径符都可以。 改图展示了通过 touch test.txt 命令创建了一个 test.txt文件&#xff0c;其中深色的代表文件夹&#xff0c;白色的代表文件。 使用 ls -lh…

React学习(二)——状态(数据)与状态修改

useState 在React中&#xff0c;useState 是一个非常重要的Hook&#xff0c;它允许你在函数组件中添加“状态”&#xff08;state&#xff09;。在传统的React类组件中&#xff0c;我们使用this.state来管理和更新组件的状态。然而&#xff0c;在函数组件中&#xff0c;由于它们…

一个关于空格的Sql Server面试题

引子 先上题目&#xff1a; 回答下面sql 的输出结果 declare s1 varchar(10) declare s2 varchar(10) set s1a b set s2a b if s1s2 select true 答案是 true 那么上面的 s1 和 s2 是否相等的呢&#xff1f; 我们再看看下面的sql declare s1 varchar(10) declare s2 …

鞋子分类数据集17399张69类别

数据集类型&#xff1a;图像分类用&#xff0c;不可用于目标检测无标注文件 数据集格式&#xff1a;仅仅包含jpg图片&#xff0c;每个类别文件夹下面存放着对应图片 图片数量(jpg文件个数)&#xff1a;17399 分类类别数&#xff1a;69 类别名称:[“0”,“1”,“2”,“3”,“4”…

计组_指令的执行过程

2024.06.19&#xff1a;计算机组成原理指令的执行过程学习笔记 第18节 指令的执行过程 8.1 指令周期8.2 指令的执行过程8.2.1 取指令8.2.2 译码8.2.3 根据源操作地址计算并取操作数8.2.4 执行数据操作8.2.5 目的操作数地址计算并存结果 8.3 指令的数据流8.3.1 取值周期的数据流…