做软件开发够久的话,就会注意到模式的问题。尚未讨论透彻的一个模式就是系统如何随着时间变化。软件行业总着眼于当下的风格,让我们失去了大局观。如今,大多数的“发明”都是数十年前所创造的,我们面临的大多问题也是别人解决过的。
软件开发者并不怎么了解我们的历史。本着这样的想法,本篇将阐述笔者对软件未来发展的看法,还有相关原因。
综述
开始前,我们需要定义一个术语:软件地心引力(Software Gravity),这是一种随着时间发展将功能、复杂性与资源引向软件系统的力量,正是它推动软件发展的力量。
软件会随着时间而愈加复杂,正是功能请求与用户期望构成了软件地心引力。这种地心引力为软件带来复杂性,复杂性又对资源形成需求。
块魂效应
我称之为块魂效应(Katamari Damacy,一款操纵王子通过不断黏住周围的物体来增加自己身前的球状物的大小),就像游戏中那样,功能持续增长,就像滚雪球一样形成了日益复杂的雪球。
从小而简单的东西,无可避免地成长为庞然大物。最终,功能雪球可能会被错当成月亮。这个球定期会变得太过复杂,导致工作无法进行下去,只能通过重构来适应现有的可用资源。由于这个原因,软件是按照某种可预测的方式来发展的。
软件发展阶段
初始阶段:人、纸、excel
第一阶段:简单的脚本
第二阶段:一堆文件
第三阶段:框架
第四阶段:超越框架
第五阶段:模块化
第六阶段:网络系统
所有的软件都处于其中的某个阶段。在阶段之间为了处理块魂效应,有一个自然发展的过程。随着系统越来越复杂,需要用不同的方式来解决问题。
复杂性决定了系统所处的发展阶段,进化的阶段决定了代码和团队具体的形态,不可反推。在这个过程中,反复出现的主题就是分而治之,各个击破。随着复杂程度增长,最有效的方式就是将大问题拆成较小的问题。
我经常看到程序员为了哪个工具才是最佳解决方案而争论不休。这与程序员在软件特定复杂度上的经验关系更大,而不是出于具体需求的需要。对所有系统来说,最好的语言、工具、数据库或框架并不存在。随着需求变更,方法也要产生相应的变化,否则只会浪费时间和金钱。
初始阶段:人、纸、excel
软件最开始并不是软件,大多数软件的存在是为了让现有的过程自动化,或者有效地沟通信息。软件最初的形态就是人们用纸和电子表格,还有其他的沟通手段来解决问题,这也是被我称为初始阶段的系统。
例如:复式记帐始于以纸张为基础的阶段。会计师会保存两套记录,并互相比对、去除错误。这也是我们称之为复式记账的原因。软件借用了这种形式,将它搬到电子表格、数据库和在线事务处理中。基本上这两者是一回事。电子记账系统速度更快、价格更低,不过结果是一样的。
无论系统有多复杂,都是在解决同一个问题。
在很多情况下,初始系统比复杂的软件系统更可取。例如:有100万个todolist应用。对我来说,要记录每日目标,没什么比得上一支笔、一个笔记本了。
软件必须提供重要的沟通功能,或将非软件解决方案自动化的益处,否则就不应该存在。要记住我们所处的行业就是改善沟通与自动化的行业,而不是写代码的行业,这样才能聪明行事。
第一阶段:简单的脚本
初始阶段的系统最终会变成软件,就从一个简单的脚本开始。
简单的脚本正是它该有的样子,从以脚本语言写就的单个文件开始,也许是PHP、Ruby、Python、Perl或者Bash编写的,这都没什么关系。这个简单脚本的定义是:一个有着单一目的、极少功能的单独文件,而不是一个面向公众的产品。它是一个手边能够解决问题的东西。
这个简单的脚本由单个开发人员创建与维护,代码风格完全由开发人员的经验与个性所决定。每个脚本都是不同的,不过应当很容易理解。快速demo或者概念验证应用都属于第一阶段。
在这个阶段,你可以将整个应用存在大脑里。应用应当很容易理解,也很容易debug。有用的脚本能够获取用户和功能需求。然后块魂效应就开始了。你的软件球开始四处滚动,获得更多功能。
功能复杂性是软件地心引力所引发的第一类复杂性。当简单的脚本变得太过复杂,需要将其切分为多个文件。然后会开始有着一堆文件的第二阶段,软件球继续成长。
第二阶段:一堆文件
在第一阶段,软件只为单独的目的服务。随着时间推移,软件地心引力吸引了更多功能。这些功能叠加起来,就不是简单的脚本所能解决的了,必须将其拆成一堆文件。这就是第二阶段。
我们将代码拆成文件,让它更易懂。大多人无法记住5000行代码,不过却能理解10个不同的500行代码的文件,将其分而治之。
短期内一个程序员还能对付这一堆文件,如果软件要足够有用,需要继续增加功能复杂性,就不是一个开发者所能搞定的了,此时需要围绕这个系统组建一支团队。团队成员会从“全栈”开发者开始成长,随着团队成长变得逐渐专业化起来。
在一开始,所有角色都由一名开发者担当,最后每个人都能担任专家。每个角色都有不同的头衔:设计师、后端开发者、前端开发者、项目管理者等等。
团队复杂性是软件地心引力所引发的第二类复杂性,始于第二阶段。随着功能复杂性的增长,引发了团队复杂性。团队复杂性的问题在于沟通过程。像敏捷软件开发之类的方法都是源自要解决团队复杂性的沟通问题。
第二阶段的应用都是围绕基于数据库的方法来设计。每个系统中都有一个相似却有些微不同的数据库组合,慢慢地会形成我们常见的模式。这个系统通常会包括数据库对象、系统流程控制与UI界面/系统,最终形成一个很像框架的草制版本。在软件发展中,这是很常见也很明显的。
随着功能复杂度的增长,开发者对重复劳动不再感兴趣了,而更着重于解决专门问题。随着团队复杂性的增长,想要沟通顺畅需要通用的语言和解决问题的工具集。
当功能复杂度与团队复杂度超出了一堆文件所能解决的范围,就会进入第三阶段,采用框架,软件球继续成长。
第三阶段:框架
框架是为解决常见问题并让沟通更顺畅而存在。一个框架就是一系列的约定还有数据库,合在一起来解决常见的问题。框架没什么特别的,只是减轻功能与团队复杂性的工具。以常见模式解决常见问题,团队就能发展地更快。
框架让雇人也变得更为简单。第三阶段的项目需要雇用的人才,需要具备的技能会根据框架的不同而不同。大多数专业的软件开发都是在第三阶段。项目基于框架构架不代表就是第三阶段的系统,第一和第二阶段的系统也经常从框架开始。
从第三阶段开始,公司会雇用专业的开发者来工作,他们大多很熟悉特定的框架。因此,他们会在不需要的时候也使用框架。人们更习惯使用熟悉的东西,而不是合适的东西。
与之前一样,团队和功能的复杂性会推动软件成为更大型的系统。在第三阶段末,可能会碰到第三种复杂性——数据复杂性。
数据复杂性是软件地心引力所引发的第三类复杂性。随着功能复杂性的增长,引发了数据复杂性。数据复杂性的问题常见于系统的架构、数量还有数据使用。
复杂的功能会导致数据结构的复杂性超出了框架的设计。在需要复杂或缓慢查询才能获取数据的复杂数据库模型中,会见到这种情况。大量的数据也同样存在问题,所存储的每一点数据都会拖慢系统,让它更难管理,最后我们会碰到“大数据”问题。
数据使用是第三种类型的数据复杂性。高流量或复杂的报告都需要单独的缓存或者报告用的基础架构。很多框架都不是按照这个理念构建的。随着系统复杂度的增加,会遇到框架的极限。此时项目需要转移到框架之上,进入第四阶段。软件球继续成长。
第四阶段:超越框架
每个框架都有极限,没有框架能解决所有问题。在某种程度上,功能、团队、数据复杂性都会推动你的系统,超出框架所能处理的范围。在第四阶段中不用改换框架。
人们很容易考虑改换框架,不过这样只能让复杂的问题转移。
第四阶段的界限很模糊,很难发现进入了第四阶段,下面列举了一些第四阶段的特点。团队开始“创建”或“探索”新的模式,旧的框架中并没有这种模式。这是发展的自然进程,会同时发生在系统的前端和后端。
一旦UI足够复杂,前端框架或单独的客户端应用就会出现。在后端,复杂度的增加会导致团队朝向内部服务对象、数据模型decorator及多数据系统的模式发展。另一个第四阶段的标志是:如果有资深开发者开始担忧架构的问题,或者开始阅读企业架构模式方面的书籍。
很多方面,第四阶段和第三阶段十分相似,两者的约定都少于第三阶段。项目会在第四阶段停留很长时间,在第四阶段,会遇到另一种复杂性——运营复杂性。
运营复杂性是软件地心引力所引发的第四类复杂性。随着整体复杂性的增长,出现了运营复杂性,它以基础设施和支持复杂性的形式出现。本来在一个服务器上运行的东西,现在需要一打服务器才能运行了。管理多个数据系统、备份、安全、更新等等工作会自成一体成为一个项目,这也是DevOps的来源。
随着复杂程度超过了个体能力,对整个系统产生深远影响之后,第四阶段触及到了一个极限。此时,单个开发者增加功能和修复bug的成本会高于所增加功能的价值。
很多人将这个系统称为“大泥球”。
一旦团队发现了自身所处的困境,就需要二选一,找一个解决办法。或者选择系统重构,或者将系统拆分成更小的部件。大型重构是个典型的错误,已经失败了很多次,请不要这样做。将系统拆分为更小的部件才是更好的解决方案。这也是第五阶段的方式——模块化。软件球继续成长。
第五阶段:模块化
当系统变得足够大,就不能在白板上进行推理了,需要拆分成更小的组件。我们使用一个奇妙的词:模块化,不过这其实并没有什么特别,只是分而治之而已。
在软件中存在自然行,可以围绕它们划出边界。拆分系统有两种办法,很多团队两者并用。
首先,将其按照不同的功能拆分。可能会有一系列像是报告系统、沟通系统、文件分享系统等等功能。此时项目到达第五阶段,系统中经常会有一打甚至更多不同功能的“模块”。
其次,可以将其按照所共享的基础设施进行拆分。可能会有用户验证、文件存储、图片处理、发送邮件、排队等等。像AWS之类的云供应商会按照这种模式来拆分。
在第五阶段,每个模块会有5到10人的团队。可能会有公共API团队、个人模块团队、移动应用团队等等。软件会通过增加团队和将系统拆分为5到10人能搞定的模块来成长。边界的存在允许团队独立工作,并通过约定的通讯协议与其他工具部件沟通。
一开始,模块化的系统会使用同一个代码库,用简单的文件夹结构来定义界限。最终,模块之间有必要出现更强的界限定义。经过自然发展,模块界限会成为物理上独立的系统。 随着模块拆分为不同的系统,系统也会随之进入第六阶段——网络系统。软件球继续成长。
第六阶段:网络系统
网络系统包括一系列较小的系统,彼此之间通过网络以公共协议的形式来通讯。
网络将较小的系统粘贴起来,以达成更大的目标。只要复杂度足够,所有的系统都会成为一系列较小系统组装而成的系统,来完成更大的目标。
在第六阶段,我们必须了解构建网络系统的规则。好的网络系统是由公共标准、优秀的文档加上易用性所定义。如果你曾经使用过公共API,就会了解这些东西的重要性。在第六阶段,每个系统都必须被当作公共API来对待。
随着系统平均复杂性的提升,网络系统更加热门起来。我们称之为面向服务架构(SOA)与微服务。这两种方式都是为了解决同一个问题。两者都创建了一系列较小的系统,彼此间通过网络来沟通。
成功的网络系统设计与架构与数据模型关系不大,而与组件间的设计沟通协议更有关联。在第六阶段,各系统之间语言和平台出现不同。对整体系统来说,各个系统在用什么语言根本没有关系,系统间的协议才是重要的。
想象一个网络系统的好办法就是想象一系列1-5阶段的系统通过网络协同工作。第六阶段的系统就像是几十个第二阶段或第三阶段的系统按照管弦乐队的方式共同协作。看一下类似谷歌、Facebook和微软之类的公司,它们的系统非常相似。软件球继续成长。
还有阶段七吗?
在网络系统之上,我并未看到什么有意义的进化。大型系统的基本模式就是组成一堆较小的项目,通过网络协作来解决问题。从大处看,就是互联网,这正是网络系统的定义。从小处看,就是各种服务甚至由较小处理系统组成的电脑硬件所构成的操作系统,这些较小的系统通过信息总线来彼此通讯。
如果确有网络之上的东西,我猜测是围绕网络进行更高级别的抽象,从而构成更大的系统。如果如今的网络系统就像汇编语言或机器代码的话,那么第七阶段就像是其上更高级别的语言。
在系统复杂性超出第六阶段所能处理的程度前,我不认为我们能看到第七阶段出现,到时我们不得不管理超级复杂的网络系统。
结论
我相信这个模式对于理解软件和我们的行为都很有用。很多决定在制定时都是基于对工具、人群和复杂性的错误理解。理解软件地心引力和块魂的基础概念之后,就能更好地在软件开发中作出决定。
在本文中尚有未提及的内容,包括现有的软件系统发展如何影响新的系统,还有在市场上软件是如何一并发展的。此外,本文并未涉及软件复杂性如何对全行业还有下游产业施加影响这一话题。也许今后会谈及更多相关的内容。现在回到软件球相关的内容吧,让我们看着它发展下去。 |