从前,客户需要一个矢量(1 2 3)。
于是,我们就打造了它。
可是,一段时间之后,客户觉得这个矢量不好,不能满足需要。
他们实际需要的是(6 5 4)。
为了避免从头开始。
我们聪明的使用了原来的矢量(1 2 3),并增加了新功能(5 3 1)。
于是(1 2 3)+(5 3 1)=(6 5 4)。
我们降低了生产成本,很高兴。
又过了一段时间,客户觉得(6 5 4)还是不好。
他们真正需要的是(3 6 9)。
我们很无奈,再用老办法吧。
(6 5 4)+(-3 1 5)=(3 6 9)
这里出现了负号,意思就是有些功能不需要了,删掉它。
这时候,我们心里有点不舒服了。
增加功能还可以接受,有成就感。
而删除功能,很不舒服。
当时加班加点做了,现在又不要了。
出尔反尔。
随后的日子里,你也知道。
事情经常会像你不敢想的那样发展。
客户的需求一直在变,我们没办法预测它。
问题和答案
到底是哪里出现问题了呢?
有人说,需求变更是软件项目的本质特征,控制需求一个是软件工程问题。
这是对的,过度频繁的变更是有问题的。
但是,通常一方面的问题也会掩盖其他方面的问题。
人们通常会认为后果只是由一个原因造成的。
比如,设计,难道就没有问题了吗?
除了紧跟着客户的需要来做,我们还能做什么?
我们发现,关键在于,
不要想着让软件去适应未来,这是不可能的。
我们要提供所需的基本功能,用它去实现未来。
正交化的设计
回到文章开始那个例子上。
假如客户需要一个矢量(1 2 3)。
而我们实现的是(1 0 0),(0 1 0)和(0 0 1)。
会怎样?
首先,这三个基本矢量,足够少。
因为,任何一个矢量都不能用剩下的两个来实现它。
其次,它们又足够多。
实现客户的需求是没问题的。
1 * (1 0 0)+2 * (0 1 0)+3 * (0 0 1)=(1 2 3)
当客户的需求变化了,他们想要(6 5 4)。
我们就不需要更改基本的三个矢量了。
我们只需要重新拼装它们。
6 * (1 0 0)+5 * (0 1 0)+4 * (0 0 1)=(6 5 4)
对于(3 6 9)亦然。
3 * (1 0 0)+6 * (0 1 0)+9 * (0 0 1)=(3 6 9)
反思
这个例子并不是一个实际问题,
但是却能让我们学会很多。
作为一个功能提供商,我们并没有直接提供功能。
我们提供的是一个正交的功能集。
再由这些功能集来实现客户的需求。
好处是什么呢?
原则上,我们永远不需要更改或删除已有的功能了。
我们只需要增加新的正交分量即可。
并且,随着时间的积累,
功能集会越来越大,可以用来实现各种各样的需要。
对未来来说,他们是无价之宝。
Don't base your venture on a plan.
Instead base it on a strategic foundation.
——《How google works》
结语
编程,不只是体力劳动。
我更倾向于认为它是一个设计过程。
好的软件是秉承一系列优秀设计实践,逐渐发展起来的。
而正交化的设计,只是好的设计实践中的一种。
就像小说一样,文学作品的好坏,不在于字数。
阅读它,能给读者带来多少思考,能营造什么气氛。
这才是重要的。
好的软件,功能稳定,结构优雅。
后记
假如客户需要矢量(1 2 3 4),怎么办呢?
我会写一个函数f(x y z)=(x y z 0),然后再写一个矢量(0 0 0 1)。
1 * f(1 0 0)+2 * f(0 1 0)+3 * f(0 0 1)+4 * (0 0 0 1)=(1 2 3 4)