学生门户示例
一所社区学院希望开发一个学生门户,为学生提供在线服务。他们邀请了一名学生代表、部门工作人员以及门户管理员成员组成团队,参与学生门户开发项目。以下是第一次会议的会议纪要。
利益相关方会议
议程
- 建议学生门户的功能
- 讨论所提议功能列表的可行性
- 对功能进行优先级排序,分为核心功能、下一阶段功能、可选功能等……
史蒂夫(开发团队):欢迎……我们希望让会议更加高效且富有成果。当我们提出希望拥有的功能时,可以使用以下标准表达方式:谁(你是谁),想要什么功能(你想要什么),以及为什么想要它……这些功能将作为用户故事记录下来,作为本项目中沟通的工具。
随后,我们从不同利益相关方中头脑风暴出一份用户故事列表:
学生代表:注册课程、缴纳学费、查看时间表、编辑时间表、查看成绩单、退课……
学术代表:添加课程信息、编辑课程信息、删除课程信息……
门户管理员:备份课程信息、编辑学生账户状态
现在,我们有一堆卡片,可以将其整理成行和列

优先排序所提议的功能
开发团队代表:我们有一份已优先排序的用户故事列表,将在接下来的迭代中实现。为此,我们需要对每个用户故事进行深入分析,以获取更多信息。让我们来逐一分析第一个核心功能——“注册课程”用户故事

我们从最终用户处获得了以下额外信息:
- 学术代表:学生必须是全日制注册学生
- 管理员:学生必须提供正确的账户凭据才能登录
- 学术代表:该课程未满员
- 学术代表:必须满足课程先修要求
- 管理员:选定添加到时间表的课程不能与其他课程的时间段冲突
- 开发人员:在目标系统中,你希望将此功能与其他哪些功能组合成一个列表?
- 学生:退课、查看时间表、编辑时间表。
用户故事的三个C
好的用户故事远不止是一句陈述。一个标准的用户故事包含三个部分,通常被称为三个C。每个用户故事的第一个“C”应遵循标准化格式:作为[角色],我想要[做某事],以便[获得好处],这是放入卡片中的用户故事最基本内容。第二个“C”是对话(Conversations),代表最终用户、项目负责人和开发团队之间的讨论内容。在这些对话中,会记录口头讨论,以及其他有用信息,如邮件、线框图或项目相关的其他内容。用户故事的最后一个“C”是确认(Confirmation),即验收标准,用于确认用户故事已正确实现并成功交付。
让我进一步详细说明如何开发用户故事的确认部分。在这里,我们使用最著名的模板——Gherkin,它采用Given-When-Then公式来指导用户故事验收测试的编写:
- (Given.. and) 一些上下文
- (When.. and) 执行一些操作
- (Then.. and) 执行一些操作
像Cucumber和Jbehave这样的测试框架鼓励使用Given/Then/Then模板进行自动化测试,尽管它也可以纯粹作为一种启发式方法使用,而无需依赖特定工具。
让我们收集“注册课程”用户故事的所有信息,并将其放入3Cs格式中:

现在,让我们将这些信息输入到UeXceler中,其中包括我们之前开发的转换和确认部分。

将史诗拆分为用户故事
如果我们进一步详细研究“注册课程”用户故事,可能会发现它太大,无法放入一个冲刺中。我们可以将其视为一个史诗(更大的用户故事),可以拆分为一组相关的较小用户故事。我们可以将史诗拆分为任务,我称之为水平拆分,或者另一种方式是将史诗拆分为场景,称为垂直拆分。
水平拆分
传统上构建大型功能的方法是将其分解为在架构层面上需要完成的工作。例如,模型-视图-控制器(MVC)或客户端-服务器架构,以便为系统架构实现关注点分离,随后将其细化为多层架构,如GUI、控制逻辑、对象模型、对象关系映射、数据库层等。多层架构包含的内容相当多,我这里仅列举其中几个:
- 使我们能够在某一架构层面上发展出高水平的专业能力
- 其他应用程序能够复用由你的各层提供的功能。
- 你可以将各层分布在多个物理层级上。这可以显著提升你的应用程序性能(有时)、可扩展性和容错能力。
- 由于各层之间的耦合度较低,你的应用程序维护起来更加容易。
- 为你的应用程序添加更多功能变得更加容易。
- 各层使你的应用程序更具可测试性。
基于多层架构的成功实现有很多,例如Ruby on Rails和基于Web服务的架构。
用户故事与水平拆分
虽然多层架构为我们的系统带来了诸多好处,但在与用户故事方法结合使用时也存在一些缺点。由于我们需要等待每个人完成各自的部分后再进行集成并确保其正常工作,因此反馈周期往往非常缓慢,具体取决于功能的大小。术语“水平切片”指的是将这种架构层方法作为大型功能分解的主要手段。
垂直拆分
为了加快反馈循环,我们可以将一个史诗拆分为多个用户场景,这些场景贯穿各个架构层。我们可以将几乎任何功能拆分为多个切片,使得所有部分的构建、集成和测试最多只需几天时间即可完成。每个切片都包括在架构层中需要完成的工作,以及为使其可发布而可能需要进行的测试和集成工作。
通常,水平拆分会将功能在架构组件层面拆分为用户故事或任务。例如:前端UI、数据库或后端服务。而垂直切片则会产生可运行、可演示的软件,能够带来业务价值。因此,垂直拆分提升了团队在每个冲刺中交付潜在可发布产品增量的能力。想象一下,你有一块夹有奶油、巧克力、水果和蛋糕的蛋糕。如果你水平切开,你的朋友只会得到一块蛋糕、巧克力、奶油或水果。我相信你的朋友们更希望得到包含所有层次的一小块。只得到单一层次的蛋糕,无法让他们品尝到整块蛋糕的真实味道。更友好的做法是制作垂直切片(即所需的价值)。

按目标进行史诗的水平拆分
回想一下,在利益相关者会议中我们对“注册课程”用户故事的讨论,该功能最初由学生(主要角色)提出,并得到了其他利益相关者的支持。当我们深入探讨用户故事的细节时,发现其他作为支持角色的利益相关者保留了相当多的信息。
实际上,有些学生可能并不在意,甚至不想接受“支持角色”设定的限制。例如,学生忘记密码并连续输入错误三次后,他/她可能不想经历重置密码的过程,但仍自信可以再尝试几次;或者学生不希望对图书馆书籍数量或借阅期限设置限制,等等。那些希望为所提议的功能设定业务规则和约束作为其用户目标的人,正是在用户故事中担任支持角色的人。如果未对所提议的功能施加业务规则和第二目标,该功能就不应运行。如果我们根据次要角色的目标将史诗进行水平拆分,形成一组用户故事,就可以同时满足次要角色的目标,使用户故事可测试,并直接从正确的人那里获得反馈。

让我们分析转换和确认部分的信息,看看如何将更大的故事“注册课程”拆分为一组相关的用户故事。
- 学生必须是注册学生,否则将无法获得新学生通知中提供的凭证。如果他已经收到通知但账户尚未激活,则需要在线注册新账户(标记为红色圆圈1)
- 如果我们进一步深入分析登录流程,就会知道,如果学生连续三次输入凭证错误,就需要进入“重置密码流程”(标记为红色圆圈2和3)

通用用户故事区
让我们在UeXceler中创建这些用户故事:
登录账户的用户故事已创建在通用用户故事组件下。除了登录相关的用户故事外,还有两个相关用户故事也正在通用用户故事组件下创建,分别是“重置密码”和“创建新学生账户”,原因如下:
- 如果学生连续三次输入账户凭证错误,将触发“重置密码”用户故事;
- 如果一名新学生尚未注册学生账户,他/她可以在登录界面触发“创建新学生账户”用户故事。
- 作为一名学生,我希望登录学生门户,以便……
- 作为门户管理员,我希望验证登录者是否为注册学生,以便……
- 作为课程负责人,我希望在允许选定课程加入学生课表之前验证其适用性,以便……
我们可以认为这三个用户故事是从“注册课程”这一史诗中横向拆分出来的,但这些用户故事将实现某些支持角色的目标,你可以从中获取反馈并加以确认。如果前提条件未满足,主用户故事将完全失去意义。
你可以看到,我们将所有这些用户故事放在通用用户故事组件下,原因是它们很可能与同一调用页面中的其他相关功能(用户故事)共享相同的前置条件,例如“查看课表”、“编辑课表”、“退课”等。
用例组件
如上图所示,还有三个被红色圆圈标记为4、5和6的项目。它们是“注册课程”这一史诗的备选场景。只要这三个条件中的任意一个不成立,就会产生相应的备选场景,并可表示为拆分后的用户故事。也许我们可以选择横向或纵向拆分,但难道垂直拆分总是更优吗?不一定,这实际上取决于具体情况。世界上没有一种放之四海而皆准的解决方案,我们需要考虑哪种方法更适合你的实际情况。让我进一步详细说明一下。
例如,如果你打算将主用户故事分配给一名开发人员,而将登录、注册新账户和重置密码这三个用户故事分配给另一名开发人员,你可能会更倾向于让负责主用户故事的开发人员在当前迭代中先开发正常流程场景,并在后续迭代中由同一开发人员逐步开发备选场景(如果时间允许)。但如果时间较紧,同时你又觉得让同一开发人员承担整个史诗任务过于繁重,那么可以将其横向拆分为多个任务,分配给多名开发人员并行处理。
将史诗拆分为场景
如果我们考虑史诗确认部分所描述的场景细节,就能很容易地找到一组相关用户故事的垂直切片。
- 作为课程负责人,我希望在允许选定课程加入学生课表之前验证先修要求,以便……
- 作为课程负责人,我希望在允许课程加入学生课表之前检查课程是否可选,以便……
- 作为课程负责人,我希望在允许选定课程加入学生课表之前验证学生的时段可用性,以便……
将史诗拆分为任务
如果我们希望将史诗分解为更小的任务,以便在同一个迭代中并行开发,如下所示:
- 检查课程名额状态
- 检查课程先修要求
- 检查时间时段
现在,你可以根据自己的选择将史诗拆分为敏捷开发过程中的用户故事。你可以看到,“注册课程”用户故事及其相关用户故事都放在一个用例组件(你的史诗)中,该组件也称为“注册课程”,用作占位符,以容纳所有主用例以及从主用例中拆分出的其他用例。
