【IT168 技术文章】
一致性原则是软件开发中重要原则,也是最令人困惑的原则。做到完全的一致性将会导致高昂的成本,而不一致又会导致项目出现各种各样的问题。可以想到,这又是一个需要权衡的问题。
意图
软件过程中的大部分工件都需要保持其相互之间的一致性。可是,工件越多,保持一致性的开销也就越大。我们需要在一致性和成本之间保持平衡。
示例
天利软件公司的项目经理正在为开发过程中出现的大量的不一致问题头疼不已。从软件过程一开始的需求阶段,版本问题就一直困扰着项目组的成员。项目组的成员并非没有经验,只是这一次的项目规模超过了以往的项目,因此公司决定采用严格的过程,以保证软件质量。开发过程中每个活动都必须产出文档。第一次的不一致发生在需求调研中期的一次研讨会上,当时的会议上发现了三种不同版本的需求规格书。之后情况越来越糟,文档的数量不断增多,由于所有的开发人员都需要编写文档、使用文档,因此发生了各种原先没有料到的事情,包括新文档被覆盖;设计人员手中的需求规格书不是最新的版本;设计书变更之后,代码却没有相应的变更;代码中的方法说明和设计模型中的方法说明不一致。为了对文档进行控制,公司不得不从项目组中抽调了两名开发人员来控制文档,并加强了文档的管理。但是随之而来的是开发周期的延长。
上下文
一致性包括两个方面:一是不同工件之间的一致性,二是使用同样的方法来处理问题。
软件过程的每个阶段都需要产出不同的工件。典型的例如需求分析阶段将产出需求规格书、用例模型、非功能需求等,设计阶段产出设计模型、类图。而在软件开发中,我们常常会遇到需求的变更的情况,因此我们需要依次对我们的需求模型、分析模型、设计模型、代码进行修改,以保持它们之间的一致性。如果项目处于前期阶段,这种修改量并不大,因为工件较少,如果项目进行到后期。需求的改变将会涉及到大量的工件。对于期限比较紧张的项目而言(这似乎是所有项目共同的特征),这种成本几乎是无法接受的。
另一方面,在同一个软件组织中,解决同样的问题有着不同的办法。不同的人有着不同的编码习惯、不同的目录组织方式、不同的类库、不同的框架。虽然我们并不反对同一问题的不同解法,但是当这种情况对项目的进展起到负面的效果的时候,我们就有必要来思考这个问题了。
问题
我们如何在一致性和一致性带来的成本之间做好平衡?我们如何保证不同工件之间的一致性,又该如何保证项目中解决方法的一致性。
方法
首先,我们应该肯定,一致性这个问题并没有标准的答案。不同的项目对一致性有着不同的要求。对于要求严格、规模庞大、涉及到一些重要领域的项目来说,一致性的要求是极为严格的。而对于一个小型的、较为无关紧要的项目来说,一致性的要求就低了许多。因此,和前面介绍的模式类似的,一致性的正确的答案只能够从读者自己的项目中去寻找。而这里提供的,是寻找该项标准的建议。
我们先从后者-解决方法一致性入手。
和这个标准答案相关的是一致性的程度。一致性的标准可大可小。大的标准包括了设计原则、软件架构;小的标准包括用例的书写格式、设计模型的注释格式等等。由于大的标准对开发过程的作用最大,因此在进行一致性处理的时候,应该先从大的标准入手。在 代码是最终目的模式中,我们提到了构架的概念。以构架为中心来制定标准,保证一致性是一种自然的思路。核心构架应该要能够确定软件开发的方向,这样可以最有效的保证一致性,但是不要涉及到过于具体的细节,除非你对它们已经有足够的经验。而标准的深入程度则要取决于组织经验、已有的软件过程、项目规模等因素。
对于项目来说,一致性存在一个逐步精化的过程。正如我们上面所讲的,一开始最重要的是引入核心原则,这里我们认为是可重用框架。对于可重用框架中已经明确确立的事项,必需要严格的遵守,因为框架中的内容是足够稳定的,已经经过验证的。这其实是重用思想的一种衍生。我们在 短期利益和长期利益的权衡模式中还会谈到。如果有必要,引入的核心原则需要针对项目进行部分调整。例如,框架中根据项目的规模大小定义了用例的三种写作模板,在项目开始的时候,我们会选择其中最接近的一种模板,并根据项目增删模板中的一些元素。而对于框架中不曾定义的,一些尚未明晰的事件,我们在项目初期不需要进行过于详细的处理,而是随着项目的进展来慢慢的完善它。例如,由于需求没有确定,我们并不知道最后的设计模型是什么样的,因此我们先根据框架定义,选择JSP和Servelet作为主要的技术基础,由JSP负责提供视图,由Servelet提供业务逻辑。其它的信息则等待进一步的精化。
在项目一开始的时候,不要过分注意工件的修饰。例如,我们选择的用例模板要求每一个用例都需要包括6种元素,包括主要角色、级别、前置条件、主要流程、备选流程、优先级。并规定引用其它用例的地方需要做出链接。但是在一开始需求调研的时候就按照这种标准完善用例将会提高成本,因为需求尚未稳定,我们主要的工作是尽可能完整的收集用例,而不是把时间花费在修饰用例上。因此一开始任何形式的用例描述都是允许的。你可以使用几句话来描述用例(事实上,利用自然语言来描述用例有着图形无法比拟的优势),并用着重号标记出其中可能和其它用例相关联的词汇。这样就已经足够了,修饰的工作可以等到下一步来做。其它的工件也是一样的。这时候的一致性的要求并不十分严格。
我们需要注意一些关键点(检查点),例如在 知识接力模式中提到的需求复审。在项目接近这个关键点的时候,用例应该是已经根据要求整理完毕,可以接受复审了。粗糙的工件能够迅速的创建,但是不容易让人理解,也违背了一致性。因此我们虽然可以延迟工件的精化,但是这个工作是不可以缺少的,当然,精化的程度是可以变化的。利用关键点来保证工件的一致性也保证了信息传递的一致性(参见 知识接力模式)。
因此,我们应该根据项目进行的不同时期来保证工件的一致性。例如,在实现阶段的初期,设计尚未定性,花费大量的时间在设计文档上就没有什么必要,只需要准备一份草稿,在设计的时候随手记录下设计思路就可以了。注意,不要不做任何记录,人的记忆是最不可靠的,过目不忘只出现在小说中。而当项目逐渐成数的时候,我们就可以为其中成熟的设计进行文档化的工作了。这些可以根据需要发布给用户,也可以整合到构架中。
再来看第一个问题,如何保证不同工件之间的一致性。
同时维护3个工件的工作量,要比同时维护10个工件的工作量小的多。因此,我们应该尽可能的减少在每个关键点中需要保证一致性的工件数量。同时,我们需要区分,哪一些工件属于必须保持一致性的工件,哪些属于不需要随时更新的工件。例如,我们在需求分析中使用了CRC卡片,但是它的主要目的是为了顺利的进行沟通,并得到基本的分析类。这样的工件,保持它的一致性并没有太大的意义。这种思路类似于CMM中的KPI的思路,从软件开发过程中找出关键的工件,把有限的力量投入到保持这些关键工件的一致性上。虽然一致性狂热者未必认同这种做法,但是对于一个软件组织来说,最重要的是在有限的资源下的尽量提高软件开发能力。
在构架中包括一系列指南,来指导工件一致性是有必要的。较好的做法是结合 知识接力模式中的知识传递指南。例如,指导如何从需求说明书中提炼出接受测试和设计模型,这个过程本身就是保持一致性的过程。同时,光有书面的指导也是不够的,项目进行中,架构师之类的角色需要对开发人员进行指导(一对一的形式是非常好的的)。这一点我们下文还会提到。
为一致性使用工具有时候也是很有必要的。例如,我们在 知识接力模式中就提到使用建模工具来保持设计模型和代码中信息的一致性。这里我们再次强调这一点,因为根据我们的经验,这种做法对于软件项目是非常重要的。因为设计是很难能够一开始就达到目标的,它需要不断的改进,而项目的最终期限决定我们不可能等到设计已经尽善尽美之后才开始编码。因此设计过程和编码过程一定是反复进行的。重构不断的发生 。我们需要不断的修改设计模型,而此时的设计模型和代码的信息的不一致就成为主要的矛盾了。一方面它延长了重构的时间,另一方面,它使得开发人员无法把精力集中在设计上。如果能够有工具来自动的完成这项工作,将会极大的提高生产率。
另一种工具是版本控制系统,是否使用版本控制系统和项目大小无关。再小的项目同样存在着版本问题,只是严重程度不同而已。因此将工件纳入到版本控制系统中来是较好的做法。对于小型的项目而言,并没有必要限制权限,但对于大一些的项目权限控制就有必要了。
一致性的组织保证
首先,如果不进行任何的管理,项目一定是不一致的。这和人的基因有关系。因此我们需要为一致性制定专门的人选。例如,需求的一致性应该由架构师和领域专家负责,模型的一致性由开发人员负责。但是最终需要一位总的负责人,他负责所有工件的一致性,包括技术和领域两方面,并决定是否将项目中的经验并入构架中。
不要让一致性为难你的开发人员。一致性是很容易滥用的,因此请确保正确的使用了它们。例如,制定缩进或留白之类的一致性标准并没有太大的意义,因为它的成本远远超出了它的意义,并且会遭到开发人员的严重反对。另外的例子包括过于严苛的编码规则、以及模式的滥用。这些都是经常会犯的错误。它并不会带来生产率的上升,只会另开发人员反感。避免它们的办法是,在制定一致性规则之前,先思考,该条规则的成本是否大过它的利益,开发人员是否愿意接受。
一旦定义了一致性规则之后,就需要进行培训。最好是通过例子来开始。例如,教授新的模式。如果你进行类似的培训,你就会发现,把正式的培训和非正式的指导相结合是很好的做法。因此,我们往往习惯把培训课程分为理论培训和实践两个部分。实践可以安排在课程结束就开始,也可以安排在项目进行中。
最后,一致性规则需要在整个开发过程中被不择不扣的执行。这是必须的。此外,还需要评估一致性规则的实际运作情况,如果有不充分或是不完善的情况,则需要改进它。如果效果不佳,则要考虑废除该规则。
小结
使用构架来保证大原则的一致性。
在事情未确定之前,工件可以是不一致的,但是在关键点时,工件必须是一致的。
尽量减少需要保持一致性的工件数量。
使用工具来维护一致性。
指定一致性的负责人。
一致性规则必须合理。
严格执行一致性,并根据实际情况改进一致性规则。
i. 关于重构是否需要彻底执行,目前也由不同的看法。XP主张重构应该要彻底、无情,直到没有重复代码为止。这和它缺少前期设计有关系。而敏捷方法的另一些方法论则认为,少量的代码重复并没有什么值得大惊小怪的。我们的观点是,对于需要进入构架的设计(例如商业组件),需要进行严格的重构、审查和测试。而对于一些外围的代码(例如脚本代码)则比较无所谓。