OverRainbow

General principles of software development

☕️ 2 min read

你可以从本文了解到

本文是对《软件设计的 201 个原则》的第1章——软件开发的一般原则的学习

001 质量第一

质量第一,没有可权衡的余地

当你被要求加快测试、忽视剩余的少量bug、在设计或需求达成一致前就开始编码时,直接说"不"。

002 质量在每个人眼中不同

对开发者来说,质量可能是优雅的设计或优雅的代码。

对在紧张环境中工作的用户来说,质量可能是响应时间或大容量。

对成本敏感的项目来说,质量可能是低开发成本。

对一些客户来说,质量可能是满足他们所有已知和未知的需求。

这里的难题是,以上要求可能无法完全兼顾

当优化某人关注的质量时,可能会危害其他人关注的质量(这就是温伯格的“政治困境”原则)。

项目必须确定各因素的优先级,并清晰地传达给所有相关方。

003 开发效率和质量密不可分

对质量要求越高,开发效率就越低。对质量要求越低,开发效率就越高。

004 高质量软件是可以实现的

大型软件系统可以以非常高的质量构建,但价格昂贵:每行代码高达 1000美元。

作为软件开发人员,应该学习和了解已被验证、可以极大提高软件质量的方法。

这些方法包括:让客户参与(原则 8)、原型设计(在全面开发之前验证需求;原则 11 至 13)、保持设计简单(原则 67)、代码评审(原则 98)和雇用最优秀的人(原则 130 和 131)

作为客户,追求卓越的同时,要意识到随之而来的高额成本。

005 不要试图改进质量

质量无法通过软件的改进来获得。

这适用于质量的任何定义:可维护性、可靠性、适应性、可测试性、安全性等等。

即使我们在开发过程中努力,使软件具备高质量也是十分不易的。如果我们不努力,又怎么可能期望获得高质量呢?

这就是绝不能将“一次性原型”转换成产品的主要原因(原则 11)。

006 低可靠性比低效率更糟糕

如果软件执行效率不高,通常可以分离消耗大部分执行时间的程序单元,重新设计或编码以提高效率(原则194)。

低可靠性问题不仅难以发现,也更难以修复。

一旦低可靠性问题显现,通常难以隔离其影响。

007 尽早把产品交给客户

如果遵循传统的瀑布模型,那么在99%的开发资源已经耗尽之后,才会第一次向客户交付产品。

可在开发过程的早期构建一个快速而粗糙的原型,将这个原型交付给客户,收集反馈,然后编写需求规格说明、并进行正规的开发。使用这种方法,当客户体验到第一个产品版本时,只消耗了5–20%的开发资源。

这有助于确保将剩余的资源用于开发正确的系统。

008 与客户/用户沟通

永远不要忽视软件开发的原因:满足真正的需求,解决真正的问题。

解决真正需求的唯一方法,是去跟有真正需求的人沟通。

009 激励开发者与客户对齐

为对齐双方的目标,有如下方法:

(1)按优先级对需求排序(原则50),以便开发人员了解它们的相对重要性

(2)根据需求的优先级奖励开发人员(例如:所有高优先级的需求必须完成;每完成一个中优先级的需求,开发人员可获得一些额外的小奖励;每完成一个低优先级的需求,可获得的奖励非常小)

(3)对逾期交付实行严厉的处罚

010 做好抛弃的准备

对一个项目来说,最关键的成功因素之一是,它是否是全新的。

在全新领域(可能涉及:应用程序、体系结构、接口、算法等)研发的程序很少第一次就成功。

弗雷德·布鲁克斯(Fred Brooks)在《人月神话》中明确建议:“无论如何,你一定要做好抛弃的准备”。

这个建议最初由温斯顿·罗伊斯(Winston Royce)在1970年提出,他说一个人应该做好准备:第一个完整部署的系统,往往是第二个被创建的系统

第一个系统至少可用于验证关键的设计问题和操作概念。

此外,罗伊斯建议,应该使用大约25%的资源开发这样的预发布版本。

作为一个全新定制产品的开发人员,在开始全面的开发之前,要规划开发一系列“一次性原型”(原则11、12和13)。

作为商用大规模系统的开发人员,可以预期,第一个产品版本在一定年限内将能够被修改,之后它将被完全替换(相关原则185,186,188和201)。

作为产品的维护者,请注意,在程序变得不稳定以至于必须被替换之前,你对程序可以调整的地方还有很多 (请参阅相关原则 186,191,195 和 197)。

010 开发正确的原型

有两种原型:一次性(throwaway)原型和演进式(evolutionary)原型

一次性原型用快速而粗糙的方式构建,交给客户以获得反馈,在得到期待的信息后即被废弃。获得的信息被整理进需求规格说明,用于正规的产品开发。

演进式原型用高质量的方式构建,交给客户以获得反馈,获得期待的信息便进行修改,以更加贴近用户的需求。重复此过程,直到产品收敛到所期望的样子。

一次性原型应该在关键需求特性没有很好理解时使用。

演进式原型应该在关键特性已被充分理解,但很多其他需求特性没被充分理解时使用。

如果对大多数功能都不了解,则首先构建一个一次性原型,然后从零开始构建一个演进式原型。

012 构建合适功能的原型

当建立一次性原型时,只需要开发那些没有被充分理解的特性。如果你开发已充分理解的特性,最终除了浪费资源外,将一无所获。

当建立演进式原型(原则13)时,要优先开发那些已经被充分理解的特性。(注意,它们可能已经被充分理解,因为之前已使用一次性原型进行验证)

你的希望是,通过体验这些特性,用户能更好地确定其它需求。

如果你基于模糊的需求(高质量的)开发了一个演进式原型,一旦需求搞错了,你将不得不抛弃这个高质量的软件,并且浪费了资源。

013 要快速的开发一次性原型

如果你已经决定开发一次性原型,就要用最快的方式。

不用担心质量。可使用“一页纸”的需求规格说明。不用担心设计或编码中的文档。可以使用任何工具。可以使用任何编程语言,只要能够便利程序的快速开发。不用担心编程语言的可维护性。

014 渐进地扩展系统

渐进地扩展系统,是降低软件开发风险的最有效方法之一。

从一个小的可用系统开始,只实现少数功能。然后逐步扩展,覆盖越来越多的最终功能子集。

这样做的好处是:(1)降低每次开发的风险;(2)看到一个产品版本,通常可以帮助用户想象出他们想要的其他功能。

这样做的缺点是:如果过早选择了一个不合适的系统架构,则可能需要全面的重新设计、才能适应后续的变更。在开始增量开发之前,开发一次性原型(原则11,12和13),可以降低这种风险。

015 看到越多,需要越多

在软件行业,一次次见证了:提供给用户越多的功能(或性能),用户想要的功能(或性能)就越多。

当然,这支持了原则7(尽早把产品交给客户),原则14(渐进的开发系统),原则185(软件将会持续改变)以及原则201(系统的存在促进演变)。

但更重要的是,你必须为不可避免的情况做好准备。

管理和工程流程的每个方面都应该意识到,一旦客户看到产品,他们就会想要更多。

这意味着,所产生的每个文档都应该以有利于更改的方式进行存储和组织。

这意味着,配置管理流程(原则174)必须在距离交付很长时间之前就位。

这也意味着,在软件部署后不久,你就应该准备好,以应对用户口头或书面请求的冲击。

这还意味着,你选择的设计方案应使容量、输入速率和功能都很容易变更。

016 开发过程中的变化是不可避免的

爱德华·伯索夫(Edward Bersoff)等人将系统工程的第一定律定义为:“无论你在系统[开发]生命周期中的何处,系统都将发生变化,并且对其进行改变的愿望将在整个生命周期中持续存在。

与原则 185 和 201(强调软件部署后,需求可能发生巨大变化)不同,本原则想表达,在开发过程中,软件也可能发生巨大变化。

这些变化可能体现在编写新的代码、新的测试计划或新的需求规格说明。

这些变化可能意味着,要去修复某个被发现是不正确的中间产品。可能它们反映了完善、或改进产品的自然过程。

为变化做好准备,要确保:软件开发涉及的所有产品之间的相互引用都是适当的(原则 43,62 和 107);变更管理流程已就位(原则 174,178 至 183);预算和进度表有足够的余地,不会为了满足预算和进度表而倾向于忽略必要的变化(原则 147,148 和 160)。

017 只要可能,购买而非开发

要降低不断上涨的软件开发成本和风险,最有效的方法就是,购买现成的软件,而不是自己从头开发。

确实,现成的软件也许只能解决 75% 的问题。

但考虑一下从头开发的选择吧:支付至少 10 倍于购买软件的费用,冒着超出预算 100% 且延期的风险(如果最后能够完成!),并且最终发现,它只能满足75%的预期。

对一个客户来说,新的软件开发项目似乎最初总是令人兴奋。

开发团队也是“乐观的”,对“最终”解决方案充满了希望。

但几乎很少有软件开发项目能够顺利运行。不断增加的成本通常会导致需求被缩减,最终研发出的软件,它可以满足的需求,也许跟现成的软件差不多。

作为一个开发者,应该复用尽可能多的软件。复用是“购买而非开发”原则在较小范围内的体现。参考相关的原则 84。

018 让软件只需简短的用户手册

手册越短,软件质量越好。

019 每个复杂问题都有一个解决方案

Wlad Turski说,“每一个复杂的问题,都有一个简单的解决方案…但这是错误的!”

无论任何人向你提出“只要遵循这10个简单步骤,软件质量问题就会消失”,或是其他类似建议,都要保持高度的怀疑。

020 记录你的假设

系统运行的环境在本质上是无限的,不可能被完全理解。

当我们开发一个系统,宣称要解决某个环境中的一个问题时,我们会对该环境进行假设。

对需求工程、设计、编码和测试期间所做的所有假设,始终保持觉察是不可能的。

尽管如此,我还是建议,对你有意识做出的假设做个记录。即使这个假设是显而易见的、或其它选项很荒谬,也要这样做。

还要记录它们的影响,也就是说在产品中,这些假设是如何体现的?理想情况下,你应该会通过封装每个假设来隔离这些影响(原则65)。

021 不同的阶段,使用不同的语言

使用越多的符号、越丰富多样的表达方式,我们就越能更好地对开发中的产品进行可视化。

对于需求工程,应该选择一组最优的技术和语言(原则47和48)。

对于设计工作,应该选择一组最优的技术和语言(原则63和81)。

对于编码,应该选择一个最适合的语言(原则102和103)。

另一方面,如果一个语言从某方面在两个阶段都是最优选择,就务必使用它。

022 技术优先于工具

在使用工具前,你应该“有规矩”(即,理解并遵循适当的软件开发方法)。

当然,你也要了解如何使用工具,但这和“有规矩”相比是第二位的。

我强烈建议,在投资于工具、以对某个技术“自动化”之前,先手工验证这个技术,并说服自己和管理层、这个技术是可行的。

在大多数情况下,如果一项技术在手工时不灵,那在自动时它也不灵。

023 使用工具,但要务实

一些软件工具(如CASE)会让他们的用户更加高效。

务必要使用它们。

024 把工具交给优秀的工程师

然而,就像文字处理软件不能让一个平庸的小说家(写小说,但卖不出去)变得出色,CASE工具也不能让一个平庸的软件工程师(写软件,但不可靠、不满足用户需求等)变的出色。

因此,你想把CASE工具只提供给优秀的工程师。

你最不想做的一件事,就是把CASE工具提供给平庸的工程师:你希望他们尽量少(而非多)的开发出质量低劣的软件。

025 CASE工具是昂贵的

CASE工具对软件开发是必需的。

它们应该被视为业务成本的一部分。

在做投资回报分析时,不仅需要考虑购买工具的高额费用,还需要考虑没有购买工具带来的更高代价(更低开发效率、更高的客户失望率、延迟的产品发布、增加的重复工作、更差的产品质量、增加的员工流动)。

026 “知道何时”和“知道如何”同样重要

知道如何很好地使用技术,既不会让技术本身成为好技术,也不会让你成为一名优秀的工程师。

一名优秀的工程师了解很多不同种类的技术,并且知道每种技术何时适合项目或项目的一部分。

在进行需求工程时,要了解哪种技术对问题的哪些方面最有用(原则 47)。

当进行设计时,要理解哪些技术对系统的哪些方面最有用(原则 63)。

当进行编码时,要选择最合适的编程语言(原则 102)。

027 实现目标就停止

软件工程师遵循许多方法(也称为技术或流程)。

每个方法都有各自的用途,通常对应软件开发的一个子目标。

例如,结构化(或者面向对象)分析的目标是理解要解决的问题,DARTS的目标是处理架构,结构化设计的目标是理清调用层次结构。

这些例子中的方法都包含一系列的步骤。

不要太过陷于具体的方法,而忘记了目标本身。

不要为更换目标而感到内疚。

例如,如果只执行了方法的一半步骤,你就理解了问题,那就停下来。

另一方面,你需要对整个软件过程有很好的认识,因为基于本原则所抛弃的某个方法的后续步骤可能会对未来软件的使用产生重要影响。

028 了解形式化方法

每个项目中,至少应该有一个人熟练使用形式化方法,以确保不会错过提升产品质量的机会。

很多人以为,使用形式化方法的唯一途径,就是完全使用它们来定义系统。

其实并非如此。

实际上,最有效的方法之一,是先用自然语言描述。

尝试用更形式化的方式书写,会帮助你发现在自然语言中存在的问题。

修正自然语言表达中的问题,你会得到一个更好的文档。

029 和组织荣辱与共

尽管有许多影响因素,有个日本的观念与此密切相关:产品中的缺陷是公司的耻辱;软件工程师引起的公司耻辱,是工程师的耻辱。

一般而言,当任何人发现你在产品中的错误时,你应该心存感激,而不是试图辩解。

将错误广而告之有两个好处:(1) 帮助其他工程师,避免同样的错误 (2) 对后续的错误修正,也可以不那么抵触。

030 跟风要小心

大家都做的事情,不一定对你也是正确的。

也许它是正确的,但你也应该评估它对你所处环境的适用性。

这样的例子包括:面向对象,软件度量(原则142、143、149、150 和 151),软件复用(原则84),过程成熟度(原则163),计算机辅助软件工程(CASE,原则22至25),原型设计(原则11、12、13、42)。

在所有案例中,这些方法都提供了非常积极的帮助,体现在提高质量、降低成本、提高用户满意度等方面。

然而,这些好处只在它们有作用的组织中才会显现出来。

尽管回报显著,它们的作用常常被过度宣传,其实它们并不是那么必然或通用。

当你学习“新”技术,不要轻易接受与之相关的不可避免的炒作(原则129)。

仔细阅读。

理性考虑它的收益和风险。

在大规模应用之前进行试验。

但同时也绝对不要忽略“新”技术(参见原则31)。

031 不要忽视技术

软件工程技术日新月异。在几年内对新的发展视而不见,是你无法承受的。

软件工程的发展像波浪一样。每一波都会带来大量的“潮流元素”和流行语。

尽管每一波只持续5–7年,但它们并不是简单消失。恰恰相反,其后每一波都是基于前一波的最好特征。(理想情况,“最好”应该指“最有效”,但遗憾的是,它往往指“最流行”)

有两种方式可以让你紧跟技术潮流:阅读正确的杂志,和正确的人交谈。《IEEE Software》期刊就是一个很好的渠道,可以了解未来5年内可能有用的技术。

《PC Week》、《MacWorld》等是学习硬件平台、常见商用工具和语言的好地方。要通过和人交谈来学习,就要找到正确的人。虽然和同事交流很必要,但还不够。

每年都应该努力参加1–2个关键会议。和参会者的交流,很可能比会议报告更重要。

032 使用文档标准

如果你的项目、组织或客户要求遵循一套文档标准,就要遵循它。无论如何,永远不要抱怨标准,认为这是不需要的。

所有我熟悉的标准,无论是政府标准还是商业标准,都提供了组织和内容方面的指导。

创新!即遵循标准,同时聪明的执行。无论标准怎么规定,把你知道应有的内容都包含进去。

这意味着用清晰的语言来编写,意味着添加额外的有意义的组织层级。

IEEE发布的文档标准,是我所知道的、最广泛的可用软件文档标准之一。

033 文档要有术语表

034 软件文档都要有索引

035 对相同的概念,用相同的名字

在技术文档中,必须使用相同的术语来表示相同的概念,使用相同的语句结构来表述相似的信息。

036 研究再转化,不可行

要实现从研究所到开发机构的最成功的成果转化,从一开始双方就要紧密合作。需要使用工业界的环境作为萌发想法并验证效果的实验室,而不是在想法成形后再做技术转化。

037 要承担责任

在所有工程学科中,如果一个设计失败,工程师会受到责备。因此,当一座大桥倒塌,我们会问“工程师哪里做错了?”当一个软件失败了,工程师很少受到责备。如果他们被责备了,他们会回答,“肯定是编译器出错了”,或“我只是按照指定方法的15个步骤做的”,或“我的经理让我这么干的”,或“计划剩余的时间不够”。

事实是,在任何工程学科中,用最好的方法也可能产出糟糕的设计,用最过时的方法也可能做出精致的设计。

不要有任何借口。如果你是一个系统的开发者,把它做好是你的责任。要承担这个责任。要么做好,要么就压根不做