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

news2024/9/19 18:26:30

这是一本老书,作者 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.coloradmin.cn/o/1858519.html

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

相关文章

chatglm系列知识

一、目录 chatglm 是什么语言模型与transformer decoder 的区别解释prefix LM与Cause LMchatglm(prefix LM)与decoder-only LM 核心区别glm 架构chatglm 预训练方式chatglm 微调chatglm与chatglm2、chatglm3的区别chatglm 激活函数采用gelu, 为什么chat…

融资融券账户与普通账户有何区别?一文读懂为什么要开通两融账户

01 融资融券账户与普通账户的区别 1、开通条件不同: ①普通账户:开户的门槛低,一般年满18岁以上就能开通。(70岁以上需要临柜开户)。 ②融资融券:融资融券的准入门槛相对较高,需要满足以下几…

SQLite3的使用

14_SQLite3 SQLite3是一个嵌入式数据库系统,它的数据库就是一个文件。SQLite3不需要一个单独的服务器进程或操作系统,不需要配置,这意味着不需要安装或管理,所有的维护都来自于SQLite3软件本身。 安装步骤 在Linux上安装SQLite…

python桌面应用

py文件 import osimport wx import wx.html2class MyFrame(wx.Frame):def __init__(self, parent):wx.Frame.__init__(self, parent, title"启动啦", size(1000, 700))# 创建一个Web视图组件self.browser wx.html2.WebView.New(self)# 加载本地HTML文件# self.brow…

WebFlux 和 Spring Security 会碰出哪些火花?

项目创建成功后,我们添加一个接口,用来获取登录用户信息,如下: RestController public class UserController { GetMapping(“/user”) public Mono getCurrentUser(Mono principal) { return principal; } } 注意我们的返…

【Leetcode每日一题】 01背包 - DP41 【模板】01背包(难度⭐⭐)(80)

1. 题目解析 题目链接:DP41 【模板】01背包 这个问题的理解其实相当简单,只需看一下示例,基本就能明白其含义了。 2.算法原理 第一问:不超过总体积的背包问题 1. 状态表示 dp[i][j] 表示:从前 i 个物品中挑选&…

android adb常用命令集

1、系统调试 #adb shell:进入设备的 shell 命令行界面,可以在此执行各种 Linux 命令和特定的 Android 命令。 #adb shell dumpsys:提供关于系统服务和其状态的详细信息。 #adb logcat:实时查看设备的日志信息。可以使用过滤条件来…

Arduino称重传感器和 HX711 放大器(数字秤)

Arduino称重传感器和 HX711 放大器(数字秤) Arduino with Load Cell and HX711 Amplifier (Digital Scale) In this guide, you’ll learn how to create a digital scale with the Arduino using a load cell and the HX711 amplifier. First, you’l…

二叉树-左叶子之和(easy)

目录 一、问题描述 二、解题思路 三、代码实现 四、刷题链接 一、问题描述 二、解题思路 此题属于树遍历的简单题,用递归深度遍历的方式,当遇到左叶子结点(在递归函数中加上一个判断当前结点是左结点还是右结点的标记位),此时加上当前结点…

ONLYOFFICE 桌面编辑器 8.1:全新升级,助您轻松高效处理办公文档

ONLYOFFICE 桌面编辑器 一、前言二、轻松编辑器 PDF 文件三、用幻灯片版式快速修改幻灯片四、无缝切换文档编辑、审阅和查看模式五、改进从右至左语言的支持 & 新的本地化选项六、版本 8.1:其他新功能七、ONLYOFFICE 官网:https://www.onlyoffice.co…

OnlyOffice8.1新功能测评

一、导语 时隔四个月,OnlyOffice推出了8.1版本。 四个月过去,笔者的项目也接近尾声,在项目过程中还把OnlyOffice插件推荐给了项目组,希望官方多出好用功能,造福我们广大项目O(∩_∩)O 回归正题,与前几个…

【数据结构与算法】之(数据结构绪论篇)(一)溢彩色

总而言之:《数据结构》是介于数学、计算机硬件和计算机软件三者之间的一门核心课程 1-1.抽象数据类型: 一个数学模型及定义在该模型上的一组操作;抽象数据类型体现了程序设计中问题分解,抽象和信息隐藏的特性。 抽象&#xff1a…

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

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

【人机交互 复习】第1章 人机交互概述

人机交互的知识点碎,而且都是文字,过一遍脑子里什么都留不下,但是背时间已经来不及了,最好还是找题要题感吧,加深印象才是做对文科的关键 一、概念 1.人机交互(Human-Computer Interaction,HCI)&#xff1…

路由器ARP和ARP-proxy(华为)

#交换设备 路由器ARP和ARP-proxy(华为) 当一个广播域中的主机想要访问另外一个广播域的主机时,会广播ARP报文,询问目标IP地址所对应的MAC地址,默认情况下,arp记录是设备自动生成的,但是这样会容易受到ARP欺骗攻击&am…

系统架构设计师 - 数据库系统(2)

数据库系统 数据库系统规范化理论 ★ ★ ★ ★ ★函数依赖求候选键Armstrong公理范式判断第一范式 1NF第二范式 2NF第三范式 3NFBC 范式 BCNF 模式分解保持函数依赖分解无损分解 并发控制 ★事务的 ACID 特性并发存在的问题并发解决方案 - 封锁协议 数据库的安全性 ★安全性的分…

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

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

pdf转图片转换器,pdf转图片的工具

在日常的工作和学习中&#xff0c;我们经常会遇到需要将PDF文件转换为图片格式的情况。那么&#xff0c;如何才能将PDF格式转换为图片格式呢&#xff1f;今天&#xff0c;我将为大家介绍几种简单易用的方法&#xff0c;帮助大家轻松实现PDF转图片。 打开“轻云pdf处理官网网站”…

Linux 动态监控系统

top与ps命令很相似。它们都用来显示正在执行的进程。Top与ps最大的不同之处&#xff0c;在于top在执行一段时间可以更新正在运行的的进程。 一、基本指令 top top -d&#xff1a; 秒数 :每隔设定值秒数更新&#xff0c;未设置下默认为3秒 top -i:使top不显示任何闲置或者僵死进…