1. 在你经历的项目中,哪些情况被定义为错误,需要汇报的。(注意,这里的错误不包括编程错误(bug);而是指IO错误和文件错误这类。)给个模糊的答案即可:)
2. 你们使用的错误处理机制是error-code呢还是exception?
3. 你们如何确保error-safe的?也就是说,当错误发生的时候,如何确保能够正确clean up的?
4. 你们如何确保所有错误都被妥善(如果能handle的话)handle或者(如果不能handle的话)传播给上级的?
5. 你们的项目中,有多少错误是可以就地handle,有多少错误是就地没有足够上下文,需要传播到上层模块解决的?
汗一个。。。貌似JAVA的强制异常声明是无数大牛痛批的对象吧,掩耳盗铃的典范。
Atry 写道:
感谢Atry的信息!:-)
编程错误的确应该直接崩掉,因为编程错误本就是应当在发布之前解决的,所以"C++ Coding Standard"提倡用assert来对付这类错误。
> 后者即所谓 unhappy path , 往往解决办法都是一目了然的
这个能举个例子么?
> 剩下的偶发错误的解决办法往往是不解决。即使一个模块编写者,比如我,一本正经的把错误传递给上层,上层往往也是抛弃这些偶发错误。
"剩下的偶发错误"其实也是属于unhappy path吧。
> 也是混用, goto 、 try-catch 、 RAII 都用。
try-catch是用来对付异常的抛出端和接收端的。RAII是用来在异常经过路径上保证strong guanrantee的,这些都不算混用。goto用在里面,或者error-code用在里面,就的确是混用了。
就我所知goto和error-code都是不被提倡的。见"C++ Coding Standard"的相应章节。Atry这么用想必有什么原因?
> 有一些错误需要异步处理,因此不能用异常,只能用错误码,类似 Boost.Asio 里面的那种错误。
这个,Atry能否稍加解释?:)
例如一个程序的通信部分, 分为底层 socket stream 部分和高层 RPC 部分的话,
非预期的tcp 连接中断, 对高层 RPC 的 call stub 来说, 可以视为是一个错误;
但是对于底层 socket stream 来说, 这只是一个普通的事件.
>
> 2. 你们使用的错误处理机制是error-code呢还是exception?
1. 接近 OS层的代码, 更多的是用 error-code, 接近业务概念的代码, 用
exception 较多. 例如, 对于底层io封装代码而言, 打开一个文件失败, 可以视作
一个常见的情况, 不值得惊奇; 但是对上层而言, 一个配置文件打不开, 就是一件
大事了.
2. 优化的考虑, 从一个数据结构之类删除一个 item, 参数不在数据结构中的处
理, 一般情况应该当作exception; 但是由于应用程序的特殊性, 总调用次数多,
出现的 "错误情形" 的比率也高, 而进行调用的点不多(检查 error -code 带来的
编程负担较小), 可能会作为 error-code. 这个要结合具体应用决定.
3. 库的通用性考虑: 如果作为 error-code 设计, 以后要改成 exception, 只需
要 wrap 一层 inline 函数即可, 反过来 wrap 的话, 性能代价已经付出, 所以写
一些较通用的库, 把不准这个库以后被用的情况下, 所谓"错误"情况的出现频率高
不高的时候, 会用 error-code.
>
> 3. 你们如何确保error-safe的?也就是说,当错误发生的时候,如何确保能够
> 正确clean up的?
主要是RAII, 为此宁愿写很多丑陋的函数 local 类, 强制这个方式之后, 程序员
容易掌握.
>
> 4. 你们如何确保所有错误都被妥善(如果能handle的话)handle或者(如果不
> 能handle的话)传播给上级的?
就是很一般的做法,
1. 每个scope 保证自己的资源不会泄漏, C++ 代码里面用 RAII (C 代码里面用
goto)
2. 对于有 error code 要检查的代码, 一定要检查 error code
3. 自己有一定的异常处理能力, 则 catch 相关的异常, 否则不要去 catch
4. 自己 catch 的异常, 根据是否处理完毕, 决定要不要 rethrow
>
> 5. 你们的项目中,有多少错误是可以就地handle,有多少错误是就地没有足够
> 上下文,需要传播到上层模块解决的?
没有留意这个数据.
我的应用中, 有一部分软件是执行网络包过滤任务的, 用 C/C++ 实现. 这里,
我宁愿程序来检查处理所有的错误, 拿网络帧来说, 从 ip 字段到 udp 字段到
PDU 内容, 每一个字段都需要检查数据是否可用, 有没有恶意伪造引发后续的检查
代码, 数据结构代码出错的可能性.
程序最好是可以连续运行很多年不会死机, 如果userspace 程序的heatbeat
没有被检测到, 进程会被软件 watchdog杀掉重新启动; 如果 kernel 部分程序的
heatbeat 没有被检测到(那么就是 OS or headware 出错), CPU板会被硬件
watchdog reset.
只要有 1ms 程序的网络服务不正常, 整个CPU板就会被网络板隔离, 然后监视
它什么时候恢复. 即使双电源供电都失效了, 网络板还能够保证将进出的网线直接
相连, 系统降级丢失过滤功能, 但是保留基本服务.
对我来说, OS 可能还是目前的做法好 :)
> > >http://groups.google.com/group/pongba- 隐藏被引用文字 -
>
> - 显示引用的文字 -
(译者注:在写一段程序时,如果没有用try-catch 捕捉异常或者显式的抛出异常,而希望程序自动抛出,一些语言的编译器不会允许编译通过,如Java 就是这样。这就是Checked Exceptions 最基本的意思。该特性的目的是保证程序的安全性和健壮性。Zee&Snakey(MVP) 对此有一段很形象的话,可以参见:
http ://www.blogcn.com/user2/zee/main.asp 。Bruce Eckel 也有相关的一篇文章(《Does Java need Checked Exceptions 》),参见:
http ://www.mindview.net/Etc/Discussions/CheckedExceptions )
Bruce Eckel :C# 没有Checked Exceptions ,你是怎么决定是否在C# 中放置这种特性的么?
Anders Hejlsberg :我发现Checked Exceptions 在两个方面有比较大的问题:扩展性和版本控制。我知道你也写了一些关于 Checked Exceptions的东西,并且倾向于我们对这个问题的看法。
Bruce Eckel :我一直认为Checked Exceptions 是非常重要的。
Anders Hejlsberg :是的,老实说,它看起来的确相当重要,这个观点并没有错。我也十分赞许 Checked Exceptions特性的美妙。但它某些方面的实现会带来一些问题。例如,从 Java中Checked Exceptions 的实现途径来看,我认为它在解决一系列既有问题的同时,付出了带来一系列新问题的代价。这样一来,我就搞不清楚 Checked Exceptions特性是否可以真的让我们的生活变得更美妙一些。对此你或许有不同看法。
Bruce Eckel :C#设计小组对 Checked Exceptions特性是否有过大量的争论?
Anders Hejlsberg :不,在这个问题上,我们有着广泛的共识。C# 目前在Checked Exceptions 上是保持缄默的。一旦有公认的更好的解决方案,我们会重新考虑,并在适当的地方采用的。我有一个人生信条,那就是----如果你对该问题不具有发言权,也没办法推进其解决进程,那么最好保持沉默和中立,而不应该摆出一个非此即彼的架势。
假设你让一个新手去编一个日历控件,他们通常会这样想:"哦,我会写出世界上最好的日历控件!我要让它有各种各样的日历外观。它有显示部分,有这个,有那个......"他们将所有这些构想都放到控件中去,然后花两天时间写了一个很蹩脚的日历程序。他们想:"在程序的下一个版本中,我将实现更多更好的功能。"
但是,一旦他们开始考虑如何将脑海中那么多抽象的念头具体实现出来时,就会发现他们原来的设计是完全错误的。现在,他们正蹲在一个角落里痛苦万状呢,他们发现必须将原来的设计全盘抛弃。这种情况我不是看到一次两次了。我是一个最低纲领主义者。对于影响全局的问题,在没有实际解决方案前,千万不要将它带入到整个框架中去,否则你将不知道这个框架在将来会变成什么样子 。
Bruce Eckel:极限编程(The Extreme Programmers)上说:"用最简单的办法来完成工作。"
Anders Hejlsberg:对呀,爱因斯坦也说过:"尽可能简单行事。"对于Checked Excpetions特性,我最关心的是它可能给程序员带来哪些问题。试想一下,当程序员调用一些新编写的有自己特定的异常抛出句法的API时,程序将变得多么纷乱和冗长。这时候你会明白Checked Exceptions不是在帮助程序员,反而是在添麻烦。正确的做法是,API的设计者告诉你如何去处理异常而不是让你自己想破脑袋。
2、Checked Exceptions的版本相关性
Bill Venners :你提到过Checked Exceptions的扩展性和版本相关性这两个问题。现在能具体解释一下它们的意思么?
Anders Hejlsberg :让我首先谈谈版本相关性,这个问题更容易理解。假设我创建了一个方法foo ,并声明它可能抛出A、 B、 C三个异常。在新版的foo 中,我要增加一些功能,由此可能需要抛出异常D 。这将产生了一个极具破坏性的改变,因为原来调用此方法时几乎不可能处理过 D异常。
也就是说,在新版本中增加抛出的异常时,给用户的代码带来了破坏。在接口中使用方法时也有类似的问题。一个实现特定功能的接口一经发布,就是不可改变的,新功能只能在新版的接口中增加。换句话说,就是只能创建新的接口。在新版本中,你只有两种选择,要么建立一个新的方法foo2 ,foo2可以抛出更多的异常,要么在新的 foo中捕获异常 D,并转化为原来的异常A 、B 或者C。
Bill Venners :但即使在没有Checked Exceptions特性的语言中,(增加新的异常)不是同样会对程序造成破坏么?假如新版foo抛出了需要用户处理的新的异常,难道仅仅因为用户不希望这个异常发生,他写代码时就可以置之不理吗?
Anders Hejlsberg :不,因为在很多情况下,用户根本就不关心(异常)。他们不会处理任何异常。其实消息循环中存在一个最终的异常处理者,它会显示一个对话框提示你程序运行出错。程序员在任何地方都可以使用try finally 来保护自己的代码,即使运行时发生了异常,程序依然可以正确运行。对于异常本身的处理,事实上,程序员是不关心的。
很多语言的 throws语法(如 Java),没必要地强迫你去处理异常,也就是逼迫你搞清楚每一个异常的来源。它们要求你要么捕获声明的异常,要么将它们放入 throws语句。程序员为了达到这个要求,做了很多荒谬可笑的事情。例如他们在声明每个方法时,都必须加上修饰语:" throws Exception"。这完全是在搧这个特性的耳光,它不过是要求程序员多作些官样文章,对谁都没有好处。
Bill Venners :如此说来,你认为不要求程序员明确的处理每个异常的做法,在现实中要适用得多了?
Anders Hejlsberg :人们为什么认为(显式的)异常处理非常重要呢?这太可笑了。它根本就不重要。在我印象中,一个写得非常好的程序里,try finally 和try catch语句数目大概是 10: 1。在C# 中,也可以使用和类似try finally 的using语句(来处理异常)。
Bill Venners :finally到底干了些什么?
Anders Hejlsberg :finally保证你不被异常干扰,但它不直接处理异常。异常处理应该放在别的什么地方。实际上,在任何一个事件驱动的(如现代图形界面)程序中,在主消息循环里,都有一个缺省的异常处理过程,程序员只需要处理那些没被缺省处理的异常。但你必须确保任何异常情况下,原来分配的资源都能被销毁。这样一来,你的程序就是可持续运行的。你肯定不希望写程序时,在 100个地方都要处理异常并弹出对话框吧。如果那样的话,你作修改时就要倒大霉了。异常应该集中处理,并在异常来临处保护好你的代码。
3 、Checked Exceptions 的扩展性
Bill Venners:那么 Checked Exceptions的扩展性又是如何呢?
Anders Hejlsberg : 扩展性有时候和版本性是相关的。 在一个小程序里,Checked Exceptions 显得蛮迷人的。你可以捕捉FileNotFoundException 异常并显示出来,是不是很有趣?这在调用单个的API 时也挺美妙的。但是在开发大系统时,灾难就降临了。你计划包含4 、5个子系统,每个子系统抛出 4到 10个异常。但是(实际开发时),你每在系统集成的梯子上爬一级,必须被处理的新异常都将呈指数增长。最后,可能每个子系统需要抛出 40个异常。将两个子系统集成时,你将必须写 80个 throw语句。最后,可能你都无法控制了。
很多时候,Checked Exceptions 都会激怒程序员,于是程序员就想办法绕过这个特性。他要么在到处都是写"throws Exception ",要么----我都不知道自己看到多少回了----写"try, da da da da da( 译者注:意思是飞快的写一段代码), catch curly curly( 译者注:即'{ }' )",然后说:"哦,我会回头来处理这些空的异常处理语句的。"实际上,理所当然的没有任何人会回头干这些事情。这时候, Checked Exceptions 已经造成系统质量的极大下降。
所以,你可能很重视这些问题,但是在我们决定是否将Checked Exceptions 的一些机制放入C#时,却是颇费了一番思量的。当然,知道什么异常可能在程序中抛出还是有相当价值的,有一些工具也可以作这方面的检查。我不认为我们可以建立一套足够严格而严谨的规则(来完成异常检查),因为(异常)还可能是编译器的错误引起的呢。但是我认为可以在(程序)分析工具上下些功夫,检测是否有可疑代码,是否有未捕获的异常,并将这些隐藏的漏洞给你指出来。
因为你说的 3 这种情况,一定是属于某个模块内部的函数,某个模块内部的函数考虑耦合做什么,若应该申明异常就坦白的声明就是了。
而 2 这种情况其实是最常见的。底层关心的异常的细节,未必是上层关心的,包装是必须的。问题在于,往往程序员在接口中偷懒,直接暴露底层异常,这是那个偷懒的程序员的错,而不能谴责语法特性给他偷懒造成了障碍。
嗯,你说的那种 exception-transparent 应该用模板解决,for_each 的实现者可以用function_traits 之类的办法知道应该声明什么异常。
不过用 function_traits 的缺点就是代码编写很繁琐。可以增加一个特性,一个函数模板可以自动声明异常。
类似这样:
template<typename Function>
void foo(Function f) throw(auto) {
f();
}
On 10/1/07, Atry <pop....@gmail.com > wrote:就算traits真能萃取出应该声明的异常,那也是workaround。关键是,我一开始说的,我认为,error-handling本质上是一个cross-layer的东西,只应该让错误发生端和接收端耦合起来,中间的层次应该保持透明。否则一旦发生端有分吹草动,中间的层次就会受到无辜牵连。嗯,你说的那种 exception-transparent 应该用模板解决,for_each 的实现者可以用function_traits 之类的办法知道应该声明什么异常。
我现在关心的倒不是如何让中间的层次自动声明异常列表,而是,我说的这种"底层异常----中间层透明----上层处理"的情况在现实中到底存在得有多普遍?!我的感觉是,中间层出现一两层函数调用的情况是很多见的。多于一层的话,checked exception就掣肘了,因为它逼迫每一个根本只想保持透明的中间层都wrap异常或者声明下面传上来的异常。
On 10月1日, 下午6时05分, Atry <pop.a...@gmail.com> wrote:
> 或者更明确地说,函数声明不可以 throw(auto) ,只有函数定义可以 throw(auto)
>
> 在07-10-1,Atry <pop.a...@gmail.com> 写道:
>
>
>
> > throw(auto) 当然只对模板函数有效,但是一个实例化以后函数抛出的类型是明确的。
>
> > 在07-10-1,pongba <pon...@gmail.com> 写道:
>
2. 需要往上汇报。这就是以前许多文章建议的用checked exception的情况,因为这个exception是可以恢复的,只是不在这一层恢复,而checked exception便可以迫使调用方采取一些手段。但问题是,这层的编写者咋就知道这个异常一定能被紧贴着的上一层调用方解决呢?既然都需要往上汇报了,鬼知道汇报几层才得到解决,后者是调用方的事情,调用方完全可能会在中间插入一两层薄薄的调用的。所以这种情况下checked exception又不恰当。
2. 需要往上汇报。这就是以前许多文章建议的用checked exception的情况,因为这个exception是可以恢复的,只是不在这一层恢复,而checked exception便可以迫使调用方采取一些手段。但问题是,这层的编写者咋就知道这个异常一定能被紧贴着的上一层调用方解决呢?既然都需要往上汇报了,鬼知道汇报几层才得到解决,后者是调用方的事情,调用方完全可能会在中间插入一两层薄薄的调用的。所以这种情况下checked exception又不恰当。
就算不是在紧贴着的上一层解决,上一层增加一个异常声明浪费了你很多行代码吗?
这里有两个问题。
1. 在经过的每一层增加这个异常声明有没有用?
2. 增加这个异常声明会不会很麻烦?
这个约束太严格了,通常绝大多数异常的处理都是集中到一个地方.做一下事务的回滚,写一下log文件,或者显示一下错误消息.只有少数异常是做其他处理
的.因此对于大多数的函数,只是简单的把异常继续往上抛.这个约束会产生很多不必要的异常申明.
On 9月30日, 下午4时42分, pongba <pon...@gmail.com> wrote:
> 1.
> 在你经历的项目中,哪些情况被定义为错误,需要汇报的。(注意,这里的错误不包括编程错误(bug);而是指IO错误和文件错误这类。)给个模糊的答案即可:)
>
> 2. 你们使用的错误处理机制是error-code呢还是exception?
>
> 3. 你们如何确保error-safe的?也就是说,当错误发生的时候,如何确保能够正确clean up的?
>
> 4. 你们如何确保所有错误都被妥善(如果能handle的话)handle或者(如果不能handle的话)传播给上级的?
>
> 5. 你们的项目中,有多少错误是可以就地handle,有多少错误是就地没有足够上下文,需要传播到上层模块解决的?
>
> 感谢大家哈:-)
>
> --
> 刘未鹏(pongba)|C++的罗浮宫http://blog.csdn.net/pongba
> TopLanguagehttp://groups.google.com/group/pongba
因为你说的 3 这种情况,一定是属于某个模块内部的函数,某个模块内部的函数考虑耦合做什么,若应该申明异常就坦白的声明就是了。
而 2 这种情况其实是最常见的。底层关心的异常的细节,未必是上层关心的,包装是必须的。问题在于,往往程序员在接口中偷懒,直接暴露底层异常,这是那个偷懒的程序员的错,而不能谴责语法特性给他偷懒造成了障碍。在07-10-1, Atry <pop....@gmail.com> 写道:你举的例子中,2和3都是值得提倡的。如果分层,就需要包装成另一个上层关心的异常(而不是那个底层异常)。如果不是分层,而是某个模块内部处理代码,那么简单的在这个内部函数中声明会抛出同样的异常就行了。在07-10-1,pongba < pon...@gmail.com> 写道:关于checked exception为什么是邪恶的,我再用一个具体的例子来说明一下:
checked exception一旦出现,只留给立即调用方(immediate caller)三个选择:
1. 立即解决这个exception(当然,不是吞掉,瞎吞异常以后总是要还的)
2. wrap成自己这个抽象层次上的新异常。比如一个SQLException被wrap成一个IOException。
3. 直接在自己的接口上声明底层传上来的异常,让异常往上自动传播。
例子:最常见的比如FileNotFoundException,在JDK中是一个checked exception。我们来分析以上三种做法的可行性:
首先假设这样一种场合,在这个场合,最底层代码遇到文件不存在异常时是不知道如何解决的,这应该是可能的。
1. 既然刚才说了,底层不知道如何对付这个异常(有可能到了高层会弹出对话框让用户提供一个文件路径,就像Adobe安装过程中找不到ArcoUpdate.msi文件一样) 。因此这个异常需要往上传,这就把我们逼到2、3两种可能。
2. 调用方可能根本不能算是一个抽象层,所以也不想wrap这个异常。这就只剩下一种可能----3.
3. 显然也不好,因为既然这层不处理异常,那干嘛让它的接口和这个异常耦合起来呢。前面我说过,耦合的只应该是发生端和接收端。
这个逻辑有漏洞吗?
Atry,
1. 为什么"3 这种情况,一定是属于某个模块内部的函数"?
2. 为什么模块内部的函数不用考虑耦合呢?(耦合只有两种,一种是不必要的,一种是必要的。如果是不必要的耦合,即便是在模块内部,我也认为是没有为好。而我认为3这种情况下的耦合就是不必要的,既然一个函数不处理异常,而这是任其向上穿过它的这层栈,那么其实这个函数对这个异常就是语意上透明的,强迫其在接口上声明这个异常不是不必要的耦合又是什么呢?)
在我的认知中,"低耦合" 的同义词就是"良好定义的接口"。一个模块内部实现代码与耦合无关。
写一个内部函数的时候考虑耦合是愚蠢的。内部函数质量高低的唯一标准是代码长度——代码越短越好。这一点正是前段时间" Linus 炮轰事件"给我的启示。"用 C 编码,用 C++ 实现"也是这个意思。模板这样的猥琐特性有利于缩短代码长度,但反而增加耦合。所以模板用在模块内部( cpp 文件中)就无所谓,用来约定接口就不妥。
C++之所以不适合作为模块间的接口,我认为主要是C++缺乏必要的二进制兼容性,而不是说C++不能作为模块间的接口。
关于模块内的耦合,我认为也是尽量的少为秒。比如你写了一个函数作为模块对外的API,确实简单,十分好用。但是如果我是一个服务程序,而你的函数需要依赖于MFC,我就要考虑能不能了。同样的道理,如果你的library有很大,我只用了其中一个加密算法,你就要我把这个library的DLL全部带进去,我也不会愿意的。MFC以前的一个问题就是,哪怕我用一个CString,我都要把MFC DLL全部带进去,我十分反感这样的做法(这是基于商业绑定的猥琐做法),所以我尽量就不用CString。
"接口" 是广义的还是狭义的 ?
Atry 写道:
> 我再说一遍。"低耦合" 的同义词就是"良好定义的接口"。没有接口就没有重
> 用,模块内的所谓"最小代码修改"只是你的幻想而已。
>
1. 在你经历的项目中,哪些情况被定义为错误,需要汇报的。(注意,这里的错误不包括编程错误(bug);而是指IO错误和文件错误这类。)给个模糊的答案即可:)
2. 你们使用的错误处理机制是error-code呢还是exception?
3. 你们如何确保error-safe的?也就是说,当错误发生的时候,如何确保能够正确clean up的?
4. 你们如何确保所有错误都被妥善(如果能handle的话)handle或者(如果不能handle的话)传播给上级的?
5. 你们的项目中,有多少错误是可以就地handle,有多少错误是就地没有足够上下文,需要传播到上层模块解决的?
感谢大家哈:-)
--
刘未鹏(pongba)|C++的罗浮宫
http://blog.csdn.net/pongba
TopLanguage
http://groups.google.com/group/pongba
我一般是预料可能遇到的错误使用reutrn error code,比如fopen()失败,connect()超时之类。完全预料之外的使用
except,比如明明刚才已经fopen() succ,接下来fwrite()出错,就抛出except。
然后except如果发生在一个内部调用的接口,就只做自己范围内的清理工作之后继续throw,把最终决定权交给调用方或者顶层的
application来决定,最后如果是个工具性程序就terminated,如果是个service或者daemon就只log之然后忽略。