无痛苦的软件维护——被遗忘的需求
引言:从“超级牛”的笑话说起
首先分享一个笑话。有一位生产队队长对专家说:“现在我们生产队的地越来越多,牛越来越忙不过来了。我想要一种牛,它吃的草和普通牛一样多,但是干的活是普通牛的十倍。”专家回答:“这种牛是可以造出来的,现在有基因工程。”队长说:“好吧,那你给我造几头这样的牛。”于是专家找到生物实验室,试图通过基因工程把牛造出来。结果工程浩大,投资无法保证,合作多半以不愉快的收场告终。
现实世界里,很多人分析需求的过程就类似于这位专家。他们把注意力放在用户提出的功能点上,而对用户的实际需求没有兴趣。不少软件公司和程序员,其实都在做类似的“基因工程”。如果这位专家把注意力放在生产队长的业务需求上,而不是太在乎他提出的功能点,他会说:“我认识一个卖拖拉机的,可以带你去看看。”
核心症结:被遗忘的业务需求
软件维护之所以痛苦,一个很重要的原因在于:需求已经被遗忘了。
需求是对用户具有直接商业价值的活动,而不应该牵涉到任何的功能实现方式。
实现同一个需求可以使用多个方案,每个方案有自己的功能方式。在某个方案中至关重要的功能点,也许在另一个方案中根本无关紧要。
传统开发模式的困境
瀑布式(Waterfall)的开发过程,首先是由一批懂得用户业务的专家去调查用户的需求,分析出系统应该具有哪些功能,形成一份重要的文件——《xxx 系统需求规格说明书》。客户认可了这份说明书,签字盖章并加入合同附件,项目验收便以此为准。
这时候,需求就已经分解成了一个个功能点。从这时候开始,需求本身就渐渐被人们遗忘。设计人员围绕着这些功能点进行工作,考虑用什么样的技术手段把功能点制造出来。功能点的制作细节形成了另外一份重要的文件——《xxx 项目设计规格说明书》,随后交给程序员进行编码。
这样的做法在开发中已经暴露出很大的问题:
- 需求溯源困难:面对《需求规格说明书》,设计人员已经无法知道最初的需求是什么。假如说明书中的功能点没有互相矛盾,但串起来却和用户的实际需求不同,设计人员没办法发现,编码人员也无法发现。假如测试也是根据这份说明书来进行,测试人员同样发现不了。直到最后客户看见程序展现在他们面前,问题才爆发。需求的分析需要在随后的过程中不断得到反馈,传统的过程不是没有反馈,而是反馈的时间太长了。
- 业务建模缺失:由于设计人员无法知道基本的需求是什么,也就无法对业务进行建模。这样的需求分析是以开发人员的需要为核心的,结果恰恰妨碍了开发人员对需求的理解。如果开发人员对用户的业务过程不甚了解,他们只有一种选择:不要试图去了解需求了,直接按照这些功能点做吧。于是,他们建立的对象模型就不是以需求为核心的,而是以功能、界面为核心的。我见到过很多这样的系统,开发者确实有很高的抽象思维水平,程序中设计了非常巧妙的控制器和界面,可以很方便地进行开发和变更。唯独业务层的对象非常简陋,一旦发生实际业务的变更,仍然十分辛苦。
案例分析:移动通信入网系统
更大的困难发生在维护程序的时候。假设有一个移动通信公司需要制造一个系统,用来解决手机用户入网的问题。这个需求有下面几个要素:
- 用户付钱,得到一个 SIM 卡和一个号码,把这个 SIM 卡装到手机里就可以通话。
- 营业员收的钱要记录下来,提供给稽核人员,现金和账目必须是平的。
- 用户付的话费要划入他自己的账户,可以打印票据。
- 用户要在入网合同上签字,然后营业员把合同归档。
这几个要素都是和通信公司的商业利益直接相关的,没有牵涉到任何系统实现方式。如果不考虑通信公司内部的业务规范,实现方案可以有几十种,下面列举两种:
- 方案一:SIM 卡发给营业员,用户入网的时候,选择一个号码,然后付钱。营业员把 SIM 卡号码和电话号码输入系统,在交换网络上进行注册,这个 SIM 卡就可以通话了。然后各种费用记入各人账户,合同归档。
- 方案二:SIM 卡在下发给营业员之前,先在交换网络上注册,并且已经预先设置了一定的话费。用户选择了这个号码,付钱之后直接拿走 SIM 卡就可以打电话了。营业员事后再输入用户的合同资料,费用计入各人账户,合同归档。
这两种方案在实现过程上是不同的,因此具有不同的功能点。比如第二种方案中的 SIM 卡在出售之前是可以进行通话的,所以必须对这样的号码的通话费用进行监控,这个功能在第一种方案中是根本不需要的。并且两种方案在账目的核对方式上区别也是比较大的。这两种方案是不同的功能点的集合,但它们完成的是同一个业务需求。
维护痛苦的根源
系统在开发阶段如果没有保留用户的业务需求情况,而是只留下一个功能点的列表,会给维护人员造成很大的困难。维护人员无法从这样一堆功能点中发现最初的需求是什么样子。
各位可以试试,假设我们忘记上面的四个需求要素,只看下面的某个实现方案,从这个复杂的实现过程中,我们很难知道用户现在的需求到底是什么。一旦需求发生了变化,这些功能点就会出错,或者是功能点的时序发生意料不到的错误:也许账目核对不上了,也许是用户拿走的 SIM 卡不能打电话了。
看不见需求在哪里,不知道手里这段代码会触动需求的哪根神经。维护人员的痛苦大部分来源于此。
“不要紧,客户记得自己的需求。”但是客户通常不懂技术,即使他们懂技术,他们也不知道系统是如何实现的。如果开发人员依靠客户提出新需求的解决方案,结果就是让软件工程变成“生物工程”。到最后是钱基本花光,人基本累死,甲乙双方感情基本破裂。
解决方案:回归业务需求核心
软件开发必须划分成几个过程,但是各个步骤应该有一个统一的核心——业务需求。
- 需求调查阶段:要搞清楚用户的业务需求。为了达到这个目的,可以提问回答,可以对用户进行跟踪采访,或者做一个 Demo 给用户看看。最终的目的是为了搞清楚用户在做什么事,遇到了什么问题,程序应该去解决什么问题,这就是这一阶段的工作。
- 设计阶段:设计系统的逻辑结构和物理结构。逻辑结构要符合需求的概念,各个对象互相调用要能够实现需求中的业务过程;同时物理结构划分合理,符合实际的分布状况,可以达到要求的性能,业务过程的物理运行方式合理高效。这一阶段仍然是以业务需求为核心。
编码阶段:
- 首先是编写一些基础设施,比如网络通信、数据库、文件的读写、分布式计算。这些基础设施和业务需求没有什么关系,有很多现成的框架,借助这些框架我们可以尽快度过这个黑暗的阶段。
- 然后编写业务对象,这时候业务需求中的一些概念逐步出现在代码中。比如上面说的那个例子,“用户”、“号码”、“合同”、“入网”、"SIM 卡资源”这样的业务要素逐渐出现,这些对象所拥有的属性、可以运行的行为也和现实的需求一样。
- 接着这些业务对象串接起来,实现业务过程,现在业务需求又回到了人们的视野当中。业务需求是什么,如何实现,在这里一目了然。
- 最后将这些过程在 UI 或者接口中调用,将功能提供给用户或者别的系统。
- 测试阶段:测试更是要围绕着业务需求来进行。正常的业务流程应该产生正常的结果,如果缺少某个资源,或者输入了不合适的数据,应该出现业务含义明确的异常。并且系统的业务对象是处在一个独立的层次上,与 UI 和基础设施没有很大的关联,这样可以方便地采用自动化的测试方法。
结语
这样的系统维护起来,一定少很多痛苦。
说明:本文主要基于传统软件工程视角探讨需求与实现的关系。虽然文中以“瀑布式开发”为例进行批判,但其核心观点——“维护业务需求与功能实现的分离,并在全生命周期中保持业务视角”——在现代敏捷开发(Agile)及领域驱动设计(DDD)中同样具有重要的参考价值。
版权声明:本文为原创文章,版权归 戴老师的博客 所有,转载请联系博主获得授权。
本文地址:https://1diff.fun/archives/wu-tong-ku-de-ruan-jian-wei-hu--bei-yi-wang-de-xu-qiu.html
如果对本文有什么问题或疑问都可以在评论区留言,我看到后会尽量解答。