软件实现

来源:互联网 发布:base64解码 java 乱码 编辑:程序博客网 时间:2024/05/01 20:39

在完成软件架构设计和制定了开发计划之后,我们进入软件实现阶段。这个阶段的主要任务是用程序语言把我们设计的软件功能和需求变为可运行的程序。在整个软件开发阶段,软件实现占据了最长的时间,对软件最终质量和进度来说是最重要的阶段。大部分软件开发过程的问题,对软件成败至关重要的因素都发生在这个时期。可以说,如果较好地组织了这个阶段的软件实现任务,大多数软件开发的困难都迎刃而解。

在这一章,我们将分别讨论迭代式软件开发,团队成员及时沟通,用户介入,代码审查和重构,单元测试,自动化,以及早期部署。

 

迭代式软件开发

迭代式软件开发把整个开发周期分割成多个短周期的迭代过程。它的特点是:每次迭代都包含从需求分析,设计,实现,到测试的完整的开发过程;每次迭代末期输出产品级质量的实现部分功能的软件;每次迭代在前一次迭代的基础上,增加新的功能;每次迭代周期都比较短,一周、两周,最长一个月;每次迭代末期是一个正式的开发进度和质量的检查点,为项目计划的调整提供最真实、最准确的反馈信息。

以迭代的形式组织软件开发,解决了大瀑布模型缺乏有效的反馈机制的问题。在大瀑布模型中,一般只有在一个阶段结束安排正式的评审,但是一旦评审没有通过或者发现了前一阶段的问题,大瀑布模型没有行之有效的回溯机制,或者说回溯很难困,对软件质量和真实进度也缺乏足够的信息进行有效监控。

短周期多次迭代正好弥补了大瀑布模型的不足。给软件过程控制增加了更多更有效更自然的检查和调整点。即便由于各种原因软件项目没法在预计时间内完成,如果采用迭代开发,可以让项目组在更早期得到这个信号,避免所谓的“最后一分钟惊讶”;而大瀑布模型往往到了系统测试阶段才发现整个软件质量和进度离预期相差甚远,“最后一分钟惊讶”和“无穷无尽的项目延期”基本成为必然。

每次迭代,软件质量是最重要的检查指标。在一次迭代周期需要实现的功能可以根据实际进度添减,甚至实际的迭代周期长短都可以做些微调。但是每次迭代结束,软件质量一定不能妥协。

因为质量不能妥协,所以每次迭代都必须要有严格的测试。除了程序员的单元测试,如果有条件,最好进行独立的集成和系统测试。如果没有专门测试人员资源分配在迭代期,开发组也需要自己执行比较完整的集成和系统测试。

 

团队成员即时沟通

所以项目成员的即时沟通是敏捷开发模式的基础,没有即时交流,整个项目的运行效率和效果将大打折扣。为了保证项目成员的沟通交流即时有效,必须倡导使用灵活、轻量级的沟通原则和方法。

坐在一起

项目成员最好坐在一起工作。也就是说项目成员工作的物理距离越近越好。项目成员跨地域分布,不但增加通讯成本,也极大降低沟通的效率。因此,除非万不得已,一个项目的成员应该在一个办公室里工作,而且最好把团队成员的座位安排在最相邻的一个区域里。近的物理距离可以是项目成员有更多可选的交流方式,为最有效的沟通创造条件。

如果项目成员由于不可避免的原因,必须远程合作,一定要在不同地域分布的项目成员和项目组之间事先做合理的模块划分和定义清晰的模块接口或者通讯协议。否则跨地域、跨时区的合作所带来的额外沟通成本会大大抵消其原本应该具有的好处。

倡导口头交流

传统的重量级软件开发方式十分强调项目文档写作,一个项目运行下来,大大小小的项目文档花费了项目成员大量的时间和精力。表面来看,似乎这是花费是必须的。其实不然。我们只要粗略的回顾一下很多认为理所当然必须写的文档,就会发现,许许多多的文档从其一诞生就被宣告死亡,因为许多文档根本就没有人会去阅读。花在这些没人阅读的文档上的任何时间和精力都是不明智的。我们应该更明智地花费我们的宝贵时间和精力。所以,对待项目文档,应该用辩证的态度。只写那些有一定“观众量”的文档,其余的沟通交流应尽量采用非正式的、灵活而轻量级的方式。比如简单的邮件,特别是成员之间的口头交流。

清晨站会

曾经有位项目经理跟我交流他的一个困惑,说有一次他去跟踪项目开发进度,询问程序员甲最近在做什么开发,甲告诉这位项目经理,他正在等待程序员乙完成某个模块。项目经理于是就去寻问乙的进度,乙却告诉他那个模块已经完成两天了。这位项目经理就很困惑,大家都坐在一个办公区,每周有项目小组例会,平时有电话,即时通讯,邮件等等可以交流进度,甚至走过去口头交流也不过几米远的距离,为什么开发人员之间的信息还如此不同步呢?我们都知道,软件研发最大的成本就是开发人员人力成本,由信息不同步造成的时间浪费是必须避免的。前面举的例子,虽然说明这个开发团队的团队建设还有改进余地,因为好的团队,所有成员应该是能自觉主动相互通过。不过从团队管理的角度看,应该有更有效的团队行为来改善成员之间的沟通。每日清晨站会就是一种低成本却十分有效的加强沟通的方式。早上,在大家正式上班前,开发小组所有成员站列在一间会议室,每人分配一分钟左右时间,快速总结昨天做了什么,今天打算做什么,有什么需要协商解决的问题。站会组织者,根据每个人的反馈,对大家反映的问题迅速判断涉及哪些人员,再根据情况组织专门的、小范围的讨论会来解决问题。每日站会不但可以为团队建立一条高效的沟通渠道,还对保持团队士气有很好的作用。让每个人在所有团队成员面前宣布自己昨天的成就和今天安排,无形中持续激励了每位成员的工作热情。

 

用户介入

在传统软件开发模式中,用户介入发生在项目初期和末期。用户在项目初期提出功能需求,在项目末期确认和接受软件。那在软件开发过程中,用户为什么还要介入呢?长期的软件开发实践告诉我们,以自然语言描述的用户需求往往是不准确、不完整的。毕竟自然语言是不严格的,在把自然语言表达的需求“翻译”为程序语言的过程中,经常因为用户和程序员之间思维习惯不同,知识背景不同,造成不同的需求理解。另外用户往往在看到真正可运行的软件之后,才能意识到他真正需要什么样的软件。即便我们有经过用户签字画押的需求文档来跟用户理论,但无论如何,让用户满意才是对软件最终最实在的评价,因为我们开发软件的最大目的是满足用户的需求,让软件对用户有用。而更早更长的让用户介入到整个软件开发过程中,让用户有更多的机会亲自获得对软件的第一手使用感觉,才能更有效地保证我们所做的就是用户所需要的。

在敏捷开发模式中,用户介入有多种形式。

在安排各个功能模块开发顺序时,让用户列出他对所有功能需求在重要性和紧急性上的优先次序。软件开发组先做最重要最紧急的功能。

在每次迭代末期,发布具有部分功能的预览版给用户,让用户获得真实的用户使用体验,并鼓励用户给出即时的反馈。

这样,由于用户的介入,开发项目组的功能模块开发顺序更合理,又能持续地获得用户真实反馈,最大限度保证软件开发不会偏离用户的真实需求。而且更早的用户需求变更让开发组有时间,或者代价相对较小的去“拥抱变化”。

当然,有人会有顾虑,担心用户在整个开发过程中不断提出需求变更怎么办。这就涉及到用户管理。可以使用下面方法来规范和约束用户随意和不合理的需求变更请求。

当得到一些比较明显欠考虑的需求变更请求,首先鼓励用户三思而行,然后开发组对这样的需求变更进行一定时间段的积累。不要太盲目地、太快地去改动代码。因为说不定第二天用户的想法又有变化。

对于影响较大的需求变更,如果超出了原始开发计划所能承受的范围,需要有更正式的需求变更处理机制。比如召集软件项目组需求控制委员会(CCB)来对变更的意义、对项目日程和开发成本的影响进行充分的讨论,然后作出合理安排。或者把变更推迟到下一个版本,或者合理地延长开发周期或增加开发资源投入。

 

共享代码所有权,代码审查,重构

代码是一个软件怎么强调都不过分的部分。软件最终质量由其代码所决定。这一节所讨论的三个话题,代码审查、重构、共享代码所有权,就是以代码为中心的软件开发行为和理念。

编程实质上是人们精确地为计算机编写逻辑处理步骤供机器使用。程序必须逻辑严密,考虑周全。而作为万物之灵的人类,其实更擅长模糊思维,在需要精确思维领域出错率远远高于机器。所以,编程本身就是极其容易犯错的活动。再加上人类习惯的先入为主的思维,对自己所犯错误又经常是熟视无睹。再者,一个人的知识能力是有限的,一个团队里面开发者现实能力也是有高有低。作为团队行为的软件开发的质量不应该被“木桶”理论所决定,即软件质量不应该取决于一个团队所有成员的最低水平。成熟的团队生产出来的软件应该能达到或者接近这个团队开发者的最高水平。倡导共享的代码所有权,进行有效的代码审查,坚持对代码实行无情的重构,可以很大程度上解决以上所列问题,极大地提高软件质量。

共享代码所有权是一种理念上的变化。它倡导代码不是由某个程序员排他性的开发和维护者。一个模块可以由一个程序员原创,但是其后续的代码审查,重构和维护的权力属于整个团队。任何成员,只要他适合,就可以依照一定的流程改动代码。有人会说,排他性的代码维护方式让团队成员的职责分工更明确,似乎更能激发团队成员的责任性和干劲。虽然我们承认排他性的代码所有权或许在某些时候可以减少团队成员之间扯皮等现象,但是它的不利面是可能限制了整个团队对每个模块的创造力。因为如果一个模块为某个程序员独有,其他程序员可能比较忌讳对该模块提改进建议,负责该模块的程序员也很可能碍于面子本能抵触别人好的修改建议,久而久之,团队级别的活动,如代码审查可能逐渐流于形式,而整个系统的各个模块的质量就直接地受制于其负责者的水平,心情和态度。这种状态下的团队开发质量很难稳定一致。而共享代码所有权,因为其切断了模块和程序员之间固定的开发关系,可以更有效地鼓励所有程序员对所有模块做出贡献。当某个模块需要改动时,不一定是其原创者操刀,可能其他程序员更适合,改动效率更高。总之,在团队建设比较成熟的团队,共享代码所有权,可以更大范围内激发成员的创造力,保证团队开发作为一个整体更稳定。

代码审查是其他成员审查一个成员的代码,使得程序代码和单元测试用例至少经过两个人脑思考。代码审查的好处不言而喻。这里主要讨论如何让代码审查更有效。我们知道,一个人要更真正审查一个模块代码的逻辑流程,他必须具备相当程度的对该模块的知识,否则代码审查可能只能肤浅的检查一下类名,方法名和参数名,审查者只能提些无关痛痒的建议。但要让审查者具备相当的知识,审查者必然要花相当的时间和精力。在每个人都有自己的开发任务的情况下,如何保证审查者既完成了自己的任务,又具备了条件看别人的代码呢?固定的代码审查关系可以解决这个问题。所谓固定的代码审查关系,是在项目开发之初,根据模块的分工、模块之间的逻辑联系、以及团队成员的知识爱好,确定相对固定的合作关系。一个模块由一个程序员主创,另外至少一个程序员辅助这个模块的实现。辅助者在整个开发过程中,定期审查该模块的实现,最后他对该模块的熟悉程度必须达到主创者的水平。这样的安排可以避免由于知识不足造成不能发挥代码审查真正效果的现象,同时,由于一个模块至少被两个人熟悉,任何团队成员的变动也不会对正在开发的项目造成根本上的冲击。

重构是在不改变软件可察觉功能的前提下,优化代码结构使之更合理、可读性更好、更易扩展。重构是提高软件质量很重要的活动。因为,我们很难在第一次就写出堪称完美的代码。对问题的认识需要一个过程。往往是在实现一个功能之后,我们会得出更好的解决方案。所以现代软件研发理念特别提倡在实现一个功能之后进行重构,在修改bug之前重构,在增加新功能之前重构,也就是所谓的“无情(Relentless)”的重构。看似概念简单的重构,其实给我们带来了对于软件研发全新的思维:重构让人们重归以代码为中心的软件研发活动;重构让人们明白我们写代码的第一目的是让别人和今后的自己能很容易地看懂代码逻辑;重构让人们认识真正好的代码、真正有生命力的设计,不一定是一蹴而就的,多数情况是经过多次修改才得到的。

 

单元测试

单元测试经常被人们所忽视。由于有“测试”一词在里面,似乎这项活动天然不是程序员的事,程序员假定都是雇来写程序,而不是写“测试”。其实单元测试是写程序代码天然的有机的组成部分。单元测试的主要目的是程序员写单元测试用例检查自己写的程序代码逻辑。编译器只能为我们检查编程语言语法上的错误,而程序代码逻辑是否符合我们所期望,只有通过单元测试才能有效验证。无数的软件研发实践告诉我们,单元测试不充分,甚至根本没有单元测试的开发过程,其结果必然导致系统集成非常困难。因为如果许多或者全部模块连一次运行都没试过就集成到一起,一定问题无穷,甚至于连最基本的运行逻辑都无法通过。其实系统集成是否顺利是软件研发过程质量很直接的衡量指标。对于没有或者单元测试不充分的软件开发,系统集成其实就是被推迟的单元测试,其痛苦和低效就不言而喻了。

极限编程(XP)倡导测试驱动开发(TDD),其基本思路是既然单元测试是如此重要,就把它发挥到极致,在写任何程序逻辑前,先写单元测试。在TDD下,开发节奏是写单元测试用列->写程序代码使单元测试用例运行通过->重构优化代码。或许不同的人对这种开发方式有不同的看法。在实践中,我们也发现不一定非得把开发方式规定得这么机械,可以更灵活地根据实际情况和开发者的思维习惯来确定是先写还是后写单元测试。最根本的原则是程序员提交产品的程序代码到代码版本控制库时,一定要同时提交相当数量且全部运行通过的单元测试代码。

下面列举一些单元测试用列编写指南。

所有公共方法和函数必须被测试;

单元测试必须覆盖一般情况,边界情况和异常情况;

单元测试代码覆盖率至少70%

当发现新bug,添加相应单元测试用例;

使用成熟的单元测试框架,如JUnitCppUnitNUnit

 

自动化

自动化是指使用相应工具软件由计算机自动地完成一些日常性的、重复性的开发任务和活动。例如,自动编译,自动运行单元测试甚至部分系统测试回归测试,自动打包安装程序,自动代码检查,编译测试结果自动通知。自动化不仅节省了人力成本,更重要的是,它最大程度上避免由于人的粗心大意或者心态的波动所带来的不可预知的结果。简单地说,人通常是不可靠的,而机器和工具,一旦配置好,除非发生大灾难,其运行结果是稳定可靠的。

对于软件开发,建议自动化编译服务器是必须的。有许多成熟的自动化编译工具可以使用,例如CruiseControl系列。自动化编译服务器自动从代码控制库中取出最新或者特定分支、特定版本的源代码进行编译,如果编译成功,自动运行单元测试,统计单元测试代码覆盖率,最后自动打包生产安装程序。

其中自动运行单元测试无比重要。前面我们讨论过单元测试的作用,单元测试不仅是在第一时间验证程序逻辑正确与否,随着代码不断演化,单元测试必须不断被运行来检测是否新代码的加入或者原来代码的改动带来了意想不到的后果。但是如果没有低代价高效率的运行方式,久而久之,人们可能会忘记运行,或者嫌麻烦不想运行。那样,单元测试的功效就会大打折扣。因此我们说单元测试加自动化运行是软件研发过程的一道有效而廉价的防火墙。

 

早期部署

早期部署是指尽量早地把软件或者阶段性发布的软件安装在真实或者尽量真实的运行环境中,实际检验软件的各种功能。而不要仅仅在软件开发后期才这样做。因为开发所用的环境和实际运行环境可能存在较大差异,在真正部署之前,我们开发基于的假定有可能存在偏差。这种偏差有可能造成软件的实现跟实际情况不符。如果在软件开发后期才发现偏差,势必带来不可能避免的返工,影响软件开发的日程。

其实把早期部署的思路延伸下去,对软件开发过程中可能碰到的一切任务和问题,都应该尽早去做、去面对。即便是刻安装程序光盘这样的事,如果有,也一定要早做。安装程序需要打包的文件,如帮助文件,即便是还在继续更新,也需要尽早的把它打包进安装程序。不要把问题留在最后,也不要想当然地把假定当作现实。整个软件开发过程始终是充满风险的,任何任务,只有经历了严格验证,我们才能认为它没有问题。

原创粉丝点击