统一软件工程流程框架(Unified Software Engineering Process Framework)

统一软件工程流程框架(Unified Software Engineering Process Framework)

“凡治众如治寡,分数是也;斗众如斗寡,形名是也;三军之众,可使毕受敌而无败者,奇正是也。”
——《孙子兵法-兵势第五》

如果我们把Coding能力很强,能够凭一己之力搞定一切的程序员比作小说中武功高强的剑客,那么一支由多人组成的工程团队就必定是一支军队。如果我们把只有一个人的队伍也看成是“团队”的特殊情况,那么对于这样的“团队”而言,要想完成不那么复杂的任务,他/她只要把自己的个人技艺训练到炉火纯青即可;但对于复杂到一定程度,要处理的事情千头万绪,需要各种专业技能才能完成的任务来说,就必须通过多人团队的方式来完成。人多就一定力量大吗?未必!就算是一支人数众多,由各种各样专业人才组成的队伍,如果没有接受过一定的工程化训练,就不可能做到实战中如臂使指,就会有败亡的危险。反过来,一支实战经验暂时不那么丰富,个人技能稍微要弱一些的队伍,如果接受了一定的工程化训练,能够在实战中通力合作,形成合力,那么他们每个人的力量就会得到加成,他们的经验就会在实践中得到锻炼和加强,失败的可能性就会降低。因此在这个世界上,只要有需要人和人之间相互协作来做成的事情,就一定存在着“系统工程”的思维方式。

软件工程是人类认识世界和改造世界的一个具体的领域,具有一定的客观规律,是“把系统的,有序的,可量化的方法应用到软件的开发,运营和维护上的过程”,它具有复杂性,不可见性,易变性,服从性和非连续性等固有属性。IT从业者有必要认识和把握这样一些规律和属性,以增进技艺,更好地进行软件工程实践活动。本文在邹欣老师的《构建之法:现代软件工程》一书的基础上进行总结和提炼,基于“敏捷软件工程”和“微软解决方案框架(MSF)”构思了一套统一软件工程流程框架,因此可以把它看作是这本书的读书笔记。但正如邹老师说的:“知识体系是构建出来的,而不是接收到的。与其灌输知识,不如自己构建”,于是我在学习的过程中有意识地尝试把书中介绍的各种流程和方法串成一根线,正所谓“阵而后战,兵法之常;运用之妙,存乎于心”,要想达到“运用之妙,存乎于心”的境界,还是要从软件工程的基本思维和基础方法开始修炼起。这也是这篇文章的目的所在。在任何社会活动中,人是最重要和最关键的影响因素,因此下面我们就先来聊聊软件工程师的个人战术素养。

一. 软件工程师的个人战术素养

IT行业是知识密集型行业,软件工程师最大的职业风险就是被不断出现的新技术淘汰。一方面,要掌握一些方法和技巧,提高自己的工作效率,尤其是交付的作品的质量;另一方面,把节约出来的时间用于学习新知识和锻炼身体。拓展出来的新技能和强健的体魄又能促进个人价值的最大化,形成增益的良性循环。下面我们就来谈谈有关软件工程师个人战术素养的两个主要方面:开发能力和职业成长。

1.1 培养开发能力的几个要素

1.1.1 测试驱动开发

软件工程师首先要做到的就是对自己的代码负责,不管是维护自己的个人项目也好,是完成具体的工作任务也好,交出来的东西要尽量保证质量好,让别人读完文档就能直接拿来用。怎么保证呢?当然是通过测试的方法来保证——软件工程师一定要写单元测试代码,并与功能代码一起维护。单元测试的主要目的是使模块的质量得到稳定的,量化的保证,它还有一个额外的好处,就是提供了代码使用的示例,单元测试如果写的特别完善,别的开发者甚至可以通过阅读单元测试代码来学习和掌握模块的使用。比如下面这段Go语言单元测试代码就展示了怎样在CouchDB中保存一个大的文档数据:

func TestCreateLargeDoc(t *testing.T) {
    var buf bytes.Buffer
    // 10MB
    for i := 0; i < 110*1024; i++ {
        buf.WriteString("0123456789")
    }
    doc := map[string]interface{}{"data": buf.String()}
    if err := testsDB.Set("large", doc); err != nil {
        t.Error(`db set error`, err)
    }
    doc, err := testsDB.Get("large", nil)
    if err != nil {
        t.Error(`db get error`, err)
    }
    if doc["_id"].(string) != "large" {
        t.Errorf("doc[_id] = %s, want large", doc["_id"].(string))
    }
    err = testsDB.DeleteDoc(doc)
    if err != nil {
        t.Error(`db delete doc error`, err)
    }
}

编写单元测试需要遵循一些基本的规范:

  • 在最基本的功能/参数上验证程序正确性
  • 必须由最熟悉代码的人(程序作者)来写
  • 测试过后相关状态保持不变
  • 测试速度要快,几秒钟内完成
  • 测试产生可重复,一致的结果
  • 独立性:不依赖于别的测试,可人为构造数据保持测试独立性
  • 覆盖所有代码路径,但100%的覆盖率并不代表100%正确性,代码覆盖率对本该实现但并没有实现的功能无能为力
  • 能够集成到自动测试框架中
  • 和产品代码一起保存和维护

测试前,我们要做一些必要的准备(setup),比如插入一些用于测试的样例数据;测试后我们要清理相关数据(teardown),使测试过后相关状态保持不变。值得注意的是,现代编程语言一般都集成了自己的测试框架。以Go语言为例,我们可以在一个叫TestMain()的函数中编写我们自己的setup()和teardown()函数,并在二者之间运行所有的单元测试代码:

func TestMain(m *testing.M) {
    setup()
    code := m.Run()
    teardown()
    os.Exit(code)
}

Go语言规定了凡是以“test”结尾的代码文件都是测试文件,所有的单元测试函数都以Test开头,只要在命令行运行go test命令就能自动运行所有的单元测试并输出结果,除此之外Go语言还可以编写Example示例函数来展示函数用法,Godoc工具会使用相关的示例函数自动生成文档,例如下面这个函数展示了标准库中stringutil.Reverse()函数的使用方法:

func ExampleReverse() {
    fmt.Println(stringutil.Reverse("hello"))
    // Output: olleh
}

随着模块功能的演进,相关的测试用例也在不断演进,这些测试用例集就构成了模块功能的基准线。作为回归测试(Regression Test)的基础,如果有任何一个测试用例失败了,就说明模块的功能从正常工作的稳定状态“回归”到了不正常的不稳定状态。这样一来,不管是增加新功能还是修复bug,回归测试就能确保新的代码实现了期望的功能或者修复了bug,而没有破坏模块原有的功能。

1.1.2 效能分析工具

除了没有bug,软件工程师通常还希望自己的代码运行的又快又好,效率较高,那么除了必要的单元测试技能外,还需要掌握一定的效能分析工具,这些效能分析工具能够分析代码运行的整理情况,模块和函数之间的调用关系拓扑图,以及每个函数耗费的时间,通过对这些数据进行分析,我们就可以定位代码运行效率较低的位置,进行有的放矢的优化工作。

1.1.3 重视个人开发流程的训练

卡耐基梅隆大学(CMU)的能力成熟度模型(CMMI)是用来衡量一个团队能力的一套模型。CMU的专家们对软件工程师也有一套模型,叫个人软件流程(PSP),PSP2.1展示了一个软件工程师在接到一个任务后应该怎么做:

  1. 计划:估计任务需要多少时间
  2. 开发:分析需求,生成设计文档,设计复审,代码规范,具体设计,具体编码,代码复审,测试
  3. 报告:记录用时,测试报告,计算工作量,事后总结并提出过程改进计划

有丰富经验的工程师会在“需求分析”和“测试”这两方面明显地要花更多时间,但是在编码上,要花更少的时间——花在写代码上的时间反而少了。从我维护个人开源项目的经验上来看,分析需求,生成文档和测试花的时间是最多的。得先想清楚代码编写的思路,才能粗略估算完成这个任务需要多少时间,特别是对于比较复杂的代码,我会写一篇文章详细阐述关键代码的设计思路,防止时间长了之后自己忘记当初为什么要这样写了,然后我会将功能代码和相关的测试代码发布到Github上,并撰写尽可能详细的说明文档。如果别的程序员对这个项目感兴趣,他们就会通过各种方式和我交流,个别极其认真的还会提出代码的设计缺陷和bug,这也就相当于设计和代码复审了,因此文档作为一种交流工具非常重要。

1.1.4 二人战斗小组

但如果是两个开发人员组成一个小组的话,就可以做一些代码复审的工作了。这在其他行业也经常见到:

  • 越野赛车(驾驶,领航员)
  • 驾驶飞机(驾驶,副驾驶)
  • 狙击小组(狙击手,观察手)

在软件工程领域,我们叫“结对编程”。在进行结对编程前,两位开发人员首先要达成共识——有关代码风格规范和设计规范的共识。风格规范主要是文字上的一些规定,比如花括号的位置,缩进的大小或者函数的命名等等;设计规范牵涉程序设计,模块间关系和设计模式等方方面面。

达成共识后,两位开发人员就可以开始正式的结对编程活动了,他们分别扮演驾驶员和领航员的角色,前者控制键盘输入,后者起领航和提醒作用。具体来说,驾驶员写设计文档,进行编码和单元测试,而领航员审阅文档和编码,考虑单元测试的覆盖,思考是否需要和如何重构,帮助驾驶员解决具体技术问题。注意驾驶员和领航员要不断地轮换角色,任何一个人不要扮演同样角色超一个,每工作一小时休息15分,由领航员控制。在结对编程的活动中,任何一个任务首先都是两个人的责任,要主动参与——大家只有水平上的差距,没有级别上的差异,拥有平等决策权利。

结对编程有很多好处,首先两人合作解决问题,能够提更好的设计质和代码质量,这会给大家带来信心,高质量产出带来更高满足感。这也有利于促进团队成员之间的更有效交流:相互学习和传递经验,分享知识。最后,结对编程还能带来不间断的复审——看代码是否在“代码规范”的框架内正确地解决了问题,越早发现问题越好,越是项目后期发现的问题,修复的代价越大。同伴复审可作为自我复审和团队复审的一个有效的补充形式。复审的目的在找出代码错误,发现逻辑和算法错误,发现潜在错误和回归性错误,发现可能需要改进的地方,以及互相教育传授经验,促进团队成员熟悉项目各部分代码,熟悉应用领域相关实际知识。复审的一个标准流程如下:

  1. 代码必须采用最严格编译警告等级成功编译,必要时提供Debug/Retail版本;
  2. 程序员必须测试过并提供新的代码;
  3. 复审者选择复审方式:面对面,独立复审或者其他方式;
  4. 开发者控制流程,讲述修改的前因后果,复审者有权在任何时候打断叙述,给出自己意见;
  5. 复审者逐一提供反馈意见,开发者有义务给出详尽回答;
  6. 开发者负责确保所有问题得到满意的解释或解答,或确保这些问题得到处理;
  7. 最后,达成复审结果一致意见:
    • 若发现致命问题,则打回,解决之前不能签入;
    • 若发现一些小问题,可有条件同意(不需复审,修改后签入);
    • 代码可以不加新改动,直接签入;
  8. 复审后整理记录,更正明显的错误,对无法很快更正的错误记录下来形成bug report,开发把所有错误记录在“我常犯的错误”表中,作为自我复审参考:
    • 症状:从用户角度看,软件出现了什么问题?
    • 程序错误:从代码的角度看,代码的什么错误导致了软件问题?
    • 根本原因:导致代码错误的根源是什么?
1.1.4.1 如何影响对方

影响对方的方式有四种:

  1. 断言(Assertion):“就这样吧,听我的,没错”,感情强烈,适用于有充分信任的同伴;
  2. 桥梁(Bridge):“能不能再给我讲讲你的理由……”,给双方充分条件互相了解;
  3. 说服(Persuasion):“如果我们这样做,根据我的分析,我们会有这样的好处……”,有条理,建立在逻辑分析基础上,即时不能完全说服,对方也可能部分接受;
  4. 吸引(Attraction):“你想过舒适生活么?你想在家里发财么?加入我们吧,几个月后就能有上万收入”,有效地传递信息,但要注意信息准确性,夸大渲染会降低个人可信度;
1.1.4.2 如何正确给予反馈

评论别人有三种层次:

  1. 最外:评论别人的行为和结果,对方较容易接受:行为可以改正,后果可以弥补,有挽回局面的机会;
  2. 中间:评论别人的习惯和动机,对方较难接受:比较难表白和澄清动机,容易引起误会;
  3. 最内:评论别人的本质和固有属性,对方很难接受:无法回应,容易引起冲突和矛盾;

因此如果我们要正确地给予同伴反馈,既要正确地反馈信息让对方接受,又要避免对方造成误会引起矛盾,可以采取“三明治方法“:

  1. 做好铺垫(面包):强调双方的共同点,从团队共同愿景讲起,让对方觉得处于一个安全环境;
  2. 建设性意见(肉):少沉溺于过去,多展望将来,“过去你做的不够,但我们以后可以做的更好”;
  3. 呼应开头,鼓励对方把工作做好(面包);

当然,也不是所有的情况下结对编程都适用。首先,处于探索阶段的项目需要深入思考,不适合进行结对编程;其次,后期维护时,若技术含量不高,只需要有效复审即可;另外,对于要运行很长时间的验证测试,两个人坐在那里等待结果也比较耽误时间;然后如果团队成员在多个项目中工作,也不能充分保证足够的结对编程时间;最后,结对编程需要最大限度发挥领航员作用,若用处不大,则无需结对。

1.2 工程师的职业成长路径

1.2.1 个人能力的培养

工程师个人能力的培养主要包含4个方面。首先是技术能力,即软件开发技能,包括对具体技术的掌握(编程语言和开发平台等),动手能力(能够诊断/提高效能)以及软件工程思想;其次是领域知识的积累,包括问题领域的知识和经验,据说有些开发财务软件的公司要求自己的开发人员通过注册会计师考试;然后是一定的职业技能,包括:

  1. 自我管理能力:时间管理和解决问题的思维能力
  2. 学习能力:
    • π型人才:在哪几个方面追求“专和精”,在哪几个方面追求“知道就好”
    • 如何学习:知识是构建出来的,刻意练习构建心智模型(mindset)
  3. 表达和交流能力:能说会写
  4. 与人合作能力
  5. 按时按质量交付任务能力
    • 项目/任务有多大:代码行数和功能点数
    • 花了多少时间:人数 * 时间
    • 质量如何:交付的代码有多少缺陷?缺陷数量除以项目大小是多少?
    • 是否按时交付:实际用时的平均值和标准差(推崇稳定,一致的交付时间)

最后,工程师一定要重视自己的实际工作成果:

  1. 参与的产品用户评价如何?
  2. 市场占有率如何?
  3. 对用户有多大价值?
  4. 你在其中起了什么作用?
  5. 参与开源项目,为开源社区做贡献?

平时要有意识地训练自己使用STAR原则描述自己的工作:

  1. Situation(情景):简单项目背景,比如规模,软件功能和目标用户;
  2. Task(任务):自己完成的任务,要分清楚“参与”和“负责”;
  3. Action(行动):为了完成任务做了哪些工作,怎么做的;
  4. Result(结果):得到了什么成果,满足了什么需求,有什么贡献;

1.2.2 工程师的职业发展

1.2.2.1 认证考试

软件工程师可以参加一些认证考试来证明和提高自己的职业技能。比如软件工程师的职业资格考试——“全国计算机技术与软件专业技术资格考试”,或者参加一些公司的职业认证项目,比如“红帽认证工程师(Red Hat Certified Engineer)”。目前网上有大量的在线教育网站,提供各种MOOC课程,比较有名的有Coursera和Udacity。还有“中国计算机协会计算机职业资格认证考试”和“浙江大学计算机程序设计能力考试”。

1.2.2.2 职业成长

在一般的大公司中,软件工程师的职业成长路径如下所示:

  1. SDE(初级软件开发工程师);
  2. SDE II(中级软件开发工程师);
  3. Senior SDE(高级软件开发工程师);
  4. Principal SDE(高级软件开发工程师);
  5. Partner SDE,Distinguished Engineer,Technical Fellow;

软件工程师们平时要努力提高自己的单兵战术素养,多学习,多实践,练好基本功,打好扎实的基础,才能在团队中发挥自己的作用,创造独一无二的价值。有了良好的战术素养作为基础,才能有效地实施软件工程的工作流程,下面我们就正式展开对相关流程的叙述。内容分为两个部分:战略层和战役层。战略层主要介绍的是软件工程的“构思”和“计划”,而战役层面则主要介绍软件工程的“开发”,“稳定”和“部署”。

二. 软件工程的实施(战略层)

2.1 构思阶段(Envisioning Phase)

构思阶段要完成2个主要工作:根据项目实际情况组建开发团队,并编写用户故事,形成最初的产品待办事项清单。

2.1.1 组建团队

一支完整的团队包括3个组成部分:产品负责人,开发团队和客户(团队)。开发团队由具有各种不同技术栈的开发人员组成,理想情况下,可根据项目的实际情况灵活组建。

2.1.1.1 产品负责人

“过程创新可能超越产品创新,但两个创新并驾齐驱则胜于任何一个。” ——著名用户体验专家比尔.巴克斯顿

产品管理是根据市场和用户需求协调各部门资源(产品定位,市场发展,需求分析,运营,营销,市场推广,商务合作),正确把握产品定位和方向,确保团队理解商业目标和客户需求,解决用户痛点;沟通,计划,收集内外用户反馈,持续优化产品的过程。项目管理则主要关注于如何领导软件开发过程,这需要和人以及管理流程打交道,以调配各部门资源和时间,处理和应对不确定性,正确地协调团队内部外部,形成团队风格。此外,按预算和计划管理项目,有效进行风险管理也是项目管理的一部分,对项目开发而言,风险管理有四个层次:

  1. 第一层次:没有风险管理,突发事件没有预案,事后进行危机管理;
  2. 第二层次:缓和并防止,动员相关方面(团队成员,管理层和合作伙伴)一起想办法防止事故发生,进行风险流程管理:
    • 冲刺阶段不允许修改任何需求
    • 快速迭代,增量改进
    • 结对编程增加团队对代码的了解
  3. 第三层次:定量的准备和预计;
  4. 第四层次:将问题变为机会;

要成为产品负责人,对能力有很高的要求。首先,他/她必须能够观察,理解和快速学习,具有同理心和自省能力。其次还要有“理解和表达”的专业能力,要能理解不同人心理,需求和言外之意,要能借助文字,图表,草图甚至代码来清晰准确表达自己想法。然后要能够满怀希望向用户兜售产品,向团队兜售希望,要能玩转各种工具,有文字功底,平时有大量的阅读。每天项目中发生的事情千头万绪,产品负责人要能分析并抓住重点,找到优先级,根据重要+紧急四象限做判断和决策。

作为产品负责人,他/她的具体任务是带领团队达成最重要的目标/愿景,保持“功能好,资源省,时间快”的合理平衡。然后把抽象的目标转化为可执行的,具体的和优美的设计。要代表客户和用户的利益,主动收集用户反馈,预期用户新需求,管理用户需求的收集,分析和优先级排序。跟踪项目的进展,管理软件具体功能的生命周期,分析并带领其他成员对缺陷/变更需求形成一致意见。收集团队项目管理和软件工程的各项数据,客观分析实施过程中的优缺点,推动项目成员持续改进,并提振士气,最后确保团队发布的是令客户满意的软件。

2.1.1.2 开发团队

开发团队主要的组成人员就是软件工程师,但我们可以根据经验的丰富程度和分工的不同分为以下几种角色:系统架构师,软件工程师,测试工程师,运维工程师和UI/UX设计师,若团队人数众多,则可根据实际情况拆分为角色小组。开发团队的价值在于持续交付满足用户商业价值的软件,其具体任务为:

  1. 完成具体的开发工作
  2. 负责软件开发全阶段与测试有关的数据收集
  3. 设计,实现和维护测试环境
  4. 计划和管理产品部署解决方案

2.1.2 敏捷需求管理

与其它基于场景和用例的需求管理方法不同,敏捷开发的需求管理是以“用户故事(User Story)”为中心的。用户故事从用户的角度出发,描述了对用户,系统或软件购买者有价值的功能。它包含三个核心组成成分,简称3C:

  1. 卡片(Card):故事描述,用于做计划和作为提示;
  2. 对话(Conversation):用于具体化故事细节;
  3. 确认(Confirmation):验收测试,用于表达和文档化故事细节,并用于确认完成;

卡片包含故事的文字描述,需求细节在“对话”中获得,并在“确认”部分得以记录。整个需求管理的过程从“用户角色建模”开始,中间经历“搜集用户故事”的过程,以“编写用户故事”结束,最后得到最初的产品待办事项清单(Product Backlog)。

2.1.2.1 用户角色建模

为什么我们要对用户角色进行建模?因为使用我们软件的用户可能有不同的背景,并持有不同的目标。要想搜集用户的需求,我们要做的第一步工作就是先要弄清楚有哪些用户会使用我们的系统,把他们挖掘出来,然后以用户角色为中心发散开来,根据不同的用户角色搜集和编写用户故事。在用户角色建模的过程中,我们首先要进行头脑风暴会议,列出初始的用户角色集合,会议的参与人员主要是客户和开发人员(多多益善),每个人尽量写出自己想到的角色,然后大声说出新角色的名字,要注意的是一个用户角色应该是一个用户的个体,即个人。我们要做的第二步工作是整理最初的角色集合:移动卡片位置以表明角色之间的关系,对于有重叠的角色,把它们相应的卡片也叠放在一起,如果角色有一点点重叠,那么卡片也只重叠一点点;如果角色完全重叠,那么卡片也完全重叠。第三步工作是整个角色,从重叠的卡片入手,合并等同的角色,丢弃对系统成功不太重要的角色,角色整合完成后,排列它们以展示角色之间的关系。最后一步,我们对角色进行提炼,列举所有可以区分这个角色的信息,包括用户使用软件的频率,用户在相关领域的知识水平,用户使用计算机和软件的总体水平,用户对当前正在开发的软件的熟悉程度,以及用户使用该软件的总体目标,有些用户注重使用的便捷性,有些则关注丰富的用户体验。如果有需要的话,我们还可以采取一些额外的技术,比如创建“虚构人物”或者“极端人物”。

2.1.2.2 搜集用户故事

在开始搜集用户故事之前,我们首先要对“用户需求”有一个全面正确的认识。我们要认识到,需求有多种类型:产品功能性需求,产品开发过程需求,非功能性需求,综合需求等等。用户的需求并不是“引出”或“捕捉”到的,很多需求并不容易想到,用户也并不知道所有需求,甚至用户自己都不知道自己有这样的需求。

一种比较好的比喻,是将用户需求比喻成“鱼”——需要通过拖网捕捞(trawling)捕捉到。首先,不同网眼的渔网能够捕获不同大小的需求(用商业价值高低或必要性程度衡量),第一遍捕捞获得的是大需求,第二遍捕捞获得的是中等大小的需求;其次,需求会像鱼一样不断演进,会成长,也可能会死亡:有些需求会变得越来越重要,有些需求重要性会降低;第三,不可能捕捉到所有的需求,“废弃的货物”或“漂浮的残骸”会使需求膨胀;最后,技能也是发现需求的一个要素,熟练的需求分析人员知道到哪去找需求,不熟练的需求分析人员只会用低效的方法或在错误的地方浪费时间。

搜集用户需求的方法有很多种,常用的如下:

  1. 焦点小组:找到一群目标用户的代表,加上项目的利益相关者来讨论用户想要什么,用户对软件的评价等等;
  2. 用户访谈:通过开放式问题和背景无关问题,通过提问获取用户本质需求;
  3. 用户调查问卷:向用户提供事先规定好的问题,让用户回答;
  4. 用户日志研究:用户记录自己的日常工作或生活中与所用软件相关的行为,供软件团队分析;
  5. 人类学调查:和目标用户生活在一起,“同吃同住同劳动”;
  6. 眼动跟踪研究:跟踪和研究用户视觉的移动方向;
  7. 快速原型调研:做一个产品原型,快速获取用户反馈;
  8. A/B测试:在5%-10%用户上试验两种不同产品(通常是原有产品和改进型产品),收集数据并分析得到结论;
  9. 用户观察:观察用户使用软件的情况;
  10. 故事编写工作坊:开发人员,用户,产品客户和其他对编写故事有帮助的人共同参加的会议,编写尽可能多的故事;

对于一个项目来说,客户团队里包含一个或多个真实用户是极其重要的。遗憾的是在很多时候,我们很难有机会与实际用户一起工作,那么我们就需要求助于用户代理,他们自己可能不是用户,但他们在项目里代表着用户。最理想的用户代理,是该软件以前的用户,以及能够与用户密切交流的客户。选择合适的用户代理时我们要注意甄别对象。用户的经理和开发经理是不太理想的对象,较理想的对象还有以下几种,但他们作为用户代理需要注意各自的一些问题:

  1. 市场营销团队:必须克服只关注软件功能数量而不是质量的问题;
  2. 销售人员:避免把重点放在赢回失去订单的用户故事上;
  3. 领域专家:避免将产品开发成只适合专家水平的人使用;
  4. 培训师和技术支持:避免只关注产品中那些他们每天关心的方面;
  5. 业务分析师和系统分析师:避免空想,以及在项目前期花太多时间;

那么在和用户代理合作时能做些什么呢?如果能够接触到用户但访问受限,我们可以请求准许启动一个用户顾问团队,顾问团队可以提出意见和建议,而用户代理依然是项目的最终决策者。如果实在接触不到用户,可以使用多个不同类型的用户代理,或者尽早发布产品,及早将软件交付到用户手里,看看用户的反应。或者,做竞品分析:大多数情况下市面上不会只有一款产品为用户服务,我们还可以对已有的产品进行竞品分析,有一种竞品分析的方法被称为“NABCD竞争性需求分析框架”:

  1. Need:你的创意解决了用户的什么需求?
  2. Approach:你有什么独特的招数来解决用户的痛苦?
  3. Benefit:这个产品/服务会给客户/用户带来什么好处?
  4. Competitors:这个市场有多大?目前有多少竞争者?我方优势在哪里?我方劣势在哪里?
  5. Delivery:怎样把你的创新产品交到用户的手中?

应该将客户团队建立成一个优势互补的团队,一位成员的优势能平衡另一位成员的弱势,建立客户团队有三步:

  1. 第一步:邀请不同类型的真实用户加入;
  2. 第二步:在客户团队中确定一位“项目负责人”;
  3. 第三步:确定项目成功必须的关键因素。
2.1.2.3 编写用户故事

优秀的用户故事具有6个特征,我们统称为“INVEST”。用户故事是独立的(Independent),我们要避免故事之间产生相互依赖,一种方法是将相互依赖的故事合并成一个大的,独立的故事,或者用一个不同的方式去分割故事,假如你不想合并故事,但又找不到好的方法来分割它,就还可以使用一个简单的方法,对用户故事进行两个估计:该故事早于另一个故事实现时,一个较高估计,该故事晚于另一个故事实现时,一个较低估计。用户故事是可讨论的(Negotiable),它是功能的简短描述:一两句短语,用来提醒开发人员和客户进行对话,一些注释,用以表明在对话中需要解决的问题。细节在客户团队和开发团队的讨论中产生,讨论中确定的细节将变成测试。用户故事是对用户或客户有价值的(Valuable to Purchasers or Users),要站在用户或客户的角度编写故事,避免出现技术的实现细节。用户故事是可估计的(Estimatable),用户故事难以估计是有各种原因的,一个原因是因为开发人员缺少领域知识——开发人员不理解故事,这个时候就需要和写故事的客户一起讨论,争取对故事有个大概的了解,但没有必要理解所有细节,另一个原因是因为开发人员缺少技术知识——开发人员不掌握所涉及的技术,这个时候可以采取“探针试验”的方法:将一个故事拆分为两个部分,一个快速的探针故事,用来进行技术预研获取足够的信息,另一个故事,用来真正实现功能。反过来,如果故事太大了,可以分解为多个更小的故事,或者留作史诗故事(Epic):系统中有待讨论的一大块功能的占位符。用户故事是小的(Small),这里的小是指合理大小。一方面,太小的故事要做合并,另一方面,我们要对史诗故事进行分割,对于多个小故事组成的复合故事,可以按动作(创建,编辑和删除等等)分割,也可以按数据边界分割;对于本身很大不容易分解的复杂故事,则需要消除其不确定性,将其分为调研故事和开发故事,并放到不同迭代里。用户故事是可测试的(Testable),要尽量实现测试自动化,成功通过测试可以证明开发人员正确地实现了故事。

编写优秀的用户故事有一系列准则。从目标故事开始,特别是对于有许多用户角色的大型项目,要考虑每一个用户角色,从用户使用软件的目标出发衍生出其他高层次故事,进一步衍生出新的故事。编写用户故事有一个被称作切蛋糕的方法,即用端到端的方式分解/编写故事,每个故事提供某种程度的完整功能,这样我们可以及早涉及软件应用程序架构的每一层,降低最后时刻才发现层次架构方面问题的风险,且每轮迭代交付给用户的是可用的功能。要编写封闭的故事,封闭的故事是指随着一个有意义的目标的实现而结束的故事,有始有终,让用户使用后觉得她完成了某个任务。对任何必须遵守而不需要直接实现的故事标注“约束”形成卡片约束,我们就可以描述一些非功能性需求。要根据实现时间来确定故事规模,对于即将实现的故事,规模更小,精确度更高,对于更远的将来要实现的故事,规模更大,精确度更低。编写故事时,一定不要过早涉及用户界面,且要注意有些需求并不是故事,这个时候我们就可以用其他形式(文档)来表达需求,进行补充,比如用户界面参考和系统接口文档。在故事里一定要包括用户角色,每个故事只为一个用户编写,并以用户为第一人称采用以主动语态编写,最好是由客户编写用户故事,尽量不要给故事卡编号,向故事卡编号说不,有必要的时候给故事卡取一个简短容易记的名字。最后,不要忘记意图,故事卡片的作用是提醒开发人员和客户团队对功能进行讨论,所以要保持简洁性,加入需要的细节,以便联想到继续对话的切入点,但不要加入太多细节并以此取代对话。我们所编写的用户故事的集合,就形成了我们最初的产品待办事项清单(Product Backlog)。

2.2 计划阶段(Planning Phase)

在计划阶段,我们从Product Backlog出发,对用户故事进行估算,计算团队的初始速率,然后启动发布计划,形成产品开发路线图。在每轮迭代开发的开始,将本轮迭代要完成的用户故事进一步分解为任务,启动迭代计划,进入实际开发,利用迭代燃尽图来测量和监控开发速率。

2.2.1 估算用户故事

估算用户故事的最好方法有如下几个特点:

  • 无论什么时候获得有关故事的新信息,都允许我们改变之前的想法
  • 适用于史诗故事和小故事
  • 不需要花很多时间
  • 提供进度和剩余工作的有用信息
  • 不太精确的估算也不会有太大问题
  • 可以用来制定发布计划

“故事点”估算的方法可以满足所有这些目标。故事点可以有很多定义方法,比如理想日,指没有任何干扰,没有会议,没有电子邮件,没有电话的完美的一天,或者理想周,复杂度测量什么的。这里推荐将一个故事点的工作量看作是一个理想日的工作。有两个好处,一是相较于用连续时间估算,它更简单;二是相较于用完全模糊单位,用理想日估算故事点可以使估算有更好的依据,方便换算成时间。做用户故事估算一定要以团队为单位,大概4-8个人,估算的流程是:

  1. 客户随机抽取一个故事读给开发人员听;
  2. 开发人员根据需要尽可能多发问,客户尽量解答,如果客户不知道,就猜猜看或者推迟估算;
  3. 如果对故事没有疑问,每个开发人员写下一个估算值,先不要给其他人看;
  4. 所有人同时展示自己的卡片,出示估算值给大家看;
  5. 如果估算值不同,估值高的和低的分别解释估算依据,大家讨论;
  6. 讨论完后所有人再写下修改后的估算值并展示给大家看;
  7. 直到大家得到一个统一的估算值,这个过程很少超过3轮;

最后,对所有估算过的故事进行一个“三角测量”:按故事点数分列每个用户故事,这是为了帮助团队验证他们没有逐渐改变一个故事点含义的有效方法。要注意,估算越大,对故事知道的就越少。

2.2.2 启动发布计划

启动发布计划前,我们要问自己两个问题:

  1. 我们想在什么时候发布?
  2. 希望在发布中包含哪些功能?

首先,我们对所有估算过的故事排列优先级。排列优先级的时候,我们需要考虑一些技术要素和商业价值要素。技术方面,我们要评估故事不能如期完成的风险,推迟实现一个故事对其他故事的影响,以及一些与基础性或非功能性需求有关的因素,比如架构的需要;商业价值方面,要考虑故事对广泛用户或客户的重要性,故事对于少部分重要用户或客户的重要性,故事与其他故事的内聚性,以及故事实现的成本,有时候成本会改变优先级。在确定一个故事优先级时遇到问题,我们就需要分割故事,然后对分割后的故事排不同优先级。一种根据功能定位划分优先级的方法如下:从第一种维度,我们可以将产品功能划分为核心功能(Core)外围功能(Context);从第二种维度,我们可以将产品需求划分为必要需求(Critical)辅助需求(Enabling),这四种划分结合起来,我们就能得到用户故事优先级的四个象限:

  1. 第一象限:核心功能+必要需求,建议采取“差异化”方法,全力以赴投资这一领域;
  2. 第二象限:外围功能+必要需求,建议采取“抵消”策略,快速地达到“和别人差不多”,对大家都特别看重的功能,采取“优化”的办法达到行业最佳;
  3. 第三象限:外围功能+辅助需求,建议采取“维持”策略,以最低代价维持此功能;
  4. 第四象限:核心功能+辅助需求,要么维持,要么现在不做等待好时机,要么小规模实验;

敏捷方法的精髓在于拥抱变化,反复完善系统,但当这些变化影响到用户界面时,后期的改进是无法接受的,因为用户已经学会和掌握了先前的用户界面,这会产生严重的问题。那么一种敏捷版本的以使用为中心的设计(usage-centered design)认为,我们可以采取如下的设计方法:

  • 用户角色建模
  • 捕捞高层次的用户故事
  • 排列故事优先级
  • 精炼高优先级和中等优先级故事
  • 对故事整理分组
  • 建立书面的原型
  • 精炼该原型
  • 开始编程

所以在划分优先级后,我们要精炼高优先级和中等优先级故事,将它们精炼成更小的故事,然后把高优先级和中等优先级的故事整理为有望一起执行的组,给每组的故事画出书面原型,展示给用户或用户代理看,根据他们的意见改进原型,这样我们就把用户界面给确定了下来。优先级排列完毕后就可以开始选择迭代长度了,一般来说一轮迭代通常为1-4周,如果不确定,就使用短迭代周期。剩下的就是计算团队的初始速率,这个速率是以用户故事点为单位的,代表一轮迭代能完成的故事点,如果团队做过类似项目,那么可以使用历史值,否则的话就需要猜测速率了,一种方法是将一轮迭代三分之一到一半的开发日作为预计速率(开发日 = 团队人数 × 迭代长度),当然,随着迭代的进行可以不断修正,完善估算。这样我们就可以粗略估算需要多少轮迭代才能交付产品:迭代次数 = 项目总故事点 / 每轮迭代的故事点。我们先选取当前优先级最高的用户故事(故事点数不超过一轮迭代的速率)放入第一轮迭代中,然后再将下一组次高优先级的用户故事放入第二轮迭代,如此进行,直到分配完所有故事。这样就可以制作出产品开发路线图。

2.2.3 启动发布计划后测试要做的任务

在这个阶段,负责测试的开发人员需要完成初步的规划,制定测试计划(总纲),包含以下内容:

  1. 产品是什么?
  2. 要做什么样的测试?
  3. 时间安排如何?
  4. 谁负责什么方面?
  5. 各种资源在哪里?

2.2.4 启动迭代计划

每轮迭代都是以迭代计划会议开始的,参与人员为客户和团队中的所有开发人员。我们在会议中讨论故事,客户从最高优先级故事开始,读给开发人员听,开发人员提问,直到充分理解故事,能从故事中分解出任务。这么做的好处有两个,首先实现故事的开发人员不止一个,不同的任务需要由不同的开发人员来承担;其次故事是对用户或客户有价值的功能描述,而分解后的任务成为开发人员的to-do list,这依靠所有人的集体智慧,所以不大可能出现遗漏。敏捷方法的一大特点就是用频繁的短期设计来取代瀑布过程的前期设计阶段,一个故事的任务分解是即时设计中的一次短脉冲,分解任务的准则:

  1. 如果故事的某个任务特别难估算,就把那个任务从故事的其余任务中分离出来;
  2. 如果有些任务可以很容易安排给多名开发人员共同完成,就分割它们;
  3. 如果有必要让客户了解故事某一部分完成情况,可以把那部分拿出来作为一个任务;

开发人员要主动认领和承担每个任务的职责,将自己的名字写在认领的任务旁边并对任务负责:不管是从客户那里获得其他信息还是寻找结对编程对象,都由他去负责。整个团队要有“同舟共济”的心态,要主动分担并勇于承担。认领完任务后,开发人员要进行最后的估算和确认。每个开发人员以故事点为单位估算自己承担的工作量,每个开发人员确认自己的工作量,有没有把握?如果没有,可以有三种处理策略:

  1. 留着所有任务,寄希望于一切顺利;
  2. 请求团队中其他成员接手一些我的任务;
  3. 与客户讨论,要么放弃一个故事,要么分割故事,放弃其中一部分;

如果其他开发人员评估完自己的工作量,觉得自己还能多主动分担一些额外的工作量,就可以让其他开发人员接手,如果大家都没多余精力,那就请客户帮忙从迭代中移除一些工作,这样可以保证大家最后能按自己的承诺交付产品。这样开发团队在迭代计划会议后就得到了本次迭代的冲刺待办事项清单(Sprint Backlog)。

2.2.5 启动迭代计划后测试要做的任务

测试需要同步跟进,完成测试设计(TDS)的编写。TDS首先描述功能是什么,需要测试哪些方面,特别是有没有预期的bug比较多的地方;其次是如何测试:

  1. 拟采用的具体方法;
  2. 如何做测试自动化;
  3. 准备什么样的测试数据;

然后要考虑功能如何与系统集成,如何测试这方面?最后给出Exit Criteria:什么叫测试好了?

三. 软件工程的实施(战役层)

我们来回顾一下上面我们都完成了什么工作。首先,我们组建了一支各司其职的开发团队。其次,进行需求管理,得到Product Backlog,对用户故事进行估算,评估优先级,估算团队开发速率,启动发布计划(测试工程师编写测试计划),得到产品开发路线图。紧接着进行迭代计划会议,讨论每个故事,从故事中分解任务,开发人员主动认领任务,估算并确认任务工作量,形成本次迭代开发的Sprint Backlog(测试工程师编写测试设计)。有了前面的工作作为基础,我们终于可以从战略过渡到战役,进行实际的开发工作,包括3个阶段:开发,稳定和部署。

3.1 开发阶段(Developing Phase)

3.1.1 开发人员的标准工作流程

拿到开发任务后,开发人员需要完成一系列的工作,写quick-and-dirty的快速原型代码。对于复杂到一定程度,开发者需要深思熟虑的开发任务,可以写设计文档(Technical Spec,Design Doc),用于描述开发者如何实现一组相互联系的功能。编写设计文档需要体现软件设计的一系列原则:

  1. 抽象
  2. 内聚/耦合/模块化
  3. 信息隐藏和封装
  4. 界面和实现的分离
  5. 如何处理错误情况
  6. 程序模块对运行环境、相关模块和输入输出参数有何假设?
  7. 应对变化的灵活性
  8. 可扩展性:应对大量数据的处理能力

设计过程中可以选择一些图形化建模工具作为辅助,但不是必须的。对于软件工程师而言常用的就是“统一建模语言(UML)”了,但从我自己的实际经验来看,我觉得“思维导图”是一个非常好的整理思路的工具。不管是写文章也好还是写代码也好,边思考边画思维导图,把知识之间的内在联系和框架结构画下来,思路清晰之后再写成代码或者文章,能极大的提高工作效率,特别是对于复杂对象的认识会更全面详细,不会漏掉细节。例如图3-1就是我在阅读《构建之法》和《用户故事与敏捷方法》后画的思维导图,同时也是这篇文章的思维导图。

图3-1 思维导图

有了设计文文档之后可以按照设计文档写代码。对照设计文档和代码指南进行自我复审,重构代码,并创建或更新单元测试。之后运行单元测试,既要通过自己编写的测试,也要通过整个模块/系统的单元测试并保证代码覆盖率。最后得到一个可测试版本交付给测试人员。如果出现了bug,那么开发人员就需要修复测试人员或用户发现的问题,请同事进行代码复审。然后进一步根据代码复审意见修改代码,完善单元测试和相关文档,进行伙伴测试(可选),最后把代码签入代码库中,这一系列步骤完成之后就得到了一系列完整的,验证过的功能。

伙伴测试的步骤如下:

  1. 开发人员找一个测试人员作为伙伴
  2. 开发人员做一个包含新模块的私人构建
  3. 测试人员在本地做必要的回归/功能/集成/探索测试
  4. 发现问题直接与开发人员沟通

项目进入开发阶段后需要进行严格的日常管理以保障代码质量。对于开发人员,要采用“闭门造车”模式——不要随便打断开发人员,尽量减少非开发时间,不要动不动全体会议。对于项目,要进行每日构建(Daily Build),因为即使只是一个简单的系统,团队的积极性也会上升,同时也能保证测试组能及时拿到最新版本进行测试。若开发人员手里的bug超过一定数量,那么就将他/她打入bug地狱,专心修bug。最后,根据燃尽图进行每周进度报告。

3.1.2 开发阶段的测试工作

到了这一阶段,测试小组需要开发一系列的测试用例对已完成的功能进行测试。对于每个测试用例,要写清楚:

  1. 如何设置测试前的环境;
  2. 如何操作;
  3. 预期的结果;

所有的测试用例集合形成一个测试套件(Test Suite),那么测试用例如何设计呢?有很多种方法,这些方法相互补充,帮助测试人员有效地生成测试用例:

  1. 等价类划分:每种划分要触发不同的处理逻辑或者错误,有效覆盖程序的各种可能出现问题的地方;
  2. 边界值分析:产生测试数据触发各种边界条件,能有效地验证程序在这些地方是否正确;
  3. 常见错误:根据经验推测程序通常容易出错的地方,更有效地设计测试用例,例如空文件名,在期望数字的字段填入文字等等;

针对每日构建得到的版本,要进行构建验证测试(Build Verification Test),BVT特指构建完成后自动运行的一套测试,用于验证系统基本功能。BVT有时候需要手工进行,但我们要努力让BVT自动运行,通过BVT的构建被称为可测的(Testable),意思是说团队可以用这一版本进行各种测试,因为它的基本功能是可用的。测试小组拿到一个可测的构建以后,就会按照计划测试各自负责的模块和功能。这个过程叫做验收测试。测试小组还可以进行一定的探索式测试,测试一些一般人不会想到的场景,某些测试可以抽象出来,囊括到以后的测试计划中,作为补充完善的一种手段。

测试过程中,如果发现了问题,就得提交错误报告(bug report):

  1. bug的标题:简要说明问题和严重程度;
  2. bug的内容:测试的环境和准备工作,测试的每一步步骤,实际发生的结果,期望发生的结果
  3. 其他补充材料:相关联的bug,输出文件,日志文件,调用堆栈的列表,截屏

3.2 稳定阶段(Stabilizing Phase)

3.2.1 基本流程

每个Sprint开发周期的开发任务完成后,项目就进入了这轮迭代的稳定阶段,稳定阶段是从开发阶段到发布阶段的一个过渡,起到承上启下的作用。一般来说从代码完成(Code Complete)到最终发布软件,有一套基本的工作流程(同时也是版本演进的一般方式):

  1. Alpha版本发布(集成主要功能的第一个试用版)
    • 代码完成(Code Complete,CC)
    • 集成测试,bug修复
    • 收集反馈
  2. Beta版本发布
    • DCR,Bug修复
    • 代码完成
    • 集成测试,bug修复
    • 收集反馈
  3. Release Candidate候选版本发布(一系列发布候选版,版本间隔时间较短,RC1,RC2……)
    • DCR,Bug修复
    • 代码完成
    • 集成测试,bug修复
    • 收集反馈
  4. 最终版本发布(RC足够好了)
    • 发布到应用市场(Release To Market,RTM)
    • 发布到Web(Release To Web,RTW)
    • 发布到运营团队(Release To Operation,RTO)

3.2.2 解决影响产品发布的问题

3.2.2.1 会诊小组

会诊小组由各角色代表组成,处理每一个影响产品发布的问题。对于每一个bug,会诊小组要决定采取下面哪一种行动:

  1. 修复;
  2. 设计本来如此(As Designed),用户或测试人员可能对功能有误解,或者功能的解释不完备;
  3. 不修复,这是个问题,但这个版本不打算修复;
  4. 推迟,如果我们的软件是真正解决用户问题的,有价值的,那它一定会有下一个版本;

对于大型的复杂项目,会进行更复杂的会诊工作。要按照版本发布的不同阶段采取不同的修复bug的方式,总体说来是门槛越来越高。在Alpha版本阶段,有bug可以马上修复,签入代码后再通知大家,但是在Beta版本阶段,新代码签入前必须告知会诊小组潜在风险是什么?如何应对?到了RC版本阶段,修复bug之前开发人员必须和会诊小组沟通,按照如下的标准流程进行。

  1. 开发者提交参加会诊的bug和修改方案
    • bug是什么
    • 危害是什么,如果不修复什么后果
    • 用户会有什么变通做法
    • 是否经过代码复审,是否经过伙伴测试
  2. 会议决定是否同意修改方案
    • Must:必须修复,缺陷很严重,修复方案可行,相关测试都通过
    • More Info:需要更多信息才能做决定(缺陷的影响不明确/相关测试不完备/解决方案有缺陷)
    • No:不能接受,推到下一个里程碑或解决方案不合要求
    • Like:可能,不一定必须修复,解决方案相对安全,可以和Must级别的修复一并集成到代码库中
  3. 执行
3.2.2.2 其他招数
  1. 设计变更(DCR)
    • 提交申请
      • 问题在哪里,问题的影响
      • 如果不修改,有何后果
      • 几种修改方案,各种方案优缺点和成本
    • 执行次序
      • 会诊所有DCR
      • 按影响和成本排序,得到一个自上而下的名单,根据现有资源按名单执行
  2. 零Bug构建(Zero Bug Build),某天的版本把在之前(48小时)记录的bug都解决掉
  3. 最后回归测试,所有人员回归测试所有bug
  4. 砍掉功能
  5. 逐步冻结,从用户界面开始逐步冻结,不再随意修改

3.2.3 稳定阶段的测试工作

这一阶段的测试工作主要有两方面,一是进行综合的验收/集成/系统测试,二是非功能性特性测试。

3.2.3.1 验收/集成/系统测试

在敏捷开发的稳定阶段,我们要对软件进行全面和系统的测试,以保证软件的各个模块都能共同工作,各方面都能满足用户需求。我们把计划阶段(Planning Phase)分析得到的所有用户故事都列出来,然后按功能做验收测试,如果成功则标明“成功”,否则就记录一个或几个bug ID。然后将迄今为止发现的所有bug全部重测一遍,确保没有出现回归。若所有的场景都通过,那么这个构建的质量就是“可用的”,可以发布成技术预览版。

用户故事验收测试报告模板

  1. ID
  2. 故事名
  3. 测试结果
  4. bug ID
3.2.3.2 非功能性特性测试

软件产品除了基本功能以外,还有很多功能之外的特性,叫非功能需求/服务质量需求,这些特性虽然没有体现为具体的需求,但它们对系统的稳定运行同样重要,尤其是效能测试和压力测试。主要的非功能性特性测试有:

  1. 压力测试,测试软件在负载情况下能否正常工作;
  2. 效能测试,测试软件的效能;
  3. 可访问性测试,测试软件是否向残疾用户提供了足够的辅助功能;
  4. 国际化/本地化测试;
  5. 兼容性测试;
  6. 配置测试,测试软件在各种配置下能否正常工作
  7. 易用性测试,测试软件是否好用
  8. 安全性测试
3.2.3.2.1 效能测试

效能测试用于验证设计负载内能否提供令用户满意的服务质量,这里的两个关键概念为“设计负载”和“令用户满意的服务质量”。从需求说明出发可以得到系统的正常设计负载,例如一个购物网站正常的设计负载是每分钟承受20次客户请求。而对于同样的购物网站,用户满意的服务质量可以定义为:每个用户的请求都能在2秒钟内返回结果。设计负载可以进一步按请求发生频率分类:

  1. 用户登录10%
  2. 用户查看某商品详情50%
  3. 用户比较两种商品10%
  4. 用户查看关于商品的反馈20%
  5. 用户购买商品,订单操作5%
  6. 所有其他请求5%

令用户满意的服务质量也可以细化为“请求”和“模块”两类。写操作可以适当放宽要求,比如5秒钟甚至更长。对于模块,需要用测试工具测试各种效能指标。和别的测试不同,效能测试要求控制变量,必须保证相同的机器和环境。现实环境分为静态数据和动态数据两类。静态数据包括用户记录,商品记录和运行一段时间后的交易记录。动态数据就是负载,可以分负载等级进行测试。效能测试还要回答用户这样一个问题:“如果系统变慢了,我是增加机器数量还是提高单个机器处理能力”,因此效能测试的结果应该成为“用户发布指南”的一部分,为用户发布和改进系统提供参考。

3.2.3.2.2 压力测试

压力测试的目的是验证超过设计负载的情况下是否仍能返回正常结果,有没有产生严重的副作用或崩溃。增加负载的方法有3种:沿着用户轴延长,沿着时间轴延长(模拟48小时高负载)和减少系统可用资源。一些平时不容易暴露的问题此时就暴露出来了,比如内存/资源泄露,平时被认为“足够好”的算法实现出现问题,以及进程/线程的同步死锁问题。

3.2.3.3 编写测试报告

测试工作的最后一步是编写测试报告,这里不需要写很多内容,只需简单的列举一些数字即可。对于每一个功能,收集下列数据:

  1. 有多少测试用例通过
  2. 有多少测试用例失败
  3. 有多少测试用例未完成
  4. 发现多少测试用例之外的bug

最后,把尽可能多的测试用例自动化,为下一个里程碑开发工作做好准备。

3.3 部署阶段(Deploying Phase)

终于,我们进入了迭代开发的部署阶段,在稳定阶段解决掉所有问题,成功发布产品之后我们要做的就是总结和回顾。要认真思考和反思项目中出现过的各种各样的问题,这就是下面要说的“事后诸葛亮会议”。

3.3.1 发布之后:事后诸葛亮会议

进行项目开发的复盘,把问题的根源暴露出来。如果你可以重新来过,什么方面可以做的更好?

  1. 为什么软件发布后用户报告了一个大问题?
  2. 为什么在测试阶段没有测出来?
  3. 为什么不通知PM/Test?
  4. 为什么不通知别人?

要一直追问到问题的根源和源头为止。

3.3.2 质量保障工作

如何衡量软件的质量?有三个要点可以考虑:

  1. 研发出符合用户需求的软件
  2. 通过一定的软件流程,在预计时间内发布“足够好”的软件
  3. 通过数据和其他方式展现所开发的软件是可维护和继续发展的

测试工程师可以从以下几个方面来量化分析上面提到的要点:

  1. 软件CC后DCR数量
  2. 用户的好评/差评
  3. CC后发现的bug数量
  4. 修复bug所需的平均时间
  5. 单位开发量(人*月)出现的重大bug的数量
  6. 测试用例的覆盖率
  7. 模块的复杂程度(工具检测并有量化结果)
  8. 代码的行数
  9. 文档的完备性和准确性百分率
  10. 文档的数量和复杂程度
  11. 有多少代码被重用
  12. 平均每天构建失败的次数
  13. 软件实现了多少功能点
  14. 软件能运行多久?平均初次错误时间和平均无故障时间

四. 总结

在软件开发的活动中,人是最关键的因素。因此软件工程流程的根基在于人的基本战术素养,本文首先总结了作为工程师必须具备的一些个人战术素养和技术成长的路径。然后,总结了《构建之法》这本书中讲到的各种流程,方法和行为模式。分为战略层和战役层两大部分。在战略层,我们要完成构思和计划两个阶段的任务。在构思阶段,我们要组建团队,研究和分析用户需求,产出团队+需求规格说明书+产品开发路线图。在计划阶段,我们要根据需求和路线图,整理Epic和用户故事,创建Product Backlog产品待办事项清单,测试人员需要撰写测试计划文档。然后创建Sprint Backlog冲刺开发待办事项清单,并根据用户故事做交互式系统需求设计,形成软件功能说明书,做系统概要设计,构造总体模型,功能列表,制定开发计划,进行功能设计,得到这次里程碑要发布的功能文档及其相关文档,各业务活动对应的时序图,更新的实体模型,类/方法/属性和功能实现计划,测试人员要撰写测试设计说明书。在战役层,我们要实施三个阶段的工作任务:开发,稳定和部署。开发人员拿到任务后,编写设计文档,然后开始编写功能代码和单元测试代码,测试人员要编写测试用例对代码进行测试。功能需求开发完毕后进入稳定阶段,进行迭代发布并解决影响发布的工作。测试人员需要进行场景/集成/系统测试,非功能性特性测试,编写测试报告。部署成功后,团队成员召开事后诸葛亮会议,进行总结复盘,测试人员进行质量保障工作的数据整理和总结。

以上,就是我对32万字《构建之法》和35万字《用户故事与敏捷方法》的总结和整理,只提炼和抽取了精华部分,感兴趣的一定要去读原著。

五. 参考文献

  1. 构建之法:现代软件工程
  2. 用户故事与敏捷方法
  3. The product backlog: your ultimate to-do list
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345

推荐阅读更多精彩内容