编程语言迷思,为什么它们都无法完美地处理错误?还记得《少林寺》中的牧羊女吗?如今身价过亿,年过半百依然很美

2023-05-29 0 1,034

编程语言迷思,为什么它们都无法完美地处理错误?还记得《少林寺》中的牧羊女吗?如今身价过亿,年过半百依然很美

【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语言能轻松奈镇。

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务