软件工程实践原则

软件工程实践原则


1 软件工程整体实践原则 #

David Hooker[Hoo96] 提出了7个关注软件工程整体实践的原则。

原则1:存在价值。一个软件系统因能为用户提供价值而具有存在价值,所有的决策都应该基于这个思想。在确定系统需求之前,在关注系统功能之前,在决定硬件平台或者开发过程之前,问问自己:这确实能为系统增加真正的价值吗?如果答案是“不”,那就坚决不做。所有的其他原则都以这条原则为基础。

原则2:保持简洁。在软件设计中需要考虑很多因素。所有的设计都应该尽可能简洁,但不是过于简洁。这有助于构建更易于理解和易于维护的系统。这并不是说有些特性应该以“简洁”为借口而取消。的确,优雅的设计通常也是简洁的设计,但简洁不意味着“快速和粗糙”。事实上,它经常是经过大量思考和多次工作迭代才达到的,这样做的回报是所得到的软件更易于维护且错误更少。

原则3:保持愿景。清晰的愿景是软件项目成功的基础。如果缺乏概念的一致性,系统就好像是由许多不协调的设计补丁、错误的集成方式强行拼凑在一起……如果不能保持软件系统体系结构的愿景,就会削弱甚至彻底破坏设计良好的系统。获得授权的架构师能够拥有愿景,并保证系统实现始终与愿景保持一致,这对项目开发的成功至关重要。

原则4:关注使用者。在需求说明、设计、编写文档和实现过程中,牢记要让别人理解你所做的事情。对于任何一个软件产品,其工作产品都可能有很多用户。进行需求说明时应时刻想到用户,设计中始终想到实现,编码时考虑到那些要维护和扩展系统的人。一些人可能不得不调试你所编写的代码,这使得他们成了你所编写代码的使用者,尽可能地使他们的工作简单化会大大提升系统的价值。

原则5:面向未来。在现今的计算环境中,需求规格说明随时会改变,硬件平台几个月后就会淘汰,软件生命周期都是以月而不是年来衡量的。然而,真正具有“产业实力”的软件系统必须持久耐用。为了做到这一点,系统必须能适应各种变化,能成功做到这一点的系统都是那些一开始就以这种路线来设计的系统。永远不要把自己的设计局限于一隅,经常问问“如果出现……应该怎样应对”,构建可以解决通用问题的系统,为各种可能的方案做好准备,而不是仅仅针对某一个具体问题。

但把这个建议发挥到极致会非常危险。设计通用方案会带来性能损失,并降低特定解决方案的效率。

原则6:提前计划复用。复用既省时又省力。软件系统开发过程中,高水平的复用是一个很难实现的目标。曾有人宣称代码和设计复用是面向对象技术带来的主要好处,然而,这种投入的回报不会自动实现。提前做好复用计划将降低开发费用,并增加可复用组件以及组件化系统的价值。

尽管对于准备在未来项目中复用软件的人而言,这种说法是正确的,但对于设计和实现可复用组件的人来说,复用的代价会很昂贵。研究表明,设计和开发可复用组件比直接开发目标软件要增加25%~200%的成本。在有些情况下,这些费用差别是不合理的。

原则7:认真思考。这最后一条规则可能是最容易被忽略的。在行动之前清晰定位、完整思考通常能产生更好的结果。仔细思考可以提高做好事情的可能性,而且也能获得更多的知识,明确如何把事情再次做好。如果仔细思考过后还是把事情做错了,那么,这就变成了很有价值的经验。思考就是学习和了解本来一无所知的事情,使其成为研究答案的起点。把明确的思想应用在系统中,就产生了价值。使用前6原则需要认真思考,这将带来巨大的潜在回报。

2 核心原则 #

2.1 核心原则-指导软件过程的原则 #

TODO:简化的过程框架图

原则1:敏捷。关于你所选择的过程模型是传统的还是敏捷的,敏捷开发的基本原则会提供判断方法。你所做的工作的每一个方面都应着重于活动的经济性 - 保持你的技术方法尽可能简单,保持你的工作产品尽可能简洁,无论何时尽可能根据具体情况做出决定。

原则2:每一步都关注质量。每个过程活动、动作及任务的出口条件都应关注所产生的工作产品的质量。

原则3:做好适应的准备。过程不是信奉经验,其中没有信条。必要的时候,就让你的方法适应于问题、人员以及项目本身施加的限制。

原则4:建立一个有效的团队。软件工程过程和实践是重要的,但最根本的还是人。必须建立一个彼此信任和尊重的自组织团队。

原则5:建立沟通和协调机制。项目失败是由于遗漏了重要信息,或是利益相关者未能尽力去创造一个成功的最终产品。这些属于管理的问题,必须设法解决。

原则6:管理变更。管理变更的方法可以是正式的或非正式的,但是必须建立一种机制,来管理变更要求的提出、变更的评估、变更的批准以及变更实施的方式。

原则7:评估风险。在进行软件开发时会出现很多问题。建立应急计划是非常重要的。某些应急计划会成为安全工程任务的基准。

原则8:创造能给别人带来价值的工作产品。唯有那些能为其他过程活动、动作或任务提供价值的工作产品才值得创造。每一个工作产品都会作为软件工程实践的一部分传递给别人。一定要确保工作产品所传达的是必要信息,不会模棱两可或残缺不全。

2.2 核心原则-指导实践的原则 #

原则1:分治策略(分割和攻克)。更具技术性的表达方式是:分析和设计中应经常强调关注点分离(Seqaration of Concerns, SoCs)。一个大问题分解为一组小元素(或关注点)之后就比较容易求解。

原则2:理解抽象的使用。在这一核心原则中,抽象就是对系统中一些复杂元素的简单化,用一个专业术语来交流信息。我使用报表这一抽象概念,是假设你可以理解什么是报表,报表表示的是目录的普通结构,可以将经典的功能应用其中。在软件工程实践中可以使用许多不同层次的抽象,每个抽象都通告或暗示着必须交流的信息。在分析和设计中,软件团队通常从高度抽象的模型开始(如报表),然后逐渐将这些模型提炼成较低层次的抽象(如专栏或SUM功能)。

原则3:力求一致性。无论是是创建分析模型、开发软件设计、开发源代码还是创建测试用例,一致性原则都建议采用用户熟悉的上下文以使软件易于使用。例如,为手机应用设计一个用户界面,一致的菜单选择、一致的色彩设计以及一致的可识别图标都有助于增强用户体验。

原则4:关注信息传送。软件所涉及的信息传送是多方面的 - 从数据库到最终用户、从遗留的应用系统到WebApp、从最终用户到图形用户界面(GUI)、从操作系统到应用、从一个软件组件到另一个组件,这个列表几乎是无穷无尽的。在每一种情况下,信息都会流经界面,因而,就有可能出现错误、遗漏或者歧义的情况。这一原则的含义是必须特别注意界面的分析、设计、构件和测试。

原则5:构件能展示有效模块化的软件。对重要事物的分割(原则1)建立了软件的哲学,模块化则提供了实现这一哲学的机制。任何一个复杂的系统都可以被分割成许多模块(组件),但是好的软件工程实践不仅如此,它还要求模块必须是有效的。也就是说,每个模块都应该是专门集中表示系统中约束良好的一个方面。另外,模块应当以相对简单的方式与其他模块、数据源以及环境方面关联。

原则6:寻找模式。软件工程师使用模式为他们过去遇到的问题进行分类,并重用其解决方案。通过允许复杂系统中的组件独立发展,可以将这些设计模式应用于更广泛的系统工程和系统集成问题。

原则7:在可能的时候,用大量不同的观点描述问题及其解决方法。当我们用大量不同的观点检测一个问题及其求解方法时,就很有可能获得更深刻的认识,并发现错误和遗漏。统一建模语言(UML)提供了一种从多个视角描述问题解决方案的方式。

原则8:记住,有人将要对软件进行维护。从长期看,缺陷暴露出来时,软件需要修正;环境发生变化时,软件需要适应;利益相关者需要更多功能时,软件需要增强。如果可靠的软件工程实践能够贯穿于整个软件过程,就会便于这些维护活动的实施。

3 指导框架活动的原则 #

3.1 指导框架活动的原则-沟通原则 #

原则1:倾听。在沟通之前,一定要确保你能够理解他人的观点,对对方的需求有所了解,然后再倾听。仔细倾听讲话者的每一句话,而不是急于叙述你对这些话的看法。如果有什么事情不清楚,可以要求他澄清,但是不要经常打断别人的讲述。当别人正在陈述的时候,不要在言语或动作上表现出异议(比如转动眼睛或者摇头)。

原则2:有准备的沟通。在与其他人碰面之前花点时间去理解问题。如果必要的话,做一些调查来理解业务领域的术语。如果你负责主持一个会议,那么在开会之前准备一个议事日程。

原则3:沟通活动需要有人推动。每个沟通会议都应该有一个主持人(推动者),其作用是:

  1. 保持会议向着有效的方向进行;
  2. 调解会议中发生的冲突;
  3. 确保遵循我们所说的沟通原则。

原则4:最好当面沟通。但是,如果能以一些其他表达方式把相关问题呈现出来,通常可以工作得更好,例如可以在集中讨论中使用草图或文档草稿。

原则5:记笔记并且记录所有决定。任何解决方法都可能有缺陷。参与沟通的记录员应该记录下所有要点和决定。

原则6:保持通力协作。当项目组成员的想法需要汇集在一起用以阐述一个产品或者某个系统功能或特性时,就产生了协作与合作的问题。每次小型的协作都可能建立起项目成员之间的项目信任,并且为项目组创建一致的目标。

原则7:把讨论集中在限定的范围内。在任何交流中,参与的人越多,话题转移到其他地方的可能性就越大。推动者应该保持谈话模块化,只有某个话题完全解决之后才能开始别的话题(不过还应该注意原则9)。

原则8:如果某些东西难以表述清楚,就采用图形表示。语言沟通的效果很有限,当语言无法表述某项工作的时候,草图或者绘图通常可以让表述变得更为清晰。

原则9:一旦认可某件事情,转换话题;如果不认可某件事情,转化话题;如果某项特性、功能不清晰或当时无法澄清,转换话题。交流如同所有其他软件工程活动一样需要时间,与其永无止境地迭代,不如让参与者认识到还有很多话题需要讨论(参见原则2),“转换话题”有时是达到敏捷交流的最好方式。

原则10:协商不是一场竞赛或者一场游戏,双赢才能发挥协商的最大价值。很多时候软件工程师和利益相关者必须商讨一些问题,如功能和特性、优先级和交付日期等。若要团队合作很好,那么各方要有一个共同的目标,并且协商还需要各方的协调。

3.2 指导框架活动的原则-策划原则 #

策划活动包括一系列管理和技术实践,可以为软件开发团队定义一个便于他们向着战略目标和战术目标前进的路线图。

制定计划需要遵循以下原则。

原则1:理解项目范围。如果你不知道要去哪里,就不可能使用路线图。范围可以为软件开发团队提供一个目的地。

原则2:让利益相关者参与策划。利益相关者能够限定一些优先次序,确定项目的约束。为了适应这种情况,软件工程师必须经常商谈交付的顺序、时间表以及其他与项目相关的问题。

原则3:要认识到计划的制定应按照迭代方式进行。项目计划不可能一成不变。在工作开始的时候,有很多事情有可能改变,那么就必须调整计划以适应这些变化。另外,在确定每个软件增量后,迭代式增量过程模型应该包含根据用户反馈的信息来修改时间计划。

原则4:基于已知的估算。估算的目的是基于项目组对将要完成工作的当前理解,提供一种关于工作量、成本和任务工期的指标。如果信息是含糊的或者不可靠的,估算也将是不可靠的。

原则5:计划时考虑风险。如果团队已经明确了哪些风险最容易发生且影响最大,那么应急计划就是必需的了。另外,项目计划(包括进度计划)应该可以调整,以适应那些可能发生的一种或多种风险。

原则6:保证可实现性。人们不能每天百分百地投入工作。变化总是在发生。甚至最好的软件工程师都会犯错误,这些现实情况都应该在项目制定计划的时候考虑。

原则7:调整计划粒度。粒度指的是表示或者执行某些计划要素的细节。“细粒度”的计划可以提供重要的工作任务细节,这些细节是在相对短的时间段内计划完成的(这样就常会有跟踪和控制的问题)。“细粒度”的计划提供了更宽泛的长时间工作任务。通常,粒度随项目的进行而从细到粗。在很多个月内都不会发生的活动则不需要细化(太多的东西将会发生变化)。

原则8:制定计划以确保质量。计划中应该确定软件开发团队如何确保开发的质量。如果要执行正式技术评审的话,应该将其列入进度;如果在构建过程中用到了结对编程,那么在计划中要明确描述。

原则9:描述如何适应变化。即使最好的策划也有可能被无法控制的变化破坏。软件开发团队应该确定在软件开发过程中如何适应变化,例如,客户会随时提出变更吗?如果提出了一个变更,团队是不是要立即实现?变更会带来怎样的影响和开销?

原则10:经常跟踪并根据需要调整计划。项目每次会落后进度一天的时间。因此,需要每天都跟踪计划的进展,找出计划与实际执行不一致的问题所在,当任务执行出现延误时,计划也要随之做出调整。

最高效的方法是软件开发项目组所有成员都参与到策划活动中来,只有这样,项目组成员才能很好地认可所制定的计划。

3.3 指导框架活动的原则-建模原则 #

在软件工程中要创建两类模型:需求模型和设计模型。

  • 需求模型(也称为分析模型)通过在以下三个不同域描述软件来表达客户的需求:信息域、功能域和行为域。
  • 设计模型表述了可以帮助开发者高效开发软件的特征:体系架构、用户界面以及组件细节。

Scott Ambler和Ron Jeffries[Amb02b] 在他们关于敏捷建模的书中定义了一系列建模原则,提供给使用敏捷过程模型的人,但也适用于执行建模活动和任务的软件工程师。

原则1:软件团队的主要目标是构建软件而不是创建模型。敏捷的意义是尽可能快地将软件提供给用户。可以达到这个目标的模型是值得软件团队构建的,但是,我们需要避免那些降低了开发过程的速度以及不能提供新的见解的模型。

原则2:轻装前进 - 不要创建任何不需要的模型。每次发生变化时,创建的模型必须是最新的。更重要的是,每创建一个新模型所花费的时间,还不如花费在构件软件上(编码或测试)。因此,只创建那些可以使软件的构建更加简便和快速的模型。

原则3:尽量创建能描述问题和软件的最简单模型。不要建造过于复杂的软件[Amb02b]。保持模型简单,产生的软件必然也会简单。最终的结果是,软件易于集成、易于测试且易于维护(对于变更)。另外,简单的模型易于开发团队成员理解和评判,从而使得持续不断的反馈可以对最终结果进行变化。

原则4:用能适应变化的方式构建模型。假设模型将要发生变化,但做这种假设并不草率。问题在于,如果没有相当完整的需求模型,那么所创建的设计(设计模型)会常常丢失重要功能和特性。

原则5:明确描述创建每一个模型的目的。每次创建模型时,都问一下自己为什么这么做。如果不能为模型的存在提供可靠的理由,就不要再在这个模型上花费时间。

原则6:调整模型来适应待开发系统。有时需要使模型的表达方式或规则适用于应用问题。例如,一个电子游戏应用需要的建模技术与自动驾驶汽车所使用的实时嵌入式的巡航定速软件所需的建模技术或许会完全不同。

原则7:尽量构建有用的模型而不是完美的模型。当构建需求模型和设计模型时,软件工程师要达到减少返工的目的。也就是说,努力使模型绝对完美和内部一致的做法是不值当的。无休止地使模型“完美”并不能满足敏捷的要求。

原则8:对于模型的构造方法不要过于死板。如果模型能够成功地传递信息,那么表述形式是次要的。虽然软件团队的每个人在建模期间都应使用一致的表达方式,但建模最重要的特性是交流信息,以便软件工程师执行下一个任务。如果模型可以成功地做到这一点,不正确的表达方式就可以忽略。

原则9:如果直觉告诉你模型不太妥当,尽管书面上很正确,那么你也要仔细注意了。如果你是个有经验的软件工程师,就应当相信直觉。软件工作中有许多教训 - 其中有些是潜意识的。如果有些事情告诉你设计的模型注定会失败(尽管你不能明确地证明),你就有理由再花一些时间来检查模型或开发另一个模型。

原则10:尽可能快地获得反馈。任何模型都是为了传递信息,模型应该能够独立地表达信息,不需要任何人去解释它。每个模型都应经过软件团队的评审。评审的目的是提供反馈,用于纠正模型中的错误、改变误解,并增加不经意遗漏的功能和特性。

3.4 指导框架活动的原则-构建原则 #

构建活动包括一系列编码和测试任务,从而为向客户和最终用户交付可运行软件做好准备。

在现代软件工程中,编码可能是:

  1. 直接生成编程语言源代码;
  2. 使用与待开发组件类似的中间设计来自动生成源代码;
  3. 使用第四代编程语言(如 Unreal4 Blueprints)自动生成可执行代码。

测试包括:

  1. 组件级的,单元测试;
  2. 确认测试,测试系统(或软件增量部分)是否完全按照需求开发;
  3. 验收测试,由客户检验系统所有要求的功能和特性。

在编码和测试过程中有一套原则,如下。

3.4.1 指导框架活动的原则-构建原则-编码原则 #

  1. 准备原则:在写下每行代码之前,要确保:
    • 原则1:理解所要解决的问题
    • 原则2:理解基本的设计原则和概念
    • 原则3:选择一种能够满足构建软件以及运行环境要求的编程语言
    • 原则4:选择一种能提供工具以简化工作的编程环境
    • 原则5:组件级编码完成后进行单元测试
  2. 编程原则:在开始编码时,要确保:
    • 原则6:遵循结构化编程方法[Boh00]来约束算法
    • 原则7:考虑使用结对编程
    • 原则8:选择能满足设计要求的数据结构
    • 原则9:理解软件体系结构并开发出与其相符的接口
  3. 确认原则:在完成第一阶段的编码之后,要确保:
    • 原则10:适当进行代码走查
    • 原则11:进行单元测试并改正所发现的错误
    • 原则12:重构代码来进行改进代码质量

3.4.2 指导框架活动的原则-构建原则-测试原则 #

在一本经典软件测试书中,Glen Myer[Mye79]描述了一系列测试规则,这些规则很好地阐明了测试的目标:

  1. 测试是一个以查找程序错误为目的的程序执行过程。
  2. 好的测试用例能最大限度地找到尚未发现的错误。
  3. 成功的测试能找到那些尚未发现的错误。

我们的目标是要设计一些能用最短的时间、最少的工作量来系统地揭示不同类型错误的测试。

测试的第二个好处是,它能表明软件功能的执行似乎是按照规格说明来进行的,行为需求和性能需求似乎也可以得到满足。

此外,测试时收集的数据为软件的可靠性提供了很好的说明,并且也为软件质量提供了一些说明。

但是测试并不能说明某些错误和缺陷不存在,它只能显示出现存的错误和缺陷。「测试永远不是完全的」,在测试时保持这样一个观念是非常重要的,其实这并不是悲观。

Davis[Dav95b]提出了一套测试原则,另外,Everett和Meyer[Eve09]增加了一些原则。

原则1:所有的测试都应该可以追溯到用户需求。软件测试的目标就是要揭示错误。而最严重的错误(从用户的角度来看)是那种导致程序无法满足需求的错误。

原则2:测试计划应该远在测试之前就开始着手。测试计划在分析模型一完成就应该开始。测试用例的详细定义可以在设计模型确定以后开始。因此,所有的测试在编码前都应该计划和设计好了。

原则3:将 Pareto 原则应用于软件测试。简单的说,Pareto 原则认为在软件测试过程中 80% 的错误都可以在大概 20% 的程序组件中找到根源。接下来的问题当然就是要分离那些可疑的组件,然后对其进行彻底的测试。

原则4:测试应该从“微观”开始,逐步转向“宏观”。最初计划并执行的测试通常着眼于单个程序模块,随着测试的进行,着眼点要慢慢转向在集成的组件簇中寻找错误,最后在整个系统中寻找错误。

原则5:穷举测试是不可能的。即便是一个中等大小的程序,其路径排列组合的数目都非常庞大。因此,在测试中对每个路径组合进行测试是不可能的。然后,充分覆盖程序逻辑并确保组件级设计中的所有条件都通过测试是有可能的。

原则6:为系统的每个模块做相应的缺陷密度测试。通常在最新的模块或者开发人员最缺乏理解的模块中进行这些测试。

原则7:静态测试技术能得到很好的结果。有超过 85% 的软件缺陷源于软件文档(需求、规格说明、代码走查和用户手册)[Jon91]。这对系统文档测试是有价值的。

原则8:跟踪缺陷,查找并测试未覆盖缺陷的模式。未发现的缺陷总数是软件质量好坏的指示器,未发现的缺陷类型可以很好地度量软件的稳定性。统计超时发现缺陷的模式可以预测缺陷的期望值。

原则9:包含在演示软件中的测试用例是正确的行为。在维护和修改软件组件时,未预料到的交互操作会无意识地影响另外的一些组件。在软件产品变更后要准备检测系统行为,进行一组回归测试是很重要的。

3.5 指导框架活动的原则-部署原则 #

部署活动包括3个动作:

  1. 交付
  2. 支持
  3. 反馈

由于现代软件过程模型实质上是演化式或是增量式的,因此,部署活动并不是只发生一次,而是在软件完全开发完成之前进行许多次:

  • 每个交付周期都会向客户和最终用户提供一个可运行的并具有可用功能和特性的软件增量。
  • 每个支持周期都会为部署周期中所提到的所有功能和特性提供一些文档和人员帮助。
  • 每个反馈周期都会为软件开发团队提供一些重要的引导,以帮助修改软件功能、特性以及下一个增量所用到的方法。

软件开发团队在准备交付一个软件增量时所应该要遵从的重要原则,如下。

原则1:客户对于软件的期望必须得到管理。客户期望的结果通常比软件团队承诺交付的要多,这回很快令客户失望。这将导致客户反馈变得没有积极意义并且还会挫伤软件开发团队的士气。

Naomi Karten[Kar94]在她的关于管理客户期望的书中提到,“管理客户期望首先应该认真考虑你该与客户交流什么与怎样交流。”她建议软件工程师必须认真地处理与客户有冲突的信息。(例如,对不可能在交付时完成的工作做出了承诺;在某次软件增量交付时交付了多于当初承诺要交付的工作,这将使得下次增量所要做的工作随之变少。)

原则2:完整的交付包应该经过安装和测试。所有可执行软件、支持数据文件、支持文档和一些相关的信息都必须组装起来,并经过实际用户的完整测试。所有的安装脚本和其他一些可操作的功能都应该在所有可能的计算配置(例如硬件、操作系统、外围设备、网络)环境中实施充分的检验。

原则3:技术支持必须在软件交付之前就确定下来。最终用户希望在问题发生时能得到及时的响应和准确的信息。如果技术支持跟不上或者根本就没有技术支持,那么客户会立即表示不满。支持应该是有计划的,准备好支持的材料并且建立适当的记录保持机制,这样软件开发团队就能按照支持请求种类进行分类评估。

原则4:必须为最终用户提供适当的说明材料。软件开发团队交付的不仅仅是软件本身,也应该提供培训材料(如果需要的话)和故障解决方案,还应该发布关于“本次增量与以前版本有何不同”的描述。

原则5:有缺陷的软件应该先改正再交付。迫于时间的压力,某些软件组织会交付一些低质量的增量,还会在增量中向客户提出警告:这些缺陷将在下次发布时解决。这样做是错误的。在软件商务活动中有这样一条谚语:“客户在几天后就会忘掉你所交付的高质量软件,但是他们永远忘不掉那些低质量的产品所出现的问题。软件会时刻提醒着问题的存在。”

4 建模 #

4.1 建模-需求分析的经验原则 #

创建分析模型时,应考虑一些经验原则:

  • 首先,关注问题或业务域,同时保持较高的抽象水平
  • 其次,认识到分析模型应该提供对软件的信息域、功能和行为的了解
  • 第三,将对软件体系结构和非功能性细节的考虑推迟到建模活动的后期
  • 另外,重要的是意识到软件元素与其他元素之间的相互连接方式(我们称为系统耦合)

分析模型的结构必须能够为所有利益相关者提供价值,并且应在不牺牲清晰度的情况下尽可能保持简单

4.2 建模-需求建模原则 #

原则1:问题的信息域必须得到表达和理解。信息域包含流入系统(来自终端用户、其他系统或外部设备)的数据,流出系统(通过用户界面、网络界面、报告、图形和其他方式)的数据,以及数据存储区中收集和整理的永久保存的数据。

原则2:必须定义软件执行的功能。软件功能可以为终端用户带来直接好处,而那些为其他用户可见的功能提供内部支持的功能也可以直接受益。一些功能可以变换流入系统的数据。在其他情况下,功能会在某种程度上影响内部软件处理或外部系统元素的控制。

原则3:必须表示软件的行为(作为外部事件的结果)。计算机软件的行为受其与外部环境的交互作用的驱动。终端用户提供的输入、外部系统提供的控制数据或通过网络收集的监视数据都使软件以特定方式运行。

原则4:描述信息、功能和行为的模型必须以分层(或分级)的方式进行分割以揭示细节。需求建模是解决软件工程问题的第一步。它使你可以更好地理解问题并以解决方案(设计)建立基础。复杂的问题很难整体解决,因此应该使用分而治之的策略,将一个大而复杂的问题划分为多个子问题,直到每个子问题都相对容易理解为止。这个概念称为关注点划分或者分离,它是需求建模中的关键策略。

原则5:分析任务应从基本信息转向实现细节。分析建模首先从终端用户的角度描述问题。描述问题的“本质”时没有考虑解决方案的实现方式。例如,电子游戏要求玩家在进入危险的迷宫时向其主角“指示”前进的方向。这就是问题的本质。实现细节(通常描述为设计模型的一部份)指出如何实现问题的本质。对于电子游戏,可能会使用语音输入。或者,可以键入键盘命令,可以将游戏手柄(或鼠标)指向特定的方向,可以在空中挥舞动作感应设备,或者可以直接使用读取玩家身体或眼睛动作的设备。

4.3 建模-设计过程-质量指导原则 #

McGlaughlin[McG91] 提出了可以指导良好设计演化的三个特征

  1. 设计应当实现所有包含在需求模型中的显示需求,而且必须满足利益相关者期望的所有隐式需求。
  2. 对于那些编码者和测试者以及随后的软件维护者而言,设计应当是可读的、可理解的指南。
  3. 设计应当提供软件的全貌,从实现的角度对数据域、功能域和行为域进行处理。

质量指导原则为了评估某个设计表示的质量,软件团队中的成员必须建立好的设计的技术标准。考虑下面的指导原则

  1. 设计应展现出这样一种架构:
    • 已经使用可识别的架构风格或模式创建;
    • 由能够展现出良好设计特征的组件构成;
    • 能够以演化的方式实现,从而便于实施与测试;
  2. 设计应该模块化,也就是说,应将软件逻辑地划分为元素或子系统;
  3. 设计应该包含数据、架构、接口和组件的清晰表示;
  4. 设计应导出数据结构,这些数据结构适用于要实现的类,并从可识别的数据模式提取;
  5. 设计应导出显示独立功能特征的组件;
  6. 设计应导出接口,这些接口降低了组件之间以及组件与外部环境之间连接的复杂性;
  7. 设计的导出应采用可重复的方法进行,这些方法由软件需求分析过程中获取的信息而产生;
  8. 应使用能够有效传达其意义的表示法来表达设计。

仅靠偶然性无法实现这些设计原则。而是通过应用基本的设计原则、系统化的方法学和严格的评审来得到保证。

4.4 建模-设计建模原则 #

4.4.1 建模-设计建模原则-基本原则 #

原则1:设计应可追溯到需求模型。需求模型描述了问题的信息域、用户可见的功能、系统行为以及将业务对象与为其提供服务的方法打包在一起的一组需求类。设计模型将这些信息转换为架构、一组实现主要功能的子系统以及一组实现需求类的组件。设计模型的元素应可追溯到需求模型。

原则2:始终考虑要构建系统的架构。软件架构是要构建的系统的框架,它会影响接口、数据结构、程序控制流和行为、进行测试的方式、所得系统的可维护性等等。由于所有这些原因,设计应从架构开始考虑。只有当建立架构之后,才应考虑组件级问题。

原则3:数据设计与处理功能设计同等重要。数据设计是架构设计的基本要素。在设计中实现数据对象的方式不能听天由命。结构良好的数据设计有助于简化程序流程,使软件组件的设计和实现更加容易,并使整体处理更加高效。

原则4:接口(内部和外部)的设计必须谨慎。数据在系统组件之间流动的方式与处理效率、错误传播和设计简单性有很大关系。精心设计的接口使集成更容易,并可帮助测试人员验证组件功能。

原则5:用户界面设计应适应最终用户的需求。但是,在每种情况下,都应强调易用性。用户界面时软件的可见表现,无论其内部功能多么复杂,无论其数据结构如何全面,无论其架构设计得多么好,不好的界面设计通常会导致人们认为该软件“不好”。

原则6:组件级设计应在功能上独立。功能独立性时对软件组件“专一性”的度量。组件提供的功能应该具有内聚的,也就是说,它应该专注于一个唯一的功能。

原则7:组件应彼此送耦合,并应与外部环境松耦合。通过组件接口,消息传递和全局数据等多种方式实现耦合。随着耦合程度的提高,错误传播的可能性也随之增加,并且软件的整体可维护性下降。因此,组件耦合应保持在合理的范围内。

原则8:设计表示(模型)应易于理解。设计的目的是与要生成代码的从业人员、要测试软件的人员以及将来可能维护软件的其他人员交流信息。如何设计难以理解,它将不能用作有效的沟通媒介。

原则9:设计应迭代式开发。在每次迭代中,设计人员都应争取更加简单。像几乎所有创意活动一样,设计是反复进行的。最初的迭代可精化设计并纠正错误,但后续的迭代应努力使设计尽可能简单。

原则10:设计模型的创建并不排除采用敏捷方法的可能性。一些敏捷软件开发的支持者坚持认为,代码是唯一需要的设计文档。然而,设计模型的目的是帮助必须维护和演化系统的其他人。在现代多线程运行时环境中,很难理解代码片段的更高层次用途或其与其他模块的交互方式。 敏捷设计文档应与设计和开发保持同步,以便在项目结束时,可使用设计文档理解和维护代码。设计模型之所以具有优势,是因为它是在抽象级别创建的,该抽象级别去除了不必要的技术细节,并且与应用程序的概念和需求紧密相关。补充的设计信息可以包含设计的基本原理,包括对被否定的架构备选设计方案的描述。

4.4.2 建模-设计建模原则-架构可靠性的设计原则 #

原则1:使系统自愈。系统故障需要提前预测,在故障发生时,系统应该能自动响应并进行恢复,这就是所谓的系统自愈自愈是指系统从故障中自动恢复的能力。具备自愈能力的系统能主动检测故障,并从容应对,使其对客户影响最小。故障可能发生在系统的任一层,其中包括硬件故障、网络故障或软件故障。通常情况下,数据中心不会每天发生故障,而对于数据库连接、网络连接等会频繁发生故障的,就需要执行更精细的监控。系统需要持续监控故障并在需要的时候及时采取行动进行恢复。

要对故障进行响应:

  1. 首先需要确定应用程序和业务的关键绩效指标(Key Performance Indicator,KPI)。在用户层面,这些KPI可能包括每秒的请求数或网站的页面加载延迟。在基础设施层面,可以定义CPU利用率低阈值(比如不超过60%)。
  2. 定义KPI后,应将监控系统部署到位,以跟踪故障并在KPI达到阈值时进行通知。应该基于监控来实施自动化,以便系统在发生故障时能够自我修复。例如当CPU利用率达到50%时,自动增加服务器。主动监控有助于防止故障发生。

原则2:实现自动化。重复相同的步骤用手动配置每个环境容易出错,人为错误(比如数据库名称中出现的拼写错误)总是不可避免的。自动化是提高应用程序可靠性的关键。应尝试将应用程序部署和配置乃至整体基础设施的一切都自动化。自动化提供了敏捷性,让团队可以快速行动并频繁地进行实验。

  • 你可以通过一键复制整个系统基础设施和环境来测试新功能。
  • 你可以根据日程来规划应用程序的自动伸缩功能。
  • 你可以根据用户的请求量来进行自动伸缩。

原则3:创建分布式系统。在系统正常运行时,单体应用程序的可靠性很低,因为某个模块中的一个小问题都可能会导致整个系统瘫痪。将应用程序划分为多个小服务,可以减小影响范围,这样源自应用程序某一部分的问题就不会影响整个系统,应用程序还可以继续提供其他的关键功能。在服务层面,可以通过对应用程序进行水平伸缩来提高系统的可用性。应将系统设计为可以使用多个较小的组件协同工作,而不是设计为单体系统,这样可以减小故障出现时受影响的范围,在分布式设计中,请求由系统的不同组件处理,一个组件的故障不会影响系统其他部分的功能。

原则4:容量监控。资源饱和时应用程序故障的最常见原因。例如,应用程序由于CPU、内存或硬盘过载而开始拒绝请求。

在传统本地环境中,需要根据事先的假设来计算服务器的容量,而在线流量常常难以预测。通常情况下,硬件采购可能需要3到6个月的时间,提前预测容量是很困难的。资源不足将导致业务因应用程序不可靠而造成损失,而订购多余的硬件将产生额外的成本,因为资源会被闲置。

在云场景下,云环境是一个无须预测容量的环境,这样应用程序就可以按需伸缩。需要考虑提升应用程序的伸缩性,以及选择合适的云服务。

原则5:验证恢复过程。在大多数情况下,在验证基础设施的可用性时,组织会专注于验证一切正常的可行路径。相反,我们应该验证系统是如何发生故障的以及恢复过程工作得如何。应在一切都可能失败的假设上验证应用程序,不要仅仅期望恢复和故障转移策略一定会起作用,请确保定期进行测试,以免出现问题。

基于模拟的验证可帮助发现潜在风险。可以将可能导致系统故障的场景自动化,并准备好相应的事件响应方案。验证应确保生产环境不会发生故障的方式来提高应用程序的可靠性。

可恢复性作为可用性的一个组成部分有时会被忽视。为了提高系统的恢复点目标(RPO)和恢复时间目标(RTO),应该将数据和应用程序及其配置作为机器镜像进行备份。这样如果因为自然灾害导致一个或多个组件不可用或破坏了主要数据源,应该能够快速恢复服务而不会丢失数据。

4.5 建模-组件级设计-基本设计原则 #

开闭原则(Open-Closed Principle, OCP):模块(组件)应该对外延具有开放性,对修改具有封闭性。简单的说,设计者应该采用一种无须对组件自身内部(代码或者内部逻辑) 做修改就可以(在组件所确定的功能域内) 进行扩展的方式来说明组件。为了达到这个目的,设计者需要进行抽象,在那些可能需要扩展的功能和设计类本身之间起到缓冲区的作用。

Liskov 替换原则(Liskov Substitution Principle, LSP):子类可以替换它们的基类。最早提出该设计原则的Barbara Liskov建议,将从基类导出的类传递给组件时,使用基类的组件应该能够正确完成其功能。LSP原则要求源自基类的任何子类必须遵守基类与使用该基类的组件之间的隐含约定。在这里,“约定”既是前置条件(组件使用基类前必须为真),又是后置条件(组件使用基类后必须为真)。当设计者创建了导出来,这些子类必须遵守前置条件和后置条件。

依赖倒置原则(Dependency Inversion Principle, DIP):依赖抽象,而非具体实现。抽象可以比较容易地对设计进行扩展,又不会导致大量的混乱。组件依赖的其他具体组件(不是依赖像接口这样的抽象类) 越多,扩展起来就越困难。代码是具体的,如果设计者跳过设计直接变吗,那就违反了依赖倒置原则。

接口分离原则(Interface Segregation Principle, ISP):多个客户专用接口比一个通用接口要好。多个客户组件使用一个服务器类提供的操作的实例有很多。ISP原则建议设计者应该为每个主要的客户类型都设计一个专用的接口。只有那些与特定客户类型相关的操作才应该出现在该客户的接口说明中。如果多个客户要求相同的操作,则这些操作应该在每个专用的接口中都加以说明。

发布/复用等价性原则(Reuse/Rlease Equivalency Principle, REP):复用的粒度就是发布的粒度。当类或组件被设计为可复用时,在可复用实体的开发者和使用者之间就建立了一种隐含的约定关系。开发者承诺建立一个发布管理系统,用来支持和维护实体的各种老版本,同时用户逐渐地将其升级到最新版本。明智的方法是将可复用的类分组打包成能够管理和控制的包,并作为一个更新的版本,而不是对每个类分别进行升级。设计可复用的组件不仅需要好的技术设计,也需要高效的配置控制机制。

共同封装原则(Common Closure Principle, CCP):一同变更的类应该合在一起。类应该根据其内聚性进行打包。也就是说,当类被打包成设计的一部份时,它们应该处理相同的功能或者行为域。当某个域的一些特征必须变更时,只有相应包中的类才有可能需要修改。这样可以进行更加有效的变更控制和发布管理。

共同复用原则(Common Reuse Principle, CRP):不能一起复用的类不能被分到一组。当包中的一个或者多个类变更时,包的发布版本号也会发生变更。所有那些依赖已经发生变更的包的类或者包,都必须升级到最新版本,并且都需要进行测试以保证新发布的版本能够无故障运转。如果类没有根据内聚性进行分组,那么这个包中与其他类无关联的类有可能会发生变更,而者往往会导致进行不必要的集成和测试。因此,只有那些一起被复用的类才应该包含在一个包中。

4.6 建模-用户体验设计-可用性准则 #

4.7 建模-用户体验设计-可访问性准则 #

5 质量与安全 #

5.1 质量与安全-正式技术评审-评审指导原则 #

5.2 质量与安全-移动测试准则 #

6 软件项目管理 #

6.1 软件项目管理原则-Boehm W5HH原则 #

Barry Boehm[Beo96]在其关于软件过程和项目的优秀论文中指出:“你需要一个组织原则,对它进行缩减来为简单的项目提供简单的(项目)计划。”

Boehm 给出了一种方法,该方法描述项目的目标、里程碑、进度、责任、管理和技术方法以及需要的资源。这种方法通过提出一系列问题,来导出对关键项目特性以及项目计划的定义:

  1. 为什么(Why)要开发这个系统? 所有利益相关者都应该了解软件工作的商业理由是否有效。该系统的商业目的值得花费这些人力、时间和金钱吗?
  2. 将要做什么(What)? 定义项目所需的任务集。
  3. 什么时候(When)做? 团队制定项目进度,标识出何时开展项目任务以及何时到达里程碑。
  4. 某功能由谁(Who)负责? 规定软件团队每个成员的角色和责任。
  5. 他们的机构组织位于何处(Where)? 并非所有角色和责任均属于软件团队,客户、用户和其他利益相关者也有责任。
  6. 如何(How)完成技术工作和管理工作? 一旦确定了产品范围,就必须定义项目的管理策略和技术策略。
  7. 每种资源需要多少(How much)? 对这个问题,需要在对前面问题回答的基础上通过估算而得到。(TODO)

6.2 软件项目管理-项目进度安排-基本原则 #

参考资料 #

  1. 《软件工程 - 实践者的研究方法》
  2. 《解决方案架构师修炼手册》
  3. Seven Principles Of Software Development
© 2024 lyremelody.cn All Rights Reserved
访问量: