👉导读
👉目录
static std::atomic<int> thread_counter;
void foo() {
thread_local int tid = ++thread_counter;
try {
throw MyException("Data from thread", tid);
} catch (const MyException &ex) {
assert(ex.tid == tid);
}
}
foo
,C++ 11 已经能够保证 throw
的异常和捕获住的异常是同一个对象,不会出现线程读写冲突,因为每个 std::current_exception()
都是线程变量而非全局变量。C++11 还有更高级的用法,使用 std::current_exception()
和 std::rethrow_exception()
,可以将一个线程的异常获取保存下来并在另外的线程抛出。return_value
和 return_void 函数包装在了 try/catch
中,所以你不需要再这么做。__cxa_allocate_exception
分配异常对象的内存,在 catch 之后使用 __cxa_free_exception
来释放内存,而通过分析也可知道异常对象的内存是在栈上保存的,不存在异常抛出时协程切换导致异常对象被其他协程修改。EXPECT_EQ
和 Google Protobuf 中 CHECK_EQ
都用了此类技术。static constexpr int fake_result = 1;
std::string test;
UCLI_ASSURE_GT(fake_result, 9) // ASSURE_XX 之类的宏用于保证某个条件一定能实现,否则就触发异常
<< 503 // int 或 枚举类型会翻译成错误码
<< UnifiedControlCode::UCC_UNCERTAIN_BUSY // UnifiedControlCode 会被视为设置控制码
<< "The server is busy, try later" // 字符串类型会被视为附加的错误信息
<< DoReport<OssCheckPoint<123, 1>>() // DoReport 指令用立即上报一个和框架相关的数值(框架相关)
<< WithReport<tags::InvalidTradeNo>() // WithReport 指令用于对 InvalidTradeNo 标记进行上报(框架无关)
<< WithRes<MyString>("My Additional Text") // WithRes 指令用于抛出异常时附加其他数据
<< [&]() { test = "Oh god!"; }; // 可接受一个 Callable 用于执行附加的操作
operator <<
这样的运算符,也只需要配合示例特化 UnifiedExceptionApplier
即可。UnifiedRpcController controller;
int a = controller.SafeCall([]() {
UCLI_ASSURE_EQ(101, 102) << 500 // 错误码
<< UnifiedControlCode::UCC_UNCERTAIN_RETRY // 表示是可重试的错误
<< "Server down!" // 错误文本
<< WithRes<int>(123456); // 其他附加资源
return 100; // 如果成功就会走到这一步
});
a; // 0 出现了错误,直接返回默认值
controller.IsOk(); // false 控制信息记录错误
controller.IsDone(); // true 控制信息表示代码块完成
controller.IsUncertainRetry(); // true 控制信息表示是结果不确定需要重试
controller.error_code(); // 500 控制信息包含的错误码
controller.ErrorText(); // "Check failed: (101) == (102): Server down!" 断言文本+错误提示
controller.Options<int>() // 123456 其他附加资源
controller
中,这样业务方也就没有心智负担的调用其领域服务的逻辑了,当然也可以直接使用 try..catch..
来处理异常。UnifiedRpcController
已经包含了异常的所需的错误码、控制码、错误信息等,那么也应该有一个方法可以让一些含有异常信息的对象转换为异常抛出。例如:UnifiedRpcController contorller;
contorller.SetResult(UnifiedControlCode::UCC_UNCERTAIN_BUSY, 504, "Server busy");
UCLI_ASSURE_OK(contorller); // 将抛出一个异常,错误信息控制码错误码都来源于 controller
if return
出错了居然还有人忍受,一步步去看日志,一步步去跳转代码查看错误原因?if return
,看到一个错误码就懵逼了根本就不知道是哪个错误条件引起的。所以我们在设计之初就为方便调试做了诸多规划。ProcessInBusiness
函数。ProcessInComponent
函数;ProcessInComponent
处理完成了,按照异常处理流程,如果在自己的处理的业务逻辑中,此时应该引发一个新的错误,而不是对上次异常进行重新抛出;PushForward
和 SetFail
在语义上由非常大的区别,一个用于在错误信息中添加一个节点的记录,一个表示完全清空错误链信息;SetFail
时错误使用了 PushForward
并转义了错误码,导致只是在错误链中增加了一行源代码记录信息(如上图中右下的 错误码 -2:❶ 基础组件报错 没有被清除);@ControllerAdvice
或 @RestControllerAdvice
注解,这两个注解都是Spring MVC提供的,作用于控制层的一种切面通知,可以进行全局异常处理、全局数据绑定以及全局数据预处理。GlobalException
),这个异常类可以用于处理项目中的异常,并收集异常信息。这个全局的异常处理类(如GlobalExceptionHandler
)内部使用了 @ExceptionHandler
注解去捕获异常,包括处理自定义异常。总的来说,虽然我们可以为每个业务创建一个唯一的异常子类,但在实践中,这可能会导致代码过于复杂和难以管理。更常见的做法是定义一些通用的异常类,如GlobalException
,并通过全局的异常处理类来捕获和处理这些异常。struct MyString : public string {
using string::string;
std::string ToString() const { return *this; }
};
try {
static constexpr int fake_result = 1;
UCLI_ASSURE_GT(fake_result, 9)
<< 503 << UnifiedControlCode::UCC_UNCERTAIN_BUSY
<< "The server is busy, try later"
<< WithRes<MyString>("My Additional Text");
} catch (const UnifiedException& ex) {
ex.Res<MyString>();
}
WithRes<T>
的模板函数,将某些特定的数据类型在抛出之前放置到异常对象中;当需要关注此异常数据的使用方捕获住异常后,使用 Res<T>
获取抛出时异常对象中的特定数据。On Error Resume Next
并不是在所有情况下都是最佳的错误处理方式。因为它仅仅是忽略错误,而不是解决错误。如果错误涉及到的是关键任务或者数据,这种做法可能会导致程序在后续运行中出现更严重的问题。因此,应该谨慎使用 On Error Resume Next
,并确保在使用它时能够在适当的地方处理或记录错误。Delphi
中的 madExcept
) 可以提供一定的兜底措施,但如果确实是领域逻辑中会出现的异常,还是应该给出友好的错误提示,并提供可被验证的恢复方案。std::bad_alloc
这样的异常);abort
也是一种兜底策略std::runtime_error
mysqlpp::ConnectionFailed
捕获住,为当前场景添加合适的错误码、带上下文的错误描述等。而由于 C++ 的语言特性,一旦 catch 住异常后,再也没有办法可以获取异常发生时的上下文信息、包括调用帧、代码位置等信息,所以框架此时应该直接让操作系统接管,并生成 coredump 文件用于排查调试模式下的可能出现的运行时异常;std::bad_cast
:使用 dynamic_cast
向下转换时失败引发的异常;std::bad_any_cast
:使用 std::any_cast<T>
进行拆箱时引发的转换错误;std::bad_optional_access
:使用 std::optional<T>::value()
获取没有值时引发的错误;google::protobuf::FatalException
:可能由于使用了不正确的反射获取不匹配消息字段引发;boost::bad_lexical_cast
:使用 boost::lexical_cast
进行类型转换引发的异常;fmt::format_error
:使用 fmtlib
对目标对象进行格式化时,由于格式化串错误引发的异常;Json::LogicError
:使用 JsonCpp
获取不到值时,或无法将 Json 类型进行转换时引发的异常(非常常见);mysqlpp::ConnectionFailed
: 使用 MySQL++ 库连接 MySQL 客户端时无法连接上引发的异常;mysqlpp::ConnectionFailed
及时捕获,并在专用系统中登记明确登记错误码,将这个运行时异常转化为逻辑异常(表示这个异常是我已经预期到的,可以被正确的处理的,异常的收敛的也是处理方式之一);std::logic_error
,本方案中的对应 UnifiedException
。即对于不同框架制作一个适配层用于捕获业务异常,再将其转换为框架的能返回回去。UnifiedException
,将其中的错误码转换为返回码、错误信息注入的回报的 error_message
中,其他的信息可以使用 RespCookie 返回;UnifiedException
在执行工作函数时将异常捕获,并按照框架的需求返回UnifiedRpcController::SafeCall
函数先包一层,再进行到 MeshRet
的转换(WxMesh),或在每次调用时使用 try...catch...
手动进行异常处理。if
造成干扰。这一点其实对于一个务实的框架码农是非常容易完成的,我们提出的一些更更加人性化的考虑会重点放在调试和运营阶段。