uframer(焦冶)的专栏

目前,这里的一切都是关于D语言的。

D语言中的错误处理

D中的错误处理

所有的程序都要应付错误。错误是不在程序正常操作范围内的异常情况:

  • 内存耗尽
  • 磁盘空间耗尽
  • 文件名无效
  • 试图写只读文件
  • 试图读不存在的文件
  • 请求不支持的系统服务

错误处理问题

C 语言检测报告错误的传统方法并没形成传统,每个函数都有自己的方法,包括:

  • 返回 NULL 指针。
  • 返回 0 值。
  • 返回非零的错误代码。
  • 需要检查 errno(译注:错误代码,鉴于此缩写已约定俗成,以下不译)。
  • 如果上述检查失败了,需要调用一个函数。

为了处理可能出现的错误,必须为每个函数添加繁琐的错误处理代码。如果发生了错误,必须有错误恢复代码,并且需要有代码将错误以某种友好的方式告诉给用户。如果不能在发生错误的局部环境处理错误,就必须显式地将错误传播给它的调用者。如果要显示错误类型,需要将一大堆 errno 值转换为合适的文字。这些代码可能占用整个项目编码时间的一大部分,而且如果运行时系统加入了一个新的 errno 值,旧有的代码就不能显示有意义的信息。

功能良好的错误处理代码很容易将清晰而简洁的实现搞得一团糟。

更糟的是,功能良好的错误处理代码本身就很容易出错,还总是整个项目中测试最不充分的部分(因此充满错误),并且还总是被简单地省略。最终的结果就如同“蓝屏死机”那样,程序由于无法处理一些未预料到的错误而失败。

并不值得为 Quick&Dirty 程序编写错误处理代码,因此使用这类程序就好像使用没有锯罩的多功能台式电锯。

我们所需要的是如下的错误处理哲学和方法学:

  • 标准化——如果用法一致,它就更有用。
  • 就算程序员无法查出出现了什么错误,也要使程序以合适的方式终止。
  • 在无需改动旧代码的前提下就可以加入新的错误类型,从而重用旧的代码。
  • 不会由于粗心大意而忽略某些错误。
  • 使 Quick&Dirty 程序能够正确地处理错误。
  • 可以很容易地写出清晰的错误处理代码。

D 的错误处理解决方案

先让我们观察一下并对错误作出以下假设:

  • 错误不属于正常的程序流。错误是异常的、不常见的、不该出现的。
  • 因为错误不常见,所以错误处理代码的性能并不十分重要。
  • 程序的正常逻辑流的性能很关键。
  • 所有的错误必须采用统一的方式处理,不论是显式地编写代码处理它们还是采用系统默认的处理方式。
  • 相比于负责从错误中恢复的代码,负责检测错误的代码拥有更多关于错误的信息。

解决方案是使用异常处理来报告错误。所有的错误都是从抽象类 Error 派生的对象。Error 类有一个叫做 toString() 的纯虚函数,它返回一个 char[] 类型的人类可读的错误描述。

如果代码检测到了一个错误,如“内存耗尽”,则抛出 Error 对象,其中包含消息“内存耗尽”。函数调用堆栈会被展开,并查找 Error 的处理程序。随着堆栈的展开,相应的 finally 块会被执行。如果找到了错误处理程序,就在该处恢复程序的执行。否则,会运行默认的错误处理程序,该程序会显示错误消息并终止程序。

这个解决方案是否能满足我们的要求?

标准化——如果用法一致,它就更有用。
这正是 D 的方法,D 运行时库和示例都采用这种方式。
就算程序员无法查出出现了什么错误,也要使程序以合适的方式终止。
如果没有相应的错误处理程序,程序会执行默认的错误处理程序,该处理程序打印出适当的消息,并使程序优雅的推出。
在无需改动旧代码的前提下就可以加入新的错误类型,从而重用旧的代码。
旧的代码可以决定是捕获所有的错误,或者是只捕获特定的错误并向上传播剩余的错误。无论那种情况,都不必为每个错误代码关联一个消息了,编译器会自动为其提供正确的消息。
不会由于粗心大意而忽略某些错误。
异常会采用这种或那种方式处理。不会出现使用 NULL 指针表示出错,但后面的代码却试图使用该 NULL 指针的情况。
使 Quick&Dirty 程序能够正确地处理错误。
Quick&Dirty 代码根本不需要包含任何错误处理代码,也不需要检查错误。错误会被默认的处理程序捕获,显示适当的消息,然后程序会优雅的终止。
可以很容易地写出清晰的错误处理代码。
try/catch/finally 语句比那些无穷无尽的 if (出错) goto 处理程序; 语句要清晰得多。.

这个解决方案是否符合我们对于的错误的假设?

错误不属于正常的程序流。错误是异常的、不常见的、不该出现的。
D 的异常处理机制完全符合上述假定。
因为错误不常见,所以错误处理代码的性能并不十分重要。
异常处理堆栈的展开相对较慢。
程序的正常逻辑流的性能很关键。
因为正常的代码流不必检查每个函数调用的返回值是否代表出错,使用异常处理来处理错误确实会使程序运行得更快。
所有的错误必须采用统一的方式处理,不论是显式地编写代码处理它们还是采用系统默认的处理方式。
如果某个错误没有对应的处理程序,就会由运行时库中的默认处理程序处理。如果一个错误被忽略了,那一定是程序员特意添加了代码用于忽略这个错误,因此这一定是在程序员意料之中的。
相比于负责从错误中恢复的代码,负责检测错误的代码拥有更多关于错误的信息。
不需要将错误代码翻译为人类可读的字符串,错误检测代码而不是错误恢复代码负责生成正确的字符串。这种做法也使不同的程序出现相同的错误时会输出相同的消息。
阅读更多
个人分类: D Language
上一篇D语言中的调试、版本控制和静态断言
下一篇D语言中的垃圾收集机制
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭