Day952.如何降低认知负载 -遗留系统现代化实战

news2024/12/28 3:30:50

如何降低认知负载

Hi,我是阿昌,今天学习记录的是 关于如何降低认知负载的内容。

认知负载。这个看似与软件开发毫无瓜葛的知识,实际上却决定了软件系统的成败。

因此在遗留系统现代化中,把“以降低认知负载为前提”作为首要原则。

总说认知负载如何如何,降低认知负载又是多么重要,那怎么才能真正降低认知负载呢?

有哪些方法能降低认知负载。其中最重要的工具,就是·活文档


一、什么是活文档

活文档(living document),顾名思义,就是指活着的文档,也就是在持续编辑和更新的文档,有时候也叫长青文档或动态文档。

比如维基百科中的一个词条,随时都有人更新和维护,这就是一个活文档。与之相对的是静态文档,也就是一旦产生就不会更新的文档,比如大英百科全书中的一个条目。

可以想象一下,在软件开发过程中,无论是瀑布模式还是敏捷,拿到的需求文档或故事卡是“维基百科”还是“大英百科”呢?

想大多数情况可能是,在最终需求还没有敲定时还是“维基百科”,也就是还会随时更新,而一旦敲定开始开发后,就变成了“大英百科”,再也不会更新了吧。然而随着需求的不断叠加,“大英百科”作为当时系统的一个“快照”,早就已经失去了时效性。只有将不同时段、不同模块的文档片段合并在一起,才能得到当前系统的快照。但这个合并放在现实中是很难操作的。

正是因为发现了这样的问题,《实例化需求》一书的作者 Gojko Adzic 将活文档的概念引入到了软件开发当中;而去年出版的《活文档——与代码共同演进》一书,又在此基础上对活文档如何落地做了系统指导。

在这里插入图片描述


二、如何用活文档挖掘业务知识

那它是如何降低遗留系统的认知负载的。

1、为遗留代码添加注解

下面这段虚构的遗留代码(抱歉我实在编不出更糟糕的代码了……),在没有任何文档的情况下,如何理解这段代码的意思呢?

public class EmployeeService {
  public void createEmployee(long employeeId) { /*...*/ }
  public void updateEmployee(long employeeId) { /*...*/ }
  public void deleteEmployee(long employeeId) { /*...*/ }
  public EmployeeDto queryEmployee(long employeeId) { /*...*/ }
  public void assignWork(long employeeId, long ticketId) {
    // 获取员工
    EmployeeDao employeeDao = new EmployeeDao();
    EmployeeModel employee = employeeDao.getEmployeeById(employeeId);
    if (employee == null) {
      throw new RuntimeException("员工不存在");
    }
    // 获取工单
    WorkTicketDao workTicketDao = new EmployeeDao();
    WorkTicketModel workTicket = workTicketDao.getWorkTicketById(ticketId);
    if (workTicket == null) {
      throw new RuntimeException("工单不存在");
    }

    // 校验是否可以将员工分配到工单上
    if ((employee.getEmployeeType() != 6 && employee.getEmployeeStatus() == 3)
          || (employee.getEmployeeType() == 5 && workTicket.getTicketType() == "2")) {
      throw new RuntimeException("员工类型与工单不匹配,不能将员工分配到工单上");
    }

    if (!isWorkTicketLocked(workTicket)) {
      if (!isWorkTicketInitialized(workTicket)) {
        throw new RuntimeException("工单尚未初始化");
      }
    }
    
    // ...
  }

  public void cancelWork(long employeeId, long ticketId) { /*...*/ }
}

如果每个方法都很长,这样一个类就会愈发不可读,从中理解业务知识的难度也越来越大,这就是之前提到的认知负载过高。

如果把这种代码转化为下面的脑图,是不是一下子就清晰许多了呢?

在这里插入图片描述

阅读代码时,是以线性的方式逐行阅读的,这样的信息进入大脑后,就会处理成上面这样的树状信息,方便理解和记忆。但当代码过于复杂的时候,这个处理过程就会需要更多的脑力劳动,导致过高的认知负载。

可以通过在代码中加入活文档的方式,来降低认知负载。

其实要得到上面的脑图,只需要在代码中加入一些简单的注解:

@Chapter("员工服务")
public class EmployeeService {
  @Doc("员工创建")
  public void createEmployee(long employeeId) { /*...*/ }
  @Doc("员工修改")
  public void updateEmployee(long employeeId) { /*...*/ }
  @Doc("员工删除")
  public void deleteEmployee(long employeeId) { /*...*/ }
  @Doc("获取员工信息")
  public EmployeeDto queryEmployee(long employeeId) { /*...*/ }
  @Doc("给员工分配工单")
  public void assignWork(long employeeId, long ticketId) { /*...*/}
  @Doc("撤销工单分配")
  public void cancelWork(long employeeId, long ticketId) { /*...*/ }
}

编写一个工具,它可以基于这些注解来生成根节点和二级节点,并将方法中抛出的异常作为叶子节点。

这么做的原因是,虽然遗留系统中的很多文档和代码注释已经不是最新的了,但这些异常信息往往会直接抛出去展示给用户看,是为数不多的、可以从代码中直接提取的有效信息。

当然这样做也有一定局限性,因为异常信息中可能包含一些运行时数据。比如“ID 为 12345 的员工不存在”这样的异常信息,是由“ID 为 + employeeId + 的员工不存在”这样的字符串拼接而成,静态扫描字节码,是无法得出这些运行时数据的。但即使只在叶子节点中显示“ID 为 %s 的员工不存在”这样的信息,也已经非常有用了。

通过这样的工具,可以把一个非常复杂的业务代码,转化为下面这样的脑图(为了过滤掉敏感信息,故意将图片做了模糊处理)。

在这里插入图片描述

这段业务代码总共有 5000 多行,一行一行地去阅读代码会让人抓狂,但有了这样的脑图,认知负载简直降低了一个数量级。

看到这里,你一定对这个工具十分感兴趣了。但是很遗憾,这个自研的工具目前还没有开源。

不过它的原理其实十分简单,想必你已经猜到了,就是扫描 Java 字节码,获取到用注解标记的代码,然后再进一步分析得到异常信息,组织成树形结构,再生成一些中间文档,并通过一些绘图引擎绘制出来。

在实际操作过程中,只需要有一个人通读一次代码,哪怕花上几个礼拜的时间,但只要能理出一个业务模块的基本逻辑,添加上注解,就可以通过图形化的方式来展示代码结构。其他人不需要再次这么痛苦地阅读代码了,可谓一劳永逸,效率会大大提升。这么做还有一个好处是,当新的需求来临时,开发人员可以迅速定位到要修改的地方,不需要再去扒一遍代码了。

传统的代码和文档最大的问题是,代码是代码,文档是文档,彼此分离。代码和文档的关联关系储存在开发人员脑子里,这样认知负载比较高。当开发人员看到一份新的需求文档时,需要搜索一下脑子里的记忆,才能想起来这部分内容是在代码的什么位置。

然而人脑不是电脑,这种记忆是十分不靠谱的,搜索定位的过程也十分低效。而上面这样的脑图就和代码很好地结合了起来,可以说找到文档,就找到了代码,非常有效地降低了认知负载。这么做的第三个好处是有利于团队协作。

业务分析师、开发人员、测试人员都可以围绕这样一份文档来讨论需求、设计测试用例。


2、实例化需求最好的工件就是活文档

用实例化需求的方式编写的测试也是一种活文档。所谓实例化需求,实际上指的是以现实中的例子来描述需求,而不是抽象的描述。

怎么理解呢?在生活中我们会遇到很多文字描述,比如产品说明书、合同文本、法律法规等。

这些描述大多数时候都是抽象的,普通人读起来很难理解,甚至引起歧义。

如果抽象的说明能够配几个具体的示例,认知负载就会大大降低。

软件开发中的需求描述也是如此。让我印象非常深刻的是,在刚加入 Thoughtworks 没几天的时候,曾经跟着 BA 和其他开发人员找客户对一个关于用户权限的需求,大概是不同的用户在不同的场景下,能看到一个页面中的哪些字段。

那位 BA 没有像我之前见过的 BA 那样,写一大篇文档,而是简单地把界面打印了出来了好几张,每张纸上注明场景,用马克笔把不能看到的字段打个大叉划掉。

就这样,他用最简单的方式,在 5 分钟内就快速确认了所有的需求,客户也对这种直观的方式非常满意。这些纸随后就给了我们开发人员,我们根本没必要再去看需求文档了,因为需求已经以如此实例化的方式展示给我们了。这就是典型的实例化需求。

在开发时,可以将这种需求转换为测试,这种以实例化方式描述的测试,也是一种活文档。

它们不但很好地展示了业务知识,而且是随代码更新的。比如上面的给员工分配工单的例子,按实例化需求的方式,可以写出一系列组织良好的测试,如下所示:

@Test
public void should_be_able_to_assign_work_to_an_employee() {}
@Test
public void should_not_assign_work_to_when_employee_not_exist() {}
@Test
public void should_not_assign_work_when_ticket_not_exist() {}
@Test
public void should_not_assign_work_when_employee_type_and_ticket_type_not_match() {}
@Test
public void should_not_assign_work_when_ticket_is_not_initialized() {}

其实就是将需求文档的描述转换成了测试的方法名。

读到测试,就相当于读到了需求文档;测试通过,就相当于需求完成了。以后如果需求有了变更,只需要同步修改测试的名称即可。

这时候,测试是和代码共同演进的,也就是活文档。

在某些框架下运行上面的测试,还能帮我们去掉中间的下划线,这就更像是文档了。


3、用依赖分析工具展示系统知识

经过多年的腐化,类与类之间、包与包之间、模块与模块之间、服务与服务之间分别是什么样的依赖关系呢?

这就好像我们来到一个陌生的城市时,对这个城市的行政区域、大街小巷都不了解。

如果想从一个地方到另一个地方,应该怎么办呢?最好的办法就是搞一张当地的地图(当然你也可以用地图 App),有了地图的指引,就不会迷路了。

同样,可以通过依赖分析工具,建立一张遗留系统的地图,这样就可以快速知道一个业务是由哪些模块组成的。

市面上存在很多做系统依赖分析的工具,如 Backstage、Aplas、Honeycom、Systems、Coca 等等。感兴趣的同学可以去了解一下。

在这里插入图片描述

但我们也会发现,有时这些工具并不能解决我们的全部问题。

比如在做系统的数据拆分时,希望知道一个 API 调用都访问了哪些表,从而评估工作量。

这种定制化的需求很多工具都无法满足,不过不要灰心,发挥开发人员优势的时候又到了。没有轮子,就造一个出来。其实这种根据入口点获取表名的逻辑并不复杂,只需要遍历语法树,把所有执行 SQL 语句的点都找出来,然后分析它的语句中包含哪些表就可以了。对于存储过程或函数,也可以找到执行它们的点,获得存储过程或函数的名称,然后再根据名称找到对应的 SQL 文件,再做类似的分析。

当然,这要求首先要治理好编写在数据库中的存储过程和函数治理,将 DDL(Data Definition Language)迁移到代码库中,进行版本化。

这样分析工具定位起来才方便。对于复杂的入口方法,你可能会得到一幅相当大的列表或脑图,它虽然能列出全部内容,但读起来仍然很费劲。这时候我们有两个办法。

  • 一是重构复杂的入口方法,抽取出若干小的方法,再以小方法为入口点做分析。

  • 二是修改分析工具,直接分析存储过程或函数。如果存储过程或函数过大,也可以进一步拆分。

继续改进分析工具。比如分析不同模块之间所依赖的对方的表有哪些,这对于数据拆分也是非常有帮助的。


三、总结

虽然遗留系统中可能没有太多的测试,但仍然可以通过向代码中添加注解的方式来编写活文档,并通过工具来实现图形化展示,将遗留系统中无处可寻的业务知识暴露在面前。

除此之外,还可以使用依赖分析工具来挖掘系统知识,同样也可以用图形化的方式来帮助我们理清系统内的依赖关系。

这对开发新需求或推动代码和架构的现代化都非常有帮助。

《活文档》这本书在介绍遗留系统的“文档破产”时,是这样描述遗留系统的,这段话:

遗留系统里充满了知识,但通常是加密的,而且我们已经丢失了秘钥。没有测试,就无法对遗留系统的预期行为做出清晰的定义。
没有一致的结构,就必须猜测它是如何设计的、为什么这么设计以及应该如何演进。没有谨慎的命名,就必须猜测和推断变量、方法和类的含义,以及每段代码负责的任务。

虽然遗留系统是“文档破产”的,是“加密”的,但是只要我们掌握了活文档这个“破译工具”,就可以一步一步破解出那些隐匿在系统深处的知识。


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

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

相关文章

设计模式-结构型模式之适配器模式(Adapter)

结构型模式简介 结构型模式(Structural Pattern)描述如何将类或者对 象结合在一起形成更大的结构,就像搭积木,可以通过 简单积木的组合形成复杂的、功能更为强大的结构。 结构型模式可以分为类结构型模式和对象结构型模式: 类结构型模式关心类…

Springboot整合redis实现缓存

此次笔记是在实现了数据库的增删改查操作的基础上进行的,因此需要准备数据库、数据表,以及实现相对应的服务层、持久层、控制层。可参考之前的笔记Spring Boot整合 druid、Mybatis-plus 一、Redis安装 Redis 是完全开源免费的,遵守BSD协议&…

【每日一短语】对(某人)吹毛求疵

1、短语及释义 get all sensitive on sb. 释义: 对某人吹毛求疵。 sensitive: 敏感的,引申为“事多的”,“爱挑刺的”。 2、示例及出处 美剧:《摩登家庭》第4季第7集 Modern Family, Season 4 Episode 7 Jay Pritchett: Don’t g…

中医诊所一定要去尝试软文营销,效果简直不要太好

中医诊所是一种传统的医疗机构,随着互联网时代的发展,软文营销已经成为了中医诊所宣传推广的一种重要方式。通过撰写高质量的软文,中医诊所可以提升品牌知名度、增加患者数量、提高医疗服务质量等方面取得良好的效果。今天结合我10年营销经验…

INDEMIND双目惯性模组运行实时ORB-SLAM3教程

现在实验室视觉SLAM已经不够满足,所以需要多模态融合,正巧购入高翔博士推荐的INDEMIND双目惯性模组,根据官方例程在中使用ros接入ORB-SLAM3INDEMIND的双目鱼眼imu模组,这回有SDK及ORB-SLAM3安装过程中的各种常见性问题解决方法及安…

Boost的安装

过程 boost是一个非常重要的东西,是对C函数库的扩展,非常得不错,是第三方扩展,不过不是一般的第三方,是C标准委员会工作组成员发起的。 通过vs命令工具进入boost目录 执行bootstrap.bat命令 开始安装 b2.exe instal…

JavaScript经典教程(二)-- CSS基础部分

179:HTML基础部分(元素分类、特性、特殊元素等) — 补充 1、盒子模型 定义:一个元素在页面中所占的位置大小,叫盒子模型。 包含的样式:即影响元素大小的样式: width、height、margin、padding…

使用MyBatis实现关联查询

文章目录 一,查询需求(一)针对三张表关联查询(二)按班级编号查询班级信息(三)查询全部班级信息 二,创建数据库表(一)创建教师表(二)创…

云计算中的自动化运维技术及其实践

引言 随着云计算技术的快速发展,云计算成为了企业数据中心的新生态,提供了更加灵活、高效、安全的 IT 基础设施和应用服务,让企业能够更加专注于业务创新和变革。但是,云计算的快速发展也带来了新的安全挑战,尤其是在…

达摩院开源工业级说话人识别模型CAM++

近日,达摩院正式向公众开源工业级说话人识别通用模型CAM,兼顾准确率和计算效率,训练labels类别达20万,每类含20~200条梅尔频谱特征。当前该模型已上线Modelscope魔搭社区,后续将陆续开源针对各场景优化的工…

TCP协议三次握手过程分析

TCP(Transmission Control Protocol) 传输控制协议 TCP是主机对主机层的传输控制协议,提供可靠的连接服务,采用三次握手确认建立一个连接: 位码即tcp标志位,有6种标示:SYN(synchronous建立联机) ACK(acknowledgement 确认) PSH(push传送) FIN(finish结…

Vue3技术4之watch监视属性、watch时value问题

Vue3技术4 watch监视属性watch监视ref定义的数据情况一:监视ref所定义的一个响应式数据App.vueDemo.vue 情况二:监视ref所定义的多个响应式数据App.vueDemo.vue 添加immediate属性Demo.vue watch监视reactive定义的数据情况一:监视reactive所…

直播带货冲击实体生意,杭州四季青打响禁止直播带货第一枪

最近,“杭州四季青部分市场禁止直播”的话题冲上热搜。 身处“直播电商之城”杭州的“中国服装行业第一街”杭州四季青的部分市场,打响了“驱逐直播第一枪”! 杭州四季青部分市场内为什么要明令禁止直播,直播卖货对实体商户带来了…

华锐3d虚拟数字人提供哪些智能化服务?

虚拟数字人的诞生是互联网时代的产物,它的出现为数字化经济提供了全新解决方案。数字化技术和网络使人类得以进入以“智能机器数据算法”为主线的新生态之中。 广州华锐互动作为专业的AI虚拟数字人开发商,拥有成熟的技术团队和一流的解决方案&#xff0…

零售数据分析操作篇15:用总聚合做销售分析

上一讲讲了内存计算筛选,又可称之为自定义计算成员筛选,即当某列是通过自定义计算成员得到的时候,还要想利用其作为筛选条件,就需要用到自定义计算成员筛选功能。 上一讲还给大家出了道作业,就是:想知道哪…

RHCE-DNS服务器

主机名称解析服务器配置 要求: 1、建立DNS服务器,负责解析的域为openedu.com; 建立DNS首先需要在服务器端配置主配置文件: (1)临时关闭防火墙和selinux:systemctl stop firewalld;…

靶机精讲之HackademicRTB1

主机发现 nmap扫描 端口扫描 只有80端口开放 UDP扫描 web渗透 服务扫描 脚本扫描 DOS攻击漏洞 枚举漏洞 查看web端 进行目录爆破 点击 点击后发现地址结构像有目录爆破 接上面枚举漏洞 复制那枚举目录到web 接目录爆破 apeache服务器 查看内容管理系统是否是自建的 在库搜索…

【rustdesk】rust入门及 windows尝试编译

rustup 微软建议用vs code开发 下载了64位的版本: vs code 插件 rust-analyer 介绍Better TOML,用于更好的展示.toml文件Error Lens, 更好的获得错误展示 One Dark Pro, 非常好看的Vscode主题 CodeLLDB, debugger程序 安装

2023年工商管理在职研究生择校、择专业指南

工商管理在职研究生是许多管理岗位从业者提升职业素质、竞争力的重要途径。 工商管理在职研究生学习的内容更加专业、深入,涵盖的领域更加广泛,通过学习,可以提高专业素养,掌握更深入的理论和实务知识,拓宽人脉&#…

爆爆爆!!Deep Mind与Google Brain合并,成立 Google DeepMind 新部门

图|2010-2023,从 DeepMind 到 Google DeepMind,再到 DeepMind,再到 Google DeepMind 来源: 学术头条 微信号:SciTouTiao 或许是深深感受到了来自 OpenAI 与微软一起给到的巨大压力,以及加速实现通用人工智…