
1.3 软件质量目标
每个人都喜欢精美的设计,包括我。设计模式使软件不仅只是看起来很好。我们所做的一切都应该是有目的。
GoF将面向对象的设计模式分类为创建型、结构型和行为型。对于Julia,让我们采取不同的观点,并按照各自的软件质量目标对模式进行分类,如下所示:
·可重用性
·性能
·可维护性
·安全性
让我们在以下各节中了解每一个目标。
1.3.1 可重用性
人们在设计软件时经常谈论自上而下和自下而上的方法。自上而下的方法从一个大问题开始,然后将其分解为一系列较小的问题。之后,如果问题还不够小(正如我们在研究“单一职责原则”时所讨论的那样),我们会将问题进一步分解为更小的问题。重复这个过程,直到问题小到可以进行设计和编码。
自下而上的方法刚好相反。有了领域知识,你就可以开始创建基础模块,然后通过将这些基础模块进行组合来创建复杂的模块。不管怎么做,最终都会有一组相互配合的组件,从而构成应用程序的基础。
我喜欢这个比喻。即使是5岁的孩子也可以仅用几种乐高积木构建各种结构。想象力才是限制因素。你是否想知道为什么它如此强大?好吧,如果你还记得,每个乐高积木都有一组标准的连接器:1个、2个、4个、6个、8个或更多。使用这些连接器,每个积木都可以轻松插入另一个积木。创建新结构时,可以将其与其他结构组合以创建更大、更复杂的结构。
在构建应用程序时,关键的设计原理是创建可插拔的接口,以便轻松地重用每个组件。
可重用组件的特征
以下是可重用组件的重要特征:
·每个组件都只有一个目的(SOLID中的单一职责原则)。
·每个组件都有明确的定义,可以重复使用(SOLID中的开放/关闭原则)。
·专门为父子关系设计的抽象类型层次结构(SOLID中的里氏替换原则)。
·接口被定义成一组较小函数的合集(SOLID中的接口隔离原则)。
·接口用于在组件之间桥接(SOLID中的依赖反转原则)。
·模块和函数在设计时考虑到了简单性(KISS)。
可重用性很重要,因为它意味着我们可以避免重复代码和浪费精力。我们编写的代码越少,维护软件所需的工作就越少。这不仅包括开发工作,还包括时间测试、打包和升级。可重用性也是开源软件如此成功的原因之一。特别是Julia生态系统包含许多开源包,它们倾向于相互借鉴功能。
接下来我们讨论另一个软件质量目标——性能。
1.3.2 性能
Julia语言是为高性能计算而设计的,但并非随意写出的代码就具有高性能。在性能方面,需要练习编写对编译器更友好的代码,从而使程序更有可能转换为优化的机器代码。
在过去的几十年中,计算机的变化一年比一年快。使用现在的硬件,可以更轻松地解决曾经存在的性能瓶颈。同时由于数据爆炸,我们也面临更多挑战。一个很好的例子是大数据和数据科学领域。随着数据量的增长,我们需要更强大的计算能力来处理这些新的用例。
不幸的是,计算机的速度增长并不像过去那样快。摩尔定律说:微芯片上的晶体管数量大约每18个月翻一番,自1960年以来,它与CPU速度的增长相关。但是,众所周知,由于物理上的限制(可以安装在芯片上的晶体管数量和制造工艺的精度),摩尔定律很快将不再适用。
为了满足当今的计算需求,特别是在人工智能、机器学习和数据科学领域的计算需求,从业人员一直在努力实现横向扩展策略,做法是在许多服务器上利用多个CPU核心,充分利用GPU和TPU的效率。
高性能代码的特征
下面就是高性能代码的特征:
·函数小,容易进行优化(SOLID中的单一职责原则)。
·函数包含简单逻辑而不是复杂逻辑(KISS)。
·数字数据被放置在连续的内存空间中,使编译器可以充分利用CPU。
·内存分配应保持最小,以减少深度垃圾回收。
性能对任何软件项目来说都是一个重要的指标。对于数据科学、机器学习和科学计算用例而言,性能尤其重要。很小的设计更改可能会产生巨大的影响,例如某些情况下,可能会将24小时的执行过程优化为30分钟。它还可以提升用户使用Web应用程序时的体验,而不是一个“请稍候……”的弹出框。
接下来我们将讨论另一个软件质量目标——软件的可维护性。
1.3.3 可维护性
如果设计正确,则可以更轻松地维护软件。一般而言,如果你能够有效地使用前面列出的设计原则(SOLID、KISS、DRY、POLA、YAGNI和POLP),那么你的应用程序就更有可能是为了便于长期维护而精心设计的。
可维护性是大规模应用的重要组成部分。一个研究院的研究项目是不会持续很长时间的,但企业应用程序可能会持续数十年。我最近从一个同事那里听说,COBOL仍在使用中,并且COBOL的开发人员仍然过着不错的生活。
我们经常听到技术债,与现实生活中的债务一样,技术债是代码更改时必须付出的东西。而且技术债持续的时间越长,你所花费的精力就越多。
请考虑这样一个例子,假如有一个模块,该模块中存在重复的代码或不必要的依赖关系。每当添加新功能时,都必须更新源代码的多个部分,并且必须对系统的较大区域执行回归测试。因此,每次更改代码后,你最终都要偿还债务(根据编程时间和精力),直到债务全部清偿为止(即代码完全重构)。
可维护代码的特征
以下是可维护代码的特征:
·没有无用的代码(YAGNI)。
·没有重复的代码(DRY)。
·代码短小精炼(KISS)。
·代码清晰易懂(KISS)。
·每个函数都只有一个目的(SOLID中的单一职责原则)。
·每个模块都包含相互关联、相互协作的函数(SOLID中的单一职责原则)。
可维护性是任何应用程序的重要方面。如果设计合理,即使是大型应用程序也可以频繁、轻松地进行更改而无须担心出现问题。应用程序也可以服务很长时间,从而降低软件成本。
接下来我们讨论另一个软件质量目标——软件的安全性。
1.3.4 安全性
“安全性——避免遭受或造成伤害或损失的状态。”
——《韦氏词典》
我们期望应用程序正常运行。当应用程序发生故障时,可能会产生不良后果,其中一些可能是致命的。例如NASA使用的关键任务火箭发射子系统。单个缺陷可能会导致发射延迟,在最坏的情况下,它可能导致火箭在空中爆炸。
编程语言旨在提供灵活性,但同时提供安全功能使软件工程师可以减少错误。例如,编译器的静态类型检查确保将正确的类型传递给需要这些类型的函数。此外,大多数计算机程序都是对数据进行操作,而数据并不总是干净或可用的,所以能够正确处理错误或数据丢失是衡量软件质量的重要方面。
安全的应用程序的特征
安全的应用程序的一些特征如下:
·每个模块都暴露最少的类型、函数和变量。
·每个函数都带有参数,以使各个类型实现该函数的预期行为(SOLID中的里氏替换原则、POLA)。
·函数的返回值是明确和文档化的(POLA)。
·丢失的数据可以被正确处理(POLA)。
·变量的使用限制在最小范围内。
·异常应该被捕获并合理地处理。
安全性是软件的重要质量目标之一。错误的应用程序可能会导致重大灾难,甚至可能会使公司损失数百万美元。2010年,由于防抱死制动系统(ABS)的软件缺陷,丰田召回约40万辆混合动力汽车。1996年,欧洲航天局发射的阿丽亚娜5号火箭在发射后仅40秒就爆炸了。当然,这些只是一些极端的例子。通过利用最佳实践,我们可以避免陷入此类尴尬而代价高昂的事件中。