由于是对技术的个人评判,欢迎理性讨论。

我曾经也当过纯函数式的脑残粉,认为宇宙第一棒的代数数据结构用来处理错误,是无上的优雅和绝对的安全。一个看似人畜无害的接口抛出异常带来的崩溃,是各类疑难杂症的罪魁祸首。综合起来,sum 类型相比 exception 的优势有:

  • 不可变数据结构,purity is dignity;
  • 编译检查,必须处理,懒鬼退散,nobody can avoid it(异常经常不被妥善处理,直至发酵)
  • 可以写在接口声明中,并且严格规定了能产生的错误类型,anyone can hear the lion in the box(几乎所有使用异常机制的语言都不要求标注异常,且一个函数可能抛出任何预料之外类型的异常);
  • 恢复错误时没有exception那么大的开销。可以用清晰简洁的方法处理结果(unwrap, expect 等临时方法,以及 and, or 等 combinator),并且能清楚地区分对待严重故障(panic)和预料之内的小问题(相对而言,捕捉异常要写大堆 try ... catch 方块,并且 exception 和 panic 一样直接崩溃)

然而我越去实际使用这两种方法,越发觉得 exception 要远好于 sum type。原因如下:

  • 不强制要求处理异常,不是异常机制的问题,而是编译器设计的问题。编译器可以要求抛出异常的函数标注其抛出的异常类型,且调用这种函数的函数如果不处理这些异常,也必须标注这些异常的类型;
  • 如果强制处理异常,程序员就会抱怨 try ... catch 太多太烦。但这个麻烦也完全是设计问题。学习 Rust,同样可以设计?运算符,用例如 foo()?,在 foo() 未抛出异常时直接使用结果,否则抛出异常给上层。unwrap, except之类的操作符也很容易实现,combinator 也不在话下;
  • 代码量大了,到处都是 Result<T, E> 很让人崩溃。很多很简单的功能因为要适应其内部调用的函数或外部调用它的函数,也不得不给返回类型加上 Result。虽然为了安全,这是必要的开销,但是这里面暗藏两个问题:
    • 第1个,多写那么多 Result,并不能在错误出现时让我获得更多。层层传递的 Result不会自动保存调用链条,无法像 exception 那样从最深处 propogate(浮现?),保存直达病灶的堆栈信息。所以到需要输出问题的时候,Result 只有一行,exception 有几百行(或许编译器也可以给Result做优化);
    • 第2个,Result 的 err type 实际上自缚手脚,把函数里可能产生的错误类型勒死在 err type 上。有时函数可能产生多种错误,却非要用一个单独的错误来统一,而这个单独错误还得起一个面面俱到的名字,最后名字变得极为抽象,不明所以,最后就统一一种了事(虽然经常是工程设计问题,但很多时候身不由己)。相比而言,抛出何种 exception 完全由各个功能模块自己决定,不需要相互约束。IO 产生的错误到外面还是 IOError,没有转换的开销。
  • 最后一点,用了 Result,函数签名不再纯净,我的工厂生产的啤酒不再是啤酒,而是Result<啤酒, 生产错误>,做出来的菜不再是菜,而是 Result<菜, 失败料理>再也不能挥挥洒洒写逻辑,思考也受处理 Result 阻碍。

所以我的提法是,不要抛弃异常。甚至,在有方便的异常处理操作符,且编译器严格要求程序去处理之时,可以完全不需要 Result。作为例子,看以下 Rust 代码,它是一个语法分析程序:

enum Token {/* 定义词法分析的结果:token */
}enum Expr {/* 定义语法分析的结果:表达式 */
}/* 包含多种错误,但严格来说 EOF 并不算词法错误 */
enum LexError {EOF,UnexpectedEOF,Lexeme(String),
}/* 从源码获取下一个 token */
fn next_token(source: &str) -> Result<Token, LexError> { /* ... */ }/* 不得不将 LexError 整合进来 */
enum ParseError {EOF,UnexpectedEOF,Syntax(String),
}/* 从源码解析一个表达式,看起来还挺优雅,但有转换开销 */
fn parse(source: &str) -> Result<Expr, ParseError> {/* ... */match next_token(source) {Ok(token) => /* ... */,Err(LexError::EOF) => Err(ParseError::EOF),Err(LexError::UnexpectedEOF) => Err(ParseError::UnexpectedEOF),Err(LexError::Lexeme(msg)) => Err(ParseError::Syntax(msg)),}
}/* 事情刚开始麻烦起来 */
enum ParseFileError {Syntax(ParseError),IO(IOError),
}/* 从源文件路径读取源码并解析成表达式,混入了 io::Error,可以看到判断逻辑已十分复杂,很多时候程序员都直接 unwrap 了事 */
fn parseFile(path: &str) -> Result<Expr, ParseFileError> {let mut file = std::fs::File::open(path);if let Err(ioError) = file {return ParseFileError::IO(ioError);}let mut source = String::new();if let Err(ioError) = file.read_to_string(&mut contents) {return ParseFileError::IO(ioError);}let expr = parse(&source[..]);if let Err(parseError) = expr {return ParseFileError::Syntax(parseError);}return expr.unwrap();
}

再来看有严格的 exception 会怎样:

enum Token {/* 定义词法分析的结果:token */
}enum Expr {/* 定义语法分析的结果:表达式 */
}/* 可以任意定义多种异常 */
#[derive(Exception)]
struct EOF;#[derive(Exception)]
struct UnexpectedEOF;#[derive(Exception)]
struct LexError(String);#[derive(Exception)]
struct SyntaxError(String);/* 返回类型变为单纯的 Token,加上异常标注 */
fn next_token(source: &str) -> Tokenthrows EOF, UnexpectedEOF, LexError { /* ... */ }/* 返回类型变为单纯的 Expr,并且只需处理有必要处理的 next_token 抛出的异常 */
fn parse(source: &str) -> Exprthrows EOF, UnexpectedEOF, SyntaxError {/* ... */let token = next_token(source) except {LexError(msg) => throw SyntaxError(msg),_ => throw _,}
}/* io::Error 除了要标注,可以完全不用管,清爽很多。再见了,unwrap! */
fn parseFile(path: &str) -> Exprthrows EOF, UnexpectedEOF, SyntaxError, io::Error {let mut file = std::fs::File::open(path)?;let mut source = String::new();file.read_to_string(&mut contents)?;let expr = parse(&source[..])?;return expr;
}

意义不言自明。

更新,感谢评论区 lanus 大佬(不知道为什么@不到)的启发,补充两点。

  1. 足够强大的编译器可以推断 Exception,而不需要手动用 throws 标记。相反,用 nothrow 标记不希望抛异常的函数,在里面编译器强制要求捕获并处理所有异常。
  2. Result 即便要转换,但开销还是少于恢复 exception。我想了一下,解决方向有两种,一种是仍然不要 Result,设法降低 exception 开销;另一种是加入 Result,但不用标注,由编译器推断,保留两种开销不同的机制。后者看起来更完美,但编译器要暗戳戳地改变返回类型。并且必须有匿名枚举,就像 TypeScript 的那种纯粹的 sum type。

AD19 add pins to nets错误_为什么我认为Rust的Result错误处理方式不如Exception相关推荐

  1. AD19 add pins to nets错误_《英雄联盟手游》错误代码问题大全 LOL的错误代码都是什么意思...

    英雄联盟手游上线引起广泛的关注,但是有些玩家在进入游戏的时候出现了代码报错的问题,那么针对这些不同的错误代码要如何解决呢? 100036 请求超时,网络不好或者加速器速度不够,换个好点的网络或者加速器 ...

  2. AD19 add pins to nets错误_NGINX 502 Bad Gateway错误疑难解答

    502 Bad Gateway错误非常常见,此错误代码的确切原因取决于您的特定堆栈. 由于前端Web服务器和后端应用程序处理程序之间的通信中断,通常会出现错误的网关错误.大多数情况下,潜在原因可归因于 ...

  3. python一直报缩进错误_如何避免Python中的缩进错误

    Python是当今编程界领先和新兴的编程平台之一.凭借其丰富的功能和巨大的灵活性,人们可以在这个平台上实现很多,只要他们知道如何操作它.在Python中的这个缩进错误中,我们将介 Python是当今编 ...

  4. mysql 编码错误_【分享】MySQl操作系统提示错误编码

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 在 Unix 系统中,使用 perror 程序来显示操作系统错误编码的含义,它包含在 MySQL 的分发中. 下面的列表显示常见的 Linux 系统错误代 ...

  5. access里的多步oledb错误_多步 OLE DB 操作产生错误,这问题怎么解决啊

    相信大家在调试程序时曾碰到过下面错误 数据库:ACCESS ------------------------------------------------------------------- Mi ...

  6. java case 的错误_关于java:switch case语句错误:case表达式必须是常量表达式

    我的switch-case语句昨天运行得很好. 但是当我今天早些时候运行代码时,eclipse给了我一个错误,用红色突出显示case语句并说:case表达式必须是常量表达式,它是常量我不知道发生了什么 ...

  7. syntaxerror是什么错误_【第1643期】自定义错误及扩展错误

    前言 [第1641期]异常处理,"try..catch"的续集来了.今日早读文章由@LeviDing授权分享. 正文从这开始-- 当我们在进行开发的时候,通常需要属于我们自己的错误 ...

  8. GNS3 cloud 连接错误_远程桌面连接服务器身份验证错误要求的函数不受支持

    问题描述: 在远程桌面连接服务器的时候,出现以下错误 发生身份验证错误.要求的函数不受支持 而有的客户端连接到服务器没有这个问题,就是这个台客户端连接出现问题 解决方法: 一.在远程桌面客户端修改 开 ...

  9. python库文档的错误_自己编程中遇到的Python错误和解决方法汇总整理

    开个贴,用于记录平时经常碰到的Python的错误同时对导致错误的原因进行分析,并持续更新,方便以后查询,学习. 知识在于积累嘛!微笑 +++++++++++++++++++++++++++++++++ ...

最新文章

  1. TurboMail邮件服务器围绕用户需求 建自主创新型企业
  2. [原创]修改oracle 数据库默认时间格式
  3. php与nginx整合
  4. thinking-in-java(13) String字符串
  5. Mac下matplotlib中文显示
  6. java中标准输入输出流
  7. jquery选择器之过滤选择器
  8. python反向查找字符_序列化使用(正向和反向查找)
  9. DBC连接数据库经验技巧
  10. IntelliJ IDEA插件-翻译插件
  11. 燕大学子知网使用手册
  12. opengl_纹理过滤
  13. win10重置失败,重装系统踩坑
  14. 利用PPt画卷积神经网络
  15. air.tv.douyu.android,斗鱼(air.tv.douyu.android) - 7.0.6.1 - 应用 - 酷安
  16. 29(将数字字符串转换成数字)
  17. Android wifi开发介绍
  18. 差分放大电路在信号传输的作用及设计原理
  19. RH850从0搭建Autosar开发环境【3】- Davinci Configurator之MCU模块配置详解
  20. scrapy实例 ----- 爬取小说

热门文章

  1. cpython教程_python高性能扩展工具-cython教程1快速入门
  2. axure 画小程序效果图_APP详情页如何用Axure画出来
  3. java把对象转成图片格式转换器安卓版,java 万能图片格式转换
  4. 电子工程可以报考二建_非工程类专业也能报考二建吗?
  5. android条形图,MPAndroid组条形图未显示
  6. suse 安装oracle11,Suse11安装Oracle11gR2
  7. unity 纹理压缩格式‘_纹理优化:让你的纹理也“瘦”下来
  8. 交流信号叠加直流偏置_放大器偏置电流Ib需要完整的直流回路
  9. python显示数据长度_python – 获取CSV的长度以显示进度
  10. 『收藏向 期末SSM课设救急』 教你从搭建到测试运行手撸一个SSM项目实战,附带源码,前端页面、解析和一般遇到的问题(排雷)