1.4 架构的形式与特点
1.架构以文档和代码呈现
我们一般说的架构既包括架构的设计过程,又包括设计的产出物,可以是各类设计文档、设计图,也可以是一些技术验证代码、Demo或其他相关程序。文档的目的在于准确记录我们的思维产物,在软件尚未实现时,作为指导蓝图,尽量精确地描述清楚软件。在软件的实现过程中,可能随着我们的深入研究,根据具体情况对文档做出局部的调整和修改。在软件已经实现以后,部署运行的软件实例和代码只能说明软件目前是什么状态,却无法告诉我们这个软件系统是如何从开始设计,慢慢变成现在看到的样子的,这个思维的过程和中间做出的很多决策的信息丢失了。一个软件系统的长期稳定发展,必然需要一个可靠的、随着软件本身的维护不断同步更新的文档作为每次变更的出发点。这样我们可以随时沿着架构相关的文档逆流而上,了解这个软件系统从整体到具体的设计思路。同时,文档作为结项或交接的一部分,也是整个软件项目的产出物的一部分,成为公司IT资产的有机组成部分。
其中一个架构图的例子如图1-3所示。
文档是设计的载体,代码是系统功能实现的载体,技术和业务最终都有很大一部分体现在代码里(技术的另外一部分是部署运维,即如何最终把这些代码应用到设备上;业务的另外一部分是操作流程,即如何应用到系统与人的交互上)。广义上来说,代码和代码里的注释都可以认为是文档的一部分。技术社区有一种观点:结构良好的、可读性强的代码,是最好的“文档”。
图1-3
那么怎么才能写出好的代码呢?关键在于两个词:经验、重构。GoF的23个设计模式是在面向接口的编程环境中,处理一些常见问题的代码编写经验。通过灵活应用这些模式,我们就可以在处理各种一般问题时进行抽象和总结,进而写出结构良好、可读性强,并具有一些灵活性的代码。如果是面向企业应用领域的系统,那么企业应用架构模式可以供我们参考。如果是面向多个系统的集成领域,那么企业集成模式和各种中间件可以帮助我们更好地处理问题。
技术性工作里存在一个“一万小时理论”:在一个领域里需要持续实践一万个小时,才可能成为该领域的专家。编程也是一个这样的领域,随着我们代码写得越来越多,维护性的代码改得越来越多,我们就能总结和沉淀出很多经验,变成自己的编码风格和习惯。在这个过程中通过实践和思考,不断地提升和发展自己的技能,进而反馈到代码中,我们可以认识到以前代码的不合理之处,不断地重构和改善既有的设计与实现。
从代码里,我们可以很直观地了解到,程序做了什么、不能做到什么、能做到什么程度,以及其与相关的文档(包括业务文档和技术文档)是否一致。但是代码不适合作为唯一的“文档”,只有代码没有其他文档,就像是一部只有结尾没有开头和过程的电影。我们只能了解这个系统的一个时间点的切面影像。所以,在设计类文档和代码注释里,很多时候描述清楚为什么(Why)和怎么样(How)比单纯描述是什么(What)重要得多。一个具体问题的技术选型,实现一个业务模块的某个具体功能点,其实都是在做技术相关的选择(Choice),而我们做一个技术选择的时候需要考虑:我们面临的问题是什么,有几种可行的方案,各有什么优势和劣势,选择哪个方案最适合目前的形势,并可以兼顾一下未来一段时期的发展,等等。如果我们没有留下来任何思考的痕迹,那么这些思想过程的智慧就会在软件的创造过程中丢失。随着时间的流逝,我们只能看到系统最终的样子。这种信息缺失对于目前大规模软件开发的协作过程非常不利。例如,我们看到一个8年前的遗留系统里,有一“坨”代码非常烂。我们改了一下,过段时间发现运行不正常了,回过头来再细细分析,发现业务逻辑需要优化,框架也需要调整,不然这个地方的代码会很别扭。很多时候,我们要是直接能看到以前文档描述了当时做的选择和权衡,就可以避免很多的“坑”,降低很多沟通成本,特别是涉及团队成员的变动和跨团队的协调成本。一般的知名开源项目(比如Apache里的各个活跃项目),都会在讨论组里进行技术选择的讨论,并以帖子和回复的方式,进行问题讨论的发起、提案、阐述、投票,最终确定其中的某个处理办法。这样大家既达成了一致,也留下了所有的思考和辩论痕迹。
2.架构服务于业务
正如十九世纪的伟大建筑师路易斯·沙利文(Louis Sullivan)倡导的建筑设计著名格言:“功能决定形式(Form follows function)”,软件架构首先要服务于业务功能。
设计一栋大楼不管美不美观、大气不大气,首先需要考虑的是这栋大楼是做什么用的,是要开一个百货公司,还是一个跨国集团的总部大楼,还是当地市政府的办公大楼。同理,架构首先也需要对业务负责。而业务并非总是一成不变的,随着市场环境的变化、用户习惯的变化、竞争格局的变化,业务形态也一直在顺应环境而改变。架构设计也需要考虑系统在未来一段时间内能支撑这种调整,并且在满足业务需求和一定的前瞻性的基础上,综合考虑成本、周期、效率、速度、风险等因素。
3.架构影响研发团队的组织形式
业务拆分的方法和技术框架的选择必然会影响研发团队的组织形式。业务拆分得越细致,越有利于我们更好地对项目的各项指标进行量化和计算,更精确地估计工时和成本,从而指导每个小组应该分配多少资源,使用什么样的协同和任务确认形式。随着项目的推进,计划与实际情况之间的匹配程度也可以随时进一步精确调整,进而我们应该对每一块任务的投入资源进行动态调整。技术框架的选择也一样,选择最合适的框架,比如对于Web系统采用最大众化的SSH或SSM,既有利于我们迅速找到合适的程序员组成新的研发团队,也因为比较成熟、“坑”比较少,可以在开发过程中避免很多问题,节省一些填“坑”的时间;针对一个具体的业务领域,采用一个功能很强、场景很贴近、较新的技术,则需要找到一些熟悉这些技术的人,或者培养几个人,这就增加了时间成本或人力成本。
反过来,研发组织的结构和成熟度也会对我们最终所采取的技术架构产生重要的影响。比如,一个由3个初级程序员组成的创业小团队,就不适合采取特别复杂且小众的开发框架。相反地,利用快速开发框架或脚手架把产品设计迅速实现应该是团队的核心诉求,所以某种全栈类的全家桶解决方案可能才是最适合的技术选择。以前我(本章作者)带的一个新组建的初级前端程序员团队就有类似问题,团队成员一直关注于各个美轮美奂的具体UI组件,这些组件可能是不同的技术框架衍生出来的。而我们要做的是一个业务系统的后台,炫酷的UI不是重点,组件较全、方便易用、有大量的案例,少量定制需求可以自行解决,这些才是更重要的关注点。注意到这个情况以后,我立即调整了团队的技术方向,选择了目前最流行的一个前端框架作为解决方案,最终取得了不错的成效。
4.架构存在于每一个系统
每一个已经实现并运行的系统,都是特定架构设计的载体。有些系统对应的架构,有详细的设计文档;有些系统的设计文档残缺不全,甚至还因为在系统发展变化的同时,文档没有更新,导致设计文档与实际系统不符;有些系统干脆就没有设计文档。但这些系统都是基于一定的架构来创建的。就像是优秀的老工匠,制作瓷器之前可能并不会把形状规格画到图纸上,但也能做出来一个漂亮的陶瓷花瓶。因为所有的设计细节,哪个地方需要上釉,都在老工匠的脑子里。可是这种没有显式设计的“脑内架构”方式,明显有很多缺点,例如,不能大规模进行工程化方式处理,老工匠无法通过讲述自己的想法就让学徒也做出一个同样尺寸规格的花瓶,甚至老工匠自己也无法精确复制自己的上一个作品。
5.每种架构都有特定的架构风格
每种架构方式、每个具体系统内所体现的架构设计,都可以被工程师理解,进而提炼出一些架构思想和设计原则,这些思想和原则就是这种架构方式的风格。依据这些风格,我们可以将各种架构方式分门别类,从而进一步讨论每种架构风格的特点。例如,在实现期的代码形式中,系统由各个相似的类库(作为组件)构成,在运行期这些组件又同时在同一个进程中,这时我们可以认为这是一种“组件式单体架构风格”,多种不同的架构风格将在1.1.6节讨论。
6.架构需要不断地发展演进
随着计算机软硬件的不断发展,软件架构思想也在不断地发展变化。另一方面,软件为其提供业务处理和服务能力的每个具体行业领域也在不断发展变化,业务处理流程和业务形式不断地推陈出新。这就要求我们在系统架构设计时,保持终生学习的精神,持续吸收新思想、新知识,贴近一线业务群体,随时因地制宜,调整架构设计,采取最适合当下场景的解决方案。
同时对于存量旧系统的维护与改造,很多时候无法一次完成目标,可以考虑循序渐进,设定几个大的里程碑,逐步推进,最终实现比较理想的架构设计。6年前,我在阿里负责一个遗留系统的重构,40多万行代码,没有人清楚地了解项目是什么情况,也没有一篇文档。4个架构师“小黑屋”封闭开发,先自己摸爬滚打搞清楚需求,然后采用“分布式服务化+灰度发布”的办法,先拆出来并重新实现搜索系统,再拆出来订单系统,最后“搞定”产品系统。做重构的同时还承接新的需求,经过了几个月的改造,代码减少了一半,性能提升了几十倍,成功扛住了当年的“双11”活动,并且培养了几个能完全掌握这个新系统的核心研发人员。这个过程对我影响最深的一点就是:循序渐进,终达目标。特别是在具体的设计实践中,思路和方法,比结论更重要。一个正确的结论在别处可能就是错的,但思路和方法是可以复用的。