定义代码质量高低,本来就是主观的,更多的是出于判断。为了做出更客观的评判,我个人 认为有益的做法是后退一步,考虑一下编写代码时真正试图实现的目标。在我看来,帮助我实现这些目标的代码就是高质量的,而产生阻碍作用的代码就是低质量的。
我在编写代码的时候要实现的4个高层目标如下:
- 代码应该正常工作;
- 代码应该持续正常工作;
- 代码应该适应不断变化的需求;
- 代码不应该重复别人做过的工作。
后面的内容将更加详细地介绍这4个目标。
1 代码应该正常工作
这个目标显而易见,或许不需要说明,但我无论如何都要做一番解释。我们编写代码的目的 是试图解决某个问题,例如实现某个功能、修复缺陷或执行某项任务。代码的主要目标是能够正 常工作——它应该解决我们打算让它解决的问题。这意味着,代码是没有缺陷的,因为缺陷很可 能阻止代码正常工作和全面解决问题。
确定代码“正常工作“的含义是,我们必须了解所有需求。例如,如果我们要解决的问题对 性能(如延迟和CPU占用率)很敏感,确保代码有合适的性能就应该归入“正常工作”的范畴, 因为这是需求的一部分。用户隐私和安全性等其他重要考虑因素也适用这一原则。
2 代码应该持续正常工作
代码的工作可能非常短暂。今天,它可能正常工作,但我们如何确保明天或者一年之内它都 能正常工作?这样的担心看起来好像莫名其妙,为什么代码会突然停止工作?要点在于,代码并 不是与世隔绝的,如果我们不多加小心,它很容易因为周围事物的变化而崩溃。
- 代码很可能依赖于其他代码,而这些代码会被修改、更新和更换。
- 任何新功能需求都意味着要对代码进行修改。
- 我们试图解决的问题也可能随时间的推移而发展:消费者的偏好、业务需求和技术考虑 都可能变化。
如果代码在今天能够正常工作,明天却因为上述因素变化而出现问题,那么它没有太大的用 处。创建当下可以正常工作的代码往往很容易,但创建一直能正常工作的代码就要难得多。确保 代码持续工作是软件工程师面对的问题之一,也是在编程各阶段都要考虑的问题。以事后诸葛亮 的眼光去考虑它,或者认为只要以后增加一些测试就能实现这个目标,往往都不是很有效。
3 代码应该适应不断变化的需求
很少有只编写一次、永远不用再修改的代码。一款软件的持续开发可能跨越几个月、几年, 甚至几十年。在整个过程中,需求都在改变:
- 业务状况变化;
- 消费者偏好变化;
- 设想失效;
- 新功能持续增加。
决定在代码适应性上投入多少精力,可能是很难权衡的问题。一方面,我们深知软件需求将 随时间推移而发展(极少看到反例)。另一方面,我们往往不能确定它们究竟会如何发展。对一段代码或者一款软件,几乎不可能准确预测出它在以后的一段时间内将如何变化。然而,我们不 能仅因为不能确定软件如何发展,就完全忽视软件将会发展的事实。为了说明这一点,我们来考虑两种极端的情况。
- 方案A—我们试图准确预测未来的需求可能会如何演变,并设计支持所有潜在变化的 代码。我们可能要花几天或者几周的时间来描绘出代码和软件所有可能的演变路径。然 后,我们必须小心翼翼地考虑缩写代码的每个细节,确保它支持所有未来可能出现的需 求。这将严重拖慢我们的工作,本来3个月就可以完成的软件,现在可能要花上一年甚 至更长的时间。最终,这些时间也可能是浪费的,因为竞争对手将比我们提前几个月进 入市场,我们对未来的预测很可能完全是错误的。
- 方案B——我们完全无视需求可能演变的事实。我们编写恰好满足现行需求的代码,不 在代码适应性上做任何努力。软件中到处都是不可靠的假设,各个子问题的解决方案都 捆绑在一起,成为一大堆无法区分的代码。我们在3个月内就投放了软件的第一个版本, 但初始用户的反馈说明,如果我们想要取得成功,就必须改良其中的一些功能,并添加 新功能。对需求的改变并不大,但因为我们编写代码时没有考虑适应性,唯一的选择就 是扔掉全部代码,从头再来一遍。我们必须再花3个月重写软件,如果需求再次改变, 此后还得再花3个月重写。到我们完成满足用户需求的软件时,竞争对手再一次打败了我们。
方案A和方案B是两个极端,两者的结果都很不好,它们也都不是制作软件的有效方法。 相反,我们需要找到一种介于两个极端的方法。从方案A到方案B的整个谱系中,哪里才是最 优的并没有唯一的答案,这取决于我们所开发的项目,以及我们所在单位的文化。
幸运的是,我们可以釆用一些普适技术,在不确定未来变化的情况下确保代码的适应性。
4 代码不应该重复别人做过的工作
在我们编写代码解决问题时,通常会将一个大问题分解为多个较小的子问题。例如,我们打 算编写加载图像文件,将其转换为灰阶图像,然后保存的代码,那么需要解决的子问题如下:
- 从文件中加载一些数据;
- 将这些数据解析为某种图像格式;
- 将图像转换为灰阶图像;
- 将图像转换为数据;
- 将数据存回文件。
这些问题中的许多已被其他人解决,例如,从文件中加载一些数据可能是由编程语言内置方 法完成的。我们不用自己编写与文件系统进行低层通信的代码。同样,我们也许可以从现有的库 中调用代码,将数据解析为图像。
如果我们自己编写与文件系统进行低层通信的代码,或者将一些数据解析为图像,实际上就 是在重复别人做过的工作。最好的方式是利用现有解决方案而不是重写一遍。这样做的理由有多个方面。
- 节约时间和精力——如果我们利用编程语言内置方法加载文件,可能只要几行代码、花 费几分钟。相反,自己编写代码完成这一工作可能需要阅读许多关于文件系统的标准文 档,编写成千上万行代码。我们可能要几天甚至几周的时间才能完成这项工作。
- 降低出现程序缺陷的可能性——如果现有的代码能解决指定问题,它应该已经全面测试 过。这些代码很有可能已在外界使用过,因此代码包含缺陷的可能性已经降低,即使有 缺陷,人们可能已经发现并修复过。
- 利用现有专业知识——维护图像解析代码的团队很可能是由图像编程专业人士组成的。 如果JPEG编程技术出现了新版本,他们很可能对此十分了解,并更新相应的代码。通 过重用他们的代码,我们就可以从他们的专业能力和未来的更新中获益。
- 使代码更容易理解一如果完成某项工作有标准化的方法,我们就有理由认为,其他工 程师此前也知道这种方法。大部工程师可能在某个时间阅读了一份文件,立刻意识到完 成这项工作的(编程语言内置)方法,并理解这种方法的功能。如果我们编写自定义逻 辑来完成工作,其他工程师就不会熟悉,不能一下子就知道它的功能。
“不应该重复别人做过的工作“这一概念在两个方向上都适用。如果其他工程师已编写了解 决某个子问题的代码,那么我们应该调用这些代码,而不是自己编写代码来解决。同样,如果我 们编写了解决一个子问题的代码,那么应该以某种方法构造代码,以便其他工程师能轻松地重用 它们,而无须重复工作。
由于同一类子问题往往反复出现,因此人们很快就会意识到在不同工程师和团队之间共享代码的好处。
以上内容摘自《好代码 ,坏代码》
1.易学易用:从零开始讲解编程实践,每一个经验教训以“坏代码”开始,以“好代码”结束。
2.贴合实际:通过50+条锦囊妙计、100+个案例手把手教你编写高质量代码。
3.内容丰富:通过11大主题解读卓越软件工程师编写可靠的、易于维护的代码的关键概念与技术。
4.源于实践:内容整合作者及团队成员多年的软件开发实践经验,通过理论介绍与实战相结合的方式详细分析软件开发实践。
5.注重效率:通过清晰的注释及代码分析,帮你轻松理解和掌握编程技巧。
本书分享的实用技巧可以帮助你编写鲁棒、可靠且易于团队成员理解和适应不断变化需求的代码。内容涉及如何像高效的软件工程师一样思考代码,如何编写读起来像一个结构良好的句子的函数,如何确保代码可靠且无错误,如何进行有效的单元测试,如何识别可能导致问题的代码并对其进行改进,如何编写可重用并适应新需求的代码,如何提高读者的中长期生产力,同时还介绍了如何节省开发人员及团队的宝贵时间,等等。