编程精粹—— 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函数那样,那么灵活性并未带来更多益处,相反它们会让错误更难发现。
  • 不要病急乱投医。胡乱的尝试某个方案然后期望能达到理想效果,要抵制这种想法。把时间花在寻找正确解决方案上,而不是尝试上。如果必要,联系操作系统厂商,找他们的开发支持小组。这比提出一个奇怪的实现,然后将来再返工好的多。
  • 函数应该足够小以便彻底地测试,不要克扣测试时间。记住,如果你不测试你的代码,可能就再也没人测试了。无论如何,不要期望测试组专为你测试代码。
  • 确定组内开发项目所遵循的优先顺序,并严格执行。比如某项目组正确性列为最高优先级,其次是可测试性、全局效率、可维护性、一致性、大小、局部效率、个人编码风格。






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

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

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

相关文章

vue配置中的process.env

项目中的.env开头的文件是否知道是干什么的呢 主要是为了区分测试环境还是生产环境env.development为测试环境 # 测试环境 NODE_ENV development VUE_APP_BASE_API http://xxxxxxxxx // 命名一定要以 VUE_APP_ 开头,要不然根本取不到 .env.production为生产环境…

企业文件传输系统只能传输?分享功能同样重要!(上)

在当今的商业世界里,企业间的协作和信息交流已经变得极其重要,特别是在处理那些庞大的文件时。想象一下,设计图、高清视频、大数据分析,还有软件开发包,这些文件的大小往往达到GB甚至TB级别,它们是企业日常…

猫头虎 分享已解决Error || **Data Leakage**: `Unexpectedly high validation performance`

猫头虎 分享已解决Error || Data Leakage: Unexpectedly high validation performance 🐯 摘要 📄 大家好,我是猫头虎,一名专注于人工智能领域的博主。在AI开发中,我们经常会遇到各种各样的错误,其中Data…

LONGHEADS:无需训练的多头注意力长文本处理框架

大模型(LLMs)在处理海量文本数据时展现出了前所未有的能力。然而这些模型在面对超出其训练时所见序列长度的长文本时存在两个主要问题:一是模型对于超出预训练长度的文本难以有效泛化,二是注意力机制的二次方时间复杂度导致计算成…

SAPUI5基础知识8 - 模块(Module)的使用

1. 背景 在SAPUI5中,几乎所有东西都是一个模块(例如:控件,控制器,组件等等),通过依赖管理,模块间可以相互调用。这样做的好处是,可以仅在需要时才去加载必需的模块&…

【力扣】从前序与中序遍历序列构造二叉树

🔥博客主页: 我要成为C领域大神 🎥系列专栏:【C核心编程】 【计算机网络】 【Linux编程】 【操作系统】 ❤️感谢大家点赞👍收藏⭐评论✍️ 本博客致力于分享知识,欢迎大家共同学习和交流。 给定两个整数数…

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

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

SpringCloud Alibaba Sentinel 流量控制之流控效果实践总结

当 QPS 超过某个阈值的时候,则采取措施进行流量控制。流量控制的效果包括以下几种:直接拒绝、Warm Up、匀速排队/排队等待。对应 FlowRule 中的 controlBehavior 字段。 注意:若使用除了直接拒绝之外的流量控制效果,则调用关系限流…

【JS】上传文件显示文件的为空,显示的文件参数内容只有uid

上传的文件参数file里面只包含uid,没有其他信息 例子解决办法 例子 例如使用elment ui的el-upload组件上传文件,会导致上传的文件参数file里面只包含uid,没有其他信息,如图: 正确应为如下图: 解决办法 …

视图(views)

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 下面通过一个例子讲解在Django项目中定义视图,代码如下: from django.http import HttpResponse # 导入响应对象 impo…

Idea启动服务报 Command line is too long

一、背景 合不同分支代码后,启动服务报 Error running Application, Command line is too long, Shorten the command line via JAR manifest or via a classpath file and rerun. 没有在意,然后点击了manifest 来进行 二、问题 然后自己在重新启动&…

情绪管理篇:让七情自然流露,不过分压抑也不掺杂极端的想法即可来去自如

情绪管理篇: 人有七情,本属常理,该哭的时候哭、该笑的时候笑、该怒的时候怒、该忧的时候忧 学习圣贤之学,并非让我们像木头人一样,枯木死灰,而要让自己不要被七情所缠缚、被七情所乱心,我们的喜…

最新《pvz植物大战僵尸杂交版》整合安装包,全面支持Android、ios、Windows,附教程!

今天,阿星要聊聊最近全网大火的一款老游戏——《植物大战僵尸》杂交版。 虽然它不是什么3A大作,但在阿星的心里,它永远是那个让人回味无穷的经典。记得十年前,阿星和大多数玩家一样,玩的都是盗版。那时候的《植物大战…

三品PDM电子行业解决方案介绍 电子企业PDM应用效果

随着全球化和技术创新的不断推进,电子行业正经历着前所未有的发展机遇。然而,随之而来的挑战也日益凸显,尤其是在产品数据管理PDM方面。本文将探讨电子行业在PDM方面的需求,并提出相应的解决方案,以帮助企业提升效率和…

项目中eventbus和rabbitmq配置后,不起作用

如下:配置了baseService层和SupplyDemand层得RabbitMQ和EventBus 但是在执行订阅事件时,发送得消息在base项目中没有执行,后来发现是虚拟机使用得不是一个,即上图中得EventBus下得VirtualHost,修改成一直就可以了

Latex学习之“usefont”用法

Latex学习之“\usefont”用法 一、通俗的解释 \usefont 是 LaTeX 中的一个命令,用于在文档中临时改变字体,其基本语法如下: \usefont{字体编码}{字体族}{字体系列}{字体形状}这样看起来好像蛮抽象,你可能以及晕了,什…

警告,恶意域名疯狂外联,原因竟然是……

前言 在某个风和日丽的下午,突然收到客户那边运维发过来的消息说我司的DTA设备在疯狂告警,说存在恶意域名外联,我急忙背上小背包前往客户现场,经过与客户协同排查,最终确定该事件为一起挖矿病毒引起的恶意域名外联事件…

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

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

xss初识(xss-lab)

XSS跨站脚本 XSS漏洞概述 XSS被称为跨站脚本攻击(Cross-site scripting),由于和CSS(Cascading Style Sheets) 重名,所以改为XSS。 XSS主要基于javascript语言完成恶意的攻击行为,因为javascri…

C++多线程异步日志实现

使用C11标准&#xff0c;构建了一个方便使用的、轻量化的日志系统。封装线程安全的lockQueue&#xff0c;实现对每条日志添加信息、push到lockQueue中的LogTmp类&#xff0c;实现一个多线程异步的日志系统Logger。 lockqueue.h #pragma once #include <queue> #include…