SunBo

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

很多文章都是讲如何书写正确的代码,如何书写高效的代码,其实代码中的错误、BUG才是真正应该正视的。非常感谢下面这位分享经验的前辈!

warning比error更重要!!!!

引用——“只有你不给自己留退路,你才会真正关心代码质量”

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: 诸位凭良心说下,你们做得到吗?

posted on 2009-07-09 18:29  SunBo  阅读(195)  评论(0编辑  收藏  举报