缘起

上周修改一个Bug,本来以为只需要做一些小调整就可以,后来还是发现由于受对象间的状态影响,出现了另一个错误。这也让我进一步思考对于系统设计和建模来说:面向对象是错误的,会带来后期的很多问题。

 

面向对象

在面向对象的设计中,系统是由对象和让对象状态发生改变的方法,让对象到达另一种状态来达到目的的。当系统渐渐变大,对象渐渐变多,每个对象间的纠缠越来越多的时候一个对象的状态受到多个控制信号的机会就越来越多,作为状态的使用者,你越来越无法判断你所用对象的状态——时常由于外部改动让状态发生的改变,你的编程假设也就失败。这通常会造成Bug的产生,而且很难定位和修复这样的Bug。

从代码的观点来说,操作对象的代码和对象结合的太紧密,你复用的机会会越来越少,对象上的方法是针对这个对象的特化方法,你很难加以利用。所谓的高内聚让你对于已有的方法只能看,而不能用,相信对于维护代码的同学特别有这样的感觉:你的需求基本上另一个对象已经满足了80%的功能,或者你想剥离其中某个方法,会发现根本没有办法拨出来,这个方法跟对象紧密的结合在了一起。

对于维护调试来说,你会发现当系统出现问题时候,你很难改变对象上的方法,很多时候看似有问题的方法对于这个特定的对象来说居然是正确的,前提是有很多Setup让对象处理这样的状态。在调试的时候好像一些几何相关的谜题,你移动了一个角度,以为这样就能通过,其实移动带来的副作用让你无法通过。

 

函数式架构

系统的设计还是要采用函数式的结构,把系统看做一个数据转换器(transform),而不是状态修改器(modification)。输入点上,数据是已经求值后的不可变(immmutable)值,经由数据变换,数据的二次变换,数据的三次变换(可能是要呈现的记过),比如输出html。系统更多的像是一些管道相连的一大堆通用数据处理器。

当系统出现问题时,你可以检查输入/输出,或者直接模拟输入/输出来诊断问题。这个有点像是维修模块化的电子元件,方法通常有两个:

  • 你测量输入端的电流电压和输出端的电流电压,来确定这个电子器件是否按照预期工作。
  • 或者你逐个替换系统中的元件,直到你发现当某个零件替换后,系统正常工作了。

你如果观察维修小工,你会发现他会在这两个方法见频繁转换。

由于是同用数据结构和通用数据处理,这让复用变的可以预测(Predict),如果输入和输出可以达到你的需求,那么这个函数你就可以拿来使用。好像维修小工,并不在意某个电容是哪个生产商生产的一样,达到要求就能用。

现实例子

由于状态变化原来越频繁,面向对象缺点也就越来越突出,比如近年来的前端由纯展示(View)变成了应用(App)。所以使用JQuery来修改组件的状态已经不能满足需要了,现在的Component-组件化越来越成为主流。无论是vue还是react,和其对应的 ElementUI 和 Ant Design ,让你可以对组件细节不用关心而很好的使用和替换组件。你给组件以数据,组件做这个数据的变换,变成一种呈现方式。通过约定,或者一个转换器,同一组数据,你输入到 ElementUI 和 Ant Design 出来的只是风格的偏好不同,而结果应该是一直的。

函数式架构的重点在于连接而构成的系统。通用的元件(数据结构)加上通用的变换(函数),而不是由一个个定制的对象(类型实例)而组成的,这样的系统很脆弱,坏了你没有办法维修和更换——因为都是定制的。

总结下来,当系统变得复杂以后,通过状态变化带来的副作用让系统变得脆弱和无法维护,而函数是的数据加通用处理机制会让系统工作的更好。