软件体系结构————防御性编程
防御性编程的主要思想:子程序不应由于不正确的数据被破坏传递,即使是由其他的子程序产生的错误数据。更普遍。的核心思想是要认识到,程序将有问题,既需要改变。
保护程序免遭非法输入数据的破坏:
1.检查全部源于外部的数据的值
当从文件、用户、网络或其它外部接口中获取数据时,应检查所获得的数据值。以确保它在同意的范围内。对于数值,要确保它在可接受的取值范围内;对于字符串。要确保其只是长。
假设字符串代表的是某个特定范围内的数据,那么要确认其取值合乎用途,否则就应该拒绝接受。
假设在开发过程中须要有安全的应用程序,还要格外注意那些狡猾的可能是攻击你的系统的数据,包含企图令缓冲区溢出的数据、注入的SQL命令、整数溢出以及传递给系统调用的数据。等等。
2.检查子程序全部输入參数的值
检查子程序输入參数的值,其实和检查来源于外部的数据值一样。仅仅只是数据时来自于其它子程序而非外部接口。
3.决定怎样处理错误的输入数据见以下的解说
断言:
断言是指在开发期间使用的、让程序在执行时进行自检的代码(一般是一个子程序或宏),它对于大型的复杂程序或可靠性要求极高的程序来说尤为实用。通过使用断言,能够高速排查出因改动代码或者别的原因。而弄进程序里的不匹配的接口如果和错误等。
正常情况下,并不希望用户看到产品代码中的断言信息;断言主要是用于开发和维护阶段。
通常,断言仅仅是在开发阶段被编译到目标代码中。而在生成产品代码时并不编译进去。在开发阶段,断言能够帮助查清相互矛盾的假定、预料之外的情况以及传给子程序的错误数据等。
使用断言的建议:
1.用错误处理代码来处理预期会发生的状况,用断言来处理绝不应该发生的状况
2.避免把须要运行的代码放入断言中:假设把代码写在断言里,那么当关闭断言功能时,编译器非常可能就把这些代码排除在外不运行了。最好将函数的返回值传递到一个暂时变量中,然后让断言推断这个暂时变量。
3.用断言来注解并验证前条件和后条件:
前条件是子程序或类的调用方代码在调用子程序或实例化对象之前要确保为真的属性。
后条件是子程序或类在运行结束后要确保为真的属性。后置条件是子程序或类对调用方代码所承担的责任。
4.对于高健壮性的代码,应该先使用断言再处理错误
假设变量都来源于系统外部。那么就应该用错误处理代码来检查和处理非法的数值。而不是使用断言。而假设变量的值是源于可信的系统内部,而且这段程序是基于这些值不会超出合法范围的假定而设计,使用断言则是很合适的。
错误处理技术:
断言能够处理代码中不应该发生的错误,那么该怎样处理那些预料中可能要发生的错误呢?
1.返回中立值:返回一个对程序没有危害的数据。(数值能够返回0,指针能够返回空)
2.换用下一个正确的数据
3.返回与前次同样的数据
4.换用最接近的合法值(如某个数超过了某个界限,则返回界限的边界值)
5.把告警信息记录到日志文件里
6.返回一个错误码
7.调用错误处理子程序或对象:长处在于能够把错误处理的职责集中,从而让调试工作更加简单。可是代价是整个程序都要知道这个集中点并与之紧密耦合。
8.当发生错误时显示出错误消息:这样的方法能够把错误处理的开销减到最小,可是也有可能让用户界面中出现的信息散布到整个应用程序中。并且攻击者可能会利用错误信息来发现怎样攻击这个系统。
9.用最妥当的方式在局部处理错误:这样的方法给每一个程序猿非常大的灵活度。但也带来了显著的风险,即系统的总体性能将无法满足对其正确性或可靠性的需求。
10.关闭程序:有些系统须要一旦检測到发生错误就会关闭。
健壮性与正确性:
正确性:意味着永不返回不准确的结果。哪怕不返回结果也比返回不准确的结果好。
健壮性:意味着要不断尝试採取某些措施,以保证软件能够持续地运转下去,哪怕有时做出一些不够准确的结果。
高层次设计对错误处理方式的影响:
应该在整个程序里採用一致的方式处理非法的參数。确定一种通用的处理错误參数的方法。是架构层次的设计决策,须要在那里的某个层次上解决。
除非已确立了一套不正确系统调用进行错误检查的架构性知道建议。否则请在每一个系统调用后检查错误码。一旦检測到错误,就记下错误代号和它的描写叙述信息。
异常:
异常就是把代码中的错误或异常事件传递给调用方代码的一种特殊手段。假设在一个子程序中遇到了预料之外的情况。但不知道该怎样处理的话。这就能够抛出一个异常。对出错的前因后果不甚了解的代码,能够把对控制权装交给系统中其它能更好解释错误并採取措施的部分。
建议:
1.用异常通知程序的其它部分,发生了不可忽略的错误:相比错误处理机制有可能会导致错误在不知不觉中向外扩散。而异常则消除了这样的可能。
2.仅仅在真正例外的情况下才抛出异常:异常须要做出一个取舍,一方面它是一种强大的用来处理预料之外情况的途径。还有一方面程序的复杂度会因此添加。因为调用子程序的代码须要了解被调用代码中可能会抛出异常。因此异常弱化了封装性。
3.不能用异常来推卸责任:假设某种的错误情况能够在局部处理,那就应该在局部处理掉它。不要把本来能够在局部处理的错误当成一个未被捕获的异常抛出去。
4.避免在构造函数和析构函数中抛出异常,除非在同一地方把他们捕获:这会让异常规则立即就变得很复杂。
5.在恰当抽象层次抛出异常:子程序应在其接口中展现出一仅仅的抽象,类也是如此。抛出的异常也是程序接口的一部分,和其它详细的数据类型一样。
6.在异常消息中增加关于导致异常发生的所有信息:确保信息中的内容easy让人理解异常的原因。
7.避免使用空的catch语句:由于要是做了就要么就是try里的代码不正确,由于它无故抛出了一个异常;要么就是catch中的代码不正确。由于它没能处理一个有效的异常。
8.了解所用函数库可能抛出的异常
9.考虑创建一个集中的异常报告机制
10.把项目中对异常的使用标准化
11.考虑异常的替换方案:应该自始至终考虑各种各样的错误处理机制:在局部处理错误、使用错误码来传递错误、在日志文件里记录调试信息、关闭系统或其它的一些方式等。只由于编程语言提供了异常处理机制而使用异常,是典型的”为用而用“;这也是典型的”在一种语言上编程“而非”深入一种语言去编程“的样例。
隔离程序,使之包容由错误造成的损害:
以防御式编程为目的而进行隔离的一种方法,是把某些接口选定为”安全“区域的边界。对穿越安全区域边界的数据进行合法性校验,并当数据非法时做出敏锐的反应。在类的层次上也能够做相同的处理方式。
在输入数据时将其转换为恰当的类型:由于程序在长时间传递类型不明的数据会添加程序的复杂度和崩溃的可能性。
因此。应该在输入数据后马上将其转换到恰当的类型。
确定在产品中该保留多少防御式代码:
1.保留那些检查重错误的代码
2.去掉检查细微错误的代码
3.去掉能够导致程序硬性崩溃的代码
4.保留能够让程序稳妥地崩溃的代码
5.为你的技术支持人员记录错误信息
6.确认代码中的错误消息的逗留友好
版权声明:本文博主原创文章,博客,未经同意不得转载。