防御式编程(Defensive Programming)

作者 : 开心源码 本文共11706个字,预计阅读时间需要30分钟 发布时间: 2022-05-12 共173人阅读

写在前面

新的一年开始了,大家都立下了什么新年flag呢?好久没有升级简书了,最近在看《代码大全》对于第八章的防御式编程颇有感慨,正好最近又是在准备公司的技术分享,索性用md写了篇博文,总结了一下又添加了少量我个人的了解,在这里跟各位分享一下。

Defensive Programming

防御式编程(Defensive Programming)是提高软件质量技术的有益辅助手段

怎样了解呢?防御式编程思想的了解可以参考防御式驾驶:

在防御式驾驶中要建立这样一种思维,那就是你水远也不能确定另一位司机将要做什么。这样才能确保在其余人做出危险动作时你也不会受到伤害。你要承担起保护自己的责任,哪怕是其余司机犯的错误。

防御式编程的主要思想是:

子程序应该不因传入错误数据而被破坏,哪怕是由其余子程序产生的错误数据。

更一般地说,其核心想法是要承认程序都会有问题,都需要被修改,聪明的程序员应该根据这一点来编程序,这种思想是将可能出现的错误造成的影响控制在有限的范围内。

保护程序免遭非法输入数据的破坏

计算机领域有着一句GIGO(Garbage In Garbage Out)俗语,翻译过来就是垃圾进,垃圾出,意思就是有垃圾数据进来后,出来的也是垃圾数据

而就目前而言,对于已经成型的产品可能单单是这种准则并不适用,而是应该做到垃圾进,什么也不出垃圾进,出去的是错误提醒垃圾进,经过挑选提取,出去的是有用信息或者是不许垃圾进来。换句话说,GIGO于今天的标准看来已然是差劲程序的标志了。

防御式编程针对垃圾进这种情况,有以下三种方法解决:

1、检查所有来源于外部的数据

当从文件、客户、网络或者其余外部接口中获取数据时,应检查所取得的数据值,以确保它在允许的范围内。

2、检查子程序所有输入参数的值

检查子程序输入参数的值,事实上和检查来源于外部的数据一样,只不过数据来源于其余子程序而非外部接口。

3、决定如何解决错误的输入数据

一旦检测到非法的参数,你该如何解决它呢?根据情况的不同,你可以选择适合你的错误解决技术或者断言来解决。

接下来我们将针对以上所说的情况讲解防御式编程中需要掌握的方式:

断言

断言是指在开发期间使用的、让程序在运行时进行自检的代码(通常为宏或者一个子程序)。断言为真则程序正常运行,断言为假则意味着代码中发生了错误。

举个例子:一份用户信息程序要求包含记录数不超过10,我们加一个断言。当记录数小于10,断言会默默无语两眼泪,当超过10,断言就会大声的说程序中存在一个错误!

断言对于大型复杂程序或者可靠性要求极高的程序来说尤为重要。通过使用断言,程序员能更快速排查出因修改代码或者者别的起因,而弄进程序里不匹配的接口和错误等。

OC中内置的断言:(iOS每个线程都可以指定断言解决器。想设置一个 NSAssertionHandler 的子类来解决失败的断言,在线程的 threadDictionary 对象中设置 NSAssertionHandlerKey 字段就可

对NSAssertionHandler有兴趣的童鞋请移步:传送门

#define NSAssert(condition, desc, ...)  \    do {                \    __PRAGMA_PUSH_NO_EXTRA_ARG_WARNINGS \    if (__builtin_expect(!(condition), 0)) {        \            NSString *__assert_file__ = [NSString stringWithUTF8String:__FILE__]; \            __assert_file__ = __assert_file__ ? __assert_file__ : @"<Unknown File>"; \        [[NSAssertionHandler currentHandler] handleFailureInMethod:_cmd \        object:self file:__assert_file__ \            lineNumber:__LINE__ description:(desc), ##__VA_ARGS__]; \    }               \        __PRAGMA_POP_NO_EXTRA_ARG_WARNINGS \    } while(0)#endif

下面将详情一下断言使用时的建议:

1、建立自己的断言机制

很多时候,系统自带的断言无法满足我们的需求,比方iOS断言在release模式下会失效,那么我们可以自己设置断言来适应我们的项目

下面是C++的断言宏示例:

#define ASSERT(condition, message) {    \    if (!condition) {                   \        Log("ERROR ",condition,message);\        exit( EXIT_FAILURE );           \    }                                   \}                                       \

OC中示例:

#define WYHAssert(condition, desc)  \if (DEBUG) {                        \   NSAssert(condition, desc);       \}else {                             \   NSString *app_build = [[NSBundle mainBundle].infoDictionary objectForKey:@"CFBundleVersion"]; \NSLog(@"Assert Error condition:%@ (desc: %@) \n Occur in <%s><第%d行> , AppBuildVersion:%@",condition,desc,__FILE__,__LINE__,app_build); \   [LogModule postLog];                            \} \

2、用错误解决代码解决预期发生的状况,用断言去解决那些不可发生的错误!

断言和错误解决代码的区别:

断言是用来检查永远不该发生的情况,而错误解决代码(error-handling code)是用来检查不太可能经常发生的情况,这些情况是能在写代码时被意料的,且在产品正式上线时也要解决这些情况,因此说错误解决通常用来检查有害的输入数据,而断言是用于检查代码中的bug !

有种方式可以让你更好了解断言:

把断言看做是可执行的注解,你不能依赖它来让代码正常工作,但与编程语言中的注解相比,它更能主动地对程序中的假定做出说明。

3、利用断言来注解前条件和后条件

前条件(先验条件)和后条件(后验条件)专有名词最初来自于契约式设计(Design by Contract)(DbC),使用契约式设计时,每个子程序或者类与程序的其他部分都形成了一份契约。

很多语言都有对这种断言的支持。然而DbC认为这些契约对于软件的正确性至关重要,它们应当是设计过程的一部分。实际上,DbC提倡首先写断言。(百度百科)

前条件:子程序或者类的调用方代码再调用子程序或者实例化对象之前要确保为真的属性。前条件是调用方对其所调用的代码要承担的义务。

后条件:子程序或者类在执行结束后要确保为真的属性,后条件是子程序或者类对调用方代码所承担的责任。

而断言是用来说明前后条件的有利工具。

下面举个例子说明:

/// 警报站坐标private class EStationCoordinate: NSObject {    var latitude: Float?        var longitude: Float?        var elevation: Float = 0.0        init(_ latitude: Float,_ longitude: Float,_ elevation: Float) {        super.init()                self.latitude = latitude        self.longitude = longitude        self.elevation = elevation    }}/// 获得报警站坐标////// - Parameters:///   - latitude: <#latitude description#>///   - longitude: <#longitude description#>///   - elevation: <#elevation description#>/// - Returns: <#return value description#>private func createEmergencyCoordinate(_ latitude: Float,_ longitude: Float,_ elevation: Float) -> EStationCoordinate {        // precondition    assert(-90 <= latitude && latitude <= 90, "latitude must within range !");        assert(0 <= longitude && longitude < 360, "longitude must within range !");        assert(100 <= elevation && elevation < 500, "elevation must within range !");        // handle .... searching in local        // postcondition    assert(isContain, "local not contain this coordinate !")        var coordinate = EStationCoordinate(latitude,longitude,elevation)        return coordinate    }

假如变量latitude、longitude和elevation都是来源于系统外部,那么就应该用错误解决代码来检查和解决非法的数值,而假如变量的值是源于可信的系统内部,并且这段程序是基于这些值不会超出合法范围的假定而设计,使用断言则是非常合适的。

4、避免将需要执行的子程序放到断言中

假如把需要执行的子程序代码写在断言的codition条件里,那么当你关闭断言功能时,编译器很可能就把这些代码排除在外了,下面举一个例子:

- (void)postFileToServer {        // .... make file        NSAssert([self compressFileToZip], @"File can't be compressed !");        // ... post to server }- (BOOL)compressFileToZip {        //... compress file and create a zip path !    if (zipPath.length > 0) {                return YES;    }    return NO;}

这样假如未编译断言,则condition语句的子程序也将不会执行,应修改为以下:

- (void)postFileToServer {        // .... make file    BOOL isCompressSuccess = [self compressFileToZip];        NSAssert(isCompressSuccess, @"File can't be compressed !");        // ... post to server}

错误解决技术

前面我们提过了,断言是解决程序代码中那些不应发生的错误,那么又如何解决那些我们意料之内的可能发生的错误呢?

首先我们要明确对于程序而言,解决错误最恰当的方式是要根据程序软件的类别而定,进而言之就是对于程序的两个概念:健壮性与正确性

程序的健壮性

定义:健壮性具体指的是系统在不正常的输入或者不正常的外部环境下仍能体现出正常的程度。

健壮性的准则:

  • 不断尝试采取措施来包容错误的输入以此让程序正常运转(对自己的代码要保守,对客户的行为要开放)
  • 考虑各种各样的极端情况,没有impossible
  • 即便终止执行,也要精确/无歧义的向客户展现全面的错误信息
  • 错误信息有助于进行debug

例如:视频游戏中的绘图子程序接收到了一个错误的颜色输入,那么在设计的时候可以针对这种情况,采用它的默认背景色或者前景色继续绘制,而不是让程序崩溃或者退出。

程序的正确性

定义:正确性意味着程序永不返回不精确的结果,即便这样做会不返回结果或者是直接退出程序。

例如:在设计控制治疗癌症病人的放疗设施的软件时,当软件接收到错误的放射剂量,那么也许直接关闭程序就是最佳的选择,哪怕重启也比冒险施放错误的放射剂量要好的多。

总结,两者之间的区别在于:

  • 正确性:永不给客户错误的结果,哪怕是退出程序
  • 健壮性:尽可能的保持软件运行而不是总是退出

理解了程序的健壮性与正确性,我们即可以采用以下几种手段,或者结合起来使用错误解决技术:

1、返回中立值:

有时,解决错误的最佳做法就是继续执行操作并简单的返回一个没有危害的值。

比方,一个基于输入颜色的绘图子程序接收到了一个错误的颜色输入,它可以忽略这个错误的颜色,而是采用默认的底色或者前景色继续进行绘制,而不是直接崩溃。

2、换用下一个正确的数据

在解决轮询查询状态的子程序时,假如某次查询出的输出数据错误或者有误,大可以忽略本次错误的数据,继续等待下一次轮询时读取正确的数据
(例如,假如你以每秒100次的速度读取体温计的数据,假如某一次得到的数据有误,我们可以再等上1/100秒后继续读取正确的数据)

3、返回与前次相同的数据

还是举上一个例子,假如体温计在1/100秒读取到的是一个错误数据,那么大可以返回上一次正确的数据,由于温度在1/100秒内变化不会太大。

4、换用最接近的合法值

比方,当我们在编写一个滑块在规定区域内滑动的程序时,假如滑块超过规定区域,我们可以取最接近于超过区域的安全数值返回。

5、把警告信息记录到日志文件中

在检测到错误数据时,可以选择在日志文件中记录一条警告信息,而后继续执行。

6、返回一个错误状态码

可以决定只让徐彤的某些部分解决错误,其余部分则不在局部解决错误,而是简单的返回一个错误码。

比方在客户信息编辑页面有一个保存按钮,当某些信息填写错误时,这时只是记录一个错误码,当点击保存按钮时才去判断验证这个错误码能否存在,决定能否允许客户执行下一步操作

7、调用错误解决子程序或者对象

把错误解决都集中在一个全局的错误解决子程序或者对象中,这种方法优点在于能把错误解决的职责集中到一起,从而让调试变得更简单。而代价则是整个程序都要知道这个集中点,并与之紧密耦合。

什么意思呢?比方在一系列有上下文关系的请求中,针对所有的请求错误,我们只封装一个错误管理类来集中管理这些错误。

8、当错误发生时显示出错消息

这种方法可以把错误解决的开销减到最小,然而你需要衡量此时的错误消息对于客户而言能否是友善的,相反对于攻击者而言,尽量不要让他们利用错误信息来发现如何攻击这个系统。

9、关闭程序

有少量更偏向于正确性的程序,当检测到错误发生时,也许关闭程序是最佳的选择。

如上面谈到的癌症病人的放疗设施的软件

异常

异常是把代码中的错误或者异常事件传递给调用方代码的一种特殊手段。

异常解决,英文名为exceptional handling, 是代替日渐衰落的error code方法的新法,提供error code 所未能具体的优势。异常解决分离了接收和解决错误代码。这个功能理清了编程者的思绪,也帮助代码加强了可读性,方便了维护者的阅读和了解。 异常解决(又称为错误解决)功能提供了解决程序运行时出现的任何意外或者异常情况的方法。异常解决使用 try、catch 和 finally 关键字来尝试可能未成功的操作,解决失败,以及在事后清除资源。(百度百科)

假如在一个子程序中遇到了意料之外的情况,但并不知道如何解决的话,你即可以选择抛出一个异常。

异常的基本结构是:子程序使用throw抛出一个异常对象,再被调用链上层其余子程序的try-catch语句捕获。(内建的异常机制都是沿着函数调用栈的函数调用逆向搜索,直到遇到异常解决代码为止)

我知道听到这,一定有人懵逼了,我们来看下面的例子:

+ (void)tryFirstException {    @try {        // 1        [self trySecondException];            } @catch (NSException *exception) {        //2        NSLog(@"First reason:%@",exception.reason);            } @finally {        //3    }    //4}+ (void)trySecondException {    @try {        //5        [self tryThirldException];            } @catch (NSException *exception) {        //6        @throw exception; //假如将这段代码注释后又会怎么?        NSLog(@"Second reason:%@",exception.reason);    } @finally {        //7    }    //8}+ (void)tryThirldException {    //9    @throw [NSException exceptionWithName:@"Exc" reason:@"no reason!" userInfo:nil];}

有人知道程序应该怎样执行吗?

许多常见的程序设计语言,包括Actionscript,Ada,BlitzMax,C++,C#,D,ECMAScript,Eiffel,Java,ML,Object Pascal(如Delphi,Free Pascal等),Objective-C,Ocaml,PHP(version 5),PL/1,Prolog,Python,REALbasic,Ruby,Visual Prolog以及大多数.NET程序设计语言,内建的异常机制都是沿着函数调用栈的函数调用逆向搜索,直到遇到异常解决代码为止。一般在这个异常解决代码的搜索过程中逐级完成栈卷回(stack unwinding)。但Common Lisp是个例外,它不采取栈卷回,因而允许异常解决完后在抛出异常的代码处原地恢复执行。

下面将给予少量使用异常的建议:

1、用异常通知程序的其余部分,发生了不可忽略的错误

异常机制的优越之处,在于它能提供一种无法被忽略的错误通知机制。其余错误解决技术有可能会导致错误在不知不觉中向外扩散,而异常则消除了这种可能性。

2、只在真正例外的情况下才抛出异常

仅在真正例外的情况下才使用异常————换句话说,就是仅在其余编码实践方法无法处理的情况下才使用异常。这种情况跟断言类似————都是用来解决那些不仅罕见甚至永远不该发生的情况。

3、不能用异常来推脱责任

假如某种错误情况可以在局部解决,那就应该在局部解决它。不要把本可以解决的错误当成一个未被捕获的异常抛出去。

4、避免在构造函数和析构函数中抛出异常,除非你在同一个地方把它们捕获

假如尝试在构造函数或者析构函数中抛出异常,则解决异常将变得非常复杂。
比方在C++中,只有对象在完全构造之后才能调用析构函数,也就是说假如再构造函数中抛出异常,就不会调用析构函数,从而造成潜在的资源泄漏。

5、在恰当的笼统层次抛出异常

当你决定把一个异常传给调用方时,请确保异常的笼统层次与子程序的接口笼统层次一致。
(比方在A类的某一子程序中,有一个getDefenseId的方法,在方法中的某些步骤中,我们抛出了一个文件读写错误的异常,这本应由层次更低的内部类F专职去做的事,却错误的在A类中抛出异常,破坏了封装性,也暴露了少量私有的实现细节,这显然不是我们想要的)

6、在异常消息中加入关于导致异常发生的一律信息

比方由于一个索引值错误而抛出的,就应该在异常消息中包含索引的上下界限一级非法的索引下标值等信息。

7、避免使用空的catch语句

不要视图敷衍一个不知该如何解决的异常

8、考虑创立一个集中的异常报告机制

封装一个异常报告的子程序(或者基类)专门快速方便的报告程序中需要主动抛出的异常(异常解决器),将对于异常的使用更加标准化

9、考虑异常的替换机制

尽管少量编程语言对于异常的支持已有5~10年甚至更久的历史,但关于如何安全使用异常的经验依然还是很少。

拿iOS举例,Apple.inc尽管同时提供了错误解决(NSError)和异常解决(Exception)两种机制,但是Apple不建议我们主动去使用Exception,更加提倡开发者使用NSError来解决程序运行中可恢复的错误。而异常被推荐用来解决不可恢复的错误。

Important: In many environments, use of exceptions is fairly commonplace. For example, you might throw an exception to signal that a routine could not execute normally—such as when a file is missing or data could not be parsed correctly. Exceptions are resource-intensive in Objective-C. You should not use exceptions for general flow-control, or simply to signify errors. Instead you should use the return value of a method or function to indicate that an error has occurred, and provide information about the problem in an error object. For more information, see Error Handling Programming Guide.

(developer.apple.com)

感兴趣的童鞋请移步苹果官网 传送门

多说一句,尽管apple不推荐我们经常主动使用Exception,但针对于crash的异常捕捉,iOS是可以通过NSSetUncaughtExceptionHandler来捕获大部分crash的,但值得注意的是无法捕获那些因为内存溢出野指针BAD_ACCESS导致的crash,比方Flurry中对crash解决就是这么运作的。

    - (void) uncaughtExceptionHandler(NSException *exception)     {        [Flurry logError:@"Uncaught" message:@"Crash!" exception:exception];    }     - (void)applicationDidFinishLaunching:(UIApplication *)application    {        NSSetUncaughtExceptionHandler(&uncaughtExceptionHandler);        [Flurry startSession:@"YOUR_API_KEY"];        // ....    }

隔栏

隔栏是防御式编程中的一种容损策略,举个例子,大家可以这样来了解:

船体外壳上装备隔离舱,假如船只与冰山相撞导致船体破裂,隔离舱就会被封闭起来,从而保护船体的其他部分不会受到影响。
隔栏过去叫防火墙,但现在防火墙这一术语常用来代指阻止恶意网络攻击)

如下图:

5毛钱特效翻译过来就是:

左侧外部接口数据假定是肮脏的不可信的,中间这些类(子程序)构成隔栏,负责清除和验证数据并返回可信的数据,最右侧的类(子程序)一律在假定数据干净(安全)的基础上工作,这样可以让大部分的代码毋庸再担负检查错误数据的职责!

这种策略可以拟一个比较生动的例子,可以看做是手术室里使用的一种策略。
任何东西在允许进入手术室之前都要经过消毒解决,因而手术室里的任何东西都可以认为是安全的。这其中最核心的设计决策是规定什么可以进入手术室,什么不可以,还有把手术室的门设在哪里

在编程中也是如此,商定哪些子程序可认为是在安全区域里的,哪些又是在安全区域外的,哪些负责清除数据(完成这一工作最简单的方法是在得到外部数据时,立即进行清除,不过数据往往需要经过一层以上的清除,因而多层清除有时也是必需的)

隔栏的应用:
隔栏的使用使断言和错误解决有了清晰的区分,隔栏外部的程序应该使用错误解决技术,在那里对数据做的任何解决假定都是不安全的。而隔栏内部的程序里就应该使用断言技术,由于传进来的数据应该已在通过隔栏时被清除过了。假如隔栏内部的子程序检测到了错误的数据,那么应该是程序里的错误而不是程序外的错误。

辅助调试代码

防御式编程的另一重要方面就是使用调试助手(辅助调试代码),调试助手非常之强大??,可以帮助我们快速检查错误。

应用在开发期间应牺牲少量速度和对资源的使用,来换取少量可以让开发更顺畅的内置工具。

1、应尽早的引入辅助调试代码

越早进入辅助调试代码,它能够提供的帮助也越大。假如你经常遇到某些问题,尝试自己编写或者引入少量辅助调试代码,它就会自始至终在项目中帮助你。

2、采用进攻式编程

什么又是进攻式编程,其实这也是防御式编程中的一种习惯,其思想在于:

尽量让异常的情况在开发期间暴露出来,而在产品上线时自我恢复。

比方你有一段switch case语句用来解决事件,在开发期间应尽量考虑所有你能意料得到的情况并作出解决,另外在default case语句中,假如是开发阶段可以采用进攻式编程解决,而在产品正式上线期间,针对default case应更稳妥少量,比方记录错误日志。

下面列举一下进攻式编程的方法:

  • 确保断言语句使程序终止运行
  • 完全填充分配到的所有内存
  • 完全填充己分配到的所有文件或者流
  • 确保每一个case 语句中的default分支或者else 分支都能产生严重错误(如终止程序)
  • 在删除一个对象之前把它填满垃圾数据
  • 让程序将错误日志主动用电子邮件或者推送发送给开发者(安防目前采用)

3、计划移除调试辅助的代码

假如是商用软件,调试用的代码有时会使软件的体积变大且速度变慢,从而给程序造成巨大的性能影响。要事前做好准备计划,避免调试用的代码和程序原代码纠缠不清,下面列举少量可以选择的移除方法:

  • 使用相似ant和make这样的版本控制工具
    (可以从同一套源码编译出不同版本的程序。在开发模式下,你可以让make工具把所有的调试代码都包含进来一起编译。而在产品上线期间,把那些调试代码排除在外。)

  • 使用内置的预解决器(C++ #字符为预解决器指令,包含#if、#warning、#include、#define等
    (假如你所用的编程环境里有预解决器,你可以用预解决器来包含或者排除调试的代码)

- (void)handleSomething {    #ifdef DEBUG        [self debugF];//通常为少量debug用的耗时操作#else        [self releaseF];#endif    }
  • 为应用添加调试模式的入口

假如你的应用需要同时支持两种模式(发布和调试),那么我们可以自己设置进入调试模式的入口,而不是针对编译层次的DEBUG,我们的调试代码的嵌入也依赖于这个调试模式能否开启,下面将演示安防app内定义的调试模式。

对防御式编程采取防御的姿态

说了这么多,那么是不是防御式代码越多越好呢?

其实也不是,多度的防御式编程也会引起问题,假如你在每一个能想到的地方都用每一种能想到的方法来检查参数、解决错误,那么你的程序会变得臃肿而缓慢,更糟的事,多度的防御式代码换来的是软件的复杂度。

这说明,防御式编程引入的代码也并非不会有缺陷,和其余代码一样,你同样能轻而易举的在防御式编程增加的代码中找到错误,尤其是当你随手编写这些代时更是如此。

因而,要考虑好什么地方需要进行防御,而后因地制宜地调整你进行防御式编程的优先级。

总结

  • 程序代码中对错误解决的方式远比GIGO复杂的多。

  • 防御式编程技术可以让错误更容易发现问题、更容易修改,并减少错误对产品代码的破坏。

  • 遵从防御式编程的思想去开发,会让你在开发阶段就提前解决了许多问题,提高代码质量,降低测试周期,要做到主动去发现错误并做出解决(千万不要存侥幸心理,明明可以多考虑几种情况,偏偏却要忽略它们的可能性),而不是等到bug隐式的出现所带来的未曾意料的灾难。

  • 错误解决技术更适用于暴露的接口程序或者类,而断言则更强调不可允许的错误,多适用于不暴露在外的私有方法(或者内部类)。

  • 解决错误最恰当的方式是要根据程序软件的类别而定,更倾向于正确性还是健壮性

  • 异常提供了一种与代码正常流程角度不同的错误解决手段,但同时也应该在异常和其余错误解决手段之间进行权衡比较,比方iOS中就很少采用异常解决机制。

  • 正当的运用辅助调试代码,会让你不论是在开发还是发布阶段都能更快速定位问题,并从容地处理问题。(预解决器就是个很好的选择)

最后,我对于防御式编程的了解是,我认为程序的好坏与其健壮性(和正确性)有很大的联络,所有的程序开发人员都要对它有足够的重视,主动去迎战错误,从一点一滴开始做起,不要忽视任何的细节,不能盲目依赖测试去发现bug,而是以测试驱动编程,不断地思考可能发生的问题以进行预防,做一个聪明的程序员。这才是防御式编程所告诉我们的事 !

推荐

最后列举一下文中出现的引用来源及少量推荐看的文章或者书籍:

  • 《代码大全第2版》第八章防御式编程

  • 《Writing Solid Code》断言的使用

  • 《Object-Oriented Software Construction》中有关于契约式设计的前置条件、后置条件、不变式的权威概述。

  • NSHipster 关注被忽略的 Objective-C、Swift 和 Cocoa 特性。每周升级。

  • 苹果对于Exception Handling详情

说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » 防御式编程(Defensive Programming)

发表回复