50万奖金+官方证书,深圳国际金融科技大赛正式启动,点击报名 了解详情
写点什么

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

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

关注

评论

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

Mac版cad2024发布 AutoCAD 2024 注册机

Rose

Mac软件 cad cad2024激活版 Autodesk AutoCAD

面试必问:JVM 如何确定死亡对象?

Java 面试 JVM

视频下载出来为网页格式?如何将视频转换为mp4格式?

Rose

视频格式转换 Mac视频格式转换 视频下载出来为网页

mac电脑能恢复安卓手机丢失的数据吗?

Rose

mac电脑 安卓数据恢复

互联网工程师1480道Java面试题及答案整理( 2023年 整理版)

Java你猿哥

Java 面试 面经 春招 Java八股文

基础篇丨链路追踪(Tracing)其实很简单

阿里巴巴云原生

阿里云 云原生 Tracing

开源即巅峰!《Java程序性能优化实战》GitHub三小时标星已超34k

Java 性能优化 性能调优

GitHub上架即巅峰!《Spring Cloud微服务架构实战》标星已超30k

Java 架构 微服务 Spring Cloud

灵魂拷问:你写的SQL一般有几个JOIN ?​

Java你猿哥

Java sql 后端 ssm join

Github上获赞59.8K的面试神技—1658页《Java面试突击核心讲》

Java你猿哥

Java 架构 面试 面经 春招

一文告诉你如何一键复现“TSBS 时序数据库性能基准测试报告”测试结果

TDengine

tdengine 性能测试 时序数据库

IM跨平台技术学习(七):得物基于Electron开发客服IM桌面端的技术实践

JackJiang

即时通讯 即时通讯IM

最全金融数据安全政策汇编,你应该需要它!( 附下载 )

极盾科技

数据安全

DaVinci Resolve Studio 18(达芬奇调色剪辑)中文版

Rose

达芬奇18破解版

龙蜥白皮书精选:面向异构计算的加速器 SDK

OpenAnolis小助手

开源 sdk 异构计算 加速器 龙蜥白皮书

夜莺n9e监控配置支持电话短信报警

外滩运维专家

夜莺监控 电话报警 短信报警 夜莺监控电话

连接 AI,NebulaGraph Python ORM 项目 Carina 简化 Web 开发

NebulaGraph

Python ORM 图数据库

如何使用责任链默认优雅地进行参数校验?

Linux 的 TCP 连接数量最大不能超过 65535?

Java你猿哥

Java Linux 后端 ssm

警惕看不见的重试机制:为什么使用RPC必须考虑幂等性

MobTech MobLink|场景分享的原理

MobTech袤博科技

Alibaba官方上线!Java并发编程全彩图册(终极版)GitHub已置顶

Java 并发编程 多线程 高并发

苹果发布macOS Ventura 13.3正式版更新

Rose

mac系统 苹果最新系统 macOS Ventura 13.3

玩转 ChatGPT+极狐GitLab|分分钟丝滑迁移Jenkins到极狐GitLab CI

极狐GitLab

ci DevOps jenkins CI/CD 极狐GitLab

MobTech 秒验|防控羊毛党

MobTech袤博科技

官方文档 | 【JVM调优体系】「GC底层调优实战」XPocket为终结性能问题而生—开发指南

码界西柚

Java JVM 3月日更 XPocket 技术 优化体系

阿里P7架构师的独家分享——SpringCloud 微服务实战笔记

Java你猿哥

Java 架构 微服务 Spring Boot 面经

解密COUNT(*)与COUNT(1):SQL查询你选哪个更高效?

Java你猿哥

Java sql 后端 ssm Java工程师

Nautilus Chain 首个生态基础设施 Poseiswap,公布空投规则

鳄鱼视界

在GitHub首页3分钟被下架!爱奇艺《高并发网关设计》笔记被盗?

Java 负载均衡 高并发 网关设计

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