编程精粹—— Microsoft 编写优质无错 C 程序秘诀 08:剩下的就是态度问题

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


不记录,等于没读。本文记录书中第八章内容:剩下的就是态度问题。


程序员有能力理解本书中的每一条指导原则,但如果没有正确的态度和一套良好的编程习惯,写出无错误 (BUG) 的代码将比预期困难得多。如果程序员认为错误可以简单地“消失”或认为“以后”修复错误对产品没有害处,错误就会持续存在。如果程序员经常“清理”代码,允许函数中不必要的灵活性,接受设计中冒出来的每一个“未规划”特性,或只是“尝试”一些随意的解决方案,希望找到能够工作的东西,那么写出无错误的代码将是一场艰难的战斗。拥有一套良好的习惯和态度可能是持续写出无错误代码的最重要的要求。

本书讨论的技术可以用来检测和防止错误,但这些技术并不保证一定写出无错代码。就像一支由技术高超的队员组成的球队也不能保证一定获胜一样。除了理解这些技术外,编写无错代码的另一个必要因素是一套良好的习惯和态度。

如果一个球队成天纸上谈兵而不实训、如果球队成员不断因为工资低而牢骚满腹,如果他们时刻担心被裁掉,那么这些一定会影响球员的成长和发挥。写出无错误代码也有类似的问题,要实践出真知,要有必胜的信心和良好的习惯。

本章指出一些编写无错误代码的主要障碍。

错误几乎不会“消失”

错误消失有三个原因:

  1. 错误报告不对
  2. 错误已被别人改正了
  3. 错误依然存在,但没有表现出来

作为专业开发人员,确定错误消失的具体原因是职责之一。

及时纠正,事半功倍

当我第一次参加 Excel 小组的时候,总是有进度和实现新功能的压力,但在修改错误方面,却一点压力也没有。直到某个未发布的产品因为失控的错误表而终止,这迫使 Microsoft 认真研究怎样开发产品:发现错误马上修改、发现架构问题马上重构

完成项目的功能需求后再来修改错误或者重构,会有一些列问题:

  • 修改一年前写的代码比修改几天前写的代码更难,也更费时。
  • 错误发现的越早,就能越早从错误中学习,从而越早避免再发生类似错误。
  • 放任错误来换取更快进度,会使得程序员轻视检查,因而产生更多错误,最后管理失控
  • 错误太多时,预估交付时间变得困难
  • 误导决策者。他们更关注功能,发现功能很快完成了,便可能急着推向市场

修改错误要治本,不要浮于表面

Anthony Robbins 在他的小说《唤醒巨人》中讲述了一个医生的故事。一天,一位医生在河边听到落水者的呼救声,她跳入水中,将落水者救上岸并进行抢救。这个落水者刚恢复呼吸,从河里又传来两个落水者的求救声。她再次跳入水中,将人救上岸并安顿好,然后又听到四个落水者的求救声,再然后是八个落水者的求救声……不幸的是,医生忙着救人,以至于没有时间去查明是谁把他们扔到水里。

你有过被BUG淹没的经历吗?你会思考为什么会有那么多BUG吗?

程序员经常忙于修复错误,但从没停下来思考是什么原因引起了这些错误。

在《糖果机接口》一章中,我们知道了 malloc 函数返回 NULL 违反了函数接口设计规则之一:不要在返回值中隐藏错误。程序员经常因为疏忽对 NULL 的处理而导致程序崩溃。

类似的,如果一个函数因为疏忽了对未预料的 NULL 处理而导致了崩溃,你会在这个函数中增加处理 NULL 的代码吗,就像下面的代码这样:

if(pb == NULL)return FALSE;

这样做并不正确。这只是改正了错误的症状而没有改正错误的原因。因为错误的根本原因并不是函数没有处理 NULL ,而是那个会返回 NULL 的函数,因为那个函数设计的不合理。

我们再次强调函数接口设计的一个原则:设计可以引导程序员去做正确事情的函数接口。

一旦一个函数设计的不合理,就会迫使使用它的程序员承担额外的出错可能。那些有经验的或者从错误中学习过的程序员才能正确使用这个函数,总会有程序员在这个函数上出错,一个接一个。当出错时,我们是会联系函数的设计者,推动这个函数设计的更加合理,还是自责自己疏忽大意,然后在局部增加对未预料返回值的处理呢

我希望你是前者。即便像 malloc 这样的既成事实的函数,我们仍可以使用一个包装函数,将其封装成更合理的接口,就像《强化你的子系统》一章中函数 fNewMemory 做的那样。

断病断因,治病治根。有时候BUG不断,可能是因为没有找到错误的根源。

无事生非

某些程序员总要强行在代码上留下自己的痕迹。比如喜欢将整个文件重新格式化以适合他们的口味。尽管大多数程序员对“清理”代码非常谨慎,但是,似乎所有程序员都不同程度地做过这件事情。

**清理代码的问题在于,程序员并不总是像对待新代码那样对待改进后的代码。如果你要修改现有代码,要确保:

  • 进行测试。无论你认为修改有多简单。

    '\0' 代替数字 0 时,可能键入 '0' 而改变程序逻辑;将局部变量 hPrint1 改为 hPrint 可能因为与全局变量冲突而造成程序失效。

  • 对修改的代码逻辑了然于心。你看不懂的代码不要轻易动,因为这些代码可能有很好但又不明显的原因。

不要实现没有战略意义的新功能

如果没有必要,就不要编写或修改代码。要仔细考虑一个功能是否具有价值,优先做哪些对产品成败有重要作用的功能。有些功能对产品没有任何价值,它们只是:

  • 为了填充功能集而存在
  • 大客户的特定需求
  • 竞争对手的产品有这些功能
  • 某个决策者认为需要
  • 某个程序员认为这很酷
  • 某个程序员认为这很有技术挑战性

没有“零成本”的功能

所谓的“零成本”功能,是指在开发过程中无需额外努力便可以添加的功能。这些功能是另一个不必要的错误来源。零成本功能有一个大问题——它们几乎从未对产品的成功起到关键作用。程序员添加零成本功能是因为他们能够添加,而不是因为他们应该添加。毕竟,如果不需要花费任何代价,为什么不添加一个功能呢?但问题是,零成本功能对程序员来说可能花费不多,但成本不仅仅是编码:有人必须为这个功能编写文档、有人必须测试这个功能、还得有人修复这个功能中出现的任何错误。当我听到程序员说某个功能是零成本的,这告诉我他或她没有花太多时间考虑其中涉及的真正成本。

灵活性滋生错误

预防错误的一个重要策略是:排除设计中不必要的灵活性

  • realloc 函数具备 mallocfree 函数的功能,具备扩展内存和缩小内存的功能,这是个过分灵活的函数。设计越灵活,就越难察觉错误realloc 函数就难以验证参数的有效性,因为指针参数传入 NULL 是合法的,块大小传入0也是合法的。

  • 还有过分灵活的实现特性。最初的 HTML 文档推荐“宽容地接受数据”,也就是编写的网页即便不是严格遵守 HTML 的规范,浏览器也要尽量领会其中的意义。但是浏览器有很多种,每一种浏览器只接受规范中一个不同的超级,这就使得网页兼容所有主流浏览器变得非常困难。

“试一试”就是个屎

原文是 "TRY" Is A FOUR-LETTER WORD,在英文语境里,“A FOUR-LETTER WORD” 是一句委婉的脏话,一般指 4 字母骂人的词,比如 shit 等。

当你寻求帮助,而别人建议你“试一试……”时,“试一试”中提到的的方案通常都不是可以采纳的合适方案。当别人告诉你试一试某件事情时,只是告诉你一个考虑过的猜测,并非问题的答案。

当程序员开始尝试某方案时,这意味着待解决的事情已经超出了他的理解范围,他会寻求任何有效方案。因为不理解尝试的方案,所以即使方案有效,也很可能会带来无意识的副作用,将来还要返工。

在找到正确的解法之前,不要一味地“试”,把时间花在寻找正确解决方案上。

如果你发现自己正在测试某个问题的可能解决方案,请停下来,拿出手册,然后仔细阅读。这可没有玩代码那么有趣,也没有向别人询问怎么试那么简单,但你将学到许多有关操作系统的知识,以及如何在它上面编程。

神圣的进度表

使用进度表的缺点是大多数程序员会优先考虑进度而不是测试。如果时间紧迫,程序员会牺牲测试时间来完成进度表上的任务。这意味着如果不给程序员足够的开发时间,程序员会牺牲质量

一个程序员要用 5 天实现 5 个功能。这个程序员有两种选择:

  1. 实现一个特征就测试一个特征,一个一个地进行;
  2. 全部完成 5 个后,再测试

几年来,我考察了这两种编码风格。绝大多数情况下,边编写代码边测试的程序员较少出错。

尽量编写和测试小块代码。即使测试代码会影响进度,也要坚持测试代码。

我要再一次说明本书的时代局限性,本书出版于 1993 年,那时候还是瀑布流程为主的蛮荒年代。现在(2024年),我们应该都知道测试驱动开发,也很自然的编写和测试小块代码,而且可能是先编写测试,再编写代码。

不要依赖测试组去发现你的程序BUG

测试代码的责任不在测试员身上,而是程序员自己的责任。

测试人员不负责测试程序的主要理由是:他们不具备必要的工具和技巧。测试员不能加入断言来捕获有问题的数据流、不能在线调试程序、不能逐行、逐指令的观察代码和数据流程。

尽管公司可能设有独立的 QA 小组专门测试软件,但是开发小组仍然要把“QA 应该找不到任何错误”作为努力的目标。对于 QA 找到的每一个问题,开发团队都应该高度重视,认真对待。应该反思为什么会出现这种错误,并采取措施避免今后再犯。——《代码整洁之道-程序员的职业素养》

测试组并非无事可做,他们在开发过程中起着重要的作用,但绝不是程序员所想的那样:“还是先赶进度吧,反正测试组能测出所有 BUG,他们就是干这个的”。

程序员测试代码,是从里向外。他们总是从测试每个函数开始,逐行逐指令的通过各条代码路径,验证代码和数据流,然后逐步扩大测试范围:验证函数能够在子系统正常运行、验证子系统之间能够正确配合。

测试员测试代码,是从外向里。测试员把代码作为一个黑盒,从程序各个输入处进行测试,观察输出,寻求其中的错误。测试员也可能利用回归测试来证实所有报告的错误都已排除。然后,测试员逐步向里推进,利用代码覆盖工具,来检查未执行到的代码。

这是两个不同的测试,程序员测试强调的是代码,测试员测试强调的是功能。两者从不同的方向考虑问题,能增加发现未知错误的机会。

每当测试员在你代码中找出一个 BUG 时,你的第一反应应该是震惊和怀疑,因为你自己会严格测试代码,你不认为测试员还能发现 BUG;你的第二反应应该是表示感谢,因为测试员帮助你避免了交付错误。

测试员无法判断 BUG 的严重性或是否值得修复。测试员必须报告所有 BUG,无论是否愚蠢,因为据他们所知,这些愚蠢的错误可能是严重问题的副作用。

真正的问题不是 BUG 有多愚蠢,而是为什么程序员在测试代码时没有捕获到这个 BUG。你或许会说这个 BUG 不重要也不值得修改,但确定其原因仍很重要:防止类似的 BUG 再次出现。

BUG或许很微小,但它能出现是严重的问题

小结

  • BUG既不会自己产生,也不会自己修复。如果你收到一个BUG报告,但是你不能重现BUG,不要假设测试员产生了幻觉。努力去查找错误,甚至恢复到旧版本测试。
  • 不要推迟修复BUG。一个主要产品,因为失控的BUG列表而被取消掉,这种情况正变得非常惊人的普遍。如果你发现BUG就马上修改它们,你的项目就不会遭受毁灭性的命运。如果你的项目一直保持近乎0个BUG,那怎么可能有失控的BUG列表呢。
  • 当你发现一个的BUG,务必问问自己:这个BUG是某个严重BUG的征兆吗?修复跟踪到的表面BUG是容易的,但是你总是应该为找到真正原因而努力。
  • 不要编写不必要的的代码或进行不必要的修改。让你的竞争对手去实现看上去很酷但毫无价值的产品功能、去做不必要的代码清理,因为实现未规划的产品功能(“free” features)而推迟交付日期。无用的代码产生没有必要的BUG,让你的竞争对手去浪费时间修改这些BUG。
  • 记住灵活与易用并不是一回事。当你设计函数和产品功能时,将关注点放到容易使用上。如果仅仅只有灵活,就像realloc函数那样,那么灵活性并未带来更多益处,相反它们会让错误更难发现。
  • 不要病急乱投医。胡乱的尝试某个方案然后期望能达到理想效果,要抵制这种想法。把时间花在寻找正确解决方案上,而不是尝试上。如果必要,联系操作系统厂商,找他们的开发支持小组。这比提出一个奇怪的实现,然后将来再返工好的多。
  • 函数应该足够小以便彻底地测试,不要克扣测试时间。记住,如果你不测试你的代码,可能就再也没人测试了。无论如何,不要期望测试组专为你测试代码。
  • 确定组内开发项目所遵循的优先顺序,并严格执行。比如某项目组正确性列为最高优先级,其次是可测试性、全局效率、可维护性、一致性、大小、局部效率、个人编码风格。






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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++基础编程100题-013 OpenJudge-1.3-11 计算浮点数相除的余数

更多资源请关注纽扣编程微信公众号 http://noi.openjudge.cn/ch0103/11/ 描述 计算两个双精度浮点数a和b的相除的余数&#xff0c;a和b都是正数的。这里余数&#xff08;r&#xff09;的定义是&#xff1a;a k * b r&#xff0c;其中 k是整数&#xff0c; 0 < r < b。…

Spring Boot 快速入门4 ——JSR-303 数据校验

目录 一、前言 二、JSR303 简介 三、使用方法 常用注解 Validated、Valid区别 四、编写测试代码&#xff1a; 1. 实体类添加校验 2. 统一返回类型 3. 测试类 4.我们把异常返回给页面 5.抽离全局异常处理 2. 书写ExceptionControllerAdvice 一、前言 我们在日常开发…

鸿蒙开发系统基础能力:【@ohos.hiTraceChain (分布式跟踪)】

分布式跟踪 本模块提供了端侧业务流程调用链跟踪的打点能力&#xff0c;包括业务流程跟踪的启动、结束、信息埋点等能力。 说明&#xff1a; 本模块首批接口从API version 8开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。 导入模块 import hi…

小鹏MONA M03实车曝光

ChatGPT狂飙160天&#xff0c;世界已经不是之前的样子。 更多资源欢迎关注 小鹏汽车 MONA 系列首款车型已经官宣命名为“M03”&#xff0c;预计将于今年第三季度上市。 现在&#xff0c;这款新车的实车照片已经在网上流传开来。 此次曝光的是一款米色车漆版本&#xff0c;与当…

Twinkle Tray:屏幕亮度控制更智能

名人说&#xff1a;一点浩然气&#xff0c;千里快哉风。 ——苏轼 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 目录 一、软件介绍1、Twinkle Tray2、核心特点 二、下载安装1、下载2、安装 三、使用方法 很高兴你打开…

【linux kernel】一文总结linux输入子系统

文章目录 一、导读二、重要数据数据结构&#xff08;2-1&#xff09;struct input_dev&#xff08;2-2&#xff09;input_dev_list和input_handler_list&#xff08;2-3&#xff09;struct input_handler 三、input核心的初始化四、常用API五、输入设备驱动开发总结(1)查看输入…

【面试题】面试官:判断图是否有环?_数据结构复试问题 有向图是否有环

type: NODE;name: string;[x: string]: any; }; [x: string]: any;}; export type Data Node | Edge; 复制代码 * 测试数据如下const data: Data[] [ { id: ‘1’, data: { type: ‘NODE’, name: ‘节点1’ } }, { id: ‘2’, data: { type: ‘NODE’, name: ‘节点2’ } },…

猫头虎 AI 前沿科技探索之路(持续更新):ChatGPT/GPT-4 科研应用、论文写作、数据分析与 AI 绘图及文生视频实战全攻略

猫头虎 AI 前沿科技探索之路(持续更新)&#xff1a;ChatGPT/GPT-4 科研应用、论文写作、数据分析与 AI 绘图及文生视频实战全攻略 背景介绍 随着人工智能技术的飞速发展&#xff0c;AI 的应用已经渗透到各个领域&#xff0c;从商业决策到医疗健康&#xff0c;再到日常生活中的…

猫头虎 分享已解决Error || Vanishing/Exploding Gradients: NaN values in gradients

猫头虎 分享已解决Error || Vanishing/Exploding Gradients: NaN values in gradients &#x1f42f; 摘要 &#x1f4c4; 大家好&#xff0c;我是猫头虎&#xff0c;一名专注于人工智能领域的博主。在AI开发中&#xff0c;我们经常会遇到各种各样的错误&#xff0c;其中Vani…

React+TS 从零开始教程(3):useState

源码链接&#xff1a;下载 在开始今天的内容之前呢&#xff0c;我们需要先看一个上一节遗留的问题&#xff0c;就是给属性设置默认值。 我们不难发现&#xff0c;这个defaultProps已经被废弃了&#xff0c;说明官方并不推荐这样做。其实&#xff0c;这个写法是之前类组件的时候…

Kafka基础教程

Kafka基础教程 资料来源&#xff1a;Apache Kafka - Introduction (tutorialspoint.com) Apache Kafka起源于LinkedIn&#xff0c;后来在2011年成为一个开源Apache项目&#xff0c;然后在2012年成为一流的Apache项目。Kafka是用Scala和Java编写的。Apache Kafka是基于发布-订…

【Python/Pytorch 】-- K-means聚类算法

文章目录 文章目录 00 写在前面01 基于Python版本的K-means代码02 X-means方法03 最小二乘法简单理解04 贝叶斯信息准则 00 写在前面 时间演变聚类算法&#xff1a;将时间演变聚类算法用在去噪上&#xff0c;基本思想是&#xff0c;具有相似信号演化的体素具有相似的模型参数…

推荐一款AI修图工具,支持AI去水印,AI重绘,AI抠图...

不知道大家有没有这样的一个痛点&#xff0c;发现了一张不错的“素材”&#xff0c; 但是有水印&#xff0c;因此不能采用&#xff0c;但找来找去&#xff0c;还是觉得初见的那个素材不错&#xff0c;怎么办&#xff1f; 自己先办法呗。 二师兄发现了一款功能强大的AI修图工具…

使用Jetpack Compose为Android App创建自定义页面指示器

使用Jetpack Compose为Android App创建自定义页面指示器 在现代移动应用中&#xff0c;页面指示器在提供视觉导航提示方面发挥着重要作用&#xff0c;帮助用户理解其在应用内容中的当前位置。页面指示器特别适用于顺序展示内容的场景&#xff0c;如图片轮播、图像库、幻灯片放…

【Linux】Socket阻塞和非阻塞、同步与异步

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; &#x1f525;Linux系列专栏&#xff1a;Linux基础 &#x1f525; 给大家…

【ajax核心02】底层原理-Promise对象

目录 一&#xff1a;promise对象是什么 二&#xff1a;语法&#xff08;Promise使用步骤&#xff09; 三&#xff1a;Promise-三种状态 一&#xff1a;promise对象是什么 Promise 对象代表异步操作最终的完成&#xff08;或失败&#xff09;以及其结果值。 即Promise对象是…

uniapp接入微信小程序原生代码配置方案(优化版)

uniapp项目需要把微信小程序原生语法的功能代码嵌套过来&#xff0c;无需把原生代码转换为uniapp&#xff0c;可以配置拷贝的方式集成过来 1、拷贝代码包到src目录 2、vue.config.js中配置原生代码包直接拷贝到编译目录中 3、pages.json中配置分包目录&#xff0c;原生入口…

java基于ssm+jsp 医院远程诊断系统

1前台首页功能模块 医院远程诊断系统&#xff0c;在系统首页可以查看首页、医生信息、论坛信息、我的、跳转到后台、客服等内容&#xff0c;如图1所示。 图1前台首页功能界面图 用户登录&#xff0c;在用户登录页面可以填写用户名、密码、等信息进行用户登录&#xff0c;如图2…

安装Django Web框架

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 Django是基于Python的重量级开源Web框架。Django拥有高度定制的ORM和大量的API&#xff0c;简单灵活的视图编写&#xff0c;优雅的URL&#xff0c;适…

近2年时间,华为手机上的卫星通信功能发展成怎样了?

自从Mate 50 系列支持北斗卫星短报文功能以来&#xff0c;已经过去了近2年的时间&#xff0c;卫星相关的功能也从最开始的摸索、罕见&#xff0c;逐渐变得成熟、在各品牌旗舰机上常见起来。 那么&#xff0c;这近两年的发展&#xff0c;卫星相关的功能都有了怎样的变化呢&…