讲师中心 微信公众号
AI工具推荐 视频效率加速

掌握异常处理:最佳实践和常见陷阱

云墨同学_6760

云墨同学_6760

发布时间:2024-08-30 09:09:04

|

749人浏览过

|

来源于dev.to

转载

掌握异常处理:最佳实践和常见陷阱

异常处理是软件开发的重要组成部分,但它经常被低估、误用或忽视。对于经验丰富的开发人员来说,了解如何有效处理异常可以显着提高代码的健壮性、可维护性和整体系统的可靠性。这篇博文深入探讨了高级异常处理策略、常见错误以及超越编程语言的最佳实践,尽管许多示例将引用 java。

异常处理的哲学

在深入了解细节之前,让我们重新审视一下异常的目的:它们的存在是为了发出异常情况信号,而您的代码在设计时并未将其作为正常操作的一部分进行处理。异常处理是指定义出现这些意外情况时程序的行为方式。

异常不适用于流量控制

最常见的错误之一是使用异常作为常规控制流的机制,尤其是对于新开发人员或从其他范例过渡的开发人员。这可能会导致性能问题、代码不可读以及难以遵循或维护的逻辑。

例如:

try {
    for (int i = 0; i < array.length; i++) {
        // do something that might throw an exception
    }
} catch (arrayindexoutofboundsexception e) {
    // move to the next element or terminate
}

这是对异常的滥用。循环应该通过标准检查来管理其边界,而不是依赖于捕获异常。抛出和捕获异常的成本比较高,而且这样做会掩盖代码的实际逻辑。

只捕捉你能处理的东西

捕获异常而不正确处理它们是另一个陷阱。您是否经常看到代码捕获通用异常只是为了记录它并继续,或者更糟糕的是,捕获异常只是为了默默地吞掉它们?

try {
    // some code that might throw an exception
} catch (exception e) {
    // log and move on
    logger.error("something went wrong", e);
}

虽然日志记录很重要,但您应该只捕获您知道如何处理的异常。如果在没有明确的恢复路径的情况下捕获异常,可能会导致隐藏的错误并使诊断问题变得更加困难。

最佳实践:如果当前层代码无法有效地从异常中恢复,则让异常在调用堆栈中向上传播。这允许更高级别的组件(可能有更多上下文)决定最佳的行动方案。

设计的弹性和可读性

快速失败,尽早失败

健壮软件的原则之一是“快速失败”。这意味着当检测到错误时,应该立即报告,而不是让系统在无效状态下继续运行。

例如,如果出现问题,尽早验证方法输入可以防止进一步处理:

public void processorder(order order) {
    if (order == null) {
        throw new illegalargumentexception("order cannot be null");
    }

    if (!order.isvalid()) {
        throw new orderprocessingexception("invalid order details");
    }

    // continue processing the order
}

通过尽早验证假设,您可以防止系统执行不必要的操作并在以后遇到更深层次、更模糊的问题。

明智地使用检查与非检查异常

在像 java 这样的语言中,有检查异常和非检查异常。检查异常强制调用者处理它们,而未检查异常(runtimeexception 的子类)则不会。他们之间的选择应该经过深思熟虑。

  • 检查异常: 当调用者可以合理地预期从异常中恢复时使用这些异常。它们适用于操作失败是其生命周期的正常预期部分的场景,例如可能找不到文件的文件 i/o 操作。

  • 未检查异常:这些更适合正常情况下不应捕获的编程错误,例如空指针取消引用、非法参数类型或违反业务逻辑不变量。

过度使用检查异常可能会导致方法签名臃肿,并强制调用者进行不必要的错误处理,而过度使用未检查异常可能会导致不清楚哪些方法可能会失败以及在什么情况下失败。

单一责任原则

应该在有足够上下文来适当管理异常的情况下进行处理。这与单一职责原则 (srp) 相关,该原则规定类或方法应该只有一个更改理由。异常处理可以被视为一个单独的职责;因此,您的代码应该将异常处理委托给能够完全理解和管理故障的组件。

例如,低级数据库访问代码不一定要处理数据库连接问题本身,而是应该抛出一个异常,由更高级别的服务处理,该服务可以决定是否重试操作、回退到辅助服务系统,或通知用户。

Wikclawpedia Archive Access
Wikclawpedia Archive Access

访问、搜索、检索并提交 Wikclawpedia 代理档案中经核实的代理、平台、瞬间、语录和创作者条目。

下载

有意义的异常消息

抛出异常时,尤其是自定义异常时,请提供清晰且内容丰富的消息。此消息应该以帮助开发人员(有时还有用户)了解问题所在的方式描述问题。

throw new illegalstateexception("unable to update order because the order id is missing");

这比:
好得多

throw new illegalstateexception("order update failed");

精心设计的消息使调试变得更加容易,并减少诊断问题所花费的时间。

要避免的常见反模式

1.吞咽异常

如前所述,捕获异常而不对其进行任何处理是一种主要的反模式。这不仅隐藏了问题,还可能导致意外行为。

try {
    // risky code
} catch (exception e) {
    // do nothing
}

提示:如果您捕获异常,请确保您正在增加价值。要么处理异常,将其包装在更有意义的异常中,要么重新抛出它。

2.捕获顶级异常

广泛捕获异常或 throwable 可以掩盖不同类型的错误,包括您可能意想不到的未经检查的异常,例如 nullpointerexception 或 outofmemoryerror。

try {
    // risky code
} catch (exception e) {
    // handle all exceptions the same way
}

提示:具体说明您捕获的内容,如果您必须捕获广泛的异常,请确保您理解并能够正确处理它可能包含的各种异常。

3.忽略 interruptedexception

使用线程时,经常会遇到 interruptedexception。忽略它或重新抛出它而不重新中断线程是另一个常见错误。

try {
    thread.sleep(1000);
} catch (interruptedexception e) {
    // log and move on
}

提示:如果捕获了interruptedexception,一般应该重新中断线程,以便正确处理中断:

catch (interruptedexception e) {
    thread.currentthread().interrupt(); // restore the interrupted status
    throw new runtimeexception("thread was interrupted", e);
}

异常处理的高级技巧

1.利用自定义异常来处理特定于域的错误

自定义异常可以提供更清晰的信息并封装特定于域的错误信息。这在大型系统中特别有用,因为相同的异常在不同的上下文中可能有不同的含义。

public class invalidorderstateexception extends runtimeexception {
    public invalidorderstateexception(string message) {
        super(message);
    }
}

这样,异常本身就携带了有关错误上下文的有意义的信息,并且您可以使用异常类型来区分不同的错误情况。

2.使用异常链

异常链允许您将较低级别的异常包装在较高级别的异常中,同时保留原始异常的堆栈跟踪。当您想要在更高级别提供更多上下文而不丢失原始错误信息时,这非常有用。

try {
    // some code that throws sqlexception
} catch (sqlexception e) {
    throw new dataaccessexception("failed to access the database", e);
}

这样,原始的 sqlexception 就会被保留,并且可以在需要时进行检查,但更高级别的异常提供了有关更高抽象级别上发生的情况的附加上下文。

3.在适当的地方集中处理异常

在某些架构中,将异常处理集中在一个位置是有益的,例如 web 应用程序中的全局异常处理程序。这使您可以在一个地方处理常见问题,例如日志记录、错误响应格式或重试。

例如,在 java 中,spring mvc 允许使用 @controlleradvice 注释进行全局异常处理程序:

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(DataAccessException.class)
    public ResponseEntity<String> handleDatabaseException(DataAccessException e) {
        // Log and respond appropriately
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());
    }
}

结论

有效的异常处理既是一门艺术,也是一门科学。它需要深思熟虑可能会出现什么问题、如何检测问题以及如何应对。通过遵循最佳实践(例如避免流控制异常、仅在有足够上下文的情况下处理异常以及设计有意义的自定义异常),您可以编写更健壮、可维护且更易于调试的代码。

请记住,异常应该使您的代码更可靠,而不是更复杂。明智地使用它们来构建能够优雅地处理意外情况的系统。

热门AI工具

更多
WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

Seko
Seko Hot

一款AI视频创作工具,主要用于商汤科技推出的创编一体的AI短视频创作Agent,适合需要提升相关任务效率的用户。

讯飞绘文

讯飞绘文是一款由科大讯飞推出的一站式 AIGC 内容运营平台。

咔片AIPPT

一款在线AI演示文稿制作工具,可根据主题和内容需求辅助生成PPT结构与页面,提高演示材料制作效率。

讯飞智作

讯飞智作是一款AI视频创作工具,AI文本配音工具,数字人课程、营销视频制作。

二狗PPT
二狗PPT Hot

一款AI演示文稿工具,主要用于专为中式职场打造的AI PPT生成工具,适合需要提升相关任务效率的用户。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

AionClaw
AionClaw Hot

AionClaw是一款面向办公、创作和编程任务的AI桌面智能体。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

相关专题

更多
spring框架介绍
spring框架介绍

本专题整合了spring框架相关内容,想了解更多详细内容,请阅读专题下面的文章。

2171

2025.08.06

Java Spring Security 与认证授权
Java Spring Security 与认证授权

本专题系统讲解 Java Spring Security 框架在认证与授权中的应用,涵盖用户身份验证、权限控制、JWT与OAuth2实现、跨站请求伪造(CSRF)防护、会话管理与安全漏洞防范。通过实际项目案例,帮助学习者掌握如何 使用 Spring Security 实现高安全性认证与授权机制,提升 Web 应用的安全性与用户数据保护。

417

2026.01.26

C++ 智能指针与现代内存管理
C++ 智能指针与现代内存管理

深入讲解 C++ 现代内存管理的核心工具——智能指针,涵盖 unique_ptr 独占所有权语义、shared_ptr 引用计数机制与循环引用问题、weak_ptr 弱引用的应用场景、make_unique/make_shared 工厂函数的性能优势、自定义删除器的编写、RAII 资源管理思想的实践,以及从裸指针迁移到智能指针的重构策略,帮助开发者编写安全无泄漏的现代 C++ 代码。

299

2026.04.23

堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

4647

2023.07.18

堆和栈区别
堆和栈区别

堆(Heap)和栈(Stack)是计算机中两种常见的内存分配机制。它们在内存管理的方式、分配方式以及使用场景上有很大的区别。本文将详细介绍堆和栈的特点、区别以及各自的使用场景。php中文网给大家带来了相关的教程以及文章欢迎大家前来学习阅读。

2108

2023.08.10

堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

4647

2023.07.18

堆和栈区别
堆和栈区别

堆(Heap)和栈(Stack)是计算机中两种常见的内存分配机制。它们在内存管理的方式、分配方式以及使用场景上有很大的区别。本文将详细介绍堆和栈的特点、区别以及各自的使用场景。php中文网给大家带来了相关的教程以及文章欢迎大家前来学习阅读。

2108

2023.08.10

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

3518

2023.08.10

Buffalo框架数据库开发全教程
Buffalo框架数据库开发全教程

本专题围绕Buffalo框架数据库开发,讲解database.yml多环境配置、soda与fizz迁移生成回滚、模型结构体标签、增删改查与条件查询、一对多与多对多关联、数据校验、回调钩子、事务处理及原生SQL执行能力。

60

2026.09.23

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
SQL 教程
SQL 教程

共61课时 | 6.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn