使用CSTD技术轻松编写0 Bug的代码

前言

软件开发,在很多人的眼里是一件艰苦、困难的事情,在软件开发中发生诸多问题,如:

1.“改一出两”或客户处发生但开发环境下不再现的Bug;

2.客户“随心所欲”的更改要求,而现有的设计又无法满足。

3.进行新项目开发时,由于没有一个好的通用架构,每次都要重新进行设计和实现,却无法在项目期限内获得高效、高质量的代码。

以上等等诸多问题,使得开发人员经常加班却看不到多少效果,使得软件开发人员对软件开发逐渐失去信心,造成恶性循环。

我从编码、设计、架构三个层次,将开发过程中的一些经验进行总结,形成了“轻松进行软件开发”三部曲,合理使用后能很轻松、高效地开发出高质量的软件。

本文是三部曲的第一部――编码篇。

编写0 Bug的代码,是每个程序开发人员的梦想,但这需要相关人员有很强的能力和经验,并在分析、设计、编码和测试阶段投入大量的时间、精力,但由于种种原因,效果往往不是很好。

本文介绍了一种低投入、高回报的编码技巧,可以帮助C/C++程序人员减少编码阶段的Bug,争取实现整个程序的0Bug。

限制

本文有如下限制:

1.     只适用于C/C++等通过函数返回值判断调用是否正确的语言(可能有些别的语言也适合),不适用于Java/C#这种靠异常进行错误处理的语言;

2.     只适用于编码阶段,不涉及分析和设计阶段。

原理

目前各种语言中常用的错误处理机制有以下几种:

1.     函数返回错误值 (如 COM返回的 HRESULT 和 Linux下的 pThread 函数族 );

2.     函数为表示错误而设定flag值 (如 Windows 的SetLastError 和 Linux 的 errno );

3.     抛出异常(Java 和 C#)

 

对于前两种错误机制的处理上,编码人员一般有两种做法:

1.     对大部分的返回值不予判断,认为程序的运行不会出现那些错误。虽然代码清爽了,但实际运行环境下当程序中出现函数调用失败时,由于没有及时处理,就留下了Bug隐患,直到N久之后才发作。于是程序员就需要花费大量的时间、精力去再现、确认、更改Bug;

2.     对所有函数调用的地方都进行判断和处理。于是代码中出现大量的if…else等分支判断,造成程序的编写、维护工作量大幅上升,但是很多代码估计永远都不会执行(谁能告诉我正常情况下CloseHandle什么时候会失败,失败后又该做什么?),而且往往在函数调用失败后,不判断具体的错误信息,只是简单的进行返回。

 

以上无论哪一种方法,都给开发人员增加了不少工作负担,而且代码出现了Bug也很难立即发现。因此,我通过设计、实现出CSTD(Code Self Test  Development) 技术,自动对函数的返回结果进行检查,发现任何不对的地方,立即将错误的相关信息输出(日志和断言等),使得程序员可以专注于应用程序的逻辑,但又不会忽略任何可能的错误。

 

方法

以WindowsAPI的错误处理为例,介绍本方法。通常的错误处理逻辑为:

1.     调用函数(如CreateFile),判断返回值(如是否等于 INVALID_HANDLE_VALUE)

2.     如果有错误发生,通过 GetLastError 函数获取详细的错误码数值( 如 2 )

3.     通过 Error Lookup 工具或FormatMessage 函数获取错误的详细信息(如ERROR_FILE_NOT_FOUND)

4.     分析具体的错误类型,决定错误处理机制。

将以上步骤通过简单的XXX_VERIFY 宏进行包装,保证用最少的代码量完成函数调用、错误检查、日志输出、断言提示等功能。其宏定义如下:

#ifdef  FTL_DEBUG 

# define API_VERIFY(x)   \

bRet = (x);\

    if(FALSE ==bRet)\

    {\

      DWORD dwLastError = GetLastError();\

     REPORT_ERROR_INFO(FTL::CFAPIErrorInfo, dwLastError,x);\

     SetLastError(dwLastError);\

    }

#else   

# define API_VERIFY(x)   \

 bRet= (x);

  #endif

详细分析和介绍

1.      通过自定义的宏(本处是 FTL_DEBUG)将错误处理机制区分为“错误发现”和“正常运行”版本,之所以不选择系统定义的 DEBUG/_DEBUG,是为了在Release版本下也可以启用错误发现机制。无论哪种版本,都会执行 (x) 指定的代码,并将返回值赋给bRet变量。

2.      在“错误发现”版本下,如果发现错误(返回值为FALSE),就通过GetLastError获取错误码,通过 CFAPIErrorInfo 类获取对用户友好的详细信息,并由REPORT_ERROR_INFO[h1]  宏输出日志和断言(此处的宏没有采用 TRACE 和 ASSERT 等系统自带的,目的也是为了在Release版本下能启用错误发现机制)

3.      为了防止错误发现机制对LastError的影响,在处理结束前使用SetLastError 恢复原有的错误码,保证之后的处理逻辑正确。

注意

1.      XXX_VERIFY 只是帮助发现和更改错误的辅助机制,绝对不是错误处理逻辑。在发生错误时,一定要根据错误码进行后续的错误处理。对于大多数正常情况下不会、不该出错的代码(如 CloseHandle),可以简单使用 XXX_VERIFY 即可,但对于可能出错的代码(如 CreateFile ),可以参考例子程序中的用法。

2.      本方式有一个“副作用”,即必须存在特定名字的变量(如 bRet ),而且每次调用后都会更改这个变量的值。通常情况下,这是我们需要的结果。但是有的时候,会覆盖掉我们需要的结果。比如 CreateFile 成功,但后续处理失败时,需要返回对应错误码,并关闭文件Handle,如果对CloseHandle进行XXX_VERIFY,则会覆盖需要返回的 bRet 变量。这时一般有两种方法:使用其他的变量保存需要的返回码 或 只用 VERIFY 等宏对CloseHandle进行执行和断言。

3.      Java语言使用异常机制,在编译时通过编译错误来保证对可能发生的错误进行处理;而XXX_VERIFY使用断言机制,在运行时通过断言错误来保证对可能发生的错误进行处理[f2] 。因此可见,XXX_VERIFY 的机制比Java语言提供的机制有很大差距,但已经可以尽量发现代码中的错误了。

示例

通过编写测试用的示例程序,详细介绍 XXX_VERIFY 宏的使用方法和注意事项

 

扩展

1.      通常来说,合理使用XXX_VERIFY宏,能在很少投入的情况下(只需将原有代码中的“函数调用”换成“XXX_VERIFY(函数调用)”)发现和解决大部分的编码Bug,但如果结合敏捷开发中的TDD,将发挥更大威力。使用UT搭建自动化运行的框架并对功能进行测试,代码内部通过 XXX_VERIFY 进行测试。在分析、设计时仔细考虑一下,加上开发人员的责任心(实际上这才是实现0Bug程序的根本),再通过这两个工具的结合,实现出 0 Bug的程序将不再是梦想[f3] 。

2.      推荐大家阅读一下 John Robbins 所编写的 《Microsoft .NET和Windows应用程序调试》,其中提到的“防御性编程”对大家高效开发0Bug的代码很有帮助。大家会发现我这里的 API_VERIFY 和其中的 SUPERASSERT 大同小异,可说是英雄所见略同(自夸一下J )。

 


 [h1]可以随时更改该宏来更改错误报告机制—如Release下发现错误就记录日志

 [f2]随着代码、运行环境规模的增大,罕见的错误将变为常见的错误――如果没有类似的保证措施,将如何及时发现错误?

 [f3]我在设计和实现多线程的流水线模版框架时,只被测试人员发现了一个式样上的Bug――其他Bug在他们发现之前都被我发现并更改完毕了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值