【CSDN 萨德基】或许没一类C语言能恰当奈镇!
责任编辑译者自https://www.amazingcto.com/best-way-to-handle-errors-for-a-programming-language/
需经许可,明令禁止转发!
译者 | Stephan Schmidt 译者者 | 没错如月公司出品 | CSDN(ID:CSDNnews)当他们撰写标识符时,严重错误经常再次出现在他们初始化其它表达式的过程中:
fn f() {// 当 b() 回到两个严重错误时,可能会再次出现严重错误 a = b() …}难题是:
有时候他们期望间接从表达式中回到,不奈镇
有时候他们期望减轻严重错误的影响
有时候他们期望延迟奈镇的时机,比如和其它严重错误一起处理,最好是用正常的控制流继续执行
每种C语言都找到了一类不同的解决方案来应对这三个挑战。
Java
Java 是最早采用异常机制(Exceptions)来控制严重错误的C语言之一。方法b() 可以在再次出现严重错误时抛出异常。初始化表达式如果什么都不做,此时初始化表达式 f() 就会将异常抛给它的初始化者。当然,他们可以通过把初始化包装在 try/catch 标识符块中,以便稍后处理异常。
Java 方法的缺点是,一旦再次出现严重错误,就会打破正常的控制流程。他们要么处理它,要么让它传递上来。
Java 异常机制的两个缺点是声明检查异常。如果他们的表达式 f() 声明了它的异常,而表达式 b() 抛出了不同的异常,此时,他们就需要处理异常,因为它不能往上冒泡。
Rust
Rust 通过两个机制找到了这个难题的解决方案,这个机制可以自动将两个严重错误——即 b() 的严重错误——转换为另两个严重错误——即 f() 的严重错误。这样他们又可以让严重错误传递上来,而不用处理它。
Rust 使用 ? 来实现这一点:
fn f() { // 让表达式 f() 回到 // 严重错误自动转换并传递上来 a = b()? …}Go
一些C语言通过在回到值旁边回到两个严重错误码来处理这三个挑战。其中之一是 Go。
a, err := b()接下来他们可以通过下面的方式奈镇:
if err != nil { …. }或者选择从表达式中回到。
除非他们想要对某些操作进行处理,否则在再次出现严重错误后,他们可以恢复正常的程序流程。
a = a + 1如果再次出现了严重错误并且 a 是 nil,这就不起作用了。
现在他们每次都可以检查 a 是否存在:
if a != nil { …. }但这会变得繁琐且难以阅读。
一些C语言使用 Monad (Monads 是一类在表达式式编程中使用的结构,它可以将程序表达式和它的回到值组合起来,并在两个类型中添加额外的计算)来奈镇后的控制流难题。
// a 是 Result 类型a = b()有了 Result Monad,我就可以奈镇或从方法中回到。
如上所述,Rust 有一些特殊的语法用于回到:
a = b()?有了问号,当 b() 回到严重错误时,表达式将在那一行回到,并且严重错误会自动转换并传递上来。
他们也可以在严重错误的情况下执行正常的控制流,再次出现严重错误时,仍然可以使用 a ,非常神奇!
a = b()c = a.map(|v| v + 1)…// 稍后奈镇在再次出现严重错误的情况下,c 也会是两个严重错误,否则 c 将包含 a 加 1 的值。这样,无论严重错误是否再次出现,他们都可以在严重错误后有相同的控制流。
这使得对标识符的推理变得更加容易。
Zig 通过对类型进行注释,以 ! 的形式简化了 Result 的表示。
// 回到 i32fn f() i32 {…}// 回到 i32 或严重错误fn f() !i32 {…}Zig 还通过流分析解决了 Java 对异常声明的繁琐难题。它检查你的表达式 f(),找出所有可能回到的严重错误。然后,如果你在初始化标识符中检查特定的严重错误,它会确保是详尽无遗的。
Rust 中的 ? 有两个特殊的语法,可以简化严重错误处理,出错时可立即回到。Java 有 try/catch 的特殊语法,如果他们不撰写额外的标识符,就不会立即回到并将严重错误信息回到给表达式的初始化者。
他们应该使用较简洁的语法
难题的关键是:他们更经常做什么?回到严重错误还是继续执行?他们更常做的事情应该使用较简洁的语法。
在 Rust 中的 ? 的情况下,他们是否需要两个 ? 以便立即回到,或者用 ? 来阻止回到?
a = b()?问号可以表示 “再次出现严重错误时回到”。或者,该行为可以是,如果 b() 回到严重错误,始终立即回到,而 ? 可以阻止这种情况。
这取决于哪种情况更常见。
Golang 可能会给他们另两个思路。
当表达式回到时,它有两个特殊的语法用于执行一些清理操作:
f := File.open(“my.txt”)// 退出表达式时确保关闭文件defer f.close()a, err = b()if err != nil { // 这里初始化 f.close() return}Java 中的 finally 不太优雅。看起来人们认为严重错误应该传递上来,而他们需要在这种情况下进行简单的清理。
从我的经验来看,我也怀疑他们想让大多数严重错误自动转换后往上传递,因此 ? 可能应该表示他们不期望表达式回到,Rust 却和该预期完全相反。
看起来 Java 在异常处理上是恰当的。没暴露向上传递严重错误的语法。但是它错过了自动转换和来自 Rust 的 Exception,以及类似 Go 的本地、简单的 defer,而不是 Java 冗长的 finally。而且 Java 没解释如何恰当地使用异常,所以每个人都用错了。
假设有这样一类语言:
fn f() { // b() 回到 Result 或 Zig 中的 !V, // 如果 b 是严重错误,f() 就回到 // a 是 V 类型 a = b() // 严重错误时不回到,但 a 是 Result 或 !V 类型 a = b()! // 编译为 a = a.map(|v| v + 1) a = a + 1 // 编译为 c = a.map(|v| v.c()) // c 是 Result 类型 c = a.c() …}这具有更高的可读性。
但是,当他们初始化另两个方法时应该怎么办?
// 如果 d 需要的是 C 作为参数类型, 而不是 Result,那么就不起作用d(c)一些语言有特殊的语法来处理这个难题。例如 Haskell 有 do,Scala 有 for。但是会存在很多奈镇的特殊标识符和特殊上下文。这让事情变得复杂,标识符变得难以阅读,和初衷相违背。
所以最好抛出两个编译严重错误。并记住,默认的方式是传递上去,a 是 V 类型。
他们可以通过控制流分析来减轻这种痛苦。一些C语言,如 TypeScript,做了类似的事情
a = b()a = a + 1 // A 仍然是 Resultif a instanceof Error { return}// A 现在是 V 类型// 因为他们检查了严重错误d(a)看起来每种C语言都掌握了最佳严重错误处理谜题的一部分片段。在我看来,或许没一类C语言能轻松奈镇。