如何从容应对复杂性

简介:软件的复杂性,是一个很泛的概念。但是一直都是开发过程中的一个难题,本文旨在探讨如何去从容应对复杂性。

作者 | 無涯
来源 | 阿里技术公众号

软件的复杂性,是一个很泛的概念。

但是一直都是开发过程中的一个难题,本文旨在探讨如何去从容应对复杂性。

一 软件的熵增、构造定律

1 熵增定律

熵的概念最早起源于物理学,热力学第二定律(又称“熵增定律”),表明了在自然过程中,一个孤立的系统总是从最初的集中、有序的排列状态,趋向于分散、混乱和无序;当熵达到最大时,系统就会处于一种静寂状态。

软件系统亦是如此, 在软件系统的维护过程中。软件的生命力会从最初的集中、有序的排列状态,逐步趋向复杂、无序状态,直到软件不可维护而被迫下线或重构。

2 构造定律

自然界是如何应对这复杂性?

这在物理中被称为构造定律 (Constructal Law), 是由Adrian Bejan于1995提出的:

For a finite-size system to persist in time (to live), it must evolve in such a way that it provides easier access to the imposed currents that flow through it.

对于一个有限大小的持续活动的系统,它必须以这种方式发展演进:它提供了一种在自身元素之间更容易访问的流动方式。

这个定理在自然界中比比皆是,最典型的比如水循环系统,海水蒸发到大气,下雨时降落在地面,一部分渗入地面流入江河,一部分继续蒸发,不断循环。这种自发性质的设计反映了这一趋势:他们允许实体或事物更容易地流动 - 以最少的能量消耗到达最远的地方,就连街道和道路这些人为地构建物体,往往也是有排序的模式,以提供最大的灵活性。

二 如何应对软件系统的复杂性?

软件系统的复杂性往往是被低估的。复杂越高,开发人员会感到不安。对其的理解认知负荷代价就越高,我们就更不快乐。真正的挑战是在构建我们的系统时要保持其有序以及工程师的生产方式。

Ousterhout教授在《软件设计的哲学》书中提到:软件设计的最大目标,就是降低复杂度(complexity)。

就是设计符合业务的构造定律的演进方式,一种可以以最小的开发维护成本, 使业务更快更好的流动发展的方式。

三 软件复杂性来自哪里, 如何解决?

1 不确定性的来源

1、业务的不确定性

2、技术的不确定性

3、人员流动的不确定性

2 如何面对不确定性

面对外部的确定性,转化为内核的确定性。

面对外部的不确定性,找到稳定的内核基础。

专注问题域

当下互联网发展速度是迅猛的, 软件的形态也在不断的变化演进。面对未来的业务及变化,横向业务与纵向业务的发展都是不确定性的。

Robert C. Martin提到的BDUF,永远不要想着在开始就设计好了全部的事情(big design up front),一定要避免过度设计。除非能够十分确认的可预见变化, 业务边界,否则专注解决当前1-2年内业务变化设计, 讲好当下的用户故事,专注解决眼前的问题域。 面向不确定设计,增量敏捷开发。

确认稳定的系统内核

随着业务的变化、系统设计也要持续演进升级。没有一开始就完美的架构, 好的架构设计一定演化来的,不是一开始就设计出来的。

一个健康公司的成长,业务横向、纵向会发展的会越来越复杂,支持业务的系统也一定会越来越复杂。

系统演进过程中的成本,会受到最开始的设计、系统最初的内核影响的。面对外部业务的不确定性, 技术的不确定性,外部依赖的不确定性。一个稳定的内核应该尽量把外部的不确定性隔离。

  • 业务与技术的隔离

以业务为核心,分离业务复杂度和技术复杂度。

  • 内部系统与外部依赖的隔离
  • 系统中常变部分与不常变部分的隔离
  • 隔离复杂性(把复杂性的部分隔离在一个模块,尽量不与其他模块互动)

3 无序性

系统和代码像多个线团一样散落一地一样,混乱不堪,毫无头绪。

4 如何面对无序性

1、统一认知(秩序化)

2、系统清晰明了的结构(结构化)

3、业务开发流程化(标准化)

注:这里说的流程化并非指必须使用类似BPM的流程编排系统,

而是指对于一个需求,业务开发有一定的顺序, 有规划的先做一部分事情,开发哪一个模块再去做剩下的工作,是可以流程化的。

5 规模

业务规模的膨胀以及开发团队规模的膨胀,都会带来系统的复杂性提升。

6 如何面对规模膨胀带来的复杂性

1、业务隔离, 分而治之

2、专注产品核心竞争力的发展

3、场景分层

关键场景

投入更多的开发、测试资源、业务资源(比如单元测试覆盖率在90%以上)在关键场景。

普通场景

更快,更低成本、更少资源投入地完成普通场景的迭代

7 认知成本

是指开发人员需要多少知识才能完成一项任务。

在引入新的变化时,要考虑到带来的好处是否大于系统认知成本的提升,比如:之前提到的BPM流程编排引擎,如果对系统带来的好处不够多也是增加认知成本的一种。

不合适的设计模式也是增加认知成本的一种,前台同学吐槽的中台架构比较高的学习成本, 也是认知成本的一种。

8 如何降低认知成本

1、系统与现实业务更自然真实的映射,对业务抽象建模

软件工程师实际上只在做一件事情,即把现实中的问题搬到计算机上,通过信息化提升生产力。

2、代码的含义清晰,不模糊

3、代码的整洁度

4、系统的有序性, 架构清晰

5、避免过度设计

6、减少复杂、重复概念, 降低学习成本

7、谨慎引入会带来系统复杂性的变化

四 应对复杂性的利器

1 领域驱动设计——DDD

DDD是把业务模型翻译成系统架构设计的一种方式, 领域模型是对业务模型的抽象。

不是所有的业务服务都合适做DDD架构,DDD合适产品化,可持续迭代,业务逻辑足够复杂的业务系统,小规模的系统与简单业务不适合使用,毕竟相比较于MVC架构,认知成本和开发成本会大不少。但是DDD里面的一些战略思想我认为还是较为通用的。

对通用语言的提炼和推广

清晰语言认知, 比如之前在详情装修系统中:

ItemTemplate : 表示当前具体的装修页面

ItemDescTemplate、Template,两个都能表示模板概概念

刚开始接触这块的时候比较难理解这一块逻辑,之后在负责设计详情编辑器大融合这个项目时第一件事就是团队内先重新统一认知。

  • 装修页面统一使用 —— Page概念
  • 模板统一使用 —— Template概念

不将模板和页面的概念糅杂在一起,含糊不清,避免重复和混乱的概念定义。

贫血模型和充血模型

1)贫血模型

贫血模型的基本特征是:它第一眼看起来还真像这么回事儿。项目中有许多对象,它们的命名都是根据领域模型来的。然而当你真正检视这些对象的行为时,会发现它们基本上没有任何行为,仅仅是一堆getter/setter方法。

这些贫血对象在设计之初就被定义为只能包含数据,不能加入领域逻辑;所有的业务逻辑是放在所谓的业务层(xxxService, xxxManager对象中),需要使用这些模型来传递数据。

@Data
public class Person {/*** 姓名*/private String name;/*** 年龄*/private Integer age;/*** 生日*/private Date birthday;/*** 当前状态*/private Stauts stauts;
}
public class PersonServiceImpl implements PersonService {public void sleep(Person person) {person.setStauts(SleepStatus.get());}public void setAgeByBirth(Person person) {Date birthday = person.getBirthday();if (currentDate.before(birthday)) {throw new IllegalArgumentException("The birthday is before Now,It's unbelievable");}int yearNow = cal.get(Calendar.YEAR);int dayBirth = bir.get(Calendar.DAY_OF_MONTH);/*大概计算, 忽略月份等,年龄是当前年减去出生年*/int age = yearNow - yearBirth;person.setAge(age);}
}
}
public class WorkServiceImpl implements WorkService{public void code(Person person) {person.setStauts(CodeStatus.get());}}

这一段代码就是贫血对象的处理过程,Person类, 通过PersonService、WorkingService去控制Person的行为,第一眼看起来像是没什么问题,但是真正去思考整个流程。WorkingService, PersonService到底是什么样的存在?与真实世界逻辑相比, 过于抽象。基于贫血模型的传统开发模式,将数据与业务逻辑分离,违反了 OOP 的封装特性,实际上是一种面向过程的编程风格。但是,现在几乎所有的 Web 项目,都是基于这种贫血模型的开发模式,甚至连 Java Spring 框架的官方 demo,都是按照这种开发模式来编写的。

面向过程编程风格有种种弊端,比如,数据和操作分离之后,数据本身的操作就不受限制了。任何代码都可以随意修改数据。

2)充血模型

充血模型是一种有行为的模型,模型中状态的改变只能通过模型上的行为来触发,同时所有的约束及业务逻辑都收敛在模型上。

@Data
public class Person extends Entity {/*** 姓名*/private String name;/*** 年龄*/private Integer age;/*** 生日*/private Date birthday;/*** 当前状态*/private Stauts stauts;public void code() {this.setStauts(CodeStatus.get());}public void sleep() {this.setStauts(SleepStatus.get());}public void setAgeByBirth() {Date birthday = this.getBirthday();Calendar currentDate = Calendar.getInstance();if (currentDate.before(birthday)) {throw new IllegalArgumentException("The birthday is before Now,It's unbelievable");}int yearNow = currentDate.get(Calendar.YEAR);int yearBirth = birthday.getYear();/*粗略计算, 忽略月份等,年龄是当前年减去出生年*/int age = yearNow - yearBirth;this.setAge(age);}}

3)贫血模型和充血模型的区别

/*** 贫血模型*/
public class Client {@Resourceprivate PersonService personService;@Resourceprivate WorkService workService;public void test() {Person person = new Person();personService.setAgeByBirth(person);workService.code(person);personService.sleep(person);}
}/*** 充血模型*/
public class Client {public void test() {Person person = new Person();person.setAgeByBirth();person.code();person.sleep();}
}

上面两段代码很明显第二段的认知成本更低, 这在满是Service,Manage 的系统下更为明显,Person的行为交由自己去管理, 而不是交给各种Service去管理。

贫血模型是事务脚本模式

贫血模型相对简单,模型上只有数据没有行为,业务逻辑由xxxService、xxxManger等类来承载,相对来说比较直接,针对简单的业务,贫血模型可以快速的完成交付,但后期的维护成本比较高,很容易变成我们所说的面条代码。

充血模型是领域模型模式

充血模型的实现相对比较复杂,但所有逻辑都由各自的类来负责,职责比较清晰,方便后期的迭代与维护。

面向对象设计主张将数据和行为绑定在一起也就是充血模型,而贫血领域模型则更像是一种面向过程设计,很多人认为这些贫血领域对象是真正的对象,从而彻底误解了面向对象设计的涵义。

Martin Fowler 曾经和 Eric Evans 聊天谈到它时,都觉得这个模型似乎越来越流行了。作为领域模型的推广者,他们觉得这不是一件好事,极力反对这种做法。

贫血领域模型的根本问题是,它引入了领域模型设计的所有成本,却没有带来任何好处。最主要的成本是将对象映射到数据库中,从而产生了一个O/R(对象关系)映射层。

只有当你充分使用了面向对象设计来组织复杂的业务逻辑后,这一成本才能够被抵消。如果将所有行为都写入到Service对象,那最终你会得到一组事务处理脚本,从而错过了领域模型带来的好处。而且当业务足够复杂时, 你将会得到一堆爆炸的事务处理脚本。

对业务的理解和抽象

限定业务边界,对业务进行与现实更自然的理解和抽象,数据模型与业务模型隔离,把业务映射成为领域模型沉淀在系统中。

结构与防腐层

User Interfaces

负责对外交互, 提供对外远程接口

application

应用程序执行其任务所需的代码。

它协调域层对象以执行实际任务。

该层适用于跨事务、安全检查和高级日志记录。

domain

负责表达业务概念。

对业务的分解,抽象,建模 。

业务逻辑、程序的核心。

防腐层接口放在这里。

infrastucture

为其他层提供通用的技术能力。如repository的implementation(ibatis,hibernate, nosql),中间件服务等anti-corruption layer的implementation 防腐层实现放在这里。

防腐层的作用:

封装三方服务。

隔离内部系统对外部的依赖。

让隐性概念显性化

文档与注释可能会失去实时性(文档、注释没有人持续维护),但是线上生产代码是业务逻辑最真实的展现,减少代码中模糊的地方,让业务逻辑显性化体现出来,提升代码清晰度。

if (itemDO != null && MapUtils.isNotEmpty(itemDO.getFeatures()) && itemDO.getFeatures().containsKey(ITEM_PC_DESCRIPTION_PUSH)) {itemUpdateBO.getFeatures().put(ItemTemplateConstant.FEATURE_TSP_PC_TEMPLATEID, "" + templateId);itemUpdateBO.getFeatures().put(ItemTemplateConstant.FEATURE_TSP_SELL_PC_PUSH, "" + pcContent.hashCode());
} else {itemUpdateBO.getFeatures().put(ItemTemplateConstant.FEATURE_TSP_PC_TEMPLATEID, "" + templateId);itemUpdateBO.getFeatures().put(ItemTemplateConstant.FEATURE_TSP_WL_TEMPLATEID, "" + templateId);itemUpdateBO.getFeatures().put(ItemTemplateConstant.FEATURE_TSP_SELL_PC_PUSH, "" + pcContent.hashCode());itemUpdateBO.getFeatures().put(ItemTemplateConstant.FEATURE_TSP_SELL_WL_PUSH, "" + content.hashCode());
}

比如这一段代码就把判断里的业务逻辑隐藏了起来,这段代码其实的业务逻辑是这样, 判断商品是否有PC装修内容。如果有做一些操作, 如果没有做一些操作,将hasPCContent 这个逻辑表现出来, 一眼就能看出来大概的业务逻辑,让业务逻辑显现化,能让代码更清晰。可以改写成这样:

boolean hasPCContent = itemDO != null && MapUtils.isNotEmpty(itemDO.getFeatures()) && itemDO.getFeatures().containsKey(ITEM_PC_DESCRIPTION_PUSH);
if (hasPCContent) {itemUpdateBO.getFeatures().put(ItemTemplateConstant.FEATURE_TSP_PC_TEMPLATEID, "" + templateId);itemUpdateBO.getFeatures().put(ItemTemplateConstant.FEATURE_TSP_SELL_PC_PUSH, "" + pcContent.hashCode());
} else {itemUpdateBO.getFeatures().put(ItemTemplateConstant.FEATURE_TSP_PC_TEMPLATEID, "" + templateId);itemUpdateBO.getFeatures().put(ItemTemplateConstant.FEATURE_TSP_WL_TEMPLATEID, "" + templateId);itemUpdateBO.getFeatures().put(ItemTemplateConstant.FEATURE_TSP_SELL_PC_PUSH, "" + pcContent.hashCode());itemUpdateBO.getFeatures().put(ItemTemplateConstant.FEATURE_TSP_SELL_WL_PUSH, "" + content.hashCode());
}

2 简单设计原则——《Clean Code》

1、保持系统最大可测试

只要系统可测试并且越丰富的单元测试越会导向保持类短小且目的单一的设计方案,遵循单一职责的类,测试起来比较简单。

遵循有关编写测试并持续运行测试的简单、明确规则,系统就会更贴近OO低偶尔度,高内聚度的目标。编写测试越多,就越会遵循DIP之类的规则,编写最大可测试可改进并走向更好的系统设计。

2、避免重复

重复是拥有良好设计系统的大敌。它代表着额外的工作、额外的风险和额外且不必要的复杂度。除了雷同的代码,功能类似的方法也可以进行包装减少重复,“小规模复用”可大量降低系统复杂性。要想实现大规模复用,必须理解如何实现小规模复用。

共性的抽取也会使代码更好的符合单一职责原则。

3、更清晰的表达开发者的意图

软件项目的主要成本在于长期维护,当系统变得越来越复杂,开发者就需要越来越多的时间来理解他,而且也极有可能误解。

所以作者需要将代码写的更清晰:选用好名称、保持函数和类的短小、采用标准命名法、标准的设计模式名,编写良好的单元测试。用心是最珍贵的资源。

4、尽可能减少类和方法

如果过度使用以上原则,为了保持类的函数短小,我们可能会造出太多细小的类和方法。所以这条规则也主张函数和类的数量要少。

如应当为每个类创建接口、字段和行为必须切分到数据类和行为类中。应该抵制这类教条,采用更实用的手段。目标是在保持函数和类短小的同时,保持系统的短小精悍。不过这是优先级最低的一条。更重要的是测试,消除重复和清晰表达。

五 最后

总而言之,做业务开发其实一点也不简单,面对不确定性的问题域,复杂的业务变化,

如何更好的理解和抽象业务,如何更优雅的应对复杂性,一直都是软件开发的一个难题。

在对抗软件熵增,寻找对抗软件复杂性,符合业务的构造定律的演进方式,我们一直都在路上。

原文链接

本文为阿里云原创内容,未经允许不得转载。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/511183.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

阿里巴巴开源大规模稀疏模型训练/预测引擎DeepRec

简介:经历6年时间,在各团队的努力下,阿里巴巴集团大规模稀疏模型训练/预测引擎DeepRec正式对外开源,助力开发者提升稀疏模型训练性能和效果。 作者 | 烟秋 来源 | 阿里技术公众号 经历6年时间,在各团队的努力下&#…

产学融合如何促进技术创新,英特尔打了个样

作者 | 宋慧 出品 | CSDN 经过多年的积累与扎实科研,中国的科学与技术水平正在达到和超越世界一线水平。这离不开中国有基数庞大的用户和应用场景,为科学界和工业界提供了将理论付诸实践的机会,反复打磨迭代,以提升技术指标。 作…

3大能力升级,云效+钉钉,让研发协作更「敏捷」

简介:你的团队是否面临如下问题:没有敏捷经验,不知道如何落地敏捷或者敏捷实施不规范?研发交付过程信息更新不及时,无法及时跟进交付结果?员工入职离职,多套账号权限管理难?缺乏交付…

阿里云张献涛:自主最强DPU神龙的秘诀

简介:读懂云计算,才能看清DPU热潮。 微信公众号搜索“弹性计算百晓生”,获取更多云计算知识。 如果细数最近火爆的科技概念,DPU必然位列其中。 这是英伟达一手捧红的新造富故事,是2021年SoC领域最热火朝天的创业赛道…

Gartner发布2022年新兴技术成熟度曲线,推动沉浸式、AI自动化发展

编辑 | 宋慧 供稿 | Gartner Gartner 2022年新兴技术成熟度曲线列出了25项值得关注的新兴技术,这些技术正在推动沉浸式体验的发展和扩展、加速人工智能(AI)自动化并优化技术人员交付。 Gartner研究副总裁Melissa Davis表示:“新兴…

阿里云张献涛:公共云正不断向外延伸,一云多态是未来趋势

简介:一云多态是公有云的未来趋势,包括产品的多形态、部署的多形态和生态的多形态。 编者按:2021年10月22日,在云栖大会《一云多形态部署最佳实践》分论坛,阿里巴巴集团研究员、阿里云弹性计算产品线负责人张献涛发表…

4种典型限流实践保障应用高可用|云效工程师指北

简介:4种典型限流实践保障应用高可用,本文总结了一份AHAS限流实践指南,如果你的系统有被恶意用户攻击的风险,或者系统中某个应用出现异常可能会造成雪崩效应,那么这篇文章会对你有所帮助。 大家好,我叫黄博…

阿里巴巴云原生大数据运维平台 SREWorks 正式开源

简介:阿里巴巴云原生大数据运维平台 SREWorks,沉淀了团队近10年经过内部业务锤炼的 SRE 工程实践,今天正式对外开源,秉承“数据化、智能化”运维思想,帮助运维行业更多的从业者采用“数智”思想做好高效运维。 作者 | …

阿里云 VPC 内网性能测试最佳实践

简介:本文介绍了在阿里云 VPC 内网执行性能测试的方法。相较于传统的公网性能测试,VPC 内网性能测试完全在客户 VPC 环境进行,无需暴露服务到公网,安全性更高,灵活性更强。 作者:风起 背景 随着互联网的快速发展&am…

​在可视化大屏中轻松完成机器学习建模和调参应用实例

Streamlit 是一个开源 Python 库,可帮助开发人员为其系统创建交互式图形用户界面。它专为机器学习和数据科学家团队设计。使用 Streamlit,我们可以快速创建交互式 Web 应用程序并进行部署。前端工作对数据科学家来说并不重要,他们只想要一个小…

EventBridge 事件总线及 EDA 架构解析

简介:EventBridge 是事件驱动的具体落地产品,也是 EDA 的最佳实践方式。 作者:肯梦 作为 Gartner 定义的 10 大战略技术趋势之一,事件驱动架构(EDA)逐渐成为主流技术架构。根据 Gartner 的预估&#xff0…

开发者驱动的软件公司,如何赚取万亿美元?

【CSDN 编者按】在过去二十年中,诞生了两个价值数万亿美元的企业软件行业:SaaS(Software as a Service,软件即服务)软件和公有云。如今,第三个以开发者为核心的万亿美元软件浪潮正在来袭,企业该…

解决vue路由守卫报错信息

//在router文件中写入,修改报错信息 const originalPush VueRouter.prototype.push VueRouter.prototype.push function push(location, onResolve, onReject) {if (onResolve || onReject) {return originalPush.call(this, location, onResolve, onReject)}retu…

好云推荐官丨飞天加速之星怎样选择云服务器ECS?

编者按:本文来自“好云推荐官”活动的技术博主投稿,作者(昵称天狼)曾入选首届“飞天加速之星”,获得飞天人气奖。 ​你是否还在苦苦地寻找一家合适的云厂商,寻找合适的服务器来部署你开发的网站、程序&…

2022钉钉发布会|云钉低代码新模式、新能力、新机遇

简介:宜搭重磅发布酷应用工厂、连接器、AIFaaS扩展等新功能! 3月22日,以“科技向实,万物生长”为主题的2022钉钉发布会在杭州举行。 阿里巴巴资深技术专家,钉钉宜搭创始人叶周全出席发布会,并在 “人人都…

最佳实践|Spring Boot 应用如何快速接入 Prometheus 监控

简介:SpringBoot 微服务的开发、发布与部署只占其生命周期的一小部分,应用和系统运维才是重中之重。而运维过程中,监控工作更是占据重要位置。那么,为了对系统的状态进行持续地观测,面向Spring Boot应用我们该如何快速…

容器进程调度时是该优先考虑CPU资源还是内存资源?

大家好,我是飞哥!前几天看到一个有意思的问题,我前几天在朋友圈分享了,今天再在公众号里给大家发一下。问题是这样的:有 A B 两台服务器,其中 A 服务器 cpu 快满了,内存很空闲。另外一台 B 服务…

基于容器服务 ACK 发行版打造 CNStack 社区版

简介:本文将介绍如何使用 ACK Distro 作为基础镜像打造 CNStack 社区版以及CNStack 社区版中的容器服务 ACK 敏捷版产品如何帮助用户更好的使用容器平台能力。 作者:临石 CNStack 社区版(CNStack Community Edition, CNStack CE&#xff09…

阿里云云原生微服务可观测实践

简介:如果说监控可以告诉我们系统出问题了,那么可观测就可以告诉我们系统哪里出问题了,什么原因导致的问题。可观测不但可以判断系统是否正常,还可以在系统出现问题之前,主动发现系统风险。 作者:十眠、水…

“合”而不同,持“智”以恒,幂律智能2022产品升级发布会全程回顾!

今天,“合”而不同,持“智”以恒,幂律智能2022产品升级发布会正式和大家见面。 发布会共分为「嘉宾致辞」、「产品分享」、「客户实例」等部分,多位行业专家、学者大咖等纷纷发来视频,表达对幂律本次活动的祝愿。 清华…