速来报名!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:542627
用户头像

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

关注

评论

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

设计一个 SaaS 系统需要考虑的4个关键点

Im胡子

系统架构 SaaS SaaS设计 SaaS系统架构

Apache SeaTunnel (Incubating) 2.1.0 发布,内核重构、全面支持 Flink

Apache SeaTunnel

大数据 大数据平台 apache 社区 Apache SeaTunnel #开源项目

IT运维工具难用吗?有没有简单易操作的?

行云管家

云计算 运维 IT运维

Rust的Cow类型

Shine

rust cow

IOS技术分享| ARCallPlus 开源项目(一)

anyRTC开发者

ios 移动开发 语音通话 视频通话 呼叫邀请

什么是目标关键词?

源字节1号

前端开发 后端开发 SEO优化 网站开发

APICloud App开发教程之云修复功能

YonBuilder低代码开发平台

APP开发 APICloud 热更新

玩转OpenMLDB社区,四张角色卡待解锁

第四范式开发者社区

人工智能 数据库 开源 贡献者 特征平台

使用APICloud AVM多端框架开发仿微信通讯录功能

YonBuilder低代码开发平台

前端开发 APP开发 APICloud 多端开发 avm.js

建木小故事

Jianmu

开源 后端 持续集成 建木CI

presto是如何保证作业内存不会发生冲突和溢出

华为云开发者联盟

内存 presto 内存计算引擎 System Pool general Pool

云效DevOps全家桶评测征集令重磅来袭!免费使用云效全套功能

阿里云云效

云计算 阿里云 DevOps 云原生

IT运维工具难用吗?有没有简单易操作的?

行云管家

运维 IT运维

焕然一新的 Vue 3 中文文档来了

CRMEB

基于Laravel模块化极速开发框架 免费开源CMS

ModStart开源

深度解读「无影云电脑远程办公解决方案」

阿里云弹性计算

远程办公 无影云电脑

中国版Postman:Apifox

Liam

程序员 Jmeter Postman API swagger

java培训Redis高频面试考点

@零度

Java redis

DPU芯片头部企业云豹智能加入龙蜥社区,共同推动新一代数据中心基础设施蓬勃发展

OpenAnolis小助手

云计算 开源 芯片 龙蜥社区

前端培训之常见算法分享

@零度

前端算法

美国法院最新判决:未经 OSI 许可的开源是「假开源」!

腾源会

开源 腾源会

MongoDB与亚马逊云科技扩大全球合作

MongoDB中文社区

mongodb

ModStartCMS Laravel9 模块化建站系统 v3.5.0 多图字段支持,系统优化升级

ModStart开源

大数据培训十大Hive调优技巧

@零度

大数据 hive调优

公有云RDS太贵?基于ECS构建的多云RDS服务可降低近半成本

沃趣科技

数据库 公有云 RDS 云数据库RDS for MySQL 云数据库Redis

架构实战营模块八消息队列mysql数据库设计

刘洋

架构实战营 #架构实战营 「架构实战营」

春招进行时!当代大学生求职行为大赏

易观分析

求职 招聘 春招

你了解部署流水线吗?

华为云开发者联盟

自动化 软件开发 devcloud 部署流水线 流水线

FAQ(常见问题)页面的编写技巧

小炮

企业 常见问题 客户服务

Redis 缓存击穿(失效)、缓存穿透、缓存雪崩怎么解决?

码哥字节

Redis 核心技术与实战 Redis 热点key 缓存服务 3月月更

“后疫情时代”支付厂商发力B端已成共识,市场规模破3千亿!

易观分析

产业支付

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