当前位置: 首页 > news >正文

领域驱动设计:异常处理

一、异常的处理

异常处理是领域模型要考虑的一部分,原因在于模型的责任不可能无限大。在遇到自己处理能力之外的情况时,要采用异常机制报告错误,并将处理权转交。异常就是这样一种机制,某种程度上,它可以保证领域模型的纯洁性,让其只关注于核心逻辑,而不用包含一堆意外情况处理代码。

(一)领域模型中不要使用错误码

除异常外,也可以使用错误码报告意外情况,但我们并不推荐这种形式。使用异常要更加灵活方便,因为如果使用错误代码,你不得不在每一个出错的地方增添一个if语句。不管是对于领域模型还是它的调用者来说,这都是个坏消息。而异常可以使我们的代码更简洁,遇到问题抛出即可。同时,它可以包含丰富的领域信息和业务逻辑,而不仅仅是语言层面的错误。

另外,异常是经过精心定义的方法失败模型,因此各种工具(如监控)可能会随时注意到异常的发生。比如,性能监视器会对异常进行追踪统计,而错误代码这种形式就没有这些优点。

如果事件是模型的一种特殊逻辑扩展机制,那么异常就是一种特殊的意外情况处理机制。虽然它们不像其他模型成员那么直观易懂,但它们对于保持模型的纯洁性和扩展性有着不可替代的作用,因此我们在建模时应予以考虑。

(二)自定义异常

使用好异常的关键在于让它表达一定的领域含义,即细分模型不愿处理的条件,抛出有领域含义的异常,以便让合适的上级调用者找到合适的处理方式。显然,“购物车已满”的异常比“数组越界”的异常更容易让调用者知道如何处理。

有些专家建议,当语言框架中已有相应异常时,不要自己创建异常,这适用于语言级别的异常。对于领域层来说,自定义异常是领域逻辑的一部分,它可以丰富通用语言。相比于错误代码,自定义异常能够很自然地被领域专家所理解。

public class FullCartException extends DomainException {private String error;private int maxCount;public FullCartException(String msg){}public FullCartException(String msg){this.error = msg;}public String getError(){return this.error;}
}

自定义异常可以继承任何语言中已有的异常,本例继承自领域异常DomainException基类。

以下是自定义异常的注意点:

  • 要避免太深的继承层次,一般Exception类即可满足要求。
  • 一定要以Exception作为后缀。
  • 要使异常可序列化。为了使异常能够跨应用程序和跨远程边界工作,这样做是必须的。
  • 要把与安全性有关的信息保存在私有的异常中,确保只有可信赖的代码才能得到该信息。比如数据库连接抛出的各类异常,可能会泄露你的表命名、表结构等信息。
  • 可以为异常定义属性,这样就能从程序中取得与异常有关的额外信息。

(三) 抛出异常

设计了自定义异常以后,接下来就要决定何时抛出它了。何时抛出异常呢?

当领域模型不能或不愿处理某些意外情况时,此时应该抛出异常,将其处理权交给上一级调用者,当然,如果调用者不愿处理,则可以继续向上抛出,最后异常可能被抛到应用层,这也是很常见的情况。

这里使用了“意外情况”而不是“错误”,因为前者更符合实际含义。“不能处理”的原因是,领域逻辑并不知道合适的处理方法,可能交由他人更合适。“不愿处理”则是考虑到领域模型的纯粹性,不适宜放入与领域逻辑不相关的代码。

另外要注意,既然领域模型不处理并将处理权转交了,那么程序也就无法继续了。任何方法在抛出异常后,后面的代码都不会被执行(除了finally中的代码)。这很好理解,因为后面的代码是为正常情况准备的,而现在面对的是异常状况,自然后面的逻辑也用不到了。

在上面例子的添加购物车商品方法中,如果数量超出了购物车的最大容量,可以使用throw new FullCartException(“购物车已满”)抛出异常。抛出异常的注意点如下:

  • 在领域模型中,要使用异常来处理意外情况而不是错误码。
  • 不要在能处理的正常流程中抛出异常。
  • 要为所有的自定义异常构建一份文档,使开发人员能够掌握,让他们能使用最合理、最具针对性的异常,比如不要使用“集合超容”来描述“购物车已满”。
  • 在异常消息中避免使用感叹号和问号。
  • 注意异常消息的本地化。

(四)处理异常

处理异常使用的是try…catch…finally代码结构,在catch块中处理try块可能抛出的异常,另外,finally中的代码在遇到异常后也会被执行,这也是一种保护机制,一般要在其中释放一些占用的资源。

要注意,如果你不想处理该异常,大可不必捕获,可允许异常沿着调用栈向上传递。捕获特定异常的语法是catch(fileNotFoundException e),不要省略括号这部分,也不要捕获Exception基类,因为这会捕获所有异常,通常是没必要的,而且可能吞掉有用的异常信息,而让软件行为或交互变得奇怪。

比如,商品添加不到购物车内,用户却得不到任何提醒。定义合适的富有领域逻辑的异常,并在模型遇到意外情况时及时抛出,是完成领域模型设计并保证其纯洁性的重要工作。

定义合适的富有领域逻辑的异常,并在模型遇到意外情况时及时抛出,是完成领域模型设计并保证其纯洁性的重要工作。

二、异常的分层

不管是遵循分层架构,还是菱形对称架构,都可以针对异常划分层次,并通过为异常建立统一的层超类,来统一对异常的处理。

领域层的异常层超类为DomainException,应用层的异常层超类为ApplicationException,网关层不需要考虑自定义异常,因为它的实现代码抛出的异常属于访问外部资源的基础设施框架。

领域层通过自定义异常表现领域校验逻辑与错误消息,到了应用层,又保证了异常的统一性。

在编写领域层的代码时,对异常的态度为“只抛出,不捕获”,将所有领域层的异常带来的错误和隐患,都交给外层的应用服务。应用服务对待异常的态度迥然不同,采用了“捕获底层异常,抛出应用异常”的设计原则。

(一)应用异常

为了让应用服务告知远程服务调用者究竟是什么样的错误导致异常抛出,可以分别为应用层定义如下3种异常子类,均派生自ApplicationException类型:

  • ApplicationDomainException,由领域逻辑错误导致的异常;
  • ApplicationValidationException,由输入参数验证错误导致的异常;
  • ApplicationInfrastructureException,由基础设施访问错误导致的异常。

遵循了分层的异常设计原则后,可以考虑将异常的层超类定义为非受控异常RuntimeException的子类,如此就可以避免异常对接口方法的污染。

建议将应用接口、应用模型、应用异常等均放在API包中,因为应用模型和异常也是API的一部分;

(二)自我验证

如果验证逻辑相对复杂,就建议将验证逻辑的细节提取到一个私有方法validate(),确保构造函数的实现更加简洁。

例如,一个代表邮政编号的ZipCode值对象:

public class ZipCode {private final String zipCode;private ZipCode(String zipcode){validate(zipcode);this.zipCode = zipcode;}private void valideate(String zipCode){if(isEmptyOrNull(zipcode)) throw new InvalidZipCodeException("邮政编码不能为空");if(!isValid(zipcode)) throw new InvalidZipCodeException("邮政编码需要是有效的");}
}

自我验证方法保证了值对象的正确性。如果我们将每个组成实体属性的值对象都定义为具有自我验证能力的类,就可以使得组成程序的基本单元变得更加健壮,间接提高了整个软件系统的健壮性。值对象的验证逻辑是领域逻辑的一部分,我们应为其编写单元测试。

自我验证的领域行为仅验证外部传入的设置值。倘若验证功能还需求助外部资源,例如查询数据库以检查name是否已经存在,这样的验证逻辑就不再是“自给自足”的,不能交由值对象承担。

三、异常处理的错误模式

(一)异常淹没

程序捕获某种异常,但未对异常 进行正确处理,导致异常信息淹没。

1.忽略异常

忽略异常,对异常不作任何处理,忽略异常处理会使程序泄露意想不到的状态信息。

try(FileInputStream is = new FileInputStream(name)){}
catch(FileNotFoundException e){}

2.异常消失

异常消失,Java支持try/finally语法。若finally模块包含return语句,则会抑制异常的抛出,使异常丢失。

try{throw new MagicException();
}finally{if(retrunFromFinally())return;
}

3.不使用具体的异常

不使用具体的异常,在方法中不抛出适当的异常,而是普通的Exception或者Throwable会导致异常淹没。因多数异常都直接或间接从java.lang.Exception派生,catch(Exception e)处理几乎所有异常。普通的异常使调用者无法确定发生异常的具体种类。

try{
...
}
catch(Exception e){
e.printStackTrace();
}

(二)异常使用不当

程序捕获所有异常是个好想法,但太广泛地捕获异常或对其使用不正确则会影响程序执行效率甚至威胁程序的安全。

1.使用程序捕获 NullPointerException、0utMemoryError等非检查异常。

try{
method();
}
catch(NullPointerException npe){}

2.unlock位置不当

在try内部发生异常,以致unlock()无法调用,上锁后不释放引起死锁。正确处理是在finally中显式释放。

public class MyClass {private final ReentrantLock lock = new ReentrantLock();private int sharedResource = 0;public void incrementSharedResource() {lock.lock(); // 上锁try {// 模拟一些可能抛出异常的操作if (sharedResource < 0) {throw new IllegalStateException("Shared resource cannot be negative");}sharedResource++;// ... 其他操作 ...} catch (IllegalStateException e) {// 异常处理,但没有释放锁System.err.println("Exception occurred: " + e.getMessage());// 注意:这里我们忘记了释放锁}// 如果异常在try块中发生,lock.unlock()将不会被调用,导致死锁}
}

3.控制流中使用异常

异常只用在异常条件下 ,不能用于正常的控制流 。将异常用于控制流会降低代码可维护性和可读性 。

try{Iterator i = collection.iterator();while(true){Foo foo = i.next();...}
}
catch(NoSuchElementException e){...}

相关文章:

领域驱动设计:异常处理

一、异常的处理 异常处理是领域模型要考虑的一部分&#xff0c;原因在于模型的责任不可能无限大。在遇到自己处理能力之外的情况时&#xff0c;要采用异常机制报告错误&#xff0c;并将处理权转交。异常就是这样一种机制&#xff0c;某种程度上&#xff0c;它可以保证领域模型…...

网络网络层之(6)ICMPv6协议

网络网络层之(6)ICMPv6协议 Author: Once Day Date: 2024年6月2日 一位热衷于Linux学习和开发的菜鸟&#xff0c;试图谱写一场冒险之旅&#xff0c;也许终点只是一场白日梦… 漫漫长路&#xff0c;有人对你微笑过嘛… 全系列文章可参考专栏: 通信网络技术_Once-Day的博客-CS…...

《大道平渊》· 拾壹 —— 商业一定是个故事:讲好故事,员工奋发,顾客买单。

《大道平渊》 拾壹 "大家都在喝&#xff0c;你喝不喝&#xff1f;" 商业一定是个故事&#xff0c;人民群众需要故事。 比如可口可乐的各种故事。 可口可乐公司也只是被营销大师们&#xff0c; 作为一种故事载体&#xff0c;发挥他们的本领。 营销大师们开发故事…...

JavaScript 如何访问本地文件夹

在浏览器环境中的JavaScript&#xff08;通常指的是前端JavaScript&#xff09;由于安全限制&#xff0c;无法直接访问用户的本地文件或文件夹。这是为了防止恶意脚本访问并窃取用户的敏感数据。 但是&#xff0c;有几种方法可以间接地让用户选择并访问本地文件&#xff1a; 使…...

ArrayList顺序表简单实现

一、创建MyArrayList框架 1.1 MyArrayList 类中实现 arr 数组 import java.util.Arrays;public class MyArrayList {private int[] arr;private int usesize;private static final int P 10;public MyArrayList() {arr new int[P];} 在 MyArrayList 类内创建 arr 数组&…...

144、二叉树的前序递归遍历

题解&#xff1a; 递归书写三要素&#xff1a; 1&#xff09;确定递归函数的参数和返回值。要确定每次递归所要用到的参数以及需要返回的值 2&#xff09;确定终止条件。操作系统也是用栈的方式实现递归&#xff0c;那么如果不写终止条件或者终止条件写的不对&#xff0c;都…...

youtube 1080 分辨率 下载方式

YouTube 1080p Video Downloader 这张图像代表了Autodesk Maya中一个名为rocket_body_MAT的材质的着色器网络。下面是对节点及其连接的细分: 节点 place2dTexture12: 该节点用于控制2D纹理在表面上的位置映射。输出: Out UVrocket_body2.jpg: 该节点代表一个纹理文件,具体是…...

计算机网络ppt和课后题总结(下)

常用端口总结 计算机网络中&#xff0c;端口是TCP/IP协议的一部分&#xff0c;用于标识运行在同一台计算机上的不同服务。端口号是一个16位的数字&#xff0c;范围从0到65535。通常&#xff0c;0到1023的端口被称为“熟知端口”或“系统端口”&#xff0c;它们被保留给一些标准…...

测试基础12:测试用例设计方法-边界值分析

课程大纲 1、定义 经验发现&#xff0c;较多的错误往往发生在输入或输出范围的边界上&#xff0c;因为边界值是代码判断语句的点&#xff0c;一般容易出问题&#xff08;数值写错、多加或丢失等号、写错不等号方向…&#xff09;。所以增加对取值范围的边界数据的测试&#xff…...

AI大模型在健康睡眠监测中的深度融合与实践案例

文章目录 1. 应用方案2. 技术实现2.1 数据采集与预处理2.2 构建与训练模型2.3 个性化建议生成 3. 优化策略4. 应用示例&#xff1a;多模态数据融合与实时监测4.1 数据采集4.2 实时监测与反馈 5. 深入分析模型选择和优化5.1 LSTM模型的优势和优化策略5.2 CNN模型的优势和优化策略…...

【西瓜书】9.聚类

聚类任务是无监督学习的一种用于分类等其他任务的前驱过程&#xff0c;作为数据清洗&#xff0c;基于聚类结果训练分类模型 1.聚类性能度量&#xff08;有效性指标&#xff09; 分类任务的性能度量有错误率、精度、准确率P、召回率R、F1度量(P-R的调和平均)、TPR、FPR、AUC回归…...

使用jemalloc实现信号驱动的程序堆栈信息打印

使用jemalloc实现信号驱动的程序堆栈信息打印 本文介绍应用如何集成jemalloc&#xff0c;在接收到SIGUSR1信号10时打印程序的堆栈信息。 1. 编译jemalloc 首先&#xff0c;确保你已经编译并安装了启用prof功能的jemalloc。以下是ubuntu18.04上的编译步骤&#xff1a; git c…...

树的4种遍历

目录 树的四种遍历方式的总结 1. 前序遍历&#xff08;Pre-order Traversal&#xff09; 2. 中序遍历&#xff08;In-order Traversal&#xff09; 3. 后序遍历&#xff08;Post-order Traversal&#xff09; 4. 层序遍历&#xff08;Level-order Traversal 或 广度优先遍…...

深入探讨5种单例模式

文章目录 一、对比总览详细解释 二、代码1. 饿汉式2. 饱汉式3. 饱汉式-双检锁4. 静态内部类5. 枚举单例 三、性能对比 一、对比总览 以下是不同单例模式实现方式的特性对比表格。表格从线程安全性、延迟加载、实现复杂度、反序列化安全性、防反射攻击性等多个方面进行考量。 …...

SPOOL

-----How to Pass UNIX Variable to SPOOL Command (Doc ID 1029440.6) setenv只有csh才有不行啊PROBLEM DESCRIPTION: You would like to put a file name in Unix and have SQL*Plus read that file name, instead of hardcoding it, because it will change.You want to pa…...

挑战绝对不可能:再证有长度不同的射线

黄小宁 一空间坐标系中有公共汽车A&#xff0c;A中各座位到司机处的距离h是随着座位的不同而不同的变数&#xff0c;例如5号座位到司机处的距离是h3&#xff0c;…h5&#xff0c;…。A移动了一段距离变为汽车B≌A&#xff0c;B中5号座位到司机处的距离h’h3&#xff0c;…h’h5…...

【机器学习】Python与深度学习的完美结合——深度学习在医学影像诊断中的惊人表现

&#x1f525; 个人主页&#xff1a;空白诗 文章目录 一、引言二、深度学习在医学影像诊断中的突破1. 技术原理2. 实际应用3. 性能表现 三、深度学习在医学影像诊断中的惊人表现1. 提高疾病诊断准确率2. 辅助制定治疗方案 四、深度学习对医疗行业的影响和推动作用 一、引言 随着…...

MapStruct的用法总结及示例

MapStruct是一个代码生成器&#xff0c;它基于约定优于配置的原则&#xff0c;使用Java注解来简化从源对象到目标对象的映射过程。它主要用于减少样板代码&#xff0c;提高开发效率&#xff0c;并且通过编译时代码生成来保证性能。 我的个人实践方面是在2021年前那时候在项目中…...

redis 05 复制 ,哨兵

01.redis的复制功能&#xff0c;使用命令slaveof 2. 2.1 2.2 3. 3.1 3.1.1 3.1.2 3.1.3 4 4.1 4.2 例子 5.1 这里是从客户端发出的指令 5.2 套接字就是socket 这里是和redis事件相关的知识 5.3 ping一下...

强大的.NET的word模版引擎NVeloDocx

在Javer的世界里&#xff0c;存在了一些看起来还不错的模版引擎&#xff0c;比如poi-tl看起来就很不错&#xff0c;但是那是人家Javer们专属的&#xff0c;与我们.Neter关系不大。.NET的世界里Word模版引擎完全是一个空白。 很多人不得不采用使用Word XML结合其他的模版引擎来…...

C++初阶-list的底层

目录 1.std::list实现的所有代码 2.list的简单介绍 2.1实现list的类 2.2_list_iterator的实现 2.2.1_list_iterator实现的原因和好处 2.2.2_list_iterator实现 2.3_list_node的实现 2.3.1. 避免递归的模板依赖 2.3.2. 内存布局一致性 2.3.3. 类型安全的替代方案 2.3.…...

【Oracle APEX开发小技巧12】

有如下需求&#xff1a; 有一个问题反馈页面&#xff0c;要实现在apex页面展示能直观看到反馈时间超过7天未处理的数据&#xff0c;方便管理员及时处理反馈。 我的方法&#xff1a;直接将逻辑写在SQL中&#xff0c;这样可以直接在页面展示 完整代码&#xff1a; SELECTSF.FE…...

【人工智能】神经网络的优化器optimizer(二):Adagrad自适应学习率优化器

一.自适应梯度算法Adagrad概述 Adagrad&#xff08;Adaptive Gradient Algorithm&#xff09;是一种自适应学习率的优化算法&#xff0c;由Duchi等人在2011年提出。其核心思想是针对不同参数自动调整学习率&#xff0c;适合处理稀疏数据和不同参数梯度差异较大的场景。Adagrad通…...

《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》

引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...

在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module

1、为什么要修改 CONNECT 报文&#xff1f; 多租户隔离&#xff1a;自动为接入设备追加租户前缀&#xff0c;后端按 ClientID 拆分队列。零代码鉴权&#xff1a;将入站用户名替换为 OAuth Access-Token&#xff0c;后端 Broker 统一校验。灰度发布&#xff1a;根据 IP/地理位写…...

在四层代理中还原真实客户端ngx_stream_realip_module

一、模块原理与价值 PROXY Protocol 回溯 第三方负载均衡&#xff08;如 HAProxy、AWS NLB、阿里 SLB&#xff09;发起上游连接时&#xff0c;将真实客户端 IP/Port 写入 PROXY Protocol v1/v2 头。Stream 层接收到头部后&#xff0c;ngx_stream_realip_module 从中提取原始信息…...

JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案

JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停​​ 1. ​​安全点(Safepoint)阻塞​​ ​​现象​​:JVM暂停但无GC日志,日志显示No GCs detected。​​原因​​:JVM等待所有线程进入安全点(如…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台

🎯 使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台 📌 项目背景 随着大语言模型(LLM)的广泛应用,开发者常面临多个挑战: 各大模型(OpenAI、Claude、Gemini、Ollama)接口风格不统一;缺乏一个统一平台进行模型调用与测试;本地模型 Ollama 的集成与前…...

今日学习:Spring线程池|并发修改异常|链路丢失|登录续期|VIP过期策略|数值类缓存

文章目录 优雅版线程池ThreadPoolTaskExecutor和ThreadPoolTaskExecutor的装饰器并发修改异常并发修改异常简介实现机制设计原因及意义 使用线程池造成的链路丢失问题线程池导致的链路丢失问题发生原因 常见解决方法更好的解决方法设计精妙之处 登录续期登录续期常见实现方式特…...