04 | 接到需求任务,你要先做哪件事?

本文探讨了程序员小李与产品经理小王关于单点登录需求的分歧,揭示了需求描述不清导致的问题。通过介绍用户故事和验收标准,强调了在软件开发中明确边界和细节的重要性,提倡以用户故事的形式来确保需求完整性和减少误解。

我们书接上文,继续讲程序员小李的故事。这次小李接到一个新的需求,让他开发一个单点登录的服务,经过几天的奋战,他顺利地写完了所有的代码。正好产品经理小王路过他身边,顺便问了他一下。

小王:单点登录做得咋样了?
小李:做完了,我给你演示一下。

小李演示了一遍自己做的功能,小王看上去很满意。

小王:不错。不过,怎么没有支持验证码?
小李:为什么要做这个?
小王:这不就是登录的一部分吗?
小李:哪里规定要做验证码了?
小王:现在做登录哪有不用验证码的?

我想你已经嗅到了双方谈话的火药味,这个时候如果双方都不能很好地控制自己的情绪,那接下来一场体力的较量可能就一触即发了。

为什么双方会有这么大的分歧呢?其中一个重要的原因是,开始实现这个需求之前,任务双方都没有清晰地定义好边界,没能把需求描述清楚。

需求描述的问题

在软件开发中,程序员做什么一般都由需求来定义。我们都知道,需求是软件开发的一个重要组成部分,但你可能并没有仔细想过,不同的需求描述方式,可能会影响我们程序员对需求的理解。

因为信息的传递是会衰减的,你不可能把你理解的信息100%传递给另外一个人,而这中间,如何传递,也就是如何描述将直接决定衰减的比例。

很多公司的软件开发模式是基于功能列表的,这个列表“规定”了程序员要做的功能,各个组从产品经理那里领来开发列表,然后“照单抓药”开始写代码。但是,通常这种功能列表只是一些简单的描述,你并不能看到全局。

很多团队的一个状态就是,程序员们都知道要开发的功能是什么,但这个功能是谁在什么样的场景下使用的,很多人却回答不上来。如果你去问他为什么要开发这个功能,他通常会说:这是功能列表里规定的。

这种功能列表式的需求描述方式,将一个完整的需求敲成了碎片。 只有所有功能全部开发完成,对接在一起的时候,才是“破镜重圆”的时刻。

也就是说,不到最后一刻,大多数人并没有一个完整的图景,这就相当于看不到完整的“终”。顺着这个思路做下去,你会在最后关头遇到许多意料之外的问题,其结果必然是手忙脚乱。

根据这种基于功能列表的需求描述,每个组在安排工作的时候,都会按照自己的理解进行功能排列。

所以,当你的组完成了一个功能时,这个功能却可能上不了线,因为你还要依赖于其他组的工作,而这个组不巧,却刚好把相关的功能开发排在了后面。

这还只是两个组之间有依赖的情况,如果需要多个组协同,可以想象,状况会多么糟糕。

所以,当我们对产品经理说“时间不足,砍掉一些需求吧。”得到的答案肯定是,“对不起,做不到,因为需求已破碎,没办法调整。”

因此,一些新的需求描述方式也就应运而生,这其中,用户故事(User Story)是我最喜欢的一种方式。它是站在用户的角度来描述了一个用户希望得到的功能,关注用户在系统中完成一个动作需要经过怎样的路径。既然它是“故事”,它就需要是一个完整的场景,可以讲述出来。

“用户故事”有什么用?

我们先来以用户密码登录为例,看看用户故事长什么样?一个完整的用户故事大致包含以下几个部分:

  • 标题,简要地说明这个用户故事的主要内容,比如:注册用户使用用户名密码登录。

  • 概述,简要地介绍这个用户故事的主要内容,一般会用这样的格式:
    As a (Role), I want to (Activity), so that (Business Value).
    意思就是:作为一个什么角色,要做什么样的事,以便达成一种怎样的效果。其中最重要的是,告诉别人为什么要做这件事,虽然只有一句话,却往往是很多人欠缺的思考,只知做,不知为何做。
    举个概述的例子:作为一个注册用户,我想要通过用户密码登录,以便我可以使用注册用户才能够使用的服务。

  • 详述,详细地描述这个用户故事的完整流程,我们会把操作流程、用户界面等信息都放到这里。
    比如:用户使用正确用户名和密码登录,就可以登录成功;如果密码不正确,则登录页面提示用户“用户名密码不正确”。基本上,看到这个部分,程序员就可以在心中描绘出这个用户故事的样子了。
    超出范围的部分,比如:第三方登录不在范围内,这个部分主要是限定人们不要进一步发散。

  • 验收标准,这个部分会描述一个正常使用的流程是怎样的,以及各种异常流程系统是如何给出响应的,这是程序员常常会欠缺的思考。它会把详述中很多叙述的部分变成一个具体的测试用例。比如,下面我给出的两个验收用例:
    正常场景:给定一个注册用户张三,其用户名是zhangsan,密码是foobar,当张三使用zhangsan 和 foobar 登录系统时,可以成功登录,登录成功后,跳转到用户中心。
    异常场景:给定一个注册用户张三,其用户名是zhangsan,密码是foobar,当张三使用zhangsan 和 wrong 登录系统时,登录失败,在登录页面上提示“用户名密码不正确”。

在前面的例子中,小张和小王之所以会对需求是否完成产生分歧,是因为大家对于需求完成的定义不同。对于这种情况,我们能怎么办呢?

这个模块的主题是“以终为始”,现在你看到了用户故事是如何描述需求的,你或许已经知道我要说什么了,没错,这里非常关键的一点就是“验收标准”。很多人学习用户故事,认为最重要的是记住“As…, I want to …, so that …”这样的需求描述方式。

在我看来,无论采用哪种需求描述方式,这部分也都是能说清楚的。那我们要从用户故事中学到什么呢?我认为就是用户故事的关键点:验收标准,它可以清晰地定义出需求边界。

验收标准非常重要的一环是异常流程的描述。大部分程序员都擅长解决正常流程,而异常流程则是最容易忽略的,也是产生扯皮的关键环节。既然容易扯皮,我们就在一开始把它定义清楚。怎么才算做完需求呢?验收标准说了算。

采用用户故事之后,我经常在写完了主要流程之后,再去看一下验收标准,为自己的开发查缺补漏。因为我知道,那是标准,达不成就不算任务完成。

当我们说自己开发完成,可以交给测试人员测试时,我们需要照着验收标准给测试人员演示一遍,证明我们的系统确实能够跑通。这之后,测试人员才会把系统接手过去,做更系统的测试。

验收标准给出了这个需求最基本的测试用例,它保证了开发人员完成需求最基本的质量。如果你了解 BDD(Behavior-Driven Development,也就是“行为驱动开发”),就可以按照验收标准中给出的内容编写验收测试用例了。

在实际工作中,许多产品经理把需求交给开发人员之前,很多细节是没想清楚的,那种功能列表式的需求常常只包含了正常路径,那些缺失的细节就是在后续的过程中,由开发人员补全的。用户故事就是一种固定的格式,让他们把这些应该想清楚的问题想清楚。

如果你的团队采用用户故事的格式进行需求描述固然好,如果不能,在功能列表中,补充验收标准也会极大程度地改善双方协作的效率。

你的角色

或许你会有这样的疑问,如果产品经理通过用户故事的方式,将需求实现细节都描绘得清清楚楚,那我们程序员的发挥空间在哪里?请注意,验收标准所给出实现细节应该是业务上的,程序员在这种问题上思考才是真正意义上的浪费时间,我们的发挥空间应该是在技术实现上。

然而,在现实情况中,很多团队做不到这种程度。

你会发现,我们在开发中之所以会“丢三落四”,很重要的一个原因是,在开发一个功能特性的时候,因为一些环节的缺失,我们不得已扮演了很多的角色,其中之一就是产品经理。你是一个专业的程序员,但大多数情况下,你却只是一个业余的产品经理,“丢三落四”就在所难免了。

或许你会说,我在一个小公司工作,公司没那么多人,没有专门的产品经理,只有我们几个“全世界都缺”的程序员,需求都是老板扔给我们的,谁来帮我们写验收标准呢?

没办法,答案只能是你自己。虽然你名义上是程序员,但当拿到一个需求的时候,你要做的事不是立即动手写代码,而是扮演产品经理的角色,分析需求,圈定任务范围。相信我,事前分析绝对比你拿一个写好的系统给老板,而他却告诉你这不是他想要的,好太多了。

另外我想提醒你注意的是,扮演不同角色的时候,我们的思考模式是不同的。还是以开发用户名密码登录为例,你想到的可能是:输入正确的用户名和密码可以正常登录,输入错误的用户名和密码不能登录,并且给出提示。

如果你只扮演开发人员的角色,想到这些就算不错了。但如果你扮演的是产品经理的角色,会从产品的角度进行思考,也就会看到不同的内容,比如:

  • 登录是否需要验证码
  • 是否需要第三方登录
  • 用户名和密码的长度在系统内是否有限制
  • 密码是否需要满足一定的规则
  • ……

我知道,如果让你来填写,这个列表会更长。可能这并不是我们都需要完成的功能,但站在分析的角度,这都是我们要考虑的问题,一个登录功能,绝不仅仅是用户名和密码校验那么简单的。我们能想到这些,仅仅是因为我们正在扮演一个不同的角色。

所以,如果你要兼顾开发人员和产品经理两个角色,建议你先扮演好产品经理的角色,多花点时间把验收标准制定好,再回到开发人员的角色上去写代码。毕竟,最好维护的代码是没有写出来的代码。

总结时刻

需求,是软件开发中的一个关键环节,一旦需求理解出现问题,势必会造成大量的浪费。传统的功能列表只是简单罗列了要实现的功能,丢失了大量的上下文,会导致团队成员对于需求“只见树木不见森林”。

而在比较大的团队中,更是会将一个功能分拆到多个小团队中,每个人看到的只是功能碎片。于是,后来产生了其他的需求描述方式,比如用例和用户故事。

在实际的开发过程中,大量的分歧来自于对“需求完成”的定义。当我们把“以终为始”的原则应用在需求领域中,就会注意到,用户故事有一个非常重要的组成部分是验收标准。

验收标准不仅仅描述出了正常流程,也会关注到异常流程的处理,它也是我们验收测试用例的起点。一旦事先定义好验收标准,大量的扯皮工作就随之烟消云散了。

理解了验收标准的作用,即便我们不使用用户故事来定义需求,依然可以把用户故事中的关键点应用到自己的实践中,在功能列表的每个功能定义中,增加验收标准。

如果今天的内容你只能记住一件事,那请记住:在做任何需求或任务之前,先定好验收标准。

最后,我想请你回想一下,在实际工作中,你是如何澄清你的需求,或者因为需求不清晰给你造成了哪些困扰?欢迎在留言区写下你的想法。

感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

<think>首,用户的问题是:“我想在不了解C++的情况下编写闹钟程序的设计文档 请问如何在没有C++编程基础的情况下撰写一个闹钟程序的设计文档”。这涉及到设计文档的撰写,而不需要编程知识。 系统级指令要求: - 所有行内数学表达式使用$...$格式。 - 独立公式使用$$...$$格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 在回答中引用的段落末尾自然地添加引用标识,例如[^1]。 用户可见层要求: - 回答结构清晰,帮助用户逐步解决问题。 - 尽量保证回答真实可靠。 参考站内引用: - 引用[1]: 关于VC++和MFC实现的闹钟程序源码分析。 - 引用[2]: 关于mutex和volatile修饰的讨论。 - 引用[3]: Qt的API丰富性。 - 引用[4]: CMainFrame中添加头文的示例。 - 引用[5]: Qt5编程创建闹钟程序的步骤。 这些引用是关于C++编程的,但用户明确表示“在不了解C++的情况下”,所以我不能假设用户有C++知识。我需要基于设计文档的撰写来回答,而不涉及编程细节。 核心思路: - 设计文档是关于软设计的描述,包括功能、界面、模块、数据流等,不涉及具体代码。 - 用户想写一个闹钟程序的设计文档,但不懂C++,所以应该使用通用术语,专注于设计方面。 - 我应该提供步骤或指南来撰写设计文档。 结构清晰: - 逐步引导:解释设计文档是什么,然后分步说明如何写闹钟程序的设计文档。 - 包括关键部分:需求分析、功能模块、用户界面、数据设计等。 生成相关问题: - 在回答末尾,添加相关问题,如示例所示。 引用: - 自然地添加引用标识。既然用户提供了引用,我应该在相关内容中引用它们。但用户引用是关于C++实现的,而用户不懂C++,所以我需要小心引用。 - 例如,如果提到Qt或MFC,我可以引用相关部分,但要强调这些是可选实现方式。 - 引用[5]提到Qt创建闹钟程序,我可以用于设计参考。 - 引用[1]有时间处理示例,我可以用于设计中的数据部分。 回答大纲: 1. **介绍设计文档的概念**:解释什么是设计文档,为什么重要。 2. **步骤撰写设计文档**: - 需求分析:定义闹钟程序的功能。 - 系统架构:描述整体结构。 - 功能模块:分解为子模块如时间设置、闹钟管理、提醒机制。 - 用户界面设计:描述界面布局。 - 数据设计:定义数据结构(如闹钟时间、重复设置)。 - 非功能性需求:如性能、可靠性。 3. **不依赖编程语言**:强调设计文档是语言无关的,使用伪代码或流程图。 4. **参考示例**:引用提供的引用来说明某些点,但保持通用。 5. **总结**。 6. **生成相关问题**。 数学表达式: - 用户问题中没有明显的数学,但如果有计算如时间间隔,我可以使用行内数学表达式。例如,时间计算用$ \Delta t $ 表示时间差。 - 在独立公式中,如果有必要,但可能不需要。 确保中文回答。 最终输出: - 回答主体。 - §§相关问题§§部分。</think>### 如何在不了解C++的情况下撰写闹钟程序的设计文档 撰写闹钟程序的设计文档,不需要编程知识(如C++),因为设计文档关注的是软的功能、结构、用户交互和逻辑流程,而不是具体代码实现。设计文档是软开发的蓝图,帮助团队理解需求和设计方向。下面我将一步步引导您完成这个过程,确保文档清晰、实用。整个过程基于通用软设计原则,您可以使用工具如UML图、流程图或伪代码来描述设计,而不涉及任何编程语言细节。 #### 步骤1: 理解设计文档的核心内容 设计文档通常包括以下部分,您可以根据闹钟程序的特点调整: - **需求分析**:描述程序必须什么(功能需求)和约束条(非功能需求)。 - **系统架构**:整体结构图,展示模块之间的关系。 - **功能模块设计**:分解程序为独立模块(如时间设置、闹钟管理),用文字或简单图表描述。 - **用户界面设计**:界面布局草图或描述(如按钮、显示区域)。 - **数据设计**:定义关键数据(如闹钟时间、重复设置),可以用表格或伪代码表示。 - **交互流程**:用户操作流程(如设置闹钟、触发提醒)。 - **非功能性需求**:性能、可靠性等(如响应时间在$ \Delta t \leq 1$秒内)。 设计文档应使用自然语言和通用符号(如流程图),避免代码。例如,描述时间计算时,可以用数学表达式表示时间差:$ t_{\text{闹钟}} - t_{\text{当前}} $,但不需要实现公式。 #### 步骤2: 撰写闹钟程序设计文档的关键部分 以下是一个闹钟程序的设计文档框架,您可以根据实际需求填充内容。每个部分都保持语言无关。 1. **需求分析** - **功能需求**: - 设置闹钟时间(包括小时和分钟)。 - 支持重复设置(如每天、每周)。 - 闹钟触发时播放声音或显示提醒。 - 用户可启用/禁用闹钟。 - 查看当前闹钟列表。 - 示例需求:用户设置时间后,程序应在指定时间点触发提醒,误差不超过$ \pm 1$秒[^1]。 - **非功能需求**: - 可靠性:闹钟必须准时触发,失败率低于0.1%。 - 易用性:界面简单直观,适合非技术用户。 - 性能:启动时间小于2秒,内存占用低。 - **约束**:假设在桌面或移动设备运行,但无需指定平台(如Windows或Qt)。 2. **系统架构** - 使用框图描述整体结构: - **输入模块**:处理用户设置(如时间输入)。 - **时间管理模块**:监控系统时间并与闹钟时间比较(逻辑:$ t_{\text{当前}} \geq t_{\text{闹钟}} $ 时触发)。 - **提醒模块**:触发声音或视觉提醒。 - **存储模块**:保存闹钟设置(如文或内存)。 - 架构图示例(伪代码描述): ``` 用户 -> 输入模块 -> 存储模块 时间管理模块 <- 系统时钟 时间管理模块 -> 提醒模块 ``` 3. **功能模块设计** - **时间设置模块**: - 功能:允许用户输入时间(HH:MM格式)。 - 设计:界面包括数字输入框和确认按钮。数据验证:时间必须在$ 00:00 \leq t \leq 23:59 $ 范围内。 - **闹钟管理模块**: - 功能:添加、删除、启用/禁用闹钟。 - 设计:使用列表存储闹钟条目。每个条目包括时间、重复标志(如布尔值表示是否重复)。 - 引用示例:类似Qt闹钟程序的数据结构,但无需代码[^5]。 - **提醒触发模块**: - 功能:当系统时间匹配闹钟时间时,执行提醒动作。 - 设计:轮询机制(每秒检查一次时间),或驱动(使用系统定时器)。触发条:$ t_{\text{当前}} = t_{\text{闹钟}} $。 - 可靠性:添加错误处理(如时间同步失败时重试)。 - **用户界面模块**: - 功能:显示当前时间、闹钟列表和控制按钮。 - 设计:草图或文字描述,例如: - 主屏幕:当前时间显示(大字体),下方为闹钟列表和“添加”按钮。 - 设置页面:时间选择器、重复选项(下拉菜单)。 4. **数据设计** - 定义核心数据结构,用表格或伪代码: - **闹钟条目**: | 字段 | 类型 | 描述 | |------|------|------| | id | 整数 | 唯一标识符 | | time | 字符串 | 时间格式 "HH:MM" | | repeat | 布尔值 | 是否重复(true/false) | | enabled | 布尔值 | 是否启用 | - 数据流:用户输入 -> 存储为条目 -> 时间管理模块读取 -> 触发提醒。 - 示例:参考时间处理概念,如计算时间跨度(例如,3天20小时表示为$ 3 \times 86400 + 20 \times 3600 $秒),但无需实现[^1]。 5. **交互流程** - 用户操作序列图(文字描述): 1. 用户打开程序,主界面显示。 2. 点击“添加闹钟”,输入时间并设置重复选项。 3. 程序保存数据到存储模块。 4. 时间管理模块持续比较系统时间和闹钟时间。 5. 当匹配时,提醒模块激活(播放声音)。 6. 用户可点击“停止”按钮关闭提醒。 6. **非功能性设计** - **性能**:闹钟检查频率优化(如每秒一次,避免高CPU使用)。 - **可靠性**:处理系统休眠或时区变化(如使用UTC时间)。 - **可扩展性**:设计模块化,便于未来添加功能(如多闹钟支持)。 #### 步骤3: 撰写技巧和工具 - **语言和格式**:使用中文撰写,避免技术术语。用Markdown或Word组织文档,添加图表工具(如draw.io)绘制流程图。 - **不依赖编程**:用伪代码描述逻辑,例如: ``` // 伪代码示例:检查闹钟触发 while (程序运行): 当前时间 = 获取系统时间() for 每个闹钟 in 闹钟列表: if 闹钟.enabled and 当前时间 >= 闹钟.time: 播放提醒() ``` 这来自通用设计模式,不涉及C++[^3]。 - **参考实现但不复制**:您提到的引用(如Qt闹钟程序)提供了设计灵感,例如Qt的API丰富性支持模块化设计[^3],但您只需关注高层概念。例如,引用[5]描述了Qt闹钟创建流程,您可以借鉴其界面设计思路,而不需写代码[^5]。 - **验证设计**:分享文档给他人审阅,确保逻辑完整。真实案例:许多开源项目(如Android闹钟App)的设计文档公开可用,可参考其结构。 #### 总结 撰写闹钟程序设计文档的核心是专注于“什么”而非“怎么”。通过以上步骤,您可以创建一个详细的设计文档,覆盖需求、架构、模块和交互,完全不需要C++知识。设计文档完成后,开发人员可基于此实现代码(如使用Qt或MFC),但那是后续阶段。保持文档简洁、一致,并优描述用户价值。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值