互联网时代,究其根本,也是一个数据不断叠加的时代,而这对于开发者而言,又意味着什么?
以下为译文:
以下引用的是2006年Linus Torvalds说过的一句话:
我非常赞成围绕数据设计代码,我认为这是Git取得成功的原因之一......事实上,我认为一位糟糕的程序员与一位优秀的程序员之间的区别就在于,在他心目中代码与数据结构哪个更重要。糟糕的程序员关心的是代码,而优秀的程序员关心的则是数据结构及其关系。
这句话与2003年Eric Raymond提出的“表示原则”非常相像:
把知识叠入数据以求逻辑质朴而健壮。
与1989年Rob Pike总结的想法也很类似:
数据占主导地位。如果你选择了正确的数据结构,而且进行了良好的组织,那么算法几乎不言自明。数据结构才是编程的核心,而非算法。
还有1975年Fred Brooks在《人月神话》中说道:
数据的表现形式是编程的根本
创造出自精湛的技艺,精炼、充分和快速的程序也是如此。技艺改进的结果往往是战略上的突破,而不仅仅是技巧上的提高。这种战略上突破有时是一种新的算法,如快速傅立叶变换,或者是将比较算法的复杂度从n2 降低到n log n。
更普遍的是,战略上的突破常来自数据或表的重新表达——这是程序的核心所在。如果提供了程序流程图,而没有表数据,我仍然会很迷惑。而给我看表数据,往往就不再需要流程图,程序结构是非常清晰的。
如上所示,近半个世纪以来,很多伟人反复强调:关注数据。然而,有时我感觉人们忘记了伟人给我们的编程忠告。
让我举几个例子。
无法扩展的高度可扩展系统
这种系统的设计初衷就是为了处理CPU密集型负载,本来应该具有极佳的可扩展性。系统内没有同步操作。一切都是通过回调、任务队列和工作池来完成。
但是,这种系统存在两个问题:第一,毕竟“CPU密集型负载”其实并不是那么CPU密集,在最坏的情况下,一个任务就有可能耗费几毫秒。所以大部分架构上的决定弊大于利。第二,虽然这种系统听起来像一个高度可扩展的分布式系统,但事实并非如此,因为它只能在一台机器上运行。为什么?因为异步组件之间的所有通信都是使用本地文件系统上的文件完成的,这是目前所有扩展的瓶颈。原始设计并没有提及数据,采用本地文件的方式只是因为“简单”。大部分文档讲述的都是关于处理负载“CPU密集度”所需的所有额外架构。
面向服务的体系结构仍然是面向数据
这种系统遵循微服务设计,由单一用途的RESTful API组成。组件是存储文档的数据库(基本上是标准表单的响应以及其他电子文书)。这种系统自然会公开用于基本存储和检索的API,但很快就需要更复杂的搜索功能。设计人员认为将这类搜索功能添加到现有文档API将违背微服务设计的原则。他们认为“search”与“get/put”是不同类型的服务,因此他们的架构不应该将它们结合在一起。此外,他们计划使用的搜索索引工具与数据库本身是分开的,因此在实现时也需要此创建新服务。
最终只能创建一个包含搜索索引的搜索API,而该索引基本上是主数据库数据的副本。该数据会动态更新,因此任何通过主数据库API修改文档数据的组件都必须更新这个搜索API。在不导致竞态条件的情况下,REST API不可能做到这一点,所以无论如何,这两组数据都无法时刻保持同步。
不论架构图做出怎样的承诺,这两个API还是通过数据依赖性紧密耦合。后来人们认识到,搜索索引应该是统一的文档服务的实现细节,这样才能大大降低系统的维护难度。“仅做一件事”是数据层面的要求,而不是动作层面的要求。
荒谬的模块化与配置的混乱
这种系统是一种自动化部署流水线。最初的设计人员希望制作一个足够灵活的工具来解决整个公司的部署问题。系统可以编写为一组插件,配置文件系统不仅配置了组件,还充当了DSL,通过编程让组件嵌入流水线。
几年之后,这种系统变成了人们口中的“那个程序”——bug累累,却没人管。没人敢碰这些代码,因为都害怕破坏别的代码。没人使用DSL的灵活性。每个人在使用该程序时,都只是复制和粘贴其他人使用过的配置。
为什么会这样?尽管最初的设计文档使用了诸如“模块化”、“解耦”、“可扩展”和“可配置”之类的词语,但却从未说过任何关于数据的内容。因此,组件之间的数据依赖性最终通过全局共享的JSON blob的特殊方式处理。随着时间的推移,组件中就会出现越来越多没有文档记载的JSON blob或以前没有的内容。当然,DSL允许按照任意顺序重新排列组件,但大多数配置都不起作用。
经验教训
我选择第三个例子的原因是因为这个例子很容易说明。有一次我在做一个网站,但是我建立了一些很尴尬的XML数据库,所以最后也未能解决数据问题。然后,这个项目成了make的拙劣模仿,而且仅实现了make的一半功能,原因也是我并没有考虑我真正需要的东西。
【End】
领取专属 10元无门槛券
私享最新 技术干货