锡兰建模失败

在所有编程语言中,我们都需要处理无法“失败”的操作:

  • 纯函数可能无法产生结果,或者
  • 一个不纯净的函数可能无法产生所需的副作用(创建一个新文件,或其他)。

在任何一种情况下,我们都不能盲目地继续其余的计算。 在第一种情况下,函数的结果可能是其他函数的输入。 在第二种情况下,后续操作可能会假定发生了副作用(该文件现在存在,或其他原因)。

因此,很明显,某种操作必须有某种方式可以向调用代码发出失败信号。 编程语言为信令失败提供了两种广泛的机制:

  1. 失败可以通过返回值表示:错误代码, null联合类型总和(枚举)类型 ,例如Option / MaybeEither
  2. 可以在某种异常或“紧急”设施内发出故障信号并进行处理。

尽管细节有所不同,但大多数现代编程语言都支持这两种机制。 特别是,语言提供了不同程度的类型安全性。

  • 在对求和类型或联合类型提供适当支持的语言中,可以使用返回值以非常健壮和类型安全的方式对失败进行建模。
  • 在具有某种效果类型的语言中,例如Java样式检查的异常,这些异常本身是类型安全的。

通过类型安全 ,我的意思是,一个可能失败的操作声明了其签名失败的可能性,并且编译器强制立即调用代码以明确处理该失败。

故障类型

那么,语言应该为建模失败提供哪些便利? 返回码或异常? 类型安全与否? 为了部分回答这个问题,让我们从以下“失败”分类开始:

  • 某些故障表示立即调用代码不太可能从中恢复的问题。 示例包括事务回滚,网络故障,内存不足或堆栈溢出。
  • 某些故障通常是程序逻辑中的错误导致的。 示例包括断言失败,被零除和使用空指针。 这是一类故障,我们希望在编译时尽可能地进行检测,但是没有任何类型系统具有足够的能力来检测所有这些故障。 经过几分钟的思考,您应该能够使自己确信此类问题实际上是第一类的子类:任何计算如何从其逻辑中的错误中有意义地恢复?
  • 最后,有些“故障”通常表示可恢复的状况。 例如,可以通过创建文件来从不存在的文件中恢复。 请注意,此类中的失败不一定总是可以恢复的。

根据这种分类,我相对较快地得出以下结论。

处理可恢复的故障

对于“可恢复”条件,故障应为类型安全的。 编译器应该能够验证调用代码是否已明确决定如何处理故障:从故障中恢复或将其转换为不可恢复的故障。 我们希望防止意外发现可恢复的故障。

显然,未经检查的异常(或其他非类型安全的解决方案,例如以Java(其中null为非类型安全的语言)返回null不会阻止这种情况,并且会导致故障情况不被注意,从而导致错误。

表示可恢复故障的最方便,最优雅,最有效的方法是联合类型的返回值。 例如,如果我有一个解析JSON的函数,并且可能因非法输入而失败,则可以使用具有以下签名的函数:

JsonObject|ParseError parseJson() => ... ;

或者,如果ParseError似乎没有任何有用的信息,我可以只使用Null

JsonObject? parseJson() => ... ;

或者,在某些高级情况下,可以使用求和(枚举)返回类型。

interface ParseResult of ParseSuccessful | ParseError {}
class ParseSuccessful(shared JsonObject result) satisfies ParseResult {}
class ParseError(shared String message) satisfies ParseResult {}

ParseResult parseJson() => ... ;

但是,在锡兰通常不需要这样做。

处理不可恢复的故障

现在,对于“不可恢复”的情况,故障应该是未类型化(未经检查)的异常。 对于无法恢复的失败,我们不应该因为担心调用代码可能无法做任何有用的事情而污染它。 我们希望故障能够快速透明地传播到某些集中的,通用的,基础架构级别的错误处理。

请注意,由于未经检查的异常不会出现在操作的签名中,因此调用方不会收到任何可能发生的“合理警告”。 它们代表类型系统中的一种设计“洞”。

有疑问时

但是,等等,您可能在想,我不是在这里乞求一个大问题吗?

不能完全归入“可恢复”或“不可恢复”的失败呢?

那里是否没有一个巨大的灰色区域,充满了有时可以通过立即调用的代码恢复的故障?

确实有。 我想说,根据经验, 将这些故障视为可恢复的

考虑上面的parseJson()函数。 给定JSON文本中的语法错误很可能是我们程序中的错误的结果,但是至关重要的是, 这不是parseJson()本身的错误。 知道是程序错误还是其他原因的代码是调用代码,而不是parseJson()函数。

调用代码将可恢复的故障转换为不可恢复的故障总是很容易的。 例如:

assert (is JsonObject result = parseJson(json));
//result must be a JsonObject here

要么:

value result = parseJson(json);
if (is ParseError result) {
    throw AssertionError(result.message);
}
//result must be a JsonObject here

也就是说,当有疑问时,我们使调用代码明确记录其假设。

从另一种角度来看,我们在类型安全性方面犯了错误,因为有太多未经检查的异常开始破坏静态类型系统的整个价值。

此外,很有可能调用代码比调用代码更适合产生错误,并提供更有意义的信息(尽管我没有在上面的代码片段中对此进行说明)。

要考虑的三件事

我答应为我的原始问题提供“部分”答案,因为仍然有几个问题我不确定我是否完全回答了这个问题,而且锡兰社区也对这些问题进行了辩论。

是否过度使用了AssertionError

首先, AssertionError合法使用是什么类型的故障? 每个AssertionError应该代表程序中的错误吗? 库在遇到考虑滥用其API的情况时抛出AssertionError是否合理? 从AssertionError恢复通用的异常处理代码是否可以接受,还是应该将AssertionError视为致命的?

我的回答是是,是和是。 但这也许意味着遵循Java将AssertionErrorError而不是普通的ExceptionError的。 (这引起了有关Error角色的更大争论。)

Null是否被过度使用?

其次, Null类是一种诱人的便捷方式,用于表示返回值的函数失败。 但是我们过度使用了吗? 将Map<Key,Item>.get()的返回类型设为Item|NoItem<Key>而不是更通用的Item?会更好Item? ,即Item|Null

也许。 从某种意义上说,返回null就像抛出Exception :有点太通用了。 但是,由于必须由立即调用的代码来处理null返回值,因此最好知道该null实例表示什么,因此它的危害不如从源头处理的泛型Exception那样有害。

无论您是否同意,仍然最好避免操作可能因多种不同的故障情况而导致null操作。 我过去曾违反此规则,将来会更加小心。

没有有用的返回值的函数

第三,对于没有有用返回值的函数,也就是说,仅出于副作用而调用的函数(调用代码可以选择忽略表示失败的任何返回值),我们应该错误地抛出异常?

或者,该语言是否应该提供某种方法来强制调用者使用non- void函数的返回值执行某些操作

锡兰没有(也不会有)检查异常,但是有人可能会认为这是最有用的一种情况。

结论

因此,最终会有一些未解决的问题和灰色区域,但是在我看来,至少我们有一个相当强大的概念框架来研究这些问题。 显然,设施的组合(联合类型以及未检查的异常)是强大的故障处理的强大基础。

翻译自: https://www.javacodegeeks.com/2015/12/modelling-failure-ceylon.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值