我想在这里补充一下,因为我见过的几乎所有 java / C# 代码中的异常处理都是不正确的。即,对于忽略的异常,您最终会遇到非常难以调试的错误,或者同样糟糕的是,您会得到一个晦涩的异常,它什么也没告诉您,因为盲目地遵循“捕获(异常)是不好的”,事情只会更糟。
首先,了解异常是一种促进跨代码层返回错误信息的方式。现在,错误 1:一个层不仅仅是一个堆栈帧,一个层是具有明确职责的代码。如果您只是编写接口和实现代码只是因为,那么您有更好的东西需要修复。
如果层设计得很好并且有特定的职责,那么错误信息在冒泡时具有不同的含义。<-这是做什么的关键,没有普遍的规则。
因此,这意味着当发生异常时,您有 2 个选项,但您需要了解您在层中的位置:
A)如果你在一个层的中间,你只是一个内部的,通常是私有的,辅助函数并且出现了问题:不要担心,让调用者接收异常。它完全没问题,因为您没有业务上下文,并且 1)您没有忽略错误,并且 2)调用者是您的层的一部分,应该知道这可能会发生,但您现在可能没有上下文来处理它。
或者 ...
B)你是层的顶部边界,内部的立面。然后,如果你得到一个异常,默认应该是 CATCH ALL 并阻止任何特定的异常跨越到上层,这对调用者来说没有意义,或者更糟糕的是,你可能会改变并且调用者将依赖于一个实现细节,两者都会打破。
应用程序的强度是层之间的解耦级别。在这里,您将按照一般规则停止一切并使用一般异常重新抛出错误,将信息转换为对上层更有意义的错误。
规则:层的所有入口点都应使用 CATCH ALL 保护,并翻译或处理所有错误。现在这种“已处理”仅发生 1% 的时间,大多数情况下您只需要(或可以)以正确的抽象返回错误。
不,我确信这很难理解。真实示例->
我有一个运行一些模拟的包。这些模拟在文本脚本中。有一个包可以编译这些脚本,还有一个通用的 utils 包,它只读取文本文件,当然还有基本的 java RTL。UML 依赖是->
模拟器->编译器->utilsTextLoader->Java文件
1) 如果某个私有内部的 utils 加载器出现问题,并且我得到 FileNotFound、Permissions 或其他任何内容,那就让它通过吧。您无能为力。
2) 在边界处,在最初调用的 utilsTextLoader 函数中,您将遵循上述规则和 CATCH_ALL。编译器不关心发生了什么,它只需要现在是否加载了文件。因此,在捕获中,重新抛出一个新异常并将 FileNotFound 或其他任何内容转换为“无法读取文件 XXXX”。
3) 编译器现在将知道源没有加载。这就是它需要知道的一切。因此,如果我稍后将 utilsTestLoader 更改为从网络加载,编译器将不会更改。如果您放开 FileNotFound 并在以后进行更改,您将一无所获地影响编译器。
4) 循环重复:为文件调用底层的实际函数在得到异常时将不做任何事情。所以它让它上升。
5)当异常到达模拟器和编译器之间的层时,编译器再次 CATCHES_ALL,隐藏任何细节并抛出更具体的错误:“无法编译脚本 XXX”
6)最后再重复一次循环,调用编译器的模拟器函数就放手了。
7)最终的边界是用户。用户是一个 LAYER 并且都适用。主要有一个 catches_ALL 的尝试,最后只是创建一个漂亮的对话框或页面,并向用户“抛出”一个翻译错误。
所以用户看到了。
模拟器:致命错误无法启动模拟器
-编译器:无法编译脚本 FOO1
--TextLoader: 无法读取文件 foo1.scp
---trl:文件未找到
相比于:
a) 编译器:NullPointer Exception <-common case and a lost night debugging a file name typo
b)加载器:找不到文件<-我是否提到加载器加载了数百个脚本?
或者
c) 什么都没有发生,因为一切都被忽略了!!!
当然,这假设在每次重新抛出时您都没有忘记设置原因异常。
好吧,我的 2cts。这个简单的规则多次救了我的命……
- 啤酒