0

0

异常(exception)和执行失败有什么区别?

php中文网

php中文网

发布时间:2016-06-06 16:11:08

|

1758人浏览过

|

来源于php中文网

原创

例如一个user class 的 add 方法,在成功的情况下返回用户对象实例,在失败的情况返回false并可以通过geterror方法获取失败原因字符串........

说到这里,我好像明白了,难道add方法总是应该返回用户对象,否则抛出异常吗?
但是这样的话,他们的代码量没什么区别的啊。问题在于即使调用add方法处没有捕捉异常,该异常也能进一步向上抛出直至被处理或引发进程崩溃?可是说到底,这和程序自然崩溃有什么区别呢?

---- 以上为自言自语,下面是问题 ----

抛出异常和返回false的区别是什么,两者在什么场景下使用?

摄图AI
摄图AI

摄图网旗下AI视觉创作平台

下载

回复内容:

首先,这是一个好问题。深入理解二者的区别是突破中等程度程序员的标志之一。

先澄清一个误区,那就是异常非常慢。异常的实现需要保存异常抛出点到异常捕获点的必要信息,这是人们诟病异常性能的主要依据。但是这是错误的。第一,手工按照返回错误的风格取得行为等价于异常的实现性能绝大多数时候比不上语言内置的异常的性能。对于支持原生异常的OS,如Windows,这个差距尤其明显;第二,对于非基于栈的调用实现,异常和返回错误性能几无差别,只是风格不同而已。

回到问题,异常和返回错误的本质区别是什么?答:异常是强类型的,类型安全的分支处理技术;而返回错误是其弱化的,不安全的版本。

先看返回错误的特征。一般来说,需要用特别返回值来标识错误,意味着需要重载特定值得含义。假设一个操作会返回一个int型值,因为其不可能返回负数(如求绝对值),我们一般可以使用-1来作为操作失败(如函数库调用加载错误)的指示。-1在int类型中并无特别含义,然而我们这里强制施加了语义,而这种语义是弱的。没办法通过类型系统检查来确保正确性。

一个增强的办法是同时返回两种值。有的语言内置支持,没有内置支持的可以通过定义一个含有分别表征正确返回类型和错误类型的结构来模拟。这样的好处是传递信息的通道被分离了。

无论如何返回错误,它存在着一个显著的缺点:调用方必须显式检查错误值。当错误发生,必须终止当前操作,返回错误的时候,一般需要定义全新的错误类型。这导致操作不可任意简单组合,如模块B调用模块A发生错误的时候需要返回B定义的错误,而模块C调用A错误时则需要返回C定义的错误,而这两类错误往往是不兼容的。组合相关代码必须加入大量的粘连代码,这导致了代码膨胀,而且非常容易出错。对于接口定义明确的系统,一般定义一套通用的错误码来简化其复杂性。OS内核就是一个例子,它提供了一个适配所有OS调用的错误码表。然而其内部使用的错误码要多得多,并且往往在接口把内部错误码映射到外部错误码。

异常是一种安全的分支处理技术。因为它和错误处理密切相关,人们容易直接把异常看成是错误处理,连名字“异常”都带着浓浓的错误的含义。但是这是一个误解。异常的理论基础是假设某些分支处理中,一个分支和其它分支比起来发生的非常不频繁。与其平等地针对常见和极其罕见的情形进行处理(想一下,正常处理代码和错误处理代码往往一样多,大部分情况下后者其实更多),不如仅仅处理正常的情形,把不常见的情形归于一处统一处理。这样我们书写代码的时候仅仅关注正常情形就可以了。发生错误的时候,特别的流程会帮助程序员直接回到定义了错误处理的地方。

这样说有点过分简化。一般情况下,不能直接跳回。因为在异常抛出点到异常处理点之间,可能存在处于非一致状态的值,等待释放的资源等。所以一般情况下,特殊流程要回溯调用栈,确保执行的事务性。但是仅仅这个特殊流程是不够的,必须有合适的配套代码才行。这导致了额外的复杂性。C++引入的RAII概念是一个创举,然而依然没能全部消除程序员的工作。

因为异常是一个具体的类型,一个函数的signature就不仅仅是输入参数列表,输出参数加函数名,还要包含可能抛出异常列表。因为各个模块定义的异常层次结构迥异,这导致了额外的组合困难。然而和返回错误值的组合困难不同,前者导致的是编译时类型错误,后者则是运行时错误。一般来看,错误暴露的越早越好,我们倾向于前者。

理想情况下,一个异常结构完备的系统不会有运行时错误(大概如此,不展开)。然而因为上面提到的原因,在现有的异常支持的情况下,代码同样复杂,冗余,难以维护。

上面就是异常和错误的基本区别。

第二部分预告:有解决上述问题的优雅方法吗?

我的观点是有。但是因为现有能力还不到不借助任何参考直接澄清此问题,我回留待仔细斟酌,沐浴焚香,换到非手机输入的情况下给出,尽请期待。 都是错误处理的策略,OO语言更倾向异常,相比较判断一个函数可能的错误状况来说,“抛出”状态完整的异常对象被认为是更好的封装 ;你说的情况下,错误即false是最简单的情况,这种情况下,异常未必有优势;一般认为用异常可以简化复杂的错误处理代码,并且只要程序不需要终止执行,try catch比if else的代码更可读(错误处理的策略明显,状态可以更丰富,虽然这不是绝对的)。

result = func();
if (result === ERR_NO_1) {
  ... 
} else if (result === ERR_NO_2) {
  
} else {
  ...
}
一个很简单的例子,你要解析json的请求回包,只返回false的代码可能是
if(jsonObj.has("xxx"))
{
    if(jsonObj.has("xxxxx"))
    {
        //......... 
        return true;
    }
}

return false;
写了一些,但是觉得没什么好说的。很多疑惑的问题,见得多了写得多了自然就知道。很多时候没有孰优孰劣,只有风格上的统一。例如C++这样的支持exception的语言,在很多代码库里是完全exception free,而用类C的形式来处理异常。

异常有两类,一类是『我知道这个地方可能出错,调用方必须明确知道出错的风险』,另一类是『这就是个错误,没啥挽救余地了崩溃拉倒』。这两类异常放Java里就是checked exception和unchecked exception的区别。

异常并不仅限于错误处理,很多时候是用来分开处理normal scenario和edge case的。比如说读取一个文件,把文件中每个词出现次数统计一下。这里用Python语法:

word_count = dict()
for word in words:
    try:
        word_count[word] += 1
    except KeyError:
        word_count[word] = 1
抛出异常的函数,就不需要返回bool。
设计成返回bool或指针的函数,就不需要异常。

也就是说异常和返回值是永远不同时存在的(当然这是我个人的理解)。

(我觉得,你问题中的add函数如果有可能失败,那么就不抛异常,如果正常情况下不应该失败,那么可以抛异常。但并不一定需要在每一个add函数的地方都加try。加不加得看你的程序逻辑能否以及应该恢复..(这时你也可以写一个辅助函数在其内部调用try add..其他地方使用这个辅助函数:),继续跑。否则就在程序入口加个try,然后退出即可)

好像还没答全你的这个好问题,明天电脑上再讨论讨论。 能返回Either或者Maybe就方便了, 像Either这样错误信息够丰富的情况下根本不需要异常 从WINDOWS实现上来讲一个分支语句,一个是SEH或者全局回调处理,后者可以很好的防止不再预料的情况时程序奔溃! 第一,异常都是可以预见的,所以它有可预知性。第二,异常不关心发生的条件而关心如何善后。第三,异常是被定义过的错误,比较容易定位问题。第四,异常可以传递。

执行失败的结果就是直接退出。。。。 假设你对某个异常(或者说执行错误)需要这样处理:
——在用户界面显示一条错误信息。

但是这个异常是由底层的API,比如数据库调用抛出的,而客户端没有直接调用这个API的权限。
这时候,你可以选择:
  1. 抛出异常,一层一层抛出
  2. 定义错误代码,一层一层返回
你选2,
  1. 发现每一个函数都增加了一个叫err的传入参数,这个参数并没有什么卵用,因为它会被不加处理地返回给上一层,直达UI。
  2. 这并没有什么问题,但是有一天你被告知某个底层改动导致这个API会抛出一种新的错误,老板要求你对这种错误在调用栈里的某一层处理掉。于是你增加了对异常类型的判断。
  3. 后来,另一个新的模块需要复用你在上次处理的那个module里写的异常信息,你为了不c&p代码,把异常信息封装成了一个Error类,每个从Error继承来的子类都保存着自己独有的异常信息。
  4. 你对自己造的轮子很满意,然后用到了现在维护的模块中——现在你的代码看起来是这样:
    def f(err, **kwargs):
       result = None
       if(err.code == 1):
          handle_this_err(err)
       elif(err.code == 2):
          return (err, result)
       else:
          result = handle_data(**kwargs)
       return (None, result)
    
    跨函数error code。 强制错误检查。 happen path 简化。 最后一个,你得会写异常安全的代码。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
JavaScript浏览器渲染机制与前端性能优化实践
JavaScript浏览器渲染机制与前端性能优化实践

本专题围绕 JavaScript 在浏览器中的执行与渲染机制展开,系统讲解 DOM 构建、CSSOM 解析、重排与重绘原理,以及关键渲染路径优化方法。内容涵盖事件循环机制、异步任务调度、资源加载优化、代码拆分与懒加载等性能优化策略。通过真实前端项目案例,帮助开发者理解浏览器底层工作原理,并掌握提升网页加载速度与交互体验的实用技巧。

1

2026.03.06

Rust内存安全机制与所有权模型深度实践
Rust内存安全机制与所有权模型深度实践

本专题围绕 Rust 语言核心特性展开,深入讲解所有权机制、借用规则、生命周期管理以及智能指针等关键概念。通过系统级开发案例,分析内存安全保障原理与零成本抽象优势,并结合并发场景讲解 Send 与 Sync 特性实现机制。帮助开发者真正理解 Rust 的设计哲学,掌握在高性能与安全性并重场景中的工程实践能力。

21

2026.03.05

PHP高性能API设计与Laravel服务架构实践
PHP高性能API设计与Laravel服务架构实践

本专题围绕 PHP 在现代 Web 后端开发中的高性能实践展开,重点讲解基于 Laravel 框架构建可扩展 API 服务的核心方法。内容涵盖路由与中间件机制、服务容器与依赖注入、接口版本管理、缓存策略设计以及队列异步处理方案。同时结合高并发场景,深入分析性能瓶颈定位与优化思路,帮助开发者构建稳定、高效、易维护的 PHP 后端服务体系。

106

2026.03.04

AI安装教程大全
AI安装教程大全

2026最全AI工具安装教程专题:包含各版本AI绘图、AI视频、智能办公软件的本地化部署手册。全篇零基础友好,附带最新模型下载地址、一键安装脚本及常见报错修复方案。每日更新,收藏这一篇就够了,让AI安装不再报错!

50

2026.03.04

Swift iOS架构设计与MVVM模式实战
Swift iOS架构设计与MVVM模式实战

本专题聚焦 Swift 在 iOS 应用架构设计中的实践,系统讲解 MVVM 模式的核心思想、数据绑定机制、模块拆分策略以及组件化开发方法。内容涵盖网络层封装、状态管理、依赖注入与性能优化技巧。通过完整项目案例,帮助开发者构建结构清晰、可维护性强的 iOS 应用架构体系。

87

2026.03.03

C++高性能网络编程与Reactor模型实践
C++高性能网络编程与Reactor模型实践

本专题围绕 C++ 在高性能网络服务开发中的应用展开,深入讲解 Socket 编程、多路复用机制、Reactor 模型设计原理以及线程池协作策略。内容涵盖 epoll 实现机制、内存管理优化、连接管理策略与高并发场景下的性能调优方法。通过构建高并发网络服务器实战案例,帮助开发者掌握 C++ 在底层系统与网络通信领域的核心技术。

27

2026.03.03

Golang 测试体系与代码质量保障:工程级可靠性建设
Golang 测试体系与代码质量保障:工程级可靠性建设

Go语言测试体系与代码质量保障聚焦于构建工程级可靠性系统。本专题深入解析Go的测试工具链(如go test)、单元测试、集成测试及端到端测试实践,结合代码覆盖率分析、静态代码扫描(如go vet)和动态分析工具,建立全链路质量监控机制。通过自动化测试框架、持续集成(CI)流水线配置及代码审查规范,实现测试用例管理、缺陷追踪与质量门禁控制,确保代码健壮性与可维护性,为高可靠性工程系统提供质量保障。

79

2026.02.28

Golang 工程化架构设计:可维护与可演进系统构建
Golang 工程化架构设计:可维护与可演进系统构建

Go语言工程化架构设计专注于构建高可维护性、可演进的企业级系统。本专题深入探讨Go项目的目录结构设计、模块划分、依赖管理等核心架构原则,涵盖微服务架构、领域驱动设计(DDD)在Go中的实践应用。通过实战案例解析接口抽象、错误处理、配置管理、日志监控等关键工程化技术,帮助开发者掌握构建稳定、可扩展Go应用的最佳实践方法。

61

2026.02.28

Golang 性能分析与运行时机制:构建高性能程序
Golang 性能分析与运行时机制:构建高性能程序

Go语言以其高效的并发模型和优异的性能表现广泛应用于高并发、高性能场景。其运行时机制包括 Goroutine 调度、内存管理、垃圾回收等方面,深入理解这些机制有助于编写更高效稳定的程序。本专题将系统讲解 Golang 的性能分析工具使用、常见性能瓶颈定位及优化策略,并结合实际案例剖析 Go 程序的运行时行为,帮助开发者掌握构建高性能应用的关键技能。

50

2026.02.28

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
深入剖析redis教程
深入剖析redis教程

共55课时 | 8.3万人学习

传智播客redis基础视频教程
传智播客redis基础视频教程

共13课时 | 5.7万人学习

前端系列快速入门课程
前端系列快速入门课程

共4课时 | 0.4万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号