你为什么不用异常?

已查看 904 次
跳至第一个未读帖子

pongba

未读,
2007年10月26日 07:32:282007/10/26
收件人 pon...@googlegroups.com
首先,我坚定的认为应该用异常,应该使用异常作为错误报告的唯一机制。哦,实际上,不是我,是Brad Abrams(《.NET框架设计指南》作者),Anders(C#之父),Andrei Alexandrescu(《C++ Coding Standard: 101 Rules》作者)这么说的。

其次,国内的C++开发者估计都知道,在绝大多数C++项目中,异常都是被禁用的,至于是不是连编译选项都关掉了还是只是在编码规定上禁止,就不知道了(你那边的实际情况是什么?:-)),据说就连Google内部也是禁用异常的。所以估计情况还不仅仅局限于国内,甚至地球上的C++项目大抵都是如此。

然而不可否认的一个事实是,C++社群这么多年来的经验告诉我们,使用异常(具体参见《C++ Coding Standard: 101 Rules》第7x章~第7y章(x=?和y=?我忘了))。Herb Sutter和Andrei Alexandrescu在C++异常方面的经验和造诣估计C++社群还没有几个人能够质疑的。

生下来的问题就很简单了:为什么存在这个理想与现实的矛盾?这就是我想请大家讨论的:P

个人一点看法:

1. Fallacies和Myths导致的Fear心态,进而导致在异常的正确实践方式方面的标准缺乏(《C++ Coding Standard: 101 Rules》出版之前,你能找到一份哪怕比较标准的,权威的,关于如何,何时使用异常的参考文献吗?)

2. 旧时代异常实现手段的落后(g++的影栈(shadow stack)实现)以及硬件的落后(导致异常开销*相对*显得大),致使在当时,异常被打入冷宫。由于人在心理上容易忘记一个论点的前提而记住其结论,所以若干年后的今天,仍然有大量人认为异常的开销在任何情况下都很大很显著。而实际情况是,异常有开销,但仅当在极度追求效率(时间&空间)的场合(极少),这个开销是完全可以忽略的。

3. 教育问题。异常在C++里面是一个较新的特性。而且,使用错误代码,不需要新学习语言特性,只要在现有语言特性(返回值)上hack就行了。不得不承认,就连程序员也是害怕学新东西的。

4. 把错误处理的本质困难错误地归咎到异常身上。错误处理的本质困难是:定义哪些情况属于错误,错误发生的时候在什么地点响应,错误发生如何确保操作的完整语意(回滚、保存数据、释放资源)。无论用异常还是错误代码,这些问题都同样尖锐。

5. 更多重量级的Fallacies:详见Raymond Chen(《The Old New Thing》作者,Win32大牛)和Joel Spolsky(《Joel On Software》作者)的口水。Raymond Chen的"Cleaner, more elegant, and harder to recognize ",和Joel的两篇,这里这里。要知道,这两位的口水的权重可不是一般的大啊。幸亏人民群众的眼睛是雪亮的,Raymond和Joel的论点乍看十分、非常有理,实际经不起推敲。你能看出里面的漏洞吗?

6. 遗留代码问题。遗留代码里面没有用异常,导致往遗留系统里面增加新模块也首先与原先系统里用的错误处理策略的限制(这一点我不是很确定,有实际经验的可以明确的举例说明一下?)

7. 心理问题。大量C++程序员有C背景(也许是因为教材是从C往上写的也许其他什么原因),从心里就没有把异常当成一个first-class的,专门的,唯一的错误处理机制。Java通过把人们逼上梁山解决了这个心理问题。而对于C++程序员来说,也许下意识里面最"熟悉"的就是用返回值来报错;至于找出一堆理由...well...只是找出一堆理由而已。能找出一堆经得起推敲的理由,那才是真的理由。不过,对自己找的理由从来都不需要推敲的。

最想知道的就是大家经历的实际项目中的情况,越明确,越实际越好:-) 我似乎把空话套话都给说完了:P
--
刘未鹏(pongba)|C++的罗浮宫
http://blog.csdn.net/pongba
TopLanguage
http://groups.google.com/group/pongba

莫华枫

未读,
2007年10月26日 07:47:382007/10/26
收件人 pon...@googlegroups.com
一年以前,我做的所有项目没有任何异常的影子,因为那时我是mfc分子;现在,我尽我所能地用异常,因为现在我是标准C++分子。:-)
异常让我的代码干净很多,优雅很多。不过得小心,会上瘾的。:)
不用不知道,异常真奇妙。:)

在07-10-26,pongba < pon...@gmail.com> 写道:
首先,我坚定的认为应该用异常,应该使用异常作为错误报告的唯一机制。哦,实际上,不是我,是Brad Abrams(《.NET框架设计指南》作者),Anders(C#之父),Andrei Alexandrescu(《C++ Coding Standard: 101 Rules》作者)这么说的。

其次,国内的C++开发者估计都知道,在绝大多数C++项目中,异常都是被禁用的,至于是不是连编译选项都关掉了还是只是在编码规定上禁止,就不知道了(你那边的实际情况是什么?:-)),据说就连Google内部也是禁用异常的。所以估计情况还不仅仅局限于国内,甚至地球上的C++项目大抵都是如此。

然而不可否认的一个事实是,C++社群这么多年来的经验告诉我们,使用异常(具体参见《C++ Coding Standard: 101 Rules》第7x章~第7y章(x=?和y=?我忘了))。Herb Sutter和Andrei Alexandrescu在C++异常方面的经验和造诣估计C++社群还没有几个人能够质疑的。

生下来的问题就很简单了:为什么存在这个理想与现实的矛盾?这就是我想请大家讨论的:P

个人一点看法:

1. Fallacies和Myths导致的Fear心态,进而导致在异常的正确实践方式方面的标准缺乏(《C++ Coding Standard: 101 Rules》出版之前,你能找到一份哪怕比较标准的,权威的,关于如何,何时使用异常的参考文献吗?)

2. 旧时代异常实现手段的落后(g++的影栈(shadow stack)实现)以及硬件的落后(导致异常开销*相对*显得大),致使在当时,异常被打入冷宫。由于人在心理上容易忘记一个论点的前提而记住其结论,所以若干年后的今天,仍然有大量人认为异常的开销在任何情况下都很大很显著。而实际情况是,异常有开销,但仅当在极度追求效率(时间&空间)的场合(极少),这个开销是完全可以忽略的。

3. 教育问题。异常在C++里面是一个较新的特性。而且,使用错误代码,不需要新学习语言特性,只要在现有语言特性(返回值)上hack就行了。不得不承认,就连程序员也是害怕学新东西的。

4. 把错误处理的本质困难错误地归咎到异常身上。错误处理的本质困难是:定义哪些情况属于错误,错误发生的时候在什么地点响应,错误发生如何确保操作的完整语意(回滚、保存数据、释放资源)。无论用异常还是错误代码,这些问题都同样尖锐。

5. 更多重量级的Fallacies:详见Raymond Chen(《The Old New Thing》作者,Win32大牛)和Joel Spolsky(《Joel On Software》作者)的口水。Raymond Chen的" Cleaner, more elegant, and harder to recognize ",和Joel的两篇,这里 这里。要知道,这两位的口水的权重可不是一般的大啊。幸亏人民群众的眼睛是雪亮的,Raymond和Joel的论点乍看十分、非常有理,实际经不起推敲。你能看出里面的漏洞吗?

6. 遗留代码问题。遗留代码里面没有用异常,导致往遗留系统里面增加新模块也首先与原先系统里用的错误处理策略的限制(这一点我不是很确定,有实际经验的可以明确的举例说明一下?)

7. 心理问题。大量C++程序员有C背景(也许是因为教材是从C往上写的也许其他什么原因),从心里就没有把异常当成一个first-class的,专门的,唯一的错误处理机制。Java通过把人们逼上梁山解决了这个心理问题。而对于C++程序员来说,也许下意识里面最"熟悉"的就是用返回值来报错;至于找出一堆理由...well...只是找出一堆理由而已。能找出一堆经得起推敲的理由,那才是真的理由。不过,对自己找的理由从来都不需要推敲的。

最想知道的就是大家经历的实际项目中的情况,越明确,越实际越好:-) 我似乎把空话套话都给说完了:P
--
刘未鹏(pongba)|C++的罗浮宫
http://blog.csdn.net/pongba
TopLanguage
http://groups.google.com/group/pongba




--
反者道之动,弱者道之用
m...@seaskysh.com
longsh...@gmail.com
http://blog.csdn.net/longshanks/

red...@gmail.com

未读,
2007年10月26日 08:19:272007/10/26
收件人 pon...@googlegroups.com
不用异常的话, 某个函数的返回值是否被检查, 是容易用人工检查的(外包给别人
检查都可以), 也容易用程序质量分析软件处理(国外有这样的软件/service), 其
实可以保证到每个函数的返回值都被检查, 甚至可以保证到函数的每个错误值都被
检查.

但是用异常的话, 会不会造成资源泄漏或者其他不稳定的因素, 可就不是堆砌人力
或者分析软件可以分析出来的了.

我的想法, 不用异常的话, 程序质量的保证更容易由非天才工程师保证; 用了异常
的话, 嗯, 我们只能期待每个角落的编码, 都考虑到异常的 10 rules 了.

pongba 写道:

pongba

未读,
2007年10月26日 08:27:142007/10/26
收件人 pon...@googlegroups.com


On 10/26/07, red...@gmail.com <red...@gmail.com> wrote:
不用异常的话, 某个函数的返回值是否被检查, 是容易用人工检查的(外包给别人
检查都可以), 也容易用程序质量分析软件处理(国外有这样的软件/service), 其
实可以保证到每个函数的返回值都被检查, 甚至可以保证到函数的每个错误值都被
检查.

但是用异常的话, 会不会造成资源泄漏或者其他不稳定的因素, 可就不是堆砌人力
或者分析软件可以分析出来的了.

呃.. 从理论上,是可以做出这样的工具:它能够检查一个程序中所有潜在抛出的异常是否都被catch了。目前有没有这个工具我不知道,但根据异常在其它语言中的繁荣程度,至少不应该不会出现。只要检查是否所有异常都被catch,是否有空catch,是否有无副作用的catch,等等就可以一定程度上保证软件没有无视异常了吧。因此我觉得这个论据不成立。

Googol Lee

未读,
2007年10月26日 08:50:452007/10/26
收件人 TopLanguage
一个是最早学的时候就没有很好的异常教材,语法书里只是讲了怎么用异常,而没有讲什么是异常。真正讲什么是异常的书到现在也就那么几本吧。而且真正要想
正确区分错误和异常,在实践上感觉还是比较难的,有可能出现前期发现是异常,后面却要转成错误,或者相反的情况。毕竟对一个新项目来说,前期分析不可能
做到尽善尽美。

另一个,整个系统是基于返回值的,导致后面在用的时候也主要去想怎么用返回值描述了。

不过我做的系统倒是有一个地方很有意思,如果new失败,也不返回null,也不抛异常,而是系统直接吐核了,相当于是个操作系统级别的异常吧......

pongba

未读,
2007年10月26日 08:55:272007/10/26
收件人 pon...@googlegroups.com
On 10/26/07, Googol Lee <goog...@gmail.com> wrote:
一个是最早学的时候就没有很好的异常教材,语法书里只是讲了怎么用异常,而没有讲什么是异常。真正讲什么是异常的书到现在也就那么几本吧。而且真正要想
正确区分错误和异常,在实践上感觉还是比较难的,有可能出现前期发现是异常,后面却要转成错误,或者相反的情况。毕竟对一个新项目来说,前期分析不可能
做到尽善尽美。

错误和异常...需要区分吗?要区分的是错误和非错误,异常和错误代码一样,只是错误表达的工具。

red...@gmail.com

未读,
2007年10月26日 09:08:262007/10/26
收件人 pon...@googlegroups.com
不是 catch 的问题, 而是 throw 和 catch 中间路径的代码, 是不是都是异常安全的, 这个检查起来比较费事.

返回值的检查与否, 是静态路径, 局部化的; 但是异常安全, 是动态的, 全局的, 检查代码是否ok 麻烦很多.

例如, 析构函数是不应该让异常跑出去的, 如果一个模板类的析构函数要调用 policy 中的一个函数, 此时要不要 try, catch ? 如果每个析构函数都要写上 try, catch, 是不是代码其实也很烦 ? 但是你有办法吗? 你无法预料别人给你一个什么样的 policy.

这里, 反而返回值可能还简单一些, 反正之后相关的东西都不用了, 忽略掉似乎关系也不大.

异常还涉及到 API 的设计, 不是有些容器, 某些功能合起来一起好用, 但是提供的 api 是分开的, 为了异常安全吗 ? 写个什么东西都要考虑这么多, 这不是 "心智负担" 吗? 这些与语义有关的东西, 人工检查不容易, 开发自动软件好像也不太容易吧 ?

你可能说, 要求每个程序员都做好 10 rules 就行, 但是, 如果不是整个团队水平和责任心都这么好呢? 如果碰到赶工想快点交货呢 ?  如果要使用一个第三方库呢 ? 如果要调用一个 C 库, 并且还要给 C 库回调函数呢 ?

我觉得, 异常带来了优雅, 但是同时带来了很大的负担.
不用异常, 代码的表面罗嗦, 但是却容易保证正确性.

pongba 写道:


On 10/26/07, red...@gmail.com <red...@gmail.com> wrote:
不 用异常的话, 某个函数的返回值是否被检查, 是容易用人工检查的(外包给别人

检查都可以), 也容易用程序质量分析软件处理(国外有这样的软件/service), 其
实可以保证到每个函数的返回值都被检查, 甚至可以保证到函数的每个错误值都被
检查.

但是用异常的话, 会不会造成资源泄漏或者其他不稳定的因素, 可就不是堆砌人力
或者分析软件可以分析出来的了.

wang xin

未读,
2007年10月26日 09:12:342007/10/26
收件人 pon...@googlegroups.com


在07-10-26,red...@gmail.com <red...@gmail.com> 写道:
不用异常的话, 某个函数的返回值是否被检查, 是容易用人工检查的(外包给别人
检查都可以), 也容易用程序质量分析软件处理(国外有这样的软件/service), 其
实可以保证到每个函数的返回值都被检查, 甚至可以保证到函数的每个错误值都被
检查.

但是用异常的话, 会不会造成资源泄漏或者其他不稳定的因素, 可就不是堆砌人力
或者分析软件可以分析出来的了.

我的想法, 不用异常的话, 程序质量的保证更容易由非天才工程师保证; 用了异常
的话, 嗯, 我们只能期待每个角落的编码, 都考虑到异常的 10 rules 了.

我在最近的工作中都鼓吹不要使用异常的原因就是这个,天才工程师不是随便就可以抓到的

莫华枫

未读,
2007年10月26日 09:15:382007/10/26
收件人 pon...@googlegroups.com
资源问题,用不用异常是等价的:
//code #1
HRESULT fun1(int n) {
    int* p=new int[n];
    ...
    if(...)
        return E_FAIL; //此处泄漏
    ...
    delete[] p;
    return S_OK;
}
//code #2
HRESULT fun2(int n) {
    vector<int> v(n);
    ...
    if(...)
        return E_FAIL; //此处安全的
    ...
    return S_OK;
}
//code #3
void fun3(int n) {
    int* p=new int[n];
    ...
    if(...)
        throw exception("..."); //此处泄漏
    ...
    delete[] p;
}
//code #4
void fun4(int n) {
HRESULT fun2(int n) {
    vector<int> v(n);
    ...
    if(...)
        throw exception("..."); //此处安全的
    ...
}
这四段代码分别代表:不用异常,手动资源管理;不用异常,自动资源管理(RAII);用异常,手动资源管理;用异常,自动资源管理。
可以看到,只要手动资源管理,异常安全很麻烦,否则,就容易很多。
至于异常的捕捉,如果一个异常未被捕捉,那么最终会造成一个程序中止或系统中止。这是显式的,完整的测试可以消除绝大部分的这类问题,即使有遗漏,系统也不会悄悄地"带病工作"。相反,如果一个返回错误码未被捕捉,则不会有任何反应。问题是隐式的,严格的测试也无法保证能够发现这类问题。更不用说运行时,程序的奇怪行为了。


在07-10-26,red...@gmail.com <red...@gmail.com> 写道:

yq chen

未读,
2007年10月26日 09:18:312007/10/26
收件人 pon...@googlegroups.com
异常并不是你不用就不存在的,如果你有一个第三方的库它使用异常,你怎么弄?以返回值返回错误的函数,你们确保都能被程序员检测吗?一个不太恰当的问题,有多少人检测过MessageBox的返回值?
 
所以返回值返回我看悬。

 
在07-10-26,wang xin <cber.w...@gmail.com> 写道:

pongba

未读,
2007年10月26日 09:21:522007/10/26
收件人 pon...@googlegroups.com
On 10/26/07, red...@gmail.com <red...@gmail.com> wrote:
不是 catch 的问题, 而是 throw 和 catch 中间路径的代码, 是不是都是异常安全的, 这个检查起来比较费事.

用错误代码的话,检查起来同样费时。实际上,不管是用错误代码还是用异常,检查一个中间函数是否异常中立几乎都是不可能的。

我猜你的意思应该是,使用错误代码的话,一个工具可以很容易检查需要错误中立的函数是否check了每个函数的返回值。但是,that's it。工具也只能检查到这个份上。值得注意的是,"检查了每个返回值"并不代表"做到了错误安全",而这个正是Raymond的那篇post犯的错误,比如:
BOOL ComputeChecksum(LPCTSTR pszFile, DWORD* pdwResult)
{
BOOL fRc = FALSE;
HANDLE h = CreateFile(pszFile, GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (h != INVALID_HANDLE_VALUE) {
HANDLE hfm = CreateFileMapping(h, NULL, PAGE_READ, 0, 0, NULL);
if (hfm) {
void *pv = MapViewOfFile(hfm, FILE_MAP_READ, 0, 0, 0);
if (pv) {
DWORD dwHeaderSum;
if (CheckSumMappedFile(pvBase, GetFileSize(h, NULL),
&dwHeaderSum, pdwResult)) {
fRc = TRUE;
}
UnmapViewOfFile(pv);
}
CloseHandle(hfm);
}
CloseHandle(h);
}
return fRc;
}
这段代码做到错误安全了吗?不管是人眼,还是工具,看到这段代码唯一能够得出的结论是:写代码的人*想要*做到错误安全。然而"写代码的人想不想做到错误安全"并不是真正重要的问题,重要的是,代码实际上是不是异常安全的。

后者只能靠开发者自己来保证。

而要保证开发者编码的时候*想要*尽量做到错误安全,靠工具检查的确可以做到监督的作用。然而这并不能改变开发者在保证错误安全方面的编码能力。不能解决问题的本质。

返回值的检查与否, 是静态路径, 局部化的; 但是异常安全, 是动态的, 全局的, 检查代码是否ok 麻烦很多.

例如, 析构函数是不应该让异常跑出去的, 如果一个模板类的析构函数要调用 policy 中的一个函数, 此时要不要 try, catch ? 如果每个析构函数都要写上 try, catch, 是不是代码其实也很烦 ? 但是你有办法吗? 你无法预料别人给你一个什么样的 policy.

这个,已经有了既有的实践,那就是catch-all。析构函数无论如何不应该抛出异常。 我不知道有没有例外情况。

这里, 反而返回值可能还简单一些, 反正之后相关的东西都不用了, 忽略掉似乎关系也不大.

用catch-all,也是忽略。这个区别并不真正重要。

异常还涉及到 API 的设计, 不是有些容器, 某些功能合起来一起好用, 但是提供的 api 是分开的, 为了异常安全吗 ? 写个什么东西都要考虑这么多, 这不是 "心智负担" 吗? 这些与语义有关的东西, 人工检查不容易, 开发自动软件好像也不太容易吧 ?

呃。这个... 就是把错误处理的本质困难归咎到异常头上了吧。用错误代码同样需要考虑错误安全。要想摆脱心智负担就是忽略一切错误,仅编写happy path。 所以这个论据并不能得出"异常的心智负担比错误代码大"的结论。

你可能说, 要求每个程序员都做好 10 rules 就行, 但是, 如果不是整个团队水平和责任心都这么好呢? 如果碰到赶工想快点交货呢 ?  如果要使用一个第三方库呢 ? 如果要调用一个 C 库, 并且还要给 C 库回调函数呢 ?

同上。如果不是整个团队水平和责任心都这么高,用错误代码也是白搭。据说C程序员一个经典的做法就是忽略fopen的错误返回。有人甚至建议,如果团队反正责任心不高,还不如别搞什么错误处理,直接写happy path算了,因为只要一个模块没考虑好错误处理,其它模块考虑了也可能是白搭。

我觉得, 异常带来了优雅, 但是同时带来了很大的负担.
不用异常, 代码的表面罗嗦, 但是却容易保证正确性.

 这个结论。跟Raymond的一样。我在上面已经提出了我的看法:-)

莫华枫

未读,
2007年10月26日 09:30:262007/10/26
收件人 pon...@googlegroups.com


在07-10-26,red...@gmail.com <red...@gmail.com> 写道:
不是 catch 的问题, 而是 throw 和 catch 中间路径的代码, 是不是都是异常安全的, 这个检查起来比较费事.

这方面,异常的确复杂于返回码。但是,解决的方法却是相同的,即资源的自动化管理。这是一劳永逸的方法,对于两种形式都有作用。

返回值的检查与否, 是静态路径, 局部化的; 但是异常安全, 是动态的, 全局的, 检查代码是否ok 麻烦很多.

例如, 析构函数是不应该让异常跑出去的, 如果一个模板类的析构函数要调用 policy 中的一个函数, 此时要不要 try, catch ? 如果每个析构函数都要写上 try, catch, 是不是代码其实也很烦 ? 但是你有办法吗? 你无法预料别人给你一个什么样的 policy.

这是gp带来的挑战,而非异常本身的问题。同样,使用返回码,也存在类似的问题,只是问题相对直观,我们更加习惯而已。


这里, 反而返回值可能还简单一些, 反正之后相关的东西都不用了, 忽略掉似乎关系也不大.

异常还涉及到 API 的设计, 不是有些容器, 某些功能合起来一起好用, 但是提供的 api 是分开的, 为了异常安全吗 ? 写个什么东西都要考虑这么多, 这不是 "心智负担" 吗? 这些与语义有关的东西, 人工检查不容易, 开发自动软件好像也不太容易吧 ?

这些问题在传统的oop设计中很少出现,只是运用了gp后,问题复杂化了。这也并非异常本身的问题。

你可能说, 要求每个程序员都做好 10 rules 就行, 但是, 如果不是整个团队水平和责任心都这么好呢? 如果碰到赶工想快点交货呢 ?  如果要使用一个第三方库呢 ? 如果要调用一个 C 库, 并且还要给 C 库回调函数呢 ?

rule的问题我是这么看的。我本行是机械专业,现在做软件觉得这些rule并没有什么困难,也没有成为负担。因为在机械行业里,rule遍地都是,渐渐地也就习惯了。软件实在是太"soft" 了,任何方法都能做成一件事情,对于rule的忍耐度也就小得多了。但是,我认为如果软件行业要真正的成熟、可靠,必须象机械行业那样,认真地接受各种各样的rule,毕竟没有规矩不成方圆。

我觉得, 异常带来了优雅, 但是同时带来了很大的负担.
不用异常, 代码的表面罗嗦, 但是却容易保证正确性.

pongba 写道:


On 10/26/07, red...@gmail.com < red...@gmail.com> wrote:
不 用异常的话, 某个函数的返回值是否被检查, 是容易用人工检查的(外包给别人
检查都可以), 也容易用程序质量分析软件处理(国外有这样的软件/service), 其
实可以保证到每个函数的返回值都被检查, 甚至可以保证到函数的每个错误值都被
检查.

但是用异常的话, 会不会造成资源泄漏或者其他不稳定的因素, 可就不是堆砌人力
或者分析软件可以分析出来的了.

呃.. 从理论上,是可以做出这样的工具:它能够检查一个程序中所有潜在抛出的异常是否都被catch了。目前有没有这个工具我不知道,但根据异常在其它语言中的 繁荣程度,至少不应该不会出现。只要检查是否所有异常都被catch,是否有空catch,是否有无副作用的catch,等等就可以一定程度上保证软件没 有无视异常了吧。因此我觉得这个论据不成立。




pongba

未读,
2007年10月26日 09:31:252007/10/26
收件人 pon...@googlegroups.com


On 10/26/07, yq chen <mephist...@gmail.com> wrote:
异常并不是你不用就不存在的,如果你有一个第三方的库它使用异常,你怎么弄?以返回值返回错误的函数,你们确保都能被程序员检测吗?一个不太恰当的问题,有多少人检测过MessageBox的返回值?
 
所以返回值返回我看悬。

没错,这正是返回值的问题之一。
redsea和wangxin(以及Raymond Chen)的意思都是,可以利用工具或者人眼review,确定一个函数里面是否检查了每个返回值。
但最关键的问题,也是真正的问题却是:难道检查了每个返回值,就能够做到错误安全了吗?

真正需要check的,是在每一个可能的错误发生点上,前面的操作都能够被回滚掉,资源会被正确释放掉等等。这个才是真正重要的check。这个我觉得可见的将来都只能由人来做了(review)。

因此,是否容易用工具检查程序员是否check了每个返回值,其实是一个irrelevant的问题。因为即便check了每一个,也不能确保程序任何程度上的正确性。

莫华枫

未读,
2007年10月26日 09:36:422007/10/26
收件人 pon...@googlegroups.com
再说两句rule。在机械行业里,不用天才,也能遵守rule,并且只有遵守了rule,才能保证任何资质的人都能很好的完成任务。毕竟这是自然规律,遵守它是天经地义的。
软件行业相比机械行业还处于少儿阶段,整个的培训体系不是以rule为基础的,以至于造成很多工程方面的问题。其实,我们与其大谈什么设计方法、理念,还不如认认真真地传授一些rule,比任何软件工程方法都来的有用。


在07-10-26,莫华枫 <longsh...@gmail.com> 写道:

abware

未读,
2007年10月26日 09:42:432007/10/26
收件人 TopLanguage
可能产生异常的地方不要new,用构造的析构来new和delete...
咳咳,貌似比较丑陋

> 至于异常的捕捉,如果一个异常未被捕捉,那么最终会造成一个程序中止或系统中止。这是显式的,完整的测试可以消除绝大部分的这类问题,即使有遗漏,系统也不会悄 悄地"带病工作"。相反,如果一个返回错误码未被捕捉,则不会有任何反应。问题是隐式的,严格的测试也无法保证能够发现这类问题。更不用说运行时,程序的奇怪行 为了。


>
> 在07-10-26,red...@gmail.com <red...@gmail.com> 写道:
>
>
>
>
>
>
>
> > 不用异常的话, 某个函数的返回值是否被检查, 是容易用人工检查的(外包给别人
> > 检查都可以), 也容易用程序质量分析软件处理(国外有这样的软件/service), 其
> > 实可以保证到每个函数的返回值都被检查, 甚至可以保证到函数的每个错误值都被
> > 检查.
>
> > 但是用异常的话, 会不会造成资源泄漏或者其他不稳定的因素, 可就不是堆砌人力
> > 或者分析软件可以分析出来的了.
>
> > 我的想法, 不用异常的话, 程序质量的保证更容易由非天才工程师保证; 用了异常
> > 的话, 嗯, 我们只能期待每个角落的编码, 都考虑到异常的 10 rules 了.
>
> > pongba 写道:
> > > 首先,我坚定的认为应该用异常,应该使用异常作为错误报告的唯一机制。哦,
> > > 实际上,不是我,是Brad Abrams(《.NET框架设计指南》作者),Anders(C#之
> > > 父),Andrei Alexandrescu(《C++ Coding Standard: 101 Rules》作者)这么
> > > 说的。
>

> --
> 反者道之动,弱者道之用
> m...@seaskysh.com
> longshank...@gmail.comhttp://blog.csdn.net/longshanks/- Hide quoted text -
>
> - Show quoted text -

red...@gmail.com

未读,
2007年10月26日 09:46:512007/10/26
收件人 pon...@googlegroups.com
嗯, 老实话, 我不是很清楚 raymond 的代码的问题在哪里 ?
>
> 同上。如果不是整个团队水平和责任心都这么高,用错误代码也是白搭。据说C
> 程序员一个经典的做法就是忽略fopen的错误返回。有人甚至建议,如果团队反
> 正责任心不高,还不如别搞什么错误处理,直接写happy path算了,因为只要一
> 个模块没考虑好错误处理,其它模块考虑了也可能是白搭。
如果我规定,
1. 用返回值处理错误
2. 涉及到资源申请的变量, 必须开始的时候赋一个表示非法资源的值
3. 涉及到状态修改的代码(例如修改一个 stack 的内部状态), 必须在 ok: label
下面写, 在这里才修改内部状态
4. 所以的失败代码, 必须 goto 到 fail: 这里会检查资源并且释放

这样起码能够避免大多数的错误, 如果写不出这样的代码, 则证明有些 api 设计
不合理, 要考虑重新设计.

这种规定, 是容易检查是否符合的, 而且不必高手就可以检查, 并且工具检查也不
是很难.

-----
如果我规定

大家一定要写异常安全代码,

我的细则应该怎么写?

一个函数内部, 应该先写可能抛出异常的代码, 然后修改状态
缺省构造函数应该如何如何
析构函数应该如何如何
拷贝构造函数应该如何如何
赋值构造函数应该如何如何
重载了 new 应该如何如何
重载了 delete 应该如何如何
返回临时对象的函数应该如何如何 ?
如果是模板中, 返回模板参数中的类型, 又应该怎么办 ?
写类似stl 中 pop_back 之类的函数应该如何如何 ?
......

那么细则太多条了, 不但设计到函数内部的代码, 还设计到 api 设计.

检查代码的时候, 很辛苦啊.


red...@gmail.com

未读,
2007年10月26日 09:51:552007/10/26
收件人 pon...@googlegroups.com
莫华枫 写道:


在07-10-26,red...@gmail.com <red...@gmail.com> 写道:
不是 catch 的问题, 而是 throw 和 catch 中间路径的代码, 是不是都是异常安全的, 这个检查起来比较费事.

这方面,异常的确复杂于返回码。但是,解决的方法却是相同的,即资源的自动化管理。这是一劳永逸的方法,对于两种形式都有作用。

  不仅仅是资源管理完毕了就好了啊, 例如, 我往你的 list 类里面插一个对象, 你的list 做了一些内部事情, 然后执行 = 赋值这个对象, 此时异常发生, 仅仅资源不泄漏, 但是内部状态改掉了.

  关键是异常是只要你调用了外部的函数就可能发生, 甚至一个操作符也可能发生, 时时刻刻都要注意, 同时规则又多, 我比较害怕.

  (不重载操作符的日子, 要轻松一些, 容易知道, 这几行代码是不会跑个异常出来吓人一跳. )

莫华枫

未读,
2007年10月26日 10:06:522007/10/26
收件人 pon...@googlegroups.com
这些实际上应该是C++(98)的异常rule,而并非异常rule。在D,Java甚至C++0x这些具备gc的语言中,这些rule中的大多数都可以不用操心的。

在07-10-26,red...@gmail.com < red...@gmail.com> 写道:

莫华枫

未读,
2007年10月26日 10:09:382007/10/26
收件人 pon...@googlegroups.com


在07-10-26,red...@gmail.com <red...@gmail.com> 写道:
莫华枫 写道:


在07-10-26,red...@gmail.com <red...@gmail.com> 写道:
不是 catch 的问题, 而是 throw 和 catch 中间路径的代码, 是不是都是异常安全的, 这个检查起来比较费事.

这方面,异常的确复杂于返回码。但是,解决的方法却是相同的,即资源的自动化管理。这是一劳永逸的方法,对于两种形式都有作用。

  不仅仅是资源管理完毕了就好了啊, 例如, 我往你的 list 类里面插一个对象, 你的list 做了一些内部事情, 然后执行 = 赋值这个对象, 此时异常发生, 仅仅资源不泄漏, 但是内部状态改掉了.

  关键是异常是只要你调用了外部的函数就可能发生, 甚至一个操作符也可能发生, 时时刻刻都要注意, 同时规则又多, 我比较害怕.

这个问题同样套在非异常方式上,赋值发生错误,如何维持commit or  rollback语义呢?这里所需的规则同使用异常时的有多大的差别呢?


  (不重载操作符的日子, 要轻松一些, 容易知道, 这几行代码是不会跑个异常出来吓人一跳. )


red...@gmail.com

未读,
2007年10月26日 10:22:322007/10/26
收件人 pon...@googlegroups.com
莫华枫 写道:



  关键是异常是只要你调用了外部的函数就可能发生, 甚至一个操作符也可能发生, 时时刻刻都要注意, 同时规则又多, 我比较害怕.

这个问题同样套在非异常方式上,赋值发生错误,如何维持commit or  rollback语义呢?这里所需的规则同使用异常时的有多大的差别呢?

但是如果我规定函数的写法(另外一个帖子中), 这个问题还是容易处理的.

如果用返回值方式, 我看到一个外部函数, 没有返回值要检查, 我就知道, 这个函数可以认为一定成功, 可以在任何地方调用, 否则就需要注意.

我以前见过别人写这样的代码, 我不用异常的时候, 也是这样写


#define FAILED(v)   {res=v; goto end; }

int some_func()
{
int   res=0;
int   fd = -1;
char *p1=null, *p2 =null;

if ( (fd=open("xxx", O_RDWR) ) == -1 )
   FAILED(FILE_CAN_NOT_OPEN);

if ( (p1=malloc(8000) == NULL ))
   FAILED(NO_MEM);

if ( (p2=malloc(4000) == NULL ))
   FAILED(NO_MEM);

// 如果 fd 需要交给别的函数, 同时 ownership 也转移, 那么成功转交之后, fd 要设置为 -1
// 或者干脆接收 api 自己做这个设置 -1 的工作

end:
  if (p2) free(p2);
  if (p1) free(p1);
  if (fd) close(fd);
}


这样很清楚啊, 我还不必提心吊胆哪里会有异常.

只是现在的基本库都有异常了, 所以我也用异常了, 不过规则太多, 我记不住, 也就随便乱写, 可靠性还不如上面的代码.

莫华枫

未读,
2007年10月26日 10:24:072007/10/26
收件人 pon...@googlegroups.com
  (不重载操作符的日子, 要轻松一些, 容易知道, 这几行代码是不会跑个异常出来吓人一跳. )

呵呵,我也害怕被吓一跳。但是与其让用户吓一跳,还不如让我吓一跳的好。
我过去在运用错误返回值时,经常会有错误无法在测试的时候找到。多数时候,即便跑过了这段代码,由于错误码未被捕捉,系统未有任何反应。直到数据被搅乱,或者客户开始嚷嚷。
异常的最大好处在于,无论你是否理睬它,它都会引起你的注意。而返回码则无法做到这一点。
诚然,运用异常要求较高的技能和知识水平。但是,它所带来的好处依然是不容忽视的。有所得,必有所失。福兮祸之所倚,祸兮福之所伏。任何事物都有两面性。经过适当的培训和锻炼,异常运用技巧总能掌握的,这是死的,暂时的。但是,它所带来的好处则是却是永远的,终生都能受益的。
相反,返回错误码的方式所存在的不足也是无法改变的。当你在运用错误码时,依然会面临资源问题,rollback问题等等。当这些解决之后,路实际上也已经走出一半了,那为何就不干脆走到底呢?



red...@gmail.com

未读,
2007年10月26日 10:31:332007/10/26
收件人 pon...@googlegroups.com
莫华枫 写道:

> 这些实际上应该是C++(98)的异常rule,而并非异常rule。在D,Java甚至C++
> 0x这些具备gc的语言中,这些rule中的大多数都可以不用操心的。
即使有 gc, 但是, 异常发生的时候, 管理非内存资源的对象的资源释放函数没有
被即时释放的时候, 这个对象可能被某些指针指着不会释放; 或者已经没有指针指
着它了, 但是 gc 不高兴现在释放,或者就是不高兴释放它((think in java 里面
写着, 不能保证对象一定会被释放, D 里面也这样我, 保守gc的资料里面也这样
写.) 你怎么办 ?

因此, 有了 gc, 如果要求不是特别高, 日子还是好了很多, 起码多数要释放的是
内存资源, 问题不大, 一部分非内存资源也能随gc 释放, 但是不能保证全部都会
gc 释放. 要写很可靠的程序, 这个不能保证, 和没有这个功能, 似乎差得不是很远啊.

莫华枫

未读,
2007年10月26日 10:33:212007/10/26
收件人 pon...@googlegroups.com
这段代码么,呵呵,可以这样:如果你用raii,那么就可以把后面那个end:省掉。(raii对谁都有好处)。然后,直接往外扔异常,代码更加简洁可靠。规则就这么点,不复杂吧。
这里我要埋怨一下C++的几个大牛,写书的时候只顾炫技,搞得大家只见树木不见森林。其实,这些复杂的规则基本上都归结到一个解决方法,就是使用自动化的资源管理。记住这一个基础规则,剩下的就不多了。


在07-10-26,red...@gmail.com <red...@gmail.com> 写道:

pongba

未读,
2007年10月26日 10:33:352007/10/26
收件人 pon...@googlegroups.com
On 10/26/07, red...@gmail.com <red...@gmail.com> wrote:

鉴于你对使用返回值构造的条例主要针对的是资源管理,我也试试对使用异常的情况下如何正确管理资源制定条例:

1. 使用RAII来包装每一个资源(很多时候可以利用智能指针类来免于自己手写包装类)。
1'. 如果用D的话,在每一个资源申请下面加上相应的scope(exit)子句。

2.  析构函数不许抛出异常。

3. 运用copy-swap手法实现异常安全的copy-assignment操作符。

OK,我觉得最常见场景需要知道的essential rules就以上这些了。完全的Rules列表也并没有多少,《C++ Coding Standards》上面写得一清二楚。

yq chen

未读,
2007年10月26日 10:34:062007/10/26
收件人 pon...@googlegroups.com
      楼上的有道理,错误越早被发现越好,鸵鸟政策总是行不通的。
      返回值的很大一个缺陷就是对返回值检查不完全。另一个问题就是他严重干扰了happy path代码的编写流程,以及整个程序的结构。
       为了能够在失败的情况下也能够对资源进行适当的清理,你必须把所有变量都尽可能放到函数的开头。在C++中,这个不一定是好事情:)。而且整个函数必须有一个执行路径,没有借助RAII的机制时多半伴有一个或多个goto语句以及一个或多个label语句。
       尽管如此,百密一疏,系统还是会有没有处理的错误冒出来导致系统崩溃。

 
在07-10-26,莫华枫 <longsh...@gmail.com> 写道:

莫华枫

未读,
2007年10月26日 10:34:422007/10/26
收件人 pon...@googlegroups.com
那就靠确定性的raii呗,办法总是有的,都是自动资源管理嘛。

在07-10-26,red...@gmail.com <red...@gmail.com > 写道:

pongba

未读,
2007年10月26日 10:40:122007/10/26
收件人 pon...@googlegroups.com
On 10/26/07, red...@gmail.com <red...@gmail.com> wrote:
莫华枫 写道:


在07-10-26,red...@gmail.com <red...@gmail.com> 写道:
不是 catch 的问题, 而是 throw 和 catch 中间路径的代码, 是不是都是异常安全的, 这个检查起来比较费事.

这方面,异常的确复杂于返回码。但是,解决的方法却是相同的,即资源的自动化管理。这是一劳永逸的方法,对于两种形式都有作用。

  不仅仅是资源管理完毕了就好了啊, 例如, 我往你的 list 类里面插一个对象, 你的list 做了一些内部事情, 然后执行 = 赋值这个对象, 此时异常发生, 仅仅资源不泄漏, 但是内部状态改掉了.

  关键是异常是只要你调用了外部的函数就可能发生, 甚至一个操作符也可能发生, 时时刻刻都要注意, 同时规则又多, 我比较害怕.

这个... 正是Joel那篇文章里面的论点。问题是,如果是错误,终究还是错误,错误必然导致退出点,不管用异常还是错误代码,终究都是要时时刻刻关心一个函数是否抛出错误的。不同的是,用错误代码,可以容易的一眼看出程序员是否把错误检查放在心上了(是否有if(err...)语句)(但仍然不能就认为程序员一定做了正确的错误检查),用异常代码,需要检查上下文(比如是否用了RAII)来确保无论在哪个点上退出,前面的资源都能释放。

但两种情况下(不管是错误代码或异常),要想检查一个程序是否错误'安全,都少不了要hard check。

  (不重载操作符的日子, 要轻松一些, 容易知道, 这几行代码是不会跑个异常出来吓人一跳. )


莫华枫

未读,
2007年10月26日 10:40:472007/10/26
收件人 pon...@googlegroups.com
这些规则也是"多功能的"(C++多功能的东西还真不少:)),C++的其他很多缺陷都依赖于这些方法,比如资源安全问题,以及自我赋值问题等等。

在07-10-26,pongba <pon...@gmail.com> 写道:

red...@gmail.com

未读,
2007年10月26日 11:11:282007/10/26
收件人 pon...@googlegroups.com

> 鉴于你对使用返回值构造的条例主要针对的是资源管理,我也试试对使用异常的
> 情况下如何正确管理资源制定条例:
这是很多年前写的代码了, 那时还没有怎么用 RAII.
去年用 C++ 代码的时候, 就比较懒, 不管函数不好看, 直接在函数内部定义用于
RAII 的局部类.

如果返回值结合 RAII, 其实也不复杂啊, 用 RAII 管理数据, 发现返回值错误,
直接就返回自己的错误值, 不就可以了, 更简单.
>
> 1. 使用RAII来包装每一个资源(很多时候可以利用智能指针类来免于自己手写


> 包装类)。
> 1'. 如果用D的话,在每一个资源申请下面加上相应的scope(exit)子句。
>
> 2. 析构函数不许抛出异常。
>
> 3. 运用copy-swap手法实现异常安全的copy-assignment操作符。
>
> OK,我觉得最常见场景需要知道的essential rules就以上这些了。完全的Rules
> 列表也并没有多少,《C++ Coding Standards》上面写得一清二楚。

还有一条你没有实现啊, 就是错误发生的时候, 不要影响对象的状态.

用返回值规则的话, 只要调用完所有需要检查返回值的函数, 都没有返回值都通过
检查了, 接着就可以调用不需要检查返回值的函数, 以及直接修改持久性的变量了.

如果用异常的话, 你要一个一个确认某个函数会不会产生异常, 检查和对象有关的
每个操作符, 是否被重载掉了, 重载函数会不会抛异常, 才能决定应该如何写这段
代码, 检查工作多了很多啊.

(所以我不重载操作符, 我胆小).

red...@gmail.com

未读,
2007年10月26日 11:12:272007/10/26
收件人 pon...@googlegroups.com

> 鉴于你对使用返回值构造的条例主要针对的是资源管理,我也试试对使用异常的
> 情况下如何正确管理资源制定条例:
这是很多年前写的代码了, 那时还没有怎么用 RAII.
去年用 C++ 代码的时候, 就比较懒, 不管函数不好看, 直接在函数内部定义用于
RAII 的局部类.

如果返回值结合 RAII, 其实也不复杂啊, 用 RAII 管理数据, 发现返回值错误,
直接就返回自己的错误值, 不就可以了, 更简单.
>
> 1. 使用RAII来包装每一个资源(很多时候可以利用智能指针类来免于自己手写
> 包装类)。

> 1'. 如果用D的话,在每一个资源申请下面加上相应的scope(exit)子句。
>
> 2. 析构函数不许抛出异常。
>
> 3. 运用copy-swap手法实现异常安全的copy-assignment操作符。
>

> OK,我觉得最常见场景需要知道的essential rules就以上这些了。完全的Rules
> 列表也并没有多少,《C++ Coding Standards》上面写得一清二楚。

还有一条你没有实现啊, 就是错误发生的时候, 不要影响对象的状态.

用返回值规则的话, 只要调用完所有需要检查返回值的函数, 函数没有return
with err, 接着就可以调用不需要检查返回值的函数状态修改函数, 以及修改持久

pongba

未读,
2007年10月26日 11:18:022007/10/26
收件人 pon...@googlegroups.com
On 10/26/07, red...@gmail.com <red...@gmail.com> wrote:

> 鉴于你对使用返回值构造的条例主要针对的是资源管理,我也试试对使用异常的
> 情况下如何正确管理资源制定条例:
这是很多年前写的代码了, 那时还没有怎么用 RAII.
去年用 C++ 代码的时候, 就比较懒, 不管函数不好看, 直接在函数内部定义用于
RAII 的局部类.

如果返回值结合 RAII, 其实也不复杂啊, 用 RAII 管理数据, 发现返回值错误,
直接就返回自己的错误值, 不就可以了, 更简单.
>
> 1. 使用RAII来包装每一个资源(很多时候可以利用智能指针类来免于自己手写
> 包装类)。
> 1'. 如果用D的话,在每一个资源申请下面加上相应的scope(exit)子句。
>
> 2. 析构函数不许抛出异常。
>
> 3. 运用copy-swap手法实现异常安全的copy-assignment操作符。
>
> OK,我觉得最常见场景需要知道的essential rules就以上这些了。完全的Rules
> 列表也并没有多少,《C++ Coding Standards》上面写得一清二楚。

还有一条你没有实现啊, 就是错误发生的时候, 不要影响对象的状态.

这个就属于错误处理的本质困难了啊,没有时间机器。用错误代码,还是要面临这个问题啊。

用返回值规则的话, 只要调用完所有需要检查返回值的函数, 函数没有return
with err, 接着就可以调用不需要检查返回值的函数状态修改函数, 以及修改持久
性的变量了.

如果用异常的话, 你要一个一个确认某个函数会不会产生异常, 检查和对象有关的
每个操作符, 是否被重载掉了, 重载函数会不会抛异常, 才能决定应该如何写这段
代码, 检查工作多了很多啊.

用异常的话,先调用可能出错的函数,并在中间用scope(failure)来guard,然后调用no-throw的函数。这不 跟错误处理代码本质上一样的么?
但用scope(failure)或RAII的话,代码的scalability就好多咯。不会出现层层叠叠的if-else-if...结构。也不用goto。模式很简单,就是一个操作,一个guard。一个萝卜一个坑。

(所以我不重载操作符, 我胆小).

李扬

未读,
2007年10月26日 11:54:092007/10/26
收件人 pon...@googlegroups.com
在我的实践中:
 
重要的并不是用不用异常,而是怎么去用异常。
在c++这种异常规范实际等于圈圈的语言当中,你无法很直接的预测某个函数会抛出哪些异常。异常并不等于错误,或者程序的bug。在某些情况下,这些异常都是能够handle然后处理的。
很明显解析一个报文由于格式错误抛出的异常不应该让整个服务器当掉,在上层捕获这个异常然后打印一条日志然后继续。。。。
 
当我写的报文解析器要抛出不止一个异常的时候,应该怎么去捕获这些异常了?明显这些抛出的异常应该基于某个继承体系。只需要捕获基类异常就可以了。
在我定义的异常体系之外的异常,应该被看做程序的bug(或者是由于我们使用的某个库抛出的异常,这表明我们还没吃透这个库的用法)。。。。。那么让程序崩溃未尝不是一件好事。
 
 
异常被看作对某个函数调用的后置条件的保证应该是没有异议的
 
返回值方式的错误报告机制通常就是实现的这种功能:
  1. 如果返回值为真那么表明我这个函数满足了我声明的责任或者义务其他的就是你调用方的事情了和我无关了;
  2. 如果返回值为假,sorry我无法满足你的要求。。因为我遇到特殊情况了你还是另外想办法吧。
异常作为函数前置条件不满足时候的抗议机制也是一个不错的选择
  1. 通常你不满足我,我当然也不会满足你。而且为你引起你的重视还要小小的发发牢骚。
  2. 至于怎样发牢骚那就是仁者见仁的事情了,也许assert一把或者抛出个异常。(我倾向于后者)
  3. 当然我相信你不会故意和我为难(通常你都是因为小小的开了个小差或者没有仔细读懂我给你的协议才会犯这个错误的,还是要惩罚下你),所以这种异常一般还是不要捕获,如果你捕获了而又不满足我------那只能证明你有病(java的异常声明,某些情况下就是这种有病的表现)。
  4. 我反对assert。。一个明显的理由----并不是每个人都习惯于debug版到release版的过程(曾经有人给我说他从不debug版,因为gcc没得debug版的概念)。
 
综上所述,我为什么不用异常了,我找不到理由。异常可以干比返回错误值更多的事情,而且还干得更好。对于对前置条件的检测来说错误代码的方式本质上来说是无能为力的---试想当你面对一个有十几个参数windows api 通过GetLastError 来查错的时候,你会是怎样一个心情。(你会反复的想这个到底是我参数传错了啊,还是这个设备本来就没有准备好?你说有错误代码,可以去msdn。。那我就无语了。。。碰到模凌两可的解释到处遇到,代码永远比文更真实反映意图,当然前提是你能够准确的表达意图。从这个角度讲,我对通过c++异常来表达我的意图还是有自信的)
 
至于异常安全问题,我相信这不只是c++异常的问题或者异常的问题,错误代码方式一样存在。(操作原子性问题,资源释放问题等等)相对来说由于c++有RAII处理这些问题还来得优雅得多。相较于c代码中的一堆if else..我还是比较喜欢c++的方式--唯一的问题的是你必须适应一种惯用手法。相较于它的优点来说这不是大问题--只要不是太多:) 再说没有习惯用法的语言好像还没有出现吧,呵呵。
 
再鄙视下java。丑陋的try catch finally 。。。。。C#有using还好些。
在07-10-26,red...@gmail.com <red...@gmail.com> 写道:

pongba

未读,
2007年10月26日 12:20:022007/10/26
收件人 pon...@googlegroups.com
你的这个正是andrei在<C++ Coding Standard>中倡导的做法:-)

abware

未读,
2007年10月26日 12:29:102007/10/26
收件人 TopLanguage
人肉GC,百密难免一疏。而且团队里的人水平参差不齐,真正做的时候更难。

SevenCat

未读,
2007年10月26日 12:41:162007/10/26
收件人 TopLanguage
最主要还是判断什么时候该用,什么时候不该用。

整个软件架设在异常的出错机制基础上确实有时候比较爽,但由此带来的复杂性也不算少。

使用异常,你必须能自己控制异常,而且对异常和各种状态都有所理解,但使用错误,不需要考虑太多的,最多是些机械的劳动而已。

我们调用的库本身也各种风格,小组内成员也有可能水平参差不齐,强制使用错误返回能省不少的事情。

当然要是自信自己所在的团队能驾预异常,那也未尝不可。我认为异常并不适合所有的情况,我们得权衡和根据实际情况作出选择。

wang xin

未读,
2007年10月26日 12:50:422007/10/26
收件人 pon...@googlegroups.com
人肉GC比之人肉compiler,难度还是要稍小一些
在团队中使用异常,就需要大部分人都具有人肉compiler的实力;而使用error code,则只需要小部分的人肉GC……

不可否认的是,异常比之error code,在理论上有着无以匹敌的优势,但实际应用中推广难度太大
还是一句话,这个行业没有那么多天才,甚至,没有那么多合格的人才

"坐而论道"人人会做,"广而告之"就要难一些了,至于"谈笑皆鸿儒、往来无白丁"的年代,我看近期内是看不到了
就以我们现在的讨论来说,能够影响到的人又能有多少呢?

在07-10-27,abware <abw...@gmail.com> 写道:

Atry

未读,
2007年10月26日 12:55:432007/10/26
收件人 pon...@googlegroups.com
我还是坚定的认为只有异常规范才是解决办法。没有异常规范的函数,调用起来总是提心吊胆的。

在07-10-26,pongba <pon...@gmail.com> 写道:
首先,我坚定的认为应该用异常,应该使用异常作为错误报告的唯一机制。哦,实际上,不是我,是Brad Abrams(《.NET框架设计指南》作者),Anders(C#之父),Andrei Alexandrescu(《C++ Coding Standard: 101 Rules》作者)这么说的。

其次,国内的C++开发者估计都知道,在绝大多数C++项目中,异常都是被禁用的,至于是不是连编译选项都关掉了还是只是在编码规定上禁止,就不知道了(你那边的实际情况是什么?:-)),据说就连Google内部也是禁用异常的。所以估计情况还不仅仅局限于国内,甚至地球上的C++项目大抵都是如此。

然而不可否认的一个事实是,C++社群这么多年来的经验告诉我们,使用异常(具体参见《C++ Coding Standard: 101 Rules》第7x章~第7y章(x=?和y=?我忘了))。Herb Sutter和Andrei Alexandrescu在C++异常方面的经验和造诣估计C++社群还没有几个人能够质疑的。

生下来的问题就很简单了:为什么存在这个理想与现实的矛盾?这就是我想请大家讨论的:P

个人一点看法:

1. Fallacies和Myths导致的Fear心态,进而导致在异常的正确实践方式方面的标准缺乏(《C++ Coding Standard: 101 Rules》出版之前,你能找到一份哪怕比较标准的,权威的,关于如何,何时使用异常的参考文献吗?)

2. 旧时代异常实现手段的落后(g++的影栈(shadow stack)实现)以及硬件的落后(导致异常开销*相对*显得大),致使在当时,异常被打入冷宫。由于人在心理上容易忘记一个论点的前提而记住其结论,所以若干年后的今天,仍然有大量人认为异常的开销在任何情况下都很大很显著。而实际情况是,异常有开销,但仅当在极度追求效率(时间&空间)的场合(极少),这个开销是完全可以忽略的。

3. 教育问题。异常在C++里面是一个较新的特性。而且,使用错误代码,不需要新学习语言特性,只要在现有语言特性(返回值)上hack就行了。不得不承认,就连程序员也是害怕学新东西的。

4. 把错误处理的本质困难错误地归咎到异常身上。错误处理的本质困难是:定义哪些情况属于错误,错误发生的时候在什么地点响应,错误发生如何确保操作的完整语意(回滚、保存数据、释放资源)。无论用异常还是错误代码,这些问题都同样尖锐。

5. 更多重量级的Fallacies:详见Raymond Chen(《The Old New Thing》作者,Win32大牛)和Joel Spolsky(《Joel On Software》作者)的口水。Raymond Chen的" Cleaner, more elegant, and harder to recognize ",和Joel的两篇,这里 这里。要知道,这两位的口水的权重可不是一般的大啊。幸亏人民群众的眼睛是雪亮的,Raymond和Joel的论点乍看十分、非常有理,实际经不起推敲。你能看出里面的漏洞吗?

6. 遗留代码问题。遗留代码里面没有用异常,导致往遗留系统里面增加新模块也首先与原先系统里用的错误处理策略的限制(这一点我不是很确定,有实际经验的可以明确的举例说明一下?)

7. 心理问题。大量C++程序员有C背景(也许是因为教材是从C往上写的也许其他什么原因),从心里就没有把异常当成一个first-class的,专门的,唯一的错误处理机制。Java通过把人们逼上梁山解决了这个心理问题。而对于C++程序员来说,也许下意识里面最"熟悉"的就是用返回值来报错;至于找出一堆理由...well...只是找出一堆理由而已。能找出一堆经得起推敲的理由,那才是真的理由。不过,对自己找的理由从来都不需要推敲的。

最想知道的就是大家经历的实际项目中的情况,越明确,越实际越好:-) 我似乎把空话套话都给说完了:P
--
刘未鹏(pongba)|C++的罗浮宫

pongba

未读,
2007年10月26日 15:11:002007/10/26
收件人 pon...@googlegroups.com


On 10/27/07, SevenCat <BastE...@gmail.com> wrote:
最主要还是判断什么时候该用,什么时候不该用。
 
那具体而言到底什么时候不该用呢?

 
整个软件架设在异常的出错机制基础上确实有时候比较爽,但由此带来的复杂性也不算少。
 
具体又是哪些复杂性呢?

 
使用异常,你必须能自己控制异常,而且对异常和各种状态都有所理解,但使用错误,不需要考虑太多的,最多是些机械的劳动而已。
 
不懂.. 在我看来,使用异常也只是机械劳动.而错误处理的几个本质问题则跟用异常还是错误代码根本没关系.
何谓"控制异常"和"对各种状态有所理解",能否详细?

 
我们调用的库本身也各种风格,小组内成员也有可能水平参差不齐,强制使用错误返回能省不少的事情。
 
这倒是实情.此外还有一个实情就是,STL是使用异常作为标准错误报告手段的.
另,具体省哪些事情?

当然要是自信自己所在的团队能驾预异常,那也未尝不可。我认为异常并不适合所有的情况,我们得权衡和根据实际情况作出选择。
 
还是那个问题:具体权衡哪些方面?
 

pongba

未读,
2007年10月26日 15:36:172007/10/26
收件人 pon...@googlegroups.com
我总结一下,目前最主要的论点是cber和redsea的,认为错误代码更容易检查正确性,异常代码不容易检查.
 
我不同意 :-) 观点如下:
 
On 10/27/07, wang xin <cber.w...@gmail.com> wrote:
人肉GC比之人肉compiler,难度还是要稍小一些
在团队中使用异常,就需要大部分人都具有人肉compiler的实力;而使用error code,则只需要小部分的人肉GC……
 
On 10/26/07, red...@gmail.com < red...@gmail.com> wrote:
不是 catch 的问题, 而是 throw 和 catch 中间路径的代码, 是不是都是异常安全的, 这个检查起来比较费事.

用错误代码的话,检查起来同样费事。

因为"检查了每个返回值"并不代表"做到了错误安全",而这个正是Raymond的那篇post犯的错误,比如:
BOOL ComputeChecksum(LPCTSTR pszFile, DWORD* pdwResult)
{
BOOL fRc = FALSE;
HANDLE h = CreateFile(pszFile, GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (h != INVALID_HANDLE_VALUE) {
HANDLE hfm = CreateFileMapping(h, NULL, PAGE_READ, 0, 0, NULL);
if (hfm) {
void *pv = MapViewOfFile(hfm, FILE_MAP_READ, 0, 0, 0);
if (pv) {
DWORD dwHeaderSum;
if (CheckSumMappedFile(pvBase, GetFileSize(h, NULL),
&dwHeaderSum, pdwResult)) {
fRc = TRUE;
}
UnmapViewOfFile(pv);
}
CloseHandle(hfm);
}
CloseHandle(h);
}
return fRc;
}
这段代码做到错误安全了吗?不管是人眼,还是工具,看到这段代码唯一能够得出的结论是:写代码的人*想要*做到错误安全。然而真正重要的问题却是"代码*实际*是否是错误安全的",后者能够用工具check出来吗?

说到底只能靠开发者自己来保证。

check工具只能促使开发者*试图*去做到错误安全,跟真正做到错误安全是两码事.后者取决于开发者的责任心和编码素养.
这就使得check工具实际上的作用微乎其微,不是吗?
 
此外,关于错误代码or异常的决策并不能仅看一个方面,还要看其他的异常的优点,做全面的权衡.一旦往全面了考虑,异常的优点是显而易见的.
 
但我也的确认为,尤其是在C++里面,我怀疑10个有9个根本不知道异常的最佳实践是什么,也许程序员的合格性才是真正的问题,人的问题.其实C++异常的最佳实践也不复杂,为什么会出现这个问题可能还是要归结为教育问题.而教育问题估计又要归结为业界长期的谬误流传.C++当初加入异常几乎是主流语言第一个吃螃蟹的,退缩心理自然不会少.口口相传,就形成了不健康的亚文化.我得承认,我自己接触C++这么长时间,也从来没有觉得错误处理是个重要的问题.(估计就算是其他主流语言的程序员也是如此).
话说回来,10个有9个半不会用异常(这里不存在"有点会用"这个灰色区域,错误处理要么成功要么失败,有点会用也是白搭),但如果用错误代码呢?实际上每个人都会.就是if/else,控制流而已.所以这里真正的问题是"大家都不会用",而不是"这个工具不好".

red...@gmail.com

未读,
2007年10月26日 21:29:482007/10/26
收件人 pon...@googlegroups.com
>
>
> 用异常的话,先调用可能出错的函数,并在中间用scope(failure)来guard,然
> 后调用no-throw的函数。这不跟错误处理代码本质上一样的么?
> 但用scope(failure)或RAII的话,代码的scalability就好多咯。不会出现层层
> 叠叠的if-else-if...结构。也不用 goto。模式很简单,就是一个操作,一个
> guard。一个萝卜一个坑。
>

这种goto用法我并不觉得有多少不好, 我不求代码 goto-free.

再考虑这个问题:

如果由于程序库或者业务发展的需要,
1. 返回值方式: 某个原来没有返回值的函数, 现在有返回值了
2. 异常方式: 某个原来自己不throw, 调用的函数自己也不throw 的函数, 现在会
throw 了

看看我们应该怎么处理:

1. 错误值方式
全局 search 所有调用该函数的地方, 在每个调用, 它处于 "最后做安全的事情"
的那部分, 那么应该移到前面 "可能失败" 的部分, 并且检查返回值.

有可能某个调用者, 原来不返回值, 现在它自己处理不了这个错误的话, 那么也要
改成返回值. 这个事情的处理是递归的.

如果它被用来赋值给某个函数指针, 那么现在这个赋值应该编译不过, 需要修改函
数指针定义, 再递归处理函数指针的调用.

函数指针的调用者的递归处理

如果某个多态函数, 基类函数中没有返回值, 这不应该抛出异常, 那么只要任何派
生类函数调用了上面说到的函数, 而自己又不能处理掉错误, 那么这套函数就应该
变成有返回值, 将基类函数返回值改掉, 同时该函数如果不是abstract virtual,
那么临时修改成 abstract virtual 编译一下, 能够将所有需要修改的点给你找出来.

所有这些调用者的递归处理.

2. 异常方式
nothrow 现在不是 c++ 标准吧? 异常抛出规范现在也好像用处也不大, 我们全部
不管这些.
全局 search 所有调用该函数的地方, 这个和上面一样.
如果某个调用者, 原来是不抛出异常的, 现在抛出异常了, 需要递归检查, 这个也
和上面一样.

对于函数指针, 要进行同样的检查, 不过这次编译器不能提醒你了.

还有一个编译器提醒不了的地方:

如果某个多态函数, 基类函数中写明, 这不应该抛出异常, 那么只要任何派生类函
数调用了上面说到的函数, 而自己又不能处理掉异常, 那么这套函数就应该变成会
抛出异常的函数, 所有调用者代码需要改掉. 这下没有什么帮助可以迅速找出所有
要修改的点来了,只有全局 search 了. 希望没有哪个地方, 通过模板参数传进这
个基类, 然后参数传递给另外一个模板, 在后者的函数当中, 派生了这个类 (这个
用法听起来很变态, 但是我写一些辅助功能的小代码的时候, 确实用过).


比较起来, 返回值方式是很直白, 代码不漂亮, 不过用自动化工具确实容易处理很多.

------------------------------------
哎, 写代码还是累啊, 光是一个返回值/异常 throw 的修改, 后面搞不好就是一大
堆工作.


red...@gmail.com

未读,
2007年10月26日 22:22:272007/10/26
收件人 pon...@googlegroups.com
嗯, 我也是这种看法.

本来要写出合格健壮的程序来(无论是哪个语言), 已经不容易了, 要高清楚业务领
域的事情, 要搞清楚底层平台的时候, 如果语言再给加上一些随时要记住的规则,
多一条都很累人的.

莫华枫 写到建筑业的规则大家都是记得住的. 我倒是觉得, 其实我见过很多建筑
都有很多毛病, 整体的外观设计对内部居住的某些方面也有不良影响, 如果换到软
件行业, 肯定是需要 ---- 重构的.

wang xin 写道:

abware

未读,
2007年10月27日 03:37:582007/10/27
收件人 TopLanguage
同意,说到底还是人的问题。
会用异常的人都去做Manager了,不会用的人去编码。
而股东们考虑的是降低成本,至于做出来的产品,能卖得出去就行。软件这个东西,真的是很软。

On Oct 27, 3:36 am, pongba <pon...@gmail.com> wrote:
> 我总结一下,目前最主要的论点是cber和redsea的,认为错误代码更容易检查正确性,异常代码不容易检查.
>
> 我不同意 :-) 观点如下:
>

> On 10/27/07, wang xin <cber.wang...@gmail.com> wrote:
>
>
>
> > 人肉GC比之人肉compiler,难度还是要稍小一些

> > 在团队中使用异常,就需要大部分人都具有人肉compiler的实力;而使用error code,则只需要小部分的人肉GC......


>
> On 10/26/07, red...@gmail.com <red...@gmail.com> wrote:
>
>
>
> > 不是 catch 的问题, 而是 throw 和 catch 中间路径的代码, 是不是都是异常安全的, 这个检查起来比较费事.
>
> 用错误代码的话,检查起来同样费事。
>
> 因为"检查了每个返回值"并不代表"做到了错误安全",而这个正是Raymond的那篇post犯的错误,比如:
>
> BOOL ComputeChecksum(LPCTSTR pszFile, DWORD* pdwResult)
> {
> BOOL fRc = FALSE;
> HANDLE h = CreateFile(pszFile, GENERIC_READ, FILE_SHARE_READ,
> NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
>
> if (h != INVALID_HANDLE_VALUE) {
> HANDLE hfm = CreateFileMapping(h, NULL, PAGE_READ, 0, 0, NULL);
> if (hfm) {
> void *pv = MapViewOfFile(hfm, FILE_MAP_READ, 0, 0, 0);
> if (pv) {
> DWORD dwHeaderSum;
> if (CheckSumMappedFile(pvBase, GetFileSize(h, NULL),
> &dwHeaderSum, pdwResult)) {
> fRc = TRUE;
> }
> UnmapViewOfFile(pv);
> }
> CloseHandle(hfm);
> }
> CloseHandle(h);
> }
> return fRc;
>
> }
>

> 这段代码做到错误安全了吗?不管是人眼,还是工具,看到这段代码唯一能够得出的结论是:写代码的人*想要*做到错误安全。然而真正重要的问题却是"代码*实际* 是否是错误安全的",后者能够用工具check出来吗?

oldrev

未读,
2007年10月27日 04:23:252007/10/27
收件人 pon...@googlegroups.com
认为用返回值比异常能够更好地保证错误安全其实和认为用静态语言比动态语言开
发的程序质量高一样是不可信的,唯一可信的是测试。

在 2007-10-27六的 00:37 -0700,abware写道:

--

Live Long and Prosper
- oldrev

李扬

未读,
2007年10月27日 07:20:362007/10/27
收件人 pon...@googlegroups.com
redsea 写道:
2. 异常方式: 某个原来自己不throw, 调用的函数自己也不throw 的函数, 现在会
throw 了
 
异常本质上就应该这样。你所依赖的库的行为都发生变化了。虽然接口没有变化:)那么还是通知你比较好。
获得异常的调用堆栈还是比较容易的。。。(只是没有像java或者c#那样能够把调用堆栈打印到标准输出?或者有这样的方法我还不晓得,呵呵就因为这样写java的人都不用调试器了。)
 


 
在07-10-27,oldrev <old...@gmail.com> 写道:
> > 际上每个人都会.就是if /else,控制流而已.所以这里真正的问题是"大家都不会用",而不是"这个工具不好".

wang xin

未读,
2007年10月27日 08:19:152007/10/27
收件人 pon...@googlegroups.com
对,有了充分的测试,即便不用异常又如何
"异常",顾名思义,就是不正常的情况,unhappy path在一个程序的运行中出现的概率实在是太低了(当然,如果频率高的话,就可以用一条happy path来取代它);再说了,又不是所有的程序都要求24 X 7,像我们开发的那个server程序,即便用户高达4xxx家(不乏一些要求很苛刻的用户,如:美国海军、DoD等……),依然有若干fatal error运行程序崩溃。

在07-10-27,oldrev <old...@gmail.com> 写道:
> > 此外,关于错误代码or异常的决策并不能仅看一个方面,还要看其他的异常的优点, 做全面的权衡.一旦往全面了考虑,异常的优点是显而易见的.
> >
> > 但我也的确认为,尤其是在C++里面,我怀疑10个有9个根本不知道异常的最佳实践是什么,也许程序员的合格性才是真正的问题,人的问题.其实C++
> > 异常的最佳实践也不复杂,为什么会出现这个问题可能还是要归结为教育问题.而教育问题估计又要归结为业界长期的谬误流传.C++
> > 当初加入异常几乎是主流语言第一个吃螃蟹的, 退缩心理自然不会少.口口相传,就形成了不健康的亚文化.我得承认
> > ,我自己接触C++这么长时间,也从来没有觉得错误处理是个重要的问题.(估计就算是其他主流语言的程序员也是如此).
> > 话说回来,10个有9个半不会用异常(这里不存在"有点会用"这个灰色区域,错误处理要么成功要么失败,有点会用也是白搭),但如果用错误代码呢?实
> > 际上每个人都会.就是if /else,控制流而已.所以这里真正的问题是"大家都不会用",而不是"这个工具不好".

> >
> > --
> > 刘未鹏(pongba)|C++的罗浮宫http://blog.csdn.net/pongba
> > TopLanguagehttp://groups.google.com/group/pongba
>
> >
--

Live Long and Prosper
- oldrev




--
Everything is possible and available if we trust ourselves!

yq chen

未读,
2007年10月28日 08:33:482007/10/28
收件人 pon...@googlegroups.com
话也可以反过来说:有了充分的测试,使用异常又如何?
关键是有没有充分的测试?

 
在07-10-27,wang xin <cber.w...@gmail.com> 写道:

wang xin

未读,
2007年10月28日 08:51:372007/10/28
收件人 pon...@googlegroups.com
测试是在编码之后进行的,使用异常带来的结果就是编码过程中的难度变大,而用error code就没有这个问题了,即便是水平一般的程序员,也可以很简单的把程序给调试通过并提交给测试用例
如果编码结束后,使用异常当然就没有什么了,何况之后使用异常比用error code还好

在07-10-28,yq chen <mephist...@gmail.com> 写道:
话也可以反过来说:有了充分的测试,使用异常又如何?
关键是有没有充分的测试?

 
在07-10-27,wang xin <cber.w...@gmail.com > 写道:
对,有了充分的测试,即便不用异常又如何
"异常",顾名思义,就是不正常的情况,unhappy path在一个程序的运行中出现的概率实在是太低了(当然,如果频率高的话,就可以用一条happy path来取代它);再说了,又不是所有的程序都要求24 X 7,像我们开发的那个server程序,即便用户高达4xxx家(不乏一些要求很苛刻的用户,如:美国海军、DoD等……),依然有若干fatal error运行程序崩溃。

在07-10-27,oldrev < old...@gmail.com > 写道:
认为用返回值比异常能够更好地保证错误安全其实和认为用静态语言比动态语言开
发的程序质量高一样是不可信的,唯一可信的是测试。

lijie

未读,
2007年10月28日 09:15:012007/10/28
收件人 pon...@googlegroups.com
这个问题好像可以像berkeleydb(c++)和mysql++这样,在构造对象时指定是否使用异常。

测试时使用错误码来判断,使用时改用异常,因为这个异常就是从错误码转换过来的。

在07-10-28,wang xin < cber.w...@gmail.com> 写道:

莫华枫

未读,
2007年10月28日 09:53:412007/10/28
收件人 pon...@googlegroups.com
只要测试完整,案例充分,那么,即便正确没有被正确捕捉,也能够立刻反应出来(程序退出或崩溃)。但是error code如果没有被正确捕捉,除非检测所有相关状态和结果,否则无法发现。
为什么会认为"水平一般"的程序员无法胜任异常处理?java、C#程序员在平均编程能力上无法与C++的程序员相比,但他们依然能够应付异常,只是处理的好坏而已(异常保证)。
在C++里保证异常安全相对困难些,但是只要遵照pongba给出的三条半准则,(没有比这更简单了吧),依然可以很容易地使用异常。(请注意,其中第三条是为了获得强烈保证,如只需获得基本保证,也就是error code一般情况下所做的,只需前头两条半即可,比error code所需的要求更少)。
同时,异常使得软件的结构更清晰,更接近于业务模型。我观察下来,很多初入行的程序员(我身边的都是C#),很自然地接受了异常,在处理异常的时候,也没有任何困难。C++新手或许难一些,但是只要记住pongba的三条半(至少两条半),也就不在话下了。
即便是一个团队中都是习惯于老方式的C++程序员,那么推广和使用异常也不是件难事,毕竟都已经是老鸟了。关键在于观念。请记住,一个团队永远不可能"自己"形成足够的能力,团队是需要建设的。


在07-10-28,wang xin <cber.w...@gmail.com> 写道:
测试是在编码之后进行的,使用异常带来的结果就是编码过程中的难度变大,而用error code就没有这个问题了,即便是水平一般的程序员,也可以很简单的把程序给调试通过并提交给测试用例

pongba

未读,
2007年10月29日 02:40:482007/10/29
收件人 pon...@googlegroups.com
On 10/27/07, wang xin <cber.w...@gmail.com> wrote:
对,有了充分的测试,即便不用异常又如何
"异常",顾名思义,就是不正常的情况,unhappy path在一个程序的运行中出现的概率实在是太低了(当然,如果频率高的话,就可以用一条happy path来取代它);再说了,又不是所有的程序都要求24 X 7,像我们开发的那个server程序,即便用户高达4xxx家(不乏一些要求很苛刻的用户,如:美国海军、DoD等……),依然有若干fatal error运行程序崩溃。

对了,我对这个现象很有兴趣。
你说"依然有若干fatal error运行程序崩溃",我想知道的是,这个崩溃是像"非法访问"那样的"被动"崩溃,还是check到一个不可恢复的错误状态然后abort()那样的"主动"崩溃?

就让我假定是前者吧(如果猜错了,纠正我:-))。如果是前者,那么几乎肯定是程序早在崩溃点之前就已经发生了错误状态感染了,为什么在当即点上没有check出错误并abort()?跟错误处理机制有关吗?我们知道的是,如果一个应用自底向上使用异常来报告错误,那么任何错误都能够在第一时间用异常抛出去,之后一直到遇到catch子句,是不会被动崩溃的(假定stack-unwinding不会抛出新异常——这是必须遵循的编码原则)。那么如果我们再一个功能模块的顶层用一个general catch来abort模块,便可以做到在该模块中的任何错误都是主动崩溃,而不是被动崩溃。

这个逻辑对不对?也就是说,我的意思是,如果用异常的话,理想情况下是不会出现nasty的"你的程序执行了非法操作,windows将关闭它"这样的错误的,取而代之的是体面的提示对话框,因为在general catch子句中,程序仍然在可用状态。

wang xin

未读,
2007年10月29日 02:48:222007/10/29
收件人 pon...@googlegroups.com


在07-10-29,pongba <pon...@gmail.com> 写道:


On 10/27/07, wang xin < cber.w...@gmail.com> wrote:
对,有了充分的测试,即便不用异常又如何
"异常",顾名思义,就是不正常的情况,unhappy path在一个程序的运行中出现的概率实在是太低了(当然,如果频率高的话,就可以用一条happy path来取代它);再说了,又不是所有的程序都要求24 X 7,像我们开发的那个server程序,即便用户高达4xxx家(不乏一些要求很苛刻的用户,如:美国海军、DoD等……),依然有若干fatal error运行程序崩溃。

对了,我对这个现象很有兴趣。
你说"依然有若干fatal error运行程序崩溃",我想知道的是,这个崩溃是像"非法访问"那样的"被动"崩溃,还是check到一个不可恢复的错误状态然后abort()那样的"主动"崩溃?

唉,这个产品的source code太多,我还不熟悉;目前知道的就是,在定义错误代码的文件中,有一个段是用来定义fatal error的,自己还从来没有往这个段添加过东西,也没有注意他们在什么地方被使用;我猜应该是被被下面所说的SEH给抓一下然后log,接着退出吧
就目前的一点点经验来看,貌似在win下面,他们利用了SEH,在xNix那边可能就用了signal(我猜的),反正no std::exception,no stl……

就让我假定是前者吧(如果猜错了,纠正我:-))。如果是前者,那么几乎肯定是程序早在崩溃点之前就已经发生了错误状态感染了,为什么在当即点上没有check出错误并abort()?跟错误处理机制有关吗?我们知道的是,如果一个应用自底向上使用异常来报告错误,那么任何错误都能够在第一时间用异常抛出去,之后一直到遇到catch子句,是不会被动崩溃的(假定stack-unwinding不会抛出新异常——这是必须遵循的编码原则)。那么如果我们再一个功能模块的顶层用一个general catch来abort模块,便可以做到在该模块中的任何错误都是主动崩溃,而不是被动崩溃。

这个逻辑对不对?也就是说,我的意思是,如果用异常的话,理想情况下是不会出现nasty的"你的程序执行了非法操作,windows将关闭它"这样的错误的,取而代之的是体面的提示对话框,因为在general catch子句中,程序仍然在可用状态。

对话框这个东西我很久没有看过了(H)

--
刘未鹏(pongba)|C++的罗浮宫
http://blog.csdn.net/pongba
TopLanguage

red...@gmail.com

未读,
2007年10月29日 02:55:512007/10/29
收件人 pon...@googlegroups.com

>
> 对了,我对这个现象很有兴趣。
> 你说"依然有若干fatal error运行程序崩溃",我想知道的是,这个崩溃是像"非
> 法访问"那样的"被动"崩溃,还是check到一个不可恢复的错误状态然后abort()
> 那样的"主动"崩溃?
>
> 就让我假定是前者吧(如果猜错了,纠正我:-))。如果是前者,那么几乎肯定
> 是程序早在崩溃点之前就已经发生了错误状态感染了,为什么在当即点上没有
> check出错误并abort()?
这个问题很大了, 如果每个出错点都可以立刻check 出来, 那么目前的软件代码质
量就明显要高很大一截了.

莫华枫

未读,
2007年10月29日 03:24:272007/10/29
收件人 pon...@googlegroups.com
在使用异常的情况下,这些是完全可以做到的。在一般的应用环境中,(.net、java、vc等C++编译器等),一个异常一旦被抛出,如果没有被捕捉,会有一个程序级,甚至系统级的默认catch将其捕捉,同时显示出来,甚至打印出环境状态。
即便没有这么一个默认catch,也仅仅需要在最主要的入口,比如main中补充一个catch(...),便可以捕捉所有漏网之鱼,然后给出相应的提示或反馈。
这也就决定了异常是"测试友好"的。除非测试漏掉了相应的代码路径,否则,只要有问题,异常总会以最强烈的方式通知你。反观error code,即便是测试经过了该路径,如果没有进入调试代码,检测数据状态,根本无法知道是否遗漏了那个error code。
我曾经非常害怕异常,总是竭力回避。因为有时异常导致的系统提示看起来就像非法访问那样。现在明白,这仅仅是心理作用。异常实际上是对我的保护,异常发生时,程序很可能还处于可挽回的状态,而使得程序避免出现非法访问这种不可恢复的错误。


在07-10-29, red...@gmail.com <red...@gmail.com> 写道:

pongba

未读,
2007年10月29日 03:30:532007/10/29
收件人 pon...@googlegroups.com
错误发生的来源只有两种:一,外界输入。二,内部编程逻辑错误(如边界条件等)。
OOSC(《面向对象软件构造》,Meyer)中提到,用契约式编程,并在违反任何契约的地方抛出异常,从本质上就可以将一切错误在其源头用异常抛出。
加上stack-unwinding过程中不抛出新异常。
这不就做到了?

其中的困难在于,要enforce契约式编程,需要严格的编码素质。但这跟错误代码的对比好处就在于,不用手动传播错误,也不会产生错误被忽视。注意,这里的重点是,无需任何人力,就省去了"确保错误没被忽视(要review来确保)并被传播"这一环的所有工夫。

为什么程序会被动崩溃,我的推理是就是因为没有在错误的发生点上立即抛出异常从而使执行流不可能带着错误感染状态往下执行。《Why Programs Fail》里面也提到,大量错误在发生的时候早就离发生点十万八千里了,结果就要一系列的推导。我的认为是,如果按照OOSC的方法,一旦违反契约便以异常方式抛出,就不存在这个问题了。

这个逻辑正确吗?实际有多可行呢?

莫华枫

未读,
2007年10月29日 03:41:402007/10/29
收件人 pon...@googlegroups.com
手工实现契约比较麻烦,也不容易保证完整性,特别在修改代码的过程中。自动维持似乎需要语言的支持。但是,我觉得相比解决的问题,付出的代价还是值得的。(每当我想到搅乱了客户的数据,而无法恢复,便会毛骨悚然,手脚冰凉)。:)

在07-10-29,pongba < pon...@gmail.com> 写道:

red...@gmail.com

未读,
2007年10月29日 03:47:232007/10/29
收件人 pon...@googlegroups.com
本质上啊 ? 这是理论的话啊.

错误的来源, 没错, 种类不多, 但是错误的后果呢? 不一定就是修改了本类的状态啊, 如果是将另外一个对象错误修改了呢 ? (最恶劣难检查的是指针错误, 很多时候都是改掉别人的东西.)

甚至检查代码本身都有可能写错, 本该不修改东西的, 但万一写错了, 修改了本对象或者其他对象的状态, 又如何呢 ?

又怎么保证一个类用几个 invariant, 加上几个 pre, post, 就可以 inplace 发现所有的错误呢 ?

pongba 写道:
错误发生的来源只有两种:一,外界输入。二,内部编程逻辑错误(如边界条件等)。
OOSC(《面向对象软件构造》,Meyer)中提到,用契约式编程,并在违反任何契约的地方抛出异常,从本质上就可以将一切错误在其源头用异常抛出。
加上stack-unwinding过程中不抛出新异常。
这不就做到了?

其中的困难在于,要enforce契约式编程,需要严格的编码素质。但这跟错误代码的对比好处就在于,不用手动传播错误,也不会产生错误被忽视。注意,这 里的重点是,无需任何人力,就省去了"确保错误没被忽视(要review来确保)并被传播"这一环的所有工夫。

为什么程序会被动崩溃,我的推理是就是因为没有在错误的发生点上立即抛出异常从而使执行流不可能带着错误感染状态往下执行。《Why Programs Fail》里面也提到,大量错误在发生的时候早就离发生点十万八千里了,结果就要一系列的推导。我的认为是,如果按照OOSC的方法,一旦违反契约便以异 常方式抛出,就不存在这个问题了。

red...@gmail.com

未读,
2007年10月29日 03:47:372007/10/29
收件人 pon...@googlegroups.com
没有 catch 的异常当然会有最强烈的方式通知你.

但是, 可能有一个你内部已经 catch 的异常(从而你也没有对它多害怕), 它的路
径中引发了内存泄漏或者资源泄漏, 这个除非你有别的手段, 否则你用这个测试方
法, 对这个问题没有什么帮助.

对于我现在写的程序而言, 静悄悄地引发资源泄漏的危险, 和返回值没有检查的危
险是差不多的, 都是不可靠. 而返回值没有被正确处理更容易看出来.

代码质量分析工具我们也没有. 我妹妹在一个美国软件公司做, 他们用一个外部公
司的软件来分析代码质量, 对软件分析出的可疑点, 都要重点地做代码review.


莫华枫 写道:

pongba

未读,
2007年10月29日 03:52:132007/10/29
收件人 pon...@googlegroups.com
啊,这就是彻底的怀疑论了 ;P
关键是程度问题。如果有N种方法,每种都不完美,都不绝对保险。那只有问一个保险系数的问题啊。
如果一个方法归结到最后成为人的问题,几乎肯定都是不保险的。所以自动化工具才极其重要。只可惜目前软件开发中的大问题还真就归结到人身上。那么,哪种方法对于人来说更能可靠地执行呢?是契约式+异常,还是review+错误代码?

莫华枫

未读,
2007年10月29日 03:52:332007/10/29
收件人 pon...@googlegroups.com
保证使用raii管理资源,就无须担心资源泄漏。

在07-10-29,red...@gmail.com <red...@gmail.com> 写道:

red...@gmail.com

未读,
2007年10月29日 03:59:212007/10/29
收件人 pon...@googlegroups.com
呵呵, 去年我是坚定的 RAII 用户.

今年的代码, C++ 只完成少量工作, 大部分的工作是 C 的, 偷懒了 :) ---- 反正
返回值方式都免不了.

看来, 屁股决定脑袋, 做的事情决定了思维的方式.

我眼下不是一个典型 C++ 用户, 发言无效 :)

莫华枫 写道:
> 保证使用raii管理资源,就无须担心资源泄漏。
>

李扬

未读,
2007年10月29日 04:03:192007/10/29
收件人 pon...@googlegroups.com
呵呵。按照readsea的那种说法,我觉得就算不用异常一样的问题多多。异常不是万能。如果异常能够避免程序员所有的错误那么我们的程序员日子就好过了。
但是,在异常和错误代码之间的选择。我还是倾向于在纯粹的c++环境中(不需要跨边界)还是异常好些。
如果我改了个指针。是否错误代码能够想外面提供一个此指针作废的提示了?即使有,我也会马上不用这个人写的代码。最安全的方式是在函数中恢复调用前的状态。这个问题根本不关错误代码或者异常的问题---归根结底是一个编程素质的问题。工具能够帮你检查的是在你有源代码的情况下,没源代码你怎么办?如果恰好你忘记check这个修改了指针的函数。。。。可想而知,,当程序崩溃的时候,你就也崩溃了。这个时候也许栈都被改得一塌糊涂了,离错误发生地远了十万八千里。

 
在07-10-29,莫华枫 <longsh...@gmail.com> 写道:

red...@gmail.com

未读,
2007年10月29日 04:03:472007/10/29
收件人 pon...@googlegroups.com
其他一个 project 中, C++ 代码和 C 代码混合工作的人, 不知道是采用什么方式的多 ?

red...@gmail.com 写道:

red...@gmail.com

未读,
2007年10月29日 04:11:532007/10/29
收件人 pon...@googlegroups.com
这个说法是由于

1. 异常抛出的时候, 当前执行路径中, 如果有些代码是非 RAII的, 很容易就造成静悄悄的资源泄漏,
2. 我现在的一些程序 (混合了C), 还就没有用 RAII, 所以我觉得异常很危险.

我的做法应该不是典型, 够勤劳用返回值检查的人, 应该够勤劳在 C++, C 边界上用 expt --> error, error -->expt, 然后用 RAII.

ignore me.


李扬 写道:
呵呵。按照readsea的那种说法,我觉得就算不用异常一样的问题多多。异常不是万能。如果异常能够避免程序员所有的错误那么我们的程序 员日子就好过了。
但是,在异常和错误代码之间的选择。我还是倾向于在纯粹的c++环境中(不需要跨边界)还是异常好些。
如果我改了个指针。是否错误代码能够想外面提供一个此指针作废的提示了?即使有,我也会马上不用这个人写的代码。最安全的方式是在函数中恢 复调用前的状态。这个问题根本不关错误代码或者异常的问题---归根结底是一个编程素质的问题。工具能够帮你检查的是在你有源代码的情况下,没源代码你怎 么办?如果恰好你忘记check这个修改了指针的函数。。。。可想而知,,当程序崩溃的时候,你就也崩溃了。这个时候也许栈都被改得一塌糊涂了,离错误发 生地远了十万八千里。

 
在07-10-29,莫 华枫 <longsh...@gmail.com> 写道:
保 证使用raii管理资源,就无须担心资源泄漏。

red...@gmail.com

未读,
2007年10月29日 04:19:262007/10/29
收件人 pon...@googlegroups.com
排除掉目前做的程序对我的影响, 之前我对异常的态度应该是:

1. 这玩意好难缠啊, 可能会在任何一个地方冒出来, 一个 = 都可能被打断
2. 代码倒是清晰了很多
3. 现在不用也不行啊, 这么多库都用它

那就用它吧, 尽量用得好点.

至于错误提醒之类, 老早老早写程序就会有一个窗口(即使是gui project, 程序开
始的时候也会自行创建一个 console 窗口), 发现什么重要的错误, 都即时
output 出来, 不用异常也能及早发现有问题的地方.

pongba

未读,
2007年10月29日 04:30:412007/10/29
收件人 pon...@googlegroups.com
On 10/29/07, red...@gmail.com <red...@gmail.com> wrote:
排除掉目前做的程序对我的影响, 之前我对异常的态度应该是:

1. 这玩意好难缠啊, 可能会在任何一个地方冒出来, 一个 = 都可能被打断

这.. 如果一个地方是会出错的,那么就是会出错的,不管用异常还是用错误代码,总是要面对的。用错误代码的话,"="就是一次函数调用,不仍然还是要check错误返回值嘛。

莫华枫

未读,
2007年10月29日 04:36:102007/10/29
收件人 pon...@googlegroups.com
redsee啊,咱们争了那么多,我开始相信你对异常的态度是有道理的。倒不是我要跳票,而是我觉得你的观点在你所面临的业务特点中是最恰当的。:)
从以往的帖子里可以发现,你的业务偏重于实时,甚至有些类似嵌入。这种地方对于C++而言不很擅长(相比C),或者说缺少可靠的证明。或许在这种领域中,全功能的C++显得笨重,反而不如C来得实惠。所以,应用异常的时候,资源管理比较麻烦。这就限制了异常在你们项目中的应用异常。我想你们是没法用智能指针的吧。
从我的业务领域来看,没有你那么受限,所以用起异常来要容易得多。所以我并没有觉得异常有多大的问题。
咱们是不是在瞎子摸象啊?:P

在07-10-29,red...@gmail.com < red...@gmail.com> 写道:

red...@gmail.com

未读,
2007年10月29日 04:42:202007/10/29
收件人 pon...@googlegroups.com
pongba 写道:


On 10/29/07, red...@gmail.com <red...@gmail.com> wrote:
排 除掉目前做的程序对我的影响, 之前我对异常的态度应该是:


1. 这玩意好难缠啊, 可能会在任何一个地方冒出来, 一个 = 都可能被打断

这.. 如果一个地方是会出错的,那么就是会出错的,不管用异常还是用错误代码,总是要面对的。用错误代码的话,"="就是一次函数调用,不仍然还是要check 错误返回值嘛。



不能用异常的话, 就不会有人去设计可能会出错的 = 了 :)



red...@gmail.com

未读,
2007年10月29日 05:43:302007/10/29
收件人 pon...@googlegroups.com
莫华枫 写道:
> redsee啊,咱们争了那么多,我开始相信你对异常的态度是有道理的。倒不是我

> 要跳票,而是我觉得你的观点在你所面临的业务特点中是最恰当的。:)
> 从以往的帖子里可以发现,你的业务偏重于实时,甚至有些类似嵌入。这种地方
> 对于C++而言不很擅长(相比C),或者说缺少可靠的证明。或许在这种领域中,
> 全功能的C++显得笨重,反而不如C来得实惠。所以,应用异常的时候,资源管理
> 比较麻烦。
我目前的多数项目, 内存资源充足(但是不能泄露), CPU 能力有时候比较紧张.
但是异常的开销这个问题, 其实不是主要问题, 我不会去用一个 getByKey() 不存
在就抛出异常的 hashmap 这种变态库, 异常还是可以当作发生得少的情况的.

讨论下来, 我才发现我有一个好玩的现象了: 如果我用 STL, 我就会倾向于用更多
的 C++ 特性, RAII, 异常等;
如果我要不用STL, 又要写底层的操作, 例如创建一个共享内存区, 创建一个FIFO,
我就会很自然地使用 C style.
> 这就限制了异常在你们项目中的应用异常。我想你们是没法用智能指针的吧。
智能指针的同步开销在用得多的对象中无法承受, 性能要求不高的地方, 可能又用
了 python 了, 所有智能指针真的用得少. 但是用过一个 "手工智能指针", 就是
自己 addref, release 控制生存期的指针, 那是一个多个线程都会访问的
session 型数据结构, 但是几个线程都有明确的开始使用和使用完毕的点; 每次复
制 addref, release 开销太高, 手工控制成本就低了.

> 从我的业务领域来看,没有你那么受限,所以用起异常来要容易得多。所以我并
> 没有觉得异常有多大的问题。
> 咱们是不是在瞎子摸象啊?:P
哈哈 :)

oldrev

未读,
2007年10月29日 06:45:552007/10/29
收件人 pon...@googlegroups.com
不用异常的理由千头万绪,归根结底还是一句话:C++的异常不好用。理由是:
1) C++的异常算然形式看起来很自由,代价却是埋伏下了若干的陷井,唯一避免的
办法是背熟 More Effective C++ 那一整章 :)
2) RAII 在语言级支持得不够,反正是不方便。
3) 异常没有ABI规范,所以连 COM 也只能用返回值

在 2007-10-26五的 19:32 +0800,pongba写道:
> 首先,我坚定的认为应该用异常,应该使用异常作为错误报告的唯一机制。哦,
> 实际上,不是我,是Brad Abrams(《.NET框架设计指南》作者),Anders(C#
> 之父),Andrei Alexandrescu(《C++ Coding Standard: 101 Rules》作者)
> 这么说的。
>
> 其次,国内的C++开发者估计都知道,在绝大多数C++项目中,异常都是被禁用
> 的,至于是不是连编译选项都关掉了还是只是在编码规定上禁止,就不知道了
> (你那边的实际情况是什么?:-)),据说就连Google内部也是禁用异常的。所
> 以估计情况还不仅仅局限于国内,甚至地球上的C++项目大抵都是如此。
>
> 然而不可否认的一个事实是,C++社群这么多年来的经验告诉我们,使用异常
> (具体参见《C++ Coding Standard: 101 Rules》第7x章~第7y章(x=?和y=?我
> 忘了))。Herb Sutter和Andrei Alexandrescu在C++异常方面的经验和造诣估
> 计C++社群还没有几个人能够质疑的。
>
> 生下来的问题就很简单了:为什么存在这个理想与现实的矛盾?这就是我想请大
> 家讨论的:P
>
> 个人一点看法:
>
> 1. Fallacies和Myths导致的Fear心态,进而导致在异常的正确实践方式方面的
> 标准缺乏(《C++ Coding Standard: 101 Rules》出版之前,你能找到一份哪怕
> 比较标准的,权威的,关于如何,何时使用异常的参考文献吗?)
>
> 2. 旧时代异常实现手段的落后(g++的影栈(shadow stack)实现)以及硬件的落
> 后(导致异常开销*相对*显得大),致使在当时,异常被打入冷宫。由于人在心
> 理上容易忘记一个论点的前提而记住其结论,所以若干年后的今天,仍然有大量
> 人认为异常的开销在任何情况下都很大很显著。而实际情况是,异常有开销,但
> 仅当在极度追求效率(时间&空间)的场合(极少),这个开销是完全可以忽略
> 的。
>
> 3. 教育问题。异常在C++里面是一个较新的特性。而且,使用错误代码,不需要
> 新学习语言特性,只要在现有语言特性(返回值)上hack就行了。不得不承认,
> 就连程序员也是害怕学新东西的。
>
> 4. 把错误处理的本质困难错误地归咎到异常身上。错误处理的本质困难是:定
> 义哪些情况属于错误,错误发生的时候在什么地点响应,错误发生如何确保操作
> 的完整语意(回滚、保存数据、释放资源)。无论用异常还是错误代码,这些问
> 题都同样尖锐。
>
> 5. 更多重量级的Fallacies:详见Raymond Chen(《The Old New Thing》作
> 者,Win32大牛)和Joel Spolsky(《Joel On Software》作者)的口水。
> Raymond Chen的"Cleaner, more elegant, and harder to recognize",和Joel
> 的两篇,这里和这里。要知道,这两位的口水的权重可不是一般的大啊。幸亏人
> 民群众的眼睛是雪亮的,Raymond和Joel的论点乍看十分、非常有理,实际经不
> 起推敲。你能看出里面的漏洞吗?
>
> 6. 遗留代码问题。遗留代码里面没有用异常,导致往遗留系统里面增加新模块
> 也首先与原先系统里用的错误处理策略的限制(这一点我不是很确定,有实际经
> 验的可以明确的举例说明一下?)
>
> 7. 心理问题。大量C++程序员有C背景(也许是因为教材是从C往上写的也许其他
> 什么原因),从心里就没有把异常当成一个first-class的,专门的,唯一的错
> 误处理机制。Java通过把人们逼上梁山解决了这个心理问题。而对于C++程序员
> 来说,也许下意识里面最"熟悉"的就是用返回值来报错;至于找出一堆理
> 由...well...只是找出一堆理由而已。能找出一堆经得起推敲的理由,那才是真
> 的理由。不过,对自己找的理由从来都不需要推敲的。
>
> 最想知道的就是大家经历的实际项目中的情况,越明确,越实际越好:-) 我似乎
> 把空话套话都给说完了:P


> --
> 刘未鹏(pongba)|C++的罗浮宫
> http://blog.csdn.net/pongba
> TopLanguage
> http://groups.google.com/group/pongba
> >

pongba

未读,
2007年10月29日 06:53:502007/10/29
收件人 pon...@googlegroups.com


On 10/29/07, oldrev <old...@gmail.com> wrote:
不用异常的理由千头万绪,归根结底还是一句话:C++的异常不好用。理由是:

Negative :)

1) C++的异常算然形式看起来很自由,代价却是埋伏下了若干的陷井,唯一避免的
办法是背熟 More Effective C++ 那一整章 :)

ME的那一章写得根本不好。看看BS的TC++PL相关章节和《C++ Coding Standard》足够了。真正需要记得的重要条例根本不多。

2) RAII 在语言级支持得不够,反正是不方便。

现实是,RAII很好。C++的RAII是所有主流语言里面最漂亮的error-safe设施。但问题是RAII还不够,因为有时候人们并不想费事去写一个RAII类。D的scope(exit)/scope(failure)/scope(success)实在是一个太漂亮的补充。

3) 异常没有ABI规范,所以连 COM 也只能用返回值

从实践者的角度来说,这不是什么大问题。在模块边界转换为error-code即可。但话说回来,ABI的缺失的确很令人恼火(但并不足以成为不用异常的理由)。

wave

未读,
2007年10月29日 07:28:122007/10/29
收件人 TopLanguage
试图在不同模块之间传递异常是个挺让人头疼的问题。
尤其是由不同语言实现的模块。
一般设计良好的模块可以减少跨模块的错误传递,但当无法避免时,我觉得使用错误代码优于异常。


On 10月26日, 下午7时32分, pongba <pon...@gmail.com> wrote:
> 首先,我坚定的认为应该用异常,应该使用异常作为错误报告的唯一机制。哦,实际上,不是我,是Brad

> Abrams(《.NET框架设计指南》作者),Anders(C#之父),Andrei Alexandrescu(《C++ Coding


> Standard: 101 Rules》作者)这么说的。
>

> 其次,国内的C++开发者估计都知道,在绝大多数C++项目中,异常都是被禁用的,至于是不是连编译选项都关掉了还是只是在编码规定上禁止,就不知道了(你那边的实际情况是什么?:-)),据说就连Google内部也是禁用异常的。所以估计情况还不仅仅局限于国内,甚至地球上的C++项目大抵都是如此。


>
> 然而不可否认的一个事实是,C++社群这么多年来的经验告诉我们,使用异常(具体参见《C++ Coding Standard: 101
> Rules》第7x章~第7y章(x=?和y=?我忘了))。Herb Sutter和Andrei
> Alexandrescu在C++异常方面的经验和造诣估计C++社群还没有几个人能够质疑的。
>
> 生下来的问题就很简单了:为什么存在这个理想与现实的矛盾?这就是我想请大家讨论的:P
>
> 个人一点看法:
>

> 1. Fallacies和Myths导致的Fear心态,进而导致在异常的正确实践方式方面的标准缺乏(《C++ Coding Standard: 101
> Rules》出版之前,你能找到一份哪怕比较标准的,权威的,关于如何,何时使用异常的参考文献吗?)


>
> 2. 旧时代异常实现手段的落后(g++的影栈(shadow
> stack)实现)以及硬件的落后(导致异常开销*相对*显得大),致使在当时,异常被打入冷宫。由于人在心理上容易忘记一个论点的前提而记住其结论,所以若干年后的今天,仍然有大量人认为异常的开销在任何情况下都很大很显著。而实际情况是,异常有开销,但仅当在极度追求效率(时间&空间)的场合(极少),这个开销是完全可以忽略的。
>
> 3.

> 教育问题。异常在C++里面是一个较新的特性。而且,使用错误代码,不需要新学习语言特性,只要在现有语言特性(返回值)上hack就行了。不得不承认,就连程序员也是害怕学新东西的。


>
> 4.
> 把错误处理的本质困难错误地归咎到异常身上。错误处理的本质困难是:定义哪些情况属于错误,错误发生的时候在什么地点响应,错误发生如何确保操作的完整语意(回滚、保存数据、释放资源)。无论用异常还是错误代码,这些问题都同样尖锐。
>

> 5. 更多重量级的Fallacies:详见Raymond Chen(《The Old New Thing》作者,Win32大牛)和Joel


> Spolsky(《Joel On Software》作者)的口水。Raymond Chen的"Cleaner, more elegant, and

> harder to recognize<http://blogs.msdn.com/oldnewthing/archive/2005/01/14/352949.aspx>
> ",和Joel的两篇,这里 <http://www.joelonsoftware.com/items/2003/10/13.html>和这里<http://www.joelonsoftware.com/items/2003/10/15.html>
> 。要知道,这两位的口水的权重可不是一般的大啊。幸亏人民群众的眼睛是雪亮的,Raymond和Joel的论点乍看十分、非常有理,实际经不起推敲。你能看出里面的漏洞吗?
>
> 6.
> 遗留代码问题。遗留代码里面没有用异常,导致往遗留系统里面增加新模块也首先与原先系统里用的错误处理策略的限制(这一点我不是很确定,有实际经验的可以明确的举例说明一下?)
>
> 7.
> 心理问题。大量C++程序员有C背景(也许是因为教材是从C往上写的也许其他什么原因),从心里就没有把异常当成一个first-class的,专门的,唯一的错误处理机制。Java通过把人们逼上梁山解决了这个心理问题。而对于C++程序员来说,也许下意识里面最"熟悉"的就是用返回值来报错;至于找出一堆理由...well...只是找出一堆理由而已。能找出一堆经得起推敲的理由,那才是真的理由。不过,对自己找的理由从来都不需要推敲的。


>
> 最想知道的就是大家经历的实际项目中的情况,越明确,越实际越好:-) 我似乎把空话套话都给说完了:P

莫华枫

未读,
2007年10月29日 07:48:102007/10/29
收件人 pon...@googlegroups.com

在07-10-29,red...@gmail.com <red...@gmail.com> 写道:
莫华枫 写道:
> redsee啊,咱们争了那么多,我开始相信你对异常的态度是有道理的。倒不是我
> 要跳票,而是我觉得你的观点在你所面临的业务特点中是最恰当的。:)
> 从以往的帖子里可以发现,你的业务偏重于实时,甚至有些类似嵌入。这种地方
> 对于C++而言不很擅长(相比C),或者说缺少可靠的证明。或许在这种领域中,
> 全功能的C++显得笨重,反而不如C来得实惠。所以,应用异常的时候,资源管理
> 比较麻烦。
我目前的多数项目, 内存资源充足(但是不能泄露), CPU 能力有时候比较紧张.
但是异常的开销这个问题, 其实不是主要问题, 我不会去用一个 getByKey() 不存
在就抛出异常的 hashmap 这种变态库, 异常还是可以当作发生得少的情况的.

讨论下来, 我才发现我有一个好玩的现象了: 如果我用 STL, 我就会倾向于用更多
的 C++ 特性, RAII, 异常等;
如果我要不用STL, 又要写底层的操作, 例如创建一个共享内存区, 创建一个FIFO,
我就会很自然地使用 C style.

这也自然,不用stl,想要raii就得自己写,麻烦。

> 这就限制了异常在你们项目中的应用异常。我想你们是没法用智能指针的吧。
智能指针的同步开销在用得多的对象中无法承受, 性能要求不高的地方, 可能又用
了 python 了, 所有智能指针真的用得少. 但是用过一个 "手工智能指针", 就是
自己 addref, release 控制生存期的指针, 那是一个多个线程都会访问的
session 型数据结构, 但是几个线程都有明确的开始使用和使用完毕的点; 每次复
制 addref, release 开销太高, 手工控制成本就低了.

这就是了,不用智能指针资源安全没法保证,毕竟标准容器不能当智能指针用啊。所以,异常安全就困难了。

> 从我的业务领域来看,没有你那么受限,所以用起异常来要容易得多。所以我并
> 没有觉得异常有多大的问题。
> 咱们是不是在瞎子摸象啊?:P
哈哈 :)



莫华枫

未读,
2007年10月29日 22:31:262007/10/29
收件人 pon...@googlegroups.com
再问redsea一个问题,如果用D了,你会用异常吗?

在07-10-29,莫华枫 <longsh...@gmail.com> 写道:

red...@gmail.com

未读,
2007年10月29日 23:22:182007/10/29
收件人 pon...@googlegroups.com
我用 C++ 的时候也会用异常啊, 当然用 D 也会用异常.

一个函数, 如果我觉得他碰到的错误, 高层很有可能可以修复的, 我多半会选择用
错误值来返回, 如果他碰到的错误我觉得很严重, 多半是修复不了的, 我就会用异
常; 处于两种之间, 难以判断的, 我会倾向于用错误值.

而且, D 的 scope 不仅是对一场处理有帮助, 对错误值的处理同样也是有帮助的,
可以在函数级别的 scope(exit) 中做释放处理, 这样, 判断出错误的代码, 就可
以立即 return 错误值.

前天看 tango 的 hashmap, 刚开始看漏了, 以为 get (bykey) 的那个函数, 只有
一个版本, 就是找不到 key 就抛异常, 当时心里就想骂娘了, 这不是要让我自己
copy 再改一份吗? 这里的代码, 每秒钟十万个查询, 大多数都是找不到的.

然后同事提醒我, 下面还有一个版本, 是错误值返回的, 心里立刻给tango 库作者
道歉: 别人还不是那么笨的. ----- 我觉得写基础库的时候, 这种会影响到性能的
api, 两个版本都提供比较好; 库的用户更加知道, 用错误值更好, 还是用异常更
好. 如果只提供一个版本, 我则宁愿要错误值的, 需要的时候再转异常, 没有不能
接受的开销.

莫华枫 写道:
> 再问redsea一个问题,如果用D了,你会用异常吗?
>

oldrev

未读,
2007年10月29日 23:49:512007/10/29
收件人 pon...@googlegroups.com
恩,D的运行时库和标准库广泛使用异常,客户程序不用也不行了。

OT: redsea 用的 scim吧?这东东老是把“一场”放在“异常”前面,呵呵。


在 2007-10-30二的 11:22 +0800,red...@gmail.com写道:

oldrev

未读,
2007年10月29日 23:49:582007/10/29
收件人 pon...@googlegroups.com
恩,D的运行时库和标准库广泛使用异常,客户程序不用也不行了。

OT: redsea 用的 scim吧?这东东老是把“一场”放在“异常”前面,呵呵。


在 2007-10-30二的 11:22 +0800,red...@gmail.com写道:

red...@gmail.com

未读,
2007年10月30日 00:21:502007/10/30
收件人 pon...@googlegroups.com

> 恩,D的运行时库和标准库广泛使用异常,客户程序不用也不行了。
>
> OT: redsea 用的 scim吧?这东东老是把“一场”放在“异常”前面,呵呵。
>
>
>

哈, 之前用 linux desktop 的时候, 倒是用 scim 的, 现在用 windows desktop,
用的是google 拼音, yichang 输入了许多次, 现在总算在前面了, 其实他应该可
以分析出不同的用户的行业, 提供不同的次序的.

用回 windows desktop 几个主要原因是:

1. source insight, linux 下的 source navigator 还是太差
2. windows 跑在虚拟机里面的速度太慢了, 但是经常碰到需要 IE 检查 web
page, 所以还是 windows host, vmware 跑linux 好些.

pongba

未读,
2007年10月30日 02:05:332007/10/30
收件人 pon...@googlegroups.com

这个场景太有启发性了,呵呵:-)

lijie

未读,
2007年10月30日 22:34:392007/10/30
收件人 pon...@googlegroups.com
刚发现gcc有个括展, 可以对返回值没有检查予以警告。

int foo() __wur;

int foo()
{
    return 0;
}

int main()
{
    foo();
}

> gcc -o testwur testwur.c -D_FORTIFY_SOURCE=2 -O2
testwur.c: In function 'main':
testwur.c:12: warning: ignoring return value of 'foo', declared with attribute warn_unused_result

一般来说,只要不忽略警告,正确处理它,就能避免返回值没有被检查的问题。如果只是接受返回值而不处理,则会有另一个警告:变量未使用。至于是否正确处理了返回值,这是逻辑相关的,编译器没办法做这件事。

在07-10-28,莫华枫 <longsh...@gmail.com> 写道:
只要测试完整,案例充分,那么,即便正确没有被正确捕捉,也能够立刻反应出来(程序退出或崩溃)。但是error code如果没有被正确捕捉,除非检测所有相关状态和结果,否则无法发现。
为什么会认为"水平一般"的程序员无法胜任异常处理?java、C#程序员在平均编程能力上无法与C++的程序员相比,但他们依然能够应付异常,只是处理的好坏而已(异常保证)。
在C++里保证异常安全相对困难些,但是只要遵照pongba给出的三条半准则,(没有比这更简单了吧),依然可以很容易地使用异常。(请注意,其中第三条是为了获得强烈保证,如只需获得基本保证,也就是error code一般情况下所做的,只需前头两条半即可,比error code所需的要求更少)。
同时,异常使得软件的结构更清晰,更接近于业务模型。我观察下来,很多初入行的程序员(我身边的都是C#),很自然地接受了异常,在处理异常的时候,也没有任何困难。C++新手或许难一些,但是只要记住pongba的三条半(至少两条半),也就不在话下了。
即便是一个团队中都是习惯于老方式的C++程序员,那么推广和使用异常也不是件难事,毕竟都已经是老鸟了。关键在于观念。请记住,一个团队永远不可能"自己"形成足够的能力,团队是需要建设的。


在07-10-28,wang xin <cber.w...@gmail.com > 写道:
测试是在编码之后进行的,使用异常带来的结果就是编码过程中的难度变大,而用error code就没有这个问题了,即便是水平一般的程序员,也可以很简单的把程序给调试通过并提交给测试用例
如果编码结束后,使用异常当然就没有什么了,何况之后使用异常比用error code还好

在07-10-28,yq chen <mephist...@gmail.com> 写道:
话也可以反过来说:有了充分的测试,使用异常又如何?
关键是有没有充分的测试?

 
在07-10-27,wang xin <cber.w...@gmail.com > 写道:
对,有了充分的测试,即便不用异常又如何
"异常",顾名思义,就是不正常的情况,unhappy path在一个程序的运行中出现的概率实在是太低了(当然,如果频率高的话,就可以用一条happy path来取代它);再说了,又不是所有的程序都要求24 X 7,像我们开发的那个server程序,即便用户高达4xxx家(不乏一些要求很苛刻的用户,如:美国海军、DoD等……),依然有若干fatal error运行程序崩溃。

在07-10-27,oldrev < old...@gmail.com > 写道:
认为用返回值比异常能够更好地保证错误安全其实和认为用静态语言比动态语言开
发的程序质量高一样是不可信的,唯一可信的是测试。
--
Everything is possible and available if we trust ourselves!

redsea(肖海彤)

未读,
2007年10月30日 22:40:082007/10/30
收件人 pon...@googlegroups.com
这个扩展看来有用, 什么 fopen 之类的加上, 再加上警告当错误的哪个编译选项, 想忽略都不行了.

在07-10-31,lijie <cpu...@gmail.com> 写道:

redsea(肖海彤)

未读,
2007年10月30日 22:45:402007/10/30
收件人 pon...@googlegroups.com
在很有必要检查返回值的函数后面加上 -wur
gcc 编译选项再加上
 
-W -Wall -Wpointer-arith -Wno-unused-parameter -Wno-unused-function -Wunused-variable -Wunused-value -Werror
 
这样, 可以帮助发现不少问题.

 
在07-10-31,redsea(肖海彤) <red...@gmail.com> 写道:

lijie

未读,
2007年10月30日 22:51:072007/10/30
收件人 pon...@googlegroups.com
stdio.h里面很多函数都加了这个,无意间从fwrite上发现的。

在07-10-31,redsea(肖海彤) <red...@gmail.com> 写道:

wing fire

未读,
2007年10月30日 23:35:162007/10/30
收件人 pon...@googlegroups.com
从诸多理论文章来看,异常确实比返回码好。返回码不仅仅是占用了本来有意义返回的返回值,而且,混淆了错误和返回值。本质上,返回值是可以忽略的,而错误不能。异常恰好开辟了一个新的信道返回错误,并且不可忽略。但是,什么是错误,什么是返回值是不能脱离设计意图来谈的,但我们常常并不提及这一点。从而认为,让返回值返回错误代码和普通的结果从形式上来说,是一样的,但这种思路这将导致接口定义的含糊。
从实践上来说,要想正确运用异常,首先是离不开contract.异常的抛出条件可以通过契约来形式化的定义, 从而避免了凭经验决定是否抛异常。其次是强保证。强保证是事务性的,只有强保证,才可能让异常安全穿越而不必捕获不关心的异常, 对于template,通常还可以选择异常中立。这一点常常被忽略。基本保证基本无用,除非可以在更高层次全部撤销其效果, 但这种做法不大可能比返回值方式更简单和高效。事实上,即便考虑到效率因素,基本上也能做到强保证。从我的经验来看,接口定义不良导致无法实现强保证的概率大的多。
异常问题说到底是和设计紧密相关的,错误码的设计不可能稍加修改或不加修改就用异常替换掉。正好昨天review代码,有个很好的例子。
一个uuid类的方法setByString,可以接受字符串,并以二进制方式存储在对象中.很显然,这里的字符串可能格式错误,从而无法处理。原来的设计是,不正确的字符串就抛出异常。这样也确实可以工作。但是问题是,用户代码并没有uuid相关的知识,因此也无从判断字符串是否合法,setByString的happy-path就是处理字符串,却把非法字符串作为异常。因此,这种设计本身就不合理,而不是异常有问题。想象一下,编译器把语法错误当作异常处理会怎样?
修改的建议有两种:
1. setByString返回bool值,成功返回true,失败返回false.这是用错误码来处理,这里很合适。
2. 提供一个validate方法用来验证字符串。setByString的pre_condition要求 validate(str_uuid)为真。用户就可以保证调用setByString的字符串一定合法。
这样,在setByString中可以去掉validate部分,可以运行的更快。而validate的责任交给用户代码。
从我的经历来看,说异常不好用,主要原因是思想还没转过弯来导致的滥用。其实返回码也一样有滥用问题,历史原因造成的大家忍受程度比较高而已。

在07-10-31,redsea(肖海彤) <red...@gmail.com > 写道:

red...@gmail.com

未读,
2007年10月31日 00:23:272007/10/31
收件人 pon...@googlegroups.com
有道理.

再结合我的实践, 继续总结一下:
1. 一种特别一点的事情发生了, 只有上层才能知道, 这属于可预料的常规情况, 还是少见的错误情况, 如果是错误, 属于recoverable or unrecoverable 的, 也是上层才知道

2. 一个api, 如果其直接上层使用者很少, 那么特别一些的情况发生, 属于可预料的常规/还是错误, 很有可能编写本 api 的时候, 情况已经知道了, 统一一起进行设计应该效果更好

3. 一个api, 如果是个通用的 api, 各种上层用户代码很多, 那么无法预计别人怎么看自己返回的 "特别情况", 此时, 怎么选择? 我是这样做的: 对性能很有可能有影响的代码(调用次数多, "特别情况" 出现的几率不小), 肯定是用返回值; 否则就根据 "特别情况" 可能的特别性, 决定用返回值还是异常.



wing fire 写道:
从诸多理论文章来看,异常确实比返回码好。返回码不仅仅是占用了本来有意义返回的返回值,而且,混淆了错误和返回值。本质上, 返回值是可以忽略的,而错误不能。异常恰好开辟了一个新的信道返回错误,并且不可忽略。但是,什么是错误,什么是返回值是不能脱离设计意图来谈的,但我们 常常并不提及这一点。从而认为,让返回值返回错误代码和普通的结果从形式上来说,是一样的,但这种思路这将导致接口定义的含糊。

从实践上来说,要想正确运用异常,首先是离不开contract.异常的抛出条件可以通过契约来形式化的定义, 从而避免了凭经验决定是否抛异常。其次是强保证。强保证是事务性的,只有强保证,才可能让异常安全穿越而不必捕获不关心的异常, 对于template,通常还可以选择异常中立。这一点常常被忽略。基本保证基本无用,除非可以在更高层次全部撤销其效果, 但这种做法不大可能比返回值方式更简单和高效。事实上,即便考虑到效率因素,基本上也能做到强保证。从我的经验来看,接口定义不良导致无法实现强保证的概 率大的多。
异常问题说到底是和设计紧密相关的,错误码的设计不可能稍加修改或不加修改就用异常替换掉。正好昨天review代码,有个很好的例子。
一个uuid类的方法setByString,可以接受字符串,并以二进制方式存储在对象中.很显然,这里的字符串可能格式错误,从而无法处理。原来的设 计是,不正确的字符串就抛出异常。这样也确实可以工作。但是问题是,用户代码并没有uuid相关的知识,因此也无从判断字符串是否合法, setByString的happy-path就是处理字符串,却把非法字符串作为异常。因此,这种设计本身就不合理,而不是异常有问题。想象一下,编译 器把语法错误当作异常处理会怎样?
修改的建议有两种:
1. setByString返回bool值,成功返回true,失败返回false.这是用错误码来处理,这里很合适。
2. 提供一个validate方法用来验证字符串。setByString的pre_condition要求 validate(str_uuid)为真。用户就可以保证调用setByString的字符串一定合法。
这样,在setByString中可以去掉validate部分,可以运行的更快。而validate的责任交给用户代码。
从我的经历来看,说异常不好用,主要原因是思想还没转过弯来导致的滥用。其实返回码也一样有滥用问题,历史原因造成的大家忍受程度比较高而已。

在07-10-31,redsea (肖海彤) <red...@gmail.com > 写道:
这 个扩展看来有用, 什么 fopen 之类的加上, 再加上警告当错误的哪个编译选项, 想忽略都不行了.

在07-10-31,lijie <cpu...@gmail.com> 写道:
刚 发现gcc有个括展, 可以对返回值没有检查予以警告。

pongba

未读,
2007年10月31日 00:58:512007/10/31
收件人 pon...@googlegroups.com


On 10/31/07, wing fire <wing...@gmail.com> wrote:
从诸多理论文章来看,异常确实比返回码好。返回码不仅仅是占用了本来有意义返回的返回值,而且,混淆了错误和返回值。本质上,返回值是可以忽略的,而错误不能。异常恰好开辟了一个新的信道返回错误,并且不可忽略。但是,什么是错误,什么是返回值是不能脱离设计意图来谈的,但我们常常并不提及这一点。从而认为,让返回值返回错误代码和普通的结果从形式上来说,是一样的,但这种思路这将导致接口定义的含糊。
从实践上来说,要想正确运用异常,首先是离不开contract.异常的抛出条件可以通过契约来形式化的定义, 从而避免了凭经验决定是否抛异常。其次是强保证。强保证是事务性的,只有强保证,才可能让异常安全穿越而不必捕获不关心的异常, 对于template,通常还可以选择异常中立。这一点常常被忽略。基本保证基本无用,除非可以在更高层次全部撤销其效果, 但这种做法不大可能比返回值方式更简单和高效。事实上,即便考虑到效率因素,基本上也能做到强保证。从我的经验来看,接口定义不良导致无法实现强保证的概率大的多。
异常问题说到底是和设计紧密相关的,错误码的设计不可能稍加修改或不加修改就用异常替换掉。正好昨天review代码,有个很好的例子。
一个uuid类的方法setByString,可以接受字符串,并以二进制方式存储在对象中.很显然,这里的字符串可能格式错误,从而无法处理。原来的设计是,不正确的字符串就抛出异常。这样也确实可以工作。但是问题是,用户代码并没有uuid相关的知识,因此也无从判断字符串是否合法,setByString的happy-path就是处理字符串,却把非法字符串作为异常。因此,这种设计本身就不合理,而不是异常有问题。想象一下,编译器把语法错误当作异常处理会怎样?
修改的建议有两种:
1. setByString返回bool值,成功返回true,失败返回false.这是用错误码来处理,这里很合适。
2. 提供一个validate方法用来验证字符串。setByString的pre_condition要求 validate(str_uuid)为真。用户就可以保证调用setByString的字符串一定合法。
这样,在setByString中可以去掉validate部分,可以运行的更快。而validate的责任交给用户代码。
从我的经历来看,说异常不好用,主要原因是思想还没转过弯来导致的滥用。其实返回码也一样有滥用问题,历史原因造成的大家忍受程度比较高而已。
有道理。尤其是最后的三点建设性的分析:-)
.NET里面也有一个类似的例子:Int32.TryParse

pongba

未读,
2007年10月31日 01:07:362007/10/31
收件人 pon...@googlegroups.com


On 10/31/07, red...@gmail.com <red...@gmail.com> wrote:
有道理.

再结合我的实践, 继续总结一下:
1. 一种特别一点的事情发生了, 只有上层才能知道, 这属于可预料的常规情况, 还是少见的错误情况, 如果是错误, 属于recoverable or unrecoverable 的, 也是上层才知道

2. 一个api, 如果其直接上层使用者很少, 那么特别一些的情况发生, 属于可预料的常规/还是错误, 很有可能编写本 api 的时候, 情况已经知道了, 统一一起进行设计应该效果更好

3. 一个api, 如果是个通用的 api, 各种上层用户代码很多, 那么无法预计别人怎么看自己返回的 "特别情况", 此时, 怎么选择? 我是这样做的: 对性能很有可能有影响的代码(调用次数多, "特别情况" 出现的几率不小), 肯定是用返回值; 否则就根据 "特别情况" 可能的特别性, 决定用返回值还是异常.


对第三点我也再补充一下,《.NET框架设计规范》里面提到,如果要设计一个api,这个api不知道调用方是不是把其失败情况看作错误的话,就不能确保抛出异常是不是会影响效率。这个时候可以考虑使用TryParse惯用法。

if(Int32.TryParse(...)){
}

TryParse跟Parse的区别就在于微妙的pre-condition上。Parse的pre-condition是"这个num必须是可parse为Int"的,因而让Parse在typeof(num)!=Int32的时候返回false,不符合直觉。而TryParse就不同了。其pre-condition并不要求typeof(num)==Int32。

因此,wingfire的例子,可以提供两个函数:setByString和trySetByString。这样就避免了validate和setByString的分离。当然,如果有应用场景需要仅仅validate的话,单独提供validate还是必要的。

wing fire

未读,
2007年10月31日 02:07:102007/10/31
收件人 pon...@googlegroups.com
直接的说,我不会采用tryParse的方式,原因是tryParse和Parse不正交。这种"微妙"的差别非常糟糕, 还不如返回码方式。而validate和setByString是正交的。再考虑在C++中怎么支持pre_condition:
void setByString(string& str){
 PRE_CONDITION(validate(str));
...
//process code.
}
很显然,这里的process code在try方式中有两份。如果你把这重复的代码放到单独的函数中,那和我的分解方式实际上就一样。功能分解的正交性是不可忽视的,tryParse完全可以基于validate和parse来定义(我这里说的是定义,不是实现),而validate和parse几乎不可能被混淆。这种正交分解的问题在于,validate和parse的协作方式还不能被很多程序员所接受。


在07-10-31,pongba <pon...@gmail.com> 写道:

莫华枫

未读,
2007年10月31日 02:14:502007/10/31
收件人 pon...@googlegroups.com
看起来需要带resume的异常。

在07-10-31,wing fire <wing...@gmail.com> 写道:

pongba

未读,
2007年10月31日 02:15:362007/10/31
收件人 pon...@googlegroups.com
On 10/31/07, wing fire <wing...@gmail.com> wrote:
直接的说,我不会采用tryParse的方式,原因是tryParse和Parse不正交。这种"微妙"的差别非常糟糕, 还不如返回码方式。而validate和setByString是正交的。再考虑在C++中怎么支持pre_condition:
void setByString(string& str){
 PRE_CONDITION(validate(str));
...
//process code.
}
很显然,这里的process code在try方式中有两份。如果你把这重复的代码放到单独的函数中,那和我的分解方式实际上就一样。功能分解的正交性是不可忽视的,tryParse完全可以基于validate和parse来定义(我这里说的是定义,不是实现),而validate和parse几乎不可能被混淆。这种正交分解的问题在于,validate和parse的协作方式还不能被很多程序员所接受。

正交性是原则,不是教条。
"针对最常用场景设计"我认为是一个更高的原则。对这个例子,如果我们能够预料到常用的场景是:

if(validate(...)) {
  set(...);
} else {
  ...
}

那么使用try就会更简洁:

if(!trySet(...) ) {
  ...

pongba

未读,
2007年10月31日 02:16:452007/10/31
收件人 pon...@googlegroups.com


On 10/31/07, 莫华枫 <longsh...@gmail.com> wrote:
看起来需要带resume的异常。

D&E里面提到,带resume的异常被大量实践证明不是一个好主意(虽然听上去很美)。

wing fire

未读,
2007年10月31日 02:19:062007/10/31
收件人 pon...@googlegroups.com

2. 一个api, 如果其直接上层使用者很少, 那么特别一些的情况发生, 属于可预料的常规/还是错误, 很有可能编写本 api 的时候, 情况已经知道了, 统一一起进行设计应该效果更好
-------------------------------------------
基本上,我认为这是个错误的设计思路。设计API要做的是说清楚你定义的概念,尽量用"X是Y"的方式来定义的你接口,而不应该用"X是Y,不处理a,b,c"的方式。概念应该尽可能做到小而完备,套句老话,只做一件事,并且把它做到极致。
你永远无法知道用户会怎样使用你的API,所以,你不可能满足所有情况。现有的用户使用方式只是你的材料,可供你思考你的API的本质到底是什么,到底想要做什么,应该做什么。如果你只是致力于满足用户的欲望,失去的将是效率,得到的将是复杂性。你和用户最终都会陷入到扭曲抽象的泥坑中。


在07-10-31,pongba <pon...@gmail.com> 写道:

pongba

未读,
2007年10月31日 02:24:482007/10/31
收件人 pon...@googlegroups.com
On 10/31/07, wing fire <wing...@gmail.com> wrote:

2. 一个api, 如果其直接上层使用者很少, 那么特别一些的情况发生, 属于可预料的常规/还是错误, 很有可能编写本 api 的时候, 情况已经知道了, 统一一起进行设计应该效果更好
-------------------------------------------
基本上,我认为这是个错误的设计思路。设计API要做的是说清楚你定义的概念,尽量用"X是Y"的方式来定义的你接口,而不应该用"X是Y,不处理a,b,c"的方式。概念应该尽可能做到小而完备,套句老话,只做一件事,并且把它做到极致。
你永远无法知道用户会怎样使用你的API,所以,你不可能满足所有情况。现有的用户使用方式只是你的材料,可供你思考你的API的本质到底是什么,到底想要做什么,应该做什么。如果你只是致力于满足用户的欲望,失去的将是效率,得到的将是复杂性。你和用户最终都会陷入到扭曲抽象的泥坑中。

说实话我没看懂redsea这第2点表达的意思.. :( 主要是最后一句话:"统一一起进行设计"不理解。

莫华枫

未读,
2007年10月31日 02:30:202007/10/31
收件人 pon...@googlegroups.com
呵呵,这让我想起我们那些vb代码里随处可见的"on error resume next"。

在07-10-31,pongba <pon...@gmail.com> 写道:

pongba

未读,
2007年10月31日 02:32:502007/10/31
收件人 pon...@googlegroups.com
On 10/31/07, 莫华枫 <longsh...@gmail.com> wrote:
呵呵,这让我想起我们那些vb代码里随处可见的"on error resume next"。

 "on error resume next"? 我还以为是on-error goto呢:)

在07-10-31,pongba <pon...@gmail.com > 写道:


On 10/31/07, 莫华枫 < longsh...@gmail.com > wrote:
看起来需要带resume的异常。

D&E里面提到,带resume的异常被大量实践证明不是一个好主意(虽然听上去很美)。


--
刘未鹏(pongba)|C++的罗浮宫
http://blog.csdn.net/pongba
TopLanguage
http://groups.google.com/group/pongba



red...@gmail.com

未读,
2007年10月31日 02:41:582007/10/31
收件人 pon...@googlegroups.com
pongba 写道:


On 10/31/07, wing fire <wing...@gmail.com> wrote:

2. 一个api, 如果其直接上层使用者很少, 那么特别一些的情况发生, 属于可预料的常规/还是错误, 很有可能编写本 api 的时候, 情况已经知道了, 统一一起进行设计应该效果更好
-------------------------------------------
基本上,我认为这是个错误的设计思路。设计API要做的是说清楚你定义的概念,尽量用"X是Y"的方式来定义的你接口,而不应该用"X是Y,不处理a, b,c"的方式。概念应该尽可能做到小而完备,套句老话,只做一件事,并且把它做到极致。
你永远无法知道用户会怎样使用你的API,所以,你不可能满足所有情况。现有的用户使用方式只是你的材料,可供你思考你的API的本质到底是什么,到底想 要做什么,应该做什么。如果你只是致力于满足用户的欲望,失去的将是效率,得到的将是复杂性。你和用户最终都会陷入到扭曲抽象的泥坑中。

说实话我没看懂redsea这第2点表达的意思.. :( 主要是最后一句话:"统一一起进行设计"不理 解。
对我的表达能力说声 sorry.

我的意思是: 某个api, 如果上层使用者数目并不多的话, 那么其实可以大致判断出来, 上层使用者对本api 碰到某个 "不那么常见的情况", 是有预料, 认为比较常规, 会有处理的, 还是相反. 将这些信息综合起来, 一起设计这个 api.

如果本 api 认为不那么常见的情况, 上层 api 认为普通, 那么用返回值返回大概就合适了.

例如: 我们要设计一个跨平台的文件 open 封装, 将 OS 相关的特殊 attribute 都封装到里面去, 而且这个 open 封装不打算通用化, 只打算程序内部用.

好了, 对于大多数的文件, 打不开文件都是不容易恢复的错误, 这一作为异常来处理应该是比较好的.

但是, 如果我现在写的程序, 是尝试写打开某个目录下的所有文件, 就是要测试一下当前哪些文件是我可以写的(有权限, 并且没有其他exclusive write open), 那么, 失败的情形应该比较常见, 上层也应该很有准备, 那么这个 open 用错误值返回打不开的情况, 我觉得更合适一些.


莫华枫

未读,
2007年10月31日 02:44:112007/10/31
收件人 pon...@googlegroups.com
唉,"on error goto ..."就对了,跳转到error处理块了。"on error resume next"就是执行发生error语句的下一句,忽略刚才的错误。:(

张沈鹏(电子科大08年本科应届)

未读,
2007年10月31日 05:54:052007/10/31
收件人 pon...@googlegroups.com
Python.Django的ORM中
Model.get是获取单一的查询返回值
当查询对象不存在,返回一个异常
该异常的类型为查询对象的类型.NotExist(比如A.NotExist),从ObjectNotExist继承而来
当查询对象多于一个,也返回一个异常

这里异常的优点有:
1.可以比error code更清晰的表达类型
2.可以分出错误类型的大类别,可以统一处理,也可以特化

比如open不能打开可能有
1.权限不够
2.文件不存在
3.路径格式不合法
用error code表示不能如此清晰:)

在 07-10-31,red...@gmail.com<red...@gmail.com> 写道:


--
个人网站:http://zsp007.com.cn/
双学位:生物医学工程+计算机科学与技术
技能:C++(STL,BOOST) Python(Django) HTML+CSS AJAX
-- 张沈鹏

pongba

未读,
2007年10月31日 06:00:292007/10/31
收件人 pon...@googlegroups.com
On 10/31/07, 张沈鹏(电子科大08年本科应届) <zsp...@gmail.com> wrote:
Python.Django的ORM中
Model.get是获取单一的查询返回值
当查询对象不存在,返回一个异常
    该异常的类型为查询对象的类型.NotExist(比如A.NotExist),从ObjectNotExist继承而来
当查询对象多于一个,也返回一个异常

这里异常的优点有:
1.可以比error code更清晰的表达类型
2.可以分出错误类型的大类别,可以统一处理,也可以特化

比如open不能打开可能有
1.权限不够
2.文件不存在
3.路径格式不合法
用error code表示不能如此清晰:)

"查询一个值"跟"打开一个文件"是有本质区别的:查询一个值在某些应用场景中可能会出现在内层循环并且可能只有1%的查询命中率(redsea前面给出例子了),因此如果强制使用异常的话,性能开销是很可观的。而打开一个文件则不同:谁会一秒种连续打开一万个文件并且还只有1%打得开?

李扬

未读,
2007年10月31日 06:14:382007/10/31
收件人 pon...@googlegroups.com
本质上异常是不可预料的错误(对于api都设计者来说,也就是设计者在它的上下文中无法正确处理这种错误)。像测试一个文件是否能够打开,这个是api提供的功能集的一部分通过返回值表示无可厚非。至于像前置条件不满足比如Int32.Parse 抛出异常。这个异常根本就不应该在release代码中抛出(主要是给你找bug,至于你怎么去保证传入的是个能被解释成int32的字符串那是调用方的问题,个人觉得提供一个Valid比TryPase 感觉好些)。还是那个观点作为前置条件check抛出的异常只能是勉强合格的选择,需要其它更好的替代方式(这种异常抛出就不应该让程序运行了)。 一个传统的做法用assert,但是assert有两点坏处
1.release版不可用
2.提供的调试信息不够用。
 
在07-10-31,pongba <pon...@gmail.com> 写道:

李扬

未读,
2007年10月31日 06:52:272007/10/31
收件人 pon...@googlegroups.com
刚才想到的。
TryParse / Parse 应该是用于不同的场景才对。
Parse 应该是用于不能够解析的情况很罕见时,这时候提供能够解析度字符串应该是调用方的责任。
TryParse 是用于去测试一个字符串是否能够被解析,然后解析它(函数的契约要求函数把不能够解析度情况当成有可能经常发生的,能够解析就不再是函数前置条件的一部分)。
 
所以这时候应该用TryParse 来实现Parse。
 
 
为什么不用一个单独Valid来构造测试了?
由于要测试一个串是否能够解析为字符串实际上还要解析这个字符串。也就是说这个Valid 并不是与Parse功能正交,虽然对客户代码来说看来是正交其实对实现来说并不正交。
 
Parse 可以被看作为TryParse的一个装饰类
呵呵这些都应该不违反我们所熟悉的一些原理。。。。
 
 
 
当Valid动作和Parse并不正交的情况也是很常见的比如对报文的校验和,这种情况下定义一个Valid应该是很明智的选择
 


 
在07-10-31,李扬 <yaya...@gmail.com> 写道:
正在加载更多帖子。
0 个新帖子