QCon 演讲火热征集中,快来分享技术实践与洞见! 了解详情
写点什么

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:542682
用户头像

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

关注

评论

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

在华为云构建多云跨云的容灾系统,真的很香

路过的憨憨

2023移动云大会重磅官宣,云改“新三年”强势开局引期待?

ToB行业头条

更专业、安全的过等保,华为云等保合规解决方案值得选择

IT科技苏辞

华为云等保合规解决方案,助力企业高效过等保

秃头也爱科技

不愧是Github获赞78.9K的「LeetCode全彩版刷题小册」面面俱到,太全了

采菊东篱下

面试 算法 力扣

华为云等保合规解决方案,助力企业轻松、省心、安全“上云”

秃头也爱科技

如何利用人工智能的语言模型创造价值和收益

派大星

ChatGPT

MySQL数据文件被误删,如何进行恢复?

架构精进之路

MySQL 数据库 三周年连更

Go 也能实现 “继承”?

陈明勇

Go golang 继承 三周年连更

布隆过滤器的设计之美,后端程序员一定要好好体会

程序员小毕

程序员 数据结构 面试 后端 布隆过滤器

华为云等保方案,为企业上云保驾护航

科技说

华为云等保合规方案,助力企业最快30天过等保

科技说

跨平台应用开发进阶(四十四)一文走近应用层抓包工具:Charles

No Silver Bullet

应用层 抓包分析 抓包工具 三周年连更

Vue3 watch 与 watchEffect

程序员海军

Vue Vue 3 watch 三周年连更

助力企业网络安全建设,华为云等保合规解决方案值得拥有

路过的憨憨

机器视觉公司,在玩一局玩不起的游戏

脑极体

CV

克隆远端项目,idea 自动生成了一个.idea 文件夹,怎么办?

bug菌

git IDEA .gitignore 三周年连更

Meetup 直播预告|助力企业数字化转型,8 大微服务&容器开源实践亮点抢先看

阿里巴巴云原生

阿里云 开源 容器 微服务 云原生

【Linux】之【网络】相关的命令及解析[ethtool、nload、nethogs、iftop、iptraf、ifstat]

A-刘晨阳

Linux 网络 三周年连更

新技术加持下前端开发工程师的未来在哪里?| 社区征文

No Silver Bullet

前端开发 新技术 三周年征文

Exception和Error有什么区别吗 | 社区征文

共饮一杯无

Java Exception Error 三周年连更

跨平台应用开发进阶(四十五)uni-app集成企微客服实战

No Silver Bullet

uni-app 项目实战 三周年连更

基于 RocketMQ Connect 构建数据流转处理平台

阿里巴巴云原生

阿里云 RocketMQ 云原生

一天吃透操作系统八股文

程序员大彬

面试 操作系统

Mac怎么创建txt文件?如何设置新建txt的快捷键?

互联网搬砖工作者

termius使用ssh教程 【XShell的神器Termius】

互联网搬砖工作者

华为云等保方案,轻松满足企业等保合规要求

IT科技苏辞

聊聊JavaScript和Scala的表达式 Expression

汪子熙

JavaScript scala 三周年连更

连Hibernate技术都不清楚,你敢说你自己会ORM框架?

浅羽技术

Java hibernate 框架 ORM框架 三周年连更

云效AppStack--扫雷亲测

六月的雨在InfoQ

云效 AppStack 云效流水线 Flow 三周年连更

Spider实战系列-爬取鬼吹灯小说

浅辄

案例分享 三周年连更

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