优质软件质量是什么? - Marco


Marco Faella 是那不勒斯费德里科二世大学的副教授,也是《Seriously Good Software》一书的作者。在这一集中,Marco 解释了他所说的真正好的软件是什么意思,从多个不同的角度看待软件质量。然后,我们深入研究了这些软件质量中的一些,并提供了一些实用技巧,介绍了软件工程师如何改进他们的工艺以生产高质量的软件。最后,我们还谈到了最小可行代码的概念,为什么了解理想代码是什么样子很重要,同时在找到正确的折衷方案时仍然实用。   
 
软件工程师教育的作用

  • 我也相信有很多例外,很多情况下,聪明人在没有完成学业,甚至没有接受过正规教育的情况下就做出了伟大的事情。我认为这种情况更罕见,这种可能性更小,但当然可能会发生。
  • 另一方面,我认为正规的正规教育途径只是最安全、最有可能进入企业、进入行业、进入行业并从正确的立足点开始的方式。从良好的基础开始是在行业中蓬勃发展、灵活并准备好适应新的和不断变化的技术的最佳方式。
  • 我想告诉他们在接受教育期间尽量享受在那里的时光。忍受并尝试享受更理论化的东西,或者看起来不能立即适用的东西,因为我认为至少其中大部分仍然有用。
  • 更清晰地思考,发展抽象,你对抽象和结构的感觉,甚至像物理和数学这样的话题;即使它们与你未来的编程或软件工程工作没有直接关系,它们仍然可以帮助你进入正确的框架,进入正确的心态。

 
什么是真正的好软件
  • “真正的好软件”不仅仅是高效的软件。这不仅仅是满足其功能需求的软件,所以它做正确的事情,而且它也以正确的方式做。当然,有许多不同的方法可以衡量您的软件是否适合以及有多好。
  • 例如,你有可读性,这是一种非功能性的品质,因为最终用户不知道你的软件是否可读。但它仍然是一个非常重要的品质,因为它会影响可维护性。并且根据它的可读性,修复或修改它会更容易或更难,总体上会发展它。
  • 我认为无论是在教育领域还是在某些行业,都需要更多地关注非功能性品质:外部无法立即看到的东西,但对于软件的未来发展或其性能仍然非常重要。
  • 作为一名初级程序员,我认为你首先应该意识到你可以用不同的方式来编程你的小函数、你的小类或你的大子系统。而且,当然,这与您的经验自然而然。
  • 培养这种意识,即有成千上万种不同的方式来编程,即使是一个小单元,甚至是一个小类,有时甚至是一个小函数。如果你只是培养这种对各种方式的可能性的认识,并且你会习惯它,那么也许在给你的那么短的时间内,你可以做得更好。您可以每周在脑海中评估不同的可能性。你不会花很长时间这样做。如果您习惯了,不同的可能性就会自然而然地浮现在您的脑海中。
  • 初级程序员需要积极追求这种心态。然后,即使在您的狭小空间和有限的时间里,您也可以做得更好。您可以评估不同的可能性并选择正确的平衡点。
  • 不同质量之间的适当平衡——性能和可读性等方面的平衡、健壮性等,以及适当的测试量等——取决于上下文。因此,您应该了解各种可能性,同时了解应用程序的上下文。并且根据您制作的产品类型,您应该强调一种软件质量而不是其他软件质量。

  
软件质量象限
  • 第一个是内部质量与外部质量,这是最终用户实际可以看到的质量之间的区别,这些质量称为外部质量。
    • 例如,功能正确性。所以程序做它应该做的事情。它提供了正确的功能。这是一种外在的品质。
    • 而时间表现多半是外在品质。因此,如果您的程序需要 1 秒或 10 秒来执行其操作,这对用户来说是非常明显的。
    • 另一方面,区别不是那么明显,因为如果您的程序需要一毫秒或两毫秒,您可以争辩说它是否可见,这取决于。如果它是一个面向用户的程序,那么它实际上是不可见的,因为它对最终用户来说完全相同。另一方面,如果它是一个将在更大的程序中使用的服务,它可能会变得可见。单个动作,即一毫秒对两毫秒的动作,将连续执行 100 万次。然后,当然,它变得可见。
  • 区分软件质量的另一种方法是功能性与非功能性。非功能性意味着它如何执行任务。例如,它需要多少内存?它需要多少网络带宽?这些是非功能性的品质。
    • 我之前提到的可读性是主要的非功能性品质之一,因为它既是内部的又是非功能性的,但它仍然是一个非常重要的品质。
  • 我不认为原则上,任何象限比其他象限更重要。我只是认为这是一个有用的心理工具。
  • 在一些教育和一些行业,他们可能对这些象限中的某些象限看重太多,特别是他们对外部和功能质量看重太多,而对其他象限看重太少。而我认为,原则上,它们都同样重要。

 
速度和时间效率
  • 我认为,时间效率是所有软件工程师大部分时间都考虑的品质之一。这不是您真正必须强调和强调的品质之一。它显示了我之前谈论的一些理论基础如何真正发挥作用。
  • 如果你知道正确的数据结构,你的速度会快一百倍。
  • 我认为这是证明正规教育的有用性的一个很好的点,在那里你可以学习一些专业的数据结构。

 
空间和内存效率
  • 空间效率,即使它就像时间效率的表亲,(是)完全不同的对待。作为开发人员,时间效率总是在您的脑海中,而空间效率实际上是次要的,或者有时几乎被认为是无关紧要的。
  • 当然,现在我们有很多廉价的内存,包括 RAM 和廉价的持久内存。所以当然,一般来说给它更少的重量是正确的。
  • 有两种情况您要检查您的空间需求,您的空间占用。它们非常明显,即当您有大量数据时,或者当您有一点内存时。
  • 在光谱的另一端,您有大量包含大量数据的应用程序。这不是数据分析,正如您所想的那样。是电子游戏。您必须将所有这些信息压缩到磁盘中,然后压缩到 RAM 和图形单元中。所以这是另一个对内存要求很重要的大行业;是相当重要的。

 
 
通过监控实现可靠性
  • 这有点与防御性编程有关。这也与众所周知的合同法设计有关。
    • 这个想法是从你的方法的显式契约开始。整本书都集中在小的代码单元上。所以这就是为什么我通常谈论单个类甚至单个方法的原因,但是这些概念中的大多数都可以推广到更大的体系结构。
    • 应用于单一方法的按合同设计意味着对该方法具有清晰明确的合同。通过契约,我的意思是,这些方法对其调用者做出的假设是什么?该方法对其调用者的保证是什么?这些也称为此方法的前置条件和后置条件。
  • 然后,您可以将这些契约的一些内容挂钩到代码中,基本上是作为断言。所以断言是众所周知的编程技术。
    • 它与 if-then-else 非常相似,但具有额外的功能,您可以在运行时打开或关闭所有这些检查。如果发生不好的事情,则引发此异常。但是在运行时,您可以选择是否在执行期间检查这些条件。
    • 这尤其适用于安全关键软件,您真的非常关心可靠性。所以除了测试;这不是一种替代技术。除了测试之外,您在每个方法的开头都有这些断言。如果你真的想推送它,你还可以在方法的末尾添加检查,以检查该方法是否确实完成了它应该做的事情。
  • 我认为他们互相称赞。测试的想法是主动向您的程序提交输入,以查看它是否会根据规范做出反应。另一方面,监控只是通过定期检查来丰富您的程序。一是主动,测试。另一种是被动的。这就是我称之为监控的原因,因为这就像在我们的程序之上添加一个监视器。理想的情况是两者兼而有之。

通过测试获得可靠性
  • 标准和最简单的方法就是强制执行某些编码实践。有代码审查和代码审查,您可以包括对测试用例的审查。所以你基本上可以强制每个类都应该有它的单元测试。

 
不变性
  • 如果您查看上面的一种方法,并查看整个类,大多数情况下,您可以确定类对象应该始终具有的一些属性。所以你的类的数据字段的一些属性应该总是正确的。如果您可以识别这样的属性,则称为不变量。
  • 这意味着某些东西不会改变,或者至少它不应该改变。所以这是你的断言可以监控的东西。他们可以检查这个条件是否全部为真。

  
可读性
  • 可读性只是你的代码应该可以被不同的人或你自己轻松理解。
  • 因为如果你一个月不看(你自己的代码)然后你不得不回去看它,你可能忘记了细节。因此,如果您以可读的方式编写它,您自己也会受益。所以要考虑人类阅读它,而不仅仅是编译器阅读它。
  • 编写更具可读性的代码显然会减少将来理解它的努力。这就是直接的后果。但最重要的后果是您的软件变得更易于维护的中长期后果。发现错误变得更容易。修复它们变得更容易。尤其是,以不会在其他地方产生新问题的方式修复它变得更加容易。因为您实际上了解该代码的意图。

 
可重用性
  • 大约 30 年前,面向对象语言问世时,可重用性曾经是它们的一大卖点。但是现在,它的强调程度要低得多。我想这可能是一开始就被过分强调了。
  • 即使使您的代码更通用,也需要付出代价。然后它变得更复杂、更抽象,而且可能效率更低,可读性也更低。但是你会得到它可重用的单一好处。
  • 你真的必须找到正确的平衡点。所以我认为总的来说,你希望你的大部分代码都是特定于应用程序的,而不是可重用的。您将拥有一些可重用的部分,并且大部分代码将是临时的和自定义的,不可重用。
  • 选择适量的可重用性,并将其变成有用的通用框架。但另一方面,如果您过分依赖这些框架,或者如果您试图让所有代码过于通用,您最终会得到一个过于抽象且可能未得到优化的代码库。

  
线程安全
  • 我在书中提出的第一个我认为可能很有趣的建议是为您的类建立一个明确的并发策略。
    • 如果你知道你的类将被不同的线程并发使用,那么即使在急于在这里和那里放置互斥锁和同步原语之前,你也应该首先建立一个明确的并发策略。
    • 什么是并发策略?这是一条规则,该类中的哪些操作可以在不同步的情况下并行执行,因为它们影响不同的数据,而哪些操作需要序列化,因此需要同步,因为它们可能影响相同的数据。
  • 另一方面,一个更普遍的建议是避免所有这些问题。只需使用您的语言或您的框架将提供的更高级别的并发原语。
  • 我们应该尽量避免所有同步混乱和竞争条件风险带来的所有混乱,而只使用您的语言或框架中内置的内容。
  • 换句话说,了解你的标准库,了解你的框架,并尽量在抽象中保持尽可能高的水平,这样所有这些问题都已经为你处理好,并经过测试,你在这方面的风险要少得多道路。
  • 拥有一旦创建就无法修改的对象,这就是不变性的含义。这是一个强大的工具,可以避免竞争条件,避免并发的许多令人讨厌的错误。
    • 另一方面,您可能会有与不可变数据相关的性能成本,因为有时应用程序的上下文确实需要您更改某些对象的状态。
  • 你必须平衡它。因此,如果您有很多并发,或者如果您有一点并发,但您处于一个真正对安全至关重要的应用程序中,其中一个错误可能会对经济或金钱或健康造成严重的后果,如果您是从事健康技术业务,然后保持不变。付出一点性能代价,了解高效的不可变集合、不可变模式,你就会更安全。

  
最小可行代码
  • 我认为瞄准或渴望完美的代码是明智的。但另一方面,对理想代码的外观有一个粗略的想法。
  • 但是,当然,还有实际问题。有截止日期。有顾客。所以,当然,你必须妥协。我认为您获得的经验越多,您就越容易找到正确的折衷方案。
  • 争取妥协固然很好,但至少,您应该知道如何改进。你没有时间。这太糟糕了。这就是生活。但如果你做到了,那将是一个了不起的成就。你知道你所做的妥协。如果你的老板或你的客户多给你一周时间,你会如何改进它。你已经知道如何让它变得更好。