设计的正交性

从前,客户需要一个矢量(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)