加强警戒(En garde)!
要记住,您的客户对您的产品有与您不一样的想法。他们会在一个您的小组很可能从来也没想到的 —— 或者至少是没有可能测试的 —— 环境中安装它。他们会以您从来没有想到过的方法使用它,并以您意想不到的方法配置它。下面的列表有助于帮助您保证他们不会发怒:
验证所有收到的参数的完整性(考虑如果您期待一个数组而传递来的是一个 null
,但是您在索引数组之前没有检查这种可能性时会发生什么情况)。
考虑所有可能的错误情况并增加处理每种情况的代码(您希望代码得体地处理错误条件而不是堵塞它)。
对于那些未预料到的错误条件,加入一个一般性的“捕获所有”错误处理程序。
在适当的时候和地点使用常量。
在代码各处加入跟踪和日志。
如果您的产品将翻译为另一种语言,那么保证您的代码可以“支持”它。即使出现这种情况的机会很小,但是提前计划总是好一些。修改代码以使它提供支持是最容易产生缺陷的。下面是几个您要考虑的与支持相关的问题:
-
您是否有任何硬编码的字符串?
-
您是否正确地处理不同的日期/时间?
- 不同的货币表示呢?
还有,在代码中使用大量断言。
给您的代码加上充分的 注释。总之,您还记得在六个月前编写那个方法时的想法吗?一年后要修改您的代码的某个人又会怎么想呢?在我们提出的所有建议中,这一条可能是最重要的。
单元测试(防御性测试技术)
在本文中,我们所说的 单元测试 是开发人员在自己的代码正确编译后、在交给功能测试小组之前进行的所有测试和分析。正如我们在 这只是一个测试 中提到的,主动进行单元测试并 在测试时像一位测试者那样思考(即,必须往坏处想、热衷于破坏并喜欢恶作剧)是很重要的。下面是在单元测试时要记住的几件事。
静态代码分析工具
第一种,也是最容易的分析代码的方法是让别人替您做 —— 或者像在这里一样,让其他 工具 替您做。有一些不同的静态代码分析工具可用,从综合性的工具 —— 一些开发机构实际上在他们的“编译”环境(这可是需要购买的)中加入了这样的工具 —— 到其他可以免费从 Internet 上下载的工具。
发现缺陷
当您准备运行代码并检查缺陷时,要记住往坏处想。这些缺陷是您所创建的或者由您忽略的代码产生。下面是一些帮助您找到代码中缺陷的提示:
-
试着强行制造您所想到的所有错误条件并检查可以出现的所有错误消息。
-
试着使用与其他组件或者程序交互的代码路径。如果其他程序或者组件还不存在,那么就自己编写一些脚手架代码以使您可以试用 API 或者填充共享内存、共享队列,等等。并让您的功能测试小组可以使用这个脚手架代码,这样他们就可以把它加入到他们的武器库中。
-
对于 GUI 中的每一个输入字段,试验下面多种不同的组合(考虑 自动化):
-
不可接受的字符(控制字符、非打印字符等)。
-
过多的字符。
-
过少的字符。
-
负数(特别是当您只期待正数时)。
-
过大和/或者过小的数。
-
剪切和粘贴数据和文本到输入字段,特别是当您编写的代码限制用户可以键入该字段的内容时。
-
文本和数字的组合。
- 全大写字母和全小写字母。
-
为代码创建“压力条件”,如大量活动、慢连接的网络和所有您想到的可以将代码推到极限条件的东西。
-
反复进行同样的步骤,然后:
-
检查未预计到的内存损失条件。
-
检查当内存用光时发生什么。
- 试图创建缓存溢出、满队列、不可用的缓存以及其他“不能正确工作”的情况。
- 对于数组和缓存,试着向数组(或者缓存)增加
n
个数据项,然后试图删除 n+1
个项。
关于时间的考虑?
如果操作“b”在操作“a”之前发生会怎么样?就算您 认为 它不会发生 —— 您能 使 它发生吗?如果是的话,可以打赌您的客户会使它发生的。最好现在找出来,而不是在修复成本更高、并听到客户报怨您的软件质量糟糕之后再去做。
脚手架代码
我们在前面 发现缺陷 中讨论了脚手架代码。如果是为自己的使用需要而创建的,一定要将它交给验证工程师。可能您提供的脚手架代码使他们可以很快地开始测试您的代码,或者至少使他们更好地理解当其他组件存在时可以预期什么。
如果您的产品有保护性的安全功能,那么您必须测试它们。提供可以创建您想要防止的情况的脚手架代码是很重要的:您必须能够创建系统试图防止的那种情况。
脚手架代码的另一个简单例子是提供操纵队列的代码。如果您的产品使用队列,那么想像如果有一个可以在运行时从队列中增加或者删除项、破坏队列中的数据(以保证适当的错误处理)等等的工具会有多方便。
源代码级调试程序
使用源代码级的调试程序是进行彻底和成功的单元测试的关键方法。开发人员应该与他们的调试程序共生死。不幸的是,对源代码级的调试程序的充分了解和使用是一种正在消亡的做法,尽管这些调试程序的好处远远超过任何学习曲线。简而言之,我们强烈鼓励您全面学习一种调试程序。下面是用源代码级调试程序对代码进行单元测试的几种方法。您可以:
-
在运行中操纵数据 —— 例如,在输入代码时设置中断点,然后重新设置传递的参数值以检查代码是否能正确处理(现在为)无效的参数。以这种方式使用调试程序就不需要让错误条件真正发生。
-
设置断点,然后“单步”通过代码,这样您就可以看到每一行代码所做的事情。
-
设置对变量的“监视(watch)”。
-
强制使用错误代码路径。
-
观察调用堆栈以检查哪一个例程调用了您的代码。
-
在错误发生时“捕获”它们。
- 执行边界检查。
认识您的验证工程师
验证工程师是测试知识的很好来源。他们可以给您指出要测试什么并帮助您了解可以在代码中加入什么以帮助他们测试(如代码钩子)。此外,您可以向他们展示如何使用您的脚手架代码。他们还会很有兴趣了解您认为在测试中哪些应该是自动化的 —— 如果您某些事情做了不止一遍,那么他们也会。
开始测验!
现在是进行小测试的时候了。让我们看看您是否用心了。
问题
您希望检查一个整数的值是否为 5。通常,要这样编写代码:
<ccid_nobr></ccid_nobr>
<ccid_code><font size="4"><font color="#a52a2a">if ( i == 5 ) then
{
//
// do something...
//
}</font></font></ccid_code> |
不过,如果您对代码进行“手指检查”,并且把代码写成了下面这样会出现什么情况呢?
<ccid_nobr></ccid_nobr>
<ccid_code><font size="4"><font color="#a52a2a">if ( i = 5 ) then
{
//
// do something...
//
}</font></font></ccid_code> |
这个失误是一个缺陷,但是只有在运行时才能捕获它 —— 可能需要相当的调试努力才能找到它。编译器会轻易放过您的代码,那么如何防止这种错误发生?
答案
实际上有两个答案:您可以使用一种上面描述的静态代码分析工具,并希望它有足够的健壮性可以捕获这种错误,也可以交换操作数以使常量位于左边:
<ccid_nobr></ccid_nobr>
<ccid_code><font size="4"><font color="#a52a2a">if ( 5 == i ) then
{
//
// do something...
//
}</font></font></ccid_code> |
因为这种方法保证您可以在编译代码时立即捕捉到问题,所以它是首选的技术。虽然它看上去有些笨,但是代码可以编译并运行得很好。然而,当您“手指检查”代码时就可以立即看到好处了:
<ccid_nobr></ccid_nobr>
<ccid_code><font size="4"><font color="#a52a2a">if ( 5 = i ) then
{
//
// do something...
//
}</font></font></ccid_code> |
可是编译器不喜欢这样,因为 5 不能被赋值为另一个值。这就是我们在 前面 说您应当将编译器看成是您的朋友时所表达的意思。
您还可以在检查 null 指针时使用这个技巧。看下面的代码:
<ccid_nobr></ccid_nobr>
<ccid_code><font size="4"><font color="#a52a2a">if ( returnString == null )
{
//
// do something...
//
}</font></font></ccid_code> |
如果您偶然将它“误写”成下面这样会发生什么呢?
<ccid_nobr></ccid_nobr>
<ccid_code><font size="4"><font color="#a52a2a">if ( returnString = null )
{
//
// do something...
//
}</font></font></ccid_code> |
您可能不会得到想要的结果。而改用下面的方法您会得到与我们刚描述过的同样的“编译器保护”:
<ccid_nobr></ccid_nobr>
<ccid_code><font size="4"><font color="#a52a2a">if ( null == returnString )
{
//
// do something...
//
}</font></font></ccid_code> |
结束语
为保持简明扼要我们做了一个相当简洁的归纳:要么现在去做,要么以后花 多得多 的代价去做。换句话说,您在开发周期的早期在测试和预防代码缺陷上花的时间越多,您在以后节省的时间和金钱就越多。这就是防御性编码的意义。它就是这么简单。
分享到:
相关推荐
同时,为了确保系统的健壮性,应遵循防御性编程原则,对输入进行严格的校验,并防止潜在的运行时错误。 在实现编码生成时,可能采用两种常见的策略:顺序编码和基于规则的编码。顺序编码通常适用于数量庞大且不断...
Web系统安全和渗透性测试是保护在线业务免受恶意攻击的关键环节。在这个领域,了解基本概念、技术以及最佳实践至关重要。下面将详细阐述这个主题。 首先,我们要理解Web系统安全的核心概念。Web系统是由服务器、...
阿里推荐编码规范是阿里巴巴集团为提升代码质量和团队协作效率而制定的一套标准,它涵盖了多个编程语言和开发领域的最佳实践。这些规范不仅适用于Java、Python、JavaScript等常见编程语言,还包括数据库设计、版本...
静态代码安全测试(Static Application Security Testing,简称SAST)是一种在软件的编码和设计阶段分析源代码、字节码或二进制文件以识别潜在安全漏洞的技术。这种测试方法不需要运行被测应用程序,可以在软件开发...
代码注入技术允许开发者将自定义的代码片段插入到运行中的进程,以此来操控或扩展程序的行为,以测试其稳定性和安全性。在游戏领域,外挂Call测试主要是为了检测游戏客户端或服务器是否能够正确处理非法或异常的调用...
1. **Metasploit框架**:Metasploit框架是渗透测试的核心工具,它包括了漏洞数据库、exploits(漏洞利用代码)、payloads(有效载荷)和encoders(编码器)。用户可以通过它快速地构建和执行攻击链路,以测试目标...
- 测试类型包括单元测试、集成测试、系统测试、验收测试等,以及白盒、黑盒和灰盒测试。 - 软件问题从错误到缺陷,再到故障和失效,有明确的层次关系。 - 测试标准如GB/T 16260.1和GB/T 18905系列规范指导软件...
测试规范的目的是指导开发人员和测试人员通过一系列流程、规则和技术手段来发现和修补这些漏洞。 规范主要涉及以下几个方面: 1. 输入验证:这是防止注入攻击(比如SQL注入和XML注入)的第一道防线。测试规范需要...
在一些关键应用 (如民航订票系统、银行结算系统、证券交易系统、自动飞行控制软件、军事防御和核电站安全控制系统等) 中使用质量有问题的软件,还可能造成灾难性的后果。 软件危机曾经是软件界甚至整个计算机界最...
Fortify SCA的规则库包含了大量针对不同编程语言(如Java)的预定义规则,这些规则基于已知的安全最佳实践和常见的安全漏洞模式。当开发者在项目中引入这些规则时,Fortify SCA能够自动检查代码,找出可能存在的安全...
在开发过程中,重视安全性和防御性编程是必不可少的。这包括验证输入数据、防止SQL注入和XSS攻击、加密敏感信息,以及使用异常处理来增强应用程序的健壮性。安全意识应贯穿于整个开发周期,确保软件的安全性和用户...
- **正确性**:示例代码需准确展示其意图,经过严格的测试,确保可以按照文档描述成功编译和运行。 - **一致性**:示例代码应遵循统一的编程风格和设计规则,确保不同示例之间具有一致性,方便用户组合使用。 - **...
同时,结合编码函数入口地址和编码前内存地址,该方法还能生成符合协议完整性的测试数据,这极大地提升了对编码协议潜在漏洞的发现能力。 总的来说,这篇研究论文提出的网络编码协议污点回溯逆向分析方法,为网络...
7. **防御性编程**:提倡采取预防措施以减少错误发生的可能性,比如使用断言和边界检查等技术。 8. **其他规则**:涵盖了一些特定场景下的编程建议,例如国际化支持、性能优化等。 通过上述内容,我们可以看到...
2. 配置和部署管理测试:检查服务器配置是否安全,如防火墙规则、更新策略、错误处理机制等。不正确的配置可能导致安全漏洞,使得攻击者能够利用。 3. 搜索引擎侦察:利用搜索引擎查找敏感信息,如内部文档、员工...
防御性程序设计原则旨在减少错误的发生,包括初始化所有变量、编写清晰的注释、避免使用全局变量、对齐大括号以增强可读性、避免假设和重视编译器的警告信息。 在实践中,常见的低级错误主要包括混淆赋值和比较操作...
5. 应用防火墙(WAF)测试:验证WAF规则的有效性,确保能有效阻止已知攻击。 6. 安全性能测试:评估安全措施对系统性能的影响,确保安全与性能之间的平衡。 四、安全管理体系 1. 安全政策:建立全面的安全政策,...