需求变化是唯一不变的事
需求变化是有害的
星期五下午 4 点,陈旭已经差不多干完了计划的活儿,心理盘算着明天和大学室友老汉约球的事儿。 在面试的那段时间,陈旭一度很羡慕老汉,同样大学毕业,陈旭长了张娃娃脸,而他的头顶已经有「资深」程序员的特征了,于是拿到的 Offer 都比陈旭多。 他俩先后找到了心仪的工作,从那以后,虽然还在同一个城市,但他们已经好久没见过面了,约了几次,不是临时陈旭有事,就是老汉有事。 陈旭心想这次终于约上了,一定要跟老汉痛痛快快地打一场球。
“叮叮......” 耳机中传来办公 IM 的声音,将陈旭从思绪中拉了回来。 “各位伙伴,两个事情跟大家同步一下,很遗憾 #602 需求取消,#739 需求有紧急变更,需要赶在下周三的窗口投产,按照惯例,周一要封版进行两天的回归测试,虽然有挑战,但我相信大家合理安排工作时间,奋力拼搏,一定可以战胜困难!”
“这不摆明让 LZ 加班吗?!” 陈旭心里大骂,还不解气,狠狠踹了主机箱一脚。主机发出「滋滋」声表示抗议。 “都特么什么年代了,还在用 6G 内存的电脑,连固态硬盘都没有...”
领导都暗示的这么明显了,显然晚上和明天都要献给工作了。 陈旭开始怀疑当初为什么要填报计算机专业,当初「用代码改变世界」的梦想还能实现吗? 陈旭只能给老汉发个微信说要加班,能不能改到周日见面。还好老汉也是程序员,表示很理解陈旭的处境,答应了他的提议。
陈旭知道每一次需求紧急变更都是噩梦的开始,加班加点已经让陈旭头昏脑涨,没有时间也没有精力去思考优雅的设计方案,代码规范也只能靠边站。 在时钟即将指向凌晨 12 点时,陈旭终于按新需求改完了所有代码,启动应用,简单跑一下,修复了几个明显的错误。 完整的测试还是交给测试团队的同事明天测吧。如果专业的测试都测不出来,那可怪不到我头上。 陈旭敲下回车,提交代码,伸个懒腰,心满意足地回家了。
周六早上 11 点,陈旭嚼着鸡蛋灌饼来到公司,打开电脑,等待电脑启动的时候,陈旭习惯用手机浏览了一会儿关注的技术公众号,每天层出不穷的开发框架让陈旭无所适从,工作中重复的增删改查让他对未来充满危机感,知识不成体系,什么都会一点儿,什么都谈不上精通,到底该学什么才能保值增值? 懒得去想了,陈旭放下手机,在早已启动好的电脑上登录「项目协作系统」。打开「我的工作台」,测试人员已经指派了几个 Bug 给他。 陈旭开始和 Bug 玩捉迷藏游戏,调试,定位,修复,重新部署,反复几次,终于修完了所有 Bug,然后代码已经不忍直视了,有一瞬间,陈旭很想重构一下,但一想时间这么紧,改完我还得测试一遍,关键测试数据还这么难造,而且就算我测完,测试同事也不放心,还得重新测一遍,陈旭可不想当那个坏人。他瞅一眼显示器下面的《重构》和《代码整洁之道》,又瞅一眼刚刚改完的一屏都无法安放的长方法,感叹理想和现实的距离就像哈尔滨到三亚那样远。
陈旭觉得加班,缺陷增多,代码质量腐坏,这一切的源头都是因为需求变化,陈旭很想知道如何才能脱离苦海,但是身边又没有人可以讨论这个话题,于是陈旭想起最近关注的一个公众号「小波老西」,他是一位程序员,敏捷教练,还是三个孩子奶爸。据说 30 出头的年纪已经年入百万,最让人羡慕的是,发际线还依然坚挺。 虽然陈旭不知道敏捷教练具体是干什么的,但陈旭这几年也参加过几次「敏捷软件开发」的培训,大概是缩短交付周期,提升研发效能,改进产品质量。 好像他周六在郑州敏捷之旅有个分享,要不去听听看,说不定能解决疑惑呢?
于是陈旭打开「小波老西」的公众号,留言提问: “小波老西,你好,为什么需求老是变?程序员要如何应对需求变化呢?”
第二天睡到 10 点,匆匆赴约,和老汉到了球场,工作关系,久坐不动,体力也大不如前,几个半场打下来,喘的像狗一样,已经蹦不过年轻人了,趁兴而去,败兴而归。 他们决定去喝点小酒,谈谈心,一起回忆大学时光,一起感叹曾经的精神小伙,如今已经有了大叔模样。 饭桌上谈起需求变更的问题,老汉也是深受其害,无能为力。
下午 4 点,陈旭回到家中,收到微信一条微信通知,打开一看,难道小波老西这么快就回复了?
需求变化的常见原因
你好呀,谢谢你的提问,需求变化是程序员面对的最大的苦恼了,我也曾经深受其害。 值得开心的是你已经意识到了其危害,并作出积极的努力去应对。 我分享一点我的想法,希望能对你有所帮助。 首先要接受一个事实就是「这个世界上唯一不变的就是需求一直在变」。 因为抱怨需求变化是没有任何积极作用的,而且这样做会让你把责任都推给别人,就是说我没问题,都是业务分析师的问题,是产品经理的问题,是用户的问题。他们说不清楚自己要什么,甚至自己都没有想清楚自己要什么。 对了,他们就说不清楚,甚至自己也没想清楚。
前不久,我老婆的堂兄,也就过年一起吃过几次饭,跟其他兄弟一起灌过我酒,平时没什么交集,给我发来一条消息:
小波,什么时候有空,我想做抓取网页上的数据的小程序,网页地址:https://xxxxxx
很多程序员的思维方式是,一收到需求,就开始想怎么去实现,开始考虑技术细节,然后你提出来的疑问,需求人员根本不懂,就没法聊了。 我收到这个需求,我首先判断这是一个「功能需求」,做一个抓取网页数据的小程序。 我要先挖掘他背后的「用户需求」,而不是立马开始去想这个小程序要用什么技术去实现。
这种「一句话需求」,如果你拿着这种需求就开始去干,就会遇到很多问题,然后你想去确认需求,又发现对方很难有时间跟你及时澄清细节。就像鸽子飞到广场上拉了一泡屎就飞走了,所以我们称其为「鸽子需求」。
这时我就给他打语音电话了,我为什么要打电话而不是发文字消息呢? 程序员都有个毛病:
- 能文字沟通就不语音沟通
- 能语音留言就不电话
- 能电话沟通就不见面 越不擅长沟通的程序员,越喜欢用异步的沟通方式,殊不知这样沟通效果最差,也无法锻炼到沟通能力。 我打过电话去,首先要搞清楚几个问题:
- 要完成什么工作?
- 完成工作的过程中遇到了什么问题?
- 没有程序的时候,是怎么解决这个问题的?
- 有了这个程序,能带来什么好处?
通过这些问题,我知道了他是在采用一种「小仓位 xxx」的短线投资方式,需要选股,这个网站提供了一些数据,他要把这些数据手工添加到一款股票软件里面去分析。
用户最了解的是自己的场景,任务和问题。不了解的是技术能做到什么,能如何帮助他们解决问题。 所以他们提出的需求往往是不靠谱的。 如果你按照他提的需求去做,你就是个工具人。最后做出来的东西,不满足他的需要。 这就是为什么需要产品经理,业务分析师,在程序员和用户中间,架起一座桥梁。 把用户的「一句话需求」细化成可以落地的「功能需求」。
但是,我们要知道:
- 你理解的不一定是业务分析师想表达的
- 业务分析师理解的不一定是产品经理想表达的
- 产品经理理解的不一定是用户想表达的
- 用户表达的也不一定是用户真正需要的
2015 年我创业时,团队里有一位工程师,很有产品思维,每一个需要都关注用户价值,但有点过犹不及,一定要让产品经理证明其价值,产品经理没办法证明。下一个星期,我就让他去做跟用户沟通,做需求分析,他就理解了:所有的需求都是假设。
- 假设用户痛点真的存在
- 假设我们的方案能解决用户的痛点
- 假设我们能在时间,成本,质量等约束下实现方案
- 假设方案实现后,用户会选择我们的方案
需求变更,往往是发现前面的假设不成立。
- 找错了用户痛点
- 痛点确实存在,但我们的方案没有解决用户的痛点
- 我们无法按原计划实现我们的方案
- 我们的方案确实能解决问题,但没有竞争对手做得好,导致用户不愿意使用
接受了「需求都是假设」这个前提,我们就不再执着于得到「确定的需求」。 而是反过来去思考:
- 如何才能快速低成本地验证假设?
- 如何降低需求变更带来的成本和风险?
第一个问题的解决方案是《精益创业》思想中的「快速试错」,要找出 MVP(Minimal Viable Product),先把主流程跑通,尽早地把产品推向市场(缩短 Time-to-Market),看看用户真实的数据反馈; 2014 年我辅导过一个做 P2P 的客户,P2P 的核心就是出借人放款,借款人借钱和还钱。当时为了尽早把产品推出来,抢占市场。我们做出了一个大胆的决定,在做完借钱功能,还没有还钱功能的时候,就上线了。 为什么呢?因为从业务规则上来讲,用户借钱后要至少 30 天后才会进行第一次还款。我们没有必要在借钱功能开发完后,再多等半个月甚至一个月去把还钱功能做完才上线。因为那样会损失很多潜在用户。
而上线借钱功能后,我们有 30 天时间去开发还钱功能,哪怕开发不完,我们通过客户帮助,通过传统的银行转账也是可以还款的。 但我们有信心开发完,因为我们会做简化。一般还款都支持多种渠道,比如银行转账,那又需要对接多家银行;比如第三方支付渠道扣款;我们没有必要对接完全部的银行和第三方才上线 举一个更极端的例子,当时我们的登录界面上和其他 App 一样,有注册,登录,找回密码功能。 找回密码这个功能,什么时候会用的比较多呢?长时间不登录,才容易忘记密码对吧。 刚线上的产品,新注册的用户,应该不会有很多人会去找回密码。 而找回密码的开发成本呢? 安全问题找回密码,比如:
- 你幼儿园所在的街道叫什么名字?
- 你邻居家的宠物的性别? 安全邮箱找回秘密; 手机验证找回密码; 如果要做全,开发加测试,可能又是一星期的工作量。 你猜我们最终怎么做的? 我们在用户点击找回密码时,弹出系统拨号盘,给客服打电话,客服帮你重置成默认密码。
这样的例子不胜枚举,最终这家公司现在已经成长为一家金融科技独角兽。
如何降低需求变更带来的成本和风险? 这个问题的解决方案是「极限编程」中的「简单设计」和「测试驱动开发」。 过度设计的软件,会变得结构复杂僵化,修改起来成本就很高。 简单设计定义了 4 条原则:
- 通过所有测试
- 没有重复
- 表达意图
- 最少程序元素 从上到下,优先级逐渐降低。 通过所有测试,就是要满足功能需求。也就是说不能为了保持简单而不实现功能。 没有重复,因为重复是万恶之源,会导致后期变更时要修改多个地方,漏掉的话就是缺陷。 表达意图,方法,变量的命名都要体现出意图,尤其是要体验出业务领域里面的用词。 比如代码里有一行:
if (user.getOrgId().equals(org.getId())) {
// do something
}
而需求里的描述是:
当用户属于该机构,就...
那么这段代码就缺少了「属于」这个概念,可以被重构为:
if (user.belongsTo(org)) {
// do something
}
最后一条,最少化程序元素。 程序元素就是类,接口,方法,常量,变量,参数等等,越多就表示程序员越复杂,在满足前面 3 天的前提下应尽量减少。
程序员为什么不喜欢需求变更,因为变更让我们压力很大。 什么压力呢?就是怕搞出缺陷。 其实大部分的缺陷不是写代码的时候写出来的,因为每次写代码,改代码,起码还是会把当前这次的需求都自测一下的,就算程序员没有自测,测试同学也会测试一下的。 但是,我们在实现新功能的时候,也很有可能影响已有的功能,这时候程序员就难以全量回归测试了,因为一是已有的需求不一定都清楚,而是造数据很复杂,耗费的时间很多,而工期又很短。所以这时候往往会寄希望于后续的测试环节。而且,心里会想,如果上线后除了问题,那也不是我的问题,毕竟专门做测试的测试团队都没有测出来...... 而测试同学就苦了,就会去问开发同学,你这次改动会影响哪些功能,你告诉我我重点测试一下。开发同学会怎么回应呢?
保险起见,你最好还是全部重新测一遍吧。
但是全部回归一遍需要时间啊,所以团队响应变化的能力就越来越差。
那怎么减少这种担心搞坏之前功能压力呢? 那就是自动化测试,包括单元测试,接口测试,端到端测试等。 这里重点说单元测试,如果程序员采用「测试驱动开发」的方式写代码,先写测试用例,测试运行失败,再写产品代码让失败的测试用例通过,再看看代码有没有可以优化的地方,就可以保证代码的整洁,设计的优雅,并且得到接近 100%的单元测试覆盖率,那样就可以在需求变更不小心引入缺陷时,被之前的单元测试捕获到,从而第一时间发现和修复缺陷。
还有一种可怕的变更,是优先级变更,就是计划本来都做好了,突然插入一个紧急的需求,为啥这么紧急呢?因为是「老板」提出来的。 这时就会打断团队的节奏。这种叫「河马」需求,谁嘴巴大就听谁的。
这种呢,我们也不能一味抱怨,也要想办法积极应对。 比如:
- 定期跟老板沟通,看看老板有什么新想法
- 做计划前过一下需求池里面的所有需求,看看有没有什么需求变得紧急了
- 尽量把任务拆小,采用小步提交的方式,保持编码工作区干净,减少任务切换的成本
最后,我了解到大舅哥用的那款软件支持用 XML 导入的方式,所以让他给了我一个模板,我抓取完数据后直接填充到模板里,给他提供了一个 URL,他一打开就会自动下载当天的数据文件,导入进去使用,大舅哥很满意,过年吃饭应该不会灌我酒了,哈哈。 就聊到这里,我带孩子去公园了。
总结
看完了小波老西的回复,陈旭觉得收获很大,但他知道必须要思考和应用,才能解决实际问题,于是他做了如下总结: 需求的三个层次:
- 业务需求 - 要赚多少钱
- 用户需求 - 要解决哪些人遇到的哪些问题
- 功能需求 - 用什么方式来解决,用什么形式来交互
需求的本质是假设:
- 假设用户痛点真的存在
- 假设我们的方案能解决用户的痛点
- 假设我们能在时间,成本,质量等约束下实现方案
- 假设方案实现后,用户会选择我们的方案
需求变化的原因:
- 鸽子需求和河马需求
- 用户痛点没找对或发现新痛点
- 方案不能解决痛点
- 方案无法在约束条件下实现
- 体验优化
如何应对需求变化:
- 跳出功能需求,挖掘用户需求和业务需求
- 延迟决策,尽量晚定细节,这样能留出更多时间来接受变化
- 采用简单设计,提高设计能力,降低扩展成本
- 采用测试驱动开发,降低变更风险
突然,陈旭觉得眼前一黑,抬头看向窗外,原来是太阳穿过了云层,树的影子跑到了书桌上。 陈旭眉头舒展,嘴角露出久违的笑容,仿佛生活又充满了希望。