免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
123
最近访问板块 发新帖
楼主: 77h2_eleven
打印 上一主题 下一主题

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

论坛徽章:
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
21 [报告]
发表于 2008-07-05 11:28 |显示全部楼层
晕,俺的话就那么难以理解吗?


1、错误必须正确分类
网线断掉、硬件不稳定等等都是正常情况设计师必须预先考虑到这些,设置合理的处理/恢复逻辑。
    代码中的逻辑错误与以上相同,在总体设计层面上也是正常情况,没有为这些东西准备措施的就是面条设计师。

2、不同错误不同对待
    正常情况式的异常将纳入系统异常流程逻辑错误必须立即让它爆发,然后在影响范围之外纳入系统异常流程


举例来说:
     strcpy得到一个空指针,这就是典型的逻辑错误
     对有经验的设计者来说,这意味着如下几种可能:
            1、调用者的上级模块有问题,且调用者本身没有严格检查参数
            2、调用者本身逻辑有问题,比如使用了未初始化的内存或野指针
            3、之前的流程出现了严重故障,内存写越界导致程序跑飞

     如果相关代码继续运行,必然招致如下后果(全部或其中之一):
            1、错误被稀里糊涂的丢弃,业务流向稀里糊涂,最终导致用户数据莫名其妙地消失
            2、后续逻辑继续引用未初始化的内存或野指针,捣毁栈结构甚至导致程序跑飞
            3、跑飞的代码……谁知道它会做什么……这种故障没有任何人能准确界定原因(某责任人满头大汗地“偶发,偶发……”)。

所以,对这种情况的处理不能简单返回一个arg_error了事,而是必须想办法隔离相关模块,在更高的、不受影响的层次上恢复运行。


3、在错误精确分类的基础上再考虑如何恢复
看在下曾用过的这个设计:

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

各位觉得这个设计怎样?
如果strcpy空指针问题撞上这个系统,显然不会崩溃,而是在经验老道的程序员手下准确界定范围不触动可能导致无法预料的总爆发的任何东西直线返回,然后在可能恢复错误最低层次重试

如果你们也有这种系统,返回错误码俺没意见;可惜在下看不出各位有使用过类似系统的任何迹象。

在下说过,这是对异常系统的一种模拟。如果你们不打算或不能用异常,只要有这个,效果和异常相差无几
也许有些朋友是在这种系统下工作,那么返回错误无可厚非;但你要站出来说“我们不用assert,我们不用异常”——不客气的说:你们那位可怜的设计师的脸面被你们给丢尽了;同时希望你们回去好好看看设计,不要误导初学者。

在下以前也在华为做过。当时我们的系统也使用了异常,但同时还定义了类似的异常级别系统,如 WARNING MINOR_ERROR 等等。
这样,上层模块接到下层抛出的异常,就可以大致估计影响范围,然后决定在何种层次上恢复正常。
我们同样大量使用assert;但assert被我们定义为 CRITICAL_ERROR,并且会保留在发行代码中。


4、试图恢复无法预知的错误必将导致错误扩散甚至更为严重的伤害;
   并且这种错误几乎无法界定原因

所以,在下更早的项目组宁可放弃原有的错误分类系统,也要迁就新手,让他们把逻辑错误当逻辑错误处理。

换句话说,正规软件开发是要估计代码稳定程度的。
隐藏错误的系统将导致测试经理评估出一个虚假的bug级别;像strcpy空指针伴生野指针之类的bug绝不会无缘无故消失,总有一天它会爆发出来——当然,此时您很可能已经不在公司了,没有人会知道这是您干的。
相反,把所有逻辑错误暴露出来,测试经理给出的bug级别才是准确的。

假设一个未知bug最终导致crash的几率是5%,那么:
不暴露错误的系统里,评估内部约有20个bug;实际是内部还有 200 个bug被隐藏;这220个bug至少有11个会导致crash且无法追踪。
暴露错误的系统里,评估内部约有20个bug,实际就是20个bug;这20个bug则只有1个会导致crash——由于没有隐藏错误,这一个crash会很快被捕获并修正。

于是,半年试运行后,不暴露错误的系统可能会crash 11次,修修补补后修复了8个,同时又引入3个可能导致crash的bug;
暴露错误的系统crash 1次,修正后永远不会crash。


看看Linux、orache他们是怎么做的;他们有没有重写c/c++库,去掉库中的assert(至少,release版的标准strcpy是不检查传入参数的)。



5、请注意讨论范围,不要任意发散

在下已经批评过某“坏人 ”把硬件不稳定这种八杆子打不着的问题扯进来了;有些朋友也不想想,既然我们连错误级别系统都搞出来了,系统可能没有日志吗?如此详细的错误级别,日志可能不细致、准确吗?这种精确错误级别界定机制下,我们的恢复机制粒度会不细吗?

日志不是糊涂账,不是火锅,不能什么都往里写——否则,这本糊涂账就不会有人去认真分析。


日志分很多种,比如访问日志、业务日志、调试日志等等,请注意它们的严格分类以及作用。


正式发布的正规系统都不会写调试日志,除非明确打开——比如Linux、Windows、oracle、ms sql server等等。

为什么?

原因就是: 调试日志中,低级别(TRACE)的记录相当于单步跟踪,这是为整体联调服务代码的无奈之举;高级别(WARNING及以上)记录则用于跟踪逻辑错误。

一个稳定的系统,肯定是不允许逻辑错误存在的。
测试一定要做到可以保证逻辑错误几乎不发生时才敢往出发代码——否则,谁知道会爆出什么炸弹。


为何c/c++标准库有大量 用assert、不检查strcpy参数指针等等“恶心” 行为?同样用着这些东西,为何别人就能写出真正稳定的代码?

原因很简单: 这种低级逻辑错误是必须在测试期处理掉的;assert就相当于调试日志,而且还是只写高危级错误的那种日志。


如果有些公司的测试连这种高危漏洞都不能排除——各位读者,你们觉得在下称它为“走过场”是否夸张?



为何Linux、Windows、oracle、ms sql server以及许许多多的公司敢用assert?
原因很简单——能被assert检查出来的都属于 第一梯队 bug,这类bug测不出来是近乎不可思议的事。

然后,assert查不出来的(如野指针等)才是 第二梯队 bug;这类bug除了良好设计的catch,谁都无能为力。

以上两类都属于 极端严重级别 的bug,它们是单元测试和第一阶段测试的针对目标。

然后,测试第二阶段开始,此阶段测的,才是业务逻辑是否正常、运算结果是否正确等“软”故障。



strcpy空参变arg_error式的行为,说白了就是把第一梯队的严重bug变成第二梯队的偶发故障和第三梯队的“软”故障。

这样一来,测试初期阶段就会形势一片大好,几乎没有几个会crash的bug——但是,一旦crash,就是那句话“有个逻辑错误,偶尔的发生, 影响到了3-5格模块的内容, 其他的逻辑错误都是返回错误。但是没有的异常错误, 最后在strcpy 里面爆发了。你想定位就慢慢花时间吧。”

好不容易把这种漏洞糊上,离deadline已经不远了;于是关于“软”故障的测试匆匆而过——该交货了。

长此以往,他们怎么不可能患上崩溃恐惧症?



——比较一下:
——这是在下曾呆过的一家小公司为某部/局级要害部门做的设备故障检测、处理及验证系统(人命关天的东西:如果我们有计算错误,导致设备状况恶化查不出来,一次死上万人都有可能)
——测试开始还不到一星期,测试组就已经发怒了:怎么到这时候了还有英文(指的是因assert爆出的错误对话框)?
——原因是:不到三天的密集爆发后,这个系统已经再也发现不了assert错了;所以他们已经进入追查“软”故障的流程,对再次遇到assert毫无心理准备!
——那个assert,是他们在持续4、5个月的测试中发现的最后3次“英文”之一。
——请注意,我们是遇到任何含糊之处全部抛异常或assert的。
——当然,我们当时用的不是c/c++,不需要处理指针。
(不过,虽然在下后来做的c/c++项目都没有经过如此严格的测试,但几乎每次都是前2、3次单元测试之后,内部就再也不会有任何种类的崩溃——于是后面的测试,在下就可以把精力集中在参数传递上了)

好吧,即使某些大佬们的系统不需要这样的安全性,得不到长达4、5个月的测试时间——1周不到的测试,仅余2个可能导致崩溃的bug: 诸位凭良心说下,你们做得到吗?

论坛徽章:
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
22 [报告]
发表于 2008-07-05 12:02 |显示全部楼层
说白了很简单:

我先前呆过的几家公司都是从一开始就没打算放过任何错误,不管大小。

这种心态下,你可以assert,程序可以崩溃,损失再大也得扛——自然,责任人当然也不可能逍遥法外。

同样,测试部门也必须担责任。


正规公司里,测试可不是伺候程序员大爷的苦工。

本质上说,程序员是公司任务的承包商;测试则是代表公司的采购部验货人。双方几乎是领导与被领导的关系——测试即使不是领导,也绝对不可能是面条公司的苦工


这里面就有个责任界定问题,即: 出了大事故谁来负责?

华为这种公司,一台9系列光交换机崩溃,一个小国的网络就彻底崩溃。
这责任,谁扛得起?


很简单,3级责任:
1、如果一个可以预料的故障,设计师居然预料不到,没有设计异常流程,他的饭碗甚至人身自由恐怕就很成问题了。
   对设计师来说,把不稳定的设备玩稳定是基本功。做不到,请另谋高就。

2、一个本该立刻暴露的故障,程序员稀里糊涂把它“容错”掉了,瞒得过测试,瞒得过数亿用户?
   一旦追究起来,此人饭碗难保;部门经理也要掉一级工资了。

3、作为测试,出现明显的漏测导致线上系统崩溃,不该自己反省下吗?下次卡紧点,不然你们经理又要掉一级工资了。



所以,某些公司里,如果程序员代码太垃圾,测试甚至会刻意保留一些bug不报,仅指出bug数目;要求对方彻底检查,直到保留的bug被自测出来,他们才可以放心。


形成习惯后,由于通过测试的部分可认为绝对可靠,后续开发的效率是极其惊人的。

比如,在下为那个要命系统做的2期改造,40多个修改点一星期搞定,测试找不出任何bug。
之后,客户单位根据自身情况提的任何需求,向来是上午的需求更改提交过来,下午就能出补丁包,半年未出现过bug。
以至于自在下03年底离职至今,竟没有人能接手这个项目——客户不信任,评价永远是“不如老系统”,压力太大。


总结:
    容错必须精确、理性。不然越容错越多。
    调试日志必须是空的。里面出现的任何WARNING及以上级别的信息都意味着逻辑错误,意味着不可挽回的损失。
    任何逻辑错误都不可容忍。它们总有爆的一天——不管你用什么方式美化它、容纳它。
    不要为自己准备太多退路。严格考察起来,这点小技俩瞒不过任何人。

只有你不给自己留退路,你才会真正关心代码质量,才能写出真正无措的代码——同样用着c/c++标准库,Linux、Windows、oracle他们为何就不担心崩溃?

论坛徽章:
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
23 [报告]
发表于 2008-07-05 12:07 |显示全部楼层
总之一句话,我再说一遍: 入鲍鱼之肆,久而不知其臭。

激流勇上与苟且偷安,这是两种心态的根本区别。

论坛徽章:
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
24 [报告]
发表于 2008-07-06 16:27 |显示全部楼层
吃饭肯定是会噎住的。你给我找个吃饭永远噎不住人出来。

如果找不出来,那么大家都应该拒绝吃饭。


走路是会摔倒的。所以古猿本来就不应该站起来。


车祸永远是避免不了的。所以我们要把桥都拆了,路都挖了。


蔬菜洗得再干净,人体内的细菌也还有数公斤之多。所以对楼上来说,洗菜和吃大便没什么两样。


不过,吃大便是您的选择;到处宣传的话,还是很让人反感的。

论坛徽章:
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
25 [报告]
发表于 2008-07-06 16:31 |显示全部楼层
是的。逻辑错误难以避免,软件bug层出不穷,没有人能保证它永不出现。

但是,如果有办法可以辅助捕捉到——甚至于,如果有确凿的迹象(比如strcpy得到了空指针)可以证实——逻辑错误的存在,我们是不是应该抓住这个机会把菜洗一洗?

不知阁下是装傻呢,还是吃那个什么已经吃习惯了?

[ 本帖最后由 shan_ghost 于 2008-7-6 16:36 编辑 ]

论坛徽章:
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
26 [报告]
发表于 2008-10-28 15:43 |显示全部楼层
呵呵,在另外那个帖子中讨论时还针对神七飞船写了个案例,不过感觉前面的帖子已经说清楚了,就没有发。

大家在学校学的东西,往往都是一些局部的、分离的算法,很少有站在大局角度通盘考虑的;并且国内教授自己多数也没什么实践经验,教不好错误处理很正常。


案例:

神七飞船载重有限,因此减速火箭只能在着陆前5秒打开(因为燃料就这么多);实际上,这个数据考虑了最不利状态下姿态控制的需要并留有1秒的余量。

现在,要求是设计一个能保证安全着陆的程序。
这个程序要读取飞船当前的姿态、速率以及高度、风向、风速等信息,然后实时控制飞船着陆。


现在,考虑这样一种情形: 假如上级调度模块在宇宙空间的恶劣环境下,因射线击中存储器某单元造成状态翻转,致使栈写越界——于是着陆控制模块发现,指向飞船姿态数据区的指针被改写成0了。

当然,这种情况是无法事先预估到的。怎么办?


如果返回错误码,是可以避免因引用无效内存而崩溃;但被破坏的栈可不会自动恢复。
很可能,这个return指令会因为栈中返回地址也已经被破坏,而使得代码跳到一个未知的位置继续执行——也许飞船都撞到地面了,这段跑飞的代码还没回来;减速火箭自然也就不可能打开。

于是,飞船撞毁,宇航员必死无疑。



正确的做法只能是——发现异常就crash!

PS:介绍个新概念:watch-dog(搞嵌入式的应该很熟悉这个)。
——watch-dog的原理就是:要求程序必须安排合适逻辑定时清零计数器(报告状态正常);一旦计数器到时没有清零,就立即reset系统(相当于crash)。因为此时程序可能已经跑飞了(当然,也可能是时序没算对)。
——当然,watch-dog的周期不可能太短,否则对系统性能消耗过大;但太长又可能导致反应不够灵敏,这是必须根据实际折中考虑的。

实时系统的重启速度相当快。玩数码相机的应该有所体会——我的那个好像说是0.0x秒吧。

于是,由于设计上预留的1秒缓冲,若能立即crash的话,系统可以容忍连续10次失败仍能保证飞船安全着陆——连续2次或3次失败后自动切换备用系统等等设计也才有了发挥空间。

可见,crash不是洪水猛兽。它是构建真正稳定的系统的不可缺少的一环(当然,普通如word等软件,出错直接崩溃提交就行了,没必要设计机制保障7*24的绝对安全性)。


相反,如果仅仅返回个稀里糊涂的错误码,系统跑飞被watch-dog抓到还算好(但也已经耽误不少时间了),万一跑飞的那个地方刚好是刷新watch-dog的逻辑,岂不完蛋?

搞出这种飞机的,有几个脑袋够M249打眼?

——————————————————

说到底,这纯粹是个态度问题。

都知道捕获所有异常并忽略是完完全全的混蛋;但每个错误都要查清来龙去脉却也不是易事——尤其是在缺乏责任心的人那里。

所以,“聪明”人就有了发挥其“中国式智慧”的机会了:有错,我知道,我也报告了——别人不处理怪他。
别人?
别人也不是傻蛋。你报告我也报告,说不定问题绕了一圈又回你手里了。
——无所谓,反正程序还能跑。

最后,代码中充斥着各种各样稀奇古怪的错误,到处都是错误码,人人都在写日志——反正正常流程能走通,交得了差。

你说什么?空指针不准确确定原因和影响范围,稀里糊涂返回错误码可能导致程序跑飞?
跑飞就跑飞。又没崩溃。
反正到处都是检查,到处都是容错,说不定飞着飞着它就又飞回来了——像蟑螂一样,这系统命硬着呢。

你说这样说不定哪天就撞到格硬盘刷bios的那块代码了?
没事,那才多大点概率。我可以保证99%,至少也有90%的情况下跑不到那里。

总之,不崩溃才是王道——很明显,这种容错容的跑飞说不定还能跑回来的系统,你哪里知道崩溃时已经跑飞多少遍了!

一旦崩溃——苍天啊!这问题谁有本事定位!!

说不得,整个系统大返工,每个函数每个参数的——不是查故障啦,没法查——加容错代码。
——口号是:一定要保证程序跑飞后还能跑回来!
(当然,这时候逻辑是否正确、数据有无丢失就顾不得了)

显然,随着程序的崩溃,搞出这种垃圾的程序员怎么可能不恐惧到崩溃。
——我理解这种病人;但不认为他们值得同情。




一言以蔽之:一群没责任心的懒蛋想偷懒,结果越偷懒越是忙得不可开交;最终代码写了5000行,其中容错逻辑倒有3000多行——其中1800行是为了给那2000行代码加容错逻辑;另外800行是给1800行容错代码加的容错;剩下的则是容错代码的容错的容错(的容错……)。

越容越复杂,错也就越多;错越多越无计可施,只能继续容错,于是系统就更复杂更不可调试……

——————————————
assert的思想本质就是: 出错了,一定要立刻崩溃(报告),千万别拖来拖去搞出先跑飞再崩溃的飞机。
只有这样,排错才会轻松惬意——跑一跑,根据assert报的行号直接过去修改问题就是,几乎不需要什么代价。
assert越多、越准确,错误就被限制得越死;一旦触发到,问题也就越好解决。

至于发行版assert自动失效(这已经是内建于c/c++标准库的机制了),原因仅仅是不希望为无用的东西付出运行时效率而已(无用二字正体现了对测试流程的自信,同时也显示出这种系统有多好测)。
——这种境界,显然不是没这样做过的人所能理解的。

论坛徽章:
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
27 [报告]
发表于 2008-12-18 10:48 |显示全部楼层
原帖由 cuinantrue 于 2008-12-16 17:30 发表


此人对编码着实有些研究,只是让我想起一个人,祢衡。



http://bbs.chinaunix.net/viewthr ... =9806331&page=2

看看,我说要随时提高警惕吧。

问楼上一句: 这种行为,是不是跟着那个偷偷跑僻静处拿针扎纸人的太监学的?
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

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

清除 Cookies - ChinaUnix - Archiver - WAP - TOP