免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
楼主: 77h2_eleven

[函数] 大家在项目代码中会大量使用断言么?  关闭 [复制链接]

论坛徽章:
0
发表于 2008-07-04 08:24 |显示全部楼层

回复 #1 77h2_eleven 的帖子

从来没用过

论坛徽章:
0
发表于 2008-07-04 09:42 |显示全部楼层
我还等着阁下 解释一下 著名的开源程序100行以内,高手所写代码, 在几年甚至几十年后
仍然爆出bug的问题呢?100行以内,还写出bug, 看来阁下认为他们的水平不砸的押!

既然阁下认为您的函数都没问题,还用try catch 来干什么呢?


后面的流程 会按照出错的方法来解决, 就像你的try catch 捕捉到一样。
//不好意思,您处理不了。爆了,说明是严重bug,后面catch到可以防止带病运行;不爆,必定要带病运行。正是这个带病运行会格用户的硬

盘。
//请不要再在这个问题上掺杂不清。说车轱辘话的人很讨厌。

// 既然阁下武断的认为,被catch 到以后的,就一定要断掉它。 至于带病运行会格掉客户影片这种
想法也已经根深蒂固。 还有什么好说? 一副“ 顺我者昌,逆我者亡”的架势。我想问问,那么象windows这种 bug横生,linux 也是补丁不

断的系统,您是不是该把它们打下1000层地狱? 他们的bug可不是try catch 就能解决的?  


你有没有试过,遇到错误,但是异常没有跑出来的情况呢?就像strncpy( 的dest * 为NULL. 它执行了,它没有崩溃, 它也没有异常,但是它

错误了。  如果你使用过 录音卡 如 ipm 这类板块,你就会知道。 它的系统运行在独立的板块上, 它未必每次工作都是稳定的。都能把异常

抛出来。
//这是特殊平台的特殊问题,请别拿到普遍问题讨论里来搅和。这并不能显示您的水平。
//真想玩的话,CU首页有个brian fuck机,在那上面写程序更好玩。

普遍的?这就是天天开发面对的问题。对于您 您是遇不上的,如果要面对20-30种板卡,帮他们不断的擦屁股,你就知道遇不遇得到了。



不用了, 第二个版本 的崩溃 你们就已经被使用商退回来了。你们已经out!
//不好意思,即使catch没有起到作用,我们仍然没有让用户损失数据;倒是格了用户硬盘的你们,还正忙着打官司,正在公证人的监视下,让

数据恢复公司提取硬盘数据取证呢。
//同时,由于被用户控告在代码中嵌入病毒,公司正雇人调查您们项目组的全体工作人员,看谁平常牢骚最多呢

阁下又认为 我们会格式化掉用户的硬盘了,嗯的确有可能的。 不过操作系统 ,普通程序任何一个程序的任何一个bug都可能会有这样的后果

,那您为什么还这么放心的使用硬盘啊? 说不定在捕捉异常以前已经格式化掉了硬盘了。



错误了
某些信息莫名其妙丢失却总也找不到原因的。 那只是你的代码水平问题。 要知道 不是每个工程师都对错误视而不见。
//很不幸,把非法的空指针参数转换成莫名其妙的arg_error的strcpy函数,正是阁下力挺的杰作;反倒是在下一直在讨伐这种代码

把非法的空指针参数转换成莫名其妙的arg_error
我们还有个错误结果返回呢?如果您不看返回结果。再有强大的异常捕捉又有什么用? 红灯亮了, 继续向前走就可以了。

站在设计师的角度看问题,一个个的函数、模块就是这样一台台随时可能出问题的机器。
关键就在于: 只有正常工作的机器,它们的报告才是可信的;出了问题的机器必须确认问题原因不会影响它的可信度,才能决定它是否应被屏蔽掉。

机器已经告诉您它不可信了,您还固执的认为,靠try catch 拿来的报告我相信的。 返回值回来的,我不相信,我不会屏蔽你。我让您继续运行,然后落下话柄 去被人攻击。

那么,断言是做什么的?

它的作用是: 在测试期就找到并剔除这样的机器。
如果此时没找到,就只能寄希望于异常了。

第一我没有认为不应该使用断言。
第二 我认为4个答案里面3比1好
第三 3的开始 可以加上断言。
第四 加上断言 不能取代内部的判断。


总结一下, 阁下认为我们的代码有一丁点问题,客户的硬盘就没了。函数的错误,我们从来不处理,让後让整个系统布满逻辑错误。
100行以内的代码就是安全的了.  只相信异常,不相信返回值。

论坛徽章:
8
CU大牛徽章
日期:2013-04-17 10:59:39CU大牛徽章
日期:2013-04-17 11:01:45CU大牛徽章
日期:2013-04-17 11:02:15CU大牛徽章
日期:2013-04-17 11:02:36CU大牛徽章
日期:2013-04-17 11:02:58技术图书徽章
日期:2013-12-04 10:48:50酉鸡
日期:2014-01-03 10:32:30辰龙
日期:2014-03-06 15:04:07
发表于 2008-07-04 13:39 |显示全部楼层
成功的团队很多,而且往往各有各的成功之道。
但有一点品质肯定是它们所共有的: 分工明确,权责分明。

废话少说,言归正传。


昨天在70楼的帖子中论述了总体设计层面上对不同错误的不同看待和处理方式。

可能有人要怒了: 你的方法严重依赖于try catch!

是的,是的。这点我承认。但您的头脑能不能稍微灵活点呢?

比如,甭看不起COM,看看微软是怎么做的。


在下以前做的某个项目中也有类似的设计。
我们定义了若干个错误级别,如WARNING、MINOR_ERROR、MAJOR_ERROR、CRITICAL_ERROR以及FINAL_FAULT。

如何界定错误等级呢?

举例来说: 连接数据库失败,或者正在访问数据库时把网线拔了,这不是错误,连WARNING都不算。
程序应该做的,是准确识别网络返回码,通过合理流程优雅的回退当前事务——当然,要给用户事务日志里写一笔。
一旦网络故障排除,程序自然会恢复正常。

类似的,网络信号差、外接数字仪表易受干扰、被控单板机工作不稳定等等,这些都不是错误,而是必须预设逻辑正确处理的正常情况。
不同点在于: 必须根据实际情况区别对待,以不同的方式将不稳定的设备稳定利用起来。

把这种东西和程序逻辑错误混为一谈的,显然是需要多读点书了。



那么,什么样的问题才算错误呢?
假设功能模块A设计是给部长级别的人用的,经过完整的权限检查,在底层某个写库模块出了个“数据库访问权限不足”故障,这就是错误。
原因很简单: 前面的权限检查要么没发挥作用,让无权的人访问到了不该他接触的东西;要么数据库的权限设置出了问题,把本该有权限的人给挡到了外面。
当然,不恰当的重用了某些访问模块,也可能会导致类似问题——这种时候,类似问题通常完全不影响逻辑,也不会影响使用——但显然我们不能冒这个险。
此外,软件配置不当,连到了错误的数据库,也可能造成此故障(但这又说明数据库连接模块没有尽到自己的职责)。

无论如何,这种东西即使在运行时被精确识别,是不应该也没办法处理的。

很明显,这个时候就应该抛异常。
不过,基于种种原因,我们禁用了异常,设计了另一套代替机制: WARNING、MINOR_ERROR、MAJOR_ERROR、CRITICAL_ERROR以及FINAL_FAULT。

根据具体情况的不同,同样的错误可能对应到不同级别的异常:
WARNING:    程序设计有错,但只影响当前函数的此次调用,可当作失败处理;但必须写调试日志以跟踪错误。
MINOR_ERROR:程序设计错误影响到了整个子模块,此模块应停止一切活动,把这个错误直传上去,让上级模块撤销此次操作。
MAJOR_ERROR:程序设计错误影响到了中间控制模块,此模块应停止一切活动把错误直传上去,让框架撤销操作
CRITICAL_ERROR: 程序设计错误影响到了负责总控的框架,整个框架不应再作任何操作,应结束程序并重新启动服务。
FINAL_FAULT:发生了无法恢复的重大故障,应结束程序且不得重新启动(或重启动后禁止写任何数据以免造成数据无法恢复),等待专业人员修复(这个级别从未被用到过)。

假如发生了strcpy参数为空指针这样的故障,我们会在捕获到后,根据可能受影响的指针(即包括与这个指针同时声明的指针)界定影响范围(定义错误级别),凡在影响范围的模块都必须无条件停止除释放资源外的一切工作,以避免闹出无意格式化硬盘的笑话。
本质上,这就是对try...catch机制的一种模拟。

请注意一点: 即使是最低的WARNING故障,也意味着代码逻辑有问题,也是不可容忍的。
因此,在调试版里,所有故障全部被当作assert fault处理。

只有调试日志中没有任何内容时,才说明程序逻辑过关,可以推出试用了。


显然,既然可以且必须保证调试日志中没有任何内容,那么绝大部分情况下,用assert代替以上繁杂的错误级别就是完全可行的。
因为你显然不会触发到它。

举例来说,c/c++基础库里用了多少assert?
它们的strcpy会不会返回arg_error?
用着这些“不可靠”代码,为何你的系统不会崩溃?
莫非你们自己重写了全套c/c++基础库?

众所周知,linux就是用充满了assert的c基础库写的;即使频繁因为bug打补丁,为何它仍然是最稳定的服务器平台?
它的稳定,是因为用了assert还是相反?



和基于异常的系统相比,我们这个错误级别系统的缺点是:
1、无法抵抗绝对无法在运行时识别的故障(比如野指针)——这个缺点凡非基于异常的系统都有;基于异常的系统同样也有无法处理的情况,比如指针越界已经彻底捣毁了栈结构。
   但是,只要不是糊涂蛋,就不会看不到不同策略下整体所能达到的稳定程度(比如基于异常的系统可以恢复野指针导致的轻度崩溃,其他方式则都难以达到同样的高度),而是总拿些特殊问题来胡搅蛮缠。
2、使用繁复,难以推广


另一方面,假设纳入本系统管理的故障有M个,无法纳入管理的故障有N个;
假设第一次测试发现了110个故障,其中100个属于M,10个属于N;
反复测试,直到软件质量稳定,设新发现的故障有50个属于M,5个属于N。
那么,可以认定: 无法纳入管理的bug占可以纳入管理的bug数目的10%。
根据测试理论,可以通过历次测试的bug曲线估算出项目中仍然存在的bug数(比如,10个);其中约10%的bug无法控制——这点任何人都无能为力,只能通过测试解决。
当然,这1个bug确实有极小的导致崩溃的可能。

这种系统的可靠性会如何呢?大家可以想象一下。

结论: 错误确实无可避免,但至少我们可以界定模块的质量,测出它的可靠性,然后采取种种措施保证总体的稳定与可靠。


后来,因为新人加入,虽然我们可以解释清楚逻辑错误和必然发生因而必须处理的情况之间的区别,但他们还没有能力准确界定错误级别。
怎么办?
我们取消了错误级别这种做法,全部代之以assert。
因为不这样做,他们就不会使用错误系统,而是严重倾向于使用防错逻辑——这显然容易导致错误扩散,为定位带来麻烦。

将错误界定为错误,bug修正率几乎可以达到100%,且很少因为修改bug而引入新的bug;使用防错逻辑,一方面导致错误被掩盖、扩散,另一方面显然会为定位造成很大麻烦,甚至导致错误越改越多。

大概来说,这种情况下,bug可能只有80%能被修正。
于是,165个bug,过去全部被修复;预计还有10个bug,1个无法控制(虽然bug可以控制不等于功能正常);
而现在,165个bug,只有165*80%=132个真正被修复;不考虑这种编码方案会隐藏bug,还是会有165-132+10=43个bug隐藏,其中4个无法控制。

事实上,由于错误隐藏以及bug修复引入新的bug,代码质量前后差距相差几达数十倍。
一个重要原因是:那些被“防错”代码“控制”的bug,其实根本就没有真正被控制;仅仅是在表面上被隐藏起来而已。
strcpy得到空指针、然后根据错误返回不精确地处理掉就是典型的例子;这种东西会带来更多难以发现的bug。
因此,实际无法控制的缺陷肯定远超过20个,甚至可能更多(换句话说:这种糊涂蛋系统里,已经几乎没有可信的东西了)。

将所有错误用assert标识出来,放弃错误控制流程;于是,经过几轮测试,预估仍然存在的bug很快再次被压缩到20个左右——但这次全部无法控制。
不过,一共20个无法控制的bug,显然远胜于 (20个不可控制的bug) + (20/10% = 200个据说已经被控制的bug)
(后来,我们破除了头脑中对异常的成见,连最后这20个缺陷也用异常保护起来了)

隐藏错误导致测试难以起到作用,是造成这种现象的根本原因。

暴露bug,这就是assert的意义。




所以说,没有问题是不能解决的;但必须要有正确的心态和灵活的头脑。

我这里仅仅说了try catch和错误级别,如果有死脑筋认为只有这两招可以防错的话,请看微软那位老兄举的execl加速计算代码的例子:
由于必须区分有改动和无改动的单元格,并准确识别公式计算会涉及到的那些单元格,这显然需要极端复杂的代码。
怎么保证它的有效性?
他们写了另外一个只在调试期执行的、缓慢而简单的全部计算程序,用assert检查这个程序和正式代码的计算结果有无差异。

这种机制在运行期有什么用?它甚至都不会被编译到发行代码里!
它的作用就是: 不允许把bug留到运行期。

看看 write clear code 这本书吧。昨天贴的关于assert的小故事,只是它的第二章的十分之一不到。
剩余的内容,都是如何让编译器帮助排除各种错误的。
莫非有人能脑筋死到必须让我把这整本书给您复述一遍?还是你根本就没打算理解这些东西,仅仅就是想吵个架而已?


另,看看这则关于google的报道:
http://server.it168.com/server/2008-06-10/200806101108413.shtml
一般每个新业务上线的第一年,通常会发生1000次个别主机的故障、数千次硬盘故障;一次电力输送问题,会导致500至1000太主机失效约6小时;20次机柜损坏,每次会造成40至80台主机下线;5次机柜摇晃,会导致一半的网络封包在传送过程中遗失;整个业务至少一次重新上线,在两天之内的任何时间,影响5%到主机。整个业务中还有一半的几率会过热,可能导致5分钟内让几乎所有服务器当机,恢复则需要花费1到2天地时间。

没关系。即使硬件如此不稳定,Google也不会崩溃。
只要他们的程序逻辑没有问题。

至于某个坦承自己的项目会“有个逻辑错误,偶尔的发生, 影响到了3-5格模块的内容, 其他的逻辑错误都是返回错误。但是没有的异常错误, 最后在strcpy 里面爆发了。你想定位就慢慢花时间吧。”的“坏人”^_^,在下老早就已经帮他指出了问题所在。
对于任何一边偷偷向在下的观点投降,一边拿在下自己的观点掺杂上他自己一直没弄懂的问题过来缠杂不清的"坏人",在下将不得不秉承一贯作风,从此置之不理了。


最后: 这本来是专注于assert的讨论,但现在显然已经远远超出范围,甚至都波及到硬件稳定性了。
在下认为关于assert的一切都已经被阐述明白,没什么需要多说的了;那么,就让这个“面条”话题就此结束吧。

论坛徽章:
0
发表于 2008-07-04 14:37 |显示全部楼层
原帖由 shan_ghost 于 2008-7-4 13:39 发表
成功的团队很多,而且往往各有各的成功之道。
但有一点品质肯定是它们所共有的: 分工明确,权责分明。

废话少说,言归正传。


昨天在70楼的帖子中论述了总体设计层面上对不同错误的不同看待和处理方式 ...

感觉两位兄弟都是有感而发,都具有一定的项目经验,也有自己对待软件开发方面的理解或者说是个性。
这个帖子是因为我看《编程精粹》而发。
不如这样。从现在开始,我们在自己的代码中尝试着使用断言以及单步调试等该书作者建议的方法。
至于断言没有没有用,我觉的,用过的人才知道。
如果不好用。我回头再来告诉大家~~~~~·

论坛徽章:
0
发表于 2008-07-04 16:17 |显示全部楼层
原帖由 shan_ghost 于 2008-7-4 13:39 发表
成功的团队很多,而且往往各有各的成功之道。
但有一点品质肯定是它们所共有的: 分工明确,权责分明。

废话少说,言归正传。


昨天在70楼的帖子中论述了总体设计层面上对不同错误的不同看待和处理方式 ...


阁下对我的问题视而不见, 不断的偷换概念。 我还等你回答那些问题呢。

暴露bug,这就是assert的意义。

第一 我没有说过 不使用assert.
第二 我指出 第3种更好
第三 应该第3中加断言 更好些。
第四 只加了断言 不使用判断是错误的。

google 可以坏掉一般的服务器, 我们的服务可不能坏掉一半的服务器。 我们不是google. google 暂时无法提供服务了,没人拿着合同去追债, 我们会。

assert 更容易暴露bug, 换句话说  应该是它粗暴的提醒你, 有 bug必须改掉,而且仅仅在debug的情况下,在release的情况下,它或许只给你一个重启,什么都不留下。  而我们的方法是 告诉你 有错误了,快改掉它。

如果没有责任心和科学的方法, 第一次出了问题,再调一次, 没问题,不管了。 任你断言再多也是无济于事。


对了最后我想问一下 google 的系统是怎么查找bug 的? 莫非那些大牛有透视的本领。 看着主机就说,恩, 此行有bug? 还不是靠着
日志,一点一点查出来的。

论坛徽章:
0
发表于 2008-07-04 16:46 |显示全部楼层
沒大量用過

寫HELLO WORLD時候用過

论坛徽章:
0
发表于 2008-07-04 19:33 |显示全部楼层
用的不多,仅测试时用

论坛徽章:
0
发表于 2008-07-05 01:51 |显示全部楼层
shan_ghost   这种观点我并不赞同:
相反,在下一直强调发现错误就让它爆掉,这有什么好处?
好处就是:strcpy会因为引用null而在用户那里崩溃,但这个错误不会造成任何其他潜在的破坏。
由于整个流程上有机制在保护——比如,发行版中,我的try_catch块会捕获电话接入流程的所有异常;启动错误日志记录之,然后断掉这个电话或提示对方重新开始。所以我的设计当然要比你们隐藏错误的设计更稳定,用户体验更好
于是,在下的设计:
1、不造成更大故障 2、立刻捕获错误,避免错误扩散后难于定位——也许你们还在通宵查服务器硬盘被格式化的原因,我们第10个新版本已经上线了 3、用户体验好——对了,他必定得到正确结果;错了,立刻报告错误并提请重试:总之,绝对不会出现面条系统里面某些信息莫名其妙丢失却总也找不到原因的下三滥问题。

个人认为:
并不是发现错误,如空指针就一定要抛出去,中断流程。错误是要处理的,但是中断流程也要看错误影响范围。
例:电信计费系统,通话结束后,先发送系统短消息(如:余额不足),接着写清单。 如果在系统短消息处理中出现错误,难道就要中断程序不写清单了么?但是,这么做是不合理的,通话结束就需要写清单,尽管写前需要发送系统短消息。系统短消息出错,应定位为内部错误,影响短消息;但是不应该影响写清单。
一个相对比较大的系统,因考虑错误影响范围,而不应该是发现错误中断流程。如shan_ghost 说的<断掉这个电话或提示对方重新开始>
断掉电话或提示对方重新开始:如果用户余额充足,只能在程序错误导致无法计费时才可以断掉电话。

论坛徽章:
0
发表于 2008-07-05 09:02 |显示全部楼层
原帖由 crowsy001 于 2008-7-5 01:51 发表
shan_ghost   这种观点我并不赞同:
相反,在下一直强调发现错误就让它爆掉,这有什么好处?
好处就是:strcpy会因为引用null而在用户那里崩溃,但这个错误不会造成任何其他潜在的破坏。
由于整个流程上有机 ...

断言用在调试版比较常见,在发布版出现是一件比较危险的事情。
比较好的方式是以如下方式使用:

#ifdef DEBUG
   assert(....)
#endif

这样可以通过编译开关,在调试/发布时,加入/禁止断言。

断言本身,与异常或错误码方式不同, 它并不是一种错误处理机制。其目的是其实是很明确的, 如果存在一种明显的逻辑错误,因无法确定解决办法,已经导致程序继续运行会存在致命风险的,即以此中止程序运行,其实对系统管理员而言,这是一种致命缺陷的即时通知。

至于是否使用它,本身并不是一个可以技术上探讨的问题,而是由应用程序的业务逻辑决定的。在不同的应用场景下的,可能有不同的"缺陷管理观“,也就会有不同的使用断言的观点。
一般而言,如果程序员自已实现一个函数时,可能会有很多不同应用场合的人来调用,并不知道该函数要应用于什么场景,那么最好是不要轻率以断言方式中止程序,因为这样带来的后果可能是严重的,较好的方式是抛出异常,或者返回出错码,让上级程序知道出错原因,将处理的选择权留给应用者。
而真要使用断言的,最好都在一程序中比较高层次的模块里出现,而且使用者必须已经明确,非断言无法避免因这种逻辑缺陷带来的风险,也必须非常清楚这种主动中止掉程序所付出的代价,要远低于程序继续运行可能导致的危险。

论坛徽章:
0
发表于 2008-07-05 10:55 |显示全部楼层
原帖由 zszyj 于 2008-7-5 09:02 发表

断言用在调试版比较常见,在发布版出现是一件比较危险的事情。
比较好的方式是以如下方式使用:

#ifdef DEBUG
   assert(....)
#endif

这样可以通过编译开关,在调试/发布时,加入/禁止断言。

用不着这么麻烦。assert本身在宏定义处就做了你这个工作。
看一下assert.h就知道了。
编译时加-DNDEBUG就会不用断言的。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP