系统困境与软件复杂度,为什么我们的系统会如此复杂
简介:读 A Philosophy of Software Design 有感,软件设计与架构复杂度,你是战术龙卷风吗?
作者 | 聂晓龙(率鸽)
来源 | 阿里技术公众号
读 A Philosophy of Software Design 有感,软件设计与架构复杂度,你是战术龙卷风吗?
一 前言
有一天,一个医生和一个土木工程师在一起争论“谁是世界上最古老的职业”。医生说:“上帝用亚当的肋骨造出了夏娃,这是历史上第一次外科手术,所以最古老的职业应该是医生”,土木工程师说:“在创世纪之前,上帝从混沌中创造了天堂与人间,这是更早之前的一次土木作业,所以最古老的职业应该是土木工程”。这时软件工程师拖着键盘走出来说,“那你认为,是谁创造了那片混沌?”
建筑师不会轻易给100层的高楼增加一个地下室,但我们却经常在干这样的事,并且总有人会对你说,“这个需求很简单”。到土里埋个地雷,这确实不复杂,但我们往往面临的真实场景其实是:“在这片雷区里加一个雷”,而雷区里哪里有雷,任何人都不知道 。
二 什么是复杂性
我们一直在说系统很复杂,那到底什么是复杂性?关于复杂的定义有很多种,其中比较有代表的是Thomas J. McCabe 在1976提出的理性派的复杂性度量,与John Ousterhout 教授提出的感性派的复杂性认知。
1 理性度量
复杂性并不是什么新概念,早在上世纪70年代,软件就已经极其复杂,开发与维护的成本都非常高。1976年McCabe&Associates公司开始对软件进行结构测试,并提出了McCabe Cyclomatic Complexity Metric,我们也称之为McCabe圈复杂度。它通过多个维度来度量软件的复杂度,从而判断软件当前的开发/维护成本。
2 感性认知
复杂度高的代码一定不是好代码,但复杂度低的也不一定就是好代码。John Ousterhout教授认为软件的复杂性相对理性的分析,可能更偏感性的认知。
Complexity is anything that makes software hard to understand or to modify
-- John Ousterhout 《A Philosophy of Software Design》
译:所谓复杂性,就是任何使得软件难于理解和修改的因素。
50年后的今天,John Ousterhout教授在 A Philosophy of Software Design 书中提到了一个非常主观的见解,复杂性就是任何使得软件难于理解和修改的因素。
模糊性与依赖性是引起复杂性的2个主要因素,模糊性产生了最直接的复杂度,让我们很难读懂代码真正想表达的含义,无法读懂这些代码,也就意味着我们更难去改变它。而依赖性又导致了复杂性不断传递,不断外溢的复杂性最终导致系统的无限腐化,一旦代码变成意大利面条,几乎不可能修复,成本将成指数倍增长。
三 复杂性的表现形式
复杂的系统往往也有一些非常明显的特征,John教授将它抽象为变更放大(Change amplification)、认知负荷(Cognitive load)与未知的未知(Unknown unknowns)这3类。当我们的系统出现这3个特征,说明我们的系统已经开始逐渐变得复杂了。
症状1-变更放大
Change amplification: a seemingly simple change requires code modifications in many different places.
-- John Ousterhout 《A Philosophy of Software Design》
译:看似简单的变更需要在许多不同地方进行代码修改。
变更放大(Change amplification)指得是看似简单的变更需要在许多不同地方进行代码修改。比较典型的代表是Ctrl-CV式代码开发,领域模型缺少内聚与收拢,当需要对某段业务进行调整时,需要改动多个模块以适应业务的发展。
/*** 销售捡入客户*/
public void pick(String salesId, String customerId) {
// 查询客户总数
long customerCnt = customerDao.findCustomerCount(salesId);
// 查询销售库容
long capacity = capacityDao.findSalesCapacity(salesId);
// 判断是否超额
if(customerCnt >= capacity) {
throws new BizException("capacity over limit");}
// 代码省略 do customer pick
}
在CRM领域,销售捡入客户时需要进行库容判断,这段代码也确实可以满足需求。但随着业务的发展,签约的客户要调整为不占库容。而客户除了销售捡入,还包括主管分发、leads分发、手工录入、数据采买等多个场景,如果没对库容域做模型的收拢,一个简单的逻辑调整,就需要我们在多个场景做适配才能满足诉求。
症状2-认知负荷
Cognitive load: how much a developer needs to know in order to complete a task.
-- John Ousterhout 《A Philosophy of Software Design》
译:开发人员需要多少知识才能完成一项任务。
认知负荷(Cognitive load)是指开发人员需要多少知识才能完成一项任务。使用功能性框架时,我们希望它操作简单,部署复杂系统时,我们希望它架构清晰,其实都是降低一项任务所需的成本。盲目的追求高端技术,设计复杂系统,增加学习与理解成本都属于本末倒置的一种。
TMF是整个星环的支柱,也是业务中台面向可复用可扩展架构的核心。但TMF太过复杂,认知与学习成本非常高,我们日常中所面临的一些扩展诉求99%(或者应该说100%)都不适合TMF,可能通过一些设计模式或者就是一些if else,可能更适合解决我们的问题。
除此之外,还包括一些简单搜索场景却用到了blink等流式引擎,简单后台系统通过DDD进行构建,几个商品发布的状态机转换用上了规则引擎等等,都属于认知负荷复杂度的一种。
症状3-未知的未知
Unknown unknowns: it is not obvious which pieces of code must be modified to complete a task
-- John Ousterhout 《A Philosophy of Software Design》
译:必须修改哪些代码才能完成任务。
未知的未知(Unknown unknowns)是指必须修改哪些代码才能完成任务,或者说开发人员必须获得哪些信息才能成功地执行任务。这一项也是John Ousterhout教授认为复杂性中最糟糕的一个表现形式。
当你维护一个有20年历史的项目时,这种问题的出来相对而言就没那么意外。由于代码的混乱与文档的缺失,导致你无法掌控一个500万行代码的应用,并且代码本身也没有明显表现出它们应该要阐述的内容。这时“未知的未知”出现了,你不知道改动的这行代码是否能让程序正常运转,也不知道这行代码的改动是否又会引发新的问题。这时候我们发现,那些“上帝类”真的就只有上帝能拯救了。
四 为什么会产生复杂性
那软件为什么越来越复杂,是不是减少一些犯错就能避免一场浩劫呢?回顾那些复杂的系统,我们可以找到很多因素导致系统腐化。
- 想简单图省事,没有及时治理不合理的内容
- 缺少匠心追求,对肮脏代码视而不见
- 技术能力不够,无法应对复杂系统
- 交接过渡缺失,三无产品几乎无法维护
除了上述内容外,还可以想到很多理由。但我们发现他们好像有一个共同的指向点 - 软件工程师,似乎所有复杂的源头就是软件工程师的不合格导致,所以其实一些罪恶的根因是我们自己?
1 统一的中国与分裂的欧洲
欧洲大陆面积大体与中国相当,但为什么欧洲是分裂的,而中国是统一的。有人说他们文化不一样,也有人说他们语言不通是主要原因,也有人说他们缺一个秦始皇。其实我们回顾欧洲的历史,欧洲还真不缺一个大一统的帝国。罗马帝国曾经让地中海成为自己的内海,拿破仑鼎盛时期掌管着1300万平方公里的领地。欧洲也曾出现过伟大的帝国,但都未走向统一。
我们再观察地图,其实除了中国、俄罗斯以外,全世界99%的国家都是小国。分裂才是常态,统一才不正常。马老师也曾说过,成功都有偶然性只有失败才存在必然。只有极少国家才实现了大一统,所以我们不应该问为什么欧洲是分裂的,而应该问为什么中国是统一的。类比到我们的软件也同样如此,复杂才是常态,不复杂才不正常。
2 软件固有的复杂性
The Complexity of software is an essential property, not an accidental one.
-- Grady Booch 《Object-Oriented Analysis and Design with Applications》
译:软件的复杂性是一个基本特征,而不是偶然如此。
Grady Booch在 Object-Oriented Analysis and Design with Applications 中提出这样一个观念,他认为软件的复杂性是固有的,包括问题域的复杂性、管理开发过程的困难性、通过软件可能实现的灵活性与刻画离散系统行为的问题,这4个方面来分析了软件的发展一定伴随着复杂,这是软件工程这本科学所必然伴随的一个特性。
Everything, without exception, requires additional energy and order to maintain itself. I knew this in the abstract as the famous second law of thermodynamics, which states that everything is falling apart slowly.
-- Kevin Kelly 《The Inevitable》
译:世间万物都需要额外的能量和秩序来维持自身,无一例外。这就是著名的热力学第二定律,即所有的事务都在缓慢地分崩离析。
Kevin Kelly在 The Inevitable 也有提过类似的观点,他认为世间万物都需要额外的能量和秩序来维持自身,所有的事物都在缓慢地分崩离析。没有外部力量的注入事物就会逐渐崩溃,这是世间万物的规律,而非我们哪里做得不对。
五 软件架构治理复杂度
为软件系统注入的外力就是我们的软件架构,以及我们未来的每一行代码。软件架构有很多种,从最早的单体架构,到后面的分布式架构、SOA、微服务、FaaS、ServiceMesh等等。所有的软件架构万变不离其宗,都在致力解决软件的复杂性。
1 架构的本质
编程范式指的是程序的编写模式,软件架构发展到今天只出现过3种编程范式( paradigm ),分别是结构化编程,面向对象编程与函数式编程。
- 结构化编程取消 goto 移除跳转语句,对程序控制权的直接转移进行了限制和规范
- 面向对象编程限制 指针 的使用,对程序控制权的间接转移进行了限制和规范
- 函数式编程以 λ演算法 为核心思想,对程序中的赋值进行了限制和规范
面向对象的五大设计原则 S.O.L.I.D。依赖倒置限制了模块的依赖顺序、单一职责限制模块的职责范围、接口隔离限制接口的提供形式。
软件的本质是约束。商品的代码不能写在订单域,数据层的方法不能写在业务层。70年的软件发展,并没有告诉我们应该怎么做,而是教会了我们不该做什么。
2 递增的复杂性
软件的复杂性不会凭空消失,并且会逐级递增。针对递增的复杂性有3个观点:
- 模糊性创造了复杂,依赖性传播了复杂
- 复杂性往往不是由单个灾难引起的
- 我们可以容易地说服自己,当前变更带来的一点点复杂性没什么大不了
曾经小李跟我抱怨,说这段代码实在是太恶心了,花了很长时间才看懂,并且代码非常僵硬,而正好这个需求需要改动到这里,代码真的就像一坨乱麻。我问他最后是怎么处理的,他说,我给它又加了一坨。
3 编程思维论
战术编程
其实小李的这种做法并非是一个个体行为,或许我们在遇到复杂代码时都曾这样苟且过,John教授这种编程方法称之为“战术编程”。战术编程最主要的特点是快,同时具备如下几个特点。
- 当前一定是最快的
- 不会花费太多时间来寻找最佳设计
- 每个编程任务都会引入一些复杂度
- 重构会减慢当前任务速度,所以保持最快速度
@HSFProvider(serviceInterface = AgnDistributeRuleConfigQueryService.class)
public class AgnDistributeRuleConfigQueryServiceImpl implements AgnDistributeRuleConfigQueryService {@Overridepublic ResultModel<AgnDistributeRuleConfigDto> queryAgnDistributeRuleConfigById(String id) {logger.info("queryAgnDistributeRuleConfigById id=" + id);ResultModel<AgnDistributeRuleConfigDto> result = new ResultModel<AgnDistributeRuleConfigDto>();if(StringUtils.isBlank(id)){result.setSuccess(false);result.setErrorMsg("id cannot be blank");return result}try {AgnDistributeRuleConfigDto agnDistributeRuleConfigDto = new AgnDistributeRuleConfigDto();AgnDistributeRuleConfig agnDistributeRuleConfig = agnDistributeRuleConfigMapper.selectById(id);if(agnDistributeRuleConfig == null){logger.error("agnDistributeRuleConfig is null");result.setSuccess(false);result.setErrorMsg("agnDistributeRuleConfig is null");return result}this.filterDynamicRule(agnDistributeRuleConfig);BeanUtils.copyProperties(agnDistributeRuleConfig, agnDistributeRuleConfigDto);result.setSuccess(true);result.setTotal(1);result.setValues(agnDistributeRuleConfigDto);} catch (Exception e) {logger.error("queryAgnDistributeRuleConfigById error,", e);result.setSuccess(false);result.setErrorMsg(e.getMessage());}return result;}
}
我们看上面这段代码,是一段查询分发规则的业务逻辑。虽然功能能够work,但不规范的地方其实非常多
- Facade层定义全部逻辑 - 未做结构分层
- 业务与技术未做分离 - 耦合接口信息与业务数据
- Try catch 满天飞 - 缺少统一异常处理机制
- 没有规范化的日志格式 - 日志格式混乱
但不可否认,他一定是当前最快的。这就是战术设计的特点之一,永远按当前最快速交付的方案进行推进,甚至很多组织鼓励这种工作方式,为了使功能更快运作,只注重短期收益而忽略长期价值。
战术龙卷风
Almost every software development organization has at least one developer who takes tactical programming to the extreme: a tactical tornado.
-- John Ousterhout 《A Philosophy of Software Design》
译:几乎每个软件开发组织都有至少一个将战术编程发挥到极致的开发人员:战术龙卷风。
将战术编程发挥到极致的人,叫战术龙卷风。战术龙卷风以腐化系统为代价换取当前最高效的解决方案(或许他自己并未觉得)。战术龙卷风也有如下几个特点:
- 是一位多产的程序员,没人比龙卷风更快完成任务
- 总能留下龙卷风后毁灭的痕迹
系统困境与软件复杂度,为什么我们的系统会如此复杂相关推荐
- 系统困境与软件复杂度:为什么我们的系统会如此复杂?
点击上方"朱小厮的博客",选择"设为星标" 后台回复"书",获取 后台回复"k8s",可领取k8s资料 读 A Phil ...
- 心理测评软件的心理测试法的优缺点,心理测评平台系统优点 心理测试软件 靠谱的心理测评系统...
心理测评系统,通过编程优化,在电脑上就能完成对心理的测评,使用简单,容易操作.类似于平时接触到的电脑答题系统.将答题者的选择转换成相应的分值,再通过程序自行运算,去假去虚,加权平衡,后给出相对客观的评 ...
- win7系统备份还原软件_非常好用的系统备份和还原的软件,OneKey一键还原工具
OneKey一键还原工具是一款简单好用的电脑系统备份和还原工具,它具有完全免费.安全无捆绑.快速.压缩率高.兼容好等特点. OneKey一键还原 避免电脑中毒.系统硬盘损坏.系统故障等原因,电脑无法正 ...
- http网址捆綁代理php_决心下载win7原版系统没有捆绑软件没有广告的win7原版系统镜像...
使用系统的 iso刻录到usb, iso刻录到usb的工具 https://dl.pconline.com.cn/download/2142416.html 经测试,OK, 不建议使用PE工具安装系统 ...
- 安卓系统怎么安装软件_【图文】安卓系统手机如何下载安装APP
我们以"支付宝APP"为例,介绍一下安卓系统手机(除苹果手机外大部分手机为安卓系统手机)下载安装APP的步骤. 安卓系统的手机使用方法是相似的,我使用的是华为P40,大家可以参考操 ...
- 安卓系统怎么安装软件_福利| 旧电脑安装安卓系统。网上学习办公两不误
世界那么大,谢谢你来看我!!关注我你就是个网络.电脑.手机小达人 自从疫情以来,学生现在开始网上上课,可是很多学习软件不支持windows系统,手机屏幕又太小,长时间给小孩子看眼睛会受不了,PC端的安 ...
- linux系统类AE软件,一文详解 Linux系统常用监控工具
点上面蓝字 "CodeSheep"关注作者 本文共 1329字,阅读大约需要 3分钟,文末有计时器可自行对时! 概 述 本文主要记录一下 Linux系统上一些常用的系统监控工 ...
- win7 系统 内存测试软件,win7怎么检测内存 win7系统检测内存的三种方法
win7系统检测内存的方法有很多种,我们都知道内存是电脑中十分重要的硬件,内存越大,电脑运行速度越快.当然内存也会发生错误,和磁盘坏道一样,内存有问题时容易导致系统崩溃,数据丢失,那么 Win7 64 ...
- 心理测试软件购买价格,心理测评系统 心理测评软件 心理测评量表 测评系统价格...
心理测评系统功能介绍: 1.能够提供在线测试和纸笔测试.一些心理老师表示在线测试过程需自动化,无需人工计分,系统可自动保存和处理分析测试结果,形成学生的心理档案:纸笔测试是以的形式进行,系统可将量表导 ...
最新文章
- [JavaWeb基础] 007.Struts2的配置和简单使用
- Word中快速插入目录
- Python3--baby网的数据爬取
- 为什么CTRIP 网站页面中要把小图做在一起的原因.---减少HTML 链接数.
- poj 2528_2
- 漫画 | 让设计师崩溃的十个瞬间
- golang的panic用法
- bp神经网络预测未来五年数据_基于小波神经网络的数据中心KPI预测
- android mainactivity 刷新fragment,Android刷新从FragmentActivity android.support.v4.app.Fragment
- PCL使用类成员函数作为pclvisualizer的回调函数
- a letter and a number
- 怎么开启计算机的无线网络,笔记本怎么开启无线投屏
- PyCharm(Python编译器汉化)
- jquery 原生控件 超大文件分片校验上传 易迁移 webuploader springboot
- 计算机基础——11种排序(sort)算法
- 【python】【数据处理】画多维数据分布图
- python中的search的group(0),group(1).........的方法
- 3D卷积“LP-3DCNN: Unveiling Local Phase in 3D Convolutional Neural Networks”
- Unity Content Size Fitter 刷新不及时
- c语言字符串中字母降序数字升序,将字符串以ASCII码降序排列
热门文章
- python画五角星填充不同颜色_不同颜色牡丹怎么画?3种牡丹图解教你画,适合0基础学习,收藏...
- JAVA入门级教学之(类之间究竟有哪几种关系呢)
- rocketmq 消息指定_进大厂必备的RocketMQ你会吗?
- git for windows_手把手教会舍友玩 Git (包教包会,再也不用担心他的学习)
- jsp项目开发案例_Laravel中使用swoole项目实战开发案例一 (建立swoole和前端通信)
- 乱码 讯飞 语音识别_一段讯飞、百度等语音识别API无法识别的语音最终解决办法...
- python实现杨辉三角形博客园_Python实现杨辉三角
- electron增加导航按钮_Electron发布6.0 Released版本
- php常用功能代码,10段PHP常用功能代码(1)_PHP教程
- bcp 不能调用where 子句_技术分享 || Mysql中IS NULL、IS NOT NULL不能走索引?
- 系统困境与软件复杂度:为什么我们的系统会如此复杂?