错误处理(Error-Handling):为何、何时、如何(rev#2)

错误处理是为了构建健壮系统,以应对现实世界中可能出现的各种意外情况。何时处理错误?当遇到不应发生的情况,如文件缺失、配置错误等,应当进行错误处理。错误分为可恢复和不可恢复,前者可以通过异常或status-code处理,后者可能导致程序退出或转入错误恢复子过程。异常相比于错误代码在错误传播、携带信息和资源管理上有优势。C++中,RAII(Resource Acquisition Is Initialization)是保证错误安全的重要手段,通过智能指针和容器确保资源在异常发生时也能正确释放。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

错误处理(Error-Handling):为何、何时、如何(rev#2)

 

By 刘未鹏(pongba)

C++的罗浮宫(http://blog.youkuaiyun.com/pongba)

TopLanguage(http://groups.google.com/group/pongba)

 

引言

错误处理(Error-Handling)这个重要议题从1997年(也许更早)到2004年左右一直是一个被广泛争论的话题,曾在新闻组上、博客上、论坛上引发口水无数(不亚于语言之争),Bjarne StroustrupJames GoslingAnders HejlsbergBruce EckelJoel SpolskyHerb SutterAndrei AlexandrescuBrad AbramsRaymond ChenDavid Abrahams…,各路神仙纷纷出动,好不热闹:-)

 

如今争论虽然已经基本结束并结果;只不过结论散落在大量文献当中,且新旧文献陈杂,如果随便翻看其中的几篇乃至几十篇的话都难免管中窥豹。就连Gosling本人写的The Java Programming Language中也语焉不详。所以,写这篇文章的目的便是要对这个问题提供一个整体视图,相信我,这是个有趣的话题:-)

 

为什么要错误处理?

这是关于错误处理的问题里面最简单的一个。答案也很简单:现实世界是不完美的,意料之外的事情时有发生。

 

一个现实项目不像在学校里面完成大作业,只要考虑该完成的功能,走happy path(也叫One True Path)即可,忽略任何可能出错的因素(呃.. 你会说,怎么会出错呢?配置文件肯定在那,矩阵文件里面肯定含的是有效数字.. 因为所有的环境因素都在你的控制之下。就算出现什么不测,比如运行到一半被人拔了网线,那就让程序崩溃好了,再双击一下不就行了嘛)。

 

然而现实世界的软件就必须考虑错误处理了。如果一个错误是能够恢复的,要尽量恢复。如果是不能恢复的,要妥善的退出模块,保护用户数据,清理资源。如果有必要的话应该记录日志,或重启模块等等。

 

简而言之,错误处理的最主要目的是为了构造健壮系统。

 

什么时候做错误处理?(或者:什么是“错误”?)

错误,很简单,就是不正确的事情。也就是不该发生的事情。有一个很好的办法可以找出哪些情况是错误。首先就当自己是在一个完美环境下编程的,一切precondition都满足:文件存在那里,文件没被破坏,网络永远完好,数据包永远完整,程序员永远不会拿脑袋去打苍蝇,等等这种情况下编写的程序也被称为happy path(或One True Path)。

 

剩下的所有情况都可以看作是错误。即“不应该”发生的情况,不在算计之内的情况,或者预料之外的情况,whatever

 

简而言之,什么错误呢调用方违反被调用函数的precondition、或一个函数无法维持其理应维持的invariants、或一个函数无法满足它所调用的其它函数的precondition、或一个函数无法保证其退出时的postcondition;以上所有情况都属于错误。(详见C++ Coding Standards: 101 Rules, Guidelines, and Best Practices70章,或Object Oriented Software Construction, 2nd edition1112章)

 

例如文件找不到(通常意味着一个错误)、配置文件语法错误、将一个值赋给一个总应该是正值的变量、文件存在但由于访问限制而不能打开,或打开不能写、网络传输错误、网络中断、数据库连接错误、参数无效等。

 

不过话说回来,现实世界中,错误与非错误之间的界定其实是很模糊的。例如文件缺失,可能大多数情况下都意味着一个错误(影响程序正常执行并得出正常结果),然而有的情况下也可能根本就不是错误(或者至少是可恢复的错误),比如你的街机模拟器的配置文件缺失了,一般程序只要创建一个缺省的即可。

 

因此,关于把哪些情况界定为错误,具体的答案几乎总是视具体情况而定的。但话虽如此,仍然还是有一些一般性的原则,如下:

 

哪些情况不属于错误?

1.      控制程序流的返回值不是错误:如果一个情况经常发生,往往意味着它是用来控制程序流程的,应该用status-code返回(注意,不同于error-code),比如经典的while(cin >> i)。读入整型失败是很常见的情况,而且,这里的“读入整型失败”其实真正的含义是“流的下一个字段不是整型”,后者很明确地不代表一个错误;再比如在一个字符串中寻找一个子串,如果找不到该子串,也不算错误。这类控制程序流的返回值都有一个共同的特点,即我们都一定会利用它们的返回值来编写if-else,循环等控制结构,如:

  if(foo(…)) { … }
  else { … }



  while(foo(…)) { … }

这里再摘两个相应的具体例子,一个来自Gosling的《The Java Programming Language》,是关于stream的。

使用status-code

  while ((token = stream.next()) != Stream.END)
      process(token);
  stream.close();

使用exception

  try {
    for(;;) {
      process(stream.next());
    }
  } catch (StreamEndException e) {
    stream.close();
  }

高下立判。

另一个例子来自TC++PLWell, not exactly):

  size_t index;
  try {
    index = find(str, sub_str);
    … // case 1
  } catch (ElementNotFoundException& e) {
    … // case 2
  }

使用status-code

  int index = find(str, sub_str)
  if(index != -1) {
    … // case 1
  } else {
    … // case 2
  }

以上这类情况的特点是,返回值本身也是程序主逻辑(happy path)的一部分,返回的两种或几种可能性,都是完全正常的、预料之中的。

1’ .
另一方面,还有一种情况与此有微妙的区别,即“可恢复错误”。可恢复错误与上面的情况的区别在于它虽说也是预料之中的,但它一旦发生程序往往就会转入一个错误恢复子过程,后者会尽可能恢复程序主干执行所需要的某些条件,恢复成功程序则再次转入主干执行,而一旦恢复失败的话就真的成了一个货真价实的让人只能干瞪眼的错误了。比如C++里面的operator new如果失败的话会尝试调用一个可自定义的错误恢复子过程,当然,后者并非总能成功将程序恢复过来。除了转入一个错误恢复子过程之外,另一种可能性是程序会degenerate入一条second-class的支流,后者也许能完成某些预期的功能,但却是“不完美”地完成的。

这类错误如何处理后面会讨论。

2.      编程bug不是错误。属于同一个人维护的代码,或者同一个小组维护的代码,如果里面出现bug,使得一个函数的precondition得不到满足,那么不应该视为错误。而应该用assert来对付。因为编程bug发生时,你不会希望栈回滚,而是希望程序在assert失败点上直接中断,调用调试程序,查看中断点的程序状态,从而解决代码中的bug

关于这一点,需要尤其注意的是,它的前提是:必须要是同一个人或小组维护的代码。同一个小组内可以保证查看到源代码,进行debug。如果调用方和被调用方不属于同一负责人,则不能满足precondition的话就应该抛出异常。总之记住一个精神:assert是用来辅助debug的(assert的另一个作用是文档,描述程序在特定点上的状态,即便assert被关闭,这个描述功能也依然很重要)。

注意,有时候,为了效率原因,也会在第三方库里面使用assert而不用异常来报告违反precondition。比如strcpystd::vectoroperator[]

3.      频繁出现的不是错误。频繁出现的情况有两种可能,一是你的程序问题大了(不然怎么总是出错呢?)。二是出现的根本不是错误,而是属于程序的正常流程。后者应该改用status-code

 

插曲:异常(exceptionvs错误代码(error-code

异常相较于错误代码的优势太多了,以下是一个(不完全)列表。

 

Traceback (most recent call last): File "C:\Program Files\Python310\lib\site-packages\moviepy\video\io\ffmpeg_reader.py", line 286, in ffmpeg_parse_infos match = re.findall("([0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9])", line)[0] IndexError: list index out of range During handling of the above exception, another exception occurred: Traceback (most recent call last): File "C:\Users\Administrator\PycharmProjects\pythonProject\bilibili视频提取.py", line 16, in <module> audio = AudioFileClip('美女1.mp3').subclip(1, 27) File "C:\Program Files\Python310\lib\site-packages\moviepy\audio\io\AudioFileClip.py", line 70, in __init__ self.reader = FFMPEG_AudioReader(filename, fps=fps, nbytes=nbytes, File "C:\Program Files\Python310\lib\site-packages\moviepy\audio\io\readers.py", line 51, in __init__ infos = ffmpeg_parse_infos(filename) File "C:\Program Files\Python310\lib\site-packages\moviepy\video\io\ffmpeg_reader.py", line 289, in ffmpeg_parse_infos raise IOError(("MoviePy error: failed to read the duration of file %s.\n" OSError: MoviePy error: failed to read the duration of file 美女1.mp3. Here are the file infos returned by ffmpeg: ffmpeg version 7.1-essentials_build-www.gyan.dev Copyright (c) 2000-2024 the FFmpeg developers built with gcc 14.2.0 (Rev1, Built by MSYS2 project) configuration: --enable-gpl --enable-version3 --enable-static --disable-w32threads --disable-autodetect --enable-fontconfig --enable-iconv --enable-gnutls --enable-libxml2 --enable-gmp --enable-bzlib --enable-lzma --enable-zlib --enable-libsrt --enable-libssh --enable-libzmq --enable-avisynth --enable-sdl2 --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxvid --enable-libaom --enable-libopenjpeg --enable-libvpx --enable-mediafoundation --enable-libass --enable-libfreetype --enable-libfribidi --enable-libharfbuzz --enable-libvidstab --enable-libvmaf --enable-libzimg --enable-amf --enable-cuda-llvm --enable-cuvid --enable-dxva2 --enable-d3d11va --enable-d3d12
03-13
wjs@wjs-desktop:~/Drone_Slam$ ros2 launch fishbot_grapher test_grapher_3.launch.py [INFO] [launch]: All log files can be found below /home/wjs/.ros/log/2025-07-27-22-33-04-888587-wjs-desktop-3334 [INFO] [launch]: Default logging verbosity is set to INFO pkg_share =/home/wjs/Drone_Slam/install/fishbot_grapher/share/fishbot_grapher<== model_path =/home/wjs/Drone_Slam/install/fishbot_grapher/share/fishbot_grapher/urdf/fishbot_base.urdf<== [INFO] [sllidar_node-1]: process started with pid [3335] [INFO] [wit_ros2_imu-2]: process started with pid [3337] [INFO] [test01-3]: process started with pid [3339] [INFO] [robot_state_publisher-4]: process started with pid [3341] [INFO] [cartographer_node-5]: process started with pid [3343] [INFO] [cartographer_occupancy_grid_node-6]: process started with pid [3346] [robot_state_publisher-4] [WARN] [1753626785.292549553] [robot_state_publisher]: No robot_description parameter, but command-line argument available. Assuming argument is name of URDF file. This backwards compatibility fallback will be removed in the future. [robot_state_publisher-4] [INFO] [1753626785.326000258] [robot_state_publisher]: got segment base_link [robot_state_publisher-4] [INFO] [1753626785.338680611] [robot_state_publisher]: got segment imu_link [robot_state_publisher-4] [INFO] [1753626785.342302312] [robot_state_publisher]: got segment laser_link [sllidar_node-1] [INFO] [1753626785.398643897] [sllidar_node]: SLLidar running on ROS2 package SLLidar.ROS2 SDK Version:1.0.1, SLLIDAR SDK Version:2.0.0 [sllidar_node-1] [INFO] [1753626785.454849740] [sllidar_node]: SLLidar S/N: 7885EC95C1EA9ED1B2E49CF4FB594571 [sllidar_node-1] [INFO] [1753626785.455013574] [sllidar_node]: Firmware Ver: 1.01 [sllidar_node-1] [INFO] [1753626785.455063630] [sllidar_node]: Hardware Rev: 18 [sllidar_node-1] [INFO] [1753626785.507953718] [sllidar_node]: SLLidar health status : 0 [sllidar_node-1] [INFO] [1753626785.508177608] [sllidar_node]: SLLidar health status : OK. [cartographer_node-5] [INFO] [1753626785.633687427] [cartographer logger]: I0727 22:33:05.000000 3343 configuration_file_resolver.cc:41] Found '/home/wjs/Drone_Slam/install/fishbot_grapher/share/fishbot_grapher/config/test02.lua' for 'test02.lua'. [cartographer_node-5] [INFO] [1753626785.635179989] [cartographer logger]: I0727 22:33:05.000000 3343 configuration_file_resolver.cc:41] Found '/opt/ros/humble/share/cartographer/configuration_files/map_builder.lua' for 'map_builder.lua'. [cartographer_node-5] [INFO] [1753626785.636018011] [cartographer logger]: I0727 22:33:05.000000 3343 configuration_file_resolver.cc:41] Found '/opt/ros/humble/share/cartographer/configuration_files/map_builder.lua' for 'map_builder.lua'. [cartographer_node-5] [INFO] [1753626785.636677940] [cartographer logger]: I0727 22:33:05.000000 3343 configuration_file_resolver.cc:41] Found '/opt/ros/humble/share/cartographer/configuration_files/pose_graph.lua' for 'pose_graph.lua'. [cartographer_node-5] [INFO] [1753626785.637022719] [cartographer logger]: I0727 22:33:05.000000 3343 configuration_file_resolver.cc:41] Found '/opt/ros/humble/share/cartographer/configuration_files/pose_graph.lua' for 'pose_graph.lua'. [cartographer_node-5] [INFO] [1753626785.637744815] [cartographer logger]: I0727 22:33:05.000000 3343 configuration_file_resolver.cc:41] Found '/opt/ros/humble/share/cartographer/configuration_files/trajectory_builder.lua' for 'trajectory_builder.lua'. [cartographer_node-5] [INFO] [1753626785.637998224] [cartographer logger]: I0727 22:33:05.000000 3343 configuration_file_resolver.cc:41] Found '/opt/ros/humble/share/cartographer/configuration_files/trajectory_builder.lua' for 'trajectory_builder.lua'. [cartographer_node-5] [INFO] [1753626785.638368374] [cartographer logger]: I0727 22:33:05.000000 3343 configuration_file_resolver.cc:41] Found '/opt/ros/humble/share/cartographer/configuration_files/trajectory_builder_2d.lua' for 'trajectory_builder_2d.lua'. [cartographer_node-5] [INFO] [1753626785.638664079] [cartographer logger]: I0727 22:33:05.000000 3343 configuration_file_resolver.cc:41] Found '/opt/ros/humble/share/cartographer/configuration_files/trajectory_builder_2d.lua' for 'trajectory_builder_2d.lua'. [cartographer_node-5] [INFO] [1753626785.639450397] [cartographer logger]: I0727 22:33:05.000000 3343 configuration_file_resolver.cc:41] Found '/opt/ros/humble/share/cartographer/configuration_files/trajectory_builder_3d.lua' for 'trajectory_builder_3d.lua'. [cartographer_node-5] [INFO] [1753626785.639716324] [cartographer logger]: I0727 22:33:05.000000 3343 configuration_file_resolver.cc:41] Found '/opt/ros/humble/share/cartographer/configuration_files/trajectory_builder_3d.lua' for 'trajectory_builder_3d.lua'. [cartographer_node-5] [INFO] [1753626785.715124123] [cartographer logger]: I0727 22:33:05.000000 3343 map_builder_bridge.cpp:136] Added trajectory with ID '0'. [sllidar_node-1] [INFO] [1753626785.720324961] [sllidar_node]: current scan mode: DenseBoost, sample rate: 32 Khz, max_distance: 18.0 m, scan frequency:10.0 Hz, [test01-3] Traceback (most recent call last): [test01-3] File "/home/wjs/.local/lib/python3.10/site-packages/serial/serialposix.py", line 322, in open [test01-3] self.fd = os.open(self.portstr, os.O_RDWR | os.O_NOCTTY | os.O_NONBLOCK) [test01-3] FileNotFoundError: [Errno 2] No such file or directory: '/dev/mcu_usb' [test01-3] [test01-3] During handling of the above exception, another exception occurred: [test01-3] [test01-3] Traceback (most recent call last): [test01-3] File "/home/wjs/Drone_Slam/install/fishbot_grapher/lib/fishbot_grapher/test01", line 33, in <module> [test01-3] sys.exit(load_entry_point('fishbot-grapher==0.0.0', 'console_scripts', 'test01')()) [test01-3] File "/home/wjs/Drone_Slam/build/fishbot_grapher/fishbot_grapher/test01.py", line 101, in main [test01-3] tf_subscriber = TFSubscriber() [test01-3] File "/home/wjs/Drone_Slam/build/fishbot_grapher/fishbot_grapher/test01.py", line 20, in __init__ [test01-3] self.ser = serial.Serial("/dev/mcu_usb", 115200) [test01-3] File "/home/wjs/.local/lib/python3.10/site-packages/serial/serialutil.py", line 244, in __init__ [test01-3] self.open() [test01-3] File "/home/wjs/.local/lib/python3.10/site-packages/serial/serialposix.py", line 325, in open [test01-3] raise SerialException(msg.errno, "could not open port {}: {}".format(self._port, msg)) [test01-3] serial.serialutil.SerialException: [Errno 2] could not open port /dev/mcu_usb: [Errno 2] No such file or directory: '/dev/mcu_usb' [wit_ros2_imu-2] [INFO] [1753626787.245410580] [imu]: Serial port opening failure [ERROR] [test01-3]: process has died [pid 3339, exit code 1, cmd '/home/wjs/Drone_Slam/install/fishbot_grapher/lib/fishbot_grapher/test01 --ros-args'].
最新发布
07-28
评论 22
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值