细说可持续的需求分析和软件设计

最近和大家一起讨论了一些内容管理方面的功能和设计,有些思考,和大家分享一下。

在讨论内容管理的功能需求时,我们常常会考虑某个功能各种各样的情况,功能性、易用性、复杂的处理场景、异常的处理场景,这些无疑都是非常非常有价值的,一个系统做到最好的境界,从客户角度来看,也就是这些功能了。

同时,我们也讨论了很多软件设计方面的一些内容,考虑了很多灵活性、扩展性方面的内容,同时设计和功能也是紧密相连的,设计常常对功能的具体展现会有一定的影响。

那我们实际中遇到的困难是什么呢?针对上面我们讨论的两个方面,主要是两个问题:

1)功能太多了,有时候是越想越多,如何选取合适的功能集合成为讨论的焦点;

2)还有就是设计的灵活性和扩展性的把握,感觉好的设计,往往需要更多实现的时间,然后项目时间似乎又不允许。

在说明这两个问题之前,我想有必要稍微说明一下软件质量的一些分类。

最近看一本书(Scrum and XP from the Trenches),对软件的质量,划分为内部质量(internal quality)和外部质量(external quality),外部质量指的是从客户角度可以看到的质量,比如软件的功能,易用性、性能等等;内部质量则是是从程序员角度来看的质量,比如代码的健壮性、可扩展性、可维护性等等。外部质量好的软件,内部质量不一定好;但是内部质量不好的软件,外部质量一般很难好。很难想像,一个设计很糟糕,代码质量很糟糕的系统,功能、性能和易用性可以很好。内部质量就好比是外部质量的基石,代码的可维护性和扩展性,直接影响了系统的功能的改进和提升。

外部质量和内部质量比较容易对于到功能和设计两个问题上。

那么回过头来看我们遇到的两个问题,首先是功能的取舍问题。

我们Agile的团队讨论,大家对于某个User Story,讨论起来越谈就越起劲,想出了好多好多的功能点,随之也带来了很多麻烦,比如说要实现的范围好像太大了,似乎一下子工作量变得很大,随着而来也有很多压力,然后接着我们有时候也会不由自主的按照项目时间点,寻找一些“捷径”,然后可能就逐步丢掉了或者少做了一些好的功能点,甚至会转向实现一些大家虽然觉得不怎么好但是满足项目时间点的功能,这时大家都不免感到有些失落。

那我们可以怎么处理呢,可以稍微分析一下我们整理出来的功能点,我们会发现,情况也许不是我们想像的那么糟糕。我自己觉得有四个原则可以帮助我们去抉择:

<!--[if !supportLists]-->1)<!--[endif]-->功能优先级

<!--[if !supportLists]-->2)<!--[endif]-->80/20法则

<!--[if !supportLists]-->3)<!--[endif]-->完整性

<!--[if !supportLists]-->4)<!--[endif]-->可持续性

1)优先级

首先是我们可以按照优先级来选择功能点,这个是显而易见的。重要的功能先做,次要的功能可以先放一放。特别是最基本的功能,比如客户一定要的功能,没有这个功能客户就玩不转了;比如内容管理,如果内容创建、修改和删除,这些功能如果都没有,那么系统都无法正常运转了,肯定是不行的了。

2)80/20法则

80/20法则,就是先选择哪些客户日常使用最需要用到的功能,比如说内容处理的基本流程,有一些内容同步的异常情况,实现起来是很复杂的,但是实际中遇到的可能性相对较少。又比如内容创建流程的易用性,这个用户使用频率是非常高的,那么怎么优化内容创建的用户体验,这个功能点优先级也就是很高的,然而它的代价可能不会特别高。

3)完整性

要特别注意是功能点的完整性,比如说内容异常流程的处理,假设因为项目时间,先不实现了,那么也不是说完全不处理异常了,还是要做到有一定完整性,即使是简单的实现也是需要的(比如说记录日志以供人工查询),但是这个简单实现是代价最小的,而且是以后可以很快去替代的。

4)可持续性

功能点的实现选择,要考虑的还有可持续性的问题,就是功能点是可以不断去叠加来完善的,而不是说不断的推翻后重新实现一把,这个是差别很大的。比如说内容创建功能,现在对于异常的处理我们暂时不实现,这个是没有问题的;但是如果下次要实现异常处理的时候,就要把现在内容创建的流程的功能描述推翻重来,这个可持续性就有问题了,因为这个意味着以前的功能全部都会被推翻,很可能是以前的实现都白费了,这就是功能点设计的的不可持续性了。功能点设计一定要有持续性,如果是这样子,系统的功能就能够越做越强。

所以我们可以把每一个的User Story的各个功能点想的更加完善,这个是很好的,剩下的只是如何取舍的了,所谓取舍,只是阶段性的舍弃和选择罢了。所以在讨论过程中,不要因为功能的增强,范围的扩大而让我们感到害怕和困惑,把他们记录下来,就是很好的逐步改进系统的武器,我们只要运用上面的一些原则,就能够让我们做的更好。

下来再谈谈设计的问题

在Head First Object-Oriented Design的书中,定义Good Design就是Flexible Design。而The Art of Agile Software Development一书中,定义Good Design为“A good software design minimizes the time required to create, modify and maintain the software while achieving acceptable runtime performance.”就是软件的可维护性。所以Agile Design强调的功能,基本上都是从如何不断改进软件的可维护性和可扩展性而努力的,只有软件具备了良好的可维护性和可扩展性,那么软件能够很好的不断叠加功能,软件才具有旺盛的生命力。

我们实际中面向的问题呢?其实还是很简单,就是好的设计和项目时间的冲突,好的设计是需要时间考虑的,也是需要时间来实现的(虽然不是绝对,有时候好的设计会节省更多的工作量)。

对于第一个问题,于项目时间的冲突,这个可以回到前面开始谈的内部质量和外部质量的问题,前面对于功能(外部质量)的问题,我们已经谈了取舍的方法,那么,内部质量(设计),是不是也可以取舍呢?在“Scrum and XP from the Trenches”书里面,作者自己是这么认为的:

Internal quality, however, is not up for discussion. It is the team’s responsibility to maintain the system’s quality under all circumstances and this is simply not negotiable. Ever.

在这上面我是持一样的观点的。

然而我们的问题依然存在。和项目时间的冲突如何平衡呢?我想可以考虑两个原则:

<!--[if !supportLists]-->1) <!--[endif]-->足够好(First Right)

<!--[if !supportLists]-->2) <!--[endif]-->分阶段实现/可持续性

1)足够好(First Right & Good Enough)

所谓First Right & Good Enough是让我们看看我们所作的设计是不是足够清晰的架构我们的系统,而不是太过的复杂导致项目时间不足,往往好的设计并不是要花更多的时间实现的,通常只有Over Design才让我们感到力不从心。所以我们发现设计导致实现的时间过长的时候,我们需要看看,是不是我们想的太复杂了?

另外一方面,我们不提倡Over Design,避免Needless Complexity,但是还是要Good Enough & First Right,就是在看的到的需求范围内,我们的设计要好,好的设计和不好的设计,差别往往是在维护代码和增加功能的时候才能够看到,稍微花一些时间完成一些精妙的设计,不仅是技术,更是艺术美感的体现,这个只有在未来才能够体会到。

要注意的是Refactoring和First Right并没有冲突,只有每次都是Right的比Wrong的更多,逐步的将Wrong的地方进行Refactor,系统才能够越做越好。否则系统的质量就始终处于初级阶段了。

2)分阶段实现/可持续性

好的设计往往是Flexible的,是可以分阶段实现的,很多设计的基本原则,比如面向接口编程,就是支撑“分阶段实现”的一个很好的原则。当我们定义的接口层次很清晰的时候,接口的具体实现,是可以根据项目的时间点,进行控制的。项目时间比较紧的时候,可以做一些快速的实现,然后在下一阶段再Refactoring,如果对象之间是接口依赖而不是类依赖的话,下一阶段的Refactoring也对系统已有功能影响也就非常的小,这个也是IOC核心价值所在了。

举个例子,比如说话单文件格式的灵活设计,假设话单有好几种格式,那么它的设计可以有几个阶段:

第一个阶段是能够在类这个层级易于维护,先通过Template的设计模式定义一个话单父类后,不同的话单格式的子类实现父类的模板方法,如formatCDR(格式话话单记录)即可,这种情况下,外部函数写话单的时候,只需要实例化相应对象后,调用父类的接口就可以写出相应的话单。而有新话单格式的时候,只要生成一个新的子类并实现相应方法就可以了,可以看到这种设计是一个Good Enough的设计。

第二个阶段是把类的可维护性提升到配置的可维护性,比如说通过一种XML的方式来配置话单格式,使得系统的可维护性达到运行时而不是编译时。这个时候,还是在原来的基础上,生成一个新的子类,这个子类在formatCDR的时候是从XML配置文件里面读取配置后生成格式化数据的罢了,系统还是支持很多种默认的CDR格式并且对于一些无法通过配置的CDR格式,还是可以通过子类继承的方式来实现。

第三个阶段是做一个很好的GUI来配置和管理话单XML配置文件了。

相关推荐