工程代码中的错误处理

简介: 概要:在前序的文章中,我们已经陈述过了工程代码是在对被设计对象的了解不完全清楚的情况下所作出的设计。这就使得工程代码中不可避免的会出现相关的错误。由于工程代码是复杂的,如果对错误不加以控制,将可能会导致错误的扩大化。因此,对代码中可能出现的错误进行分类,并基于错误分类进行不同的错误处理是必要的工作。错误的扩大化对于高度复杂的系统,由于系统之间的联动,局部的错误可能会导致全局性的错误。用系统论的话来

概要:

在前序的文章中,我们已经陈述过了工程代码是在对被设计对象的了解不完全清楚的情况下所作出的设计。这就使得工程代码中不可避免的会出现相关的错误。由于工程代码是复杂的,如果对错误不加以控制,将可能会导致错误的扩大化。因此,对代码中可能出现的错误进行分类,并基于错误分类进行不同的错误处理是必要的工作。

错误的扩大化

对于高度复杂的系统,由于系统之间的联动,局部的错误可能会导致全局性的错误。用系统论的话来说,就是我们设计的系统之间可能存在正反馈,使得某一局部的错误被正反馈放大。这种观点是Nam Suh 的《公理设计》中,要求我们所设计的系统必须是完全独立的基础出发点之一。对于人类所设计的系统,由于我们对对被设计对象的了解不完全清楚,我们往往不能用数学的方式对被设计对象进行完全建模,计算系统的是否存在是否存在大于1的特征值也无从谈起。因此,默认系统是存在正反馈的是更为安全的做法。

举一个现实的例子,对于ETL组件(数据传输模块,用于写日志),我们往往需要给用户提供一个sdk,便于用户调用,并写入下游。如果这个sdk出现了错误,导致写入下游失败,而相应的错误(在java中,throw了一个exception;或是在Golang中panic)在用户代码中没有被处理,那么就会导致用户的程序崩溃。对于大部分的用户来说,因为日志组件的故障,导致业务代码崩溃,这是不能接受的。

因此,一种比较聪明的做法(仅就错误处理而言)是,将sdk分为两部分,一部分独立于用户的代码,专门做数据打包,网络传输,拥塞控制等工作;一部分则只负责做数据适配等较为简单的,不易出现问题的工作,并将数据传输给传输组件(两者可以通过某个固定的端口通信)。这样,即便独立的部分出现了panic,用户的程序也不会崩溃。(这种做法是我学习到的别人的做法,并不是我的发明)

错误的分类与处理

对于错误,我们可以将其分为四种:

  1. 完全已知的错误:错误的原因,范围,特性等均已知。
  2. 已知特性的错误:了解错误的范围或特性,但是不知道错误的原因;且可以根据错误的特性,给出相应的处理手段。
  3. 有备用方案的已知范围的错误:不了解错误的特性和原因,已知错误的范围,且有备用方案。
  4. 无备用方案的已知范围的错误:不了解错误的特性和原因,已知错误的范围,且无备用方案。

相应的处理手段:

  1. 完全已知的错误:既然已经知道原因,就应该处理问题。带病上线是不对的,代码中就不该出现这种错误。
  2. 已知特性的错误:在模块的父模块,做出相应的处理。且处理后,父模块应该能够正常工作。(如果不能,那么这个错误属于未知特性错误)
  3. 有备用方案的已知范围的错误:采用备用方案,且处理后,父模块应该能够正常工作。(注意这里的备用方案是广义的,只要是根据范围给出的处理方案,均可认为是备用方案,如游戏里的单机模式)
  4. 向上抛出,直至某一层存在备用方案。如果没有,应当反馈给客户和开发者。

另外,在实践中,应当遵循防御式编程思路,不要假设依赖的模块一定不会出现问题,不要假设模块只会出现已知特性的错误。在模块的连接处,至少应当有一个无备用方案的已知范围的错误的上报方案,且该方案为默认兜底方案,而不是已知特性的错误处理方案为兜底方案。

        switch (e.errorCode){
            case 1:
                processForErrorCodeOne();
            default:
                processForUnknownErrorCode();
        }

给出这种处理手段的原因在于,我们需要构建一个系统的模块树与相应的状态树,为了保证模块树的正常运行,我们的错误分类与状态分类必须保证其在状态树上是可传递的,即我们需要保证错误的分类能够涵盖所有类型的错误,且父节点的状态可以根据子节点推出,并且仍旧符合我们对错误状态分类的定义。也就是说,状态树应该满足:

  1. 状态树中的非叶子结点的状态只与其子节点的状态相关。
  2. 状态树中每个节点的状态,仅应该描述自身的状况,而不允许跨级描述。
  3. 某个节点的子节点的状态都是正常,那么该节点应该是正常。

以下图中的状态树来举例:

节点C出现了已知特性的错误,所以节点A处理该错误后,恢复了节点A的正常状态。

节点F出现了未知特性错误,错误范围为F;节点D无法处理这一错误,因此继续向上抛出错误至节点B1,此时错误范围为D;同样B1无法处理节点D的错误,向上抛出错误至节点X,错误范围为B1;节点X选取B1的备份中的一个替代B1的功能,恢复了正常工作。

常见的不好的处理方式与case

  1. 错误处理超范围:错误处理手段涵盖了其他模块。例如,节点D中,处理节点F错误的代码将节点G的错误也进行了囊括。例如:
     try {
            a = getConfig("a");
            b = getConfig("b");
        } catch (Exception e) {
            a = 0;
        }
  1. 错误处理超特性:错误的处理手段,并不能处理该特性下的所有错误。例如,在ETL软件中,为了保证数据的exactly once,在网络传输出现故障时,进行了事务性的回滚操作。但是,回滚重试这一操作,并不能处理掉一切的网络传输问题,其只能处理网络用塞带来的问题。如果是权限问题,或是某条数据本身就超过了下游允许发送的最大数据长度,那么就会导致数据的反复重新发送。特别是少量数据的反复发送,最终不仅没有实现exactly once,还会导致正常的数据也被堵死无法发送;更严重的后果是ETL模块使用CPU过高打挂用户的线上任务引发了严重故障。
  2. 跨层级错误处理:在非直接祖先节点中,处理子节点的问题。例如在节点B1中,处理节点F的问题。这会导致变更时,如果我们换掉了节点D的实现,那么原有的错误处理会成为脏代码。例如在节点B1中写如下的代码:
        if(D.getF()==0){
            D.setF(defaultFValue);
        }
        if(D.getG()==0){
            D.setG(defaultFValue);
        }

  1. 错误不抛出:当出现错误时,不向上抛出而返回正常。例如对于ETL组件,如果配置错误没有上报,用户会误认为数据已经正常收集,导致数据丢失。

目录
相关文章
|
关系型数据库 数据库 PostgreSQL
|
资源调度 前端开发 测试技术
前端工程化实践:从零搭建现代化项目构建流程
【4月更文挑战第6天】本文介绍了前端工程化的概念和重要性,包括模块化、自动化、规范化和CI/CD。接着,讨论了选择合适的工具链,如包管理器、构建工具和测试框架。然后,详细阐述了如何从零开始搭建一个基于React的现代化项目构建流程,涉及初始化、代码规范、测试、CSS处理、代码分割和CI/CD配置。最后,提到了持续优化与迭代的方向,如性能优化、类型检查和微前端。通过这样的实践,开发者可以提升开发效率和代码质量,为项目长远发展奠定基础。
1159 0
|
存储 数据中心
什么是T3机房?T1/T2/T3/T4机房等级对比详解
我们在购买IDC服务时都会选择T3机房或者T4机房,那么什么是T3机房?T3机房和T4机房有什么区别?服务器百科网来说说T1/T2/T3/T4机房等级对比详解: IDC机房等级划分 IDC机房的等级划分是根据《数据中心电信基础设施标准》而定的,它是美国国家标准学会(ANSI)颁布的,它将IDC数据中心(大型的设备和管理都比较完善的机房可以称为数据中心)基础设施的可用性定义了四种不同等级,即Tier 1、Tier 2、Tier 3和Tier 4四个等级。
22108 0
|
网络安全
mac系统charles无法抓包如何解决
mac系统charles无法抓包如何解决
1599 0
|
数据可视化 数据挖掘 Python
Python数据挖掘实用案例——自动售货机销售数据分析与应用(二)
Python数据挖掘实用案例——自动售货机销售数据分析与应用(二)
1733 0
|
弹性计算 Docker 容器
构建dify
本教程介绍如何在ECS服务器上使用Docker构建Dify。首先拉取最新镜像`langgenius/dify-web:latest`,然后通过命令运行该镜像,映射端口并设置环境变量。需在安全组中添加入方向规则,放行指定端口(如3000)。完成后保存规则,初始化安装Dify,并登录Web界面验证部署是否成功。
1764 17
|
架构师 数据库
大厂面试高频:数据库乐观锁的实现原理、以及应用场景
数据库乐观锁是必知必会的技术栈,也是大厂面试高频,十分重要,本文解析数据库乐观锁。关注【mikechen的互联网架构】,10年+BAT架构经验分享。
大厂面试高频:数据库乐观锁的实现原理、以及应用场景
|
存储 安全 容器
【多线程面试题二十一】、 分段锁是怎么实现的?
这篇文章解释了分段锁的概念和实现方式,通过将数据分成多个段并在每段数据上使用独立锁,从而降低锁竞争,提高并发访问效率,举例说明了`ConcurrentHashMap`如何使用分段锁技术来实现高并发和线程安全。
【多线程面试题二十一】、 分段锁是怎么实现的?
|
Java 编译器 Maven
Java一分钟之-AspectJ:AOP库
【6月更文挑战第13天】AspectJ是Java的AOP框架,扩展了语言并提供编译时和加载时织入,便于模块化横切关注点。关键概念包括编织、切面、切点和通知。常见问题涉及编译时织入配置、切点表达式误用、异常处理和版本兼容性。通过正确配置构建工具、精准设计切点、妥善处理异常和确保版本兼容,可避免这些问题。文中还提供了一个记录服务层方法执行时间的代码示例,帮助读者快速上手AspectJ。
868 2
|
Unix Python
python 的标准库模块glob使用教程,主要为glob.glob()使用与glob.iglob()使用
python 的标准库模块glob使用教程,主要为glob.glob()使用与glob.iglob()使用
1036 0