dotNET:怎样处理程序中的异常(理论篇)?

平时在软件开发的过程中,首先是要保证功能可以正常运行,满足业务需求,除此之外,还需要考虑代码在异常的时候怎么处理,让程序能够健壮地运行。正确合理地处理异常可以减少程序的 Bug、保证代码质量,当然也不是一件很容易的事。

在日常工作中我们排查错误时经常会遇到这样一些问题,如果没有,说明你做的还不错了:

  • 想通过日志的方式分析错误原因,发现日志记录不完整;

  • 找到错误日志了,记录的是“未将对象引用设置到对象的实例”,也知道代码行数,然而这一行上有多个引用类型的对象,还是不知道真实原因;

  • 问题是偶发的,无法重现。

最终需要还原数据库进行单步调试才能解决问题,然而:

  • 客户的数据库涉密,不能提供;

  • 客户的数据库运行多年,数据量很大,无法快速备份还原;

  • 如果是互联网 Saas 应用,更是难于将库拿到本地进行调试。

所以需要在代码层面、在日志层面来进行优化来达到可以快速定位问题的目的。

dotNET 经典错误

上面这张图,经历过 dotNET Framework 时代的程序员应该都不陌生,这就是经典的「黄页」和经典的 「未将对象引用设置到对象的实例」错误。

首先这个错误显示非常不友好,除了让人知道这个是 dotNET 开发的,别无他用,另外这个错误提示对排查错误也没有帮助,只知道对象为 null 了,但原因是什么并不知道,只能猜,能不能猜中就得看运气了。

正确的错误处理思路

一个系统一般有两类人使用,普通用户和系统管理员。不管是普通用户还是系统管理员,在操作系统时都期望所有的操作是有反馈的,要么正常返回想要的结果,要么给出友好的错误提示,能够指引进行下一步操作。

当出现异常时,可以导向一个专属类型的错误提示页面,也可以以模态的方式弹出错误提示,内容包含:

  • 错误提示,例如:系统异常,请联系管理员,拨打 xxx 、保存失败,请联系管理员;

  • 全局错误码,下面会讲到;

  • 异常编码,可以根据此编码在后台的日志记录快速查询,异常编码使用日期加流水号即可,建议不要使用 Guid,曾经被非技术人员当成是乱码。

如果是系统管理员使用的功能,将真实错误原因显示在错误提示中,我认为也是可以的。

全局错误码

设置全局错误码,可以让管理员在收到反馈的错误时能快速地根据错误码进行问题的定位和找到解决方法。所以需要有公开的全局错误码文档,记录错误的原因和解决方案参考。

大类上可以分为 4xx 和 5xx,4xx 表示前端的参数问题、验证问题等,5xx 表示后端的逻辑问题。

在 5xx 类型中可以再进行细分,例如:

  • 500100:表示数据库操作相关问题

  • 500200:表示列表展示相关问题

  • 等等

异常处理的一些原则

1、在方法中不要返回错误码,因为错误码的信息太单一;
2、抛异常时选择具体的异常类型,不要直接抛出 System.Exception ;
3、错误信息目的是为了让开发人员可以定位问题和解决问题,而不是给最终用户看,给前端用户看的信息要友好易懂;
4、不能吞异常,比如 catch 异常后不做任何处理,如果有些资源需要清理,可以使用 try…finally 或者使用 using ;
5、只有当你知道怎么样从异常中恢复时,才需要去捕获异常,在执行一些操作时,我们可能知道出现错误的原因,但无法恢复,这时不要去捕获异常。

在方法中怎样处理异常?

一个方法中有三个部分:参数、业务逻辑和返回值

参数

引用类型的参数,在方法的开始一定要做非空判断,判断后是抛异常还是继续下面的逻辑这个要根据具体情况来定:

  • 如果参数为 null 时会对后续的业务有影响,就应该抛出异常;

  • 如果我们判断 null 后能做一些初始化处理,能让程序继续正常运行,而且保证业务也是正确的,就不必抛异常。

业务逻辑

业务逻辑的部分分为三种情况:

  • 在方法内部调用其他类型的一个方法,比如 var user= userService.GetUser(); 对 user 的判断,当为 null 时是否抛异常,跟上面参数的逻辑一致;

  • 多个逻辑组合到一起进行判断后,如果不能满足下一步的输入,应该抛出异常;

  • 对于更低一层的调用,有时会进行异常的捕获,当捕获到异常后,应该要抛出符合当前上下文的专有异常信息,更利于定位问题。

返回值

一个方法的返回值可以返回值类型,如 string、int、bool ,也可以返回引用类型,如返回一个 User 对象,不管是返回什么类型,原则是一样的,都需要更具上下文来进行判断。

有个 GetUser 方法来获取用户对象 ,如果根据 Id 没有找到用户,可以直接返回 null ,而不是返回一个空的 User 对象,如果返回空对象,程序不会出错,但前端展示却没有数据,就搞不清是没找到用户,还是找到了但没值;返回 null,可以由上层来决定怎么来处理。

再有个 GetUserList 方法根据条件获取用户集合,如果根据搜索条件没有找到符合的用户,可以返回空对象 List,而不是返回 null 。

对于值类型也是一样,要看上下文,比如 C# 中用来查找字符在一个字符串中的索引位置的函数 IndexOf ,返回的是 int 类型,当找不到的时候返回的是 -1 ,而不是 null 。

最后

好的异常处理可以使我们的程序更加的健壮,也能在出现问题时更好的定位和排查问题,本文的内容偏理论,下一篇以代码示例的方式来进行演练下。

希望本文对您有所帮助。

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

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

相关文章

[Spring5]IOC容器_底层原理

IOC的概念和原理 什么是IOC 1.控制反转,把对象创建和对象之间的调用过程,交给Spring进行管理 2.使用IOC目的:为了耦合度减低 IOC底层原理 xml解析工厂模式反射 底层原理示例图 目的:将耦合度降低到最低限度 IOC过程 1.xml…

使用Azure DevOps Pipeline实现.Net Core程序的CI

上次介绍了Azure Application Insights,实现了.net core程序的监控功能。这次让我们来看看Azure DevOps Pipeline功能。Azure DevOps Pipeline 是Azure DevOps里面的一个组件,对于12个月试用账号同样永久免费。持续集成CI持续集成指的是,频繁…

Blazor带我重玩前端(四)

布局Blazor中的布局和MVC中的布局是类似的。创建布局新建一个Razor页面,所有新增的布局都要继承LayoutComponentBase,同时标识自定义内容的输出位置,即标识Body的位置。如图所示:应用布局我们修改一下index.razor页面中的代码&…

kubernetes+Azure DevOps实现.Net Core项目的自动化部署均衡负载

1. 前言前前后后学习kubernetes也有一个来月了,关于kubernetes的博客也写了有十多篇。但是技术如果无法落地到实际的应用场景终归是纸上谈兵,所以就有了这一出:通过结合kubernetes和azure devops实现项目的CI/CD以及均衡负载写完这篇后kubern…

ASP.NET Core 消息传递:MediatR

MediatR[1] 是参考中介者模式实现的一个 .NET 工具类库,支持在进程内以单播或多播的形式进行消息传递,通过使用 MediatR 可实现消息的发送和处理充分解耦。在介绍 MediatR 之前,先简单了解下中介者模式。中介者模式主要是指定义一个中介对象来…

[Spring5]IOC容器_Bean管理XML方式_注入其他类型属性

xml注入其他属性 bean: package com.atguigu.spring;/*** 演示使用set方法进行注入属性*/ public class Book {private String bname;private String bauthor;private String address;public Book(String address) {this.address address;}public String getBname() {return…

ERP平台的自动化测试技术实践

源宝导读:ERP是“业务密集”的大型复杂软件,而且对于业务逻辑与数据的精确度要求几乎是零容忍,其质量保障的挑战很大。本文将介绍ERP平台通过自动化测试保障质量的技术实践。一、自动化测试概念介绍测试金字塔原理1.1、测试的成本UI自动化依赖…

[Spring5]IOC容器_Bean管理XML方式_注入集合类型属性

xml注入集合属性 1.注入数组类型属性 2.注入List集合类型属性 3.注入Map集合类型属性 (1)创建类,定义数组,list,map,set类型属性,生成对应set方法 package com.atguigu.collectiontype;imp…

Logging with ElasticSearch, Kibana, ASP.NET Core and Docker

“好久不见,前两周经历了人生第一次"伪牛市",基金和股市大起大落,更加坚信“你永远赚不到超出你认知范围之外的钱,除非靠着运气”,老韭菜诚不欺我也。当能力与野心不匹配,只能多看书,收割那些不求…

[Spring5]IOC容器_Bean管理_工厂Bean

IOC操作Bean管理(FactoryBean) 1.Spring有两种类型bean,一种普通bean,另外一种工厂bean(FactoryBean) 2.普通bean:在配置文件中定义bean类型就是返回类型 3.工厂bean:在配置文件定…

Redis 6.0 新特性 ACL 介绍

Redis 6.0 新特性 ACL 介绍Intro在 Redis 6.0 中引入了 ACL(Access Control List) 的支持,在此前的版本中 Redis 中是没有用户的概念的,其实没有办法很好的控制权限,redis 6.0 开始支持用户,可以给每个用户分配不同的权…

[Spring5]IOC容器_Bean管理_bean的作用域和bean的生命周期

IOC操作Bean管理(bean作用域) 1.在Spring里面,设置创建bean实例是单实例还是多实例 2.在Spring里面,默认情况下,bean是单实例对象 package com.atguigu.spring.test;import com.atguigu.spring.collectiontype.Book…

手动造轮子——为Ocelot集成Nacos注册中心

前言近期在看博客的时候或者在群里看聊天的时候,发现很多都提到了Ocelot网关的问题。我之前也研究过一点,网关本身是一种通用的解决方案,主要的工作就是拦截请求统一处理,比如认证、授权、熔断、限流、注册发现、负载均衡等等。随…

程序员修神之路--简约而不简单的分布式通信基石

点击“蓝字”关注,领取架构书籍菜菜哥,请教一个问题呗面试又被卡住了?还是你了解我呀,tcp协议面向连接是怎么回事呢?这个说详细起来,那本好几百页的tcp协议的书籍你倒是可以看看分布式系统可以总结为是处于…

[Spring5]IOC容器_Bean管理注解方式_创建对象

IOC操心Bean管理(基于注解方式) 1.什么是注解 (1)注解是代码特殊标记,格式:注解名称(属性名称属性值,属性名称属性值…) (2)使用注解,注解作用在类上面,方…

一个有趣的问题, 你知道SqlDataAdapter中的Fill是怎么实现的吗

一:背景1. 讲故事最近因为各方面原因换了一份工作,去了一家主营物联柜的公司,有意思的是物联柜上的终端是用 wpf 写的,代码也算是年久失修,感觉技术债还是蛮重的,前几天在调试一个bug的时候,看到…

.Net Core in Docker极简入门(上篇)

点击上方蓝字"小黑在哪里"关注我吧环境准备Docker基础概念Docker基础命令Docker命令实践构建Docker镜像Dockerfilebulid & run前言Docker 是一个开源的应用容器引擎,它十分火热,如今几乎成为了后端开发人员必须掌握的一项技能。即使你在生…

[Spring5]AOP底层原理

AOP底层原理 1.AOP底层使用动态代理 (1)有两种情况动态代理 第一种 有接口的情况,使用JDK动态代理 a.创建接口实现类代理对象,增强类的方法 第二种 没有接口的情况,使用CGLIB动态代理 a.创建子类的代理对象&#…

Hangfire定时触发作业,好像很简单?

【导读】本节我们继续稍微详细讲讲在我没有详细了解源码的前提下来探讨通过Hangfire定时触发作业有哪些需要注意的事项间隔时间内执行作业举个栗子,每隔10秒监控系统CPU,若CPU飙高(根据实际业务定义百分比)则在控制台打印输出&…

五分钟快速搭建Serverless免费邮件服务

1. 引言本文将带你快速基于 Azure Function 和 SendGrid 构建一个免费的Serverless(无服务器)的邮件发送服务,让你感受下Serverless的强大之处。该服务可以每月免费发送2,5000封,这是完全白嫖啊,感兴趣的,赶…