再聊聊时间评估这个事

岔子

最近已经连续两个项目的时间评估出了岔子,都可以说是把问题看的太简单导致的评估时间过短。由此带来的一系列时间节点上的延迟,总归是十分麻烦的。我发现外国人分析一个问题,无论这个问题有多复杂,他们总是琢磨着如何量化、抽象、对比然后找到一定的规律,最后得出结论。如何去学习要总结,如何去睡眠也可以总结,甚至如何做一个决策也可以总结。本着理性看问题的思路,也把时间评估这个事总结一下,不求也能找到啥规律,能够惩前毖后就满足了。

先放出是什么样的两个项目:

  • 项目一:新版本功能模块合入老版本的定制代码中
  • 项目二:独立功能模块的重构

很久以前探讨过项目时间评估这个问题,那时主要还是针对新功能的开发,学习到了一些简单的方法论,核心观点还是需求划分粒度尽量小,而后根据个人能力评估和计算理想时间和预计时间,简单有效。随着对项目的熟悉程度和开发知识系统上的不断完善,细致化估时做的越来越少,终于是在老项目再开发上栽了跟头。

分析

先挨个分析分析估时过程中忽略了哪些点吧。

项目一,分支模块合并

对于第一个项目而言,是把完全新开发的模块合并到老的代码分支中,粗略的看由于功能完全是新的,模块之间的耦合度非常之小,所以可能只是模块并进来,在几处交互中添加一些相关逻辑就好了。按照这个思路,需要的时间必然不多。但是实际开发中,碰到了这样那样的问题:

  • 老版本与新模块共同依赖的SDK版本不同,由于SDK本身的不稳定性,导致强行升级SDK版本可能会出现一些未知BUG和编译不兼容问题
  • 新模块本身功能是独立解耦的,但是该功能的增加实际上会影响到其他两处的功能交互上的配合,整个体系才算是完备。而老代码本身的年代久远和定制化的需求导致了此两处无法直接利用模块升级更新的方式来解决,手动修改升级的时间成本不在预算之内
  • 老代码当时还没有做到完备的模块解耦,新模块是有的。新模块要体面的合入进去就要把模块解耦相关的依赖库处理掉,再理顺跟老代码之间的依赖关系,才能够正常使用

以上是在评估的时候完全没有意识到的几个点,在动代码的时候问题都充分的暴露了出来,再处理起来就相当的被动。其实可以看出核心问题有三个,一是老版本的代码模块化不完备;二是部分业务上的模块化的内部实现还不够灵活,定制化的需求无法通过配置来实现,那么升级就是很大的问题;三则是SDK本身的兼容问题,理论上SDK的升级是要保证接口与功能上对旧版本的兼容的,目前似乎存在疑问。就时间评估而言,没有意识到新老版本之间存在的巨大鸿沟可以说是事情办的太过简单粗暴了。

项目二,模块重构

第二个项目,关乎于重构。对于所有的重构,其实我们都面临着三个问题:

  • 什么是重构
  • 重构的范围怎么确认
  • 重构之后的质量怎么保证

这不仅仅是个概念上的问题,也对我们的时间预算也是影响至深的。关于第一个问题,在《重构-改善既有代码的设计》这本著作中,对于重构有这样的官方定义和描述:

重构(Refactoring)就是通过调整程序代码改善软件的质量、性能,使其程序的设计模式和架构更趋合理,提高软件的扩展性和维护性。

系统发展到一定阶段后,使用重构的方式,不改变系统的外部功能,只对内部的结构进行重新的整理。通过重构,不断的调整系统的结构,使系统对于需求的变更始终具有较强的适应能力。

细细品味,其实“重构”和“重写”是两个概念,即使在程序的外在表现完全相同的前提下,依然是两个不同的操作。重构更加强调在代码结构上的重新组织,对原有的代码、接口有一定的起承转合的联系,而不是完全的推倒重来。并且根据不同情况下的不同需要,也有着不同的限制,这便与第二个问题——重构的范围紧密相关了。

根据代码本身不断迭代到现状的特点、业务上新需求的要求、时间人力成本的约束来确定重构的范围是合理的。基于重构的概念,比如说我们只是重构一个类,那么该类对其他类的影响的部分如:公有的方法接口、回调接口的组织、异步传出的消息形式(如类似EventBus/Handler的消息分发)等都应当保持不变,以保证相依赖的其他类中毋需适应重构后的类。假如说根据种种外部条件来分析,重构一个类不足以满足需求,要重构一个模块来完成,那么同样的道理,把相关的依赖关系上升到模块间的层级来处理。模块间的数据传输接口协议、交互调用流程等不应当有大的牵连改动。可以看出重构本身是依赖于当前状况、需求情况、资源限制的一件应当步步为营的事情。以上可以用来预算重构项目的代码重构时间,那么在第二个项目中疏漏掉的是没有漏掉了领完一块时间,也就是第三个问题——保证重构后的质量要花掉多少时间。

在理想情况下,如果代码有一套接口对应的单元测试来保驾护航,那么对于重构的质量跟踪会少去很多成本。当然至于是维护测试用例的成本高、还是手动测试的成本高这就公说公有理婆说婆有理了。基于重构的本身的概念,对外部的接口、信息输出方式是不变的,换句话说重构前后,单元测试的覆盖也是通用的,跑通用例就可以保障大部分的逻辑重构上的正确性。遗憾的是在现实中并没有这样一套体系来保证我的重构模块质量,并且由于模块的功能还依赖于嵌入式端的数据交互使测试时间更加紧张,最终导致延期。

结论

归拢一下两个项目估时失败的原因和日后优化的方略。首先战略上的轻忽是最重要的一部分,产品经理来问要多久,没有做深入思考,大致想了想就开始拍脑袋,这是最要命的。新需求、代码合并、重构——不同的情况有不同的需要注意的点和方法,思考上更加严密周全,后面出来的时间点才会更加合理。欲速则不达,评估线条越粗后面出岔子的可能性就越大。经过这次的总结或许对后两种情况的认识更加清晰明了,以后也会在思考方向上更明确一些。

然后就是我们代码现状对类似于分支模块合并、重构这种业务需求上的影响了。其一,模块化的必要性。在这种场景下,模块化、结构化的最大意义可以说就是把程序的可拓展性大大增强了。在基于同一套模块化解耦过的代码工程中,新老版本之间模块的增加和更替就不会有太多的麻烦;其二,基础模块的稳定性和兼容性需要着重保障。类似于SDK这种东西,对外开放的接口需要慎之又慎。如果升级这种基础库的版本还有可能存在未知问题,无疑是一个时间消耗上的无底洞,十分恐怖;其三,在大的维度上模块化,小的维度上面向接口编程,其实是对构建一个测试体系的有力支撑,在时间允许的前提下,其实是可以尝试的。

感谢您赏个荷包蛋~