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

深度解析C++异常处理机制:最佳实践、性能分析和挑战

C++ 基础知识 八 异常处理 下篇

  • 一、异常处理实践
    • 1 编写高质量代码中的异常处理
      • 1.1 只在必要时才使用异常
      • 1.2 尽量减小异常的范围
      • 1.3 不要隐藏异常
      • 1.4 不要在析构函数中抛出异常
      • 1.5 使用 RAII 技术来管理资源
    • 2 维护异常类
      • 2.1 按照异常类型的功能来定义异常类
      • 2.2 继承现有的异常类
      • 2.3 提供有意义的错误信息
  • 二、 异常处理最佳实践
    • 1 避免滥用异常
    • 2 正确抛出异常
    • 3 正确捕获异常
    • 4 使用异常安全的代码设计原则
  • 三、 异常处理性能分析
    • 1 异常处理与程序性能的关系
    • 2 异常处理对程序效率的影响
    • 3 异常处理的优化建议
  • 四、 编写自己的异常类
    • 1 异常类的定义
    • 2 异常类的构造函数
    • 3 异常类的属性和行为
  • 五、 异常和异常处理的应用
    • 1 C++标准库中的异常
    • 2 异常应用于GUI开发
    • 3 异常应用于网络编程
  • 六、异常处理的问题和挑战
    • 1 异常处理的开销问题
    • 2 异常处理的线程和进程问题
    • 3 异常处理的与语言特性的互动问题
      • 3.1语言特性与异常处理的互动示例
  • 七、异常处理最佳实践总结
    • 1 异常处理设计原则
      • 1.1 适当应用异常处理
      • 1.2 保持一致性和可预测性
      • 1.3 避免不必要的资源占用
      • 1.4 保证代码可读性和可维护性
      • 1.5 保持代码一致和清晰
    • 2 异常处理的应用指南
      • 2.1 捕获特定异常类型
      • 2.2 选择合适的抛出异常类型
      • 2.3 清晰简单的异常处理
    • 3 异常处理的高级用法
      • 3.1 嵌套异常
      • 3.2 携带元数据(metadata)
      • 3.3 使用异常规范
    • 4 异常处理的最佳实践
      • 4.1 永远不要吞噬异常
      • 4.2 不要在程序框架代码中使用异常
      • 4.3 不要过度使用异常
      • 4.4 使用别称
      • 4.5 记录每个异常
    • 小结

一、异常处理实践

在编写 C++ 代码时会遇到不可预期的错误和异常情况。为了让我们的代码更健壮和可靠,我们需要使用异常处理机制来处理这些情况。

1 编写高质量代码中的异常处理

在编写高质量代码时,我们应该遵循以下一些指导原则来设计和编写异常处理代码:

1.1 只在必要时才使用异常

异常处理机制的开销很大,因此我们应该仅在必要时才使用它。异常应该仅用于处理不期望发生的错误和异常情况

1.2 尽量减小异常的范围

异常处理的捕获越广,处理逻辑就越复杂。因此,我们应该尽可能地减小异常的范围,只在必要的时候抛出异常,并且只捕获我们实际想要处理的异常。

1.3 不要隐藏异常

在异常处理代码中,我们不应该隐藏抛出的异常。如果我们不能够处理某个特定异常,可以把它重新抛出,让上层调用者来处理。

1.4 不要在析构函数中抛出异常

在析构函数中抛出异常会导致程序无法正确清理资源,因此我们应该尽量避免在析构函数中抛出异常。

1.5 使用 RAII 技术来管理资源

RAII(Resource Acquisition Is Initialization)是一种 C++ 编程技术,它可以确保在对象生命周期结束时,与对象相关的资源会被正确地释放。使用 RAII 技术来管理资源可以避免资源泄漏,并使异常处理变得更加容易。

2 维护异常类

在编写异常处理代码时,我们需要定义一些异常类来代表特定的异常情况。以下是一些关于维护异常类的指南:

2.1 按照异常类型的功能来定义异常类

我们应该按照异常类型的功能来定义异常类。例如,如果我们在解析 XML 文档时遇到了语法错误,可以定义一个名为 XmlSyntaxError 的异常类来表示它。

2.2 继承现有的异常类

我们可以继承现有的异常类来定义新的异常类,这样可以减少代码量,并使得异常类之间有更好的组织。

2.3 提供有意义的错误信息

在抛出异常时,我们应该提供有意义的错误信息,这样可以帮助开发者识别和解决问题。例如,我们可以在异常类的构造函数中提供一些额外的信息,例如行号、文件名等。

以下是一个解析 XML 文档时遇到语法错误的异常类 XmlSyntaxError 的示例代码:

在代码示例中继承了 std::runtime_error 类,并提供了一些额外的构造函数参数来表示文件名和行号。我们还定义了两个函数 fileNamelineNum 来获取文件名和行号。

class XmlSyntaxError: public std::runtime_error {
public:XmlSyntaxError(const std::string& message, const std::string& fileName, int lineNum):std::runtime_error(message + " in file " + fileName + " at line " + std::to_string(lineNum)),m_fileName(fileName),m_lineNum(lineNum) {}const std::string& fileName() const {return m_fileName;}int lineNum() const {return m_lineNum;}private:std::string m_fileName;int m_lineNum;
};

二、 异常处理最佳实践

在编写代码的过程中很容易遇到一些不可预期的错误和异常情况。为了更好地处理这些问题,我们需要使用异常处理机制来使代码更健壮、可靠和安全。在本文中,我将介绍几个异常处理的最佳实践,包括避免滥用异常、正确抛出异常、正确捕获异常和使用异常安全的代码设计原则。

1 避免滥用异常

异常的捕获和处理是非常耗时的。因此,我们应该仅在必要时才使用它们。在设计代码时应该始终考虑程序的可预测性和可维护性,而不是仅仅依赖于异常处理来解决问题。

2 正确抛出异常

当必须使用异常来处理错误或异常情况时,正确抛出异常是非常重要的。以下是一些关于如何正确地抛出异常的最佳实践:

  1. 定义清楚的异常类型和异常消息:您的异常应该有一个清楚的类型和消息,这将有助于调试和排除异常。
  2. 不要从析构函数中抛出异常:当对象被销毁时,C++将自动调用其析构函数。如果析构函数中抛出异常,则其他代码将无法处理引发的异常。
  3. 不要在 catch 块中抛出新的异常:如果您在 catch 块中抛出异常,则可能会丢失原始异常的上下文信息。
  4. 不要把异常的信息打印到 stdout/stderr:在生产环境中,这些输出被重定向或忽略,因此无法正确地调试。

以下是抛出清晰异常的示例代码:
在此示例中抛出了一个名为DBConnectionError的异常,该异常包含了有意义的错误消息。在类定义中我们重载了std::exception中的what()函数,用于返回错误消息

class DBConnectionError : public std::exception {
public:explicit DBConnectionError(const std::string& msg) : msg_(msg) {}const char* what() const noexcept override {return msg_.c_str();}
private:std::string msg_;
};void connect_to_database(const std::string& host, const std::string& port) {// ...if (error_occurs) {throw DBConnectionError("Failed to connect to database server.");}
}

3 正确捕获异常

正确捕获异常是非常重要的,因为它可以帮助我们避免一些代码中不必要的错误。以下是一些关于如何正确捕获异常的最佳实践:

  1. 只捕获您想要的异常类型:如果您只想处理某些异常,那么您只应该捕获这些异常类型。不要广泛地去捕获所有异常类型,这是因为这种行为可能会导致 bug 的潜在问题
  2. 在捕获异常时优先处理最终的异常:如果您的代码中有多个 catch block,通过捕获最终的异常然后冒泡到上一层实现代码正确的行为

以下是正确捕获异常的示例代码:
在此示例中为可能抛出的异常提供了两种不同的 catch 块。在第一个块中,我们捕获的是一个名为 DBConnectionError 的异常类型,如果出现这种类型的异常,我们将会打印出错误消息。在第二个块中,我们捕获的是所有继承自 std::exception 的异常类型,如果没有 catch 块捕获到异常,则程序将会崩溃

try {// Some code that may throw.
} catch (const DBConnectionError &err) {std::cerr << "Database connection error: " << err.what() << std::endl;// Should handle the error, or rethrow it.
} catch (const std::exception &err) {std::cerr << "Caught exception with message: " << err.what() << std::endl;// Somebody else should handle this.
}

4 使用异常安全的代码设计原则

异常安全的代码指的是那些,在面对抛出异常这种异常情况下,不会导致系统状态异常或是资源泄漏的代码。以下是几个使用异常安全的代码设计原则:

  1. 持有资源的类需要实现 RAII(资源获取即初始化)模式
  2. 尝试对资源的操作是可撤销的
  3. 实现异常安全操作

以下是一个使用异常安全的示例代码:

在示例中容器在插入新元素时可以自动扩容。在检测到容器的内存不足时,它将使用 RAII 模式确保容器扩容过程中的异常安全操作。在这种情况下,如果插入元素的过程中发生异常,旧容器对象将保持不变,而容器中的元素也不会遗漏

struct my_vector {
public:// 构造函数my_vector() : data_(nullptr), size_(0), capacity_(0) {}// 销毁资源~my_vector() noexcept {clear(); // 销毁所有元素operator delete(data_);}// 插入新元素void push_back(int val) {// 省略元素类型的构造函数// 检查是否已达到容量if (size_ == capacity_) {// 保存旧容量const auto old_capacity = capacity_;// 扩容capacity_ = (capacity_) ? capacity_ * 2 : 16;int *new_data = static_cast<int *>(operator new(capacity_ * sizeof(int)));// 省略拷贝元素的构造函数// 析构原对象,并释放旧资源for (std::size_t i = 0; i < size_; i++) {(data_ + i)->~int();}operator delete(data_);// 更新新资源data_ = new_data;}// 构造元素new (data_ + size_) int(val);size_++;}// 清除所有元素void clear() noexcept {for (std::size_t i = 0; i < size_; i++) {(data_ + i)->~int();}size_ = 0;}private:int *data_;std::size_t size_, capacity_;
};

三、 异常处理性能分析

在异常处理是必不可少的一部分,但是异常处理机制会对程序的性能产生一定的影响。下面将探讨 c++ 异常处理与程序性能之间的关系,分析异常处理对程序效率的影响,并提出异常处理的优化建议。

1 异常处理与程序性能的关系

在 c++ 中抛出异常和捕获异常都需要耗费时间。在异常未被抛出或未被捕获时,异常处理机制几乎不会对程序的运行时间产生任何影响。但是当程序遇到异常时,异常处理机制会显著地拖慢程序的运行速度。因此我们应该尽可能地避免不必要的异常处理。

2 异常处理对程序效率的影响

异常处理的处理过程通常涉及到了栈的动态分配和析构,这一过程需要耗费一定的时间和资源。下面是一些具体影响程序性能的异常处理方面:

  1. 抛出异常:抛出异常时,编译器需要构造一个 exception_obj 对象,这个构造过程会占用 CPU 时间。如果异常被多次抛出,运行时间会更长。
  2. 异常处理:当程序中发生异常时,处理程序需要进行计算机指令来寻找与异常类型相匹配的 catch 块。如果 catch 块未被匹配到,异常处理程序会把异常传递给上一级程序。
  3. 析构函数:在退出函数时,编译器会调用对象的析构函数进行资源的释放,这也会消耗一定的 CPU 时间。

以上三种情况都可能会对程序的性能产生重大的影响,应该谨慎使用异常处理机制。

3 异常处理的优化建议

为了最大程度地优化程序的性能,以下是一些异常处理的优化建议:

  1. 避免滥用异常:程序中应该尽可能地减少异常的使用,仅在必要时使用异常处理。
  2. 预留合理的调试信息:在开发过程中,我们可以在程序的 debug 版本中保留更多的调试信息,有助于发现异常所在。
  3. 合理设计代码结构:代码的设计应当合理,避免嵌套过深。合理的代码结构有助于异常处理程序中的控制流程。
  4. 避免异常值和对象:在代码中避免使用异常值和异常对象,这会使程序处理异常时更加高效。
  5. 合理使用 noexcept:在函数签名中使用 noexcept 声明有助于编译器进行可优化的代码生成。

现在来看一个使用了异常处理机制的示例,以此帮助读者了解优化的重要性。

在示例中创建了一个长度为 10 的 vector,但是试图在越界时访问超出范围的元素。当出现越界异常时,我们捕获了异常并打印异常信息。但是此时程序已经无法恢复,我们在异常处理完成后直接返回了错误代码。这时,我们可以将程序改为使用在边界情况下返回一个代表错误的特殊返回值。这种方式显然会更加高效

#include <iostream>
#include <vector>int main() {try {std::vector<int> vec(10);vec.at(20) = 42; // 试图访问越界的元素} catch (const std::exception& e) {std::cout << e.what() << std::endl;return 1;}return 0;
}
#include <iostream>
#include <vector>int main() {std::vector<int> vec(10);if (vec.size() > 20) {vec.at(20) = 42; // 试图访问越界的元素} else {return 1;}return 0;
}

在这个新版本的程序中,我们做了如下更改:

  1. 调用 vec.at(20) 之前,检查了 vector 的 size 是否超出了范围。
  2. 当 size 超出范围时,返回了非正常的退出代码。
    这种方式对程序的性能有很大的提升,同时不影响代码的可读性。

四、 编写自己的异常类

在 c++ 中除了可以使用标准库提供的异常类之外,我们也可以自己定义异常类来实现更加个性化的异常处理。在本文章中,我们将会探讨如何编写自己的异常类。

1 异常类的定义

在 c++ 中我们可以通过继承 std::exception 类来定义自己的异常类。下面是一个简单的异常类定义示例:

在这个异常类定义中,我们继承了 std::exception 类,并在类中重写了 what() 方法。在此方法中,我们返回了异常的描述信息。

class CustomException : public std::exception {
public:const char* what() const noexcept override {return "This is a custom exception!";}
};

2 异常类的构造函数

异常类的构造函数定义和其他 c++ 类型的构造函数定义一样。我们可以在构造函数中设置异常类的属性和行为。

下面是一个带有自定义描述信息的异常类构造函数示例:

在这个异常类中定义了一个带有一个参数的构造函数,这个参数表示了异常的描述信息。在构造函数中,我们把传入的描述信息保存在类的属性 msg_ 中,在 what() 方法中返回该属性。这样我们就可以自定义异常的描述信息了。

class CustomException : public std::exception {
public:CustomException(const std::string& msg) : msg_(msg) {}const char* what() const noexcept override {return msg_.c_str();}private:std::string msg_;
};

3 异常类的属性和行为

在定义异常类时不仅可以设置异常的描述信息,还可以为其设置其他属性和行为。

下面是一个支持获取异常类型和文件名的自定义异常类实现示例:
在这个定制的异常类的实现中定义了三个类的属性:

  1. msg_:描述异常的信息
  2. type_:异常类型
  3. file_:异常所在的文件名

在构造函数中初始化了这三个属性。并实现了两个方法,分别用于获取异常类型和文件名。

class CustomException : public std::exception {
public:CustomException(const std::string& msg, const std::string& type, const std::string& file): msg_(msg), type_(type), file_(file) {}const char* what() const noexcept override {return msg_.c_str();}const std::string& getType() const {return type_;}const std::string& getFile() const {return file_;}private:std::string msg_;std::string type_;std::string file_;
};

五、 异常和异常处理的应用

在 c++ 中异常是指程序运行时发生的错误或意外情况,例如数组越界、空指针引用等等。c++ 为我们提供了一套异常处理机制,以帮助我们更好地处理这些错误或意外情况。

1 C++标准库中的异常

在 c++ 中,标准库提供了一些常见的异常类,例如:

  • std::runtime_error:表示由程序运行时的错误引起的异常
  • std::logic_error:表示由程序逻辑或设计上的错误引起的异常
  • std::bad_alloc:表示内存分配失败引起的异常
  • std::invalid_argument:表示无效参数引起的异常

我们可以根据程序需要选择适当的异常类来处理异常。

下面是一个使用 std::runtime_error 异常类处理文件读取异常的示例:
在这个示例中,我们使用 std::ifstream 类读取文件,在文件打开失败时,我们抛出了一个 std::runtime_error 异常。在 main() 函数中,我们使用 trycatch 关键字来捕获异常,并在控制台输出异常信息。

#include <fstream>
#include <stdexcept>// 读取文件
void readFile(const std::string& filename) {std::ifstream inFile(filename);if (!inFile) {throw std::runtime_error("Failed to open file!"); // 抛出异常}// do something
}int main() {try {readFile("test.txt");} catch (const std::runtime_error& e) {// 处理异常std::cerr << "Exception: " << e.what() << std::endl;return 1;}return 0;
}

2 异常应用于GUI开发

在 GUI 应用程序开发中,异常处理也是非常重要的。我们可以使用异常处理机制来捕获并处理框架自带的异常和自定义异常。

下面是一个使用异常处理处理输入框类型错误的 GTK+ 应用程序示例:

在示例中创建了一个 GTK+ 窗口应用程序,当输入框的内容变更时,我们使用 std::stoi() 函数尝试将输入框的值转换成 int 类型。如果无法转换,就会抛出一个 std::invalid_argument 异常,并在 catch 块中处理异常,控制台输出错误信息。

#include <gtk/gtk.h>
#include <stdexcept>// 回调函数,用于处理输入框输入变更事件
static void on_entry_changed(GtkEntry* entry, gpointer user_data) {const char* text = gtk_entry_get_text(entry);try {// 尝试把输入框的值转为整数int value = std::stoi(text);// do something} catch (const std::invalid_argument& e) {// 处理无效参数异常g_message("Invalid input!");}
}int main(int argc, char** argv) {gtk_init(&argc, &argv);// 创建输入框GtkWidget* entry = gtk_entry_new();// 连接输入变更事件g_signal_connect(entry, "changed", G_CALLBACK(on_entry_changed), nullptr);// 显示窗口GtkWidget* window = gtk_window_new(GTK_WINDOW_TOPLEVEL);gtk_container_add(GTK_CONTAINER(window), entry);gtk_widget_show_all(window);gtk_main();return 0;
}

3 异常应用于网络编程

在网络编程中,异常的处理也是必不可少的。我们可以使用异常处理来捕获网络编程中的错误并进行错误处理。

下面是一个使用自定义异常处理套接字操作错误的示例:

在示例中定义了一个自定义的异常类 SocketException,用于处理套接字操作错误。在 sendData()recvData() 中,我们使用 throw 关键字抛出异常。在 main() 函数中,我们创建一个套接字并在出现错误时输出错误信息。

#include <iostream>
#include <stdexcept>
#include <sys/socket.h>
#include <unistd.h>// 自定义异常类,用于处理套接字操作错误
class SocketException : public std::runtime_error {
public:SocketException(const std::string& msg) : std::runtime_error(msg) {}
};// 发送数据
void sendData(int sockfd, const void* buffer, size_t size) {if (send(sockfd, buffer, size, 0) < 0) {throw SocketException("Failed to send data!"); // 抛出异常}
}// 接收数据
void recvData(int sockfd, void* buffer, size_t size) {if (recv(sockfd, buffer, size, 0) < 0) {throw SocketException("Failed to receive data!"); // 抛出异常}
}int main() {int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0) {std::cerr << "Failed to create socket! Error code: " << errno << std::endl;return 1;}// do somethingclose(sockfd);return 0;
}

总结:异常处理机制是 c++ 中重要的机制之一,在开发过程中合理使用异常处理可以使代码更加健壮,更具可读性。我们需要根据实际需求选择合适的异常类或自行定义异常类,并在 trycatch 中妥善处理异常。

六、异常处理的问题和挑战

在实际的软件开发过程中,异常处理是不可或缺的一部分。在 C++ 语言中异常的处理机制被广泛应用于各种场景,如在操作系统、应用程序、网络编程等领域中都有广泛的应用。然而与其它特性一样,异常处理也面临着一些挑战和问题。本文将讨论 C++ 异常处理的问题和挑战,分别是异常处理的开销、异常处理的线程和进程问题以及异常处理的与语言特性的互动问题。

1 异常处理的开销问题

在 C++ 中异常处理抛出异常的时候需要获取当前函数的上下文信息,这一部分信息被称为栈展开(stack unwinding)。由于需要获取当前调用栈的上下文信息,因此栈展开操作需要获取栈帧中的异常处理表(exception handling table),这个操作是非常耗时的。此外,栈展开操作还需要释放堆栈上的所有局部变量,这也会导致一定量的开销。因此异常处理在性能上会产生相对较高的开销,这也是 C++ 中使用异常处理的一个问题。

2 异常处理的线程和进程问题

在多线程和多进程的情况下,异常处理所面临的问题也会更加复杂。对于多线程程序而言,由于不同的线程之间共享同一个进程的内存空间,因此在异常处理过程中,会产生竞态条件(race condition)。此时当多个线程同时执行异常处理代码时,它们将竞争栈展开的锁,从而可能产生异常错误。

对于多进程程序而言C++ 异常处理面临的主要问题是跨进程通信。由于不同的进程是由操作系统独立创建和管理的,因此在使用异常处理时必须使用特定的工具和接口进行跨进程通信来处理异常。这是一个比较困难且容易出错的问题,需要开发者具备更高的技术水平。

3 异常处理的与语言特性的互动问题

C++ 是一种功能非常强大的编程语言,其中包括许多高级特性,如内存管理、多线程、模板和泛型编程等等。而这些高级特性和异常处理机制也不可避免地会发生相互作用(interaction)。例如,在使用模板进行函数调用时,如果出现异常C++ 编译器将需要展开大量的代码,这会导致编译时间非常漫长。此外,异常处理还会对函数调用返回值产生一定的影响。

3.1语言特性与异常处理的互动示例

下面是一个使用异常处理机制和模板进行函数调用的示例:

在示例中使用了模板进行函数调用,add() 函数用于将两个数相加并检查是否为负数,当出现负数时将抛出一个 invalid_argument 异常。在 sum() 函数中,我们调用了 add() 函数,在遇到异常时在 catch 块中处理异常。

#include <iostream>// 将两个数相加
template <typename T>
T add(T a, T b) {if (a < 0 || b < 0) {throw std::invalid_argument("Can not add negative numbers"); // 抛出异常}return a + b;
}// 计算总和
template <typename T>
T sum(T* array, int size) {T total = 0;try {for (int i = 0; i < size; i++) {total = add(total, array[i]); // 调用 add() 函数}} catch (const std::exception& e) {std::cerr << "Exception: " << e.what() << std::endl;// 处理异常}return total;
}int main() {int arr[] = {1, 2, 3, 4, 5};float arr2[] = {1.0, 2.0, -3.0, 4.0, 5.0};std::cout << "Total is: " << sum(arr, 5) << std::endl;std::cout << "Total is: " << sum(arr2, 5) << std::endl;return 0;
}

七、异常处理最佳实践总结

在软件开发过程中,异常处理是一项非常重要的任务。异常处理可以帮助我们更好地处理错误,保护系统稳定,并提高代码可读性。下面将从设计原则、应用指南、高级用法和最佳实践等方面综述异常处理的最佳实践。

1 异常处理设计原则

1.1 适当应用异常处理

异常处理是一项强大的工具,但并不适用于所有情况。在设计和实现异常处理时,需仔细权衡优缺点,并熟知相应的语言特性。只有在必要的情况下才应使用异常处理。

1.2 保持一致性和可预测性

异常处理应遵循一致的实现规则,并能够在各种情况下表现出可预测和一致的行为。为了保持公平和一致性,请确保对所有异常进行相同的处理。

1.3 避免不必要的资源占用

异常处理需要开发者为异常情况下的资源释放做好准备。所有开启的资源也应在异常处理代码中被一同释放。

1.4 保证代码可读性和可维护性

异常处理应该被编写成易于理解和维护的代码。异常处理器不应过于冗长,应是单一责任原则(SRP)的合理实现。

1.5 保持代码一致和清晰

代码应该是维护良好的,以便于后来人员能够快速理解代码的操作逻辑。在未捕获异常时,因为无法正常执行代码,所以代码已经无法保持一致和清晰。

2 异常处理的应用指南

2.1 捕获特定异常类型

当捕获特定类型的异常时,可以处理对应类型的异常更具精确性。请确保在可能发生异常的所有段代码中,这些异常得到了有效处理。

2.2 选择合适的抛出异常类型

抛出合适的异常类型有助于错误和异常的分类,提高代码的清晰度。将抛出的异常分成可预测的几类不仅易于编写代码,同时易于捕获和处理。

2.3 清晰简单的异常处理

设计应用清晰、简单、一般性良好的异常处理机制可以大幅提高代码的可读性与维护性。我们可以将异常处理代码与正常代码隔离,并选择单独的函数处理异常。

3 异常处理的高级用法

3.1 嵌套异常

嵌套异常是将异常作为另一个异常的信息来传递传递的一种方法。这种机制允许我们在处理异常时获取其他相关信息,并将所有信息一并传递到上层调用点。在库设计和开发中经常会遇到这种情况,以便将底层异常传递给高层调用 code。

3.2 携带元数据(metadata)

有时希望将异常与其他信息一起抛出。可能想捕捉一些元数据,如数据的大小、起始时间等。这可以通过在异常类中添加元数据来实现。

3.3 使用异常规范

异常规范是在函数定义中指定哪些异常是会被该函数可能抛出的。使用它将有助于加强代码的清晰度,并帮助其他开发人员理解代码的行为。

4 异常处理的最佳实践

4.1 永远不要吞噬异常

不要仅仅简单地忽略异常。即使不能处理它们,也应该合理、正确地报告它们。

4.2 不要在程序框架代码中使用异常

在程序框架代码中使用异常会使代码设计变得更加困难,并增加开销复杂度。

4.3 不要过度使用异常

除非必要,否则不要过度使用异常处理。尽可能使用其他可行的程序。这是因为异常会影响代码的可读性和可扩展性。

4.4 使用别称

使用别名来创建函数名和异常类型名。这样隐藏底层的实现特定的类型将使代码更清晰、更易读。

4.5 记录每个异常

始终记录每个异常,包括挑战过程和结果,以改善测试和问题报告。

小结

优秀的软件开发者懂得如何处理异常以确保代码的稳定性和可靠性。我们必须严格遵循设计原则,掌握异常处理的常用指南、高级用法和最佳实践。这将使代码更简洁、清晰和可维护。

相关文章:

深度解析C++异常处理机制:最佳实践、性能分析和挑战

C 基础知识 八 异常处理 下篇 一、异常处理实践1 编写高质量代码中的异常处理1.1 只在必要时才使用异常1.2 尽量减小异常的范围1.3 不要隐藏异常1.4 不要在析构函数中抛出异常1.5 使用 RAII 技术来管理资源 2 维护异常类2.1 按照异常类型的功能来定义异常类2.2 继承现有的异常类…...

【Spring事务】Spring事务事件控制,解决业务异步操作

使用背景 在业务中&#xff0c;经常会有这样的需求&#xff0c;在数据库事务提交之后&#xff0c;发送异步消息或者进行其他的事务操作。 例如当用户注册成功之后&#xff0c;发送激活码&#xff0c;如果用户注册后就执行发送激活码&#xff0c;但是在用户保存时出现提交事务…...

Java 中的注释有哪些?

在 Java 中&#xff0c;有三种注释方式&#xff1a;单行注释、多行注释和文档注释。注释是程序中的一种重要的辅助性说明文字&#xff0c;可以增加程序的可读性和可维护性&#xff0c;方便其他程序员阅读和理解代码。 单行注释 单行注释是指以“//”开头的注释&#xff0c;注释…...

yolov4

1 V4版本概述 集各种优秀方案于一身&#xff0c;嫁接了众多主流的目标识别方面的情况。 V4 贡献 3. 数据增强策略分析 BOF Bag of freebies(BOF) Mosiac 数据增强 Mixup 比如将狗和猫的两张图片混合&#xff0c;一半猫&#xff0c;一半狗。 label 也变成 Dog 0.5 , Cat 0…...

金融学第二版笔记第一章1.1

第1部分 金融和金融体系 第一章金融学 1.1 一、 对金融学进行界定 1.金融 金融是货币流通、信用活动及与之相关的经济行为的总称。 简言之&#xff0c;就是货币资金的融通。一般是指以银行、证券市场等为中心的货币流通和信用调节活动&#xff0c;包括货币的发行和流通、存…...

[架构之路-193]-《软考-系统分析师》-2-应用数学 - 项目周期与关键路径(PERT图、甘特图、单代号网络图、双代号网络图)

1. 关键概念 1.1 关键路径 关键路径通常&#xff08;但并非总是&#xff09;是决定项目工期的进度活动序列。它是项目中最长的路径&#xff0c;即使很小浮动也可能直接影响整个项目的最早完成时间。关键路径的工期决定了整个项目的工期&#xff0c;任何关键路径上的终端元素…...

滋灌中小企业,分销伙伴和华为来做“送水人”

最近有个段子&#xff1a;第一批靠生成式AI赚大钱的人&#xff0c;既不是研发人员&#xff0c;也不是国内大厂&#xff0c;而是卖课的。 大家笑谈&#xff0c;每一轮新技术的掘金之路&#xff0c;最先致富的都是送水、卖铲子的。 这其实隐藏了一个信息技术产业的普遍规律&#…...

面试华为测试岗,收到offer后我却毫不犹豫拒绝了....

我大学学的是计算机专业&#xff0c;毕业的时候&#xff0c;对于找工作比较迷茫&#xff0c;也不知道当时怎么想的&#xff0c;一头就扎进了一家外包公司&#xff0c;一干就是2年。我想说的是&#xff0c;但凡有点机会&#xff0c;千万别去外包&#xff01; 在深思熟虑过后&am…...

深入了解浮点型变量输入与输出

深入了解浮点型变量输入与输出 前言 C 语言中浮点型变量的输入和输出在程序开发中非常常见&#xff0c;比如经常出现在数据处理和科学计算中。在此篇文章中&#xff0c;我们将探讨浮点型变量输入和输出的一些细节和注意事项。 浮点型变量的定义和初始化 在 C 语言中&#x…...

Vector - CAPL - CANoe硬件配置函数 - 03

目录 canFlushTxQueue -- 刷新已定义的Tx队列 代码示例 canSetChannelAcc -- CANoe接收过滤器设置 代码示例 canSetChannelMode -- CAN控制器Tx使能/失能 代码示例 canSetChannelOutput -- Ack自应答使能/失能 代码示例 getCardTypeEx -- CAN控制器类型 canFlushTxQue…...

单开网页应用利器 - BroadcastChannel

前言 前段时间在做一个基于 psd 模板生成图片的应用&#xff0c;其中重要的功能就是打开编辑器页面来设计出图。但是有个问题&#xff0c;每当我点击一个模板&#xff0c;就会新开一个浏览器页签。现代浏览器是以空间换时间的运行思路来提高效率&#xff0c;这就导致了内存开销…...

OpenCv更改颜色空间以及图像阈值

本文主要讲解以下几个方面: 如何将图片从一个颜色空间转换到另一个&#xff0c;例如 BGR 到 Gray&#xff0c;BGR 到 HSV 等。简单阈值法另外&#xff0c;我们会创建一个从图片中提取彩色对象的应用。 1.改变颜色空间 cv.cvtColor(img, flag) 参数flag表示颜色空间转换的方…...

(邱维声)高等代数课程笔记:基,维数与坐标

3.5 基&#xff0c;维数与坐标 \quad 本节&#xff0c;继续研究线性空间的结构。一般地&#xff0c;设 V V V 是数域 K K K 上的一个线性空间。 \quad 首先&#xff0c;我们先将“线性相关”与“线性无关”的概念由“有限”向“无限”推广。 对比其它高等代数教程&#xff0c…...

Spring Security + Jwt 集成实现登录

文章目录 前言Maven 相关依赖配置文件自定义springsecurity相关认证流程继承WebSecurityConfigurerAdapter继承AbstractAuthenticationToken继承AbstractAuthenticationProcessingFilter实现AuthenticationProvider实现UserDetailsService实现AccessDeniedHandler实现Authentic…...

yolov5 用自己的数据集进行训练

在训练之前先要按照一定目录格式准备数据&#xff1a; VOC标签格式转yolo格式并划分训练集和测试集_爱钓鱼的歪猴的博客-CSDN博客 目录 1、修改数据配置文件 2、修改模型配置文件 3、训练 1、修改数据配置文件 coco.yaml 拷贝data/scripts/coco.yaml文件&#xff0c; pa…...

1951-2023最新中国基础地理信息,包括水系、行政区、DEM高程、气象站经纬位置、土地利用,这些数据获取方法介绍

水系&#xff1a; 流域内所有河流、湖泊等各种水体组成的水网系统&#xff0c;称作水系。其中&#xff0c;水流最终流入海洋的称作外流水系&#xff0c;如太平洋水系、北冰洋水系&#xff1b;水流最终流入内陆湖泊或消失于荒漠之中的&#xff0c;称作内流水系。 [1] 流域面积的…...

CAD处理控件Aspose.CAD功能演示:在 C#中以编程方式搜索 DWG 图形文件中的文本

Aspose.CAD 是一个独立的类库&#xff0c;以加强 Java应用程序处理和渲染CAD图纸&#xff0c;而不需要AutoCAD或任何其他渲染工作流程。该CAD类库允许将DWG&#xff0c; DWT&#xff0c; DWF&#xff0c; DWFX&#xff0c; IFC&#xff0c; PLT&#xff0c; DGN&#xff0c; OB…...

实验二十、压控电压源二阶 LPF 幅频特性的研究

一、题目 研究压控电压源二阶低通滤波电路品质因数 Q Q Q 对频率特性的影响。 二、仿真电路 电路如图1所示。集成运放采用 LM324AJ&#xff0c;其电源电压为 15V。 图 1 压控电压源二阶低通滤波电路幅频特性的测试 图1\,\,压控电压源二阶低通滤波电路幅频特性的测试 图1压控…...

类和对象【C++】【中篇】

目录 一、类的6个默认成员函数 1、构造函数 2、析构函数 3、拷贝构造函数 4、赋值重载函数 二、赋值运算符重载 一、类的6个默认成员函数 注意&#xff1a;默认成员函数不能在类外面定义成全局函数。因为类里没有的话会自动生成&#xff0c;就会产生冲突。 1、构造函数…...

2.SpringBoot运维实用篇

SpringBoot运维实用篇 ​ 基础篇发布以后&#xff0c;看到了很多小伙伴在网上的留言&#xff0c;也帮助超过100位小伙伴解决了一些遇到的问题&#xff0c;并且已经发现了部分问题具有典型性&#xff0c;预计将有些问题在后面篇章的合适位置添加到本套课程中&#xff0c;作为解…...

【c++】浅讲引用

【c】浅讲引用 前言引用定义作用做输出型参数引用作返回值总结 关于引用的权限 结尾 前言 博主开始细学c和linux了 这次就带来浅学了的引用。 引用 定义 引用不是在内存中开辟一个新空间的新变量 类似于给变量取别名&#xff0c;和取别名的对象在空间中公用一个对象 例&#…...

CSS布局基础(文字[行内<块>]与行内[块]垂直对齐方式 文字溢出显示省略号)

文字[行内<块>]与行内[块]垂直对齐方式 文字[行内<块>]与行内[块]垂直对齐方式概述图片底部空隙问题 文字溢出显示省略号单行文字多行文字 文字[行内<块>]与行内[块]垂直对齐方式 概述 vertical-align: top | middle| bottom | baseline(默认) | sub | sup…...

AI自动写文章_免费在线原创文章生成器

自动写文章生成器 自动写文章生成器是一种利用人工智能和自然语言处理技术&#xff0c;帮助用户快速生成文章的工具。该软件可以根据用户的需求和选择&#xff0c;自动生成符合要求的文章&#xff0c;无需手动编写和修改。 自动写文章生成器的主要功能包括以下几个方面&#…...

Java阶段二Day15

Java阶段二Day15 文章目录 Java阶段二Day15复习前日知识点对象数据类型注入数组类型注入集合类型的注入p命名空间引入外部属性文件 基于XML管理beanbean的作用域bean的生命周期代码演示生命周期后置处理器处理展示基于XML的自动装配 基于注解管理bean开启组件扫描使用注解定义B…...

从月薪3000到月薪20000,自动化测试应该这样学...

绝大多数测试工程师都是从功能测试做起的&#xff0c;工作忙忙碌碌&#xff0c;每天在各种业务需求学习和点点中度过&#xff0c;过了好多年发现自己还只是一个功能测试工程师。 随着移动互联网的发展&#xff0c;从业人员能力的整体进步&#xff0c;软件测试需要具备的能力要…...

Python魔法方法 单例模式

前言 本文介绍一下python中常用的魔法方法以及面向对象中非常重要的单例模式。 魔法方法 python中一切皆对象&#xff0c;因为python是面向对象的编程语言。python给类和对象提供了大量的内置方法&#xff0c;这些内置方法也称魔法方法。这些魔法方法总是在某种条件下自动触…...

计算机网络基础知识(三)—— 什么是OSI七层模型?

文章目录 00 | &#x1f6f8;发展史&#x1f6f8;01 | &#x1f6f8;OSI七层参考模型&#x1f6f8;02 | &#x1f6f8;OSI七层参考模型的信息流向&#x1f6f8; OSI七层模型是Open Systems Interconnection Reference Model的缩写&#xff0c;是由国际标准化组织&#xff08;IS…...

Python(符号计算常微分方程)谐振子牛顿运动方程

牛顿运动方程 牛顿运动方程可以写成以下形式 F d p d t m d v d t m d 2 r d t 2 \mathbf{F}\frac{d \mathbf{p}}{d t}m \frac{d \mathbf{v}}{d t}m \frac{d^2 \mathbf{r}}{d t^2} Fdtdp​mdtdv​mdt2d2r​ 恒力问题 具有恒定力的问题意味着恒定的加速度。 典型的例子是…...

OpenCL编程指南-1.2OpenCL基本概念

OpenCL概念基础 面向异构平台的应用都必须完成以下步骤&#xff1a; 1&#xff09;发现构成异构系统的组件。 2&#xff09;探查这些组件的特征&#xff0c;使软件能够适应不同硬件单元的特定特性。 3&#xff09;创建将在平台上运行的指令块&#xff08;内核)。 4&#xff09…...

使用 ChatGPT 辅助学习——为自己找一个老师

我们每个人都有许多标签&#xff0c;例如高中生、成绩中等、文科&#xff0c;根据这些标签我和其他拥有相同标签的人分配了相同的教程、班级和老师&#xff0c;这可以带来效率上的提升&#xff0c;因为同一份教程、老师就可以服务几十上百人&#xff0c;而无须为每个人定制&…...