gitbook/软件设计之美/docs/269714.md
2022-09-03 22:05:03 +08:00

137 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 31 | 应用的设计:如何设计一个数据采集平台?
你好,我是郑晔!
上一讲,我给你讲了 Moco 的设计,这是一个程序库级别的设计。除了开发一个程序库,在日常工作中,还有一种工作是程序员们非常熟悉的,就是开发一个应用。与之对应的设计就是应用设计。
也许你会说,应用设计不就是按照之前讲的 DDD 的方法,先通过事件风暴建立通用语言,然后,再找出子域和划分出限界上下文,最后,再按照模板找出各种对象吗?
是的设计的基本过程确实是这样的。不过DDD 的方法只能保证我们设计出一个可以接受的方案。**如果你想有一个更有扩展性的设计方案,就需要多花一点时间去构建一个更好的模型**。
这一讲,我就以一个金融指数系统为例,给你讲一下如何更好地设计一个应用。
## 一个指数系统
在金融系统中,有一个概念叫指数,用来表示金融市场的活动,比如有股票指数、期货指数等等。比较著名的指数有道琼斯指数、标准普尔指数。这个世界上的指数多得数不胜数,每个金融机构都会有自己的指数,而且,它们还会不断推出新的指数。
那指数是怎么算出来的呢如果以股票为例就是获取一堆股票的价格然后根据一个公式算出一个结果。比如我们有一个公式A_0.2+B_0.3+C\*0.5,我们把公式里的数据部分称为指标,也就是公式中的 A、B、C这个公式表示这三种指标分别占比20%、30%和50%。
这个公式就是三个不同的指标按照不同的占比进行求和。假设A指标的价格是5元、B指标是2元、C指标是1元按照公式可以算出5_20%+2_30%+1\*50%=2.1这个算出来的2.1就是指数的值。
价格是实时变化的,而公式是固定的。指数在问世之初,我们需要不断调整这个公式里面各个指标的参数,以便能更好地反映市场的变化。问题来了,我们要怎样设计一个这样的指数系统呢?
一个不假思索的设计就是,针对一个具体的指数进行开发。我们就要把指数计算中涉及的各种数据实时取过来,然后根据设置的公式去做计算。
![](https://static001.geekbang.org/resource/image/37/28/37dyy618b2c70cc30b32cc93ff0f3d28.jpg)
如果我们只有一个指数,这么做也许是可以接受的。但我们要开发的是一个指数系统,这意味着我们会有很多个指数。两个不同的指数可能会用到同样的指标,如果我们按照开发一个指数的方法,不同的指标数据要获取好多遍,从某种意义上来说,这就是一种重复。
所以,一个好的做法就是,**先做职责划分,把不同职责的部分划分出来**。正如我在这个专栏中一直说的,我们不能把各种不同的关注点混在一起,这是很多系统出问题的根源所在。
那从上面的需求描述中,我们可以把指数的计算过程分成两个部分:
* 一部分是需要实时获取的数据,比如,前面说到的各种价格;
* 一部分是根据公式进行计算出最终的结果,也就是指数最终的值。
![](https://static001.geekbang.org/resource/image/73/8b/73224c9763993abc6665d6980d75418b.jpg)
这种拆分解决了前面设计中存在的问题,使得指标数据获取和公式计算分开了,同样的数据就可以用在多个公式中,数据的获取和公式的计算就不用同步进行了。
而且,把计算过程拆成了两个部分之后,我们就可以针对这两个部分,分别进行细化了:
* 对于指标数据获取的部分,我们要解决数据获取可能出现的问题,比如,不同的数据来源如何管理、不同数据源的数据格式是怎样的、如果数据源不可用,我们该怎么办等等;
* 对于公式计算的部分,我们关心的问题则是计算要用到哪些指标、每个指标当前可用的值是多少、如果公式中有不可用的指标数据时,系统该怎么处理等等。
既然我们把系统拆分成了两个部分,还有一个问题就是,如何把这两个部分连接起来。其实,指标数据获取部分的输出,就是公式计算部分的输入,那指标数据获取部分的输出是什么呢?
我们在前面分析过,指标数据获取要实时获取,无论采用轮询的方式,还是采用数据上报的方式。这种数据的特点就是,它有一个值,还有一个时间。正是因为这种特点,数据会形成一个序列,所以,我们将这种数据称为**时序数据**。
指标数据获取部分的输出其实就是这种时序数据,只不过,针对每一种指标都会产生一个时序数据序列,而这些不同的时序数据也正是公式计算部分的输入。
既然是时序数据,也就有了时间的信息,我们的公式计算部分就可以根据时序数据的时间做一些处理了。比如,怎么判定一个指标不可用呢?如果判断一个指标最新的数据与当前时间的差值过大,我们就可以判断在这次计算中,该指标的数据不可用。
有了对于时序数据的认识,结合前面所说的数据获取和公式计算不再是同步进行的这一点,指标数据获取和公式计算两个部分就完全解耦了,二者之间可以只通过时序数据进行交互。
## 更上一层楼
现在,我们已经把数据获取和公式计算分成了两个部分,这应该是常规设计中都可以想到的。很多设计者做设计也可能就此打住,开始动手写代码了。但是,有时候我们还可以更进一步。
我们可以继续分析一下,看看还有什么可以进一步改进的地方。
我先问你一个问题,公式计算你打算怎么做?你可能会想,这难道不是业务人员给我什么样的公式,我就用写代码的方式把它实现出来吗?
这么做肯定是可以把公式实现出来,这一点是毋庸置疑的。但是,正如我前面所说,指数往往要经过一个调整的过程。因为业务人员自己也常常不确定设置的参数是否合理。
用写代码的方式实现公式,也就意味着,每次业务人员要调整一个参数,你都需要去改代码。在可以预见的未来,你的工作基本上都会与调整参数相关,而这件事一点技术含量都没有。
对我们程序员而言,**一件事是不是有技术含量往往不取决于事情本身,而取决于我们怎么做它**。换言之,问题是一样的,但不同的解决方案却会带来不同的效果。**业务人员提出的是问题,解决方案是由技术人员给出的,千万别混淆问题和解决方案**。
当你可以预见一件事将来会很繁琐、会不断重复,而且会持续相当长的时间,这时候我们就需要重新审视我们的解决方案了。
![](https://static001.geekbang.org/resource/image/1e/7a/1eed9cb9d3595170473316b07fe3677a.jpg)
最原始的解决方案是没有自动化的方案,对于任何一个系统而言,我们最好要知道没有自动化的时候,这个问题是如何解决的。这和我们前面说到的,理解一个模型来龙去脉的思路是一致的。
当然,我们现在大多数情况下接触的都是一个已经自动化的方案,但方案之间还是存在着差别。在自动化方案中,最原始的做法是开发人员自己修改代码的方案,这种做法会导致开发人员大量的时间投入,属于严重消耗时间的做法。
其次是开发人员修改配置,虽然这种做法只修改配置,但通常还会涉及到重新打包发布的过程,只能说它比修改代码要强一点。
比较好的做法是,业务人员修改配置,开发人员完全不参与其中。一方面,业务人员自己最知道自己想要什么;另一方面,没有开发人员的参与,反馈周期就缩短了。
虽然这几种方法在业务的角度是越来越好的,但在设计上,却是要求越来越高的。比起没有自动化的方案,自动化的方案需要投入一些力量去做设计。相比于修改代码,修改配置就意味着要留下扩展的接口。而能够做到让业务人员而不是开发人员修改配置,配置的接口就应该是一个业务的接口,比如,要有一个配置界面。
如果我们用这几个标准评估一下我们现在的方案,显然,我们现在的方案还处于开发人员修改代码的阶段,这说明我们还有向上努力的空间。不过,我们给出的只是一个衡量标准,并不意味着这个台阶要一步一步上,因为我们可以一步就提升到最高标准,一步到位给业务人员提供一个配置的接口。
问题来了,我们要给业务人员提供一个配置接口,它应该长啥样呢?
我们知道,这个指数设计的关键就是这个指数的公式。在前面的那个例子里面,它的公式是 A_0.2+B_0.3+C\*0.5。如果我们能够让业务人员在配置接口上这样配置,问题就解决了。
在这里A、B、C 分别代表一个指标,也就是说,我们只要能够让业务人员指定指标以及指定计算公式,剩下的问题就简单了,就是根据公式计算出相应的结果就好了。
说起来很简单但怎么把A_0.2+B_0.3+C\*0.5变成一个可执行的公式,对一些程序员来说,还是有一定难度的。解析文本执行这件事是编译原理的基本功,不过只要你能理解这里需要一点编译原理的知识就很可以了,如果欠缺知识,就去学习相关知识好了。
实际上公式的解析是编译原理入门的知识难度系数比设计一门程序设计语言要小多了。而且现在有编译器前端的工具比如Java 世界的 [Antlr](https://www.antlr.org/),它可以直接生成对应的语法树结构,我们只要负责去编写对应的执行部分就好了。
也许你发现了,我们实际上已经构建出了一门 DSL一门属于指数计算这个特定领域的外部 DSL。前面讲 DSL 的时候,我们就说过,把设计做到极致就可以构建出一门 DSL。在这里我们也看到了了解 DSL实际上也给我们增添了一个可以前进的方向。
把公式构建出来之后,我们仔细分析,还会有一个有趣的发现。你可以想一下,公式计算的结果是什么?因为我们说,它是在利用多个指标的时序数据做计算,所以它得到的结果,其实也是一个时序数据。
这样,我们发现了另一个有趣的事,公式计算的得到其实也是一个指标。如此一来,公式计算的结果也可以作为另外一个公式的输入,形成更为复杂的复合公式。显然,由于复合公式的出现,这个系统的处理能力又上了一个台阶。
不知道你是否想起了什么,没错,它和设计模式中的组合模式如出一辙。你看,我们学习到的基础知识在这里都用上了。
![](https://static001.geekbang.org/resource/image/16/70/16fbf23d1663442f46c54c275dc51070.jpg)
虽然我们这里讨论的是一个金融中会用到的指数系统,但当我们把模型经过一番整理之后,你会发现它不仅仅局限于指数系统中。比如,如果你在开发的是一个物联网系统,上报上来的数据,往往也要经过一些计算和聚合,那这个模型显然也是适用的。
再比如,你开发了一个 APMApplication Performance Management应用性能管理类的应用采集上来的数据往往也要经过一番计算再展示出来这个模型同样适用。
所以,当我们可以构建出一个好的模型时,它本身就有着更大的适用范围。
## 总结时刻
今天,我通过一个指数系统的应用给你讲了一个应用的设计过程。在这里,你知道了想要做好设计,目标就不能局限于只把功能实现出来,而是我们要去不断发现可能存在的各种问题。
简言之,只要你认为会出现重复,它就是一个值得我们去思考解决的问题。
我还给你讲了如何衡量应用的设计水平,就是看它符合下面哪个标准:
* 没有自动化;
* 开发员修改代码实现;
* 开发员修改配置实现;
* 业务员修改配置实现。
程序员常常给人写代码,实现自动化,却常常忽略了自己工作中可以自动化的部分。作为[一个懒惰的程序员](https://time.geekbang.org/column/article/86210),我们需要发现日常工作中繁琐的地方,让自己从低水平的重复中解脱出来。**一件事是不是有技术含量往往不取决于事情本身,而取决于我们怎么做它**。
这两讲我们讲的都是怎么去设计一个新东西,但在实际工作中,有时候,我们还会面对一个既有的系统,这样的系统该如何改进呢?我们下一讲来谈。
如果今天的内容你只能记住一件事,那请记住:**一个更好的设计从拒绝低水平重复开始,把工作做成有技术含量的事情**。
![](https://static001.geekbang.org/resource/image/a1/5f/a1493ea6b7afba5e9827c4caef5fb45f.jpg)
## 思考题
最后,我想请你回想一下,参照今天的内容,在你现在的工作中,有哪些可以从设计上改进的内容呢?欢迎在留言区分享你的想法。
感谢阅读,如果你觉得这一讲的内容对你有帮助的话,也欢迎把它分享给你的朋友。