.. Kenneth Lee 版权所有 2019-2020
:Authors: Kenneth Lee :Version: 1.0
不为天下先2
前几天评审了一个设计文档。里面的反映的构架设计问题很普遍,我们来深入分析一下这 个问题。
我说这个问题很普遍,但反映在每个问题上,那个问题本身会有点复杂,我需要读者有点 耐心来理解一下这个问题。
比如我有一个业务流定义,为了让这个业务流足够灵活,我们支持用Python来定义它。所 以实际上我们会把这个定义写成一个类似这样的脚本:::
#ProcessDefinition.py
a = Process(...)
a.define1(...)
a.define2(...)
a.define3(...)
...
a.run_process()
这里简化了,实际中我们的defineX可以让工作流循环,跳转,分支什么的。
好了,现在我们想调试我们定义的Process,所以,我们做了一个PRODB(其实可以叫PDB, 只是和Python自己的db冲突了),类似GDB那样,可以对执行过程进行单步,查看工作流工 作状态什么的。这个PRODB的命令行接口定义成这样:::
PRODB <ProcessDefinition.py>
(断点设在Python语句上,然后转化为对工作流的过程暂停)
我对这个设计非常不爽,我们对此有这样一个交流:
我:这个设计不合理。
设计者:怎么不合理?
我:……要不我直接挑破绽,比如我在一个脚本中写了两个Process,你这样写命令行,到底调的是谁啊?
设计者:我定义了呀,调试最后一个。
我:哪个是最后一个呢?最后一个定义的,还是最后一个生成的?
设计者:最后一个调用run_process的
我:我创建两个线程,每个线程里面创建一个Process,都调用run_process,你调试哪一个?
设计者:他非要这样弄,就调试最后一个调用run_process的
我:我不管,我不喜欢这个设计,你给我弄成在ProcessDefinition.py中直接调用a.start_debug,……
设计者:好吧,If you insist...
这个过程中,我特蛮横,特没有道理。但我认为,其实我是有道理的,但我的道理是个构 架道理,或者说是哲学道理,双方没有这个对齐的基础,我也无法在面对面的交流中把这 个道理说出来。
他这个设计的问题是什么呢?xdb program这个模式,是借鉴了gdb的应用模式。而gdb这种 模式,已经发展出了非常多的细节,这些细节,是我们采用这个模式的信心所在,以后我 们在增加新功能的时候遇到困难,我们只要参考gdb的细节就可以解决。只要你还是这样输 入,被调试的那个还是一个“流程”,gdb大部分行为都是可以借鉴的。
这好比具有某个属性的种子,我们很难想象它长大了是什么样子,但如果我们已经知道原 来一样的种子长大的样子了(关键是知道它能长大),而我们又希望和它一样,那么,那 个种子的种植方法,就应该是我现在采用的方法,除非我受到其他条件的限制。
xdb program这种方法的本质,是program定义了一个执行流,xdb对这个执行流进行控制, 让它单步执行。而PRODB Definition的做法,对此隔了一层,看起来用法很接近(而且现 在也没有引起直接的问题),但如果你没有办法在Definition脚本和Program(Process) 之间建立一个一对一的关联,那么他们之间的Gap就可以导致无穷无尽的新约束,我刚才的 讨论中已经给出一种新约束的例子(一个script多个program)了。但我关注的并非那一个 约束,而是我认为会有更多的约束,比如我中间对a进行a.copy()了,或者我在脚本中启动 了Python自己的pdb了,或者我再抽象一层,实现了Process的一个子类,然后让这个子类 作为a的一个参数了,等等等等。但我根本就不想用这种例子来反驳他,因为每个可能他都 有“现在没有”或者“这种情况不存在”来反驳,我也可以找到某些“存在”,“有可能”去再驳… …但这整个推演的过程,其实都是我不希望的,我明明直接把xdb指向那个process就可以解 决的问题(没有gap了),少了很多需要推演的工作,我吃饱没事,在现阶段浪费这个时间 干什么?
他这个设计的关键问题在于,用Definition取代Program是他个人的一点点欲望,背后是没 有足够的利益支撑的,演进的初期,一点点欲望,如果仅从“现在”会有什么问题上讨论, 它不会有问题,因为本来就不会有什么问题。但架构不是这样考虑问题的。架构也不过是 设计,它能比别的设计起更大的作用,是因为架构的设计是求大于其细,抓住软件发展的 初期这个机会,错过这个机会,所有的细节上来了,就不再有架构设计的余地了。
所以,其实设计都是为了某个目的引入新的约束,架构引入的约束就必须有非常坚挺的逻 辑要素去支撑,最坚挺的是事物自身的属性,第二坚挺的是已经成熟的经验。在前面这个 例子中,既然调试是指向一个流程的,我们在语义逻辑上就必须让它指向一个流程,这样 我们后面发展的时候,总可以使用成熟的经验。但如果我们每次都要说:当你调试这个定 义脚本的这条语句的时候,相当于你在执行流中那个步骤中如此这般……你就平白引入了风 险。
架构的关键目的常常就不是说某件事情一定会发生,所以我采取某某措施来解决它。架构 的关键目的是我根本不知道某件事情会不会发生,但我要主动采取策略去降低它发生的可 能性。所以,所谓“不为天下先”的策略,就是我总使用“有成功经验”的方案,这样我成功 的机会就变大了。
如果你总用“现在有什么问题”来讨论,这个问题就变得没有结论了。但如果我接受这个设 计,架构就已经被破坏了,这又是我不能接受的。