软件开发是一项复杂的工作,我们需要在抽象的系统上实现各种不同的业务逻辑,解决不同的技术问题,并且需要随时处理各种变化。如果缺少合理的设计,系统很容易变成一团乱麻。不过,好在有很多的设计原则、架构模式以及编程范式,指导我们应该如何设计架构和编写代码,比如SOLID原则、整洁架构、TDD、DDD等等。但是这么多原则,本身带来了相当沉重的认知负担,开发者如果没有丰富的经验和深刻的理解,很容易陷入教条主义,生搬硬套。因此,掌握一些基本的底层思想是非常重要的,而关注点分离,就是一个非常重要的基本思想。
软件系统的很多病症,如改动放大效应、无法测试、未知的未知等等,其病根通常都是没有做好关注点的分离。相对那些具体的编码实践、设计模式和架构原则来说,关注点分离似乎有点抽象,但它却是底层的通用思想,理解了它,能够加深对那些具体原则的理解,从而能够在架构和编码的实践中收放自如。
无处不在的关注点分离实现层面的关注点分离

在代码实现层面,有很多方法都基于关注点分离的思想,比如23种设计模式中的大部分,可以分为以下几类:
1)对象创建类:分离对象使用与创建。包括工厂模式、抽象工厂模式和builder模式。使用这些设计模式,将对象创建的复杂度从其他业务逻辑中解耦出来,可以独立灵活地调整对象的创建方式。其实一些依赖注入(DI)框架,做的事情本质上也是如此,只是更进一步,还对依赖关系做了控制,使得业务逻辑彻底不依赖对象的创建逻辑,至少从源码的依赖上是如此。
2)功能增强类:分离主要功能和扩展功能,或者桥接不同系统。包括代理模式、适配器模式、外观模式等。使用这些设计模式,在不影响主功能的情况下,添加各种扩展功能,比如日志、权限校验、限流等等,或者在不改动不同模块的前提下将彼此桥接起来。
3)松耦合类:分离不同关注点。包括策略模式、模板方法、观察者模式、迭代器模式、组合模式等。这类模式有的解耦主流程和实现细节,有的解耦做什么和如何做,有的解耦事件的生产者和消费者。使用这类模式,可以让代码井然有序,条理分明。
还有一些设计原则,也都是在践行这一基本思想。面向对象的五大设计原则(SOLID),其底层更加通用的思想就是关注点分离;高内聚低耦合的前提和基础也是要先做好关注点分离——相同关注点的逻辑要内聚到一起,不同关注点的逻辑要尽可能分开;切面编程的思想(AOP)也是如此,对于一些横切的关注点(如日志、权限控制、限流,甚至缓存等)都可以用切面的方式收敛到一处,避免散落各处或是在不同地方重复。
还有一个典型的例子。前端开发中,页面结构(html)、交互逻辑(js)和页面样式(css)是三个不同的关注点,一般都会分开维护;另外,ReactJs和Angular等前端框架提供的组件化的能力,让开发者可以把复杂的页面逻辑划分成不同的技术和业务组件,从而实现更进一步的关注点分离。
架构层面的关注点分离
架构的本质就是划分系统边界,以及设定跨边界的方式。在高层次上识别系统主要的关注点,并将它们划分成不同的系统、模块或组件。
以常见的分层架构举例,用得较多的是自上而下的分层架构,这是一种从技术视角出发所形成的一种架构模式 ,把系统划分为三层:表示层、应用层,数据访问层。不过,这种分层方式,还是有一些问题,比如层与层之间的耦合,扩展性不足等。这些问题的本质在于,这是一种一维的分层架构,对于关注点的分离不够彻底,很难处理一些复杂的依赖,彼此之间还是存在一些不合理的耦合。现实世界的问题通常是多维的,用低维的模型去理解和解决高维的问题,往往不够理想。另外这种分层是根据控制流来分的(上层依赖下层),但是源码上这样的依赖关系往往并不合理,从而引入耦合。
还有一种由内而外的分层架构,这是以业务为主视角所形成的架构模式。比如Blob大叔的“整洁架构”和Alistair Cockburn提出的“六边形架构”。它们的本质都是将核心业务逻辑与实现细节分离解耦,把重点放在业务逻辑上,并保持业务逻辑的独立与稳定。这里的实现细节不仅仅包括技术细节,也包括那些不那么核心的业务逻辑。这种分层模式的关键在于核心的关注点(业务逻辑)在源码上并不依赖那些非核心的关注点(web层和数据访问),而是反过来,这种分层模式通常需要在内层模块定义自身需要的接口,由外层模块提供接口的实现。
图1 整洁架构(图片来源:《架构整洁之道》)
分层架构的实践不仅仅限于业务系统,事实上,很多基础和底层技术都用到了分层,比如RPC框架dubbo和TCP/IP协议栈,每一层都是一个不同的关注点,为上层提供服务,屏蔽底层细节和复杂性。
图2 网络协议的分层架构 (图片来源于网络)
实际业务系统的架构,仅仅采用水平分层是不够的,通常不同的业务场景也是一个个不同的关注点,因此,还需要按照业务场景的不同进行垂直拆分。如下图所示,这是一种典型的业务系统的拆分模式:
图3 水平分层 + 垂直拆分
至此,我们看到,需要在两个维度上进行关注点分离。
随着业务的演进,系统的功能会越来越多,越来越复杂,这些场景可能比较稳定,但是会出现很多不同的业务形态和玩法,拿下单举例,最初可能只有一种下单流程,慢慢地会出现抢购、拼团、兑换等不同的下单形态,每种形态都有各自不同的特殊逻辑,更加复杂的是,这些玩法的特殊逻辑不仅限于下单这一个场景,可能其他场景的逻辑(比如结算页)也都有各自不同的特殊逻辑。这些不同玩法各自都形成了一个关注点,彼此之间需要分离,更重要的是,需要跟那些稳定的场景分离。现在,架构层面的关注点分离走进了三维世界。感兴趣的可以阅读我之前的一篇文章(复杂业务系统的痛点、挑战和解决之道)。
架构之道
软件开发和架构领域有许多金玉良言,比如分离控制与逻辑、分离业务和技术,分离主体和细节等等,其背后的思想都是关注点分离,区别无非在于要分离的关注点分别是什么,采用什么样的方式来分离。在不同层次,以不同视角看问题,要分离的关注点就会不一样。不同的关注点,其变化原因、变化方式、变化频率不尽相同,出于可理解性、可维护性方面的考虑,应该分开考虑、解决和维护,彼此间的耦合应该尽可能少且简单。
为什么关注点分离如此重要一句话概括:因为它符合我们的认知规律,能够降低认知负载。
图4 认知负载 (图片由AI生成)
这源于我们大脑的两个局限。
第一,大脑在理性和逻辑思维方面,是单线程的,每次只能专注于一个任务。不信的话,可以试试那个古老的挑战——左手画圆形,右手正方形,同时进行。一次性考虑多个概念会让大脑认知超载,频繁地在多个任务间切换也会极大地降低效率。
第二,大脑的短期记忆能够同时处理的概念数量非常有限,一般认为是7个左右(大致数量范围是5到9),如果觉得不可思议,可以尝试心算下67 89,感受下仅仅是临时记住这几个简单中间结果有多难。
因此,解决问题的好办法,就是把大问题拆分成小问题,一次只解决一个小问题。这个小问题既要足够小,不至于产生过多的概念,又要足够独立,不能跟很多其他问题耦合,导致我们分心。这些要逐一解决的小问题,就是所谓的关注点。
所以,一个具体方法只做一件事,系统要模块化,要划分领域上下文,架构要分层等等,都是这个思想的具体实践,都是在不同的层次分离不同的关注点。
其实,关注点分离不仅适用于软件开发,对于几乎任何工作,也都适用,它是一个普适的思想和法则。比如演讲和写作中常用的金字塔结构,就是要把不同的论点(关注点)按照一定的结构组织起来,每个章节或者段落专注于某一个论点。不着边际、东拉西扯或者试图一句话表达多个意思,只会把你的听众或读者弄晕。
总结关注点分离是对抗软件复杂度的有效方法,是实现软件可维护性和可扩展性的良方,是众多软件设计和架构原则的底层思想,是每一个程序员和架构师都应该掌握的内功心法。深刻理解这个思想,将其牢记于心,就可以忘掉那些具体的模式和原则了,然后随心所欲,游刃有余~
下篇文章,我会深入聊一聊关注点分离背后的认知规律,以及对软件开发工作的启示,敬请期待~
原文出处:https://mp.weixin.qq.com/s/gfF5bUWfXDRGva6vL63Awg
更多精彩文章,欢迎关注微信公众号:技术凌云