美文网首页iosiOS奋斗iOS Collection
防御式编程:理解断言

防御式编程:理解断言

作者: 没故事的卓同学 | 来源:发表于2016-06-11 16:10 被阅读2270次
    理论部分摘自代码大全

    在防御式驾驶中要建立这样一种思维,那就是你永远也不能确定另一位司机将要做什么。这样才能确保在其他人做出危险动作时你也不会受到危害。你要承担起保护自己的责任,哪怕是其他司机犯的错误。防御式编程的主要思想是:子程序应该不因传入错误数据而被破坏,哪怕是由其他子程序产生的错误数据。

    断言

    断言是指在开发期间使用的、让程序在运行时进行自检的代码。断言为真,则表明程序运行正常;断言为假,则意味着它已经在代码中发现意料之外的错误。
    断言可以用于在代码中说明各种假定,澄清各种不希望的情形。
    断言主要是用于开发和维护阶段。通常,断言只是在开发阶段被编译到目标代码中。在开发阶段,断言可以查清相互矛盾的假定、预料之外的情况以及传给子程序的错误数据等。

    iOS中的断言语法

    在OC中,断言使用NSAssert函数,是一个宏。基本形式是两个参数,第一个参数是一个bool值:断言的结果,第二个参数是一个描述字符串。断言的结果为false时,第二个参数描述字符串会输出。



    这里举例一段BlocksKit中的代码:

    - (instancetype)initWithBlock:(id)block
    {
        NSParameterAssert(block);
        NSMethodSignature *blockSignature = [[self class] typeSignatureForBlock:block];
        NSMethodSignature *methodSignature = [[self class] methodSignatureForBlockSignature:blockSignature];
        NSAssert(methodSignature, @"Incompatible block: %@", block);
        return (self = [self initWithBlock:block methodSignature:methodSignature blockSignature:blockSignature]);
    }
    

    断言通常用来判断外部输入的参数是否正确,所以cocoa封装了一个检查参数的断言,与NSAssert相比方便的地方就是自动定义了一个输出字符串。
    NSParameterAssert是一个封装了NSAssert的宏,定义如下:

    
    #define NSParameterAssert(condition) NSAssert((condition), @"Invalid parameter not satisfying: %@", @#condition)
    
    

    在来看上面代码中这句断言的使用:

        NSAssert(methodSignature, @"Incompatible block: %@", block);
    

    这段代码的意图是用传入的block来代替某个对应delegate的方法,所以要检查一下传入的block和这个delegate上对应的方法签名是否一致。如果不一致则中断程序,抛出“Incompatible block”提示信息。

    在swift中,断言用assert函数。两个参数和OC中的相同。

    public func assert(@autoclosure condition: () -> Bool, @autoclosure _ message: () -> String = default, file: StaticString = #file, line: UInt = #line)
    

    通过声明可以发现,断言中会记录当前文件和行号,但是这里赋了默认值,所以使用assert时可以省略。
    这里顺带提下代码规范,有些人有时为了方便会把几句代码写在一行。这样写一个潜在的坏处就是如果发生异常,日志中记录了行号。但是因为这一行有几句代码,增加了判断是由具体哪一句代码产生异常。应该避免将几句代码写在一行里。

    断言还有一个经常使用的地方是单元测试。在单元测试中,XCode中封装了几个在测试中经常使用的断言。

    这里列举AFN单元测试中用到了XCTAssert的代码,只截取部分带断言代码:

        //常规的断言
        XCTAssert([NSStringFromClass([task class]) isEqualToString:@"__NSCFBackgroundDownloadTask"]);
    
        
        XCTAssertTrue([[[serializedRequest URL] query] isEqualToString:@"key=value"], @"Query parameters have not been serialized correctly (%@)", [[serializedRequest URL] query]);
        XCTAssertFalse([policy evaluateServerTrust:trust forDomain:nil], @"Policy should not allow server trust because invalid certificaftes are not allowed");
    
        //为nil时是正确的
        XCTAssertNil(error, @"Error handling status code %@", @(statusCode));
        //不为nil时是正确的
        XCTAssertNotNil(error, @"Did not fail handling status code %@",@(statusCode));
    
        //前两个表达式结果相等时正确
        XCTAssertEqual(reachable, weakManager.isReachable, @"Expected status to match 'isReachable'");
    
    

    XCT(Xcode Test)中还有很多类似的断言,有兴趣可以自己查文档,这里就不列举了。

    断言使用的原则

    用错误处理代码来处理预期会发生的状况,用断言来处理绝不应该发生的状况

    错误处理代码用来检查不太可能经常发生的非正常情况,这些情况在写代码时就预料到的,而且在产品代码中也要处理这些情况。而断言是用于检查代码中的bug,如果在发生异常的时候触发了断言,采取的措施就不仅仅是对错误做出恰当的反应,而是应该修改源码并重新编译。可以把断言理解成一种注释,它说明了这个程序的假定。

    避免把执行的代码放到断言中

    断言的可以在编译器中设置关闭,如果你把一些操作写在断言里,在某些情况下可能编译器会过滤掉这些代码。

        NSAssert([self performAction], @"could't perform);
    

    这样写就很危险,应该这样写:

        Bool performed=[self performAction];
        NSAssert(performed, @"could't perform);
    

    高健壮性的代码,先使用断言再处理错误

    对于要求高健壮性的代码,可能项目非常庞大,超长的开发周期和很多的开发人员,也可能出现断言被触发但是没有被注意到,这时应该也处理一下触发断言时的错误。

    欢迎关注我的微博:@没故事的卓同学

    相关文章

      网友评论

      本文标题:防御式编程:理解断言

      本文链接:https://www.haomeiwen.com/subject/dmxedttx.html