AI实践哪家强?来 AICon, 解锁技术前沿,探寻产业新机! 了解详情
写点什么

C#的未来:方法契约

  • 2015-05-20
  • 本文字数:2618 字

    阅读完需:约 9 分钟

近些年来,开发者可以通过代码契约(Code Contracts)这个研究性项目获得添加方法级别契约的能力,但这种方式存在许多问题,它所使用的命令式语法相当冗长,并且通过工具提供的语法支持也很差。无论是开发类库或是应用程序,要完整的利用这一契约特性,必须要运行某种编译后指令。总的来说,这是一个有趣的项目,但要真正变得实用,还需要第一等的编译器与语法的支持。

第119 号提议——方法契约旨在提供这种支持。这一语法要求在方法签名与方法体之间定义前置与后置条件,与泛型的约束写法类似。下面这个示例展示了该语法的表现形式:

public int Insert(T item, int index)
requires index >= 0 && index <= Count
ensures return >= 0 && return < Count
{ … }

这条提议中共包含三个新的关键字。“requires”开头的语句负责处理前置条件,多数情况下将用于检查参数,但理论上也可以用于检查对象本身的状态。“ensures”开头的语句用于设定后置条件,它重用了“return”关键字,以指代该方法调用的返回结果。

快速失败还是抛出异常

类似于代码契约,这条提议最初的目的也是产生快速失败。这是一种相当激进的强制契约形式,任何对契约的违反都会立即导致应用的崩溃。在这种模型下,倾向于抛出异常的开发者不得不手动地进行标注:

public int Insert(T item, int index)
requires index >= 0 && index <= Count
else throw new ArgumentOutOfRangeException(nameof(index))
ensures return >= 0 && return < Count
{ … }

对于该提议的这一部分,人们的反对相当激烈。

Nathan Jervis 写道:

你了解程序中哪些部分不会受到影响,并且能够安全地继续执行下去。可能在某些情况下你会编写某些极其关键的代码,或许你会希望实现快速失败,但我不认为了解你的程序中哪一部分产生错误是一件不可能的事。

以立即干掉整个进程的方式作为正确的处理行为,这种假设让人觉得十分可笑。如果微软的 Word 有某个代码错误,在将文件保存到某个网络路径时产生了一个 bug,你会希望它立即干掉整个应用吗?不,你会希望它能够将文件暂存在某个临时路径下,然后为用户显示一条错误信息,记录这个问题,最后在下次加载文件时让用户选择尝试恢复它。

HaloFour 也表示附和:

我认为由于参数校验违反契约而导致整个进程崩溃绝对是一种愚蠢的做法。这种实现方式肯定会导致这一特性将无人问津,除非某人有意要写一些难懂的代码。这一特性中的这方面目的在于参数校验,而程序完全能够以某种形式从参数校验错误的情况下恢复正常运行。而且坦白地说,即使程序无法恢复正常,调用者也可以决定不要捕获这个异常。这也是我所看到在.NET 或其它任何语言中实现的代码契约的工作方式。

David Nelson 则提到了代码契约的经历:

如果你之前曾经使用过代码契约进行开发,那么你一定注意到它在是否应当采取快速失败这一方式上曾经导致大量的争论。代码契约团队进行了几个月(甚至几年?)的努力,试图说服整个社区:快速失败才是正确的做法,可最终他们还是失败了。我深切感受到了那个极具误解性的决定所带来的后果,这让我无法拥抱这一提议。我曾经是快速失败这一荒谬的方式最坚定的反对者,而且我还会继续反对下去。

稍后,他明确地列举了快速失败方式所导致的问题:

1) 你怎样记录错误日志?使用 Watson 显然不能满足需求,绝大多数的.NET 应用程序都不会使用它,因为它提供的信息非常有限并且难以理解,访问这些信息也很困难。我所看到过的每个.NET 应用程序都会生成独有的错误日志。

2) 只因为某个用户在某种极端情况下遇到了一个无害的逻辑 bug,就要让为全球几百万用户提供服务的某个生产环境中的 web 服务器挂掉,这种做法真的合适吗?

3) 如果某个单元测试违反了契约的话该怎么办?让单元测试执行器崩溃吗?

4) 如果一个程序的错误会导致进程的崩溃,为什么在.NET 中其它的错误情况下会抛出异常?NullReferenceException 又为什么还会存在,难道不应当直接干掉进程吗?为什么在 JIT 过程中编译某个方法失败时(这很明显意味着存在某个比违反契约严重得多的问题)会抛出异常,而不是直接干掉进程?

Aaron Dandy 则希望能够得到两种选择:

我当然会使用快速失败,但我只想在我私人的工作中使用它,在公共项目中我还是希望使用异常。如果调用我代码的用户决定用大量的异常去喂饱异常这个怪兽(即选择使用异常),那也是他们自己的选择,他们(同时也隐含了他们代码的用户)也需要为这一决定买单。

HaloFour 对以下观点表示同意:

我更希望方法契约能够抛出异常(至少是对于 requires 语句来说),然后添加一个语言关键字断言,在某种条件未满足的情况下会快速失败。

异常类型

这条提议中比较容易接受的部分是 Argument 异常,编译器会将某个简单的 requires 语句转化为某个 ArgumentNullException 或 ArgumentOutOfRange 异常。如果 requires 语句检查的内容是对象的状态,那么它可以抛出一个 InvalidOperationException 异常。但如果该语句同时检查参数与对象状态呢?这种情况下要决定抛出何种异常会成为一个相当复杂的问题。

另外一个问题在于 ObjectDisposedException,因为没有什么标准方式能够表现一个被回收的对象。因此只能采取一些宽松的约定,检查是否存在某个叫做 _disposed 或 m_IsDisposed 之类名称的布尔型字段。这一点的重要性在于,InvalidOperationException 异常一般来说能够通过改变对象状态的方式进行恢复,而 ObjectDisposedException 永远做不到这一点。

另一方面,需要通过某种异常表示 ensures 语句出错。与 requires 语句不同,在 ensures 契约中的错误总是意味着在方法内部存在 bug。

本地化

假设我们采取了某种基于异常的方式,那么接下来的问题就是本地化了。对于基本的参数检查来说,编译器可以简单地生成包含英文文本的参数异常。但如果这些异常信息需要本地化为其它语言呢?如果选择使用简化的语法,就不会明确地抛出某个参数异常。这种情况下,或者需要通过某种渠道添加本地化的信息,或者不得不使用冗长的语法以显示本地化异常信息。

枚举与契约

目前为止所讨论的契约都是一种附加条件,而在 Fabian Schmied 所提出的提议中,编译器将允许省略那些绝对不会命中的 return 语句。

public string GetText (MyEnum myEnum)
requires defined(myEnum)
{
switch (myEnum)
{
case One: return “Single”;
case Two: return “Pair”;
case Three: return “Triple”;
}
// 所有分支情况都已涵盖,因此即使省略了 return 语句也不会产生错误。
}

查看英文原文: C# Futures: Method Contracts

2015-05-20 08:542914
用户头像

发布了 428 篇内容, 共 190.7 次阅读, 收获喜欢 39 次。

关注

评论

发布
暂无评论
发现更多内容

从3开始,在业务系统中增加分页功能

闫同学

go语言 11月月更 后端系统

一款设计和模拟数字逻辑电路的LogiSim工具

芯动大师

集成电路 Verilog 11月月更 logisim 模电与书店

华为开发者大会2022:HMS Core 3D建模服务再升级,万物皆可驱动

HarmonyOS SDK

hdc HMS Core

2022HDC|华为阅读:探索阅读体验新变革 助力阅读生态创新发展

最新动态

HDC2022 携手共创鸿蒙生态 增长解决方案焕新升级构筑商业增长闭环

叶落便知秋

数据湖(十八):Flink与Iceberg整合SQL API操作

Lansonli

数据湖 11月月更

从零到一带你构建可靠的大型分布式系统,不愧是IT领域又一神作!

Java永远的神

Java 分布式 程序人生 后端 架构师

【C语言】char 关键字

謓泽

11月月更

2022华为开发者大会:华为阅读人-车-家一键流转,实现全场景数字阅读新增长

最新动态

数据库审计的四种类型

阿泽🧸

数据库审计 11月月更

峰会实录 | 基于StarRocks和腾讯云EMR构建云上Lakehouse

StarRocks

数据库

Fastjson最想版本RCE漏洞【漏洞分析】

网络安全学海

网络安全 信息安全 渗透测试 WEB安全 漏洞挖掘

HDC 2022 Day2精彩速递:开发者齐聚松山湖,深度体验鸿蒙开发套件

最新动态

星闪:咫尺之间,联接智能世界

脑极体

我没想到,做IT还有感动众生的机会

明道云

HDC2022 携手共创鸿蒙生态 增长解决方案焕新升级构筑商业增长闭环

最新动态

业务监控设计主要关注点

穿过生命散发芬芳

业务监控 11月月更

数据湖(十七):Flink与Iceberg整合DataStream API操作

Lansonli

数据湖 11月月更

美图是如何搭建压测监控一体化平台的?

TakinTalks稳定性社区

压测平台

HTML学习笔记(二)

lxmoe

html 前端 学习笔记 11月月更

永续合约交易所的开发有哪些特征?

W13902449729

合约交易所开发 区块链交易所开发

计算机网络:差错控制

timerring

计算机网络 11月月更

Go语言开发小技巧&易错点100例(二)

闫同学

go语言 11月月更 go开发

python小知识-并发编程(1)

AIWeker

Python 人工智能 python小知识 11月月更

去哪儿的常态化容量保障是怎么做的?

TakinTalks稳定性社区

基于开源IM即时通讯框架MobileIMSDK:RainbowChat-iOS端v6.1版已发布

JackJiang

即时通讯 MobileIMSDK im开发 开源im

既要技术制胜,也要体验为王:今天我们需要怎样的WLAN?

脑极体

架构实战营模块 4 作业

陌生流云

#架构实战营

Sonatype Nexus 如何把多仓库合并在一起

HoneyMoose

简单剖析开发:区块链杠杆合约交易所的核心优势

W13902449729

区块链交易所 合约交易所开发

2022-11-05:给定一个逆波兰式,转化成正确的中序表达式。要求只有必要加括号的地方才加括号。

福大大架构师每日一题

算法 rust 福大大

C#的未来:方法契约_C#_Jonathan Allen_InfoQ精选文章