抽象层的稳定性

当我们组织代码的时候,有一个常用的技巧,

那就是将公共模块提取出来,这可以看做是对当前场景的一种抽象。

它会降低冗余,减少代码量,提高开发效率。


不然的话,只是想一想散布在代码仓库中重复的业务逻辑,

我们那种似乎与生俱来的洁癖,就可能本能的产生一种抵触心理。


然而,公共模块也有可能带来一些问题。


假设在旧有代码中,由于功能A和功能B出现了公共逻辑,我们提取出模块X以共用。

这一切看起来是美好的,

但事实上,它却无意中建立起了A,B对模块X的依赖关系。


现在我们打算开发新功能C了,它与既有功能A,B有和X相似的公共逻辑,

但是又不完全一样。

这时候,我们是修改X呢,还是在功能C中几乎重新再写一遍X呢?


我们会发现,自己走到了一条死胡同,

要么打破原来构建公共模块的初衷,造成诸多修改,

要么违反我们编写可复用代码的原始动机。


我们到底应该怎么面对抽象层的脆弱性呢?


不断腐烂的抽象层

维基百科上对抽象给出了下述定义,

抽象,就是抽取事物一些本质的东西,剔除次要的表面东西。


在编写软件的过程中,对业务场景建模就是一种抽象手段,

我们会在多种具体情形中观察共性,提取模式

然后在应用模式的时候,补上差异化的那些特征。


当我们静态的看待软件问题时,这是一种极好的开发方法。

然而,现实中的软件都是动态变化的,在整个软件的生命周期中,变化是不可避免的。

那么,这种建模方式,还是合理的吗?


我们将如何保证,当前所建立的抽象,仍然能良好契合未来更多的场景。

显然这是无法满足的,因为我们不能断定反例不会出现。

因此,抽象层一旦建立起来,随着时间的发展,它就会变得越来越脆弱,越来越不太适用。


我想这也就是我们代码仓库中,那些不合理的抽象的根本来源吧。

也明白了是谁一直在写那些烂代码了。


不完备的测试用例

如果历史上对业务场景所做的那些假设,随着时间发展不可避免会变得不合理的话,

那么,我们首先想到的是承认这一点。

然后考虑在这种情况下该怎样做。


如果我们总是以最新的业务场景为蓝本构建模型,

总是调整代码,让它以最恰当的方式描述业务需要,

或许是一个好办法。


现在我们来看看这会带来哪些挑战。


首先我们想到的是对测试的挑战,因为我们总是在修改既有代码,

那么如何降低这些修改所带来的风险,保证功能的稳定性,就成了一个问题。


在《重构》一书中,

Martin Fowler也提到过,重构的前提是保证代码被自动化的测试用例所覆盖,

否则,我们就无法确定某次修改是否真的对软件的外在表现没有影响。


这在理论上是说得通的。


但是在现实中,很多场景尤其是用户界面,

覆盖所有特性的自动化测试用例,可能很难实现。


即使自动化测试跑过了,我们也不能保证它不会出现其他漏洞,

测试用例可能总是不够全面,例如重构化的版本可能增加了某些限制,

而有没有这些限制不违反测试用例,但是用户却需要他们。


防止过拟合

如果我们承认了抽象层的脆弱性,

另一个考虑问题的角度可能是,我们能否放弃对冗余程度的坚持。

即,打破自己洁癖,为了增加可读性,适当的保持系统在一定程度上是重复的。


在仅出现两次重复的场景中,我们大可不必为此建立任何抽象,因为,

过早的优化是万恶之源。

——Donald Knuth


从统计学上来讲,如果样本量太小,就更有可能出现过拟合

因此,不妨让系统运行一阵子,让业务发展一段时间,

再将真正的模式提取出来。


因此,大前研一这样说,

解决问题的能力,就是为印证假设不辞劳苦的行动力。

我们可以进行假设,但是要时刻做出推翻它的准备,

并且要通过不断收集证据,而不是仅仅依赖逻辑上的正确性来检验它。


而从代码层面,我们最关注的其实不是系统的重复程度,

而是可维护性,或者说可读性,

可读性并不仅仅与代码的精简程度有关,还与信息的封装和隐藏有关。


只要我们对必要的细节进行合理隐藏,

并不会给维护工作带来更多的成本,

相反,去理解一个不合理的抽象概念,反而是困难的,成本高昂的。


工程指标

从工程角度来讲,一个软件项目就是凑齐一些人,在指定的时间内,

以约定的可接受的软件质量为前提,做出满足需求规格说明的软件产品的过程。


那么抽象层腐烂,或者可维护性差,真的是整个项目的瓶颈吗?


代码难以读懂,我们当然可以说服自己,平心静气的读下去,

实在无法理解,在可控范围内,我们肯定也可以将相关功能重写。

因此,代码不是一个软件项目的制约因素。


而大部分软件项目的制约因素在于人日

即,项目的参与者,以及该项目的截止时间。


一项调查表明,大约70%的软件开发项目,超出了估算的时间,

大型项目平均超出计划交付时间20%到50%,

90%以上的软件项目开发费用超出预算,并且项目越大,超出项目计划的程度越高。


因此,绝大多数软件项目所面临的问题都是时间上的问题,

如何快速交付项目,才是人们关心的事情。

而代码是否容易维护,只是对交付时间影响很小的一个因素。


其他因素当然还有,开发者对业务需求的理解程度,

以及业务架构是否合理,返工的次数是否过多,人员之前的沟通是否顺畅,等等,

因此也有人说,不清晰的产品路线和业务架构,决定了脏乱差的代码结构。


结语

用静态的角度来看待软件,那么我们可能会写出符合美感的程序来,

但这种美感是脆弱的,不堪一击的,

一个突如其来的新功能,就可能打破这一切。


然而,我们又不想每天将自己置身于脏乱差的代码环境之中。

所以更退一步的话,我们可以放弃美感,保留代码的可读性。


但是,自古不谋万世者,不足谋一时,不谋全局者,不足谋一域。

从整个项目,从工程和业务角度来看,

代码问题却已经不能成为最重要的问题了,这个问题是否有解或者究竟何解已经不重要了,

因为,我们关心的问题层面变了。


路漫漫其修远兮,吾将上下而求索。

——屈原