抽象之美

引言

为了实现架构优化、降低耦合,IT行业提出了很多思想。有设计模式、架构模式,再到面向接口、面向服务;还有用例驱动设计、领域驱动设计。然而个人认为:上述这些都只是纯技术上解决耦合的办法。所有我将耦合分为技术耦合业务耦合。后面我会结合阐述两种耦合的关注点。以及经验驱动与抽象驱动的理解。

1 技术耦合到业务耦合

1.1 概述

在互联网开源大潮下,有很多开源框架。例如redis、dubbo、zookeeper、spring、spring boot等。这些框架都是与具体行业、具体业务无关的。这些框架除了提供底层的技术支撑,还从技术上帮我们解耦,辅助我们剥离业务。例如spring的IOC和AOP。
此外,很多开发思想也在指导我们如何解耦。例如设计模式、架构模式、面向对象、面向接口、面向服务等等。

1.2 问题

这些技术框架和开发思想从技术给出了一个解耦方法,但是,从业务上解耦还是有很多问题要解决,这也是最难的。
例如微服务架构,它隔离了功能提供者与功能使用者,功能提供者和使用者通过服务接口交互信息。但是从业务上,两者之间必须定义边界明确的服务接口,你的服务接口设计的是否合理、是否能自适应将来的需求变化;此外各个服务怎么划分等等。还是有很多需要考量。
如果照搬套路,那么可能的结果是:你的代码看上去结构完美,但是,一旦业务调整可能会大面积推翻接口。所以,无论是面向接口也好,面向服务也罢。如何定义你的接口是至关重要的,只有接口稳定才谈得上系统架构稳定,接口稳定又涉及到边界明确,边界明确了那么功能模块才能低耦合高内聚。

2 经验驱动设计

2.1 概述

为了解决业务耦合,业界提出了领域驱动设计思想。大致是由具备行业经验的业务架构师基于自己的业务知识,提炼领域概念类,然后从业务上去隔离功能,从业务上去设计接口。
这种工程模式收益颇多,主要有如下两点:

  1. 便于行业交流,因为你的代码实现是紧贴行业领域,这样你的维护人员只要具备行业经验或者看懂业务文档,就能轻松读懂代码的宏观功能。
  2. 通过与业务结合,使得模块相对分割合理、接口相对稳定。

2.2 现实的残酷

领域驱动设计在国外一直备受推崇,这也导致了特别流行。大家看过国外开源项目,一定都有共鸣。代码里面类功能太多,一个类上百个函数是家常便饭,一个函数几百行、上千行也是家常便饭。
人相对于机器,最大的优势是归纳、分析、总结能力。但是,人能同时处理的信息量是远远逊色于机器的。设计原则有一条单一职责。可以说它就是为了规避人的这种劣势而提出的。
所以,看这些开源代码的宏观结构,有开发经验的人是很容易也很快能理解。但是,一旦深入细节你就会很耗成本(比如特别是在没有文档辅助下,要读懂细节实现然后很自洽地修改其中的功能)。此外,你会发现随着业务调整和细化,你在上面添加一个小小的功能,都会分散到各个耦合的细节当中。

2.3 残酷的原因

开发一定得结合业务,但是业务往往是对应着宏观的、粗粒度的大功能。特别是在业务细化的时候,往往导致类功能膨胀。
很多对领域驱动设计的理解太强调业务经验。经验是什么?是你过去的累积,别说互联网、就是传统软件行业。需求也是随时在变化的。仅仅基于经验往往是只管过去和现在而不顾未来,那么你也只能是把你的业务套在代码里面去。如此一来将来的业务变化,都可能让你大面积重构。极可能是从接口上推翻。
此外,领域专家如果不具备很深的技术能力,也导致业务不能结合技术去设计。
举个例子,信源在A,传输的最远距离在B,要找到信号强度为T的点(允许距离误差为D)。这时领域专家就会给你一个理论公式t = f(d) ,d为距离,t为强度,如果领域专家没一点计算机基础,可能让你由近到远,以小于D的步长进行往外推近。但是,你作为一个洞察力比较强的开发者可能会发现,如果d与t反比,那么可以折半逼近的。
上面这只是一个算法的例子。你能想到折半是你理解了他的公式、或者你通过常规思维(越远信号越弱),再向他求证,然后结合技术知识得出折半;对于领域专家而言,他知道公式是成正比,但是他不懂折半算法。
其实,经验驱动设计也是类似,领域专家只是基于它的行业知识设计。有时调整下业务流程,不会影响最后的结果,但是会对架构起到质的改变,前提是你得去理解业务。当然,就算是经验驱动设计,下面实现的人也得理解了业务才能更好的实现逻辑,休想认为有一个领域专家就可以降低开发的业务沟通成本

3 抽象驱动设计

3.1 概述

虽然无论是用例驱动还是领域驱动,里面都提到抽象,但都没有特别地重视;至少在很多公司没很重视,以致于他们太重视领域经验而忽视了抽象的本质,当然也就尝不到抽象的甜头。
抽象强调归一和普适,最终实现降低业务复杂度,然后便于开发编码和后期代码迭代修改。依靠业务经验设计,会导致代码功能膨胀,无法拥抱需求变化。那么我们对业务进行分析抽象,设计更加单一内聚的功能结构,这样能解决代码功能膨胀的问题,但是能否做到适应未来需求变化呢?
根据开发经验看来,每次业务扩展也好、业务调整也罢,基本上都会在原来的代码基础上重构,而且这种重构的结局都是将自己原有的功能变得越来越瘦。也就是说:只要领域没有发生质改变,那么大多数业务调整是有很多相关功能单元可以复用的。如果我们对业务领域分析、加工、设计得更合理,那么就可能在业务调整的过程中复用更多功能。
这里强调的是对业务领域分析加工,所以你的加工必须是紧贴业务语义。

3.2 举例

说起来可能比较抽象,比如说安卓源码InputFlinger里面,你会发现从现有业务上是完备的,但是从将来的扩展上还是有诸多不足,例如InputDispatcher里面notify开头的函数都是由InputReader通知过来的然后经过一定的预处理,然后在另一个Dispatcher线程里面对其做分发,而且里面的每个特定的事件实体都有相关的中间处理参数以InputDispatcher的成员变量的形式存在。
如果我们对每个特定的事件实体定义一个抽象处理基类,提供preDispatch与doDispatch方法,由各个具体的事件处理类去实现分离;还有我们对事件队列的处理,也可以抽象为一个事件队列类,对外提供操作。然后由InputDispatcher来内聚或者说包装这些功能,可能会更好。这样对于扩展或修改一个事件处理也变得界限明确。这样,由于每个类单元的功能很少,看起来也很方便。
在设计的过程中,你要考虑你的所有的功能是尽可能扩展的,只有这样你才能尽可能设计出拥抱未来的架构。即便你的产品经理告诉你这些功能是稳定的,以后绝对不会更改。实际上,当他给你说这句话的时候,就好比老师对学生说:“我就占有大家一分钟下课时间!”。

3.3 怎么提炼抽象能力

首先,要理解业务,从概念上去理解业务。不理解业务就无法结合技术分析业务;
然后,在你理解业务的情况下去反馈,就求信号位置那个例子那样,很多业务流程调整后,对用户来说是没区别,那么就可以调整;
其次,在设计过程中,你要相信你做的功能是可能需要扩展的。
最后,到底到什么程度才算是分析到完备呢?
在大的原则上,看你的设计是否中庸地贴合设计原则;同时,最终你设计出的类实现到代码层面,要保证每个类尽量功能单一、简介和内聚,以c++为例,为了实现你的设计,要保证你的类头文件是否能控制在五十行以内,你的函数是否平均三十行,百分之九十九的函数控制在五十行。不过话又说回来,如果生搬硬套这些指标,又回到照搬套路的模式,所以还是要专注于抽象的本质去归一业务。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值